From 942bc875412bc7b5739cc05ac3425299f1058b1b Mon Sep 17 00:00:00 2001 From: Sehong Na Date: Sat, 31 May 2014 13:19:46 +0900 Subject: [PATCH 1/1] Initialize Tizen 2.3 --- .gitignore | 14 + AUTHORS | 4 + CMakeLists.txt | 87 ++ LICENSE | 203 +++ client/CMakeLists.txt | 26 + client/include/weconn.h | 163 +++ client/include/weconn_type.h | 97 ++ client/src/weconnection.c | 858 ++++++++++++ client/weconn.pc.in | 11 + include/conn.h | 32 + include/control.h | 45 + include/dbus.h | 64 + include/driver.h | 44 + include/error.h | 36 + include/events.h | 66 + include/log.h | 41 + include/object.h | 100 ++ include/service.h | 34 + include/technology.h | 62 + include/util.h | 57 + introspection/service.xml | 8 + introspection/technology.xml | 27 + packaging/weconn.spec | 110 ++ plugins/CMakeLists.txt | 16 + plugins/bluetooth.c | 1468 ++++++++++++++++++++ plugins/esap.c | 866 ++++++++++++ plugins/esap.h | 160 +++ plugins/pan.c | 923 ++++++++++++ plugins/plugin.h | 52 + resources/etc/dbus-1/system.d/weconn.conf | 21 + resources/etc/opt/upgrade/500.net.weconn.patch.sh | 6 + resources/mdbus2 | Bin 0 -> 282661 bytes resources/usr/lib/systemd/system/weconn.service | 11 + .../usr/share/appcessory/wearable-connection.xml | 47 + .../usr/share/dbus-1/services/net.weconn.service | 5 + src/control/control.c | 151 ++ src/dbus.c | 122 ++ src/driver.c | 290 ++++ src/error.c | 63 + src/main.c | 95 ++ src/object.c | 582 ++++++++ src/service.c | 56 + src/technology.c | 640 +++++++++ src/util.c | 236 ++++ test/CMakeLists.txt | 22 + test/w_connection_manager_test.c | 414 ++++++ weconn.manifest | 59 + 47 files changed, 8494 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100755 client/CMakeLists.txt create mode 100644 client/include/weconn.h create mode 100644 client/include/weconn_type.h create mode 100644 client/src/weconnection.c create mode 100644 client/weconn.pc.in create mode 100644 include/conn.h create mode 100644 include/control.h create mode 100644 include/dbus.h create mode 100644 include/driver.h create mode 100644 include/error.h create mode 100644 include/events.h create mode 100644 include/log.h create mode 100644 include/object.h create mode 100644 include/service.h create mode 100644 include/technology.h create mode 100644 include/util.h create mode 100644 introspection/service.xml create mode 100644 introspection/technology.xml create mode 100644 packaging/weconn.spec create mode 100755 plugins/CMakeLists.txt create mode 100644 plugins/bluetooth.c create mode 100644 plugins/esap.c create mode 100644 plugins/esap.h create mode 100644 plugins/pan.c create mode 100644 plugins/plugin.h create mode 100644 resources/etc/dbus-1/system.d/weconn.conf create mode 100755 resources/etc/opt/upgrade/500.net.weconn.patch.sh create mode 100755 resources/mdbus2 create mode 100644 resources/usr/lib/systemd/system/weconn.service create mode 100644 resources/usr/share/appcessory/wearable-connection.xml create mode 100644 resources/usr/share/dbus-1/services/net.weconn.service create mode 100644 src/control/control.c create mode 100644 src/dbus.c create mode 100644 src/driver.c create mode 100644 src/error.c create mode 100644 src/main.c create mode 100644 src/object.c create mode 100644 src/service.c create mode 100644 src/technology.c create mode 100644 src/util.c create mode 100644 test/CMakeLists.txt create mode 100644 test/w_connection_manager_test.c create mode 100644 weconn.manifest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..17eecd4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +*.[oa] +*~ +build-stamp +cmake_build_tmp +configure-stamp +.project +.cproject +.settings diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..8834b34 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,4 @@ +Danny Jeongseok Seo +Misun Kim +Sanghoon Cho +Kyoungyoup Park diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..368db4f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,87 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +PROJECT(weconn) + +SET(PREFIX ${CMAKE_INSTALL_PREFIX}) +SET(LIBDIR "${PREFIX}/lib") +SET(INCLUDEDIR "${PREFIX}/include") +SET(PKGCONFIGDIR "${PREFIX}/lib/pkgconfig" CACHE PATH PKGCONFIGDIR) +SET(INTROSPECTION "${CMAKE_SOURCE_DIR}/introspection") +SET(WECONNLIB "${LIBDIR}/${PROJECT_NAME}") +SET(BINDIR "${PREFIX}/sbin") +SET(main_PROGRAM "weconnd") + + +INCLUDE(FindPkgConfig) +pkg_check_modules(pkgs REQUIRED + dlog + vconf + gio-2.0 + glib-2.0 + gio-unix-2.0 + alarm-service + sap-client-stub-api + capi-appfw-application + capi-network-bluetooth + capi-system-info +) + +FOREACH(flag ${pkgs_CFLAGS}) + SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) + +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/include/) + +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}") +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -Wall -Werror -Wextra -fvisibility=hidden -fdata-sections -ffunction-sections -Wl,--gc-sections") +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-parameter -Wno-missing-field-initializers -Wdeclaration-after-statement -Wmissing-declarations") +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-local-typedefs -Wcast-align -Wconversion") +SET(CMAKE_C_FLAGS_DEBUG "-O0 -g") +SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed -Wl,-z,nodelete") + +ADD_DEFINITIONS("-DVERSION=\"${VERSION}\"") +ADD_DEFINITIONS("-DEXPORT_API=__attribute__((visibility(\"default\")))") +ADD_DEFINITIONS("-DWECONN_PLUGIN_PATH=\"${WECONNLIB}\"") + +MESSAGE(${CMAKE_C_FLAGS}) +MESSAGE(${pkgs_LDFLAGS}) + +REMOVE_DEFINITIONS("-DLOG_TAG=\"CAPI_NETWORK_WECONN\"") +ADD_DEFINITIONS("-DLOG_TAG=\"WECONN\"") + + +SET(SRCS + src/dbus.c + src/main.c + src/util.c + src/error.c + src/driver.c + src/object.c + src/service.c + src/technology.c + src/control/control.c +) + +CONFIGURE_FILE(client/include/weconn_type.h include/weconn_type.h COPYONLY) + +ADD_CUSTOM_COMMAND( + WORKING_DIRECTORY + OUTPUT ${CMAKE_BINARY_DIR}/generated-code.c + COMMAND gdbus-codegen --interface-prefix net.weconn. --generate-c-code generated-code --c-namespace weconn --c-generate-object-manager --generate-docbook generated-docs + ${INTROSPECTION}/service.xml + ${INTROSPECTION}/technology.xml + COMMENT "Generating GDBus .c/.h") + +ADD_EXECUTABLE(${main_PROGRAM} ${SRCS} ${CMAKE_BINARY_DIR}/generated-code.c) +TARGET_LINK_LIBRARIES(${main_PROGRAM} ${pkgs_LDFLAGS} "-ldl -L${CMAKE_BINARY_DIR}") + +INSTALL(TARGETS ${main_PROGRAM} DESTINATION ${BINDIR}) + + +# Wearable device connection controller plugins +ADD_SUBDIRECTORY(plugins) + +# Wearable device connection controller API library in TIZEN C API (Development) +ADD_SUBDIRECTORY(client) + +# Wearable connection manager API sample +ADD_SUBDIRECTORY(test) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d8522e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,203 @@ +Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt new file mode 100755 index 0000000..eb7ad84 --- /dev/null +++ b/client/CMakeLists.txt @@ -0,0 +1,26 @@ +SET(CLIENT "weconn") + +REMOVE_DEFINITIONS("-DLOG_TAG=\"WECONN\"") +ADD_DEFINITIONS("-DLOG_TAG=\"CAPI_NETWORK_WECONN\"") + + +CONFIGURE_FILE(../src/util.c src/util.c COPYONLY) + +SET(CLIENT_SRCS + src/util.c + src/weconnection.c +) + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include/) + +ADD_LIBRARY(${CLIENT} SHARED ${CLIENT_SRCS}) +TARGET_LINK_LIBRARIES(${CLIENT} ${pkgs_LDFLAGS}) +SET_TARGET_PROPERTIES(${CLIENT} PROPERTIES VERSION ${VERSION} SOVERSION 0 OUTPUT_NAME ${CLIENT}) + +# pkgconfig file +CONFIGURE_FILE(${CLIENT}.pc.in ${CLIENT}.pc @ONLY) + +# install +INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION include/${CLIENT}) +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/${CLIENT}.pc DESTINATION ${LIBDIR}/pkgconfig) +INSTALL(TARGETS ${CLIENT} DESTINATION ${LIBDIR}) diff --git a/client/include/weconn.h b/client/include/weconn.h new file mode 100644 index 0000000..056b53c --- /dev/null +++ b/client/include/weconn.h @@ -0,0 +1,163 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __WECONN_CLIENT_H__ +#define __WECONN_CLIENT_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "weconn_type.h" + +/** + * @addtogroup CAPI_WEARABLE_DEVICE_CONNECTION_MANAGER_MODULE + * @{ + */ + +/** + * @brief The wearable connection handle for all connection functions. + */ +typedef void *weconn_h; + +/** +* @brief Called after weconn_connect_service() is completed. +* @param[in] result The result +* @param[in] user_data The user data passed from weconn_connect_service() +* @pre weconn_connect_service() will invoke this callback function. +* @see weconn_connect_service() +*/ +typedef void (*weconn_connected_cb) (int result, void *user_data); + +/** + * @brief Creates a handle for managing wearable connections. + * @remarks @a handle must be released with weconn_destroy(). + * @param[out] conn The handle of the wearable connection + * @return On success, 0 is returned. On error, -1 or -errno is returned. + * All the errno numbers specified by POSIX, the ISO C standard. + * @see weconn_destroy() + */ +int weconn_create(weconn_h *conn); + +/** + * @brief Destroys the wearable connection handle. + * @param[in] conn The handle of the wearable connection + * @return On success, 0 is returned. On error, -1 or -errno is returned. + * All the errno numbers specified by POSIX, the ISO C standard. + * @see weconn_create() + */ +int weconn_destroy(weconn_h conn); + +/** + * @brief Gets the wearable connection service state. + * @details The returned state is for the wearable connection service type. + * @param[in] conn The handle of the wearable connection + * @param[in] type The wearable connection service type + * @param[out] state The state of wearable connection service type + * @return On success, 0 is returned. On error, -1 or -errno is returned. + * All the errno numbers specified by POSIX, the ISO C standard. + */ +int weconn_get_service_state(weconn_h conn, + weconn_service_type_e type, weconn_service_state_e *state); + +/** + * @brief Gets the wearable device state. + * @details The returned state is for the connection device type. + * @param[in] conn The handle of the wearable connection + * @param[in] type The wearable connection device type + * @param[out] state The state of wearable connection device type + * @return On success, 0 is returned. On error, -1 or -errno is returned. + * All the errno numbers specified by POSIX, the ISO C standard. + */ +int weconn_get_device_state(weconn_h conn, + weconn_device_type_e type, weconn_device_state_e *state); + +/** + * @brief Connect a service.. + * @param[in] conn The handle of the wearable connection + * @param[in] type The wearable connection service type + * @param[in] callback The callback function to be called. + * This can be NULL if you don't want to get the notification. + * @param[in] user_data The user data passed to the callback function + * @return On success, 0 is returned. On error, -1 or -errno is returned. + * All the errno numbers specified by POSIX, the ISO C standard. + */ +int weconn_connect_service(weconn_h conn, weconn_service_type_e type, + weconn_connected_cb callback, void *user_data); + +/** + * @brief Disconnect a service.. + * @param[in] conn The handle of the wearable connection + * @param[in] type The wearable connection service type + * @param[in] callback The callback function to be called. + * This can be NULL if you don't want to get the notification. + * @param[in] user_data The user data passed to the callback function + * @return On success, 0 is returned. On error, -1 or -errno is returned. + * All the errno numbers specified by POSIX, the ISO C standard. + */ +int weconn_disconnect_service(weconn_h conn, weconn_service_type_e type, + weconn_connected_cb callback, void *user_data); + +/** + * @brief Called when the state of service is changed + * @param[out] state The state of service + * @param[out] user_data The user data passed from the callback + * registration function + * @see weconn_set_service_state_change_cb() + * @see weconn_unset_service_state_change_cb() + */ +typedef void(*weconn_service_state_changed_cb) + (weconn_service_state_e state, void *user_data); + +/** + * @brief Registers the callback called when the state of service is + * changed. + * @param[in] conn The handle of the wearable connection + * @param[in] callback The callback function to be called + * @param[in] type The wearable connection service type + * @param[in] user_data The user data passed from the callback + * registration function + * @return On success, 0 is returned. On error, -1 or -errno is returned. + * All the errno numbers specified by POSIX, the ISO C standard. + */ +int weconn_set_service_state_change_cb(weconn_h conn, + weconn_service_state_changed_cb callback, + weconn_service_type_e type, void *user_data); + +/** + * @brief Unregisters the callback called when the state of service is + * changed. + * @param[in] conn The handle of the wearable connection + * @param[in] type The wearable connection service type + * @return On success, 0 is returned. On error, -1 or -errno is returned. + * All the errno numbers specified by POSIX, the ISO C standard. + */ +int weconn_unset_service_state_change_cb(weconn_h conn, + weconn_service_type_e type); +/** +* @} +*/ + +#ifdef __cplusplus +} +#endif + +#endif /* __WECONN_CLIENT_H__ */ diff --git a/client/include/weconn_type.h b/client/include/weconn_type.h new file mode 100644 index 0000000..7ad8a9b --- /dev/null +++ b/client/include/weconn_type.h @@ -0,0 +1,97 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __WECONN_TYPE_H__ +#define __WECONN_TYPE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup CAPI_WEARABLE_DEVICE_CONNECTION_ENUMERATION_TYPE + * @{ + */ + +/** + * @brief Enumerations of wearable connection service type. + */ +typedef enum +{ + /* Generic connectivity */ + W_SERVICE_TYPE_HOST_TO_WEARABLE_CONNECTIVITY = 0x01, + /* Generic connectivity between host and wearable devices */ + W_SERVICE_TYPE_INTERNET_CONNECTIVITY = 0x02, + /* Generic connectivity into the Internet accessibility */ + + /* Specific technology based connectivity */ + W_SERVICE_TYPE_BT_HFP = 0x11, /* Bluetooth Hands-Free Profile */ + W_SERVICE_TYPE_BT_SPP = 0x12, /* Bluetooth Serial Port Profile */ + W_SERVICE_TYPE_BT_PAN = 0x13, /* Bluetooth Personal Area Network */ + W_SERVICE_TYPE_BT_GATT = 0x14, + /* Bluetooth Generic Attribute Profile */ + W_SERVICE_TYPE_CELLULAR = 0x15, /* Cellular Network */ + W_SERVICE_TYPE_WIFI = 0x16, /* Wi-Fi Network */ + W_SERVICE_TYPE_WIFI_P2P = 0x17, /* Wi-Fi P2P Network */ + W_SERVICE_TYPE_WIFI_ADHOC = 0x18, /* Wi-Fi Ad-hoc Network */ + W_SERVICE_TYPE_ETHERNET = 0x19, /* Cable Network */ +} weconn_service_type_e; + +/** + * @brief Enumerations of wearable connection service state. + */ +typedef enum +{ + W_SERVICE_STATE_DISCONNECTED = 0x01, + W_SERVICE_STATE_CONNECTING = 0x02, + W_SERVICE_STATE_CONNECTED = 0x03, + W_SERVICE_STATE_DISCONNECTING = 0x04, +} weconn_service_state_e; + +/** + * @brief Enumerations of wearable connection device type. + */ +typedef enum +{ + W_DEVICE_TYPE_BT = 0x01, /* Bluetooth */ + W_DEVICE_TYPE_CELLULAR = 0x02, /* Cellular */ + W_DEVICE_TYPE_WIFI = 0x03, /* Wi-Fi */ + W_DEVICE_TYPE_WIFI_P2P = 0x04, /* Wi-Fi P2P */ + W_DEVICE_TYPE_WIFI_ADHOC = 0x05, /* Wi-Fi Ad-hoc */ + W_DEVICE_TYPE_ETHERNET = 0x06, /* Cable */ +} weconn_device_type_e; + +/** + * @brief Enumerations of wearable connection device state. + */ +typedef enum +{ + W_DEVICE_STATE_DISABLED = 0x01, + W_DEVICE_STATE_ENABLED = 0x02, +} weconn_device_state_e; + +/** +* @} +*/ + +#ifdef __cplusplus +} +#endif + +#endif /* __WECONN_TYPE_H__ */ diff --git a/client/src/weconnection.c b/client/src/weconnection.c new file mode 100644 index 0000000..93a1d95 --- /dev/null +++ b/client/src/weconnection.c @@ -0,0 +1,858 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include + +#include "log.h" +#include "dbus.h" +#include "util.h" +#include "weconn.h" + +struct weconn_connection { + GDBusConnection *connection; + GCancellable *cancellable; + + weconn_service_type_e connect_service_type; + weconn_connected_cb connected_callback; + void *connect_user_data; + + weconn_service_type_e disconnect_service_type; + weconn_connected_cb disconnected_callback; + void *disconnect_user_data; + + int ref_count; + bool lazy_destroy; +}; + +struct weconn_service_handle { + weconn_h conn; + weconn_service_state_changed_cb callback; + void *user_data; +}; + +static __thread void *handle_libweconn = NULL; +static __thread GHashTable *weconn_handle_hash = NULL; + +static __thread GHashTable *wc_internet_service_con_changed_cb_hash = NULL; +static __thread GHashTable *wc_wearable_service_con_changed_cb_hash = NULL; + +static __thread guint weconn_conn_subscribe_id_weconn_state = 0; + +static bool __weconn_check_handle_validity(weconn_h conn) +{ + struct weconn_connection *h; + + if (!conn || !weconn_handle_hash) + return false; + + h = g_hash_table_lookup(weconn_handle_hash, conn); + if (!h) + return false; + + if (h->lazy_destroy) + return false; + + return true; +} + +static GDBusConnection *__weconn_call_ref(struct weconn_connection *h) +{ + if (h->lazy_destroy) + return NULL; + + g_object_ref(h->connection); + + __sync_fetch_and_add(&h->ref_count, 1); + + return h->connection; +} + +static void __weconn_call_unref(struct weconn_connection *h) +{ + __sync_synchronize(); + if (h->ref_count < 1) + return; + + g_object_unref(h->connection); + + if (__sync_sub_and_fetch(&h->ref_count, 1) < 1 && h->lazy_destroy) + g_hash_table_remove(weconn_handle_hash, h); +} + +static const char *__weconn_get_technology_path4service( + weconn_service_type_e service_type) +{ + switch (service_type) { + case W_SERVICE_TYPE_BT_HFP: + case W_SERVICE_TYPE_BT_SPP: + case W_SERVICE_TYPE_BT_PAN: + case W_SERVICE_TYPE_BT_GATT: + return WECONN_TECHNOLOGY_BLUETOOTH_PATH; + case W_SERVICE_TYPE_CELLULAR: + return WECONN_TECHNOLOGY_CELLULAR_PATH; + case W_SERVICE_TYPE_WIFI: + return WECONN_TECHNOLOGY_WIFI_PATH; + case W_SERVICE_TYPE_WIFI_P2P: + return WECONN_TECHNOLOGY_WIFI_P2P_PATH; + case W_SERVICE_TYPE_WIFI_ADHOC: + return WECONN_TECHNOLOGY_WIFI_ADHOC_PATH; + case W_SERVICE_TYPE_ETHERNET: + return WECONN_TECHNOLOGY_ETHERNET_PATH; + default: + break; + } + + return NULL; +} + +static const char *__weconn_get_technology_path4device( + weconn_device_type_e device_type) +{ + switch (device_type) { + case W_DEVICE_TYPE_BT: + return WECONN_TECHNOLOGY_BLUETOOTH_PATH; + case W_DEVICE_TYPE_CELLULAR: + return WECONN_TECHNOLOGY_CELLULAR_PATH; + case W_DEVICE_TYPE_WIFI: + return WECONN_TECHNOLOGY_WIFI_PATH; + case W_DEVICE_TYPE_WIFI_P2P: + return WECONN_TECHNOLOGY_WIFI_P2P_PATH; + case W_DEVICE_TYPE_WIFI_ADHOC: + return WECONN_TECHNOLOGY_WIFI_ADHOC_PATH; + case W_DEVICE_TYPE_ETHERNET: + return WECONN_TECHNOLOGY_ETHERNET_PATH; + } + + return NULL; +} + +static GDBusConnection *__weconn_setup_dbus(void) +{ + GError *error = NULL; + GDBusConnection *connection = NULL; + + connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (!connection) { + ERR("Failed to get system bus %s", error->message); + g_error_free(error); + return NULL; + } + + return connection; +} + +static void __weconn_destroy_handle(gpointer data) +{ + g_free(data); + + if (handle_libweconn && g_hash_table_size(weconn_handle_hash) < 1) { + dlclose(handle_libweconn); + handle_libweconn = NULL; + } +} + +static void __weconn_bt_connection_response(struct weconn_connection *conn, + int result, gpointer user_data) +{ + struct weconn_connection *h = conn; + if (!h) + return; + + h->connected_callback(result, h->connect_user_data); + h->connected_callback = NULL; + h->connect_service_type = 0; +} + +static void __weconn_bt_disconnection_response(struct weconn_connection *conn, + int result, gpointer user_data) +{ + struct weconn_connection *h = conn; + if (!h) + return; + + h->disconnected_callback(result, h->disconnect_user_data); + h->disconnected_callback = NULL; + h->disconnect_service_type = 0; +} + +static void __weconn_connection_response(gpointer key, gpointer value, + gpointer user_data) +{ + struct weconn_connection *h = NULL; + int *result; + + + h = (struct weconn_connection *)key; + if (!h) + return; + + result = (int *)user_data; + + if (h->connected_callback) { + switch (h->connect_service_type) { + case W_SERVICE_TYPE_BT_HFP: + case W_SERVICE_TYPE_BT_SPP: + case W_SERVICE_TYPE_BT_PAN: + case W_SERVICE_TYPE_BT_GATT: + __weconn_bt_connection_response(h, *result, user_data); + break; + case W_SERVICE_TYPE_CELLULAR: + break; + case W_SERVICE_TYPE_WIFI: + break; + case W_SERVICE_TYPE_WIFI_P2P: + break; + case W_SERVICE_TYPE_WIFI_ADHOC: + break; + case W_SERVICE_TYPE_ETHERNET: + break; + default: + break; + } + } +} + +static void __weconn_disconnection_response(gpointer key, gpointer value, + gpointer user_data) +{ + struct weconn_connection *h = NULL; + int *result; + + + h = (struct weconn_connection *)key; + if (!h) + return; + + result = (int *)user_data; + + if (h->disconnected_callback) { + switch (h->disconnect_service_type) { + case W_SERVICE_TYPE_BT_HFP: + case W_SERVICE_TYPE_BT_SPP: + case W_SERVICE_TYPE_BT_PAN: + case W_SERVICE_TYPE_BT_GATT: + __weconn_bt_disconnection_response(h, *result, user_data); + break; + case W_SERVICE_TYPE_CELLULAR: + break; + case W_SERVICE_TYPE_WIFI: + break; + case W_SERVICE_TYPE_WIFI_P2P: + break; + case W_SERVICE_TYPE_WIFI_ADHOC: + break; + case W_SERVICE_TYPE_ETHERNET: + break; + default: + break; + } + } +} + +static void __weconn_pan_service_state_changed(gpointer key, gpointer value, + gpointer user_data) +{ + weconn_h conn; + struct weconn_service_handle *h_service = NULL; + int *state = NULL; + + conn = (weconn_h)key; + if (!conn) + return; + + h_service = (struct weconn_service_handle *)value; + if (!h_service) + return; + + state = (int *)user_data; + if (!state) + return; + + if (h_service->callback) { + h_service->callback(*state, h_service->user_data); + } +} + +static void __weconn_technology_signal_filter(GDBusConnection *conn, + const gchar *name, const gchar *path, const gchar *interface, + const gchar *sig, GVariant *param, gpointer user_data) +{ + const char *service; + int result; + + if (g_strcmp0(sig, WECONN_TECHNOLOGY_SIGNAL_CONNECTION_RESULT) == 0) { + g_variant_get(param, "(si)", &service, &result); + + if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_HFP), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_SPP), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_PAN), service) == 0) { + g_hash_table_foreach(weconn_handle_hash, + __weconn_connection_response, &result); + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_GATT), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_CELLULAR), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_WIFI), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_WIFI_P2P), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_WIFI_ADHOC), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_ETHERNET), service) == 0) { + } + } else if (g_strcmp0(sig, WECONN_TECHNOLOGY_SIGNAL_DISCONNECTION_RESULT) == 0) { + g_variant_get(param, "(si)", &service, &result); + + if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_HFP), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_SPP), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_PAN), service) == 0) { + g_hash_table_foreach(weconn_handle_hash, + __weconn_disconnection_response, &result); + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_GATT), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_CELLULAR), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_WIFI), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_WIFI_P2P), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_WIFI_ADHOC), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_ETHERNET), service) == 0) { + } + } else if (g_strcmp0(sig, WECONN_TECHNOLOGY_SIGNAL_SERVICE_STATE_CHANGED) == 0) { + g_variant_get(param, "(si)", &service, &result); + + if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_HFP), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_SPP), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_PAN), service) == 0) { + g_hash_table_foreach(wc_internet_service_con_changed_cb_hash, + __weconn_pan_service_state_changed, &result); + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_GATT), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_CELLULAR), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_WIFI), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_WIFI_P2P), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_WIFI_ADHOC), service) == 0) { + } else if (g_strcmp0(XSTR(W_SERVICE_TYPE_ETHERNET), service) == 0) { + } + } else { + ERR("No handle signal(%s)", sig); + } +} + +static bool __weconn_register_signal(void) +{ + GDBusConnection *connection = NULL; + guint id; + + connection = __weconn_setup_dbus(); + if (connection == NULL) + return false; + + id = g_dbus_connection_signal_subscribe( + connection, + WECONN_SERVICE_DBUS, + WECONN_TECHNOLOGY_INTERFACE, + NULL, + NULL, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + __weconn_technology_signal_filter, + NULL, + NULL); + if (id == 0) { + ERR("Failed register signals (%d)", id); + return false; + } + + weconn_conn_subscribe_id_weconn_state = id; + + return true; +} + +static void __weconn_deregister_signal(void) +{ + GDBusConnection *connection = NULL; + + connection = __weconn_setup_dbus(); + if (connection == NULL) + return; + + g_dbus_connection_signal_unsubscribe(connection, + weconn_conn_subscribe_id_weconn_state); +} + +EXPORT_API int weconn_create(weconn_h *conn) +{ + struct weconn_connection *h; + + if (!conn || __weconn_check_handle_validity(*conn)) + return -EINVAL; + + if (!weconn_handle_hash) { + g_type_init(); + + weconn_handle_hash = g_hash_table_new_full(g_direct_hash, + g_direct_equal, NULL, __weconn_destroy_handle); + } + + if (!handle_libweconn) + handle_libweconn = dlopen("/usr/lib/libweconn.so", RTLD_LAZY); + + if (!wc_internet_service_con_changed_cb_hash) + wc_internet_service_con_changed_cb_hash = g_hash_table_new_full( + g_str_hash, g_str_equal, NULL, g_free); + + if (!wc_wearable_service_con_changed_cb_hash) + wc_wearable_service_con_changed_cb_hash = g_hash_table_new_full( + g_str_hash, g_str_equal, NULL, g_free); + + h = g_try_new0(struct weconn_connection, 1); + if (!h) + return -ENOMEM; + + h->connection = __weconn_setup_dbus(); + if (!h->connection) { + g_free(h); + return -EIO; + } + + h->cancellable = g_cancellable_new(); + *conn = (weconn_h)h; + + g_hash_table_replace(weconn_handle_hash, *conn, *conn); + + __weconn_register_signal(); + + return 0; +} + +EXPORT_API int weconn_destroy(weconn_h conn) +{ + struct weconn_connection *h; + + if (!__weconn_check_handle_validity(conn)) + return -EINVAL; + + h = (struct weconn_connection *)conn; + g_object_unref(h->connection); + g_cancellable_cancel(h->cancellable); + + if (wc_internet_service_con_changed_cb_hash) { + g_hash_table_destroy(wc_internet_service_con_changed_cb_hash); + wc_internet_service_con_changed_cb_hash = NULL; + } + + if (wc_wearable_service_con_changed_cb_hash) { + g_hash_table_destroy(wc_wearable_service_con_changed_cb_hash); + wc_wearable_service_con_changed_cb_hash = NULL; + } + + __sync_synchronize(); + if (h->ref_count > 0) + h->lazy_destroy = true; + else + g_hash_table_remove(weconn_handle_hash, conn); + + __weconn_deregister_signal(); + + return 0; +} + +EXPORT_API int weconn_get_service_state(weconn_h conn, + weconn_service_type_e type, weconn_service_state_e *state) +{ + GVariant *reply, *inner; + GError *error = NULL; + struct weconn_connection *h = (struct weconn_connection *)conn; + const char *service_type = NULL; + const char *value = NULL; + weconn_service_state_e value_e; + const char *technology_path = NULL; + + if (!state || !__weconn_check_handle_validity(conn)) + return -EINVAL; + + service_type = wc_service_type_enum2string(type); + if (!service_type) { + + /* TODO: please make more general to care all of technologies */ + + if (type == W_SERVICE_TYPE_HOST_TO_WEARABLE_CONNECTIVITY) + type = W_SERVICE_TYPE_BT_SPP; + else if (type == W_SERVICE_TYPE_INTERNET_CONNECTIVITY) + type = W_SERVICE_TYPE_BT_PAN; + else + return -EINVAL; + + service_type = wc_service_type_enum2string(type); + } + + technology_path = __weconn_get_technology_path4service(type); + if (!technology_path) + return -EINVAL; + + reply = g_dbus_connection_call_sync(__weconn_call_ref(h), + WECONN_SERVICE_DBUS, + technology_path, + WECONN_TECHNOLOGY_INTERFACE, + "GetProperty", + g_variant_new("(s)", service_type), + NULL, + G_DBUS_CALL_FLAGS_NONE, + DBUS_REPLY_TIMEOUT, + h->cancellable, + &error); + + __weconn_call_unref(h); + + if (error) { + ERR("Failed to request (%s)", error->message); + g_error_free(error); + + if (reply) + g_variant_unref(reply); + + return -EIO; + } + + if (!reply) { + ERR("Failed to request"); + return -EIO; + } + + g_variant_get(reply, "(v)", &inner); + value = g_variant_get_string(inner, NULL); + g_variant_unref(inner); + + value_e = wc_service_state_string2enum(value); + g_variant_unref(reply); + + if (value_e) + *state = value_e; + else + return -EIO; + + return 0; +} + +EXPORT_API int weconn_get_device_state(weconn_h conn, + weconn_device_type_e type, weconn_device_state_e *state) +{ + GVariant *reply, *inner; + GError *error = NULL; + struct weconn_connection *h = (struct weconn_connection *)conn; + const char *device_type = NULL; + const char *value = NULL; + weconn_device_state_e value_e; + const char *technology_path = NULL; + + if (!state || !__weconn_check_handle_validity(conn)) + return -EINVAL; + + device_type = wc_device_type_enum2string(type); + if (!device_type) + return -EINVAL; + + technology_path = __weconn_get_technology_path4device(type); + if (!technology_path) + return -EINVAL; + + reply = g_dbus_connection_call_sync(__weconn_call_ref(h), + WECONN_SERVICE_DBUS, + technology_path, + WECONN_TECHNOLOGY_INTERFACE, + "GetProperty", + g_variant_new("(s)", device_type), + NULL, + G_DBUS_CALL_FLAGS_NONE, + DBUS_REPLY_TIMEOUT, + h->cancellable, + &error); + + __weconn_call_unref(h); + + if (error) { + ERR("Failed to request (%s)", error->message); + g_error_free(error); + + if (reply) + g_variant_unref(reply); + + return -EIO; + } + + if (!reply) { + ERR("Failed to request"); + return -EIO; + } + + g_variant_get(reply, "(v)", &inner); + value = g_variant_get_string(inner, NULL); + g_variant_unref(inner); + + value_e = wc_device_state_string2enum(value); + g_variant_unref(reply); + + if (value_e) + *state = value_e; + else + return -EIO; + + return 0; +} + +EXPORT_API int weconn_connect_service(weconn_h conn, + weconn_service_type_e type, weconn_connected_cb callback, + void *user_data) +{ + int err = -EIO; + GVariant *reply; + GError *error = NULL; + struct weconn_connection *h = (struct weconn_connection *)conn; + const char *service_type = NULL; + const char *technology_path = NULL; + + if (!__weconn_check_handle_validity(conn)) + return -EINVAL; + + if (h->connected_callback) { + DBG("connection is inprogress"); + return -EINPROGRESS; + } + + service_type = wc_service_type_enum2string(type); + if (!service_type) { + /* TODO: please make more general to care all of technologies */ + + if (type == W_SERVICE_TYPE_HOST_TO_WEARABLE_CONNECTIVITY) + type = W_SERVICE_TYPE_BT_SPP; + else if (type == W_SERVICE_TYPE_INTERNET_CONNECTIVITY) + type = W_SERVICE_TYPE_BT_PAN; + else + return -EINVAL; + + service_type = wc_service_type_enum2string(type); + } + + technology_path = __weconn_get_technology_path4service(type); + if (!technology_path) + return -EINVAL; + + reply = g_dbus_connection_call_sync(__weconn_call_ref(h), + WECONN_SERVICE_DBUS, + technology_path, + WECONN_TECHNOLOGY_INTERFACE, + "Connect", + g_variant_new("(s)", service_type), + NULL, + G_DBUS_CALL_FLAGS_NONE, + DBUS_REPLY_TIMEOUT, + h->cancellable, + &error); + + __weconn_call_unref(h); + + if (error) { + ERR("Failed to request (%s)", error->message); + if (g_strrstr(error->message, "AlreadyConnected")) + err = -EISCONN; + else if (g_strrstr(error->message, "NotConnected")) + err = -ENOTCONN; + else if (g_strrstr(error->message, "InProgress")) + err = -EINPROGRESS; + else if (g_strrstr(error->message, "LostConnection")) + err = -EIO; + + g_error_free(error); + + if (reply) + g_variant_unref(reply); + + return err; + } + + if (!reply) { + ERR("Failed to request"); + return err; + } + + g_variant_unref(reply); + + if (callback) { + h->connect_service_type = type; + h->connected_callback = callback; + if (user_data) + h->connect_user_data = user_data; + } + + return 0; +} + +EXPORT_API int weconn_disconnect_service(weconn_h conn, + weconn_service_type_e type, weconn_connected_cb callback, + void *user_data) +{ + int err = -EIO; + GVariant *reply; + GError *error = NULL; + struct weconn_connection *h = (struct weconn_connection *)conn; + const char *service_type = NULL; + const char *technology_path = NULL; + + if (!__weconn_check_handle_validity(conn)) + return -EINVAL; + + if (h->disconnected_callback) { + DBG("disconnection is inprogress"); + return -EINPROGRESS; + } + + service_type = wc_service_type_enum2string(type); + if (!service_type) { + + /* TODO: please make more general to care all of technologies */ + + if (type == W_SERVICE_TYPE_HOST_TO_WEARABLE_CONNECTIVITY) + type = W_SERVICE_TYPE_BT_SPP; + else if (type == W_SERVICE_TYPE_INTERNET_CONNECTIVITY) + type = W_SERVICE_TYPE_BT_PAN; + else + return -EINVAL; + + service_type = wc_service_type_enum2string(type); + } + + technology_path = __weconn_get_technology_path4service(type); + if (!technology_path) + return -EINVAL; + + reply = g_dbus_connection_call_sync(__weconn_call_ref(h), + WECONN_SERVICE_DBUS, + technology_path, + WECONN_TECHNOLOGY_INTERFACE, + "Disconnect", + g_variant_new("(s)", service_type), + NULL, + G_DBUS_CALL_FLAGS_NONE, + DBUS_REPLY_TIMEOUT, + h->cancellable, + &error); + + __weconn_call_unref(h); + + if (error) { + ERR("Failed to request (%s)", error->message); + if (g_strrstr(error->message, "AlreadyConnected")) + err = -EISCONN; + else if (g_strrstr(error->message, "NotConnected")) + err = -ENOTCONN; + + g_error_free(error); + + if (reply) + g_variant_unref(reply); + + return err; + } + + if (!reply) { + ERR("Failed to request"); + return err; + } + + g_variant_unref(reply); + + if (callback) { + h->disconnect_service_type = type; + h->disconnected_callback = callback; + if (user_data) + h->disconnect_user_data = user_data; + } + + return 0; +} + +EXPORT_API int weconn_set_service_state_change_cb(weconn_h conn, + weconn_service_state_changed_cb callback, + weconn_service_type_e type, void *user_data) +{ + struct weconn_service_handle *h_service = NULL; + + if (!__weconn_check_handle_validity(conn) || callback == NULL) + return -EINVAL; + + h_service = g_try_malloc0(sizeof(struct weconn_service_handle)); + if (h_service == NULL) + return -EINVAL; + + h_service->conn = conn; + h_service->callback = callback; + h_service->user_data = user_data; + + switch (type) { + case W_SERVICE_TYPE_HOST_TO_WEARABLE_CONNECTIVITY: + g_hash_table_replace(wc_wearable_service_con_changed_cb_hash, + conn, h_service); + break; + case W_SERVICE_TYPE_INTERNET_CONNECTIVITY: + g_hash_table_replace(wc_internet_service_con_changed_cb_hash, + conn, h_service); + break; + case W_SERVICE_TYPE_BT_PAN: + case W_SERVICE_TYPE_BT_HFP: + case W_SERVICE_TYPE_BT_SPP: + case W_SERVICE_TYPE_BT_GATT: + case W_SERVICE_TYPE_CELLULAR: + case W_SERVICE_TYPE_WIFI: + case W_SERVICE_TYPE_WIFI_P2P: + case W_SERVICE_TYPE_WIFI_ADHOC: + case W_SERVICE_TYPE_ETHERNET: + default: + ERR("Not support service type (%d)", type); + g_free(h_service); + break; + } + + return 0; +} + +EXPORT_API int weconn_unset_service_state_change_cb(weconn_h conn, + weconn_service_type_e type) +{ + if (!__weconn_check_handle_validity(conn)) + return -EINVAL; + + switch (type) { + case W_SERVICE_TYPE_HOST_TO_WEARABLE_CONNECTIVITY: + g_hash_table_remove(wc_wearable_service_con_changed_cb_hash, + conn); + break; + case W_SERVICE_TYPE_INTERNET_CONNECTIVITY: + g_hash_table_remove(wc_internet_service_con_changed_cb_hash, + conn); + break; + case W_SERVICE_TYPE_BT_PAN: + case W_SERVICE_TYPE_BT_HFP: + case W_SERVICE_TYPE_BT_SPP: + case W_SERVICE_TYPE_BT_GATT: + case W_SERVICE_TYPE_CELLULAR: + case W_SERVICE_TYPE_WIFI: + case W_SERVICE_TYPE_WIFI_P2P: + case W_SERVICE_TYPE_WIFI_ADHOC: + case W_SERVICE_TYPE_ETHERNET: + default: + break; + } + + return 0; +} diff --git a/client/weconn.pc.in b/client/weconn.pc.in new file mode 100644 index 0000000..b51db8a --- /dev/null +++ b/client/weconn.pc.in @@ -0,0 +1,11 @@ +prefix=@PREFIX@ +exec_prefix=@PREFIX@ +libdir=@LIBDIR@ +includedir=@INCLUDEDIR@/weconn + +Name: weconn +Description: Wearable device connection controller API library in TIZEN C API (Development) +Requires: dlog glib-2.0 gobject-2.0 +Version: @VERSION@ +Libs: -L${libdir} -lweconn +Cflags: -I${includedir} diff --git a/include/conn.h b/include/conn.h new file mode 100644 index 0000000..3765fa9 --- /dev/null +++ b/include/conn.h @@ -0,0 +1,32 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __WECONN_H__ +#define __WECONN_H__ + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* __WECONN_H__ */ diff --git a/include/control.h b/include/control.h new file mode 100644 index 0000000..11e4d5c --- /dev/null +++ b/include/control.h @@ -0,0 +1,45 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __WECONN_CONTROL_H__ +#define __WECONN_CONTROL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "log.h" +#include "events.h" +#include "object.h" + +#define PROP_CONTROL_TYPE_HOST 0x00 +#define PROP_CONTROL_TYPE_WEARABLE 0x01 + +int wc_control_add_bearer(WcObject * wo_bearer); +WcObject *wc_control_get_object(void); +int wc_control_get_device_type(void); + +int control_init(void); +void control_cleanup(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __WECONN_CONTROL_H__ */ diff --git a/include/dbus.h b/include/dbus.h new file mode 100644 index 0000000..ba7df57 --- /dev/null +++ b/include/dbus.h @@ -0,0 +1,64 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __WECONN_DBUS_H__ +#define __WECONN_DBUS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "conn.h" + +#define DBUS_REPLY_TIMEOUT (120 * 1000) + +#define WECONN_SERVICE_DBUS "net.weconn" +#define WECONN_PATH_DBUS "/net/weconn" + +#define WECONN_SERVICE_INTERFACE WECONN_SERVICE_DBUS ".Service" +#define WECONN_TECHNOLOGY_INTERFACE WECONN_SERVICE_DBUS ".Technology" + +#define WECONN_SERVICE_PATH WECONN_PATH_DBUS "/service" +#define WECONN_TECHNOLOGY_PATH WECONN_PATH_DBUS "/technology" +#define WECONN_TECHNOLOGY_BLUETOOTH_PATH WECONN_TECHNOLOGY_PATH "/bluetooth" +#define WECONN_TECHNOLOGY_CELLULAR_PATH WECONN_TECHNOLOGY_PATH "/cellular" +#define WECONN_TECHNOLOGY_WIFI_PATH WECONN_TECHNOLOGY_PATH "/wifi" +#define WECONN_TECHNOLOGY_WIFI_P2P_PATH WECONN_TECHNOLOGY_PATH "/p2p" +#define WECONN_TECHNOLOGY_WIFI_ADHOC_PATH WECONN_TECHNOLOGY_PATH "/adhoc" +#define WECONN_TECHNOLOGY_ETHERNET_PATH WECONN_TECHNOLOGY_PATH "/ethernet" + +#define WECONN_TECHNOLOGY_SIGNAL_CONNECTION_RESULT "SignalConnectionResult" +#define WECONN_TECHNOLOGY_SIGNAL_DISCONNECTION_RESULT "SignalDisconnectionResult" +#define WECONN_TECHNOLOGY_SIGNAL_SERVICE_STATE_CHANGED "SignalServiceStateChanged" + +int dbus_init(GBusType bus_type, const char *bus_name, const char *obj_path, + void (*__init_cb)(void)); +void dbus_cleanup(void); + +GDBusConnection *dbus_get_connection(void); + +const char *dbus_name2path(const char *name); + +#ifdef __cplusplus +} +#endif + +#endif /* __WECONN_DBUS_H__ */ diff --git a/include/driver.h b/include/driver.h new file mode 100644 index 0000000..c0e5da9 --- /dev/null +++ b/include/driver.h @@ -0,0 +1,44 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __WECONN_DRIVER_H__ +#define __WECONN_DRIVER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "object.h" + +int wc_bearer_driver_init(WcObject *wo, const char *desc_name); +int wc_bearer_driver_activate(WcObject *wo, const char *desc_name); +int wc_bearer_driver_deactivate(WcObject *wo, const char *desc_name); +int wc_bearer_driver_connect(WcObject *wo, const char *address, + const char *desc_name); +int wc_bearer_driver_disconnect(WcObject *wo, const char *desc_name); +void wc_bearer_driver_exit(WcObject *wo, const char *desc_name); + +int driver_init(const char *driver_path); +void driver_cleanup(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __WECONN_DRIVER_H__ */ diff --git a/include/error.h b/include/error.h new file mode 100644 index 0000000..09756b3 --- /dev/null +++ b/include/error.h @@ -0,0 +1,36 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __WECONN_ERROR_H__ +#define __WECONN_ERROR_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +void wc_error_add(const gchar *msg, ...); +void wc_error_return(GDBusMethodInvocation *invocation); + +#ifdef __cplusplus +} +#endif + +#endif /* __WECONN_ERROR_H__ */ diff --git a/include/events.h b/include/events.h new file mode 100644 index 0000000..786c3fc --- /dev/null +++ b/include/events.h @@ -0,0 +1,66 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __WECONN_EVENTSH__ +#define __WECONN_EVENTSH__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Generic property changed */ +#define WC_OBJECT_EVENT_PROPERTY_CHANGED "PropertiesChanged" + +/* Bearer events */ +#define WC_BEARER_EVENT_BT_CONNECTED_SPP "BtConnectedSpp" +#define WC_BEARER_EVENT_BT_CONNECTED_GATT "BtConnectedGatt" +#define WC_BEARER_EVENT_BT_CONNECTED_HFP "BtConnectedHfp" +#define WC_BEARER_EVENT_BT_CONNECTED_PAN "BtConnectedPan" + +/* Technology events */ + +/* Service events */ + +/* Bearer (plug-in) events */ +#define WC_OBJECT_EVENT_BEARER_ENABLED "BearerEnabled" +#define WC_OBJECT_EVENT_BEARER_DISABLED "BearerDisabled" + +/* eSAP events */ +/* For example, to send data uses WC_OBJECT_EVENT_SAP_SEND_DATA + * wc_object_emit_callback(wo, WC_OBJECT_EVENT_SAP_SEND_DATA, data); + * to receive data uses + * WC_OBJECT_EVENT_SAP_RECEIVE_DATA[main_cmd][sub_cmd][response_type] + * wc_object_add_callback(wo, + * WC_OBJECT_EVENT_SAP_RECEIVE_DATA[main_cmd][sub_cmd][response_type], + * user_data); + */ +#define WC_OBJECT_EVENT_SAP_SEND_DATA "eSAP-send" +#define WC_OBJECT_EVENT_SAP_RECEIVE_DATA "eSAP-receive" + + +/* TODO: to plug-in developer, + * develop each plug-in APIs and define each event for those APIs. + */ +#define WC_OBJECT_EVENT_SAP_BCMSERVICE_STATUS "bcmservice_status" + +#ifdef __cplusplus +} +#endif + +#endif /* __WECONN_EVENTSH__ */ diff --git a/include/log.h b/include/log.h new file mode 100644 index 0000000..04c2f7a --- /dev/null +++ b/include/log.h @@ -0,0 +1,41 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __WECONN_LOG_H__ +#define __WECONN_LOG_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define ERR(fmt, args...) do { LOGE(fmt, ## args); } while(0) +#define WARN(fmt, args...) do { LOGW(fmt, ## args); } while(0) +#define DBG(fmt, args...) do { LOGI(fmt, ## args); } while(0) + +#define SECURE_ERR(fmt, args...) do { SECURE_LOGE(fmt, ## args); } while(0) +#define SECURE_WARN(fmt, args...) do { SECURE_LOGW(fmt, ## args); } while(0) +#define SECURE_DBG(fmt, args...) do { SECURE_LOGI(fmt, ## args); } while(0) + +#ifdef __cplusplus +} +#endif + +#endif /* __WECONN_LOG_H__ */ diff --git a/include/object.h b/include/object.h new file mode 100644 index 0000000..b90658d --- /dev/null +++ b/include/object.h @@ -0,0 +1,100 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __WECONN_OBJECT_H__ +#define __WECONN_OBJECT_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "log.h" + +#define WC_OBJECT_TYPE_DEFAULT 0xFF000000 +#define WC_OBJECT_TYPE_BEARER_DRIVER (WC_OBJECT_TYPE_DEFAULT | 0x00010000) +#define WC_OBJECT_TYPE_SERVICE (WC_OBJECT_TYPE_DEFAULT | 0x00020000) +#define WC_OBJECT_TYPE_TECHNOLOGY (WC_OBJECT_TYPE_DEFAULT | 0x00030000) +#define WC_OBJECT_TYPE_CONTROL (WC_OBJECT_TYPE_DEFAULT | 0x00040000) + +#define WC_OBJECT_CHECK(o,t) \ + if (!o) { WARN("WcObject is NULL"); return; } \ + if (wc_object_get_type(o) != t) \ + { WARN("type(0x%x != 0x%x) mismatch", wc_object_get_type(o), t); return; } +#define WC_OBJECT_CHECK_RETURN(o,t,r) \ + if (!o) { WARN("WcObject is NULL"); return r; } \ + if (wc_object_get_type(o) != t) \ + { WARN("type(0x%x != 0x%x) mismatch", wc_object_get_type(o), t); return r; } + +#define WC_OBJECT_KEY_FIND(keys, k) \ + g_slist_find_custom((keys), (k), (GCompareFunc)g_strcmp0) + +typedef struct wc_object WcObject; +typedef gboolean (*WcObjectCallback)(WcObject *wo, const void *event_info, + void *user_data); + +WcObject *wc_object_new(const char *name, unsigned int type); +void wc_object_free(WcObject *wo); +WcObject *wc_object_find(const char *name); + +const char *wc_object_get_name(WcObject *wo); +const char *wc_object_peek_name(WcObject *wo); +unsigned int wc_object_get_type(WcObject *wo); + +int wc_object_append_element(WcObject *wo, + const char *element_name, void *element); +void *wc_object_get_element(WcObject *wo, const char *element_name); + +typedef void (*wc_object_foreach_get_elements_cb)(WcObject *wo, + const char *element_name, void *element, void *user_data); +int wc_object_foreach_get_elements(WcObject *wo, + wc_object_foreach_get_elements_cb callback, void *user_data); + +int wc_object_set_dbus_interface(WcObject *wo, GDBusInterfaceSkeleton *di); +GDBusInterfaceSkeleton *wc_object_get_dbus_interface(WcObject *wo); + +int wc_object_export(WcObject *wo, const char *path); +int wc_object_unexport(WcObject *wo); + +int wc_object_add_callback(WcObject *wo, const char *event, + WcObjectCallback callback, void *user_data); +int wc_object_del_callback(WcObject *wo, const char *event, + WcObjectCallback callback); +int wc_object_emit_callback(WcObject *wo, const char *event, + const void *event_info); + +#define wc_object_set_property(co, ...) \ + wc_object_set_property_full(co, __VA_ARGS__, NULL, NULL) + +int wc_object_set_property_full(WcObject *wo, const char *first_property, ...); +char *wc_object_get_property(WcObject *wo, const char *key); + +const char *wc_object_peek_property(WcObject *wo, const char *key); +GHashTable *wc_object_peek_property_hash(WcObject *wo); + +void wc_object_set_timeout_connecting(WcObject *wo, + guint timeout, const char *service); +guint wc_object_get_timeout_connecting(WcObject *wo); +char *wc_object_get_connecting_service(WcObject *wo); +void wc_object_set_timeout_disconnecting(WcObject *wo, + guint timeout, const char *service); +guint wc_object_get_timeout_disconnecting(WcObject *wo); +char *wc_object_get_disconnecting_service(WcObject *wo); +#endif /* __WECONN_OBJECT_H__ */ diff --git a/include/service.h b/include/service.h new file mode 100644 index 0000000..fc8865e --- /dev/null +++ b/include/service.h @@ -0,0 +1,34 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __WECONN_SERVICE_H__ +#define __WECONN_SERVICE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +int service_init(void); +void service_cleanup(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __WECONN_SERVICE_H__ */ diff --git a/include/technology.h b/include/technology.h new file mode 100644 index 0000000..f07cad2 --- /dev/null +++ b/include/technology.h @@ -0,0 +1,62 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __WECONN_TECHNOLOGY_H__ +#define __WECONN_TECHNOLOGY_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define WC_TECHNOLOGY_BLUETOOTH "bluetooth" +#define WC_TECHNOLOGY_CELLULAR "cellular" +#define WC_TECHNOLOGY_WIFI "Wi-Fi" +#define WC_TECHNOLOGY_ETHERNET "ethernet" + + +#define CONNMAN_SERVICE "net.connman" +#define CONNMAN_SERVICE_INTERFACE CONNMAN_SERVICE ".Service" +#define CONNMAN_SIGNAL_PROPERTY_CHANGED "PropertyChanged" + +/** + * @brief Enumerations of wearable connection service state. + */ +typedef enum +{ + WC_SERVICE_STATE_IDLE = 0x00, + WC_SERVICE_STATE_FAILURE = 0x01, + WC_SERVICE_STATE_DISCONNECTED = 0x02, + WC_SERVICE_STATE_ASSOCIATION = 0x03, + WC_SERVICE_STATE_CONFIGURATION = 0x04, + WC_SERVICE_STATE_READY = 0x05, + WC_SERVICE_STATE_ONLINE = 0x06, +} wc_technology_service_state_e; + + +int wc_technology_add_bearer(WcObject *wo); +WcObject *wc_technology_get_bearer(const char *technology); + +int technology_init(void); +void technology_cleanup(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __WECONN_TECHNOLOGY_H__ */ diff --git a/include/util.h b/include/util.h new file mode 100644 index 0000000..f3ff540 --- /dev/null +++ b/include/util.h @@ -0,0 +1,57 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __WECONN_UTIL_H__ +#define __WECONN_UTIL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "weconn_type.h" + +#define XSTR(X) #X + +/** + * @brief Enumerations of wearable connection popup type. + */ +typedef enum +{ + WC_POPUP_TYPE_PAN_CONNECT = 0x01, + WC_POPUP_TYPE_PAN_DISCONNECT = 0x02, +} weconn_popup_type_e; + +weconn_service_type_e wc_service_type_string2enum(const char *service_type); +const char *wc_service_type_enum2string(weconn_service_type_e service_type); +weconn_service_state_e wc_service_state_string2enum(const char *service_state); +const char *wc_service_state_enum2string(weconn_service_state_e service_state); +weconn_device_type_e wc_device_type_string2enum(const char *device_type); +const char *wc_device_type_enum2string(weconn_device_type_e device_type); +weconn_device_state_e wc_device_state_string2enum(const char *device_state); +const char *wc_device_state_enum2string(weconn_device_state_e device_state); +weconn_device_type_e wc_device_get_type_from_path(const char *path); +int wc_launch_popup(weconn_popup_type_e type, service_reply_cb callback, + void *user_data); + +#ifdef __cplusplus +} +#endif + +#endif /* __WECONN_UTIL_H__ */ diff --git a/introspection/service.xml b/introspection/service.xml new file mode 100644 index 0000000..d6774b8 --- /dev/null +++ b/introspection/service.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/introspection/technology.xml b/introspection/technology.xml new file mode 100644 index 0000000..7b18481 --- /dev/null +++ b/introspection/technology.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/weconn.spec b/packaging/weconn.spec new file mode 100644 index 0000000..676989a --- /dev/null +++ b/packaging/weconn.spec @@ -0,0 +1,110 @@ +Name: weconn +Summary: WEarable device CONNection controller framework (We connect all) +Version: 0.1.56 +Release: 1 +Group: System/Network +License: Apache-2.0 +Source0: %{name}-%{version}.tar.gz +BuildRequires: cmake +BuildRequires: python-xml +BuildRequires: pkgconfig(dlog) +BuildRequires: pkgconfig(vconf) +BuildRequires: pkgconfig(gio-2.0) +BuildRequires: pkgconfig(glib-2.0) +BuildRequires: pkgconfig(capi-base-common) +BuildRequires: pkgconfig(sap-client-stub-api) +BuildRequires: pkgconfig(capi-appfw-application) +BuildRequires: pkgconfig(capi-network-bluetooth) +BuildRequires: pkgconfig(capi-system-info) +BuildRequires: pkgconfig(alarm-service) +Requires(post): /sbin/ldconfig +Requires(post): /usr/bin/vconftool +Requires(postun): /sbin/ldconfig +Requires: /usr/bin/vconftool + +%description +Wearable device requires an automatic controller to manage various connection bearers +without UI based control panel. Wearable connection controller provides client and +daemon for managing various connection bearers within wearable devices running the +Linux operating system. + +%package devel +Summary: WEarable device CONNection controller API library (devel) +Group: System/Network +License: Apache +Requires: %{name} = %{version}-%{release} + +%description devel +Wearable device connection controller API library in TIZEN C API (Development) + +%prep +%setup -q + + +%build +cmake . -DCMAKE_INSTALL_PREFIX=%{_prefix} -DVERSION=%{version} +make %{?_smp_mflags} + + +%install +%make_install + +#Systemd service file +mkdir -p %{buildroot}%{_libdir}/systemd/system/ +cp resources/usr/lib/systemd/system/weconn.service %{buildroot}%{_libdir}/systemd/system/weconn.service +mkdir -p %{buildroot}%{_libdir}/systemd/system/multi-user.target.wants/ +ln -s ../weconn.service %{buildroot}%{_libdir}/systemd/system/multi-user.target.wants/weconn.service + +mkdir -p %{buildroot}%{_datadir}/dbus-1/services/ +cp resources/usr/share/dbus-1/services/net.weconn.service %{buildroot}%{_datadir}/dbus-1/services/net.weconn.service + +#DBus DAC (manifest enables DBus SMACK) +mkdir -p %{buildroot}%{_sysconfdir}/dbus-1/system.d/ +cp resources/etc/dbus-1/system.d/weconn.conf %{buildroot}%{_sysconfdir}/dbus-1/system.d/ + +#appcessory +mkdir -p %{buildroot}%{_datadir}/appcessory/ +cp resources/usr/share/appcessory/wearable-connection.xml %{buildroot}%{_datadir}/appcessory/ + +#FOTA patch +mkdir -p %{buildroot}%{_sysconfdir}/opt/upgrade/ +cp resources/etc/opt/upgrade/500.net.weconn.patch.sh %{buildroot}%{_sysconfdir}/opt/upgrade/ + +#License +mkdir -p %{buildroot}%{_datadir}/license +cp LICENSE %{buildroot}%{_datadir}/license/weconn + +%post + +#Systemd enhanced BT power on +mkdir -p %{_sysconfdir}/systemd/default-extra-dependencies/ignore-units.d/ +ln -s %{_libdir}/systemd/system/weconn.service %{_sysconfdir}/systemd/default-extra-dependencies/ignore-units.d/ + +vconftool set -t int file/private/weconn/connected_bt_version 0 -s system::vconf_network +vconftool set -t int file/private/weconn/disconnected_manually 0 -s system::vconf_network +vconftool set -t string file/private/weconn/connected_manufacturer "" -s system::vconf_network +vconftool set -t string file/private/weconn/autoconnectable_unique_bt_target_address "" -s system::vconf_network +vconftool set -t int file/private/weconn/last_bt_status 0 -s system::vconf_network +vconftool set -tf int memory/private/weconn/all_connected 0 -s system::vconf_network -i + +%postun -p /sbin/ldconfig + + +%files +%manifest weconn.manifest +%attr(644,-,-) %{_libdir}/*.so.* +%attr(500,root,root) %{_sbindir}/* +%attr(400,root,root) %{_libdir}/weconn/*.so +%attr(644,root,root) %{_datadir}/dbus-1/services/* +#DBus DAC +%attr(644,root,root) %{_sysconfdir}/dbus-1/system.d/* +%attr(644,root,root) %{_libdir}/systemd/system/weconn.service +%attr(644,root,root) %{_libdir}/systemd/system/multi-user.target.wants/weconn.service +%attr(700,root,root) %{_sysconfdir}/opt/upgrade/500.net.weconn.patch.sh +%{_datadir}/license/weconn +%{_datadir}/appcessory/wearable-connection.xml + +%files devel +%{_libdir}/libweconn.so +%{_includedir}/weconn +%{_libdir}/pkgconfig/weconn.pc diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt new file mode 100755 index 0000000..0061e36 --- /dev/null +++ b/plugins/CMakeLists.txt @@ -0,0 +1,16 @@ +REMOVE_DEFINITIONS("-DLOG_TAG=\"CAPI_NETWORK_WECONN\"") +ADD_DEFINITIONS("-DLOG_TAG=\"WECONN\"") + + +SET(PLUGINS + pan + esap + bluetooth +) + +FOREACH(plugin ${PLUGINS}) + ADD_LIBRARY(${plugin} SHARED ${plugin}.c ../src/util.c) + TARGET_LINK_LIBRARIES(${plugin} ${pkgs_LDFLAGS} "-L${CMAKE_BINARY_DIR}") + SET_TARGET_PROPERTIES(${plugin} PROPERTIES PREFIX "" OUTPUT_NAME ${plugin}) + INSTALL(TARGETS ${plugin} LIBRARY DESTINATION ${WECONNLIB}) +ENDFOREACH(plugin) diff --git a/plugins/bluetooth.c b/plugins/bluetooth.c new file mode 100644 index 0000000..6d7f5fa --- /dev/null +++ b/plugins/bluetooth.c @@ -0,0 +1,1468 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include + +#include "esap.h" +#include "plugin.h" + +#define BT_ADVERTISING_INTERVAL_FAST 150.0f //msec +#define BT_ADVERTISING_INTERVAL_NORMAL 500.0f //msec +#define FAST_ADVERTISING_TIMEOUT 30 //sec + +#define BT_ADV_MANUFACTURER_COMPANY 0x75 +#define BT_ADV_MANUFACTURER_SERVICE_ID 0x02 +#define BT_ADV_MANUFACTURER_VERSION 0x01 +#define BT_ADV_MANUFACTURER_PKG_NAME "watchmanager" + +#define VCONFKEY_WECONN_LAST_BT_STATUS \ + "file/private/weconn/last_bt_status" + +static int sap_rfcomm_ready = 0; +static int sap_connected = 0; +static bool hf_connected = false; + +static gboolean mBTRecoveryState = FALSE; + +static alarm_id_t fast_advertising_timer_id = 0; + +static void __bt_start_advertising(bool fast); +static void __bt_page_scan(bool on); + +static bool __bt_check_bonded_device_cb(bt_device_info_s *device_info, + void *user_data) +{ + if (device_info && device_info->bt_class.major_device_class + == BT_MAJOR_DEVICE_CLASS_AUDIO_VIDEO) { + int ret; + bool is_a2dp_connected = false; + + DBG("Major class[0x%x]", device_info->bt_class.major_device_class); + + ret = bt_device_is_profile_connected(device_info->remote_address, + BT_PROFILE_A2DP, &is_a2dp_connected); + + DBG("ret[%d], is_a2dp_connected [%d]", ret, is_a2dp_connected); + + if (ret == BT_ERROR_NONE && is_a2dp_connected == false) + __bt_page_scan(true); + } + + return true; +} + +static void __bt_notify_all_connected(int connected) +{ + int all_connected = 0; + + if (vconf_get_int(VCONFKEY_WECONN_ALL_CONNECTED, &all_connected) < 0) { + ERR("Failed to get all connected status"); + } else { + if (all_connected == connected) { + ERR("Not changed all connected state"); + return; + } + } + + if (connected == 0) + vconf_set_int(VCONFKEY_WECONN_ALL_CONNECTED, 0); + else + vconf_set_int(VCONFKEY_WECONN_ALL_CONNECTED, 1); + + DBG("All connected"); +} + +static void __bt_page_scan(bool on) +{ + int err; + bool prev_status = false; + + err = bt_adapter_get_connectable(&prev_status); + if (err != BT_ERROR_NONE) { + ERR("Failed to get bt_adapter_get_connectable(%d)", err); + return; + } + + if (prev_status == false && on == true) { + err = bt_adapter_set_connectable(true); + if (err != BT_ERROR_NONE) + ERR("Failed to start page scan(%d)", err); + else + DBG("Page scan started"); + } else if (prev_status == true && on == false) { + err = bt_adapter_set_connectable(false); + if (err != BT_ERROR_NONE) + ERR("Failed to stop page scan(%d)", err); + else + DBG("Page scan stopped"); + + /* check bonded device and enable page scan if AV headset is paired*/ + if (bt_adapter_foreach_bonded_device(__bt_check_bonded_device_cb, + NULL) != BT_ERROR_NONE) + ERR("Failed to check bonded devices"); + } else { + DBG("Skip setting: old(%d), new(%d)", prev_status, on); + } +} + +static int __get_setup_wizard_state(void) +{ + int ret = 0, state = 0; + + ret = vconf_get_bool(VCONFKEY_SETUP_WIZARD_FIRST_BOOT, &state); + if (ret < 0) { + ERR("Failed to get state(%d)", ret); + return -EIO; + } + + DBG("Setup wizard running state(%d)", state); + + return state; +} + +static void __bt_set_advertising_manufacturer_data(int setup_wizard_state) +{ + int err = 0; + static char prev_data[24] = {0, }; + char data[24] = {0, }; + int len = 0; + + /* Complete manufacturer data */ + // Company + data[0] = 0x00; + data[1] = BT_ADV_MANUFACTURER_COMPANY; + + // TODO: Read value from somewhere + // Version + data[2] = BT_ADV_MANUFACTURER_VERSION; + + // Service ID + data[3] = 0x00; + data[4] = BT_ADV_MANUFACTURER_SERVICE_ID; + + // TODO : Read value from somewhere + // Device ID (b2 : 0x01, wingtip : 0x02) + data[5] = 0x00; + data[6] = 0x01; + + if (setup_wizard_state == 1) { + // Purpose (setup : 0x01, auto connection : 0x02) + data[7] = 0x01; + + /* Data for Quick Connect */ + // Length of package name + data[8] = 0x0c; + + // Package name for auto installation + memcpy(&data[9], BT_ADV_MANUFACTURER_PKG_NAME, + sizeof(BT_ADV_MANUFACTURER_PKG_NAME)); + len = 9 + strlen(BT_ADV_MANUFACTURER_PKG_NAME); + + // BT rssi calibrating constant + data[len] = 0x00; + len++; + } else {/* setup wizard is not running */ + data[7] = 0x02; + len = 8; + } + + DBG("len(%d)", len); + memcpy(prev_data, data, sizeof(data)); + + err = bt_adapter_set_advertising_manufacturer_data(data, len); + if (err != BT_ERROR_NONE) + ERR("Failed to set manufacturer data(%d)", err); +} + +static bool __device_check_gatt_cb(bt_profile_e profile, void *user_data) +{ + bool *is_connected = (bool *)user_data; + + if (profile == BT_PROFILE_GATT) { + *is_connected = true; + DBG("LE is connected"); + return false; + } + + return true; +} + +static bool __device_check_hfp_cb(bt_profile_e profile, void *user_data) +{ + bool *is_connected = (bool *)user_data; + + if (profile == BT_PROFILE_AG) { + *is_connected = true; + DBG("HFP is connected"); + return false; + } + + return true; +} + +static bool __bt_check_connection_status(const char *addr, + bt_device_connected_profile func) +{ + int ret; + bool is_connected = false; + + if (!addr || !func || addr[0] == '\0') + return false; + + SECURE_DBG("Addr[%s]", addr); + ret = bt_device_foreach_connected_profiles(addr, func, &is_connected); + if (ret != BT_ERROR_NONE) { + ERR("Failed to bt_device_foreach_connected_profiles(0x%08x)", ret); + return false; + } + + return is_connected; +} + +static int __fast_advertising_timeout_cb(alarm_id_t alarm_id, void * data) +{ + DBG("fast advertising timer expired!!"); + + if (fast_advertising_timer_id > 0) { + alarmmgr_remove_alarm(fast_advertising_timer_id); + fast_advertising_timer_id = 0; + } + + if (sap_connected != SAP_CONNECTED && hf_connected != true) + __bt_start_advertising(false); + + return 0; +} + +static void __fast_advertising_timer_stop(void) +{ + if (fast_advertising_timer_id <= 0) + return; + + DBG("Alarm unregistered(%d)", fast_advertising_timer_id); + alarmmgr_remove_alarm(fast_advertising_timer_id); + fast_advertising_timer_id = 0; +} + +static void __fast_advertising_timer_start(void) +{ + int result = 0; + + __fast_advertising_timer_stop(); + + result = alarmmgr_set_cb(__fast_advertising_timeout_cb, NULL); + if (result != ALARMMGR_RESULT_SUCCESS) { + ERR("Failed to set timer(%d)", result); + return; + } + + result = alarmmgr_add_alarm(ALARM_TYPE_VOLATILE, FAST_ADVERTISING_TIMEOUT, + 0, NULL, &fast_advertising_timer_id); + if (result != ALARMMGR_RESULT_SUCCESS) { + ERR("Failed to add alarm(%d)", result); + return; + } + + DBG("Alarm registered(%d)", fast_advertising_timer_id); +} + +static void __bt_update_white_list(const char *bt_target_address, + int setup_wizard_state) +{ + int ret = 0; + bool is_advertising = false; + + ret = bt_adapter_is_advertising(&is_advertising); + if (ret == BT_ERROR_NONE && is_advertising == true) + bt_adapter_stop_advertising(); + + /* should remove the previous connected addresses */ + DBG("Clear white list"); + ret = bt_adapter_clear_white_list(); + if (ret != BT_ERROR_NONE) + ERR("Failed to clear white list(%d)", ret); + + if (!bt_target_address) { + DBG("target address is NULL"); + return; + } + + if (strlen(bt_target_address) < 17) { + /* BT address formatted [xx:xx:xx:xx:xx:xx] */ + DBG("Invalid mac address"); + return; + } + + if (setup_wizard_state == 1) { + DBG("Setup Wizard is running"); + return; + } + + WARN("Adding white list[%c%c:%c%c:%c%c]", + bt_target_address[0], bt_target_address[1], + bt_target_address[3], bt_target_address[4], + bt_target_address[15], bt_target_address[16]); + SECURE_DBG("white list[%s]", bt_target_address); + ret = bt_adapter_add_white_list(bt_target_address); + if (ret != BT_ERROR_NONE) + ERR("Failed to add white list(%d)", ret); +} + +static void __bt_start_advertising(bool fast) +{ + int ret = 0; + bool is_advertising = false; + bt_adapter_advertising_params_s adv_params = { 0, }; + bt_adapter_state_e device_status; + int setup_wizard_state = 0; + int disconnected_manually = 0; + const char *autoconnectable_bt_address = NULL; + + bt_adapter_get_state(&device_status); + if (device_status != BT_ADAPTER_ENABLED) { + ERR("BT disabled"); + return; + } + + if (sap_connected == SAP_CONNECTED || hf_connected == true) { + ERR("SPP(%d) HFP(%d)", sap_connected, hf_connected); + return; + } + + /* page scan should be on, even if LE does not advertised */ + __bt_page_scan(true); + + vconf_get_int(VCONFKEY_WECONN_DISCONNECTED_MANUALLY, &disconnected_manually); + if (disconnected_manually) { + DBG("Disconnected manually(%d)", disconnected_manually); + return; + } + + /* Important: BT auto-connectable address should be set by WMS, + * when EULA acceptance has been made by an user. + */ + autoconnectable_bt_address = + vconf_get_str(VCONFKEY_WECONN_AUTOCONNECTABLE_BT_ADDRESS); + if (autoconnectable_bt_address && strlen(autoconnectable_bt_address) > 0 && + __bt_check_connection_status(autoconnectable_bt_address, + __device_check_gatt_cb) == true) { + return; + } + + DBG("fast(%d)", fast); + if (fast) { + __fast_advertising_timer_start(); + + adv_params.interval_max = BT_ADVERTISING_INTERVAL_FAST; + adv_params.interval_min = BT_ADVERTISING_INTERVAL_FAST; + } else { + adv_params.interval_max = BT_ADVERTISING_INTERVAL_NORMAL; + adv_params.interval_min = BT_ADVERTISING_INTERVAL_NORMAL; + } + + setup_wizard_state = __get_setup_wizard_state(); + + __bt_update_white_list(autoconnectable_bt_address, setup_wizard_state); + + if (setup_wizard_state == 1) { + adv_params.filter_policy = BT_ADAPTER_ADVERTISING_FILTER_ALLOW_CONN_WL; + adv_params.interval_max = BT_ADVERTISING_INTERVAL_FAST; + adv_params.interval_min = BT_ADVERTISING_INTERVAL_FAST; + } else { + if (autoconnectable_bt_address && + strlen(autoconnectable_bt_address) > 0) { + adv_params.filter_policy = + BT_ADAPTER_ADVERTISING_FILTER_ALLOW_SCAN_CONN_WL; + } else { + WARN("Start advertising without white list"); + adv_params.filter_policy = BT_ADAPTER_ADVERTISING_FILTER_DEFAULT; + } + } + + g_free((gpointer)autoconnectable_bt_address); + + ret = bt_adapter_is_advertising(&is_advertising); + if (ret == BT_ERROR_NONE && is_advertising == true) + bt_adapter_stop_advertising(); + + __bt_set_advertising_manufacturer_data(setup_wizard_state); + + ret = bt_adapter_start_advertising(&adv_params); + if (ret == BT_ERROR_NONE) { + DBG("LE Advertising is started(%.2fms)", adv_params.interval_max); + return; + } + + ERR("Failed to start advertising(%d)", ret); +} + +static void __bt_stop_advertising(void) +{ + int ret = 0; + bool is_advertising = FALSE; + + /* Either HFP or SPP connected, stop advertising and disable page scan */ + if (fast_advertising_timer_id > 0) { + alarmmgr_remove_alarm(fast_advertising_timer_id); + fast_advertising_timer_id = 0; + } + + ret = bt_adapter_is_advertising(&is_advertising); + if (ret == BT_ERROR_NONE && is_advertising == false) + goto page_scan_off; + + ret = bt_adapter_stop_advertising(); + if (ret == BT_ERROR_NONE) + DBG("LE Advertising is stopped"); + else + ERR("Fail to stop advertising(%d)", ret); + +page_scan_off: + if(__get_setup_wizard_state() != 1) + __bt_page_scan(false); +} + +static void __bt_set_visibility(bt_adapter_visibility_mode_e mode) +{ + int ret = 0; + bt_adapter_visibility_mode_e visibility = 0; + int setup_wizard_state = __get_setup_wizard_state(); + + if (setup_wizard_state == 1) { + ERR("Setup wizard is running and no need to set visibility"); + return; + } + + ret = bt_adapter_get_visibility(&visibility, NULL); + if (ret != BT_ERROR_NONE) { + ERR("Failed to get bt_adapter_get_visibility(%d)", ret); + return; + } + + DBG("Visibility(%d), mode(%d)", visibility, mode); + if (visibility != mode) { + bt_adapter_set_visibility(mode, 0); + if (ret != BT_ERROR_NONE) + ERR("Failed to set bt_adapter_set_visibility(%d)", ret); + } +} + +static void __setup_wizard_state_cb(keynode_t *node, void *user_data) +{ + int err = 0, state = 0; + WcObject * wo = (WcObject*)user_data; + + if (!wo) + return; + + if (node) + state = vconf_keynode_get_bool(node); + else + err = vconf_get_bool(VCONFKEY_SETUP_WIZARD_FIRST_BOOT, &state); + if (err < 0) { + ERR("Failed to get setup wizard state"); + return; + } + + DBG("setup wizard state: %d", state); + if (state == 0) + __bt_page_scan(false); +} + +static void __power_saving_mode_cb(keynode_t *node, void *user_data) +{ + int ret = 0; + int ps_mode = 0; + bt_adapter_state_e last_bt_status = BT_ADAPTER_DISABLED; + WcObject * wo = (WcObject*)user_data; + + if (!wo) + return; + + if (node) + ps_mode = vconf_keynode_get_int(node); + else { + ret = vconf_get_int(VCONFKEY_SETAPPL_PSMODE, &ps_mode); + if (ret < 0) { + ERR("Failed to get power saving mode"); + return; + } + } + + if (ps_mode == SETTING_PSMODE_WEARABLE) { + ret = bt_adapter_get_state(&last_bt_status); + if (ret != BT_ERROR_NONE) { + ERR("Failed to get bluetooth status (%d)", ret); + return; + } + + DBG("bluetooth status : %d", last_bt_status); + if (last_bt_status != BT_ADAPTER_ENABLED) + return; + + vconf_set_int(VCONFKEY_WECONN_LAST_BT_STATUS, last_bt_status); + + ret = bt_adapter_disable(); + if (ret != BT_ERROR_NONE) + ERR("Failed to disable bluetooth (%d)", ret); + } else if (ps_mode == SETTING_PSMODE_NORMAL) { + vconf_get_int(VCONFKEY_WECONN_LAST_BT_STATUS, (int*)&last_bt_status); + if (last_bt_status == BT_ADAPTER_ENABLED) { + ret = bt_adapter_enable(); + if (ret != BT_ERROR_NONE) + ERR("Failed to enable bluetooth (%d)", ret); + } else + DBG("bluetooth was not enabled"); + } else + DBG("Invalid value : %d", ps_mode); +} + +static void __rfcomm_ready_status_changed_cb(keynode_t *node, void *user_data) +{ + int err = 0, rfcomm_ready = 0; + WcObject * wo = (WcObject*)user_data; + + if (!wo) + return; + + if (node) + rfcomm_ready = vconf_keynode_get_int(node); + else + err = vconf_get_int(VCONFKEY_RFCOMM_READY_STATUS, &rfcomm_ready); + + if (err < 0) { + ERR("Failed to get rfcomm ready status"); + return; + } + + sap_rfcomm_ready = rfcomm_ready; + DBG("rfcomm_ready(%d), SPP(%d), HFP(%d)", + rfcomm_ready, sap_connected, hf_connected); + + if (rfcomm_ready == 1) { + if (sap_connected != SAP_CONNECTED && hf_connected != true) + __bt_start_advertising(true); + } else + __bt_stop_advertising(); +} + +static void __pm_key_ignore_cb(keynode_t *node, void *user_data) +{ + int ret = 0; + int mode = 0; + bt_adapter_state_e bt_status = BT_ADAPTER_DISABLED; + static bt_adapter_state_e prev_bt_status = BT_ADAPTER_DISABLED; + + if (node) + mode = vconf_keynode_get_int(node); + else + ret = vconf_get_int(VCONFKEY_PM_KEY_IGNORE, &mode); + + DBG("current pm mode : %s mode", mode ? "clock" : "normal"); + + ret = bt_adapter_get_state(&bt_status); + if (ret != BT_ERROR_NONE) { + ERR("Failed to get bluetooth status (%d)", ret); + return; + } + DBG("bluetooth status : %d", bt_status); + + if (mode == 1) { + if (bt_status != BT_ADAPTER_ENABLED) + return; + + ret = bt_adapter_disable(); + if (ret != BT_ERROR_NONE) + ERR("Failed to disable bluetooth (%d)", ret); + } else { + if (bt_status != BT_ADAPTER_DISABLED) + return; + else if (prev_bt_status == BT_ADAPTER_ENABLED) { + ret = bt_adapter_enable(); + if (ret != BT_ERROR_NONE) + ERR("Failed to enable bluetooth (%d)", ret); + } + } + + prev_bt_status = bt_status; +} + +static void __bt_fatal_recovery(void) +{ + int ret; + bt_adapter_state_e state = BT_ADAPTER_DISABLED; + + ret = bt_adapter_get_state(&state); + if (ret != BT_ERROR_NONE) + ERR("Fatally failed to get BT state(%d)", ret); + + WARN("BT state(%d)", state); + bt_adapter_disable(); + + mBTRecoveryState = TRUE; +} + +static void __bt_check_fatal_recovery(void) +{ + const char *autoconnectable_bt_address = NULL; + + /* Important: BT auto-connectable address should be set by WMS, + * when EULA acceptance has been made by an user. + */ + autoconnectable_bt_address = + vconf_get_str(VCONFKEY_WECONN_AUTOCONNECTABLE_BT_ADDRESS); + if (autoconnectable_bt_address && strlen(autoconnectable_bt_address) > 0 && + __bt_check_connection_status(autoconnectable_bt_address, + __device_check_gatt_cb) == true) { + g_free((gpointer)autoconnectable_bt_address); + + ERR("Fatal error: both EDR and LE was connected"); + + __bt_fatal_recovery(); + return; + } + + g_free((gpointer)autoconnectable_bt_address); +} + +static void __sap_connection_status_changed_cb(keynode_t *node, void *user_data) +{ + int err = 0, sap_conn = 0; + int disconnected_manually = 0; + WcObject * wo = (WcObject*)user_data; + + if (!wo) + return; + + if (node) + sap_conn = vconf_keynode_get_int(node); + else + err = vconf_get_int(VCONFKEY_SAP_CONNECTION_STATUS, &sap_conn); + + if (err < 0) { + ERR("Failed to get sap connection status"); + return; + } + + if (sap_connected == sap_conn) { + ERR("sap connection is not changed(%d)", sap_conn); + return; + } + + sap_connected = sap_conn; + DBG("rfcomm_ready(%d), SPP(%d), HFP(%d)", sap_rfcomm_ready, sap_conn, hf_connected); + if (sap_conn == SAP_CONNECTED) { + __bt_stop_advertising(); + __bt_set_visibility(BT_ADAPTER_VISIBILITY_MODE_NON_DISCOVERABLE); + + if (hf_connected) { + __bt_notify_all_connected(1); + + vconf_get_int(VCONFKEY_WECONN_DISCONNECTED_MANUALLY, + &disconnected_manually); + if (disconnected_manually) + vconf_set_int(VCONFKEY_WECONN_DISCONNECTED_MANUALLY, 0); + } + + wc_object_set_property(wo, XSTR(W_SERVICE_TYPE_BT_SPP), + XSTR(W_SERVICE_STATE_CONNECTED)); + } else { + __bt_notify_all_connected(0); + + if (hf_connected != true) + __bt_check_fatal_recovery(); + + if (sap_rfcomm_ready == 1 && hf_connected != true) { + /* start LE advertising */ + DBG("HFP is disconnected"); + __bt_start_advertising(false); + } + + wc_object_set_property(wo, XSTR(W_SERVICE_TYPE_BT_SPP), + XSTR(W_SERVICE_STATE_DISCONNECTED)); + } +} + +static void __on_bt_adapter_state_changed(int result, bt_adapter_state_e state, + void *user_data) +{ + int sap_conn = SAP_DISCONNECTED; + const char *autoconnectable_bt_address = NULL; + WcObject *wo = (WcObject *)user_data; + + if (mBTRecoveryState == TRUE && state == BT_ADAPTER_DISABLED) { + ERR("BT fatal recovery tries to turn BT power on"); + + bt_adapter_enable(); + mBTRecoveryState = FALSE; + + return; + } + + if (!wo) + return; + + DBG("result = %d, state = %d", result, state); + switch (state) { + case BT_ADAPTER_ENABLED: + /* BT ON */ + wc_object_emit_callback(wo, WC_OBJECT_EVENT_BEARER_ENABLED, NULL); + + /* Update SPP state */ + if (vconf_get_int(VCONFKEY_SAP_CONNECTION_STATUS, &sap_conn) < 0) { + ERR("Failed to get sap connection status, but go ahead."); + sap_connected = SAP_DISCONNECTED; + } else + sap_connected = sap_conn; + + autoconnectable_bt_address = + vconf_get_str(VCONFKEY_WECONN_AUTOCONNECTABLE_BT_ADDRESS); + SECURE_DBG("last connected BT address(%s)", autoconnectable_bt_address); + if (autoconnectable_bt_address && + strlen(autoconnectable_bt_address) > 0) { + /* Update HFP status */ + hf_connected = __bt_check_connection_status( + autoconnectable_bt_address, __device_check_hfp_cb); + } else { + /* If not, clear white list, for example, factory reset */ + __bt_update_white_list(NULL, 0); + } + + g_free((gpointer)autoconnectable_bt_address); + + vconf_set_int(VCONFKEY_WECONN_LAST_BT_STATUS, 0); + + DBG("BT power(%d), rfcomm_ready(%d), SPP(%d) HFP(%d)", + state, sap_rfcomm_ready, sap_connected, hf_connected); + if (sap_rfcomm_ready == 1 && + sap_connected != SAP_CONNECTED && hf_connected != true) + __bt_start_advertising(true); + + wc_object_set_property(wo, XSTR(W_DEVICE_TYPE_BT), + XSTR(W_DEVICE_STATE_ENABLED)); + + break; + case BT_ADAPTER_DISABLED: + /* BT OFF */ + wc_object_emit_callback(wo, WC_OBJECT_EVENT_BEARER_DISABLED, NULL); + + hf_connected = false; + sap_connected = false; + + __fast_advertising_timer_stop(); + + wc_object_set_property(wo, XSTR(W_DEVICE_TYPE_BT), + XSTR(W_DEVICE_STATE_DISABLED)); + + break; + } +} + +static void __on_bt_audio_connection_state_changed_cb(int result, + bool connected, const char *remote_address, + bt_audio_profile_type_e type, void *user_data) +{ + int disconnected_manually = 0; + WcObject *wo = (WcObject *)user_data; + + DBG("result %d, type %d, rfcomm_ready(%d), SPP(%d), HFP(%d)", + result, type, sap_rfcomm_ready, sap_connected, connected); + SECURE_DBG("remote(%s)", remote_address); + + if (type != BT_AUDIO_PROFILE_TYPE_AG) { + if (type == BT_AUDIO_PROFILE_TYPE_A2DP) { + if (connected == false) + __bt_page_scan(true); + else + if (hf_connected == true || sap_connected == SAP_CONNECTED) + bt_adapter_set_connectable(false); + } + return; + } + + if (!wo) + return; + + hf_connected = connected; + if (connected) { + __bt_stop_advertising(); + __bt_set_visibility(BT_ADAPTER_VISIBILITY_MODE_NON_DISCOVERABLE); + + if (sap_connected == SAP_CONNECTED) { + __bt_notify_all_connected(1); + + vconf_get_int(VCONFKEY_WECONN_DISCONNECTED_MANUALLY, + &disconnected_manually); + if (disconnected_manually) + vconf_set_int(VCONFKEY_WECONN_DISCONNECTED_MANUALLY, 0); + } + + wc_object_set_property(wo, XSTR(W_SERVICE_TYPE_BT_HFP), + XSTR(W_SERVICE_STATE_CONNECTED)); + } else { + __bt_notify_all_connected(0); + + if (sap_connected != SAP_CONNECTED) + __bt_check_fatal_recovery(); + + if (sap_rfcomm_ready == 1 && sap_connected != SAP_CONNECTED) { + /* start LE advertising */ + DBG("SAP is disconnected"); + __bt_start_advertising(false); + } + + wc_object_set_property(wo, XSTR(W_SERVICE_TYPE_BT_HFP), + XSTR(W_SERVICE_STATE_DISCONNECTED)); + } +} + +static void __on_bt_socket_connection_state_changed_cb(int result, + bt_socket_connection_state_e state, + bt_socket_connection_s *connection, void *user_data) +{ + WcObject *wo = (WcObject *)user_data; + + DBG("result %d, state %d", result, state); + + if (!wo) + return; + +/* + if (state == BT_SOCKET_CONNECTED) + wc_object_set_property(wo, XSTR(W_SERVICE_TYPE_BT_SPP), + XSTR(W_SERVICE_STATE_CONNECTED)); + else + wc_object_set_property(wo, XSTR(W_SERVICE_TYPE_BT_SPP), + XSTR(W_SERVICE_STATE_DISCONNECTED)); +*/ +} + +static void __on_bt_device_connection_state_changed_cb(bool connected, + const char *remote_address, void *user_data) +{ + WcObject *wo = (WcObject *)user_data; + + DBG("result %d ", connected); + if(remote_address != NULL) + SECURE_DBG("remote address [%s]", remote_address); + + if (!wo) + return; + +/* This callback is triggered by ACL connected / disconnected event + if (connected) + wc_object_set_property(wo, XSTR(W_SERVICE_TYPE_BT_HFP), + XSTR(W_SERVICE_STATE_CONNECTED)); + else + wc_object_set_property(wo, XSTR(W_SERVICE_TYPE_BT_HFP), + XSTR(W_SERVICE_STATE_DISCONNECTED)); +*/ +} + +static void __on_bt_device_bond_destroy_cb(int result, + char *remote_address, void *user_data) +{ + WcObject *wo = (WcObject *)user_data; + + if (!wo || !remote_address) + return; + + DBG("result(%d) address(%s)", result, remote_address); + if (result == BT_ERROR_NONE) { + // Disable page scan if HFP or SAP is connected + if (hf_connected == true || sap_connected == SAP_CONNECTED) + __bt_page_scan(false); + } + + return; +} + +static void __on_bt_device_bond_created_cb(int result, + bt_device_info_s *device_info, void *user_data) +{ + WcObject *wo = (WcObject *)user_data; + + if (!wo || !device_info) + return; + + DBG("result(%d), device class(%d)", result, + device_info->bt_class.major_device_class); + if (device_info->bt_class.major_device_class != BT_MAJOR_DEVICE_CLASS_PHONE) + return; +} + +static gboolean __on_bt_sap_cmd_cb(WcObject *wo, + const void *event_info, void *user_data) +{ + struct SAP_event_data *evt_data = NULL; + struct SAP_event_data *new_data = NULL; + char buf[SAP_DATA_LEN_MAX] = { '\0', }; + char *bt_address = NULL; + // TODO: All of them should be from BT framework. + char *profiles = "HFP SPP GATT PAN"; + char cod[3] = {0x28, 0x07, 0x04}; + char services = 0x07; + unsigned int pos = 0; + int res, mainCmd, subCmd, cmdType, btVersion; + + evt_data = (struct SAP_event_data *)event_info; + if (evt_data == NULL) + return TRUE; + + mainCmd = (int)evt_data->data[1]; + if (mainCmd != SAP_PDU_CMD_BLUETOOTH) { + ERR("Invalid bluetooth command %d", mainCmd); + return TRUE; + } + + subCmd = (int)evt_data->data[2]; + switch(subCmd) { + case SAP_PDU_BT_CMD_GET_VERSION: + DBG("SAP_PDU_BT_CMD_GET_VERSION"); + + cmdType = (int)evt_data->data[3]; + switch (cmdType) { + case SAP_PDU_CMD_TYPE_REQ: + new_data = g_try_malloc0(sizeof(struct SAP_event_data)); + if (new_data == NULL) + return TRUE; + + new_data->count = evt_data->count; + new_data->index = evt_data->index; + new_data->data_len = SAP_DATA_LEN_MIN + 1; + + buf[pos++] = (char)new_data->data_len; + buf[pos++] = (char)SAP_PDU_CMD_BLUETOOTH; + buf[pos++] = (char)SAP_PDU_BT_CMD_GET_VERSION; + buf[pos++] = (char)SAP_PDU_CMD_TYPE_REQ_OK; + buf[pos++] = (char)SAP_PDU_BT_VER_40_P; + memcpy(new_data->data, buf, new_data->data_len); + + wc_object_emit_callback(wo, WC_OBJECT_EVENT_SAP_SEND_DATA, new_data); + g_free(new_data); + break; + case SAP_PDU_CMD_TYPE_REQ_OK: + btVersion = (int)evt_data->data[SAP_DATA_LEN_MIN]; + switch (btVersion) { + case SAP_PDU_BT_VER_30: + vconf_set_int(VCONFKEY_WECONN_CONNECTED_BT_VERSION, + SAP_PDU_BT_VER_30); + break; + case SAP_PDU_BT_VER_40_LE: + vconf_set_int(VCONFKEY_WECONN_CONNECTED_BT_VERSION, + SAP_PDU_BT_VER_40_LE); + break; + case SAP_PDU_BT_VER_40_DUAL: + vconf_set_int(VCONFKEY_WECONN_CONNECTED_BT_VERSION, + SAP_PDU_BT_VER_40_DUAL); + break; + case SAP_PDU_BT_VER_40_P: + vconf_set_int(VCONFKEY_WECONN_CONNECTED_BT_VERSION, + SAP_PDU_BT_VER_40_P); + break; + case SAP_PDU_BT_VER_41: + vconf_set_int(VCONFKEY_WECONN_CONNECTED_BT_VERSION, + SAP_PDU_BT_VER_41); + break; + default: + DBG("Unknown BT version"); + break; + } + break; + } + + break; + case SAP_PDU_BT_CMD_GET_ADDRESS: + DBG("SAP_PDU_BT_CMD_GET_ADDRESS"); + + cmdType = (int)evt_data->data[3]; + switch (cmdType) { + case SAP_PDU_CMD_TYPE_REQ: + new_data = g_try_malloc0(sizeof(struct SAP_event_data)); + if (new_data == NULL) + return TRUE; + + new_data->count = evt_data->count; + new_data->index = evt_data->index; + + res = bt_adapter_get_address(&bt_address); + if (res == BT_ERROR_NONE && bt_address) { + new_data->data_len = SAP_DATA_LEN_MIN + strlen(bt_address); + + buf[pos++] = (char)new_data->data_len; + buf[pos++] = (char)SAP_PDU_CMD_BLUETOOTH; + buf[pos++] = (char)SAP_PDU_BT_CMD_GET_ADDRESS; + buf[pos++] = (char)SAP_PDU_CMD_TYPE_REQ_OK; + memcpy(&buf[pos], bt_address, strlen(bt_address)); + memcpy(new_data->data, buf, new_data->data_len); + + free(bt_address); + } else { + new_data->data_len = SAP_DATA_LEN_MIN; + buf[pos++] = (char)new_data->data_len; + buf[pos++] = (char)SAP_PDU_CMD_BLUETOOTH; + buf[pos++] = (char)SAP_PDU_BT_CMD_GET_ADDRESS; + buf[pos++] = (char)SAP_PDU_CMD_TYPE_RESP_ERR; + memcpy(new_data->data, buf, new_data->data_len); + } + + wc_object_emit_callback(wo, WC_OBJECT_EVENT_SAP_SEND_DATA, new_data); + g_free(new_data); + break; + case SAP_PDU_CMD_TYPE_REQ_OK: + pos = evt_data->data_len - (unsigned int)SAP_DATA_LEN_MIN; + + bt_address = g_strndup(&evt_data->data[SAP_DATA_LEN_MIN], pos); + vconf_set_str(VCONFKEY_WECONN_AUTOCONNECTABLE_BT_ADDRESS, + bt_address); + g_free(bt_address); + + break; + } + + break; + case SAP_PDU_BT_CMD_GET_PROFILES: + DBG("SAP_PDU_BT_CMD_GET_PROFILES"); + + cmdType = (int)evt_data->data[3]; + switch (cmdType) { + case SAP_PDU_CMD_TYPE_REQ: + new_data = g_try_malloc0(sizeof(struct SAP_event_data)); + if (new_data == NULL) + return TRUE; + + new_data->count = evt_data->count; + new_data->index = evt_data->index; + new_data->data_len = SAP_DATA_LEN_MIN + strlen(profiles); + + buf[pos++] = (char)new_data->data_len; + buf[pos++] = (char)SAP_PDU_CMD_BLUETOOTH; + buf[pos++] = (char)SAP_PDU_BT_CMD_GET_PROFILES; + buf[pos++] = (char)SAP_PDU_CMD_TYPE_REQ_OK; + memcpy(&buf[pos], profiles, strlen(profiles)); + memcpy(new_data->data, buf, new_data->data_len); + + wc_object_emit_callback(wo, WC_OBJECT_EVENT_SAP_SEND_DATA, new_data); + g_free(new_data); + break; + } + + break; + case SAP_PDU_BT_CMD_GET_COD: + DBG("SAP_PDU_BT_CMD_GET_COD"); + + cmdType = (int)evt_data->data[3]; + switch (cmdType) { + case SAP_PDU_CMD_TYPE_REQ: + new_data = g_try_malloc0(sizeof(struct SAP_event_data)); + if (new_data == NULL) + return TRUE; + + new_data->count = evt_data->count; + new_data->index = evt_data->index; + new_data->data_len = SAP_DATA_LEN_MIN + sizeof(cod); + + buf[pos++] = (char)new_data->data_len; + buf[pos++] = (char)SAP_PDU_CMD_BLUETOOTH; + buf[pos++] = (char)SAP_PDU_BT_CMD_GET_COD; + buf[pos++] = (char)SAP_PDU_CMD_TYPE_REQ_OK; + memcpy(&buf[pos], cod, sizeof(cod)); + memcpy(new_data->data, buf, new_data->data_len); + + wc_object_emit_callback(wo, WC_OBJECT_EVENT_SAP_SEND_DATA, new_data); + g_free(new_data); + break; + case SAP_PDU_CMD_TYPE_REQ_OK: + break; + } + + break; + case SAP_PDU_BT_CMD_GET_SERVICES: + DBG("SAP_PDU_BT_CMD_GET_SERVICES"); + + cmdType = (int)evt_data->data[3]; + switch (cmdType) { + case SAP_PDU_CMD_TYPE_REQ: + new_data = g_try_malloc0(sizeof(struct SAP_event_data)); + if (new_data == NULL) + return TRUE; + + new_data->count = evt_data->count; + new_data->index = evt_data->index; + new_data->data_len = SAP_DATA_LEN_MIN + 1; + + buf[pos++] = (char)new_data->data_len; + buf[pos++] = (char)SAP_PDU_CMD_BLUETOOTH; + buf[pos++] = (char)SAP_PDU_BT_CMD_GET_SERVICES; + buf[pos++] = (char)SAP_PDU_CMD_TYPE_REQ_OK; + buf[pos++] = services; + memcpy(new_data->data, buf, new_data->data_len); + + wc_object_emit_callback(wo, WC_OBJECT_EVENT_SAP_SEND_DATA, new_data); + g_free(new_data); + break; + case SAP_PDU_CMD_TYPE_REQ_OK: + break; + } + + break; + case SAP_PDU_BT_CMD_DISCONNECTED_MANUALLY: + DBG("SAP_PDU_BT_CMD_DISCONNECTED_MANUALLY"); + + cmdType = (int)evt_data->data[3]; + switch (cmdType) { + case SAP_PDU_CMD_TYPE_NOTI: + new_data = g_try_malloc0(sizeof(struct SAP_event_data)); + if (new_data == NULL) + return TRUE; + + vconf_set_int(VCONFKEY_WECONN_DISCONNECTED_MANUALLY, 1); + + new_data->count = evt_data->count; + new_data->index = evt_data->index; + new_data->data_len = SAP_DATA_LEN_MIN; + + buf[pos++] = (char)new_data->data_len; + buf[pos++] = (char)SAP_PDU_CMD_BLUETOOTH; + buf[pos++] = evt_data->data[2]; + buf[pos++] = (char)SAP_PDU_CMD_TYPE_REQ_OK; + memcpy(new_data->data, buf, new_data->data_len); + + wc_object_emit_callback(wo, WC_OBJECT_EVENT_SAP_SEND_DATA, new_data); + g_free(new_data); + break; + default: + ERR("Invalid bt sub command %d", evt_data->data[3]); + break; + } + + break; + default: + ERR("Invalid bt sub command %d", evt_data->data[2]); + break; + } + + return TRUE; +} + +static void _bt_bearer_register_events(WcObject *wo) +{ + enum SAP_PDU_commands_type cmd_type = SAP_PDU_CMD_TYPE_REQ; + enum SAP_PDU_BT_commands bt_cmds = SAP_PDU_BT_CMD_GET_VERSION; + char *cmd = NULL; + char *event = NULL; + + cmd = g_strdup_printf("%s[%d]", + WC_OBJECT_EVENT_SAP_RECEIVE_DATA, SAP_PDU_CMD_BLUETOOTH); + + while (cmd_type < SAP_PDU_CMD_TYPE_MAX ) { + while (bt_cmds < SAP_PDU_BT_CMD_MAX) { + event = g_strdup_printf("%s[%d][%d]", cmd, bt_cmds, cmd_type); + wc_object_add_callback(wo, event, __on_bt_sap_cmd_cb, NULL); + g_free(event); + + bt_cmds++; + + if (bt_cmds == SAP_PDU_BT_CMD_FX_MAX) + bt_cmds = SAP_PDU_BT_CMD_DISCONNECTED_MANUALLY; + } + + bt_cmds = SAP_PDU_BT_CMD_GET_VERSION; + cmd_type++; + } + + g_free(cmd); +} + +static void _bt_bearer_deregister_events(WcObject *wo) +{ + enum SAP_PDU_commands_type cmd_type = SAP_PDU_CMD_TYPE_REQ; + enum SAP_PDU_BT_commands bt_cmds = SAP_PDU_BT_CMD_GET_VERSION; + char *cmd = NULL; + char *event = NULL; + + cmd = g_strdup_printf("%s[%d]", + WC_OBJECT_EVENT_SAP_RECEIVE_DATA, SAP_PDU_CMD_BLUETOOTH); + + while (cmd_type < SAP_PDU_CMD_TYPE_MAX ) { + while (bt_cmds < SAP_PDU_BT_CMD_MAX) { + event = g_strdup_printf("%s[%d][%d]", cmd, bt_cmds, cmd_type); + wc_object_del_callback(wo, event, __on_bt_sap_cmd_cb); + g_free(event); + + bt_cmds++; + + if (bt_cmds == SAP_PDU_BT_CMD_FX_MAX) + bt_cmds = SAP_PDU_BT_CMD_SET_PAN_CONFIGURATION_ON; + } + + bt_cmds = SAP_PDU_BT_CMD_GET_VERSION; + cmd_type++; + } + + g_free(cmd); +} + +static void _register_vconf_callback(WcObject *wo) +{ + int ret = VCONF_OK; + + if (!wo) + return; + + DBG(""); + + // TODO: use sap event callback + ret = vconf_notify_key_changed(VCONFKEY_SAP_CONNECTION_STATUS, + __sap_connection_status_changed_cb, wo); + if (ret != VCONF_OK) + ERR("Failed to register sap connection status callback(%d)", ret); + + ret = vconf_notify_key_changed(VCONFKEY_RFCOMM_READY_STATUS, + __rfcomm_ready_status_changed_cb, wo); + if (ret != VCONF_OK) + ERR("Failed to register sap connection status callback(%d)", ret); + + ret = vconf_notify_key_changed(VCONFKEY_SETUP_WIZARD_FIRST_BOOT, + __setup_wizard_state_cb, wo); + if (ret != VCONF_OK) + ERR("Failed to register setup wizard state callback(%d)", ret); + + ret = vconf_notify_key_changed(VCONFKEY_SETAPPL_PSMODE, __power_saving_mode_cb, wo); + if (ret != VCONF_OK) + ERR("Failed to register power saving mode callback (%d)", ret); + + ret = vconf_notify_key_changed(VCONFKEY_PM_KEY_IGNORE, __pm_key_ignore_cb, wo); + if (ret != VCONF_OK) + ERR("Failed to register pm key ignore callback (%d)", ret); +} + +static void _deregister_vconf_callback(void) +{ + int ret = VCONF_OK; + + DBG(""); + + ret = vconf_ignore_key_changed(VCONFKEY_SAP_CONNECTION_STATUS, + __sap_connection_status_changed_cb); + if (ret != VCONF_OK) + ERR("Failed to de-register sap connection status callback(%d)", ret); + + ret = vconf_ignore_key_changed(VCONFKEY_RFCOMM_READY_STATUS, + __rfcomm_ready_status_changed_cb); + if (ret != VCONF_OK) + ERR("Failed to register sap connection status callback(%d)", ret); + + ret = vconf_ignore_key_changed(VCONFKEY_SETUP_WIZARD_FIRST_BOOT, + __setup_wizard_state_cb); + if (ret != VCONF_OK) + ERR("Failed to de-register setup wizard state callback(%d)", ret); + + ret = vconf_ignore_key_changed(VCONFKEY_SETAPPL_PSMODE, + __power_saving_mode_cb); + if (ret != VCONF_OK) + ERR("Failed to de-register power saving mode callback (%d)", ret); + + ret = vconf_ignore_key_changed(VCONFKEY_PM_KEY_IGNORE, + __pm_key_ignore_cb); + if (ret != VCONF_OK) + ERR("Failed to de-register pm key ignore callback (%d)", ret); +} + +static void _register_bt_event_listener(WcObject *wo) +{ + if (!wo) + return; + + DBG(""); + + /* BT power event listener */ + bt_adapter_set_state_changed_cb( + __on_bt_adapter_state_changed, wo); + + /* BT HFP event listener */ + bt_audio_set_connection_state_changed_cb( + __on_bt_audio_connection_state_changed_cb, wo); + + /* BT SPP event listener */ + /* TODO: should register eSAP event listener */ + bt_socket_set_connection_state_changed_cb( + __on_bt_socket_connection_state_changed_cb, wo); + + /* TODO: use HFP rather than device state */ + bt_device_set_connection_state_changed_cb( + __on_bt_device_connection_state_changed_cb, wo); + + bt_device_set_bond_created_cb( + __on_bt_device_bond_created_cb, wo); + + bt_device_set_bond_destroyed_cb( + __on_bt_device_bond_destroy_cb, wo); +} + +static void _deregister_bt_event_listener(void) +{ + DBG(""); + + bt_adapter_unset_state_changed_cb(); + + bt_device_unset_bond_created_cb(); + + bt_device_unset_connection_state_changed_cb(); + + bt_audio_unset_connection_state_changed_cb(); + + bt_socket_unset_connection_state_changed_cb(); +} + +static int bt_bearer_init(WcObject *wo) +{ + int ret; + int sap_conn = SAP_DISCONNECTED; + const char *autoconnectable_bt_address = NULL; + bt_adapter_state_e state = BT_ADAPTER_DISABLED; + + ret = bt_initialize(); + if (ret != BT_ERROR_NONE) + return ret; + + ret = bt_audio_initialize(); + if (ret != BT_ERROR_NONE) + return ret; + + ret = alarmmgr_init("weconn"); + if (ret != ALARMMGR_RESULT_SUCCESS) + ERR("alarmmgr_init FAILED (%d)", ret); + + ret = bt_adapter_get_state(&state); + if (ret != BT_ERROR_NONE) + return ret; + + if (state == BT_ADAPTER_ENABLED) { + wc_object_emit_callback(wo, WC_OBJECT_EVENT_BEARER_ENABLED, NULL); + + /* Update SPP state */ + if (vconf_get_int(VCONFKEY_SAP_CONNECTION_STATUS, &sap_conn) < 0) { + ERR("Failed to get sap connection status, but go ahead."); + sap_connected = SAP_DISCONNECTED; + } else + sap_connected = sap_conn; + + autoconnectable_bt_address = + vconf_get_str(VCONFKEY_WECONN_AUTOCONNECTABLE_BT_ADDRESS); + DBG("last connected BT address(%s)", autoconnectable_bt_address); + if (autoconnectable_bt_address && + strlen(autoconnectable_bt_address) > 0) { + /* Update HFP status */ + hf_connected = __bt_check_connection_status( + autoconnectable_bt_address, __device_check_hfp_cb); + } else { + /* If not, clear white list, for example, factory reset */ + __bt_update_white_list(NULL, 0); + } + + g_free((gpointer)autoconnectable_bt_address); + + DBG("BT power(%d), rfcomm_ready(%d), SPP(%d) HFP(%d)", + state, sap_rfcomm_ready, sap_connected, hf_connected); + if (sap_rfcomm_ready == 1 && + sap_connected != SAP_CONNECTED && hf_connected != true) + __bt_start_advertising(true); + + wc_object_set_property(wo, XSTR(W_DEVICE_TYPE_BT), + XSTR(W_DEVICE_STATE_ENABLED)); + } + + _register_vconf_callback(wo); + + _register_bt_event_listener(wo); + + _bt_bearer_register_events(wo); + + return ret; +} + +static int bt_bearer_activate(WcObject *wo) +{ + int ret; + bt_adapter_state_e state = BT_ADAPTER_DISABLED; + + ret = bt_adapter_get_state(&state); + if (ret != BT_ERROR_NONE) + return ret; + + DBG("BT state = %d", state); + if (state == BT_ADAPTER_DISABLED) { + ret = bt_adapter_enable(); + if (ret != BT_ERROR_NONE) { + ERR("Failed to enable bluetooth device"); + return ret; + } + } + + DBG("Activated"); + + return 0; +} + +static int bt_bearer_deactivate(WcObject *wo) +{ + int ret; + bt_adapter_state_e state = BT_ADAPTER_DISABLED; + + DBG("Deactivated"); + + ret = bt_adapter_get_state(&state); + if (ret != BT_ERROR_NONE) + return ret; + + DBG("BT state = %d", state); + if (state == BT_ADAPTER_ENABLED) + bt_adapter_disable(); + + return 0; +} + +static void bt_bearer_exit(WcObject *wo) +{ + DBG("Unloaded"); + + _bt_bearer_deregister_events(wo); + + _deregister_vconf_callback(); + + _deregister_bt_event_listener(); + + bt_deinitialize(); + + alarmmgr_fini(); +} + +EXPORT_API struct bearer_driver_desc bearer_driver_desc = { + .name = "bluetooth", + .technology = WC_TECHNOLOGY_BLUETOOTH, + + .init = bt_bearer_init, + .activate = bt_bearer_activate, + .deactivate = bt_bearer_deactivate, + .exit = bt_bearer_exit, +}; diff --git a/plugins/esap.c b/plugins/esap.c new file mode 100644 index 0000000..c030061 --- /dev/null +++ b/plugins/esap.c @@ -0,0 +1,866 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include + +#include "esap.h" +#include "control.h" +#include "plugin.h" + + +#define BUF_SIZE 1024 +#define SAP_CHANNEL_ID 222 + +#define SAP_CONNECTION_VCONF_NOTIFICATION "memory/private/sap/conn_status" + +struct esap_private_data { + WcObject *wo_esap; + unsigned char role; +}; + +static unsigned int sap_service_handle; +static unsigned int sap_local_agentId; +static struct esap_private_data *gpd; +static GSList *send_pdu_list = NULL; + +static int __util_send_sap_pdu_data(GSList *pdu_list); +static int __util_process_sap_pdu_data(WcObject *wo, GSList **pdu_list); +static unsigned int __util_encode_sap_pdu_data(GSList *pdu_list, char **dst); +static int __util_decode_sap_pdu_data(char *src, unsigned int src_len, GSList **pdu_list); + +static int __util_process_sap_pdu_data(WcObject *wo, GSList **pdu_list) +{ + unsigned int index = 0; + int count = 0; + char *event; + GSList *list; + struct SAP_PDU_data *pdu_data = NULL; + struct SAP_event_data *evt_data = NULL; + + index = g_slist_length(send_pdu_list); + for (list = *pdu_list; list; list = list->next) { + pdu_data = (struct SAP_PDU_data *)list->data; + if (pdu_data != NULL) { + evt_data = g_try_malloc0(sizeof(struct SAP_event_data)); + if (evt_data == NULL) + return -1; + + evt_data->index = index; + evt_data->count = g_slist_length(*pdu_list); + evt_data->data_len = (unsigned int)pdu_data->length; + memcpy(evt_data->data, pdu_data->data, evt_data->data_len); + + event = g_strdup_printf("%s[%d][%d][%d]", WC_OBJECT_EVENT_SAP_RECEIVE_DATA, + evt_data->data[1], evt_data->data[2], evt_data->data[3]); + + wc_object_emit_callback(wo, event, evt_data); + g_free(evt_data); + g_free(event); + count++; + } + } + + return count; +} + +static int __util_send_sap_pdu_data(GSList *pdu_list) +{ + char *buf; + unsigned int len = 0; + int res = -1; + + if (g_slist_length(pdu_list) <= 0) { + ERR("No PDU data to send"); + return -1; + } + + len = __util_encode_sap_pdu_data(pdu_list, &buf); + if (len > 0) { + res = sendData(sap_service_handle, SAP_CHANNEL_ID, (unsigned short int)len, (void*)buf); + if (res == 0) + DBG("Completed to send data %d byte", len); + else + ERR("Failed to send data, error %d", res); + + g_free(buf); + } + + return res; +} + +static unsigned int __util_encode_sap_pdu_data(GSList *pdu_list, char **dst) +{ + GSList *list; + struct SAP_PDU_data *pdu_data = NULL; + char buf[SAP_DATA_LEN_MAX] = {0}; + unsigned int length = 0; + unsigned int index = 0; + + for (list = pdu_list; list != NULL; list = list->next) { + pdu_data = (struct SAP_PDU_data *)list->data; + if (pdu_data != NULL) { + if (pdu_data->main_cmd == 0 || pdu_data->sub_cmd == 0 || + pdu_data->type_cmd == 0) { + ERR("Invalid PDU data: main_cmd[%d], sub_cmd[%d], type_cmd[%d]", + pdu_data->main_cmd, pdu_data->sub_cmd, pdu_data->type_cmd); + continue; + } + + length = (unsigned int)pdu_data->length + (unsigned int)SAP_DATA_LEN_MIN; + buf[index++] = (char)length; + buf[index++] = pdu_data->main_cmd; + buf[index++] = pdu_data->sub_cmd; + buf[index++] = pdu_data->type_cmd; + memcpy(&buf[index], pdu_data->data, (unsigned int)pdu_data->length); + + index += (unsigned int)pdu_data->length; + } + } + + *dst = g_try_malloc0(index + 1); + g_strlcat(*dst, buf, index + 1); + + DBG("Completed to encode data %d byte", index); + return index; +} + +static int __util_decode_sap_pdu_data(char *src, unsigned int src_len, GSList **pdu_list) +{ + int res = -1; + unsigned int pos = 0; + unsigned int data_len = 0; + struct SAP_PDU_data *pdu_data = NULL; + + if (src_len < SAP_DATA_LEN_MIN) { + ERR("invalid data length, %d", src_len); + return res; + } + + while (pos < src_len) { + pdu_data = g_try_malloc0(sizeof(struct SAP_PDU_data)); + if (!pdu_data || !src) { + ERR("invalid input parameter !!!"); + return res; + } + + data_len = (unsigned int)src[pos]; + if (data_len < SAP_DATA_LEN_MIN) { + ERR("invalid pdu data length, %d", data_len); + goto error; + } + + memcpy(pdu_data->data, &src[pos], data_len); + pdu_data->length = (char)data_len; + + *pdu_list = g_slist_append(*pdu_list, pdu_data); + pos += data_len; + } + + if (*pdu_list) { + res = (int)g_slist_length(*pdu_list); + DBG("Done decode data counts = %d", res); + } + + return res; + +error: + if (g_slist_length(*pdu_list) > 0) + g_slist_free_full(*pdu_list, g_free); + else { + if (pdu_data != NULL) + g_free(pdu_data); + } + + return res; +} + +static int _request_feature_exchange(GSList *feat_list) +{ + GSList *list; + GSList *pdu_list = NULL; + struct SAP_PDU_data *sap_data = NULL; + + if (feat_list == NULL) + return 0; + + for (list = feat_list; list != NULL; list = list->next) { + if (g_strcmp0(list->data, XSTR(SAP_PDU_BT_CMD_GET_VERSION)) == 0) { + sap_data = g_try_malloc0(sizeof(struct SAP_PDU_data)); + if (!sap_data) { + ERR("invalid input parameter !!!"); + return -1; + } + + sap_data->main_cmd = SAP_PDU_CMD_BLUETOOTH; + sap_data->sub_cmd = SAP_PDU_BT_CMD_GET_VERSION; + sap_data->type_cmd = SAP_PDU_CMD_TYPE_REQ; + pdu_list = g_slist_append(pdu_list, sap_data); + } else if (g_strcmp0(list->data, XSTR(SAP_PDU_BT_CMD_GET_ADDRESS)) == 0) { + sap_data = g_try_malloc0(sizeof(struct SAP_PDU_data)); + if (!sap_data) { + ERR("invalid input parameter !!!"); + return -1; + } + + sap_data->main_cmd = SAP_PDU_CMD_BLUETOOTH; + sap_data->sub_cmd = SAP_PDU_BT_CMD_GET_ADDRESS; + sap_data->type_cmd = SAP_PDU_CMD_TYPE_REQ; + pdu_list = g_slist_append(pdu_list, sap_data); + } else if (g_strcmp0(list->data, XSTR(SAP_PDU_BT_CMD_GET_PROFILES)) == 0) { + sap_data = g_try_malloc0(sizeof(struct SAP_PDU_data)); + if (!sap_data) { + ERR("invalid input parameter !!!"); + return -1; + } + + sap_data->main_cmd = SAP_PDU_CMD_BLUETOOTH; + sap_data->sub_cmd = SAP_PDU_BT_CMD_GET_PROFILES; + sap_data->type_cmd = SAP_PDU_CMD_TYPE_REQ; + pdu_list = g_slist_append(pdu_list, sap_data); + } else if (g_strcmp0(list->data, XSTR(SAP_PDU_GENERIC_CMD_GET_MANUFACTURER)) == 0) { + sap_data = g_try_malloc0(sizeof(struct SAP_PDU_data)); + if (!sap_data) { + ERR("invalid input parameter !!!"); + return -1; + } + + sap_data->main_cmd = SAP_PDU_CMD_GENERIC; + sap_data->sub_cmd = SAP_PDU_GENERIC_CMD_GET_MANUFACTURER; + sap_data->type_cmd = SAP_PDU_CMD_TYPE_REQ; + pdu_list = g_slist_append(pdu_list, sap_data); + } else { + ERR("Not support feature : %s", (char *)list->data); + } + } + + __util_send_sap_pdu_data(pdu_list); + + g_slist_free_full(pdu_list, g_free); + + return 0; +} + +static void on_register_service_agent_confirm(int wStatusCode, unsigned int uwLocalAgentId) +{ + DBG("wStatusCode: %d, uwLocalAgentId: %d", wStatusCode, uwLocalAgentId); + + if (wStatusCode != RESULT_SUCCESS) { + DBG("Register service agent failed.\n"); + return; + } + + sap_local_agentId = uwLocalAgentId; + + if (gpd->role == SAP_ROLE_PROVIDER) { + DBG("No need to call findPeerAgent!!"); + return; + } + + if (findPeerAgent(uwLocalAgentId) != RESULT_SUCCESS) { + DBG("fail to call findPeerAgent.\n"); + // TBD: error handling + return; + } + DBG("success to call findPeerAgent\n"); +} + +static void on_deregister_service_agent_confirm(int wStatusCode, unsigned int uwLocalAgentId) +{ + DBG("wStatusCode: %d, uwLocalAgentId: %d", wStatusCode, uwLocalAgentId); +} + +static void on_peer_agent(unsigned int uwLocalAgentId, PeerAgent* pPeerAgent, int wStatusCode) +{ + DBG("wStatusCode: %d, uwLocalAgentId: %d", wStatusCode, uwLocalAgentId); + + if (wStatusCode != RESULT_SUCCESS) { + DBG("find peer agent failed."); + //TBD: error handling + return; + } else if (wStatusCode == RESULT_SUCCESS) { + DBG("find peer agent success: ALE Name %s, Id %s", pPeerAgent->pchALEName, pPeerAgent->pchASPID); + + if(createServiceConnection(uwLocalAgentId, pPeerAgent) != RESULT_SUCCESS) { + ERR("fail to create service connection"); + // TBD: error handling + return; + } + } +} + +static void on_service_connection_confirm(unsigned int uwLocalAgentId, unsigned int uwServiceHandle, PeerAgent* pPeerAgent, int wStatusCode) +{ + DBG("uwLocalAgentId: %d, uwServiceHandle: %d, wStatusCode: %d", uwLocalAgentId, uwServiceHandle, wStatusCode); + + if (wStatusCode != RESULT_SUCCESS) { + DBG("service connection fail.\n"); + // TBD: retry logic + } else { + DBG("service connection success.\n"); + sap_service_handle = uwServiceHandle; + } +} + +static void on_service_connection_indication(unsigned int uwLocalAgentId, unsigned int uwServiceHandle, PeerAgent* pPeerAgent) +{ + WcObject *wo = NULL; + + DBG("uwLocalAgentId: %d, uwServiceHandle: %d", + uwLocalAgentId, uwServiceHandle); + + if (acceptServiceConnection(uwLocalAgentId, uwServiceHandle, + pPeerAgent) != RESULT_SUCCESS) { + ERR("fail to accept service connection"); + // TBD: error handling + return; + } else { + GSList *list = NULL; + + DBG("success to accept service connection"); + // wait for service connection confirm? - Need to check. + sap_service_handle = uwServiceHandle; + + wo = wc_object_find(WC_TECHNOLOGY_BLUETOOTH); + if (wo) { + wc_object_set_property(wo, WC_OBJECT_EVENT_SAP_BCMSERVICE_STATUS, + XSTR(W_SERVICE_STATE_CONNECTED)); + } + + list = g_slist_append(list, XSTR(SAP_PDU_BT_CMD_GET_VERSION)); + list = g_slist_append(list, XSTR(SAP_PDU_GENERIC_CMD_GET_MANUFACTURER)); + + _request_feature_exchange(list); + } +} + +static void on_service_termination_indication(unsigned int uwServiceHandle, int wStatusCode) +{ + WcObject *wo = NULL; + + DBG("uwServiceHandle: %d, wStatusCode: %d", uwServiceHandle, wStatusCode); + + wo = wc_object_find(WC_TECHNOLOGY_BLUETOOTH); + if (!wo) { + ERR("No technology"); + return; + } + + wc_object_set_property(wo, WC_OBJECT_EVENT_SAP_BCMSERVICE_STATUS, + XSTR(W_SERVICE_STATE_DISCONNECTED)); +} + +static void on_receive(unsigned int uwServiceHandle, + unsigned short int uwChannelId, + unsigned int uwPayloadLength, void *pvBuffer) +{ + int res = 0; + WcObject *wo = NULL; + GSList *pdu_list = NULL; + char payload[BUF_SIZE] = { '\0', }; + + if (pvBuffer == NULL) + return; + + DBG("uwServiceHandle:%d, uwChannelId:%d, uwPayloadLength:%d\n", + uwServiceHandle, uwChannelId, uwPayloadLength); + + wo = wc_object_find(WC_TECHNOLOGY_BLUETOOTH); + if (!wo) { + ERR("No technology"); + return; + } + + memset(payload, 0, BUF_SIZE); + memcpy(payload, pvBuffer, uwPayloadLength); + + res = __util_decode_sap_pdu_data(payload, uwPayloadLength, &pdu_list); + if (res > 0) { + res = __util_process_sap_pdu_data(wo, &pdu_list); + DBG("processed %d data", res); + } else { + ERR("Mal-formed data"); + } +} + +static void on_space_available(unsigned int uwServiceHandle, + unsigned short int uwChannelId, unsigned int ulSpaceAvailable) +{ + DBG("uwChannelId: %d, ulSpaceAvailable: %d", uwChannelId, ulSpaceAvailable); +} + +static void on_error(unsigned int uwServiceHandle, unsigned int dwErrorCode) +{ + DBG("uwServiceHandle: %d, dwErrorCode: %d\n", uwServiceHandle, dwErrorCode); +} + +static SAPNotification sap_noti_funcs = { + on_register_service_agent_confirm, + on_receive, + on_service_connection_indication, + on_service_termination_indication, + on_service_connection_confirm, + on_peer_agent, + on_space_available, + on_error, + on_deregister_service_agent_confirm, +}; + +static gboolean _esap_service_init(WcObject *wo) +{ + ServiceDesc *service; + ChannelDesc *channel; + int ret; + + if (!gpd || !wo) + return FALSE; + + // register callbacks + ret = registerNotifications(sap_noti_funcs); + if (ret != RESULT_SUCCESS) { + ERR("Fail to register notification"); + return FALSE; + } + + DBG("registerNotifications success: sap_service_handle %d", sap_service_handle); + // Define service description + service = (ServiceDesc *)g_try_malloc0(sizeof(ServiceDesc)); + if (service == NULL) { + ERR("Memory allocation fail for ServiceDesc"); + return FALSE; + } + + channel = (ChannelDesc *)g_try_malloc0(sizeof(ChannelDesc)); + if (channel == NULL) { + ERR("Memory allocation fail for ChannelDesc"); + g_free(service); + return FALSE; + } + + channel->wChannelID = SAP_CHANNEL_ID; + channel->ubNoOfPayloadType = 1; // which value is to be set??? + channel->payloadTypeList = SAP_ALL_PAYLOAD; // Json or binary + + service->pchASPID = "/system/bcmservice"; + service->pchSPFName = "/system/bcmservice"; + service->wConnectionTypeMask = SAP_ALL; // all connectivity + service->ubRole = gpd->role; + service->pChDescArray = channel; + service->numChannels = 1; + + // register service + // ret = getRegisteredServiceAgent(service->pchASPID, SERVICE_AGENT_ROLE_PROVIDER); + ret = registerServiceAgent(service); + if (ret != RESULT_SUCCESS) { + ERR("registerServiceAgent Fail with reason: %d", ret); + g_free(service); + g_free(channel); + return FALSE; + } + + wc_object_set_property(wo, WC_OBJECT_EVENT_SAP_BCMSERVICE_STATUS, + XSTR(W_SERVICE_STATE_DISCONNECTED)); + + DBG("SAP service registered(%s)", (gpd->role == SAP_ROLE_PROVIDER) ? "PROVIDER" : "CONSUMER"); + return TRUE; +} + +static gboolean __on_esap_event_send_data_cb(WcObject *wo, + const void *event_info, void *user_data) +{ + struct SAP_event_data *evt_data = NULL; + struct SAP_send_data *send_data = NULL; + int res; + + evt_data = (struct SAP_event_data *)event_info; + if (evt_data == NULL) + return TRUE; + + if (evt_data->count > 1) { + send_data = g_slist_nth_data(send_pdu_list, evt_data->index); + if (send_data == NULL) { + send_data = g_try_malloc0(sizeof(struct SAP_send_data)); + if (send_data == NULL) + return TRUE; + + send_data->count = 1; + send_data->data_len = evt_data->data_len; + memcpy(send_data->data, evt_data->data, evt_data->data_len); + send_pdu_list = g_slist_append(send_pdu_list, send_data); + } else { + send_data->count++; + + if (send_data->count == evt_data->count) { + memcpy(&send_data->data[send_data->data_len], + evt_data->data, evt_data->data_len); + send_data->data_len += evt_data->data_len; + + res = sendData(sap_service_handle, SAP_CHANNEL_ID, + (unsigned short int)send_data->data_len, + (void*)send_data->data); + if (res == 0) + DBG("Completed to send data %d byte", send_data->data_len); + else + ERR("Failed to send data, error %d", res); + + g_free(send_data); + send_pdu_list = g_slist_remove(send_pdu_list, send_data); + } else { + memcpy(&send_data->data[send_data->data_len], + evt_data->data, evt_data->data_len); + send_data->data_len += evt_data->data_len; + } + } + } else { + res = sendData(sap_service_handle, SAP_CHANNEL_ID, + (unsigned short int)evt_data->data_len, + (void*)evt_data->data); + if (res == 0) + DBG("Completed to send data %d byte", evt_data->data_len); + else + ERR("Failed to send data, error %d", res); + } + + return TRUE; +} + +static gboolean _on_generic_sap_cmd_cb(WcObject *wo, + const void *event_info, void *user_data) +{ + struct SAP_event_data *evt_data = NULL; + struct SAP_event_data *new_data = NULL; + char buf[SAP_DATA_LEN_MAX] = { '\0', }; + char *value = NULL; + unsigned int len; + unsigned int pos = 0; + int mainCmd, subCmd, cmdType; + int ret; + + evt_data = (struct SAP_event_data *)event_info; + if (evt_data == NULL) + return TRUE; + + mainCmd = (int)evt_data->data[1]; + if (mainCmd != SAP_PDU_CMD_GENERIC) { + ERR("Invalid generic command %d", mainCmd); + return TRUE; + } + + subCmd = (int)evt_data->data[2]; + switch (subCmd) { + case SAP_PDU_GENERIC_CMD_GET_PLATFORM_NAME: + DBG("SAP_PDU_GENERIC_CMD_GET_PLATFORM_NAME"); + + cmdType = (int)evt_data->data[3]; + switch (cmdType) { + case SAP_PDU_CMD_TYPE_REQ: + ret = system_info_get_value_string(SYSTEM_INFO_KEY_PLATFORM_NAME, + &value); + if (ret == 0 && value != NULL) { + new_data = g_try_malloc0(sizeof(struct SAP_event_data)); + if (new_data == NULL) + return TRUE; + + new_data->count = evt_data->count; + new_data->index = evt_data->index; + new_data->data_len = SAP_DATA_LEN_MIN + strlen(value); + + buf[pos++] = (char)new_data->data_len; + buf[pos++] = (char)SAP_PDU_CMD_GENERIC; + buf[pos++] = (char)SAP_PDU_GENERIC_CMD_GET_PLATFORM_VERSION; + buf[pos++] = (char)SAP_PDU_CMD_TYPE_REQ_OK; + memcpy(&buf[pos], value, strlen(value)); + memcpy(new_data->data, buf, new_data->data_len); + + g_free(value); + + wc_object_emit_callback(wo, WC_OBJECT_EVENT_SAP_SEND_DATA, new_data); + g_free(new_data); + } + break; + case SAP_PDU_CMD_TYPE_REQ_OK: + break; + default: + break; + } + break; + case SAP_PDU_GENERIC_CMD_GET_PLATFORM_VERSION: + DBG("SAP_PDU_GENERIC_CMD_GET_PLATFORM_VERSION"); + + cmdType = (int)evt_data->data[3]; + switch (cmdType) { + case SAP_PDU_CMD_TYPE_REQ: + ret = system_info_get_value_string(SYSTEM_INFO_KEY_TIZEN_VERSION, + &value); + if (ret == 0 && value != NULL) { + new_data = g_try_malloc0(sizeof(struct SAP_event_data)); + if (new_data == NULL) + return TRUE; + + new_data->count = evt_data->count; + new_data->index = evt_data->index; + new_data->data_len = SAP_DATA_LEN_MIN + strlen(value); + + buf[pos++] = (char)new_data->data_len; + buf[pos++] = (char)SAP_PDU_CMD_GENERIC; + buf[pos++] = (char)SAP_PDU_GENERIC_CMD_GET_PLATFORM_VERSION; + buf[pos++] = (char)SAP_PDU_CMD_TYPE_REQ_OK; + memcpy(&buf[pos], value, strlen(value)); + memcpy(new_data->data, buf, new_data->data_len); + + g_free(value); + + wc_object_emit_callback(wo, WC_OBJECT_EVENT_SAP_SEND_DATA, new_data); + g_free(new_data); + } + break; + case SAP_PDU_CMD_TYPE_REQ_OK: + break; + default: + ERR("Invalid generic type command(%d)", cmdType); + break; + } + break; + case SAP_PDU_GENERIC_CMD_GET_MANUFACTURER: + DBG("SAP_PDU_GENERIC_CMD_GET_MANUFACTURER"); + + cmdType = (int)evt_data->data[3]; + switch (cmdType) { + case SAP_PDU_CMD_TYPE_REQ: + ret = system_info_get_value_string(SYSTEM_INFO_KEY_MANUFACTURER, + &value); + if (ret == 0 && value != NULL) { + new_data = g_try_malloc0(sizeof(struct SAP_event_data)); + if (new_data == NULL) + return TRUE; + + new_data->count = evt_data->count; + new_data->index = evt_data->index; + new_data->data_len = SAP_DATA_LEN_MIN + strlen(value); + + buf[pos++] = (char)new_data->data_len; + buf[pos++] = (char)SAP_PDU_CMD_GENERIC; + buf[pos++] = (char)SAP_PDU_GENERIC_CMD_GET_MANUFACTURER; + buf[pos++] = (char)SAP_PDU_CMD_TYPE_REQ_OK; + memcpy(&buf[pos], value, strlen(value)); + memcpy(new_data->data, buf, new_data->data_len); + + g_free(value); + + wc_object_emit_callback(wo, WC_OBJECT_EVENT_SAP_SEND_DATA, new_data); + g_free(new_data); + } + break; + case SAP_PDU_CMD_TYPE_REQ_OK: + len = evt_data->data_len - (unsigned int)SAP_DATA_LEN_MIN; + + value = g_strndup(&evt_data->data[SAP_DATA_LEN_MIN], len); + vconf_set_str(VCONFKEY_WECONN_CONNECTED_MANUFACTURER, value); + + g_free(value); + break; + case SAP_PDU_CMD_TYPE_RESP_ERR: + break; + case SAP_PDU_CMD_TYPE_NOTI: + break; + default: + ERR("Invalid generic type command(%d)", cmdType); + break; + } + break; + default: + ERR("Invalid generic sub command(%d)", subCmd); + break; + } + + return TRUE; +} + +static void _esap_bearer_register_events(WcObject *wo) +{ + enum SAP_PDU_commands_type cmd_type = SAP_PDU_CMD_TYPE_REQ; + enum SAP_PDU_GENERIC_commands generic_cmds = SAP_PDU_GENERIC_CMD_GET_PLATFORM_NAME; + char *cmd = NULL; + char *event = NULL; + + cmd = g_strdup_printf("%s[%d]", + WC_OBJECT_EVENT_SAP_RECEIVE_DATA, SAP_PDU_CMD_GENERIC); + + while (cmd_type < SAP_PDU_CMD_TYPE_MAX ) { + while (generic_cmds < SAP_PDU_GENERIC_CMD_MAX) { + event = g_strdup_printf("%s[%d][%d]", cmd, generic_cmds, cmd_type); + wc_object_add_callback(wo, event, _on_generic_sap_cmd_cb, NULL); + g_free(event); + + generic_cmds++; + } + + generic_cmds = SAP_PDU_GENERIC_CMD_GET_PLATFORM_NAME; + cmd_type++; + } + + wc_object_add_callback(wo, WC_OBJECT_EVENT_SAP_SEND_DATA, + __on_esap_event_send_data_cb, NULL); + + g_free(cmd); +} + +static void _esap_bearer_deregister_events(WcObject *wo) +{ + enum SAP_PDU_commands_type cmd_type = SAP_PDU_CMD_TYPE_REQ; + enum SAP_PDU_GENERIC_commands generic_cmds = SAP_PDU_GENERIC_CMD_GET_PLATFORM_NAME; + char *cmd = NULL; + char *event = NULL; + + cmd = g_strdup_printf("%s[%d]", + WC_OBJECT_EVENT_SAP_RECEIVE_DATA, SAP_PDU_CMD_GENERIC); + + while (cmd_type < SAP_PDU_CMD_TYPE_MAX ) { + while (generic_cmds < SAP_PDU_GENERIC_CMD_MAX) { + event = g_strdup_printf("%s[%d][%d]", cmd, generic_cmds, cmd_type); + wc_object_del_callback(wo, event, _on_generic_sap_cmd_cb); + g_free(event); + + generic_cmds++; + } + + generic_cmds = SAP_PDU_GENERIC_CMD_GET_PLATFORM_NAME; + cmd_type++; + } + + wc_object_del_callback(wo, WC_OBJECT_EVENT_SAP_SEND_DATA, + __on_esap_event_send_data_cb); + + g_free(cmd); +} + +static void __esap_connection_changed_cb(keynode_t *key, void *data) +{ + int status = 0; + WcObject *wo = (WcObject *)data; + + vconf_get_int(SAP_CONNECTION_VCONF_NOTIFICATION, &status); + DBG("status : %d", status); + + if (status == 1) { + _esap_service_init(wo); + + _esap_bearer_register_events(wo); + } else { + DBG("%s is disconnected", SAP_CONNECTION_VCONF_NOTIFICATION); + + _esap_bearer_deregister_events(wo); + + wc_object_set_property(wo, WC_OBJECT_EVENT_SAP_BCMSERVICE_STATUS, + XSTR(W_SERVICE_STATE_DISCONNECTED)); + } +} + +static int esap_bearer_init(WcObject *wo) +{ + int type; + unsigned char role; + + type = wc_control_get_device_type(); + if (type == PROP_CONTROL_TYPE_WEARABLE) { + role = SAP_ROLE_PROVIDER; + } else if (type == PROP_CONTROL_TYPE_HOST) { + role = SAP_ROLE_CONSUMER; + } else { + ERR("Unknown type"); + return -EINVAL; + } + + gpd = g_try_new0(struct esap_private_data, 1); + if (!gpd) + return -ENOMEM; + + /* FIXME: why WcObject -> wo.element -> WcObject (circular dependency) */ + gpd->wo_esap = wo; + gpd->role = role; + + /* FIXEME: test code */ + do { + int status = 0; + vconf_get_int(SAP_CONNECTION_VCONF_NOTIFICATION, &status); + + if (status == 1) + _esap_service_init(wo); + else + vconf_notify_key_changed(SAP_CONNECTION_VCONF_NOTIFICATION, + __esap_connection_changed_cb, wo); + } while(0); + + return 0; +} + +static int esap_bearer_activate(WcObject *wo) +{ + gboolean ret; + + ret = _esap_service_init(wo); + DBG("Activated: %d", ret); + return 0; +} + +static int esap_bearer_deactivate(WcObject *wo) +{ + DBG("Deactivated"); + + return 0; +} + +static int esap_bearer_connect(WcObject *wo, const char* address) +{ + DBG("Connected"); + // TBD: request connect() through SAP interface + return 0; +} + +static int esap_bearer_disconnect(WcObject *wo) +{ + DBG("Disconnected"); + // TBD: request disconnect() through SAP interface + return 0; +} + +static void esap_bearer_exit(WcObject *wo) +{ + vconf_ignore_key_changed(SAP_CONNECTION_VCONF_NOTIFICATION, + __esap_connection_changed_cb); + + if (gpd) { + g_free(gpd); + gpd = NULL; + } +} + +EXPORT_API struct bearer_driver_desc bearer_driver_desc = { + .name = "esap", + .technology = WC_TECHNOLOGY_BLUETOOTH, + + .init = esap_bearer_init, + .activate = esap_bearer_activate, + .deactivate = esap_bearer_deactivate, + .connect = esap_bearer_connect, + .disconnect = esap_bearer_disconnect, + .exit = esap_bearer_exit, +}; diff --git a/plugins/esap.h b/plugins/esap.h new file mode 100644 index 0000000..efa49ae --- /dev/null +++ b/plugins/esap.h @@ -0,0 +1,160 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __WECONN_ESAP_H__ +#define __WECONN_ESAP_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "log.h" +#include "events.h" +#include "object.h" + +#define SAP_ROLE_PROVIDER 0x00 +#define SAP_ROLE_CONSUMER 0x01 + +#define SAP_DATA_LEN_MIN 4 +#define SAP_DATA_LEN_MAX 255 +#define SAP_DATA_MAC_ADDR_LEN 17 + +#define VCONFKEY_WECONN_CONNECTED_BT_VERSION \ + "file/private/weconn/connected_bt_version" +#define VCONFKEY_WECONN_DISCONNECTED_MANUALLY \ + "file/private/weconn/disconnected_manually" +#define VCONFKEY_WECONN_CONNECTED_MANUFACTURER \ + "file/private/weconn/connected_manufacturer" +#define VCONFKEY_WECONN_AUTOCONNECTABLE_BT_ADDRESS \ + "file/private/weconn/autoconnectable_unique_bt_target_address" + +#define VCONFKEY_WECONN_ALL_CONNECTED "memory/private/weconn/all_connected" +#define VCONFKEY_RFCOMM_READY_STATUS "memory/private/sap/rfcomm_ready" +#define VCONFKEY_SAP_CONNECTION_STATUS "memory/private/sap/conn_status" +#define SAP_DISCONNECTED 0 +#define SAP_CONNECTED 1 + +enum SAP_PDU_commands_type { + SAP_PDU_CMD_TYPE_REQ = 0x01, + SAP_PDU_CMD_TYPE_REQ_OK = 0x02, + SAP_PDU_CMD_TYPE_RESP_ERR = 0x03, + SAP_PDU_CMD_TYPE_NOTI = 0x04, + SAP_PDU_CMD_TYPE_MAX = 0x05, +}; + +enum SAP_PDU_commands { + SAP_PDU_CMD_GENERIC = 0x01, + SAP_PDU_CMD_BLUETOOTH = 0x02, + SAP_PDU_CMD_WIFI = 0x03, + SAP_PDU_CMD_CELLULAR = 0x04, + SAP_PDU_CMD_ETHERNET = 0x05, +}; + +enum SAP_PDU_BT_commands { + SAP_PDU_BT_CMD_UNKNOWN = 0x00, + SAP_PDU_BT_CMD_GET_VERSION = 0x01, + SAP_PDU_BT_CMD_GET_ADDRESS = 0x02, + SAP_PDU_BT_CMD_GET_PROFILES = 0x03, + SAP_PDU_BT_CMD_GET_COD = 0x04, + SAP_PDU_BT_CMD_GET_SERVICES = 0x05, + SAP_PDU_BT_CMD_FX_MAX = 0x06, + + SAP_PDU_BT_CMD_SET_PAN_CONFIGURATION_ON = 0x11, + SAP_PDU_BT_CMD_SET_PAN_CONFIGURATION_OFF = 0x12, + SAP_PDU_BT_CMD_SET_PAN_CONNECTION = 0x13, + SAP_PDU_BT_CMD_DISCONNECTED_MANUALLY = 0x14, + SAP_PDU_BT_CMD_MAX = 0x15, +}; + +enum SAP_PDU_BT_version { + SAP_PDU_BT_VER_UNKOWN = 0x00, // unknown + SAP_PDU_BT_VER_30 = 0x01, // 3.0 + SAP_PDU_BT_VER_40_LE = 0x02, // 4.0 LE ONLY + SAP_PDU_BT_VER_40_DUAL = 0x03, // 4.0 DUAL + SAP_PDU_BT_VER_40_P = 0x04, // 4.0' + SAP_PDU_BT_VER_41 = 0x05, // 4.1 +}; + +enum SAP_PDU_BT_profile { + SAP_PDU_BT_PROFILE_UNKOWN = 0x00, + SAP_PDU_BT_PROFILE_HFP = 0x01, + SAP_PDU_BT_PROFILE_SPP = 0x02, + SAP_PDU_BT_PROFILE_GATT = 0x03, + SAP_PDU_BT_PROFILE_PAN = 0x04, + SAP_PDU_BT_PROFILE_MAX = 0x05, +}; + +enum SAP_PDU_WIFI_commands { + SAP_PDU_WIFI_CMD_UNKNOWN = 0x00, + SAP_PDU_WIFI_CMD_GET_VERSION = 0x01, + SAP_PDU_WIFI_CMD_GET_MAC_ADDR = 0x02, + SAP_PDU_WIFI_CMD_GET_IP_ADDR = 0x03, + SAP_PDU_WIFI_CMD_MAX = 0x04, +}; + +enum SAP_PDU_CELLULAR_commands { + SAP_PDU_CELLULAR_CMD_UNKNOWN = 0x00, + SAP_PDU_CELLULAR_CMD_GET_IP_ADDR = 0x01, + SAP_PDU_CELLULAR_CMD_MAX = 0x02, +}; + +enum SAP_PDU_ETHERNET_commands { + SAP_PDU_ETHERNET_CMD_UNKNOWN = 0x00, + SAP_PDU_ETHERNET_CMD_GET_MAC_ADDR = 0x01, + SAP_PDU_ETHERNET_CMD_GET_IP_ADDR = 0x02, + SAP_PDU_ETHERNET_CMD_MAX = 0x03, +}; + +enum SAP_PDU_GENERIC_commands { + SAP_PDU_GENERIC_CMD_UNKNOWN = 0x00, + SAP_PDU_GENERIC_CMD_GET_PLATFORM_NAME = 0x01, + SAP_PDU_GENERIC_CMD_GET_PLATFORM_VERSION = 0x02, + SAP_PDU_GENERIC_CMD_GET_MANUFACTURER = 0x03, + SAP_PDU_GENERIC_CMD_MAX = 0x04, +}; + +struct SAP_PDU_data { + char length; + char main_cmd; + char sub_cmd; + char type_cmd; + char data[SAP_DATA_LEN_MAX]; +}; + +struct SAP_event_data { + unsigned int index; + unsigned int count; + unsigned int data_len; + char data[SAP_DATA_LEN_MAX]; +}; + +struct SAP_send_data { + unsigned int index; + unsigned int count; + unsigned int data_len; + char data[SAP_DATA_LEN_MAX]; +}; + +typedef struct _SAP_PDU_CMD_Callbacks SAP_PDU_CMD_Callbacks; + +#ifdef __cplusplus +} +#endif + +#endif /* __WECONN_PLUGIN_H__ */ diff --git a/plugins/pan.c b/plugins/pan.c new file mode 100644 index 0000000..d31323b --- /dev/null +++ b/plugins/pan.c @@ -0,0 +1,923 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "util.h" +#include "plugin.h" +#include "esap.h" +#include "technology.h" + +static __thread guint weconn_conn_subscribe_id_connman_state = 0; + +static int pan_bearer_connect(WcObject *wo, const char *address); +static int pan_bearer_disconnect(WcObject *wo); + +static const char **__get_pan_profile(GDBusConnection *connection) +{ + GVariant *reply; + unsigned int n = 0; + GError *error = NULL; + const char *obj_path = NULL; + const char **obj_array = NULL; + GVariantIter *iter = NULL, *next = NULL; + GSList *string_list = NULL, *slist; + + reply = g_dbus_connection_call_sync(connection, + "net.connman", + "/", + "net.connman.Manager", + "GetServices", + NULL, + NULL, + G_DBUS_CALL_FLAGS_NONE, + DBUS_REPLY_TIMEOUT, + NULL, + &error); + + if (error) { + ERR("Failed to request (%s)", error->message); + g_error_free(error); + + if (reply) + g_variant_unref(reply); + + return NULL; + } + + if (!reply) { + ERR("Failed to request"); + return NULL; + } + + g_variant_get(reply, "(a(oa{sv}))", &iter); + while (g_variant_iter_loop(iter, "(oa{sv})", &obj_path, &next)) { + if (g_str_has_prefix(obj_path, "/net/connman/service/bluetooth_") + != TRUE) + continue; + + n++; + string_list = g_slist_prepend(string_list, g_strdup(obj_path)); + } + + if (iter) + g_variant_iter_free(iter); + + if (next) + g_variant_iter_free(next); + + g_variant_unref(reply); + + obj_array = g_try_new(const char *, n + 1); + if (!obj_array) { + ERR("No memory"); + return NULL; + } + + obj_array[n--] = NULL; + for (slist = string_list; slist; slist = slist->next) + obj_array[n--] = slist->data; + + g_slist_free(string_list); + + return obj_array; +} + +#ifdef USE_STATIC_IP +static int __request_pan_set_dns(GDBusConnection *connection, + const char *obj_path) +{ + GVariantBuilder *builder; + GVariant *params = NULL; + GVariant *reply = NULL; + GError *error = NULL; + int err = -EIO; + + const char *prop_nameserver_config = "Nameservers.Configuration"; + const char *dns = "192.168.44.1"; + + builder = g_variant_builder_new(G_VARIANT_TYPE ("as")); + g_variant_builder_add(builder, "s", dns); + params = g_variant_new("(sv)", + prop_nameserver_config, g_variant_builder_end(builder)); + g_variant_builder_unref(builder); + + reply = g_dbus_connection_call_sync(connection, + "net.connman", + obj_path, + "net.connman.Service", + "SetProperty", + params, + NULL, + G_DBUS_CALL_FLAGS_NONE, + DBUS_REPLY_TIMEOUT, + NULL, + &error); + + if (error) { + ERR("Failed to request (%s)", error->message); + if (g_strrstr(error->message, "net.connman.Error.AlreadyConnected")) + err = -EISCONN; + else if (g_strrstr(error->message, "net.connman.Error.NotConnected")) + err = -ENOTCONN; + + g_error_free(error); + + if (reply) + g_variant_unref(reply); + + return err; + } + + if (!reply) { + ERR("Failed to request"); + return -EIO; + } + + g_variant_unref(reply); + + return 0; +} + +static int __request_pan_set_static_ip(GDBusConnection *connection, + const char *obj_path) +{ + GVariantBuilder *builder; + GVariant *params = NULL; + GVariant *reply = NULL; + GError *error = NULL; + int err = -EIO; + + const char *prop_ipv4_config = "IPv4.Configuration"; + const char *prop_method = "Method"; + const char *prop_address = "Address"; + const char *prop_gateway = "Gateway"; + const char *prop_netmask = "Netmask"; + const char *manual_method = "manual"; + const char *ipaddress = "192.168.44.179"; + const char *netmask = "255.255.255.0"; + const char *gateway = "192.168.44.1"; + + builder = g_variant_builder_new(G_VARIANT_TYPE ("a{sv}")); + + g_variant_builder_add(builder, "{sv}", prop_method, g_variant_new_string(manual_method)); + g_variant_builder_add(builder, "{sv}", prop_address, g_variant_new_string(ipaddress)); + g_variant_builder_add(builder, "{sv}", prop_netmask, g_variant_new_string(netmask)); + g_variant_builder_add(builder, "{sv}", prop_gateway, g_variant_new_string(gateway)); + + params = g_variant_new("(sv)", + prop_ipv4_config, g_variant_builder_end(builder)); + g_variant_builder_unref(builder); + + reply = g_dbus_connection_call_sync(connection, + "net.connman", + obj_path, + "net.connman.Service", + "SetProperty", + params, + NULL, + G_DBUS_CALL_FLAGS_NONE, + DBUS_REPLY_TIMEOUT, + NULL, + &error); + + if (error) { + ERR("Failed to request (%s)", error->message); + if (g_strrstr(error->message, "net.connman.Error.AlreadyConnected")) + err = -EISCONN; + else if (g_strrstr(error->message, "net.connman.Error.NotConnected")) + err = -ENOTCONN; + + g_error_free(error); + + if (reply) + g_variant_unref(reply); + + return err; + } + + if (!reply) { + ERR("Failed to request"); + return -EIO; + } + + g_variant_unref(reply); + + return 0; +} +#endif + +static int __request_pan_command(GDBusConnection *connection, + const char *obj_path, const char *method) +{ + int err = -EIO; + GVariant *reply; + GError *error = NULL; + +#ifdef USE_STATIC_IP + err = __request_pan_set_static_ip(connection, obj_path); + if (err < 0) + SECURE_ERR("Failed to set static IP(%d)", err); + + err = __request_pan_set_dns(connection, obj_path); + if (err < 0) + SECURE_ERR("Failed to set DNS(%d)", err); +#endif + + reply = g_dbus_connection_call_sync(connection, + "net.connman", + obj_path, + "net.connman.Service", + method, + NULL, + NULL, + G_DBUS_CALL_FLAGS_NONE, + DBUS_REPLY_TIMEOUT, + NULL, + &error); + + if (error) { + ERR("Failed to request (%s)", error->message); + if (g_strrstr(error->message, "net.connman.Error.AlreadyConnected")) + err = -EISCONN; + else if (g_strrstr(error->message, "net.connman.Error.NotConnected")) + err = -ENOTCONN; + + g_error_free(error); + + if (reply) + g_variant_unref(reply); + + return err; + } + + if (!reply) { + ERR("Failed to request"); + return -EIO; + } + + g_variant_unref(reply); + + return 0; +} + +static int _pan_bearer_request_pan_configuration(WcObject *wo, gboolean enable) +{ + struct SAP_event_data *new_data = NULL; + char buf[SAP_DATA_LEN_MAX] = { '\0', }; + int pos = 0; + + new_data = g_try_malloc0(sizeof(struct SAP_event_data)); + if (new_data == NULL) + return -ENOMEM; + + new_data->count = 1; + new_data->index = 1; + new_data->data_len = (char)SAP_DATA_LEN_MIN; + + buf[pos++] = (char)new_data->data_len; + buf[pos++] = (char)SAP_PDU_CMD_BLUETOOTH; + + if (enable == TRUE) + buf[pos++] = (char)SAP_PDU_BT_CMD_SET_PAN_CONFIGURATION_ON; + else + buf[pos++] = (char)SAP_PDU_BT_CMD_SET_PAN_CONFIGURATION_OFF; + + buf[pos++] = (char)SAP_PDU_CMD_TYPE_REQ; + + memcpy(new_data->data, buf, new_data->data_len); + + wc_object_emit_callback(wo, WC_OBJECT_EVENT_SAP_SEND_DATA, new_data); + g_free(new_data); + + return 0; +} + +static GVariant *_get_properties(GDBusConnection *connection, + const char *obj_path) +{ + GVariant *reply; + GError *error = NULL; + + if (!connection || !obj_path) + return NULL; + + reply = g_dbus_connection_call_sync(connection, + "net.connman", + obj_path, + "net.connman.Service", + "GetProperties", + NULL, + NULL, + G_DBUS_CALL_FLAGS_NONE, + DBUS_REPLY_TIMEOUT, + NULL, + &error); + + if (error) { + ERR("Failed to request (%s)", error->message); + g_error_free(error); + + if (reply) + g_variant_unref(reply); + + return NULL; + } + + if (!reply) { + ERR("Failed to request"); + return NULL; + } + + return reply; +} + +static int _get_pan_state(WcObject *wo) +{ + int ret = W_SERVICE_STATE_DISCONNECTED; + unsigned int i, num = 0; + GError *error = NULL; + const char **obj_array = NULL; + GDBusConnection *connection = NULL; + GVariantIter *iter = NULL; + GVariant *reply; + GVariant *value = NULL; + gchar *key = NULL; + + if (!wo) + return -EINVAL; + + DBG(""); + + connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (!connection) { + ERR("Failed to get system bus[%s]", error->message); + g_error_free(error); + return -EIO; + } + + obj_array = __get_pan_profile(connection); + num = g_strv_length((char **)obj_array); + if (!num) { + ERR("Failed to get profiles"); + return -ENXIO; + } + + for (i=0; imessage); + g_error_free(error); + return -EIO; + } + + obj_array = __get_pan_profile(connection); + num = g_strv_length((char **)obj_array); + if (!num) { + ERR("Failed to get profiles"); + return -ENXIO; + } + + for (i=0; imessage); + g_error_free(error); + return -EIO; + } + + obj_array = __get_pan_profile(connection); + num = g_strv_length((char **)obj_array); + if (!num) { + ERR("Failed to get profiles"); + return -ENXIO; + } + + for (i=0; idata[1]; + if (mainCmd != SAP_PDU_CMD_BLUETOOTH) { + ERR("Invalid bluetooth command(%02x)", mainCmd); + return TRUE; + } + + subCmd = (int)evt_data->data[2]; + switch (subCmd) { + case SAP_PDU_BT_CMD_SET_PAN_CONFIGURATION_ON: + DBG("SAP_PDU_BT_CMD_SET_PAN_CONFIGURATION_ON"); + break; + case SAP_PDU_BT_CMD_SET_PAN_CONFIGURATION_OFF: + DBG("SAP_PDU_BT_CMD_SET_PAN_CONFIGURATION_OFF"); + break; + case SAP_PDU_BT_CMD_SET_PAN_CONNECTION: + DBG("SAP_PDU_BT_CMD_SET_PAN_CONNECTION"); + + cmdType = (int)evt_data->data[3]; + switch (cmdType) { + case SAP_PDU_CMD_TYPE_REQ: + res = _pan_connect(wo); + if (res < 0) { + ERR("Failed pan connect"); + + __pan_set_property(wo, XSTR(W_SERVICE_TYPE_BT_PAN), + XSTR(W_SERVICE_STATE_DISCONNECTED)); + } + + break; + default: + break; + } + + break; + case SAP_PDU_BT_CMD_DISCONNECTED_MANUALLY: + DBG("SAP_PDU_BT_CMD_DISCONNECTED_MANUALLY"); + + cmdType = (int)evt_data->data[3]; + switch (cmdType) { + case SAP_PDU_CMD_TYPE_NOTI: + pan_state = wc_object_get_property(wo, XSTR(W_SERVICE_TYPE_BT_PAN)); + if (g_strcmp0(pan_state, XSTR(W_SERVICE_STATE_CONNECTED)) == 0) { + res = _pan_disconnect(wo); + if (res < 0) + ERR("Failed pan connect"); + } + + g_free(pan_state); + break; + default: + break; + } + break; + default: + break; + } + + return TRUE; +} + +static void _pan_bearer_register_events(WcObject *wo) +{ + enum SAP_PDU_commands_type cmd_type = SAP_PDU_CMD_TYPE_REQ; + enum SAP_PDU_BT_commands pan_cmds = SAP_PDU_BT_CMD_SET_PAN_CONFIGURATION_ON; + char *cmd = NULL; + char *event = NULL; + + cmd = g_strdup_printf("%s[%d]", + WC_OBJECT_EVENT_SAP_RECEIVE_DATA, SAP_PDU_CMD_BLUETOOTH); + + while (cmd_type < SAP_PDU_CMD_TYPE_MAX ) { + while (pan_cmds < SAP_PDU_BT_CMD_MAX) { + event = g_strdup_printf("%s[%d][%d]", cmd, pan_cmds, cmd_type); + wc_object_add_callback(wo, event, _on_pan_sap_cmd_cb, NULL); + g_free(event); + + pan_cmds++; + } + + pan_cmds = SAP_PDU_BT_CMD_SET_PAN_CONFIGURATION_ON; + cmd_type++; + } + + g_free(cmd); +} + +static void _pan_connect_cb(service_h request, + service_h reply, service_result_e result, void *user_data) +{ + int ret; + char *resp = NULL; + const char *connected = NULL; + WcObject *wo = (WcObject *)user_data; + + if (!wo) { + ERR("WcObject is NULL"); + return; + } + + if (result != SERVICE_RESULT_SUCCEEDED) { + ERR("service reply failed = [%d]", result); + return; + } + + ret = service_get_extra_data(reply, "_WCPOPUP_RESP_", &resp); + if (ret != SERVICE_ERROR_NONE) { + ERR("failed to retrieve response = [%d]", ret); + return; + } + + if (g_strcmp0(resp, "OK") == 0) { + connected = (const char *)g_hash_table_lookup( + wc_object_peek_property_hash(wo), XSTR(W_SERVICE_TYPE_BT_SPP)); + if (g_strcmp0(connected, XSTR(W_SERVICE_STATE_CONNECTED)) != 0) { + ERR("CM does not connected correctly"); + __pan_set_property(wo, XSTR(W_SERVICE_TYPE_BT_PAN), + XSTR(W_SERVICE_STATE_DISCONNECTED)); + return; + } + + ret = _pan_bearer_request_pan_configuration(wo, TRUE); + if (ret < 0) + ERR("Failed pan configuration on"); + else + DBG("Requested pan configuration on"); + } else if (g_strcmp0(resp, "CANCEL") == 0) { + __pan_set_property(wo, XSTR(W_SERVICE_TYPE_BT_PAN), + XSTR(W_SERVICE_STATE_DISCONNECTED)); + } +} + +static void _pan_disconnect_cb(service_h request, + service_h reply, service_result_e result, void *user_data) +{ + int ret; + char *resp = NULL; + const char *connected = NULL; + WcObject *wo = (WcObject *)user_data; + + if (!wo) { + ERR("WcObject is NULL"); + return; + } + + if (result != SERVICE_RESULT_SUCCEEDED) { + ERR("service reply failed = [%d]", result); + return; + } + + ret = service_get_extra_data(reply, "_WCPOPUP_RESP_", &resp); + if (ret != SERVICE_ERROR_NONE) { + ERR("failed to retrieve response = [%d]", ret); + return; + } + + if (g_strcmp0(resp, "OK") == 0) { + ret = _pan_disconnect(wo); + if (ret < 0) + ERR("Failed pan connect"); + } else if (g_strcmp0(resp, "CANCEL") == 0) { + connected = (const char *)g_hash_table_lookup( + wc_object_peek_property_hash(wo), XSTR(W_SERVICE_TYPE_BT_SPP)); + if (g_strcmp0(connected, XSTR(W_SERVICE_STATE_CONNECTED)) == 0) { + __pan_set_property(wo, XSTR(W_SERVICE_TYPE_BT_PAN), + XSTR(W_SERVICE_STATE_CONNECTED)); + } + } +} + +static void __notify_bt_service_state_changed(const char *state, void *user_data) +{ + static wc_technology_service_state_e pan_service_state_old = 0; + + WcObject *wo = (WcObject *)user_data; + if (!wo) + return; + + if (!state) { + ERR("state is NULL"); + return; + } + + DBG("bt_service_state_changed : %s", state); + + if (g_strcmp0(state, "idle") == 0) { + pan_service_state_old = WC_SERVICE_STATE_IDLE; + } else if (g_strcmp0(state, "disconnect") == 0) { + if (pan_service_state_old > WC_SERVICE_STATE_DISCONNECTED) { + __pan_set_property(wo, XSTR(W_SERVICE_TYPE_BT_PAN), + XSTR(W_SERVICE_STATE_DISCONNECTED)); + } + pan_service_state_old = WC_SERVICE_STATE_DISCONNECTED; + } else if (g_strcmp0(state, "failure") == 0) { + if (pan_service_state_old > WC_SERVICE_STATE_DISCONNECTED) { + __pan_set_property(wo, XSTR(W_SERVICE_TYPE_BT_PAN), + XSTR(W_SERVICE_STATE_DISCONNECTED)); + } + pan_service_state_old = WC_SERVICE_STATE_FAILURE; + } else if (g_strcmp0(state, "association") == 0) { + pan_service_state_old = WC_SERVICE_STATE_ASSOCIATION; + } else if (g_strcmp0(state, "configuration") == 0) { + pan_service_state_old = WC_SERVICE_STATE_CONFIGURATION; + } else if (g_strcmp0(state, "ready") == 0) { + if (pan_service_state_old < WC_SERVICE_STATE_READY) { + __pan_set_property(wo, XSTR(W_SERVICE_TYPE_BT_PAN), + XSTR(W_SERVICE_STATE_CONNECTED)); + } + pan_service_state_old = WC_SERVICE_STATE_READY; + } else if (g_strcmp0(state, "online") == 0) { + if (pan_service_state_old < WC_SERVICE_STATE_READY) { + __pan_set_property(wo, XSTR(W_SERVICE_TYPE_BT_PAN), + XSTR(W_SERVICE_STATE_CONNECTED)); + } + pan_service_state_old = WC_SERVICE_STATE_ONLINE; + } +} + +static void __handle_service_state_changed(const gchar *path, + const char *key, const char *state, void *user_data) +{ + weconn_device_type_e device_type; + + if (path == NULL || key == NULL || state == NULL) + return; + + device_type = wc_device_get_type_from_path(path); + switch (device_type) { + case W_DEVICE_TYPE_BT: + __notify_bt_service_state_changed(state, user_data); + break; + case W_DEVICE_TYPE_CELLULAR: + break; + case W_DEVICE_TYPE_WIFI: + break; + case W_DEVICE_TYPE_WIFI_P2P: + break; + case W_DEVICE_TYPE_WIFI_ADHOC: + break; + case W_DEVICE_TYPE_ETHERNET: + break; + default: + break; + } +} + +static void __pan_connman_service_signal_filter(GDBusConnection *conn, + const gchar *name, const gchar *path, const gchar *interface, + const gchar *sig, GVariant *param, gpointer user_data) +{ + const char *key = NULL; + const char *value = NULL; + GVariant *var; + + if (g_strcmp0(sig, CONNMAN_SIGNAL_PROPERTY_CHANGED) == 0) { + g_variant_get(param, "(sv)", &key, &var); + + if (g_strcmp0(key, "State") == 0) { + g_variant_get(var, "s", &value); + __handle_service_state_changed(path, key, value, user_data); + } else if (g_strcmp0(key, "Error") == 0) { + g_variant_get(var, "s", &value); + ERR("Error (%s)", value); + } + + g_free((gchar *)value); + g_free((gchar *)key); + if (var != NULL) + g_variant_unref(var); + } else { + ERR("No handle signal(%s)", sig); + } +} + +static int pan_bearer_init(WcObject *wo) +{ + GError *error = NULL; + GDBusConnection *connection = NULL; + guint id; + + _pan_bearer_register_events(wo); + + connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (!connection) { + ERR("Failed to get system bus %s", error->message); + g_error_free(error); + return -1; + } + + id = g_dbus_connection_signal_subscribe( + connection, + CONNMAN_SERVICE, + CONNMAN_SERVICE_INTERFACE, + CONNMAN_SIGNAL_PROPERTY_CHANGED, + NULL, + "State", + G_DBUS_SIGNAL_FLAGS_NONE, + __pan_connman_service_signal_filter, + wo, + NULL); + + if (id == 0) { + ERR("Failed register signals (%d)", id); + return -1; + } + + weconn_conn_subscribe_id_connman_state = id; + + return 0; +} + +static int pan_bearer_connect(WcObject *wo, const char *address) +{ + int err = -EIO; + char *connected = NULL; + weconn_service_state_e state; + + if (!wo) + return -EINVAL; + + connected = wc_object_get_property(wo, WC_OBJECT_EVENT_SAP_BCMSERVICE_STATUS); + if (connected != NULL) { + if (g_strcmp0(connected, XSTR(W_SERVICE_STATE_CONNECTED)) != 0) { + ERR("CM does not connected correctly"); + g_free(connected); + return -EIO; + } + g_free(connected); + } + + + state = _get_pan_state(wo); + if (state == W_SERVICE_STATE_CONNECTING) { + DBG("InProgress..."); + return -EINPROGRESS; + } else if (state == W_SERVICE_STATE_CONNECTED) { + DBG("already connected"); + return -EISCONN; + } + + err = wc_launch_popup(WC_POPUP_TYPE_PAN_CONNECT, _pan_connect_cb, wo); + if (err < 0) + ERR("failed launch pan popup"); + + return err; +} + +static int pan_bearer_disconnect(WcObject *wo) +{ + int err = -EIO; + weconn_service_state_e state; + + state = _get_pan_state(wo); + if (state == W_SERVICE_STATE_CONNECTING) { + DBG("InProgress..."); + return -EINPROGRESS; + } else if (state == W_SERVICE_STATE_DISCONNECTED) { + DBG("Not connected"); + return -ENOTCONN; + } + + err = wc_launch_popup(WC_POPUP_TYPE_PAN_DISCONNECT, _pan_disconnect_cb, wo); + if (err < 0) + ERR("failed launch pan popup"); + + return err; +} + +static void pan_bearer_exit(WcObject *wo) +{ + GError *error = NULL; + GDBusConnection *connection = NULL; + + DBG("Unloaded"); + + connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (!connection) { + ERR("Failed to get system bus %s", error->message); + g_error_free(error); + return; + } + + g_dbus_connection_signal_unsubscribe(connection, + weconn_conn_subscribe_id_connman_state); +} + +EXPORT_API struct bearer_driver_desc bearer_driver_desc = { + .name = "pan", + .technology = WC_TECHNOLOGY_BLUETOOTH, + + .init = pan_bearer_init, + .connect = pan_bearer_connect, + .disconnect = pan_bearer_disconnect, + .exit = pan_bearer_exit, +}; diff --git a/plugins/plugin.h b/plugins/plugin.h new file mode 100644 index 0000000..35cb80a --- /dev/null +++ b/plugins/plugin.h @@ -0,0 +1,52 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __WECONN_PLUGIN_H__ +#define __WECONN_PLUGIN_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "log.h" +#include "dbus.h" +#include "util.h" +#include "events.h" +#include "object.h" +#include "technology.h" + +struct bearer_driver_desc { + const char *name; + const char *technology; + + int (*init)(WcObject *wcdrv); + int (*activate)(WcObject *wcdrv); + int (*deactivate)(WcObject *wcdrv); + int (*connect)(WcObject *wcdrv, const char *address); + int (*disconnect)(WcObject *wcdrv); + void (*exit)(WcObject *wcdrv); +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __WECONN_PLUGIN_H__ */ diff --git a/resources/etc/dbus-1/system.d/weconn.conf b/resources/etc/dbus-1/system.d/weconn.conf new file mode 100644 index 0000000..b7a275f --- /dev/null +++ b/resources/etc/dbus-1/system.d/weconn.conf @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/etc/opt/upgrade/500.net.weconn.patch.sh b/resources/etc/opt/upgrade/500.net.weconn.patch.sh new file mode 100755 index 0000000..6e1fc6d --- /dev/null +++ b/resources/etc/opt/upgrade/500.net.weconn.patch.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +#vconf +if [ ! -e /opt/var/kdb/memory/private/weconn/all_connected ]; then +/usr/bin/vconftool set -tf int memory/private/weconn/all_connected 0 -s system::vconf_network -i +fi diff --git a/resources/mdbus2 b/resources/mdbus2 new file mode 100755 index 0000000000000000000000000000000000000000..e36f4c7ec910081cefe3f9bd3a24b192554e96c6 GIT binary patch literal 282661 zcmb5X51f@_{{MfUnKN~o#yT-En^_K`u?7c&LCc9Kgj0?F(3&%v=?_zLOcU8{caQ!M z8itbC+74nv)}SS;7{tc5yIEPS4Rhvn^hXFS?XvnkU)O!#bu}B`&u<%Pw2ug)8EejpHV{J%V>%u#OXGmcXMT${~z@*Jn2!ihK`%F3LhRGw^I=QyoZ z0h2=1Qwn;i3s7w?K-P9kNl3zAB*HlaPfByXTC)AI>>V*2* zwrBt1aV-)~9#lVT+O9Yd-znB$B9CP;Hhb)-Vl zi==5J{XOdfr`m%j0Ppnh`5qKFigXX@1!egA0qG!vVQ=8k9)1q6KdCS2Nz#u={@+hP zWRputgGmjfuA~x@{>~*$B{h<6CtXbP|Ly`ANJ^7r(Na<+>2#9*6m$ApNm@$kMJgj* zPEtIbL7G82j?_&04e4ppWRm`(E^uZ7uk>&qbOV`4!nID^Bg(&ojB~e_AL+ppfd_l| z*}&hC#*i)~okEI`ZX=yUdYE(#Nq^%>14!qSrjSl09ZFh8>Ou1VZU%Y8%Utcje!!o5 zxD6C%`ul?moF%~fJiG~bzlYBTUgF^bzx42*c~G%v|DES$uCR&V`Ck4F-g}Eu=mqjk`NY z{$FLf`>bK^F@bdOhjzPP8GN z^5x`fJlgRd919%f;rjwTI}x9-^1rfh9~}lVlr)dDz#~58K^xp_XDobA-wagj7iCf8)CCF-Cro(<7>Ans&jB z6&LsFa?ZIW2Mo=-djDw!H7>NH;(w_ zs5iHV<}GeXwZ)>>9d*RC)qz)fW?w41=itLWjqXN1rE#J^*-kniMN+KF z7QVkh&D^B9$G4;Ak<&3PyhlK#rxIq&>#nMnDcT2q0@StlgMK6P)$FFx+E zf9SHaTPmN6A6vYB*#SrO`pG}mo;2~?o0s%M&{KI+1&k2&J)>UncY z-}u`vLV2ruc76T)Nyk6e{Mip4*!g19n?L$s_|&T&oA&%|Z`^aKqt_Sw(w+nWDKk4KJd9~55x)MbfFzO6X^;)Oqa?fU=x`>ya=`}J!+ z?k`jK|M<{QOse|}r(Ilunx^Z^qunSS`qeH*Up_tGbC%-`-jjXZ0o2L_!RJF@zr;gxs( z{^f5UE_pk)_?q9JcIxEUI{MTPJ8)O#_F4B`dF$w#{&CjOwTJxe!P8!tcKbOS=52ZU z`RrX&pQ>Hol78Te+65>6?XmNJc*UsCmY$V2@s&#_FUK|`KfULMt#5t$gNEM+4*T=@ ze_GuAsg6G7gD)rxJW;WE*85#k>GuzP;uja^j(Op=had0w+t{N+fBox`=brcWwq1RG zcYV=;FP)H{eN3OW*5l8-7#X|#<>8i_x6hmU?)%e^dK~%O4}NsVU%u_RbMc`k z{^$?AoIX#lzJBxblh&?({qMzsRor?TWkanip8wXKTDV zKI{GuM!vUsPWiMehCjUO@mtOt`R2v}XKg=n`Ul_kys>H8ej92RowL4m)|Fej5B~R# z!{(1Z@7B5%y zAmi>yQ*6plU=rMd~M*MkXot_CZA%J(i}jN>#Q%(KeR{5#&r^SCK z`7+C1cM?BQtNyLnbCnhUUnB2)%YG*@{@bkjPoRHaEB_?+8)4aN6Y{OF+Rw(O%Psz= zQ@-Ak?`-0yzg54E`fXPIJCNratG{Q^r`@XmE%|aQ{}TMyS^4)Fe`_s&-GjbsE&eZK z-^Et^e1*QNEPV!HuTd7iKKQrRvZpqdamyb^Vvl-DKNYRB`U_CL#>!8Cf5hVd1oEUT zdCsN%3akF(#Lp~CpPSKloYnqn^c!T!w-)_3TJiK}%9p@Te_`jdUERpfx8(mvr}970 zew$T(8TP5P5by`x1VXKM|WVh!W>Q zkoA_mzQCV$|ED0zxHDaO4&do$6ZK0iexIPE4Z9_+u~^5=EPv&@q3TKI3a@$_}cB}q}lrOgOZ=v5* zOWz)hmr<7dQS6tt`2B+X3akAh^5s_j#k6m-^2_Ldww1pT`>(e0N08rO@z10EHS^tZ-pzl->sZI$nb{d-&G&G=`h#cw|K=Ueh^#otq`_?Qg; zu;u?hqW?Cl{vh(^&=qh1=@HTf%f3C4KVsEif&4Qpc}C*zahAOG@GG*)Unf4&R{suX z-;H%OwVZv|>FE0ur@DIVgocULlbUK8o2sjw>I<){9#S`U{G_J3#&hdyCQYiFzpIB30J+Wp&od`b5-Gr-ZCXF9meeL818jc=U(^xZ_Hj^CH zvl)$Un2@ZmYZ{LLbrYK!Z>SzKdE#hgO-%_Y?t;}a%N|Rh8k{>(1;kc8Ik18K^#=;PVjai#8#H6Oi@e{{Fo-}@}v0L5s zWUiV#zP@(1IuL4_8YWOX*T0Vk`oy~HeV!K8$b!|R<#jdnlj|s(P*Y#uFdC#` z;^;bKBAE;)K)LGby3tL$B^c9Krv|7wDOo?>mfA?+75kb<0yI4FR5fu8G7e+c6Cu^L z<0p-7m^e|)C)6|{zO9n4F#S~5T|2p^9tJ*BUxB@ifC;N7)7Qx-N$A~b)YsKqCxQLO zl1SA~t+0qmd?rLyL98CzFfHzg_4 z`E`APvA9K*B*xPsHr6#wCWyw5sUA}^zP|eUn#PG<7-4{MHIu3*F(}4Qp{9{>{6uwG zJ*lCw$(Ri@Vaw4qwm4#>{%lXO1x<+g}eiCDz8d$mUn(8r)4HGm) z>=nztBAz>46T5NYa!0~()lD^5)e}5)Q9ZG4%DzO=*xMtE!6o^Gq{flo36V~$OuWV- zso&a$$*_k=3wgx&Ok={0eobK2$-260WK&|70!t_{7&EzEQRKz5JD&VLRqlDyCm9Df zG!Q=%X=MhAhUuP2*fZ*?N1NV9*G#gs^DQlz_LkDM{rKx@jM?`La@Ul5@{N<#v+1VM zo2PesX1r!oN-Z7Tn1EK_p!vrt@duNyn$gx$&7E~b#kOwPdEr7JXe3E*?CnWxv+=B% ztl4YC?2ZA~Zjwn8*PgkgJS(Ke2i2-I>PmUH#=Jm^V9rO~*b38=BLLF3ttJP^yahPxNn zo?!m0R$J3lLuFqM)1m8Q>{&PQI?DDMbN<57)Hsm<-(A|&u*WSvFS}R6S249`bi3o% z>f4_S_7w@cCr#H!4H$Z~raz;#@3YaP>l-FbZmh#&jg!W)9~gG&1^uh5hgJ=_;F1AU z9^c^AHyS~xy=wBL>WK}tT200@XcfDLLAzT-zs7{Rrg04t{kjHbFYOK#Yn}zQJMcKw zG-{!Rk*|T_zwg%GEKF0$V8-kX6ScURHp#|@DK{9Z4Chn#NV-=CdjzRA;pTeSZ{+#= zf90~8O=6X*_Sc2$$2W~5kdyT{R8O)*@{1>FO=+mT!RPOKU@wdAHlS+_cNW{*78uDQ z+cH|@PJBG~V4EcC%5DMn*lLePzHoaiX0hERI?+^9m073VnaIewdvIBv z+@n}+-I$um^-a}H<0sTLOx6l#8d1T-%J&V+&f|6OHtJ2=X;&?KOYKi{{A?&uHH#MqXlHF-T(#$^?zKPun@k^RKf%#6uVIPIH zbqYSy=zlA?)~%T|di;3J-c1eKw7R*lVB-yyS`XpO)c~YFs z)IE{O#nag}h}l;EFIWHZ!ehJlEaSFx#h=_Xrc%@8pk`?a40fR0$=iKU(j<4VmyVD`}n9#GW}oVa=CY|8L-;!UAA{_-%{k6)}6~b=lAHfuM$a+(AkJr zt$6%+PAc-8uc7I00cV&x`_S25F&7B*POg{(61_Vp9_XHd7jvmb?*@u5a^G1LpX0u3 zEaps3XX?dVhS1r5@nHANy?6v?#cv0kgRbSBlOUHdb_!m{8Is_cbo_VV9L~f96P(ov zPT)*Mkjn>QK`tZ|3v$*{BFLqJQo$vT6A`TA%t&w|XSsr7Ir9>Hh_gAt<2h3nZq$R&$Wf?RM&2y$7XR`7n# zh6K5^QZI--lY&3v%uVoW&Z-2l{8YgtXN7`yaKu>r{EFzQ?L(bvx1y~Z4l&wO}ilHT^j{waW*S>B4=)b_4r?~0RIcl#s7l+@xNdM z|Gxv|C1FtTNc=C@7yk&b|AI5|zu?pOU+_2hUvM)17d#vP z3(m&>g3Ra}1c&2)!6y7KxB~wR-iiMO@5BFsr{aIXO8hT)I{p`Y68|$_9CQKx7o3Ox z1y92Nf{){W!4vSm;6eCb@N)bwcnSU&ybJ#eUWoq%e~14CtMI?zmH1!qQ2a001OE#? zf&T^1#s7jo!2g2f_+M}U{ujI*{|jD<{{=6`|AJHTzu+?bFL*Tm7kn1~3r@rTf-(Fr zcp&~4JOuv>R^xxcd+@*DW%ysP5&sKbgZ~A8f&T@M!~cRWVyD<~TYBa>AB9pYHwW8~ z%LUsP<$ln9M|)`d3%OVyCZ5;s=s4hw*&VU3R^)uV(!!$_US{DD3oo(ou!V;#+_CVT zU+(E^yM=GI@OBGdZ{ce#e6@wQS@?1bZ?*6x7M`~7`4-+{;j=6}W#LmSJZa(MEIeW1 zBP={_;R7u^X5p0<9<}f?3y)ZMiG_zPJY?aHh41{AWq%9bY~k$|zTU#uTKH-UZ?o{_ z7T#*%ODsHX;qxuL#lmM@Dd9T zTX@LA9Sh&N!?M4HZ?^Du3tw;HYb|`Wg|}Jwatm*@@FfSa-b_-u`;cG2? zwS~7?_;L$xweTetp0@D$7T#jvvn)Ji;ZrO;Y2o86JYnG@EIe-E11&se;guF1weT_v zk63t#g@-LXWZ{m5@BG}dzlCqM@OBGdZ{ce#e6@wQS@?1bZ?*6x7M`~7`4-+{;j=6} zW#LmSJZa(MEIeW1BP={_;R7u^X5p0<9<}f?3y)ZMiG_zPJY?aHh41{#vcK@wbk=E4 zXM^E|8~2N(GrxIv!{MAG?AWO@O-G8TfqR_^Es9EUR$wBI>73cJVNH#Yrw+3|@Sui(6xYO7K{v%d;=|tkYb241CZTF5Vy9*2mXx z?tgb}4YQYXVrg`3$!-7du8)Gw+}tO?5OB81r!(Q$%FVazt{d;bvV{R>-KH@6zoIaH z#8};0KW#E>BL?-Qx07)4*g`>r%uc>EVei+Lg)oIS$)jB7}iZTues$!YBleRPeaGd<~(oqU#GnT7jM zH|WGOA@j3^bapPbn~UwvKohe0$fft&6eg27I`kw2CBuN)O^F@P+Bu#s9^a+F1Zr!?~fcoKYjbWv_Q@;R-| zjNf#o5V;!Q(;Zy(3dk3R)0wNqJL=~1;rYQgyFOCg_kcV(;rnzZkhrM>;R}zUZWm`{ zW`AS|#^!IN+{AK9JYpIH!;R0x`yab@^$LaO=7x0Q|3~<4#y+Y`JzeSSRkXIbWBVN| zH>=(6RUhByMPePwH%c#f3tvuK$@4kwOVF(Z+dofzmEotYvoB`u8^E|BE%##D*tRX+ zF$mkru9a=gxoEgK*FV~vTM=+$Zy6B2SP0OPTb6uS|n{G6*j2~m^ z%uDDUZCeQ6cs3Yakh|WS@^T{R^c}o< zw>9U6z^^a-{tmz1=uoyP$oN>83$(|wD~?)}i=nU6-khu28q5Z%U;f8nR%L;cprvpNc?A57ldaX;g~AcpPJ_!}9+*jPS%#OUj;XB@>9=3_VG!<6sC5yp1q z?dTOlcG?FLw|D&DTE=BMo#8lSt760*8+YYy6+i5KBk(TdAa>0dEG5QnS03NUhY{lK zR^{7d^SL?M@h0-V{UX>*HY&xAw(Y{$Zd~t0IoVF-vYl*L+~V43-hZr;E*hKb{-DIzjGb)qNxOVQ_7>J*5Vo33>HYFO9 z*iv)Qi@SCeM&(~(WbJ|MMVvXgk@O!_TtQQuU1t1tSw^~YOjB6q)t66=Tx}0-3Pz^q zR(-weqxFnipI^%3cb>&hG11TP8@1$YH}!{U4$9orslMv=p{~Ym;(O}(d`msPs{ao4yTP;V zfJM2sBUm5s(_LQqJjxWWiz!>8vRHHOHm~dswTq-PJ%DAgg}L%LV+4D1OxH{CR^-h= z91}8L-S~|g+oD^7*loE5pABL=JT#Ux@BAt~7=fD+`dtjAP_H*Yp2c)~7b@De%Jq$gqZWeg(7@9&JBpe}tx((f(oy zWlJcNeqAV&ZmU^K)VE@$b4;t>U&F$f-CLVp%o)LEDt0j(0+XV8>? z9eRIg{h@7xruAVJH1R2KU6^|snR*WhI3=VCQmAjh83e2+#Yo#(S4RPRL!Sy<>#wug z*CpA%vd$KvPXN90QtV?A>C8_(IlCfbi*RQ~PQGad4}yolj{|QyGvDkd*>`4EN2YgF zt?r&}k52D!v~Gl(H-{36Ik)Y3)DO!)?77UIlKyu0O`oQ3nT(H3d$=eQystEo0Q2uzD_CZJMJ~klCklMSBVf{f8bK!npK?5^W0H| zt;6x=jzALMmjumR7GU2UckbHq(tlijXGrd_tN)A8)c-iq+S1t-rvi2%H-wVH^-;LkV@5i*;-S2sxzA^Mo zq%+C3g*oXg`J{6mHpow>Gm*f8Ez@WxAAb#W*W+|11upx3Y0-WTu5t63MY|4MJP+$6 zYbfH{X!|~7l}%r!oso6s%FR*lnXnlDFur+qX~`APz933$g|YjqJXdOdC$2*=Y>dtS zE*aPt*!BAFquv^;o_LApnpOYHPx6T0ML_L8E&yuZvFYEge#Q7R8e6$J`|mx^uq)A7 z&wYx85YK$w#Dn;dJ)Q-h-Pe6i9_T&qndiJ1YrNI2h<0Dohu`iI+Qm=fxe%UyJH?R3 zq~cexdphmIiRO+U0@c?^KvhQM#;7X8PQ%^l-_YImdL(b!M8epmvZ_4s+c=V$400kk#pGj%jKRgo8T z)YjAacxdAHE76E2;fke#2ys9i%_EUO^A^Rg+WwIG(o6gYffqPcnP5B}C}!PKTdi*+ zREIN|{n%TI7LGS}e7V!@Q#Rdc+NCph(2wbZem_uo-0h=3bhWGS>a|!gQSRG^`C4+! z)i_8o2GZ%w*|dvzZBGZvUj8^pz-t@!*=CJ{0JL^@92h%FzwXd9XU_o2Mxu2C7ktX2 zN%wZ-m5e9Sk8iiR;02L=+wD)%FXr0qTX5fQy!PE~w?gmQm(6}jecxtJg73>_D-C^bn_Z!Hp3Qzr zU$Wf}^_gJ5Cg|Br_LMyL86JDvEdD(3qzWVJx(bo#O0T zXtLQ>pl`DVaM`Q`o}$TS=R;FJ_lV}%>_6b&WwR5-FXpzb1@~=ckI$s)bQ+&O5Y4k$ zSp4bpw?KbO6;SmM%%xMu)(s zz!@&R0w*#S#anY&AMuaHJC0#2iWZMIcf3lwaJso8m}1@)Z|JX@_O4Cz>{;xjv!_Gr zM!D=IA8Rah;ao@lq+NEg+5;Eofm z^LfacpK{l-*|b;8)dOY6nMMxc&a>rGcz(k?^Nlyp$o>~YGiUPDQGX-IONTdse!LC? zSKQn$eqp!XU}&oUAFrNxm4U0@8&ogu`fmexmk4tJ?ett3^Y#U5d!X9J-1*^r@GjaH zB+>zUeH&uvd(YL|zuY6o=ah@rEnc5*iD$x<@oZ?4=LExp=X}NY&2D@z@NKz|J;52Y z(->FV3ZOk#UjeVq>)SWT(|LXCLOt2>SCR+aWuG9p;Nu=mWBH%7RX^qQEzrp>v+O}B%{5$Un9#lK{$yN`U@^q%J`b==vD0$*8PuWxQbQs=y z&(p2G&Auc4w$WDcx5nd<6W_$1_$wmcIsP7}A3Oe*lb792R9jEpXP_zm0+QFGJ?Y7J zxmQniodr#@|3ftN5iT9OMD`tjlc?{<-^1Yhiod4}eed{-tDPr@;!`%=;`Mp5V8XTY zB5=uYhvBhz{H^e9sWYmW%G^EsuW6%r`?=S4uJ~Dd_UGZ%dCxuyn!RTqOJ4Rc;|nDuOB;02L=$5}b`{Wz-z_v6go6I`P@%yaghy_;yBZMus;eLes*u}r_8 z|BLdtJ06BZmrMf;f6qRgooW49{T=ohN*mcnc2?U#K-)g@X=nQsL$mF37FuN1w9GE2dpKqZsz>^QdK? z{h>>aUC>nDf2LWBZFczfvGz=Xw7V8)P3T5jt;<{JLp<`x+jcqzdYrve+}k_J25-<$ zf9;(Tu7BPpud#TE^gw3W>@8@5Q$3p6NoMu&wP>F0uJm*&i2Uyyb~p8%v}?Q7;04a* z+VcdCXAh(Pw3k|^I;`7AvzHQ0^GYMMaEiTDa?f6>kv?S`>Fcx_yJkP6UUzsX&h)%K z-qdmTQc22!&a1okQJPy+M{}us)z$2yhG)KDA9Zum^-~~xbH`kj!%y#YyHkIO^08ZT zlI&rxF>U$H=_f0*xeA831 zp}%}{E;RLH}!2FUi@Ni+Zu2`uI=?@f}!s{Cw5mm`jj2^6Mx`h$&p|W zFNp0V&*k8f;X=b>@Aaj`w;3_Lg89%%Yp=^*EW-Oc+MP<0&X)p5v^00j=gevuG2Ti{ zh`-7v!MBvSF14NsilK#x>k?u}F`~8PP-sEql}wL;?{F67(m-E_gTQ&F-rCB0nq|C? z2uFD*3@uDv_Q|K+Eb?tUqXi?xTGZA3n;rN7z9DGheW!R5$J>Y}+2nW1Q!W`CaKT4B zn&Rpm+Dnfzbo6z)4ZN7MW%Xm?I@YZB0Oou}JYS-XWK#Pb!mV?o;xO--;4hnhOxcRH zd-nKcp1u162a&dWv8sCsDeRR;o4h!>CeoQ2$&-qAaOJ&Feb!R0bw}?X&hcnf&|F`; z_k+y4D$Wx)%j{6BDK>9H7Qr9UR%f1yj|q~Qeq^t|d9oJ4OELNiW$N!r;8w<72p@`< zX!5mSA-ptah}Y%fm2`Q%=#}UB<>_>0h{~xa8$aun{|9K=Q+}b!kwxWCK%a$wyGGy_ zb;pkOW51z}bT}O-nNI^sj#Gdyb&}DK^9;P)JurJ4>iPQ4gD&6nrXT5hv&ZWQ;P2qI zeL%q3NLokIdltQW(R&y9D#-gm-OrJK%3}+21^9utGTC}~)w9nxcUaC_x%uFK@h&ae zlFQ3GFdIs>^bDn23PS05JwvH^G^yflR7*jLb5q9v$%XBr;Qc)r2ZHw#o^H-be#xj< z*PL0weLB+zK3>JUp(BQ&+}{@_EI1GLV{nhwCCIR)!su+wHIwC^7!}F z7BIGNRy)RF9=uwji*jM^2&i6)vTBbf{TBw&tqI#TVMBDx{t2GCTc+_Mxbj4B(`rm+kExK$Qg0A^TbNjpac^h{Zb$_w=C%j|Bj#Efc-i)^Z zwcalUj(UaPpYWcdH$I<~~$ZuVMmF7JQ^8!imOt$L^k&J`R6>54Dteagfv8 zF&mv~;H&XFJKWsycjTEJr=9NUB{k2xZ6(((>V1N4lA(w-=OXxpp=rEnPSKc{1x<59 z0J`iW+D#tqIH2;!d1abAu7DXhuGyn9;eg#zBUjY9?|Nh=j@46J* zYsJrs?Gh`tm#B>Kvai@Krp%9R*;cXL2WZFkz4WQrJ{-Co+jF2Rwhw`B$96MxKeivI zOtHPgi-jtDwt;w1Y!_wS*w$WOvAzD4y<_{=w3SY>zhb+zQ*1Y?9K96VLHhJ#`|HpD zZ?Ua0_O6L@ zw8riY*j;0!|96bt>)`K?-6`JKO?H|KF0}a7dHiA)za-;6#8?YO7Uhz$m78l=BW8QN zc3Qmhu#?tAtv#n$JmVfue?9Np=FT&HnP+xd^Nhx|WEE}kK4i^eKQ$tw^JmQ|YO@0V#mrqn)>7^)_gaJBry`fehV}*ff%|LtGRpp`vY5M<>H;pE zOB4eUm(H7Y z*JlK6zJ#VZ_CsjwV9Y&~zLaf;uKBVZy2kUHjAzAoIr2SCnPiuK?|-)IqrXz8mN={@ zm61k}c6xSf0#A_anDYI!On1ECsWs-W@KKz~mKEROr$2e}%TL8;x$Dfn`2NP?`<%r$ z`W<|KZ}F}C9=@|Iz7JY_%RBM4$HH$u-?g>sdw5Q=c;0UDEc+gwx4^Uhdw5=H@w~y} z*}D@@KmI3M`lr5!??8+1Xp3*;JNQ;xd>4NY-;*r97h8Nwzk}}u7T*=$!?)Dpdz!_! zq!VA;UngPP^&Zdfn!CDKd=K~d`g2$Dcg$U-p3IuNoU^|>c0biUN^eXQS~7<_$?W^i zpC2?PA|BuGl6$?y`-|=Fc=P2h`VP52K?Z-$t@QYJUTs= zM;q$VV$kM$wAVaZ6xtlo*n@uH(IU`p_v-D(y?XT#hQ?Sk^~QVk9B4B<+U;Jw?Y$P| ze&w}o^JwkRekK}o=La5bEi{ca$;|iOO&@L0k{+!*&!x3OyT+sa*sGU@HpZh}?$v97 zRwEjEob1t3&_;Q*bD+t0Z{rirg12goS5Pez`uEV4)pXa zLf1<@InG5_$-!OPk3z(l?m#;&oYi^yYLCB-x-YYytwtuvt-hq|SQ4K%tY)0v~_ zPtOpMg#)ojF*LI+A}nxNL3CS|Z4Pc5{b*tF+tZ)PKOT#Ub!*CoM0*7Vr?? z5#+_|Ve$3k8U{^eyMUrej^n^(lbxTsGj0p#m|eLCOy>d z`RB-+bGnFYt0;AA;avz{|6K4vXlfJS?ha?ly>t$EAaWS{fh&GSOOGCkpTXJV7@xA` zmDH&~7s;u*l3#Jt3P0fkDEG(6J(A0l_Z!M(C$$mpQ#;wH)QXF{X?HpG(+BMtBY%UY zxcCalo_ecfnU!Kplb3&+yt=CMM`-eQ47%b)v{yY^Jy7|2uS_!E2TlBL0v1Hv`1NC` z7xh&30%fYZ0=R?w;GOl?*i?B}OaI~2_4QvtxvhU8Yl$5r8@}DuOTT;b^{S$+&Mix5 zH@_!#h`V~UKwrRbgL*>u$HHyUicXZDhGvP0ce|yvPviV{juA97a@jqSVo-MX;-%GR)ymHCCANce87zdv~R~*bl zM}IDP`xE+2xIQ=ox_^Ew-A}f3KiVGy=)I0M_rhxdzyFc%)qfB2g5G$~NDdB!On=V+ z?~y#%O7@mM(mhnuoO_*i%4^M1-%cu>X_ZV#x1M52y!^5GZ+>f`*zCe{9OKkoyWsl- zZJNYOG9}!$+n|Ye)jr0=W7L%`&hc$Qo4dSzMb}ui`+e2x*PMN&SX=zQ)6E?%w7ZP@ zv-zEn`qq5>XRq(`q06tL{mG*>0+m1BA5)3uj$c9RPu+py5qIaV70@KhIFE!HFE>8aWQ>pT<9H%A6K_RQ*5m6G~O<>`2Eh}CmCP#c!u!p(tY@^hdoT#Ix4?=he^slKXPA@9@ts_4}s3z-KW!`}15ccxjx! z4{YUm+@I%$kzcMe0`)~Z={6YLAK#qazFX-0W0QVks+jcaT;$cs=bf%U$9?AUP@J9% zuK8{2$Mg$47QgCUzwSfme#ruo-b?9SU<`4`;p|2GY`sH}-9&ei(}{uUxxX?;ai2U_Rf7$go8#AVW=%hXGVXgF z&Tn%4DGMgK14UU0`M%`)o=81trQ|h->o;Z*Qg86J^kK^q@n!MkK<0pa`xUm7EP4kN zlstO>hfK)XOE!+K_0M#WHacD$a|jb0C=pxpNmO4GhPE+N?J==K=RLnbapwH{A`th_viQ5 z8^P^oRs9~-{oS%tI3s{e@YY%LInbxzlQL(X@w4npq!oT48Dhv6(OTxNxp`6E)fw61 zS@V7hn{%#}&vSQ3b^1|8KC9>OGWytrj(S(hC(8=Ueg52a6aRmTPr~Idon;sCZ}9lD zr{Fht!_04sRsLf`ugV@tTlICc$-8~29_NGRnJ?nilU(}kyJSg+Ie*3v2gv5M6^-(Y zd^v-%BIxq9= zm9|}JtFyg4udTir@*wq|Q(OLB9r-T=OnA@i&P4$yOp1`qSt-x7yU$7!?)zz-efBoC z7@X;2;E>F*z#Dn?QUBPumw&EV%v`sVK7Cyz&j;H$H%;i=l;88F*mKdAf1Ncq8@X=Z zYn;r>wMF<1C9%Et`R6<{PVT0@=E)<_O?Jqj(|l|?pEl1x)0o--P4?Dr`=(OX3SB-|HTaVoz;umrcP%SDrf=h5m2LD$<(UFVFJrie8mb;X|{0CbFySbBN>$&ybK%V6 zF6uaG$(GI@39fVU1*{Dr&f#T$Cow0t2zz&nP>1%Kx6;%TFYP6Ic(GRuO?^Ix9I~~Z zS#|$gePvBt49n(YQ{wR5{21>IQtt1L`4r+-U+=>`z2DK@^5`DD|ND`v_gMP)h8UF` z;@jQQc?WgWzijgvxb)`P+xSQRk}ovRAFDR5e~b@I8-9-`|M{D+y%xY%?<4ds^H0#a zQ>OjA#%ejV2xYHIe)4xh*Y`a1?~tuxzM5ieCAXW~TC*)qu%p-#fxn@I0Zp=rLj&$IJ<**K=}a8zY^ z-(v2{Z6C05bL^8{TlFm%eRE0qdE4A?kw{*xiAy~>&76vyA;x=tiup8_&O{OmbG_LE z=^F_@Ms{=lVBnCf-ZyD(ImN(XSw6i;6( z!dxx?n>jen{#Lrvr+iaK-HlP^R&briW)&mYVI6wpq%VAAi($}ZgOSo57}EZRJ0q8U zv}@~9>MliRpEd%$<--NgS}61BL!fKEm0xC9^@+Ewii=w4nuq=R=R;SYgP@PH=rQPu zi3;fYZM9z?y|&5@rO@@eYM)*KU29yL`0#zP%JanvXlqEChX)M^IC`hG0X*p(w8#v5ix}0rXq^F9Snn3qzMOg&;wQad z)SicXM45|#V;N7{kIF~vS%7%T>O`dA+iTNEpC z{1wMhd83(sBb===CN#H( zz@4<#$#mwInr}F>FU8KR#RUh@-hAUieB*rENqOphfo(L;E57p+oFkJLU%pksw=3GS zr)nI=;fs%^QdUa5jmKwdJB7S?J_2VPXC2njzVM&g#`8dgaSdO|6(lzFj(8k&-8XVl zZmfiz=A8Iw9yYNC-d*y=+;vla!g9vvpF)?6SJ0TI{v zllul_3UY>f7W)S6XS8q7oLI^jElTP8J|nU`w-N7*8OnYnJjuBbb_vG#MvA@N&qH+Ypip)Qvai$KoDh2{;e6wyP~V7Aoo6+MBj}Dk`o@WL=ayLZN$84& zOW|AGhK%UQm#K5b^j8dj$C;LUi1DR4co6bssOOJajZMwL8=W5#B0Z*0B;Z5_1k z@D4*OrS9LL={|wK4-UuNccmJm8k<_b&Dxl-o;TveOA@;iBLmS>^QSwH4$0_!VIAj7 znr}}6FNrpHR51U^2U@p6u_JR`BknkGh_@^7Q`qxE59$}yFz@1vFmeTZnDyWIRL>!w zLz7PzZlTOu>$R^9JFigJ3#V7*PN<3>p$q=-v67~Q}YmYdhk44qBWwgS69!+lDRjwJR6ziXa8AR zdoI1}KOMUGiXQW14xx{E#zNlynunMRe7W;e$jThlH`$zf3)#C8UvC>YB(vVYp`24X zg@5ANO8dwZxcZgt3nF|6Oz{VOH1t@6^&7g%L&+obZK;pSn6soqcXX&^jM{B`Gyi?A z_v>x(jwtc@5B1`C77mf?637W#(Snd_{!{iW4s^r0P77XAm9-DQ3YnK5vGHbh;HD`YlU2v9`K`cvbtvoLm>Rr!UpX2VcF4@8I#R zH`88nwP_u2qZKOf#Bp)FJYZK^ja zd#3!xccoZ6->rmi_bBh8dEXR@Xbo-2J&6wDc?j)fZ>mJ&ZnO?Rc+CBxXh_XYZs z?_{6n!I!+kTuNPI1MH&j`uX;mo4+XcEb{oaFQdNN9SdzTd|N1QppQ_S#>6Q03C+3V zOgys}POw)>@QubaGN!Pv_pQJqDd$^O?zdtzR>J;R8SIUf%QI|l9}PxZ;W;$>4dbV) zvw*7y_#)<>H$F%I@|V8f7>Q`Do}25@-C_SYZBGMzJaXG^5c^K~*WABLM8e-%!->5WW(fvkcSL$di zet@0KI1HnAnmI9#yWjLHTCL7^kg-NOBsoXJeoo4rgWlfcKDWOGEQHUSz=*?qnr`k` zOZ|M3e4+hM0ld<$g|g;5fbGn|eD_HFip<$;ORl9~^JYEQ6t{E#Hr<@NoVI;wr?Ibb zBtK}*(OjoFwYTIzSLHR9UZ;Mf&5do*}2My2xncaN5#$`=DF}2Z{9t6+M^WzE7q~mCybi4v69k2Fu z9OvmMyT1M{dqiUKcjcgX8oj2eIJnK`FkSn#T;GlD%~I&dC%rMFfqR<%~Yp>-JF zDXt>uq~{IAL$qaK?r`khLvs-PO5j%#TUemG!u+O(wVmHl!t=-Eo!EWktIT=0)&lcA zMza>W-(&28+{G#6_RdqkhHqES>df5Xy|4cdcGYu+;yuV5e3prUc$RNW6fV#>jHI)Q zonIoO@SwiMh#h{WJna-;N2v{cX-s_lp*#Pzdwp%>4n%Q^Gh%f6lrr(!2vodu0DDKU zhxd(1^Bu^D&Si&WN1|VT1i7@vC;Vq9SFaL$ke8J0oUL{|zXQJ#;?@7=BK!A8gU&r0 zUqDC2wRF_;we-}!Nppp4tn-8fauh(5f7svf?LW>g@Ocq_D5p#^={JgcCS>3JuG4~S zX=HQm-Kh7YhsYkSoWojn`9OHg{mtHQkxk#@E#x`rGw(S~zuS9^`))c9XCtQXP+GQk`~C*r>ia)}w3koXtiES`N8hvQTWh4^ZX&j*h^sHY zXX&H^`re?%Yo4(eK1LR|{O;V@)LnI^J$CQ@jIKQni zIt|V`S1syU04`hiXD#?A{LF9MIA?g|!(Cf9GRFoY+*fBl)?B=ta_x<;R{Q8Z(1x1l z4y|PcG3?#xIoIKRsn*}cYMYOs)Fc&o$~R3xB=;Xo9Eg5KGU?)r()s zJh_t^k`*SUEh;XZd>p%~!#FIcYBKKbCxI zALouE<&wc~UrqZw@=3SAzqp{cn2^F8eTJJxH_}->BFFxB795$0?a|MOey+~eHO9EuKYNArG|=hU_? z^27989Q!Xf&#lvQ%b*P+FP)cyYaGd53#2LVWS_=@h&Ret`*K4@)>mX7e8oG?pi~OXY&K@yV<+2 zeI<1H;cAs7+-IF{H>&Nh?3c7HQNQVQwvO^b>J=tQt)v$GM}2r_e~Anl(?zUH8q+(u zqoqDOXj8y>>EY&`Exf7oQ~ErH`DPxx88f#Y0X_q~3^)WB0geZ%u6PWlzKI*2Z6B44 z5$3X#8>gBZE*CB8=JOML_Y2t-?|%mBcgNz<=E?CM>#gE)h1$^1VFBk|<&Re$UXp!> z@U)9h^XmNyd32`1y7315vggR=yg$Ys0m-sVwdE~ncqK$ z-Lr>+9KZx{JzBZO7bo6R@=1hKca6S*{9JEiAle#~rU)??V1+c-> zop&O;b$`+5eu2?>AblRZ51p^^`mFHu_$l!sJ54ZpUT&U!6eHTVO128gO*}{^{Z3$& z_w8C@#LTI`quxfovyN`2&IX=se@7enL%u%D<8uVGBKAG-$sXg4ogYDKq5e3@!ualL zbV_FrhQ2n${Rt0T^Ea`jx%4 zZx&Cts=Fkr9*5Xrs2b0N=02rv**$CL|@kEfvnA8&dhaBY^RAW_xJ1Crv>qW_W5zfSgp#?F-W}w;KAIuwYPVN zY$JWGqaXc7Uh&7>7q2dNuW47rH`LXK`x`U9!)BE$o{R92)u32wb zk9E&UbDPTMS!McnBudajzpYoD{orT+7O;|jwZHUb<4%^@`{>_f;+qb9^W(TJ5uGu_ zxo4GV?$Fqo#hk2thJFYB9Q>womMOh<(Qgo6Cdt1HUAV@77<&+F*v36aZT0HGqeI_r z=z<*?@8*t{#;)$D*w1tqQ(yPDoY>vDI&jLkx5rrq^MKB1UZw5*6@k6ZXm%XQSt@6x z`#Pi1e6E6=9^;1Aw|J~#= zub+PK5}h-Kt%hebolOj7ugKcro}+QEiZ$#I>gzq~An4jd>mB7m&?L_|AbZKI#z=G$ z?|K+l@^>EhDWM;DyJ9Al&cr;ro|lU0*L))g8S{7+jHPZicWCm#wH60}x;vx%-K>d& zy!&ox)=T=#M(I=Sd1tCKi8p!Hdl#4qrb!92#H{f;7d=gdvq z!+BKtbP3Ga^xqEYak-fTcur>RzdS3O+?w|PEs|Rg2u9$UPUnZ^7y8k^gXGJ^JI!9= zr5G+Eo}x!!BkK5b@-wUx;&;92hxvrI6`nqgv>g=JUB1h*SAna|AN0*1;IFJcu*vQ| zzVLkE_Aw{-DS3ZgRQoOw)_%^?6i1iSr+AI<`o0>Tk~NmT$H?36^GZqHIk^pfKA}D7 zoZNcy;-`I$!yK)f8?+vk!9#n`N}%@LrvmjXQvuXoR8V_!-4oDrkIrv* zu!d=aWH_oDf(2~k^AVkn`lwD-+cP`rPudVpze`suj7CFN&jZkU!Vn213T6L zbq_;#-fn=0^w^;?px%}1oNf|y$@>X(f3Hx)Ig3LI4R!sp4d~y$2*lj;74vS4^A*+x z^!wj`m+u74Vc7Hg{$0K*=|xQI8y6bay1S#_shIui_x!tjTQb^XrFZ|keCJqvjuXC* zf0yrct4=X`Yu{1P*sff8;cf%!@;#kj@>SqvrYTfHLc={$iw{|;x`lfT$ zU(%ho*U^u3T`d}G;wt0YE3&=lOZI-*(Bm3AX5A4@XSPnu^q#L8Sr@wdV9p5j?5@6a zZ$))N?4zeg`S+M2`ggphA5au&F2D}?C1Oh z%pJx4&^143+-WT8SzL18686{p8_k+uOdZLp-zfNFu*@5SmBeEyxH<2_-)0=fu!XU) z8-JW*=-+&lf3lsw=QasDcK)8*#CFaWHTTB{cjUnB`!4Pf)3zwe?{_G(_wGj#`-ZMqWq7af%tw`K{Ozo#g90wP&o3MfZH(8(wYT5Odd1_g>gX@hpp;@D5;`JjRG*dsb(_ zw3Y9*P7G2$%^0Abqq|PfRPS-<8mnixG1}qabG7*py5diFdo^}+$KqPqG$o$*FAz7Jj?wShS#2V$C?xLjQrUL?%4kn$iEqE{KGzt zHVboGp=qpd21?Ekftp{7^<2Q%lZ@Ty(_u`gFV3(`U+m`#<*yCMx4&eJx$iH6)Gb$@ z=ZOF|Q+tg^Tdu{PTz{f3?Pp#G`f~N(rMmpPW6&hm%RtH124uWtbq84Y@-^;Wp&#+r zIM-Ozd#b*at%NSw^v+H`EA!&!S?J>RN9f&w{vE_8p)1etBev={i?zgz#+=69UU9=Z zVD7B8u@_3P7N&{280%?UbMDXRT14FFy>k(FE7ut~)Z7=74EhaX0N)?0_-KiDbmMM@ z?9)o$@^$@`Ma;9j|3^>xUH%+KIs1o<-m~gVM6_!>S_81iqy5~Y=`1wt(R6OBw*LSY zccRULR^ri~@#_5@SnAPMdNkd2i*%wr0nLB@^6Nbct<0-;qt{mbmwPnvku0AAqn&6! zVEPD_- zs{J(bidXH?7IRL2GWl70e?VXQ_p|PxuR`L%@9!YWlg_6fP6wXZsjm@UUs|WdQ+vEB zcpggK3XiA$Z7rVX%vo<=-W&YXw25aYcx|pBzdz5by=fPw-8bA-3)5yYyo#Z9hqf7- z;I7H zIJ8@#ZG=|o(PGeMLR$;1uSau;*%Ihl>++(^eeq__$C`69sHfj5>Ai;LGtE23*Njcw zX+4bcnbIS%XRUm|=(Xqfw7;j0_73uee>eUdbk_Nk;`5A?oW0Ju7IU_<7}@r9&Q(l( z#nSzz59Vvnp5x(tF}UJ}^)?$Jul3yD^S+{I>y*3a(ONq~`BwM5;4pY~WiJp(Hs>Be zzw&&($3%N4?XL0rdA$6+K`U*`u^0aqY=`ugJhIys=#iF%Ih`lUM%@hGk(nfWIo-p_ zW1I#(+BMMhn@asY;TmYVL)1Nj>=wV_-y%a)zeVMFhHdE-n7b;Z}Rl* zij1~=k4s-6rlU7o;*3j!5%2a{-}GJe-{us&vskOIivUAv$wF<8AaN`{%1R9G70b|(iBpZdw4VU zu_u~J**5B~qD=~X94Sd!ei3I)Y6GtOd$R@EgFC=kqy_MD&!61y4|9&n7$2-}_VA6( zxw+y9_bd6miq4|ad?Q2e8+2wd0J)`Wl4k{U&uFh7qO2cu`9k#NmYh0+(t8ave_HP~ zVw?x+>`Cu6UZ$<))}7d_^LvfsRfb*lUSk9Jr`W6>o5^mA(PK4z`R8P;^Sj?`{EWK( zdkt65z29q0QTaaJYrJphd){k=bq=0jZo;p{;Nir~+_xKu$tce`;O3jb=<*Kb_InN4 zK{BduJ>TTTi1h^LzR0L}Er_`BFZ(i{x9-&08|8!IJkuxHN2hsKP9ZaW#ptUrUDuu; zy`%lY`&%;w@#*b5XRZu6F+FeIy;0*z<52IlbcUsGMHg}=_tmal{~vpA9#>V>{g0o0 z?}ZBp3S6$Df?O3G0YOoQD=LUnPN-OpAqonHKn#XCms3t@^*q^RR#rZyrDZu}%G?6d#%0pc=kT`m<7FWkNkP* z(otM{?>3B9kOje~Ao!;Yn^99+T~;xza9T;N;iGFS3pLlkpDE`ThQ-hN@}JcquR1c{C=!KKBJ_zw6eIM za+EmJz^|&VtSYIlo#mHU($vsQxnFV#fBapcCl~D;S0XO{_4&duN_QGY6|VpG$2|Bx zjRQG&))nbv%sk4`I{WN0jDfiR+uyXpDO1WTYf${cx|)*e!Wl)?HKj%6g;x(Blvh|d zs$k@xVFNDz^2fBisHV2CtfIK2zOZs?;gr%M-0vEA{>@1HFTNwZ)s;0>B~xmPCYP5W z3m-(BNAB(8vaRH&;ct1m>wPCiAAQJxryMeF!=Rk}L<*W(6KB{O~WpT-%imAALw(yyKNk#w4 zY8gMu8SKk#Yp=-n=fyM)>;`^KRe4$MAm5-fR7MwTx+z8F*M2)7s>(}b z)bjIa+oImUFRH7UQaXwehe;!kALgHDw~sQ6aFCN)>oF<{^S5*wQyhwUBOW~d^~ARf zyrwJ)vVo>-628TVDeC}PsVQ5Dxn2rk$_&Wn_+f0rqfF&w;oz=tfnZQvaN{@B9f`?Zb(pRw@xzOCPg z^Ryq#kMG}d0Eb$5YmhbyINHLK_j3WqTX=k5S0Zqlg~#`IWdQfF@c2HjJm4V~UOspQ zz!NMyzVE9TxZJ|y`@d>|=U8}rAJ`(`WfmUa54I9`jfKa5Q)Qh6^WRt5WWoHmR<>F2 zdE~JRc(2K0J?uB>BltdU(&x=@M?W3MDDyiW-)SbDXMPu$-^{Dnq-!m_MHc!Fliq0I z?Xb}MP5OxWecb#$Z+<(lr^vr3^E=-BPBXtnXny3GyaId|J`x-~1!~_zHX+m}_>QocdOLrT%}D|DWW?f7i%o@9*;G{f19Ydy9ClfInY(toX|G z{|*1Qc>K5W{h!E}UE%M4i@#NV9_6*-|C{pEl$1~Pw9fVn8$DzQ<5gHYqbjv)p{G~2 zr$dKn<&_oFI(kxY&?o$Eg`S?-9wE~T4Woll8HJwCnwMGF(I@Yk0y)kmjYJPl3WxS9 z7&ts{qQ~PglELeMtD`5Wmj}f7{E@@+`;9CZJJDl|A2n#eu$&>HSH|VG#WB6aZeVa} z-GHzLmF08;hbxFASw$sbC0a491Pe9cVR6g(LFl3>*VmO*Q)EmI`1S!;nu-gPPzWIt!tPbD`GPX8|W*vfAvc7n?{wQdVt4Wzm*XT&xN32b9zf zqFsDL*<4(X#@aGpFD(aQ;{A(kv@93Nr(0qgSr|hwmFF}p_9zqI$@hU#T#=2^3$ol< z39X`j zR#NS(DsZj*GT&M%sX|I_l~tEaL!3&glPYD|h;$8QV*aR*`dL^H)YXi_Ee7-T(ORP0 zl3p$IuBV}`6 zM99$f46aI&VL74BFzPCjA=AAS0|t$Cy*hG%MJHQe>-6qJ@!mw3u*JQbB5bJ)9~3?-J> zLe210R+uUMX@+6s@+8!Ojm}ad8On&reGE_ggqrq=o|?KU+*XJhcFIiuC0(jchCaYL zttqMXz@HS&EHSy$z%BBif60c1M;!WJEco(gIDGW5N)IAq4j$@S&GMM{H|+F7^KwUz>e|aA`sg!l zKuB3V11f9L&n9E6sF;E%$PG`i^gxE+40K6-$&@dv)b3_uG28mFdn$q^ng=R z%{zDC(Y0l`51i$>nz2BMJ@8BzxH+q;@yx2MV||s>SCvwb7oOl zIR{C!8oAdOGmyY+Iqt!cm*hL3Y-S0{=hKp={6A%v6e1bwfYz{hmDQ5bEHP;sO7{F+ zp76LBr7~*+zSoxijTvTH1J`t&qg-gnNg>#c1IY}`R$f+u(oBWXB6J9u8Xj@U@0mR_usM+Exk36cYiJ7Gk!nG?I7gS1&+=E9KWMbdn|hy1p1K?mill{xn0M!T!Z2IAuf>>M!Qxx_mAf~4TOm@+QrW@s zRid;JG`ks<6=mpcCB?8YYp>8W8@ZuM7&x|rhPHlLZB)u`a7{AC z1}=xYcP%U|FR7STTbk_Xyt0WawR94KIQOOnMD0Uq`mX5&bN9$FU6%&XK_Zis| zxzFeslt;d_obN%eH}o8&8=TRPV~T%lKoitW)hMc{sI0|) zo-CMZ~irf>|b(6!RND%E3~^n2byK7@y32c1#mke^gJaW39?|Y)#$d zLjOeJ*8{g<|NSreV6eP?J|<^q*G;kJt@m0Bi_50eViU5qdRC!0>`!CLeQUuh`9;;$ zMP`x)J37r}dtTw~^?fAVF`{v0WNGX#l4Xwf+2C4@ZJ}#=As4KOnv}(_Nm)1COZjB< z(JzGVOgV=@m09k~W`d6p$hx+`{6;kxJcr<3*!8&TaTW3Eg!MDNM^|vp*Q2M{058X^ z8<58DMVIEYG_ly94>R}ZiG3HgPK3V3{C)(R`INN^)8F}x_4g!vLu4#_9Qok8)T@l< z_}+Cy87=Ycz=em(jJ?1yCMIv?Lc_qn(^0u$iD3i)r>rv!oZ}fOE9EfH2*AT8jexn= z=*GoAJFesJ@#F_jnIKUVz9Voo!n9|E_ z#()oea*m>>*cHvc9_YC`J$2^xmifxEtx)K@DKFFuFQY>r>v7PP(vPnGO9q&T^n{Rq z&0l1gRqJQTvmHCm%Dy5;=Xk4-X>cfc{bkI{PbELNOvVKN z!^!0J6Rym;;WqxSN0Xo7DgTB;L1ufUb{$q{Mj7_%E?48Nrg{E%?lR$Jhmf--S<^L~ zL;iRfpQ{$%i6K|^=2_&dB5Pt%k{fr!j%7uIH{wadY2@6?g#Y_-d0~J-aNPhh>Cc z&In;;H;kJb#@9DnQeGx;FSbbZ>d<$Fd2XpigvQ`M#@u_0MZnxHJI7p&+4yodhRe-R zjq%!=E7JJ{AKzvX{Ow_;;|_}k?|g>pL(ZBFR~j4S+q$bU)^D*SqMsJoxLsD60tQ$7 zIsOIz32-o)=zIE^CB|>2Mn0GHAsGC8i|_(00&9#IaC8lnR1d|Sq>&7Ud5dB2ZxJp% zvcA^?D>w|qzE!8C(dDXgJ^2D^i=oqcLTX_ChU9@r;EB^I`UpAo}YBj!b)QX9D$Bgl&GK@50<8m3jj`X z?8mSk4M-8PBeqHdx-tiQORRDOx{1*hnC$}6qOe%BXCuRajHjvaEi43Nipx(l1L@@{ zo5SH)35V>xo#6DGNKTMrKp;5%QLKRB$C)w{eh1{=2w|XO0X~E5FCtC>BUcLNHhcyv zT6+{%2v@lBVb~u;A_UjR-pmq;|6Hx_H z+26}XY})MrBlHN!(BKZ=gARKO!r)FU2VKjH2kJ}#U0Vr9gA+9hLdpLHzC9PW9wF_2 z2N{tAQrT^210hLaphFeN=#VZIpu?IYtB|g~j6Fo%G;)NsCrZ;OC@h(%JDZEGZD?3` zq8@l>4N!PkPoi|K8D-er8K|9hvuG;{WKg3H57HJ3(de>5H0n$N3IVt2j=g6ar0Mb$ z$Otb;Wk1{=3A0&ifsWU~4s@!?TG0ZGbuGvU2$FScUYZ#3?#2}A|dq66CF-$b>kpxE66p%5Eo2iWe z?ly3`adT;E2)+B4IKUQ)tqX3uzr#&mYJ39|y6y)b?$RsQ%eX_exgJLi+gW7 z1z^~4x?Y23kYRrsg2oH!bD)E0X1I9wCq`IqET}~5inGz5$)-PL`zg(D7J_Lt)eqKfwN4F1}7>5 zw8M3mss?Z~AJ@Ig%*co?K|6HaqUKStSlZ-Pwb`$@&8O%tNrK*X6FQ9O9}#!ga`g<; zod)f2J*3`cy0wUm>k%~pHP3)VPr>l*TA@Cp;$+bAu4DF_olyJ@-P`rC{aR_FhI>4Q zZr7*w$&$|D{wLau>oa?Wz##YgNa6a@extxp_a(xw>`MfOyYY|18m{AZj?lKYQSMpz zc70>!9PJ*3M&tU{ge}~UBmkbY-zI5e-0#CN*D3qY0zF3bbkx0TmAVxcqRK#rx@?Zu z>2D62oXg>ORum2Qds%=1j#mXb+%Ka>T!9XA6f&aEp%=JTEBYOE7PQB;-NBf+6VMf1 zPdOstGCOJt;SLiz+;_AAeAsL*#>{1)J?9`hFIqLMNKQ);CM&W)9a*`$?p5y@5$H-1QJ9rzv?KR=W^RAEJ;>+gf(+dcvV;3j(?%F>-b7~ZH(9Y9 zZnnT#_hBw$IFWNQalcJpF!+Q$iU#6-M~^3=VFzH?{jNsgVRynk_xrlJMuolA3Frfj zqQhRmMyvbi6okyyHYRKrV&Xo=DB9Y3!m^pmhZ@DBf~x81RWX!>0s_DbRu!WKF0DNish%kZz z$SCLt#-lokgN$o1nKa#`8Fn%hCX=R{HN!^6J&0J-Et;Xon8!@FY6ePWgsrE>6P56k zQREQb^l5Xv2%ArrcD^gJP{V$0JHVkXz|e^xgPPvj6SPjVR;Mw-#?Z;z2I%~-m_=q-oJeNCF*W@YzU};Zs(hA*8^LK^y}ebt1f*LeXgGCDPYuNv~P$qgk32 z4?{Dw0Ay6tS6H&JQJ8I-9$;oH;biDHeNFopZnRmTSo zHrHOHb-0)|$aPCL8}Drx13a#`n@Y1yM#0f+-|mbr8*^Bn!W8Hej$=mxo(pcRC>4j|4R!#)#(ce9;d=-Zbd9MQYx zfec*?GBo<^)1ajTM4$I{05-DdUs+;S9h+|SZ@TGXMYEjK-0poy6?F^9yyy&d6FbFX zwC(6D#bFo0arZ_2L}x2=CFvf6az*E;Zs<(tM((K?Mxy(v&ykQzvVEC=gVb(;;qJ{S zCtj+lL^;a+8QhN^X2NK9cZ4K5UyT-NjGOHuxaw1w0E$v+eyHOb+gQCmC3HMv{{yN3MfrZJT2jwzcBO<8f7m%Wn`VgNX z^1-^-gD#Ump$72@bes@1mcybZZOv43LexmPdrT{+;o^?2a&5~}y3+Wj3zsr^I}^=xoak$bn}Jh``Gm1DTZVsWEf zccQT%bxs6Qua`v7d)vX4vx~2!G+YBcX6i2;iR8AIJYzD*jeZ5Ku*E`^0YSvepjGq} z(NYgkBFiyk+;rpu=u~v}FdXgxz*HQD%Fz1T;M(dI0iou}H0D zHvMTUdNy2du}nP#PSlN{6Jxro9{}7N5WSdmwGc}qwhFe|m<;uXK!>#Hm`oLh(z1zv z1_LpD)JTD_Q;31O#d3848HHX8Iy^S1R|K46XNyglC2B@C0Ft)qcu6hm$ z3QNEyKy0^pz6|GJ5NWYOU4=|SXLCS_&AHW|VV;?xOlGnDbQX@Vh#X|m|FR4R_%nP3 z`L|f9?qP;6gN};5`m{g8v6p2y&X-{g49AXNuFD)2hQh^8&?r>aA5gKM}l`ney28SSB9GYhj|&Rd#_rd~qKxQ;h)Rn!su>J~E&v@B`}lOf zvF$!%DiK*c=`+T=k=Umuo5sS~R(9+$jY(Oa_LU{jQH9K$y0ceGXXjm;eN#)c(UU77 z3v}Fu&qnIs_zaN`*~YozV;Le9@+wt0kRVYnRCu6+oh-T^7%>iYOfvfnpTUai><<5P zhWoLu(|JX&M-9frs^-uSbj0B^SaBnR{pWv9FAg5>jnM3nfsT8HW?u?)Y!aH?AkeWxXtvov$3CIi9_+iKF`jgK8;sDM zAP=?d*A!hp>^vAP2Xc1hpwkws!Inb`WYEbAo0|+YyiU--Fpd;0ubwSv2zDhgthDUh z8Ze?8NZi9SzecJrK11X~N<9b=j^vB`!vUPEtUyPSC^K)Z+)#x~28D4DNuXAMkU+(O zdjc|h3)DfY)O*2?Y6@~)ToW}BL%91SQpSa=1n6+!dl*ZWIJaUFOo#4ch)!Hn69&0w z#R4`nVW|6RjN)<8CJc8cB1Cc6xskL{?tDZru7wGs-9HkxG+~T;J67UxF(&l5OAz|F zRwj&hKa8Ts#hI{!yDz4vIBeud&WY~#5%IV->K^!SYn$R8h7l(&UOgZ%%^iaENL)L! zL>caIu;V(Ju%|nw9blq*RkG^iUf2aNMU@H6bFW98#-%C*2s!t6e}>!UxUT9^$$5ag z7JVwNhuSG{pxeW&daGliobUb--*E%gae-spzYq>G;RN>|gjbnxlG}j_jT>yjVs|6L zAsho>x71yMC0^Vx)8}&cu3*4?^_64 z_ZCE0f^KJ;B({9}JLqL9`4xQH=b*w>kFiJ{F`K0QCDgG>pU$jb1UXPmE5Y!rvo7^z z9fL7im0hi~4r`6JtESHu6dLwBLana5M^Jd!n{Zl{Ygts-HE0uR#!it%hgGHmReT^Q zCJb*GF;wNRf;?e=W&l-b6dzWFuB5Jap^;!c8ul#OyQZLt`*E~8 zHQ;k<@hvn#YEdu((Hzfk8fr0T7FK9yv|Y6%Un1l%>=hu?m?(Tk%pe)8ILCDX86qDX z;n9N5p#}mSEx^_aOdld2rou4Pb)ds53S2s~vb2JXg0h-6RVVnB7*mUqDPIU~BgOjF zva$eBrtASp%h`!F{@1k6iJbE+YhBA%quzb#KLHo3e#G^!Y5$P4EI!-8U**lg4OYKG z&f2%gtp+k7YNXg8w1VmPwO|A~SZSW_p$PLgZl(5s*E*6tgsYB(qeve7qP3TSQscd>C&7~}87OYL% z!omuyvt{=fG#ZAVt|Bdq3nTs_I=I3V#6YTbG<1Mw764aGH~LkUeV7AueN>1J!; zwz~yMw5c{0Q+^tz+Gta461vg5c<~NU5N zI^kD~h-|3Fh{%R&G-;{Eh)7E{Mn!H$73ga8(}-HAn z*LSTia^kVMI9}-5wYykY-9j9iFSQ$>X{p^nNt+-w>ePIx%pgrmRR(Jvsmc(|FJ`qi z()2x~L$ywKHs>(SABcHXHP-aoq??F89bUn*>UOq77OmX`Pzj%4U82HW(nZ@<_&E`p z(?-i9HNrSB8{>e?;>q)TGSc0X-Oj>Fz!CGS-EH&n* zQDYr^&7zVX}kIzUKi;*rCBPK0Ix>${LwHWc!Fw)i6EP`nyGg5bzg)}71B2&vf z3n=cz_V4$hm&J!(79UJneCTEMA;;o_pN0=PzJiY8T(`%jlxhw8OV|>DK%)+-I2OmT zKR*n48^6NCLPMc1W;D-GOn>SHsEm@tgWc7{CO8U^ZW>)1K8&jN0ZSrT8;%o>= zij#$HJ(gW6#mW2Cgo9j_q&j(6tpnB7DS=u=r@A?7(Df5Gav_r@X%jAX0o~onyJRCi zSpf_R(yi!KzsW&Cdcgge1N`^;Oh%pXi@y)nV*d0-ePND0*P{!!exDun3*Dz>>iW_$ z#(bsuQftRuyirKl&iema(^6|EG%Yptjix2k-%37hWwtu0^OU%KCpv~_E!+F#D5c(~ z8!|0Sj%pb1Cr34m_a-gveR7m#yf2Ar81MZw+IvZq9`EA|8S3_p&G`qn9J;RCH|Chy zehOu~QJ~hR8RGVhxzNxfZ3j)uJlavy5`|8ppCBdgtS7{qxL`@tv_v6EXc-k3!la@T z?W#fPQ7^f%jEZga9NXQTNoBN)-^%*w6QmA7+s=>ncyA9F?|^L0H$s7?>#d(;!TJR8 z^uA-8w)n-Up>l(mpd-8T2(SYkgq{uTedn;aY6Pn~%;zkNt8?y##nm}kTp688kXo%& zDs3m;0)@*q*T_TS&N3aXRHRjY;u$n+wMzCy0_=R1IxIRcLUcOmfMByyOj@9m-^1i= ztl)FkK;?ISrjsBRMvds)6Js7^f<|_hkz})M*{D%Qr^7Y~A6JT*jnNst$P8uhd0dfJ z`LNELwNA;gKu46KB40EF=YOQKIDf=+-bjk#g6EPF8@~4uL3Kqt!$`i9edv;GKcU4N zowL|?FKOpSbiT;yHqpq=c^v3WG^%q)%uyz?tu;DJN57=F;73{~v$}Yj~HYY|?6z+F)xz1=%FDd{0unE=G^`KHq|Dym!I>rf)$uGmPS!iA>*2 zWcsENeUr`F<<(QZzJo4RWL zu#uLk;mvNR_FRIG3s6+#`k z1_AYD24T7EIiW2{5KLqS!9->dH1b!_a_40gwA^`F1+8#4RM1Kh8A(HVGjO|8Du^$k z%GZVY!0+4b;u}g}`gZ$ezM07M%|xbe8u=^ec9{YE6?D7ls#!s|o35G_bce;&e`r@b zeZ}_qS&*WO1=`I&;bxbac$*dYEGlB2Qim0s^4Jtr7T_8J*E7&ulcmjiq_)e!OO;03 zT3W(#V593PPFlfDE!3Q@Q>^oeK8PeN57F;DhIE-IQFezLoAQta1~%*K+8Ljaxbp#V)>*K z!;9Z6^~7wr;bhKT_hWWbv%+PyPm_G<&~lhK<2O4ioblO=OUL8JaL#a_5=%U03l7F0 zm?~4v9KpSbB|dW{{X}5#)Zq9OV84T&9*4nGxO9x>h11j7P&8viRdF`daMGWYH=IbD z6Q$_nBwY7Ew+ob#Wu=%CMfzE$4|H&gCgpv$uH!+9x{GifgRGGHQt`)w_#D>->UW*o z8k!RtOBkrRU=L30FfNY45>#tXkU2RWyyVvC)lg1J;1tlChCbZ)eXnh zwW53hLLIZh#@rDZfSE|gXp+e9AeJe*NaXJl%MH$Ckp~3;--;lqDS}gp&(WD;!Fj}E zIZTxZzJ@p}5O}KK>BMgmPZPY1xEW(wD)=7Y?hg`|Y3HTF7y?w-MpRfjt7ZfC({zPR zmzBcfNM)o)<6|hgK&mXR>LkApnZGQmW(u~$bgJCc-5@v-ID?xNTM%*dQ$yX@IP)3c zArNG41~DB()<8(SF9O{TQuBQ!g<0e@gC8Q#-F6@STF+6>Vxd(DLjExi)*hm_0#h zS_&ljxKialcd?`k3<8#W-6evX6GyOAO9dwaOTr8$lmTI-WXVUt@h5{o(k)yUd=es4 zKU@AvT$s$3!3{lCej*1?7>SdDx!Cwpt5R*9R(;B^`lU~`YZH`N?TL^+MYZ$K8pafu zU5$-X-KX|Ma8Kb@@RE6Y31uE%zC@g_F(PLCO1wHu{0?tv!|D6_Pf~ZH^IoAm>YBe{~bZE zeukovy%*(LiE?#=cIy5-BzaZL@ZANer*nW0h?z9vxB39TCU_+AlYN0-7hDaTwGTO_ z%F_MK2p+Pn1+y2Q$W?C%=3}twpF^n`t;s;~t~edg2(BE4D=Bd0FnW^ih3`jj%V}fglr6GSP`u7pbDD;uYw-HMd{8;dQVrhb(NW@PQOB4K5 za5T10bw~S5a1UU-s|cRy?si&xK`j~EeiW7dPuw^+5F0EWJok38JrL_D?KEtNH&L9Vi^)`g0qQbNVLmF z#zbKBnlYF(;`_IO%|YhP>P6fc>6^DfdZX-Bj7X63fK+dk4YJ(CUS+w79c;OY9inMz z14AXP-Y6TU`7*W+*R+hQ`C3P6bA*eXC*i>?*d6JTTSckWQQDT&Xo04sHb?7xq$aP{ z`A98Zqx12!Kt5x1K2n=wby^uR$7x!I%kerNsk;f9FZDK2Xt~W~a1_JU?ieV2n|ZCd z`S!h^Q+V0t+az=IO~?9HOg$>zj5U8pp<^AdV?Cc!RJ@A^&^p%f+9z2iC1{^yt#HzKOqIi z!Hvc*YK&c=v9jxVnyLHnXd)4*Ghkg6Muy_vJF^tLP~1aijRDhJ3zP?hDWD#BRVG}l z!U9nhN!;#%DD^4ir6x<+juWRK*lLR4wun~hT`YUC;Ay~Fn~;&7M@l0(P%XjLYX+8o zf=*E7C{?5{%#fIl#P&#fIEKGUG*vwf%#+~q$U5~&Ox0?>7L9+auWd62yZsNPC>_;3gQw7{AE=^K~I`hQaV>N;A%GG z>!ox9h-E0K7Vl;g%WzvGrG1%LhJjk~HWce)J&D$dx7~sJEQBjNp2Hf7-p+~S(|b8j zftT|rKlrPjoLljcwT<5H0|jr7;;RUWy@x>5fFtiMtAM>jL98ZG0^%?=uGboK@pYVne}HJ;62t=_t|Jiv;t3KtAl@J`7Q_!EZUMnx zw0pVP*cn9bZV&N5-wx)@UEcb4=-tSTA$2s7p zbNop6LU3I#q}e4ceFX%mMes@O7V}$)+hXOZ_DIVH2b{sfwDK3wiaEf&9+}w%%zxv`Y0Hmh z{Ah%atZk6z_5_8VQGl=SaCryf;}S0XWgTX*Wj(i9_v6;Ukr>m|V^X?YVp-p95$~=e z{uQ@dYO9p?4&sj4z>kZ!ZxSb9>93v;Z|!XmaGs$Kc^-Qv^!DkBzA%LoWy<*P1}Mv45A~6k3nRUI1i$Rgn_JP#u0Nq+F>aaoff&;0-tYJ#W!yP@OdKco4k&WM>W zfKF>#r=0uWlgd$J)7K$Mc5if^7_vVUwbVLvTJ@2r zq#{Y`fk@zw1y3M;*8}{C;5ooq2arkXS=#v`l50e6_xHkt{H36`fci{E634%BuMSo9 zRpeaMcxEklEwPd@vZAq;PhH97)d^`uJ;6;Kh6zl4BUZ){-(my)R8P%yH|O2pL!jldfY_G4trfSm{S z0Twb_57-AG$@-i{;#%o5>ghkx-nS2VQ~PM74DZ1ha?=P_EUq4qUHx}~^#<^3PP+ON zv23@$F8lgTP|B=Hes>Q#E$(Vyx_eN}CqW&}Vj-HD+T&F8GVf(f>0lS-z;Y(X+&xbI z#G@X(WI3hZBc81wezS+jdHC@!;M9L|a(z}x!Uz6R4ScA*qW=-`_U^#X$#KO+;KT^_ z-sk1e+P-$vd?RHK4Q_ENda)N*izNKBiGEMEh$jQU)1GXRoW~K1CtD;2^}yPbt>VcF zV)5j0(cergo;)F*yaud2*=F&iGlpb!uT#;B(pRwhg4UW`Sdy6BE1on>_IYxz1 zo>m@TaMmu@GIC(RYY9}|CPHsTB zytuDZA37BsjDFQH9)i~EbW%=aK9o9s3q0LKJ`$r}0vjpq+9Ks{n_{YbT>R~HXkVn1 zP5j-`m_6Y2KH-5j6o%kJg8t`$RRMTUqED)gIKhQr8LnQ?9_G(sqvEO2wamfU8WDuT zBIdAB^>+hH4j*HlRh|IF)WrvIHVEwl%s~zYJOO+@;$PtD4&Vvk3EZo|skd`o);fUC zKzvOsmDwhM&rsMQ(s61V@J;|QnpLViGZ8QPtsys3o{B>T#STTQV_t)g;Ev4mSuS?a zayfXqnH4)&dCQ5VD;7KG$tGa!Nr{7=yh1FVOm)zcl3)gug!0o(#Lf zli{NO734 zn7xdyKVz~{FF9nurRN(C;fDc`l=k;||B}RVFnBtaFG(!Rh$WUUNi3HF>(cC(*gQ-u zrFmIm`4X@$%`0XsH<<0~Rnh-~`cj$$qJN3{QkvHsx-_4grFq@K0X!Cysb1P16n!tS z(e-6~stvj{EXk2K3}Y;KJqGwnvq4J3w=3w?7yB$m=_kkY&etV{ENc=tK6l;%OP z_9w6|%|lm|W~1oGV7%6)d06zbfOTmexuP_WN@>PWUl#70M1Ll*(N%8o=Q^Ziu_U2y z;Z7M6$3S0c=1OV)4W2H|TqzAVadc_sN@@N8)}@&z-nGQ+qf0Yitnqa*x-<)}D9u9A zzlQo!H#doX9rdMdZoZ;4i{w}Ow^LtkOBRd%c3`7xZFAI3ghMv?SdtBg4TCfAlY@Mv ziICF#4xZk?i*WEMr55SF(nLsUGJthyBE`F_iKT8_VyzZfmnOPu;w zivBk0OKF;!r8#Q0v}gx64Bw>w4H(!|b1w0XFNkFquZ5&Hu)5MWw=G4#6*2FLRE+44 zB)--K94mOKU^j3p!Fz!d<=!^Vw7+hizUlKw{^uZ1ug4&B0|KS@=F%Kop2XnBUGL3l zoC1uebFrpMllL_kX=4!LDqK2MPf3;MPo?o#jghLSq?!SY+PW?X4t`*l+G0(my@O+A zh$ap8)z$}gj`mx@)3x=1owaq4SbEY2c8>NZfpu*iwR5z0W&ulW9kX+^PXN}n^`Tvl z_P*NsNc8(sUux@P(J!LD)Yd0r*>xYd-a*wm!47wr(SqjkwPx{dQv6i2FkH z-y(htt3map=-auWFq$*%SAsK$A43(Wrhw%&k1*Ota#0PEViXy=q~q@8*jT^nK6sccePKl@W@9D>yqecC z_lc$U%B8gDfpzW8Fl)~@s#l19B$lPR_9{g`1z6W!l^H$XsD8cJA4YvSbEp>ma$uut zQ`C_fpi8#^CEIZXs|rX$hx$r4KuWh0JYBj0QaZlONtb4Tl;#|;F3mvkE&`LIF3ljZ z)&*FX=Bg`7Gg$P8QeWEQ5YgwplP=BBD@rp=N^=YKWgB(4=syZ~6!;%>9 z8^+6!eCIDsTPe+1@N{X~N@>EeZq#+vR&q}!mbz*yr5ggQOBXNRPA8VSN)YSIfOYBG zT~WIBqW=i>rF0!ce?Rr5r**udbe+Whm(-U{ z_rBFoke#ccOVpPw@LY{wc*#ZX>aHzeAp}dl6WB|FpV%P5X@Ke@=bzey8aFNqzBtm%4l{w_ALR!M%!p z7H5y>X8{{sZ^8;sop8u+hUod{$FNF=Bz}a?`x7$5E(TAxtP`?cc!*ez%udLR`)^`t zStn%1{R~)_?i-mw{~(sqeJe9m%f7zSoxEbk{Z8~VA=jn*Ui7b_zLf6N6*KM+4*uS$ zj`}i&pOzVN8L@0z{wV1mC6-O!Got@8@rThU{7<5Pf%t2V>1PEeU$amnuwX=&Zl?c1X`D08iK64jC7!iKVu7 zNKaY=tZVCO=^2j`OKm+PE&UL%Zi_pw7zcKV{&&=uwzymLgK#IHYirLHO!L$4A0b83R^JNp1yCmt?h_zq;E>EG1bjB{>MJ zOLD7t_C2wbWQ|w~9^fmf21bYhI!p;@xhy*?C8_Ykp!xJRG}2rd-?F zA&GK^_YV^e)OWo96l?8~d)fxLe*{VCmvfl=Za9Qn@L6ib`& zDR$Vv-dTVW$)nk4ZG1HQZt#-j;q0@rqqd8HGj2eYI-^~ z`|@`n$Dx_>2`u5{4MoWeY6BaPX8sQ0eIU|&8o5yr+1GBE@AZJ#29Y-r#0h3)^z8t3 z=5vI3owrpnFCJbvu%ylG0iqd*G@nN9FmNQVaUeQDVbAISmI+>_B|0 zU^Pyj13Sue0rvgaqs}^tcBG#RSZhl_lx83aA2xjuI*k-Fm51N-Pr1)Qq$wHL0v&vr zlI$-tOW9xUYB&#o9&i*riqVPr3=u+}{jNqRPk^}vd>fQpvIfShW*@YuJ@8jSkfP=pQ@3+-9CP9z~9$)00`7M~m0NxN}e-nu| zv`m6;%zP2Vz(1j53_Op|WN%Y2f1orAgu;@`+aE*}h}@AN0_?wWJKqBn9dNx06W$W= z+e5)f%K)n+o-2iQ;HafCtP zLC9#0sh9XD78B}DHCW_P*~&=eMx9zGx!s6duKLYX#WHF$>^b=|!aOyf3l(f4^4&o+ zcm~%xsK1De5_>cgp*DJkMmx}Gsi~JL8;Khw?S7<5?*r*hCOq9!5;7Cp+G69X17-@C z<*N&O5L`!j4HLj(hWB8@q79`M5OC6J(^$+2W$*M>H?(3Lp_}$lss^NEEAIZuo zLG~~|mQ|Mi-Pb3QR{!qnQ(2AaGmOvlnl)t$JJDICShk*DVlNhM@xAW)xZ$k)a%Ba} zC%;@dE04UdCzhW^XuO>`kAsB9`+++@9OK9A^m>>VXZRC@vDqSJS@8Q6{Tq?9vKfru zh$PBU#W}?T11P7;q0M=bet@jgs}RbY;iH}+e&s=kIYnqX#OR%hK-QZ@Qoo@^0>h2| zgXMcpW^aBEM>Fv$d8dHm1}C=?gnjMC`QF(ex`X3g3StyRw~OfbJn#J=t_KHyc`5+C zR{hH+EsP-y{R+pH5RM&(rV;~R)n=P+o#)v)*MO5&ZtCeSvBjplL;<_R1rEboZEWk$ zBA2UH8qv z$Ar=mcH8_NVWXJ>ooz%j7+h~tG_>V7)_+Lo9M9^<;0^(QXp;|~{t4IpHZG9@?7zb} zysc{tokccjX-xb(t|?G1X9m)L7Re7KZw62IpG9)g_5iT-A1b2%)U*E_g}1u@ESB;7 z1t`H6{xD*&s%8h8XH&E=@M|0>Limo~$~>`h$!}$zSc%0-Q>;)CR#wu=8NZeJV#Nz3 zSQ%qud7eZ=Q}s5vc!G*@bpTrUe-N#5z8G%y^Xs@JBekys25M( z1J>n5M(|`KJqbd9b$MrrC+DDK&PnA^lQ}8gaME_3>fZDz2Z+|Tv&ge@OxG* zo^|7zRF3DW#j_E_;z_l5QVA@c{GvTML{B_^Pin-IRZucLp_+Ko(QwjM*8!aAHU#Ox z1y1CDgXszSuU`v8QsoC)GHu{ zMC!Pu;bb-Dse`Pya^2dP(I#BKB0)i+uae89wKcHw!rL~nyYa0bDQ7=Y9 zL_Mk(S{q;Es$(}pTHEzdGCiT1c+$pj(pH{&t0xjp)guhOFuui;_(<@n-;=50$#z=%U8qw|DSh6Rww`{2e6Pi*e$`*WhU{#)%hp%w^h(apFa5VDW+p;YB&USm*a* zym-O+6XRmNjEm0G#cHlty#-jTG9j!UrPbg3R{M(8N1+6(&oyIt z_v2Ib)MEmz7UOyq3iVTvvy``|SpJ5Vq`W<)yaAl0ES*V)3M>c#;FG%a4rU ziP0OLgopTQB3nEugc3X{wb7HaoTYncPw2&7T=SuD*6&FV@#KD55>I-FC(jX!0fMJYh!gB!iyx@q3ago@|Gb-;>B5Q0%0~MS4+=>uo5U@O#op zJo(9Qsgqc0f@w`GQ4yB%X=#k#QfIN01SMEnhG={E@VlxAP-<$4@6M${&j0nDONE@h z>pPcw7232^$;tq(KN1Q%!rCg$mz%8B7FTbzxVpyT z>RL%_cuuiNZicHH{jP4dxVqWm>Sl|pk6B#ZVsUk=c2yqCeca;G6Bd`YSzOv~ap_6Z zrRUjS55T42(|pl8U~%by#iaulmtM2D^t#2RgW4r|Hu#V>Ezx^JM^Ap}@utPqw=Aw6 zwz&GX#npFAR|jxF`yQ?amik=%-s0-_7FWNwxO&Rs>JJuIPX}K7xB|H@PLL;6qa>~FGVVYg!RRj2RP)7+W`R6<(Oo7w zFb-)A&wHF6li^ZcnJ<#b7MGGOE+t!B>SA#zMY|*sNVS;mYBAl-Vmd9bl5WUc+dVJ= zUL?rFwLLUn9;{8*`ts|a4AC(>r!(N{2)O#A-_;QoS4UV}9bs{Gq{Y=y7FP=_u8y|2 zdbP#XYb>sgFy>{!kJnDjWUKZfh9@usVj2x{T#%;~>SxwYXYqakb9k>P$;T z-=HfxARM{YYgZ-Ovn)lp(Ncuj;*#Ncj4RMJaN`fZ8*40Xtg*PU#^T0WiyOCD+_+u4 zaUb2d!{Ww2EN=YM;>Mk(8?W_5f-P`k&2_$LY_Yhp#p1>miyK=lZai*r;|cA?cS*=~ zo5hXo7B`->xbc*wLU%}7!}D?s5+8(1ZOeTw9kjS~(BjfTi%W+rF1=xK=}qm@bC?#@ zTNalNTU>hE;?g@7m);HJY+`u&G(zH&aOoMpOD8QZowT@g(&Ey07MH%axO7Up^hq*u z{lVhWX^TrgT3k9~ap@=1r7fJKoOt=vtQo#2IV~@ra>@gydc1O4UOp9MdHGbZe)*Kl zry))mukv`0+(^>u@ha5vQmQb`m+`8xJg}mTZ=x-@Pe@hVtSc)*v^8Tzr7FVybxH{P4>JTZWk+KvH zt3s#ZN^8=uc#Ip1B>uaHdLOk=p4`0=JiU)vD9`S$B9?trjW-a>KB~q~0qcEKjs5$m zh2o5VAGJ`*;@?Ltbn259b02k*`4F+Wk6I+sSCDmj6-ExtwmGQohMPVC$t9Q>gU&-Y0=d56w%Cw)&yLYe$9G| zrl#)^&0=U(;kwJOd80*B)9;FAJ+v0#ddaUj$D*m}OQN|FTI+EA;MbgI(bRP3A_FDb z0Ie;!nqBWJ`vQxmrU#4W4ruMemE+gE$)c(0>!{i1d)$2-f#w>mZ1%O;Gd3g8eB_ZbuOIwaez`b_b!Z&duT1h}@yVJ}}Qa76k9tVTU7~ z@dLb~otvHRYUFx9c-`4A>)_x}n0F~rKj2{+T&m1@J8h+dcOK%*&LDzto0%C0A_hd7Pu1HOoG#$x z4hO+31OJXgEdqCvg2STj> z3&d`4V4W-Ay|A7_>(@IUp!FBPI|Om&Qy@-}coak^Dkp0otseyH1eE&$2#NDoAg%_- z`y+^2ivA-al+=ku;%yYUK)gdD4#c0-=?nt?%=M;&NJB(W7ra?DP9qasYML{(7S>mS zHwxm+1t1vNOq!StBF(4jT?fu;aB??-kh<6g;&E`idqEr}aR9_GB#waa;N~j#a}es9 z+*AA-k^39IBt8x(^@1G!@V1b?l>Z2o>zpsspVz?~0deN@AWBF)31SwAG@q*XeQ;KS zllvtI@#i#%UEp{xf;deg5Feq4lQ$AX4-&0F6p-iyq8dbQ4-n#0E{HYYpw4(-NPi}= z&Sp5Tt%C=3;5`I!W*LZ=NK6F5ddaG1oh=07>*RA+fe?T00+9)h_hAr~B({TCN1f-i z&OGmHApT9!dmt{5_yR=gbPzv)7!D%$0tk8t$9O}Kcm{sbu{!75bj%H%8Mrco@v)G^ zAJF;-h^$R?tTm`jxN?*5A&n^$#OJuY{qPZT9f%PiI)KQX2m%#VhA(ZhFK;|EU~(5O zRp}ft69y8&>ksuz4~TIjqCixDNb{+BGr(EMEb~B!*?bVY!SPN2@fC?u5DHx-x0b|+ z+y(fOx>yM!4gwUJi^P<(7^fORWxn$#*zf-_cs$WOEZ=i7_v4!#IqP#MdA|jfk1O{) zKE$#y13$n4@P>kTo}OF^ShfJV%}Pwaz^6mmto81D|*^b{PH)IS-$1aQ;r8?gcLr zvw7wk5ba1T0+9tG&8O;RGZ_j_?hX**(>@T3!SNmf@dSyZAl{(PaS*7pGx#!_3U9t> z@;J@Ta?YuTk+!Jk@1dUA48$c8jX=0CwWj%0yRbl`W^cfkRM*WQ_CtVT^R6rLXH4_B)NJQP+J6ST(-3EF24Ul5x*kMR5NSSD?~CAc z1Sj`R5MuvB5Jlj4Pk>lK(OD3fX8z>Yh};mg7_r_I1gB^`iImw!TRuYT3!Rz#$AR`j zlsO8-VG;vC90!p`2dU=04xC@X$-MytEYJ7e45BUO$J|>%z|uV;VCTms>utQ5`_U^a55CQ2x)2SUmrcCPE&MSAduNOf$j!ho2S#r^+Vm87EI5?T`Ngyo8AEJu3k8hudLL=Fd!GnG7t93I3?9eXXX z9Pvd2@t>w>0h}NQp^=h0K@LG(l2#vpMhTtCbnVtS0^7lQFa$9=5i*8x~|%2_b%luyD=d3pyl6W1Il z%E|CJIs2UnMI*Z&p;&+`)h`_{(g!K+vm{ZN5go#ks8^%D8f#41q|v!&bttXs+~sfj0n^=s^J6n)() zW{d1Q>PnxSBem+e5%{0okk?$n1;o-y<_UfbI8j>3e923V)@@>evH-Vd^k}n~hkv#-uF2I%Mm)T_2x94YKLl~>_I2VWnUJ3r+!XCS)sE(-ehi92eoKkR1OlHCe1KSf;nYA@{|5_PJ)t! z{EX}^5bwaX&@VkN(rQZ6jW9er1&Mwx{gv^CYDC?r{G%R$2bmN68-y$m$m>lf*G;Tf$pDlE2xSq8D z3H483=Ld5SZZOLu&)O^yyqrqgs3eD7i-I|i{6Hl+n^+DH?iO4}EIaY{h?SMZvTc5^~d;)hbZXFa6jgS{JA#=M(=Dtxz;x{g*xvs?z`yWHL?FRTaqLs;gO# zP|)?*!%~mwmU_&v)MKX5Imj@po=(V=oGnHtfvM}Umo6Wb)=^33>0I&j7b?j--B)n; zMZhvo=Sk2;0cY)FBKcY0AjzqkO6S_a=c`2NKH{oTM=U?u93uEPV)=>6P%+YE zF|hnZWtimG1~{Q@2+|MNkv|H9J?U99Cz#_TQ6DCf-_4GfL}kEu0FSQyO06Q%T2C!` zsn2B5+D@6zGBpw;e#n5SDnZ#Wp4_g8(i>D~b+(RsjC%51< zVp%*j6=T;D%L1yI;AOz(8T(-M6$ju~gB1tf1?byPQV4^3E_*eYi_CYJfefYxf;mv1 z0oJPqC}Q;6!YLyNi^)NZsEl&21t%b%c1!V*<$)wshJZtoyg#_QyT2j07&r|X8F{$$ zkabubT}4Id@^1@%3^*%;xyd;8oOEiy4CS8don#TlB zAeLx;DEdo)`6tiKhogWpVYv+_qcT{{XbVp-hAGA>9uG?01@&ogLC5Q27~}Oac*%)9 zM3Jv#Pq+nGo;TS#LzwnI=fvNxh$w%|JAmo^gVZz82+2d=YzX{bo!S28UndvS%c zNlDIOBFz^mCHrwD`=yOVI!C0G9KtokFKr^yO_Zilas-#oHC!Y|DA5nyq~P}phT7Cv zu|-Y5bsn;F$Vs2OY--F_6txOieuuP40@syT&RsOlCzf*;jVpllKO>>B|J-F$W8I7# z`c2E58ta3WAS}n69pT4pm*PL5Nk4wKD`OS%*@;G_Tlj2w|LGpulKY<7@}AM7#L}WQ z&RC5I%DYhKxR}>Xz}jc^TPr;2j>yHun0Z^F{iHNGVx~>qEbEfk?aGinQqqkn4l&CNieoMJr`rBp#3P9SJ#18Mgt;6Y?6j0#1E35c0>R zXZHO6XnPOvsH!z;c+Q+jA(=ozLI@BbKoST{X3`*#MipTas;Ho-)PyD-q=Pi2gQB1! zAQrHoh=4Q|6%{)wMFp=76%+*l%T?5Gt+met^xm&L-~WGko-$u*TeqYm!66I@g2Hif5={F)*yT79F7UXL8S5+mw$P4ok|26R$ zLqet3F5yg{yn*z>sxZ>-n+o%T;EoB3Axr$?Ii!b+O0AYPP|CAaEcRx+s>(GS zI?rR6m>zvr3zgN7nI3&sbNRF6a!ZCECf6q;&Q@XE^We?2t2E(skx#ROpVa~pm!^%= z`cdmHJbJ3`vmY_hePY|dX?^QB+)p33kA5AXz_PdI{kz5`9qT(MRSI7QC`~tZ;_Mg&ExCdN)<1g_N za`gaL6w|w!Ts^=NA4;ws;Ev)~v8RLkZe$bNQQTcx*A{zGr>xh-%UrHBU2`jxYTCUh zuvH|rw0haV#lqP)gaMZbFNe8;$XKMIUe!)%KFBnaIm5LV-vOSV0;SPN!8oETqv-fP zr=lEDC-Dp9azvdKF#*Gc{+{2BEvu1C_el;_SZ@6=Hz&BKWgU(S*KE`#-jN)XNz6M+? znwhfOja;t7@TK5pfX!0)6XcrgZdDx|1~<-Pc9dD+m`8D#4xB|3I*YI9EDl9=kA$sPV{c{XJcX`M=!Xbl zGP5|v>a^GDC|1aG2u;)cbB{`4y28D}pCHV%`hC(p3!aBB1iOlGnEl%Z1$fp%QKxGj z6i))r^Fk?$is~nis4d!2Q5%_!;={r9tz$DqJs!pVz!OwbKY2pg*ayzPyoX@zGg_3} zS|H_92sH1^Uug0#7qv(@avcoGMJ-lpIplItOT_zvJL{tk*7g)s{~q&pe|CurIOap) zFHGpBTkO$7_Yuhauf|FFYYf$o$+ZUB6F&<6rs{em@*<~^&{8tG5m9K)tlih9>mP6FR--qvK~3}cBlVt=}klG zn0d`Njaqr>Ae|~Kkm;Kc)l?x~GmOCR$knHd6rTSOxLjPZ_y%&Vp-QBG8r+Q3)?PDG z&9u%Up~m`gq;^dN{xk+(-gcG!UJv+8%n9K9S*S@r7c;8KHp**M=yG^0K zaZLPnC1&;ZrLrm(gjg7ovlc29d&QQS*&E0gdASS!fT>(0Uo8F?xt=m#B3^eh_;l2$ zwbaXPYDe(2{9N#5YN+f~R?2eW>k;N_*c^O?xL%#IAOidjhi{viUC{#kPT3ww{hWO8 zl}=8xHI z@+daQeag!%>xjaYYD?_dXY_QLrhzKK_f3D(DJ3)77St(<}GM3U>geK8_s^T zK!vgTWN#%~sNyt)cG_A$_#zca2^@5yT1#WJMrr}2ypT|d1Wh9O`(p9bI94|$_&i)2 z8MqN41s_7(Jf5}PK1Tku;4CEB+3X7)C0Piy?1^msD;4cAaNobkJB4z2AKSXKa(RTI zpG1NOO~qf8){t#qYMSR-;xcKb4fg?ekyiF$%DW1CpGJBk3&6X@@WWH}$(NFM7jHrS zb5-yj;+@G)uz&OvA42{VN7+?6t2vcCt`>MNg)bw&p8RTsZyRr^r8Hcpvcy%+c8+>VRJt!}%%=JdK8|>rL5yU|3(_R@8ki8TsE(^GoOL3p4&MrKABwCv5ttZBG-d3M>T@KB-h2J55)u9kfr<~NVWg{2+?x~ z7nSN%gk*ohKL3Y2>%HW<3y&A+B?8?MeqmJPk>xk1kb2`dV~9ZVc$J#>gOtXjSG%>>nE>rycBAAA)zv|9}@iK zZ$UAB#md_3ai7qP?RgelC)+lwc?-$4$9>YvlRv}Awa0x*dy>}A7wRjVyc@dOXx|{-kzBj=8^!yO>#WO78e?Lj7?U#Q};-y{A~Ve4q41128pgevWM+AvSwhkL5j=ItOw4G{WGlx_#%R%22P%XJuP z(O93F?Z!9St-JSvYrFA{_I?e?wcRlM8giZ3GkhUs;) zP3W5a!MDy%-s}&U2kZ~VbANCa{l|JSdOh4x*MYTwpNY;tfKe2D07cZ^;5+TcyFP~! z79T=Mj^h|o8G2fwQxwXO6FBBqhJLTmM;MyNkWEO&+#7UOr8Qpx{?Y7!)u88e%H>tW zFstlAr)H&ypHV~s@rXyJ7gjH><;9?kvVUUK!92!$Uf*Cs7e{499MP?uFjto92C)s`(P zT4yK8wPlNzwXWnEGu~)ApZ??;Gcn>5$mM*hir+=9a|+eOw}a>5J9RD(vTg=cT?HJb zq6SpGXnqUsCvaaEj{Ex2W~$Mtw=zVXi^FOd-4eS^xzj59U^5gTcUl(`$f(G4(niyT zr^>GRPFk957D3H?CoNsP8@axd)>wQXc)>|@CNr{|MRUo!1i})0C(S3egDR28rWx#6 z6520J#N zLaxnSkrF;buG6E%itm02TvJDh^b^6&pDGjIRCIO6VO8CGCc9 z5dV!zZKyOI`Lz106oHp9Gxme93HQ?4yUvkz7X+9^(_ASIC)c8Bp72=w^|V+2jdX>ua3ZiL| z@_-Z@g6l6Y6`ISbU6)IcK`LGwYHJc7tm0iou1UOHIjJDmBwnGCOa(9au(Eg~B-}_P z74K$6dW~Gg8!3L0T*VtDevwAb(ukG=&>lBrObb)HslsE@`Px7|oz+)Yy_ zN{!=**fiZSmS*JYJn3yfpniI11Vc#@4@f@SC&kyDr9l75;qa)Yb+@vcv z+rr4@N%KuydC``No4#ZdvaZ>ny$Re4m7;G@S$+Yb?m;jWao1#r7O; za%fJ38O!o}6OA?byju*Gzndf-G>j(I+l^ICs0)l$O{NQtRXLgrU$(4f*6~3oqPJwfswX>$> zdK}$;ElxqT7&m?_138r+g{(z?GInm?YdCa*`{8kH@$2g9oA3 zsv*D-fPyT9bSnVEn>+D)9Op4F0|ZCBfyP##SIYY?P!bLc4sYgU4$&bDLJ!E zcsrU`C5}R7wh3>`%SXQ(+9nvD2X3y;yrb|1L{bx6r7Ld9(^>z%trcxD#vU9 zeiB|rJDNUz7T-m#+3FYZx5zb_{Hp2yXL3y_{}xX<#8hLk_#DDiHP?QwR7((IHql?0 zR5h1=DSQLM%%|_q7W75%L&kyz@L^*? z?RvymP@5h#*bHhDcX&Kf|H#DE;QrXe)xbVx^tGTkZc=ZUrX|G>YI<{n`Wa(szaN&) z8cSMI{Aes`Npa3t(vsr5!CFRKFmdOj8(Ke^xLQ*D?9@S1$bT{VT2lOKu$C16HgWH% z2c6$cTrF~cH*vMdy=e5c$o<1$Epq=faaV9z^OuRM4U)?&cP7_>+%k(j+$Gx|7d>mS z|NE-xVzhNN&ik2{O#7-`8Uwy+?Dq}Sr+w8Xpk^DWPy4Fzm9=Sy!(bt^n)-T>*w2pP ztk?t9I360|(n(e|jexsaLL=K?XyI81X{2q1DJ$1tUE9yArV-Y=3FOTU-pUc#qMFwF z8in~LRh?99Y2xaHVk_l1O(zozOj@iN!`r@45|H(Z~*}f?etTDkY_kWFaK@5C^LJs%np+c7d@Ct;Bew>8fgjP$XCS z%4cCsKDW2YgxpsjQ*m-l*BQB7)Aa_+HT5+*a!ofFxqQiu)&7LxG+i;iN%Hh&I2mr7 z%gv_trQ)*J*Mb#6nl+rIg00uZu;xgl#6&n znhniH_-&&qAMuXC@)7TvEXgk%G+EL~llM$Ebi(9)gLSgx1A}#X}Filsa!i*c# z^`~&ho#vqNBaFM#b*IQ!bw%CWP{p`M-PjOi@D0_WA1#ccY-Le&A-9P!r%SjQCOCaR zLv@)pR|&Y%e}Ke{v5v8q##klB80#3TF^2w=kA%u%<*+6MfV&7cOdH9)dUul>T_Nou z+%Rn^1Fkk+d`l|uHKtg)Ona@#pDxk%cCwFk3}1L$iiee<{YSjTb^FB>%k||Ue4^yV z(AxbE-FX#z7|SAg*FUul4Uo%|{v}>cE~ns1p!Z!vF2`VrzYm@!=inC4p4$kC+6naW z7a688?MdLG!YmcT5;%7>IRdVs8?Jaal56-zh_3|K8k-r$8hbC-*liN9wc7K=N^bP5=b} zMZEyp+<^t)He~W3Gr1wQ6zAOA9)PUvUi`G$f}h^dZS@eqJpct~ncYKRyJ+h(fQU~3 zP6Ko%_!VFYf%_Shd_!;zK)ufa1^`@5 zFcRQRf;#|C5j+Nv`USvC0ObVl0NhXTF?9&O28jO>AaNVEQ3O*Uxt?G?z+{580E-EB z0K7?X4&V!dKLHX?07M~rYl4OVHvkm*0ovD3M&asNE_NqtP;C5O)KxKLQ=!o+8(=9w zUPz|zV2k0 zYdhX;K;Q-VH$f6W!*2mn0h$98KY?6s!!eRE_dr_4;HLm?B6tm8EC4=TRs&Y4bQeVP z81?|bg9Hlz-l9_8@np+&8b@Jsh`wc57l10?0kj2ZOwb453V@=)05E)@26`-;<2n>& zAOZ?LgQRc~*hK2D0$5M*6u=t<6#yp*#sU0JFcYBaX@Ca-`~XFJ0FWVH9h|+vQ8)lK z*_#?80cI1-2jCmk3QGVE69fTH5?llDJHZrys^8O6@k&^E0>@Hj^ zugLUM2rK#=qQckla|0C)11z9IL2M%E@1WZ$KLc7O6;C+HjfUgKz0n|N%-oCpIUT%hb^+p8ihS;{Bfww(?pH`>w(*r7P4*?X+LU_A> z16e(a;xJ^2!tmq#6@P{v&;%oHK3rYlBKW|0h*$U^z(WL20lY)73gB0QJpj>X5xsZ_ zM)@Heo%TU|AGV#%AufCY;93ISw=|sKcYwPHc&E~%1o;3T5VQyQhF~Lfl79qv1|UFi z6rdf!mjL|;&H#)fh)1mX1oZ*75HtgLnV=iM5rXRhqR#X zAP-;*K^uTG1ib;0eg+r>FbaTw&b#>kNubG zAc79O33YAvMa=ZMBCy|2mjt>JO9U;b~jhTg6+KEZj?wQL0Ib5p{gM!0h%4s~a} zm=Fo9RV4GzRb*SNB7a4boqieiwCtUj@Ph~VHyr)_P`X9@I&v+8Cy8$)*D`prINzgV zmcdiRe<0T~c&fPfcW~{Kr-?TO$15<9olQ6>~1#pHkE1di?-G zwA1QZgRNQ{A=zuP5VKniwqi$e?Y_F#fO~P>POhy|59#kCuY>BedW!!-9&m$SrTD%- zz+XxL?Un~8`$+gq!U4xs37sz$$rB4lRM&2ja z>6h!oQ~m_k>6PncFG$|84ea%my&K83(YitQ#*%A)b)(|XBiFv_Ch-r*wYTagdw-Ds zl?r?PWiRe8aGk;#AbV-#I(0Kp@mrGXcGyAUE68;_>|oh@mHgvO*ejR4kH~d9Y=!Fk zG`Vhv9U@-f!cAbKhP$kxqP;=0bw}(lMMr>hN9=HgKS|!G2?mt3dRR;g?~$#s9;YK4y# zXJ5HT;rqywnt|V|@DIq((!buP@IT3~F9Bbp@alNZJX?G6`^D4AwJ%?*y6-`*Q+MlB z*Mq>@jDdg8LSZ|hvs>G0Jc=^6dJwWD2yD9wpzR|5%*0Q@Btx6T)y#wBx*2z(^mmZ=OhQVxNdFCTog|qg{eO|`M9F07|46=}82VGBALaqq$&;zl zuLTZ=vKsZb=XG>gojm(NZr+EW;`J!qK^)x~sIzH5%E$LXpt;O;PJU|yd1e))b6$Rn zk35^pZWrXYR+8&H+fUlVZzR__`k%$$19#2x z_Ak)3+$vE+a-EQ~RiYN;Iw|K-U+PS*6LVqW!m;A#Y#8*yr?28NJ#_M!%oR0GqDU3_Ft=!BDd+R4sk4sRO6eZ*?HngLN@=q!VF6_2Dy0|6bs92HmJgBZOk{KM zKgo4AvW4t5;j@po(l6!9o*u*08OoNj$LBlEGg7S-e;~QeR2GOY0mom&TNzw#z0UR1 z=QS*jV76LpW6Sj{G`w$Nz4RWv+2tp|Ru62R!@6JV!#?{0i-PO~S;Y%EYlB!R__vrRF)gd-cbwpt z4nhIK?9V>LINwtU!7?rwpgNrYmOJE!Xpj{S>E0OR4u`BnTGpKoso)fQmm$7&O+Zqt z+_!1}p^la-q6F1ctrIv8y*G$2F`$C)w}wz|VD*#o&JIv+2`J?WA!UrE@ljA~Xx%2Y zy;K_v=-ZkOaU*N3#OvEbOgC5U8E_s-%0g09@O_8|w^FS)rJT|Z$`1lo`wTxkD)aR< zNW>4Y*|P+kc$}URse!9+s7DgK;QgG zh#OlSB`zw0crCU6%7(a!H4^QC@4-Rr8yEpehEsR@QBL3IY@4Rmt%|yhMPvo}%Gs37 ztkn|lq*xVF(FS6lQz2PM-}hM3BD!6^ z*U>MnEGyz&=5!yE`wF4HDqMPITlFMIkOVn`JF=EcT+s26|FrSV4*5d7Oq^5`eg^V{sNOj$}V==U5y>iw8Np+B=f*l>83&V&NOcGUlN% zHUBv}s4J`|CH7W|KV{8!bkbPKH11(NTukB+V_}ji zE#A)6NYJskNfw!-Qpe&-TKuFHBxP2iqNY?@dSFbjIJMGZD~h{17WdNPEH*(m z$Kpe@cqRps?$#1T^;Jf_vH;>9)YD0Xr^|QonDlIcG}e*b z^(O0iiThTH$FNcRIca#&Lw(2TU;8^2J@DDSC$bEx&b zbCkz+)S-4#!$O6!Ab? zb_0CODGukjPE12^qItz7MjYG0}Qb##b{}i)nln z+u|0B6I+-)T5Z98JIOIMm&U8J-%YkoDe9)m0`1I)c#0Es7Nc&>g=DG|btR)Fa1Nhl zr5>jFEtOF}Y6S6gC+c$61*h3kXsIrc-M_f_nql3bh+g#dh(bVLI)~{@YrMqC6l)T# z%h5l}nM7xCwmeZCl3Shmb2TM9I4#b$-h#snvpqQ9=X;rwIgTU}l4=c6jqinW5$@`- zlPxmW`V=C|ig=MZ7{$tX2)&wSPGX7V^ZOy$b&#RAI}1MK#M z4IMe&Wxt4VZhwHD=<;|Xen3o*6+QyRvb$kjC3Mh5e!H z@pu*^Z+2^}D8q}!Q4;C75-!;8h+>6zJ45wWC_;Gm

P-+>PHzdqZ>JJ{u(2gx@OG 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 literal 0 HcmV?d00001 diff --git a/resources/usr/lib/systemd/system/weconn.service b/resources/usr/lib/systemd/system/weconn.service new file mode 100644 index 0000000..2ff8446 --- /dev/null +++ b/resources/usr/lib/systemd/system/weconn.service @@ -0,0 +1,11 @@ +[Unit] +Description=Wearable device connection controller + +[Service] +Type=forking +BusName=net.weconn +ExecStart=/usr/sbin/weconnd +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/resources/usr/share/appcessory/wearable-connection.xml b/resources/usr/share/appcessory/wearable-connection.xml new file mode 100644 index 0000000..4f3dffd --- /dev/null +++ b/resources/usr/share/appcessory/wearable-connection.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + +]> + + + + + + + + + + + + diff --git a/resources/usr/share/dbus-1/services/net.weconn.service b/resources/usr/share/dbus-1/services/net.weconn.service new file mode 100644 index 0000000..3b00181 --- /dev/null +++ b/resources/usr/share/dbus-1/services/net.weconn.service @@ -0,0 +1,5 @@ +[D-BUS Service] +Name=net.weconn +Exec=/bin/false +User=root +SystemdService=weconn.service diff --git a/src/control/control.c b/src/control/control.c new file mode 100644 index 0000000..2b2164a --- /dev/null +++ b/src/control/control.c @@ -0,0 +1,151 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include "control.h" +#include "driver.h" + +struct wc_control { + int device_type; + const GSList *driver_list; +}; + +static WcObject *wo_control; + +static gboolean on_spp_connected(WcObject *wo_control, const void *event_info, + void *user_data) +{ + int ret; + WcObject *wo_bearer = (WcObject*)user_data; + + /* FIXME: when spp is connected, what do you want to activate here? */ + ret = wc_bearer_driver_activate(wo_bearer, "correct name"); + DBG("activate request: %d", ret); + + return TRUE; +} + +static gboolean on_bearer_created(WcObject *wo_bearer, const void *event_info, + void *user_data) +{ + const char *name = NULL; + + if (!wo_bearer) { + DBG("no bearer object!!"); + return FALSE; + } + + name = wc_object_get_name(wo_bearer); + + if (!name) + return FALSE; + + /* FIXME: it doesn't work correctly */ + if (g_strcmp0(name, "eSAP") == 0) { + int ret; + ret = wc_object_add_callback(wo_control, + WC_BEARER_EVENT_BT_CONNECTED_SPP, on_spp_connected, wo_bearer); + DBG("add callback for SPP connection: %d", ret); + } else { + DBG("%s bearer added", name); + } + + return TRUE; +} + +int wc_control_add_bearer(WcObject *wo_bearer) +{ + /* FIXME: controller is a one of bearer?? what does that mean? */ + wc_object_add_callback(wo_bearer, WC_OBJECT_EVENT_BEARER_ENABLED, + on_bearer_created, NULL); + + return 0; +} + +WcObject *wc_control_get_object(void) +{ + if (!wo_control) { + ERR("control object is NULL !!"); + return NULL; + } + + return wo_control; +} + +EXPORT_API int wc_control_get_device_type(void) +{ + struct wc_control *cd = NULL; + + if (!wo_control) + return -EINVAL; + + cd = wc_object_get_element(wo_control, "control_driver"); + if (cd) + return cd->device_type; + + return -EINVAL; +} + +int control_init(void) +{ + struct wc_control *cd = NULL; + + wo_control = wc_object_new("control", WC_OBJECT_TYPE_CONTROL); + if (!wo_control) + return -ENOMEM; + + cd = g_try_new0 (struct wc_control, 1); + if (!cd) + return -ENOMEM; + + /* FIXME */ + /* TBD: determine current mode Host or Wearable */ + cd->device_type = PROP_CONTROL_TYPE_WEARABLE; + wc_object_append_element(wo_control, "control_driver", cd); + + /* + * TODO: According to the various wearable device scenarios, + * each control should be implemented. + * + * All of controls are based on wearable device scenarios. + */ + DBG("control init success."); + + return 0; +} + +static void __clean_control_elements(WcObject *wo, + const char *element_name, void *element, void *user_data) +{ + g_free((gpointer)element_name); + g_free(element); +} + +void control_cleanup(void) +{ + if (!wo_control) + return; + + WC_OBJECT_CHECK(wo_control, WC_OBJECT_TYPE_CONTROL); + + wc_object_foreach_get_elements(wo_control, __clean_control_elements, NULL); + + wc_object_free(wo_control); +} diff --git a/src/dbus.c b/src/dbus.c new file mode 100644 index 0000000..ebaf356 --- /dev/null +++ b/src/dbus.c @@ -0,0 +1,122 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include + +#include "log.h" +#include "dbus.h" + +static GDBusConnection *__connection = NULL; +static GDBusObjectManagerServer *__object_manager_server = NULL; + + +static void __request_name_cb(GDBusConnection *conn, const gchar *name, + gpointer user_data) +{ + void (*__init_cb)(void) = user_data; + + /* Successfully register DBus name */ + __connection = conn; + + g_dbus_object_manager_server_set_connection(__object_manager_server, conn); + + if (__init_cb) + __init_cb(); +} + +static void __lost_name_cb(GDBusConnection *conn, const gchar *name, + gpointer user_data) +{ + /* May service name is already in use */ + ERR("Service name is already in use"); + + /* The result of DBus name request is only permitted, + * such as DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER. + */ + exit(1); +} + +GDBusConnection *dbus_get_connection(void) +{ + return __connection; +} + +const char *dbus_name2path(const char *name) +{ + size_t len, i, j; + char *buf; + char hex[3]; + + if (!name) + return NULL; + + len = strlen(name); + if (len == 0) + return NULL; + + buf = g_malloc0(len * 4); + if (!buf) + return NULL; + + for (i = 0, j = 0; i < len; i++) { + if (g_ascii_isalnum(name[i]) || name[i] == '_' || name[i] == '/') { + buf[j] = name[i]; + j++; + } else { + snprintf(hex, 3, "%02X", name[i]); + buf[j++] = '_'; + buf[j++] = hex[0]; + buf[j++] = hex[1]; + buf[j++] = '_'; + } + } + + return buf; +} + +int dbus_init(GBusType bus_type, const char *bus_name, const char *obj_path, + void (*__init_cb)(void)) +{ + guint id; + + __object_manager_server = g_dbus_object_manager_server_new(obj_path); + if (!__object_manager_server) + return -ENOMEM; + + id = g_bus_own_name(bus_type, bus_name, G_BUS_NAME_OWNER_FLAGS_NONE, NULL, + __request_name_cb, __lost_name_cb, __init_cb, NULL); + if (id == 0) { + ERR("Failed to get system bus"); + return -EIO; + } + + return 0; +} + +void dbus_cleanup(void) +{ + g_object_unref(__object_manager_server); + __object_manager_server = NULL; + + g_object_unref(__connection); + __connection = NULL; +} diff --git a/src/driver.c b/src/driver.c new file mode 100644 index 0000000..a734d6d --- /dev/null +++ b/src/driver.c @@ -0,0 +1,290 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include "dbus.h" +#include "driver.h" +#include "technology.h" +#include "plugins/plugin.h" + +#include "generated-code.h" + +struct bearer_driver { + const struct bearer_driver_desc *desc; + + const char *path; + void *user_data; + void *handle; +}; + +static GSList *driver_list; + +/* TIP: driver is an element of WcObject + * For example, there might be a bluetooth WcObject, + * which has bluetooth, esap and pan elements. + */ + +static WcObject *wc_bearer_driver_new(const char *filename, + const struct bearer_driver_desc *desc, void *dl_handle) +{ + int err; + WcObject *wo; + struct bearer_driver *bd; + + wo = wc_object_find(desc->technology); + if (!wo) { + wo = wc_object_new(desc->technology, WC_OBJECT_TYPE_BEARER_DRIVER); + if (!wo) + return NULL; + } + + bd = g_try_new0(struct bearer_driver, 1); + if (!bd) + return NULL; + + bd->desc = desc; + bd->handle = dl_handle; + bd->path = g_strdup(filename); + + err = wc_object_append_element(wo, desc->name, bd); + if (err < 0) + g_free(bd); + + return wo; +} + +static void wc_bearer_driver_free(struct bearer_driver *bd) +{ + if (!bd) + return; + + g_free((gpointer)bd->path); + dlclose(bd->handle); + g_free(bd); +} + +static int __add_bearer(const char *filename, gpointer handle) +{ + GSList *list; + WcObject *wo; + struct bearer_driver_desc *desc; + + desc = dlsym(handle, "bearer_driver_desc"); + if (!desc) + return -EIO; + + wo = wc_bearer_driver_new(filename, desc, handle); + if (!wo) + return -ENOMEM; + + wc_technology_add_bearer(wo); + + wc_bearer_driver_init(wo, desc->name); + + /* Check already registered */ + for (list = driver_list; list; list = list->next) { + if (wo == list->data) { + DBG("driver %s, %p, %p, %s(%s) registered", + filename, desc, handle, desc->technology, desc->name); + + return -EALREADY; + } + } + + driver_list = g_slist_append(driver_list, wo); + + DBG("driver %s, %p, %p, %s(%s) created", + filename, desc, handle, desc->technology, desc->name); + + return 0; +} + +int wc_bearer_driver_init(WcObject *wo, const char *desc_name) +{ + struct bearer_driver *bd; + + WC_OBJECT_CHECK_RETURN(wo, WC_OBJECT_TYPE_BEARER_DRIVER, -EINVAL); + + bd = wc_object_get_element(wo, desc_name); + if (!bd) + return -EINVAL; + + if (bd->desc->init) + return bd->desc->init(wo); + + return -ENXIO; +} + +int wc_bearer_driver_activate(WcObject *wo, const char *desc_name) +{ + struct bearer_driver *bd; + + WC_OBJECT_CHECK_RETURN(wo, WC_OBJECT_TYPE_BEARER_DRIVER, -EINVAL); + + bd = wc_object_get_element(wo, desc_name); + if (!bd) + return -EINVAL; + + if (bd->desc->activate) + return bd->desc->activate(wo); + + return -ENXIO; +} + +int wc_bearer_driver_deactivate(WcObject *wo, const char *desc_name) +{ + struct bearer_driver *bd; + + WC_OBJECT_CHECK_RETURN(wo, WC_OBJECT_TYPE_BEARER_DRIVER, -EINVAL); + + bd = wc_object_get_element(wo, desc_name); + if (!bd) + return -EINVAL; + + if (bd->desc->deactivate) + return bd->desc->deactivate(wo); + + return -ENXIO; +} + +int wc_bearer_driver_connect(WcObject *wo, const char *address, + const char *desc_name) +{ + struct bearer_driver *bd; + + WC_OBJECT_CHECK_RETURN(wo, WC_OBJECT_TYPE_BEARER_DRIVER, -EINVAL); + + bd = wc_object_get_element(wo, desc_name); + if (!bd) + return -EINVAL; + + if (bd->desc->connect) + return bd->desc->connect(wo, address); + + return -ENXIO; +} + +int wc_bearer_driver_disconnect(WcObject *wo, const char *desc_name) +{ + struct bearer_driver *bd; + + WC_OBJECT_CHECK_RETURN(wo, WC_OBJECT_TYPE_BEARER_DRIVER, -EINVAL); + + bd = wc_object_get_element(wo, desc_name); + if (!bd) + return -EINVAL; + + if (bd->desc->disconnect) + return bd->desc->disconnect(wo); + + return -ENXIO; +} + +void wc_bearer_driver_exit(WcObject *wo, const char *desc_name) +{ + struct bearer_driver *bd; + + WC_OBJECT_CHECK(wo, WC_OBJECT_TYPE_BEARER_DRIVER); + + bd = wc_object_get_element(wo, desc_name); + if (!bd) + return; + + if (bd->desc->exit) + bd->desc->exit(wo); +} + +int driver_init(const char *driver_path) +{ + int err; + void *h; + GDir *dir; + char *filename; + const gchar *file; + + if (!driver_path) + return -EINVAL; + + dir = g_dir_open(driver_path, 0, NULL); + if (!dir) { + ERR("Failed to open driver(%s)", driver_path); + return -EIO; + } + + while ((file = g_dir_read_name(dir)) != NULL) { + if (g_str_has_prefix(file, "lib") == TRUE || + g_str_has_suffix(file, ".so") == FALSE) + continue; + + filename = g_build_filename(driver_path, file, NULL); + + h = dlopen(filename, RTLD_NOW); + if (!h) { + ERR("Failed to open (%s) %s", filename, dlerror()); + g_free(filename); + continue; + } + + err = __add_bearer(filename, h); + if (err < 0 && err != -EALREADY) + dlclose(h); + + g_free(filename); + } + + g_dir_close(dir); + + return 0; +} + +static void __clean_bearer_driver_elements(WcObject *wo, + const char *element_name, void *element, void *user_data) +{ + struct bearer_driver *bd = (struct bearer_driver *)element; + + wc_bearer_driver_exit(wo, element_name); + + wc_bearer_driver_free(bd); + + g_free((gpointer)element_name); + g_free(element); +} + +static void __clean_bearer_drivers(gpointer data) +{ + WcObject *wo = data; + + if (!wo) + return; + + WC_OBJECT_CHECK(wo, WC_OBJECT_TYPE_BEARER_DRIVER); + + wc_object_foreach_get_elements(wo, __clean_bearer_driver_elements, NULL); + + wc_object_unexport(wo); + wc_object_free(wo); +} + +void driver_cleanup(void) +{ + g_slist_free_full(driver_list, __clean_bearer_drivers); +} diff --git a/src/error.c b/src/error.c new file mode 100644 index 0000000..783153d --- /dev/null +++ b/src/error.c @@ -0,0 +1,63 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include + +#include "log.h" +#include "error.h" + +static char *__error_message; + +void wc_error_add(const char *msg, ...) +{ + va_list ap; + gchar buf[1024] = { 0, }; + char *new_message; + + va_start(ap, msg); + vsnprintf(buf, 1023, msg, ap); + va_end(ap); + + if (__error_message) { + new_message = g_strdup_printf("%s; %s", __error_message, buf); + + g_free(__error_message); + __error_message = new_message; + } else + __error_message = g_strdup(buf); + + ERR("%s", __error_message); +} + +void wc_error_return(GDBusMethodInvocation *invocation) +{ + if (!invocation) + return; + + if (!__error_message) + g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, "No error message"); + else + g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, __error_message); + + g_free(__error_message); + __error_message = NULL; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..3ed7d0a --- /dev/null +++ b/src/main.c @@ -0,0 +1,95 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include "log.h" +#include "dbus.h" +#include "driver.h" +#include "control.h" +#include "service.h" +#include "technology.h" + + +static void __on_log_glib(const gchar *log_domain, GLogLevelFlags log_level, + const gchar *msg, gpointer user_data) +{ + ERR("glib %s", msg); +} + +static void __init(void) +{ + technology_init(); + + service_init(); + + control_init(); + + driver_init(WECONN_PLUGIN_PATH); +} + +static void __cleanup(void) +{ + control_cleanup(); + + driver_cleanup(); + + service_cleanup(); + + technology_cleanup(); +} + +int main (int argc, char *argv[]) +{ + int err; + GMainLoop *main_loop; + + umask(0077); + + DBG("Wearable Connection Controller %s", VERSION); + + if (daemon(0, 0) != 0) + DBG("Cannot start daemon"); + + g_type_init(); + +#if 0 + g_log_set_always_fatal( + G_LOG_FATAL_MASK | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING); +#endif + + g_log_set_default_handler(__on_log_glib, NULL); + + main_loop = g_main_loop_new(NULL, FALSE); + + err = dbus_init(G_BUS_TYPE_SYSTEM, WECONN_SERVICE_DBUS, WECONN_PATH_DBUS, + __init); + if (err < 0) + return -1; + + g_main_loop_run(main_loop); + + __cleanup(); + + dbus_cleanup(); + + return 0; +} diff --git a/src/object.c b/src/object.c new file mode 100644 index 0000000..d828f42 --- /dev/null +++ b/src/object.c @@ -0,0 +1,582 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include + +#include "log.h" +#include "dbus.h" +#include "error.h" +#include "events.h" +#include "object.h" + +#include "weconn_type.h" + +struct wc_callback_type { + const char *event; + WcObjectCallback callback; + void *user_data; +}; + +struct wc_element_type { + const char *element_name; + void *element; +}; + +struct wc_object { + const char *name; + unsigned int type; + + GSList *element_list; + + GSList *callbacks; + GHashTable *property; + void *user_data; + + char *service_connecting; + guint timeout_connecting; + char *service_disconnecting; + guint timeout_disconnecting; + + GDBusInterfaceSkeleton *di; +}; + +static GSList *object_list; + +EXPORT_API WcObject *wc_object_new(const char *name, unsigned int type) +{ + struct wc_object *wo; + + if (!name) + return NULL; + + wo = g_try_new0(struct wc_object, 1); + if (!wo) + return NULL; + + wo->name = g_strdup(name); + wo->type = type; + wo->property = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + + object_list = g_slist_append(object_list, wo); + + return wo; +} + +EXPORT_API void wc_object_free(WcObject *wo) +{ + GSList *list = NULL; + struct wc_callback_type *wcb = NULL; + struct wc_element_type *we; + + if (!wo) + return; + + g_free((gpointer)wo->name); + g_object_unref(wo->di); + g_hash_table_destroy(wo->property); + + if (wo->callbacks) { + for (list = wo->callbacks; list; list = list->next) { + wcb = (struct wc_callback_type *)list->data; + if (!wcb) + continue; + + if (wcb->event) + g_free((gpointer)wcb->event); + + g_free(wcb); + } + + g_slist_free(wo->callbacks); + wo->callbacks = NULL; + } + + for (list = wo->element_list; list; list = list->next) { + we = (struct wc_element_type *)list->data; + + g_free((gpointer)we->element_name); + g_free(we->element); + } + + g_slist_free(wo->element_list); + + object_list = g_slist_remove(object_list, wo); + + g_free(wo); +} + +EXPORT_API WcObject *wc_object_find(const char *name) +{ + GSList *list = object_list; + WcObject *wo; + + if (!name) + return NULL; + + while (list) { + wo = (WcObject *)list->data; + if (g_strcmp0(wo->name, name) == 0) + return wo; + + list = list->next; + } + + return NULL; +} + +EXPORT_API const char *wc_object_get_name(WcObject *wo) +{ + if (!wo) + return NULL; + + return g_strdup(wo->name); +} + +EXPORT_API const char *wc_object_peek_name(WcObject *wo) +{ + if (!wo) + return NULL; + + return wo->name; +} + +EXPORT_API unsigned int wc_object_get_type(WcObject *wo) +{ + if (!wo) + return 0; + + return wo->type; +} + +EXPORT_API int wc_object_append_element(WcObject *wo, + const char *element_name, void *element) +{ + GSList *list; + struct wc_element_type *we; + + if (!wo || !element_name) + return -EINVAL; + + if (strlen(element_name) < 1) + return -EINVAL; + + /* Check already registered */ + for (list = wo->element_list; list; list = list->next) { + we = (struct wc_element_type *)list->data; + + if (we && g_strcmp0(element_name, we->element_name) == 0) + return -EALREADY; + } + + we = g_try_new0(struct wc_element_type, 1); + if (!we) + return -ENOMEM; + + we->element_name = g_strdup(element_name); + we->element = element; + + wo->element_list = g_slist_append(wo->element_list, we); + + return 0; +} + +EXPORT_API void *wc_object_get_element(WcObject *wo, const char *element_name) +{ + GSList *list; + struct wc_element_type *we; + + if (!wo || !element_name) + return NULL; + + if (strlen(element_name) < 1) + return NULL; + + for (list = wo->element_list; list; list = list->next) { + we = (struct wc_element_type *)list->data; + + if (we && g_strcmp0(element_name, we->element_name) == 0) + return we->element; + } + + return NULL; +} + +EXPORT_API int wc_object_foreach_get_elements(WcObject *wo, + wc_object_foreach_get_elements_cb callback, void *user_data) +{ + GSList *list; + struct wc_element_type *we; + + if (!wo || !callback) + return -EINVAL; + + for (list = wo->element_list; list; list = list->next) { + we = (struct wc_element_type *)list->data; + + callback(wo, we->element_name, we->element, user_data); + } + + return 0; +} + +EXPORT_API int wc_object_set_dbus_interface(WcObject *wo, + GDBusInterfaceSkeleton *di) +{ + if (!wo || !di) + return -EINVAL; + + wo->di = di; + return 0; +} + +EXPORT_API GDBusInterfaceSkeleton *wc_object_get_dbus_interface(WcObject *wo) +{ + if (!wo) + return NULL; + + return wo->di; +} + +EXPORT_API int wc_object_export(WcObject *wo, const char *path) +{ + GError *error = NULL; + gboolean ret; + const char *real_path = NULL; + + if (!wo || !path) + return -EINVAL; + + if (!wo->di) + return -EINVAL; + + real_path = dbus_name2path(path); + + if (g_variant_is_object_path(real_path) == FALSE) { + wc_error_add("InvalidName %s", real_path); + g_free((gpointer)real_path); + return -EINVAL; + } + + ret = g_dbus_interface_skeleton_export(wo->di, dbus_get_connection(), + real_path, &error); + if (ret == FALSE) { + if (error) { + wc_error_add("%s", error->message); + g_error_free(error); + } else + wc_error_add("DBusObjectPathFailed"); + + g_free((gpointer)real_path); + return -EIO; + } + + wc_object_set_property(wo, "object-path", real_path); + + g_free((gpointer)real_path); + + return 0; +} + +EXPORT_API int wc_object_unexport(WcObject *wo) +{ + if (!wo) + return -EINVAL; + + if (!wo->di) + return -EINVAL; + + g_dbus_interface_skeleton_unexport(wo->di); + + return 0; +} + +EXPORT_API int wc_object_add_callback(WcObject *wo, const char *event, + WcObjectCallback callback, void *user_data) +{ + GSList *list; + struct wc_callback_type *wcb = NULL; + + if (!wo || !event || !callback) + return -EINVAL; + + if (strlen(event) < 1) + return -EINVAL; + + /* Check already registered */ + list = wo->callbacks; + while (list) { + wcb = (struct wc_callback_type *)list->data; + if (wcb && wcb->callback == callback && g_strcmp0(wcb->event, event) == 0) + return -EALREADY; + + list = list->next; + } + + wcb = g_try_new0(struct wc_callback_type, 1); + if (!wcb) + return -ENOMEM; + + wcb->event = g_strdup(event); + wcb->callback = callback; + wcb->user_data = user_data; + + wo->callbacks = g_slist_append(wo->callbacks, wcb); + + return 0; +} + +EXPORT_API int wc_object_del_callback(WcObject *wo, const char *event, + WcObjectCallback callback) +{ + struct wc_callback_type *wcb = NULL; + GSList *l = NULL; + + if (!wo || !event || !callback || !wo->callbacks) + return -EINVAL; + + if (strlen(event) < 1) + return -EINVAL; + + l = wo->callbacks; + while (l) { + wcb = l->data; + if (!wcb) { + l = l->next; + continue; + } + + if (wcb->callback != callback) { + l = l->next; + continue; + } + + if (g_strcmp0(wcb->event, event) != 0) { + l = l->next; + continue; + } + + l = l->next; + wo->callbacks = g_slist_remove(wo->callbacks, wcb); + g_free((gpointer)wcb->event); + g_free(wcb); + } + + return 0; +} + +EXPORT_API int wc_object_emit_callback(WcObject *wo, const char *event, + const void *event_info) +{ + struct wc_callback_type *wcb = NULL; + GSList *l = NULL; + size_t event_len; + + if (!wo || !event) + return -EINVAL; + + event_len = strlen(event); + if (event_len < 1) + return -EINVAL; + + l = wo->callbacks; + while (l) { + wcb = l->data; + if (!wcb) { + l = l->next; + continue; + } + + if (g_strcmp0(wcb->event, event) != 0) { + l = l->next; + continue; + } + + if (wcb->callback) { + int ret = wcb->callback(wo, event_info, wcb->user_data); + if (ret == FALSE) { + l = l->next; + wo->callbacks = g_slist_remove(wo->callbacks, wcb); + continue; + } + } + + l = l->next; + } + + if (wo->di && event_len > 5 && g_str_has_prefix(event, "dbus-") == TRUE) { + /* + * if event is 'dbus-xxx', emit dbus signal 'xxx' + * but, only support one parameter. + * + * if you want use multiple parameters, + * use g_signal_emit_by_name() directly. + */ + g_signal_emit_by_name(wo->di, event + 5, event_info); + } + + return 0; +} + +static GSList *_set_property_real(WcObject *wo, const char *key, + const char *value, GSList *list) +{ + if (!wo || !key) + return list; + + if (!value) { + g_hash_table_remove(wo->property, key); + + return g_slist_append(list, (gpointer)key); + } + + g_hash_table_replace(wo->property, g_strdup(key), g_strdup(value)); + + return g_slist_append(list, (gpointer)key); +} + +EXPORT_API int wc_object_set_property_full(WcObject *wo, + const char *first_property, ...) +{ + va_list argptr; + GSList *list = NULL; + const char *k; + const char *v; + + if (!wo || !first_property) + return -EINVAL; + + va_start(argptr, first_property); + + k = first_property; + v = va_arg(argptr, char *); + list = _set_property_real(wo, k, v, list); + + while (1) { + k = va_arg(argptr, char *); + if (!k) + break; + + v = va_arg(argptr, char *); + list = _set_property_real(wo, k, v, list); + } + + va_end(argptr); + + if (!list) + return -ENODATA; + + if (g_slist_length(list) > 0) + wc_object_emit_callback(wo, WC_OBJECT_EVENT_PROPERTY_CHANGED, list); + + g_slist_free(list); + + return 0; +} + +EXPORT_API char *wc_object_get_property(WcObject *wo, const char *key) +{ + if (!wo || !key) + return NULL; + + return g_strdup(g_hash_table_lookup(wo->property, key)); +} + +EXPORT_API const char *wc_object_peek_property(WcObject *wo, const char *key) +{ + if (!wo || !key) + return NULL; + + return g_hash_table_lookup(wo->property, key); +} + +EXPORT_API GHashTable *wc_object_peek_property_hash(WcObject *wo) +{ + if (!wo) + return NULL; + + return wo->property; +} + +EXPORT_API void wc_object_set_timeout_connecting(WcObject *wo, + guint timeout, const char *service) +{ + if (!wo) + return; + + wo->timeout_connecting = timeout; + + if (wo->service_connecting != NULL) { + g_free(wo->service_connecting); + wo->service_connecting = NULL; + } + + if (service != NULL) + wo->service_connecting = g_strdup(service); +} + +EXPORT_API guint wc_object_get_timeout_connecting(WcObject *wo) +{ + if (!wo) + return 0; + + return wo->timeout_connecting; +} + +EXPORT_API char *wc_object_get_connecting_service(WcObject *wo) +{ + if (!wo) + return 0; + + return wo->service_connecting; +} + +EXPORT_API void wc_object_set_timeout_disconnecting(WcObject *wo, + guint timeout, const char *service) +{ + if (!wo) + return; + + wo->timeout_disconnecting = timeout; + + if (wo->service_disconnecting != NULL) { + g_free(wo->service_disconnecting); + wo->service_disconnecting = NULL; + } + + if (service != NULL) + wo->service_disconnecting = g_strdup(service); +} + +EXPORT_API guint wc_object_get_timeout_disconnecting(WcObject *wo) +{ + if (!wo) + return 0; + + return wo->timeout_disconnecting; +} + +EXPORT_API char *wc_object_get_disconnecting_service(WcObject *wo) +{ + if (!wo) + return 0; + + return wo->service_disconnecting; +} diff --git a/src/service.c b/src/service.c new file mode 100644 index 0000000..88ea4b6 --- /dev/null +++ b/src/service.c @@ -0,0 +1,56 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include + +#include "log.h" +#include "dbus.h" +#include "object.h" +#include "service.h" + +#include "generated-code.h" + +static WcObject *wo_service; + +int service_init(void) +{ + GDBusInterfaceSkeleton *di; + + wo_service = wc_object_new("service", WC_OBJECT_TYPE_SERVICE); + if (!wo_service) + return -ENOMEM; + + di = G_DBUS_INTERFACE_SKELETON(weconn_service_skeleton_new()); + + /* TODO: extend technology to get bearers' properties */ + wc_object_append_element(wo_service, "service", NULL); + wc_object_set_dbus_interface(wo_service, di); + wc_object_export(wo_service, WECONN_SERVICE_PATH); + + return 0; +} + +void service_cleanup(void) +{ + /* TODO: clean all of services */ + + wc_object_unexport(wo_service); + wc_object_free(wo_service); +} diff --git a/src/technology.c b/src/technology.c new file mode 100644 index 0000000..0d3fed4 --- /dev/null +++ b/src/technology.c @@ -0,0 +1,640 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include + +#include "log.h" +#include "dbus.h" +#include "util.h" +#include "error.h" +#include "driver.h" +#include "events.h" +#include "object.h" +#include "technology.h" + +#include "generated-code.h" + +#define CONNECT_TIMEOUT 120 + +static GSList *technology_list = NULL; + +static void __dbus_builder_add_sv(gpointer key, gpointer value, + gpointer user_data) +{ + GVariantBuilder *b = user_data; + if (!b) + return; + + g_variant_builder_add(b, "{sv}", key, g_variant_new_string(value)); +} + +static gboolean __get_properties(GDBusInterfaceSkeleton *di, + GDBusMethodInvocation *invocation, gpointer user_data) +{ + WcObject *wo = (WcObject *)user_data; + GVariant *result = NULL; + GVariantBuilder *b; + + DBG(""); + + if (!wo) { + wc_error_add("InvalidArguments"); + wc_error_return(invocation); + return TRUE; + } + + b = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + + g_hash_table_foreach(wc_object_peek_property_hash(wo), + __dbus_builder_add_sv, b); + + result = g_variant_builder_end(b); + + g_variant_builder_unref(b); + + g_dbus_method_invocation_return_value(invocation, + g_variant_new("(@a{sv})", result)); + + return TRUE; +} + +static gboolean __get_property(GDBusInterfaceSkeleton *di, + GDBusMethodInvocation *invocation, + const gchar *name, gpointer user_data) +{ + gpointer value; + WcObject *wo = (WcObject *)user_data; + weconn_service_type_e service_type; + weconn_device_type_e device_type; + const char *default_property = NULL; + + DBG("%s", name); + + if (!wo || !name) { + wc_error_add("InvalidArguments"); + wc_error_return(invocation); + return TRUE; + } + + service_type = wc_service_type_string2enum(name); + if (service_type) + default_property = XSTR(W_SERVICE_STATE_DISCONNECTED); + else { + device_type = wc_device_type_string2enum(name); + + if (device_type) + default_property = XSTR(W_DEVICE_STATE_DISABLED); + else { + wc_error_add("InvalidArguments"); + wc_error_return(invocation); + return TRUE; + } + } + + value = g_hash_table_lookup(wc_object_peek_property_hash(wo), name); + if (value) { + g_dbus_method_invocation_return_value(invocation, + g_variant_new("(v)", g_variant_new_string(value))); + } else { + g_dbus_method_invocation_return_value(invocation, + g_variant_new("(v)", g_variant_new_string(default_property))); + } + + return TRUE; +} + +static gboolean __set_property(GDBusInterfaceSkeleton *di, + GDBusMethodInvocation *invocation, + const gchar *name, gpointer user_data) +{ + WcObject *wo = (WcObject *)user_data; + + DBG("%s", name); + + if (!wo || !name) { + wc_error_add("InvalidArguments"); + wc_error_return(invocation); + return TRUE; + } + + /* TODO: set property */ + + g_dbus_method_invocation_return_value(invocation, g_variant_new("()")); + + return TRUE; +} + +static gboolean __scan(GDBusInterfaceSkeleton *di, + GDBusMethodInvocation *invocation, gpointer user_data) +{ + WcObject *wo = (WcObject *)user_data; + + DBG(""); + + if (!wo) { + wc_error_add("InvalidArguments"); + wc_error_return(invocation); + return TRUE; + } + + g_dbus_method_invocation_return_value(invocation, g_variant_new("()")); + + return TRUE; +} + +static const char *__service_type_string2_driver_name(const char *service_type) +{ + if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_HFP), service_type) == 0) + return "bluetooth"; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_SPP), service_type) == 0) + return "esap"; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_PAN), service_type) == 0) + return "pan"; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_GATT), service_type) == 0) + return "bluetooth"; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_CELLULAR), service_type) == 0) + return "cellular"; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_WIFI), service_type) == 0) + return "wifi"; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_WIFI_P2P), service_type) == 0) + return "p2p"; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_WIFI_ADHOC), service_type) == 0) + return "adhoc"; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_ETHERNET), service_type) == 0) + return "ethernet"; + + return NULL; +} + +static void __connect_result_emit_signal(WcObject *wo, + const gchar *service, const char *signal_name, int result) +{ + GDBusConnection *connection = NULL; + GError *error = NULL; + GVariant *variant = NULL; + + if (!wo) + return; + + if (!service) { + ERR("service is NULL"); + return; + } + + connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (!connection) { + ERR("Failed to get system bus[%s]", error->message); + g_error_free(error); + return; + } + + variant = g_variant_new("(si)", service, result); + + g_dbus_connection_emit_signal(connection, NULL, + WECONN_PATH_DBUS, WECONN_TECHNOLOGY_INTERFACE, + signal_name, variant, NULL); + + g_object_unref(connection); +} + +static gboolean __connect_timeout_cb(gpointer user_data) +{ + char *service = NULL; + char *state = NULL; + WcObject *wo = (WcObject *)user_data; + if (!wo) + return FALSE; + + service = wc_object_get_connecting_service(wo); + if (!service) + return FALSE; + + state = wc_object_get_property(wo, service); + if (state != NULL) { + if (g_strcmp0(state, XSTR(W_SERVICE_STATE_DISCONNECTED)) != 0) + wc_object_set_property(wo, service, + XSTR(W_SERVICE_STATE_DISCONNECTED)); + + g_free(state); + } + + __connect_result_emit_signal(wo, service, + WECONN_TECHNOLOGY_SIGNAL_CONNECTION_RESULT, -ETIMEDOUT); + + wc_object_set_timeout_connecting(wo, 0, NULL); + + DBG("connect_timeout callback"); + return FALSE; +} + +static gboolean __disconnect_timeout_cb(gpointer user_data) +{ + char *service = NULL; + char *state = NULL; + WcObject *wo = (WcObject *)user_data; + if (!wo) + return FALSE; + + service = wc_object_get_disconnecting_service(wo); + if (!service) + return FALSE; + + state = wc_object_get_property(wo, service); + if (state != NULL) { + if (g_strcmp0(state, XSTR(W_SERVICE_STATE_CONNECTED)) != 0) + wc_object_set_property(wo, service, + XSTR(W_SERVICE_STATE_CONNECTED)); + + g_free(state); + } + + __connect_result_emit_signal(wo, service, + WECONN_TECHNOLOGY_SIGNAL_DISCONNECTION_RESULT, -ETIMEDOUT); + + wc_object_set_timeout_disconnecting(wo, 0, NULL); + + DBG("disconnect_timeout callback"); + return FALSE; +} + +static void __connect_timer_clear(WcObject *wo) +{ + guint timer = 0; + + if (!wo) + return; + + timer = wc_object_get_timeout_connecting(wo); + if (timer > 0) { + g_source_remove(timer); + wc_object_set_timeout_connecting(wo, 0, NULL); + } + + DBG("connect_timer cleared"); +} + +static void __disconnect_timer_clear(WcObject *wo) +{ + guint timer = 0; + + if (!wo) + return; + + timer = wc_object_get_timeout_disconnecting(wo); + if (timer > 0) { + g_source_remove(timer); + wc_object_set_timeout_disconnecting(wo, 0, NULL); + } + + DBG("disconnect_timer cleared"); +} + +static gboolean __connect(GDBusInterfaceSkeleton *di, + GDBusMethodInvocation *invocation, + const gchar *service, gpointer user_data) +{ + int err; + guint timer; + char *service_state; + WcObject *wo = (WcObject *)user_data; + + DBG(""); + + /* TODO: + * 1. If host CM, at first configure PAN, BT tethering on, + * and send PAN_CONNECTION command to wearable CM. + * 2. If wearable CM, send PAN_CONFIGURATION command to host CM. + */ + + // __request_pan_configuration(); + + /* TODO: + * After PAN_CONNECTION request from host CM, do following procedure. + */ + + if (!wo || wc_object_get_type(wo) != WC_OBJECT_TYPE_BEARER_DRIVER) { + wc_error_add("InvalidArguments"); + wc_error_return(invocation); + return TRUE; + } + + service_state = wc_object_get_property(wo, service); + if (service_state != NULL) { + if (g_strcmp0(service_state, XSTR(W_SERVICE_STATE_CONNECTING)) == 0) { + wc_error_add("InProgress"); + wc_error_return(invocation); + g_free(service_state); + return TRUE; + } + g_free(service_state); + } + + err = wc_bearer_driver_connect(wo, NULL, + __service_type_string2_driver_name(service)); + if (err == -EISCONN) { + wc_error_add("AlreadyConnected"); + wc_error_return(invocation); + return TRUE; + } else if (err == -EIO) { + wc_error_add("LostConnection"); + wc_error_return(invocation); + return TRUE; + } else if (err < 0) { + /* TODO: specific error code should be defined */ + wc_error_add("ConnectionFailed"); + wc_error_return(invocation); + return TRUE; + } + + if (wc_object_get_timeout_connecting(wo) == 0) { + wc_object_set_property(wo, service, XSTR(W_SERVICE_STATE_CONNECTING)); + timer = g_timeout_add_seconds(CONNECT_TIMEOUT, + __connect_timeout_cb, wo); + wc_object_set_timeout_connecting(wo, timer, service); + } + + g_dbus_method_invocation_return_value(invocation, g_variant_new("()")); + return TRUE; +} + +static gboolean __disconnect(GDBusInterfaceSkeleton *di, + GDBusMethodInvocation *invocation, + const gchar *service, gpointer user_data) +{ + int err; + guint timer; + char *service_state; + WcObject *wo = (WcObject *)user_data; + + DBG(""); + + if (!wo || wc_object_get_type(wo) != WC_OBJECT_TYPE_BEARER_DRIVER) { + wc_error_add("InvalidArguments"); + wc_error_return(invocation); + return TRUE; + } + + service_state = wc_object_get_property(wo, service); + if (service_state != NULL) { + if (g_strcmp0(service_state, XSTR(W_SERVICE_STATE_DISCONNECTING)) == 0) { + wc_error_add("InProgress"); + wc_error_return(invocation); + g_free(service_state); + return TRUE; + } + g_free(service_state); + } + + err = wc_bearer_driver_disconnect(wo, + __service_type_string2_driver_name(service)); + if (err == -ENOTCONN) { + wc_error_add("NotConnected"); + wc_error_return(invocation); + + return TRUE; + } else if (err < 0) { + /* TODO: specific error code should be defined */ + wc_error_add("DisconnectionFailed"); + wc_error_return(invocation); + return TRUE; + } + + if (wc_object_get_timeout_disconnecting(wo) == 0) { + wc_object_set_property(wo, service, XSTR(W_SERVICE_STATE_DISCONNECTING)); + timer = g_timeout_add_seconds(CONNECT_TIMEOUT, + __disconnect_timeout_cb, wo); + wc_object_set_timeout_disconnecting(wo, timer, service); + } + + g_dbus_method_invocation_return_value(invocation, g_variant_new("()")); + return TRUE; +} + +static gboolean __wc_technology_bt_property_changed_cb(WcObject *wo, + const void *event_info, void *user_data) +{ + const char *key = NULL; + char *state = NULL; + char *service = NULL; + char *bcmservice_state = NULL; + GSList *list_properies = NULL; + GSList *list = NULL; + + list_properies = (GSList *)event_info; + if (!list_properies) + return TRUE; + + if (g_slist_length(list_properies) < 1) { + ERR("No property"); + return TRUE; + } + + for (list = list_properies; list; list = list->next) { + key = (const char *)list->data; + DBG("property : %s", key); + if (g_strcmp0(key, XSTR(W_SERVICE_TYPE_BT_PAN)) == 0) { + state = wc_object_get_property(wo, key); + if (!state) + continue; + + DBG("state : %s", state); + + if (g_strcmp0(state, XSTR(W_SERVICE_STATE_CONNECTED)) == 0) { + + service = wc_object_get_connecting_service(wo); + if (g_strcmp0(service, key) == 0) { + __connect_result_emit_signal(wo, key, + WECONN_TECHNOLOGY_SIGNAL_CONNECTION_RESULT, 0); + __connect_timer_clear(wo); + } + + service = wc_object_get_disconnecting_service(wo); + if (g_strcmp0(service, key) == 0) { + __connect_result_emit_signal(wo, key, + WECONN_TECHNOLOGY_SIGNAL_DISCONNECTION_RESULT, -ECANCELED); + __disconnect_timer_clear(wo); + return TRUE; + } + + __connect_result_emit_signal(wo, key, + WECONN_TECHNOLOGY_SIGNAL_SERVICE_STATE_CHANGED, + W_SERVICE_STATE_CONNECTED); + + } else if (g_strcmp0(state, XSTR(W_SERVICE_STATE_DISCONNECTED)) == 0) { + + service = wc_object_get_connecting_service(wo); + if (g_strcmp0(service, key) == 0) { + bcmservice_state = wc_object_get_property(wo, + WC_OBJECT_EVENT_SAP_BCMSERVICE_STATUS); + if (g_strcmp0(bcmservice_state, XSTR(W_SERVICE_STATE_CONNECTED)) == 0) + __connect_result_emit_signal(wo, key, + WECONN_TECHNOLOGY_SIGNAL_CONNECTION_RESULT, -ECANCELED); + else + __connect_result_emit_signal(wo, key, + WECONN_TECHNOLOGY_SIGNAL_DISCONNECTION_RESULT, -EIO); + + __connect_result_emit_signal(wo, key, + WECONN_TECHNOLOGY_SIGNAL_CONNECTION_RESULT, -ECANCELED); + __connect_timer_clear(wo); + return TRUE; + } + + service = wc_object_get_disconnecting_service(wo); + if (g_strcmp0(service, key) == 0) { + bcmservice_state = wc_object_get_property(wo, + WC_OBJECT_EVENT_SAP_BCMSERVICE_STATUS); + if (g_strcmp0(bcmservice_state, XSTR(W_SERVICE_STATE_CONNECTED)) == 0) + __connect_result_emit_signal(wo, key, + WECONN_TECHNOLOGY_SIGNAL_DISCONNECTION_RESULT, 0); + else + __connect_result_emit_signal(wo, key, + WECONN_TECHNOLOGY_SIGNAL_DISCONNECTION_RESULT, -ENOTCONN); + __disconnect_timer_clear(wo); + } + + __connect_result_emit_signal(wo, key, + WECONN_TECHNOLOGY_SIGNAL_SERVICE_STATE_CHANGED, + W_SERVICE_STATE_DISCONNECTED); + } + + g_free(state); + } + } + + return TRUE; +} + +static void __wc_technology_register_service_state_changed(WcObject *wo) +{ + const char *technology; + + if (!wo) + return; + + technology = wc_object_peek_name(wo); + if (g_strcmp0(technology, WC_TECHNOLOGY_BLUETOOTH) == 0) { + wc_object_add_callback(wo, WC_OBJECT_EVENT_PROPERTY_CHANGED, + __wc_technology_bt_property_changed_cb, NULL); + } else if (g_strcmp0(technology, WC_TECHNOLOGY_CELLULAR) == 0) { + + } +} + +static gboolean __wc_technology_bearer_enabled_cb(WcObject *wo, + const void *event_info, void *user_data) +{ + const char *path; + GDBusInterfaceSkeleton *di; + + DBG("technology %s added", wc_object_peek_name(wo)); + + if (!wo) + return TRUE; + + di = G_DBUS_INTERFACE_SKELETON(weconn_technology_skeleton_new()); + + g_signal_connect(di, "handle-get-properties", + G_CALLBACK(__get_properties), wo); + g_signal_connect(di, "handle-get-property", + G_CALLBACK(__get_property), wo); + g_signal_connect(di, "handle-set-property", + G_CALLBACK(__set_property), wo); + g_signal_connect(di, "handle-get-properties", + G_CALLBACK(__scan), wo); + g_signal_connect(di, "handle-connect", + G_CALLBACK(__connect), wo); + g_signal_connect(di, "handle-disconnect", + G_CALLBACK(__disconnect), wo); + + wc_object_set_dbus_interface(wo, di); + + technology_list = g_slist_prepend(technology_list, wo); + + path = g_strdup_printf("%s/%s", + WECONN_TECHNOLOGY_PATH, wc_object_peek_name(wo)); + + wc_object_export(wo, path); + g_free((gpointer)path); + + __wc_technology_register_service_state_changed(wo); + + return TRUE; +} + +static gboolean __wc_technology_bearer_disabled_cb(WcObject *wo, + const void *event_info, void *user_data) +{ + if (!wo) + return TRUE; + + wc_object_unexport(wo); + + /* TODO: g_signal_connect_closure for each c_handler */ + + technology_list = g_slist_remove(technology_list, wo); + + return TRUE; +} + +int wc_technology_add_bearer(WcObject *wo) +{ + int err; + + err = wc_object_add_callback(wo, WC_OBJECT_EVENT_BEARER_ENABLED, + __wc_technology_bearer_enabled_cb, NULL); + if (err < 0 && err != -EALREADY) + return err; + + err = wc_object_add_callback(wo, WC_OBJECT_EVENT_BEARER_DISABLED, + __wc_technology_bearer_disabled_cb, NULL); + + return err; +} + +WcObject *wc_technology_get_bearer(const char *technology) +{ + GSList *list = technology_list; + WcObject *wo; + + if (!technology) + return NULL; + + while (list) { + wo = (WcObject *)list->data; + if (wo && g_strcmp0(technology, wc_object_peek_name(wo)) == 0) + return wo; + + list = list->next; + } + + return NULL; +} + +int technology_init(void) +{ + /* TODO: initialize default technologies */ + + return 0; +} + +void technology_cleanup(void) +{ + /* TODO: clean all of technologies */ +} diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..559816a --- /dev/null +++ b/src/util.c @@ -0,0 +1,236 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include "log.h" +#include "util.h" + +#define CONNMAN_PATH "/net/connman" +#define CONNMAN_CELLULAR_SERVICE_PROFILE_PREFIX \ + CONNMAN_PATH "/service/cellular_" +#define CONNMAN_WIFI_SERVICE_PROFILE_PREFIX \ + CONNMAN_PATH "/service/wifi_" +#define CONNMAN_ETHERNET_SERVICE_PROFILE_PREFIX \ + CONNMAN_PATH "/service/ethernet_" +#define CONNMAN_BLUETOOTH_SERVICE_PROFILE_PREFIX \ + CONNMAN_PATH "/service/bluetooth_" + +weconn_service_type_e wc_service_type_string2enum(const char *service_type) +{ + if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_HFP), service_type) == 0) + return W_SERVICE_TYPE_BT_HFP; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_SPP), service_type) == 0) + return W_SERVICE_TYPE_BT_SPP; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_PAN), service_type) == 0) + return W_SERVICE_TYPE_BT_PAN; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_BT_GATT), service_type) == 0) + return W_SERVICE_TYPE_BT_GATT; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_CELLULAR), service_type) == 0) + return W_SERVICE_TYPE_CELLULAR; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_WIFI), service_type) == 0) + return W_SERVICE_TYPE_WIFI; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_WIFI_P2P), service_type) == 0) + return W_SERVICE_TYPE_WIFI_P2P; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_WIFI_ADHOC), service_type) == 0) + return W_SERVICE_TYPE_WIFI_ADHOC; + else if (g_strcmp0(XSTR(W_SERVICE_TYPE_ETHERNET), service_type) == 0) + return W_SERVICE_TYPE_ETHERNET; + + return 0x00; +} + +const char *wc_service_type_enum2string(weconn_service_type_e service_type) +{ + switch (service_type) { + case W_SERVICE_TYPE_BT_HFP: + return XSTR(W_SERVICE_TYPE_BT_HFP); + case W_SERVICE_TYPE_BT_SPP: + return XSTR(W_SERVICE_TYPE_BT_SPP); + case W_SERVICE_TYPE_BT_PAN: + return XSTR(W_SERVICE_TYPE_BT_PAN); + case W_SERVICE_TYPE_BT_GATT: + return XSTR(W_SERVICE_TYPE_BT_GATT); + case W_SERVICE_TYPE_CELLULAR: + return XSTR(W_SERVICE_TYPE_CELLULAR); + case W_SERVICE_TYPE_WIFI: + return XSTR(W_SERVICE_TYPE_WIFI); + case W_SERVICE_TYPE_WIFI_P2P: + return XSTR(W_SERVICE_TYPE_WIFI_P2P); + case W_SERVICE_TYPE_WIFI_ADHOC: + return XSTR(W_SERVICE_TYPE_WIFI_ADHOC); + case W_SERVICE_TYPE_ETHERNET: + return XSTR(W_SERVICE_TYPE_ETHERNET); + default: + break; + } + + return NULL; +} + +weconn_service_state_e wc_service_state_string2enum(const char *service_state) +{ + if (g_strcmp0(XSTR(W_SERVICE_STATE_DISCONNECTED), service_state) == 0) + return W_SERVICE_STATE_DISCONNECTED; + else if (g_strcmp0(XSTR(W_SERVICE_STATE_CONNECTING), service_state) == 0) + return W_SERVICE_STATE_CONNECTING; + else if (g_strcmp0(XSTR(W_SERVICE_STATE_CONNECTED), service_state) == 0) + return W_SERVICE_STATE_CONNECTED; + else if (g_strcmp0(XSTR(W_SERVICE_STATE_DISCONNECTING), service_state) == 0) + return W_SERVICE_STATE_DISCONNECTING; + + return 0x00; +} + +const char *wc_service_state_enum2string(weconn_service_state_e service_state) +{ + switch (service_state) { + case W_SERVICE_STATE_DISCONNECTED: + return XSTR(W_SERVICE_STATE_DISCONNECTED); + case W_SERVICE_STATE_CONNECTING: + return XSTR(W_SERVICE_STATE_CONNECTING); + case W_SERVICE_STATE_CONNECTED: + return XSTR(W_SERVICE_STATE_CONNECTED); + case W_SERVICE_STATE_DISCONNECTING: + return XSTR(W_SERVICE_STATE_DISCONNECTING); + default: + break; + } + + return NULL; +} + +weconn_device_type_e wc_device_type_string2enum(const char *device_type) +{ + if (g_strcmp0(XSTR(W_DEVICE_TYPE_BT), device_type) == 0) + return W_DEVICE_TYPE_BT; + else if (g_strcmp0(XSTR(W_DEVICE_TYPE_CELLULAR), device_type) == 0) + return W_DEVICE_TYPE_CELLULAR; + else if (g_strcmp0(XSTR(W_DEVICE_TYPE_WIFI), device_type) == 0) + return W_DEVICE_TYPE_WIFI; + else if (g_strcmp0(XSTR(W_DEVICE_TYPE_WIFI_P2P), device_type) == 0) + return W_DEVICE_TYPE_WIFI_P2P; + else if (g_strcmp0(XSTR(W_DEVICE_TYPE_WIFI_ADHOC), device_type) == 0) + return W_DEVICE_TYPE_WIFI_ADHOC; + else if (g_strcmp0(XSTR(W_DEVICE_TYPE_ETHERNET), device_type) == 0) + return W_DEVICE_TYPE_ETHERNET; + + return 0x00; +} + +const char *wc_device_type_enum2string(weconn_device_type_e device_type) +{ + switch (device_type) { + case W_DEVICE_TYPE_BT: + return XSTR(W_DEVICE_TYPE_BT); + case W_DEVICE_TYPE_CELLULAR: + return XSTR(W_DEVICE_TYPE_CELLULAR); + case W_DEVICE_TYPE_WIFI: + return XSTR(W_DEVICE_TYPE_WIFI); + case W_DEVICE_TYPE_WIFI_P2P: + return XSTR(W_DEVICE_TYPE_WIFI_P2P); + case W_DEVICE_TYPE_WIFI_ADHOC: + return XSTR(W_DEVICE_TYPE_WIFI_ADHOC); + case W_DEVICE_TYPE_ETHERNET: + return XSTR(W_DEVICE_TYPE_ETHERNET); + default: + break; + } + + return NULL; +} + +weconn_device_state_e wc_device_state_string2enum(const char *device_state) +{ + if (g_strcmp0(XSTR(W_DEVICE_STATE_DISABLED), device_state) == 0) + return W_DEVICE_STATE_DISABLED; + else if (g_strcmp0(XSTR(W_DEVICE_STATE_ENABLED), device_state) == 0) + return W_DEVICE_STATE_ENABLED; + + return 0x00; +} + +const char *wc_device_state_enum2string(weconn_device_state_e device_state) +{ + switch (device_state) { + case W_DEVICE_STATE_DISABLED: + return XSTR(W_DEVICE_STATE_DISABLED); + case W_DEVICE_STATE_ENABLED: + return XSTR(W_DEVICE_STATE_ENABLED); + default: + break; + } + + return NULL; +} + +weconn_device_type_e wc_device_get_type_from_path(const char *path) +{ + if (g_str_has_prefix(path, + CONNMAN_WIFI_SERVICE_PROFILE_PREFIX) == TRUE) + return W_DEVICE_TYPE_WIFI; + else if (g_str_has_prefix(path, + CONNMAN_CELLULAR_SERVICE_PROFILE_PREFIX) == TRUE) + return W_DEVICE_TYPE_CELLULAR; + else if (g_str_has_prefix(path, + CONNMAN_ETHERNET_SERVICE_PROFILE_PREFIX) == TRUE) + return W_DEVICE_TYPE_ETHERNET; + else if (g_str_has_prefix(path, + CONNMAN_BLUETOOTH_SERVICE_PROFILE_PREFIX) == TRUE) + return W_DEVICE_TYPE_BT; + + return 0x00; +} + +int wc_launch_popup(weconn_popup_type_e type, service_reply_cb callback, + void *user_data) +{ + int ret; + service_h service = NULL; + + ret = service_create(&service); + if (ret != SERVICE_ERROR_NONE) { + DBG("failed to create service [%d]", ret); + return -1; + } + + switch (type) { + case WC_POPUP_TYPE_PAN_CONNECT: + service_add_extra_data(service, "_WCPOPUP_TYPE_", "pan_connect"); + break; + case WC_POPUP_TYPE_PAN_DISCONNECT: + service_add_extra_data(service, "_WCPOPUP_TYPE_", "pan_disconnect"); + break; + default: + ERR("Not support popup type [%d]", type); + service_destroy(service); + return -1; + } + + service_set_package(service, "net.wc-popup"); + ret = service_send_launch_request(service, callback, user_data); + if (ret != SERVICE_ERROR_NONE) { + DBG("failed service launch request [%d]", ret); + return -1; + } + + service_destroy(service); + + return 0; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..52dee07 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,22 @@ +SET(weconn_test "${PROJECT_NAME}-test") + +SET(dependents "capi-base-common gio-2.0 glib-2.0") +SET(pc_dependents "capi-base-common") + +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/client/include/) + +INCLUDE(FindPkgConfig) +pkg_check_modules(${weconn_test} REQUIRED ${dependents}) +FOREACH(flag ${${weconn_test}_CFLAGS}) + SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) + +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} -Wall") + +aux_source_directory(. sources) +FOREACH(src ${sources}) + GET_FILENAME_COMPONENT(src_name ${src} NAME_WE) + MESSAGE("${src_name}") + ADD_EXECUTABLE(${src_name} ${src}) + TARGET_LINK_LIBRARIES(${src_name} weconn ${${weconn_test}_LDFLAGS}) +ENDFOREACH() diff --git a/test/w_connection_manager_test.c b/test/w_connection_manager_test.c new file mode 100644 index 0000000..700d64a --- /dev/null +++ b/test/w_connection_manager_test.c @@ -0,0 +1,414 @@ +/* + * Wearable device Connection Controller Framework + * + * Copyright (c) 2013 - 2014 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +gboolean test_thread(GIOChannel *source, GIOCondition condition, gpointer data); + +static weconn_h connection = NULL; + +static int __get_service_type(void) +{ + int input, rv; + + printf("Input service type:\n"); + printf("1: W_SERVICE_TYPE_BT_HFP\n"); + printf("2: W_SERVICE_TYPE_BT_SPP\n"); + printf("3: W_SERVICE_TYPE_BT_PAN\n"); + printf("4: W_SERVICE_TYPE_BT_GATT\n"); + printf("5: W_SERVICE_TYPE_CELLULAR\n"); + printf("6: W_SERVICE_TYPE_WIFI\n"); + printf("7: W_SERVICE_TYPE_WIFI_P2P\n"); + printf("8: W_SERVICE_TYPE_WIFI_ADHOC\n"); + printf("9: W_SERVICE_TYPE_ETHERNET\n"); + printf("11: W_SERVICE_TYPE_HOST_TO_WEARABLE_CONNECTIVITY\n"); + printf("12: W_SERVICE_TYPE_INTERNET_CONNECTIVITY\n"); + + rv = scanf("%d", &input); + if (rv < 0) + return -EINVAL; + + /* convert the real enum of service type */ + printf("service type %02X\n", input); + if (input < 10) { + input = input + 0x10; + if (W_SERVICE_TYPE_BT_HFP <= input && input <= W_SERVICE_TYPE_ETHERNET) + return input; + } else { + input = input - 10; + if (W_SERVICE_TYPE_HOST_TO_WEARABLE_CONNECTIVITY == input || + W_SERVICE_TYPE_INTERNET_CONNECTIVITY == input) + return input; + } + + return -EINVAL; +} + +static void __print_service_state(weconn_service_state_e state) +{ + switch (state) { + case W_SERVICE_STATE_DISCONNECTED: + printf("Disconnected\n"); + break; + case W_SERVICE_STATE_CONNECTING: + printf("Connecting\n"); + break; + case W_SERVICE_STATE_CONNECTED: + printf("Connected\n"); + break; + case W_SERVICE_STATE_DISCONNECTING: + printf("Disconnected\n"); + break; + default: + printf("Unknown\n"); + } +} + +static int __get_device_type(void) +{ + int input, rv; + + printf("Input device type:\n"); + printf("1: W_DEVICE_TYPE_BT\n"); + printf("2: W_DEVICE_TYPE_CELLULAR\n"); + printf("3: W_DEVICE_TYPE_WIFI\n"); + printf("4: W_DEVICE_TYPE_WIFI_P2P\n"); + printf("5: W_DEVICE_TYPE_WIFI_ADHOC\n"); + printf("6: W_DEVICE_TYPE_ETHERNET\n"); + + rv = scanf("%d", &input); + if (rv < 0) + return -EINVAL; + + printf("device type %d\n", input); + if (W_DEVICE_TYPE_BT <= input && input <= W_DEVICE_TYPE_ETHERNET) + return input; + + return -EINVAL; +} + +static void __print_device_state(weconn_service_state_e state) +{ + switch (state) { + case W_DEVICE_STATE_DISABLED: + printf("Disabled\n"); + break; + case W_DEVICE_STATE_ENABLED: + printf("Enabled\n"); + break; + default: + printf("Unknown\n"); + } +} + +static int test_register_client(void) +{ + int err = weconn_create(&connection); + + if (err < 0) { + printf("Client registration failed [%d]\n", err); + return -EIO; + } + + printf("Client registered successfully\n"); + + return 0; +} + +static int test_deregister_client(void) +{ + int err = 0; + + if (connection != NULL) + err = weconn_destroy(connection); + else { + printf("Cannot de-register: handle is NULL\n"); + err = -EINVAL; + } + + if (err < 0) { + printf("Client de-registration fail [%d]\n", err); + return err; + } + + connection = NULL; + + printf("Client de-registration success\n"); + + return 0; +} + +static int test_get_service_state(void) +{ + int type, err; + weconn_service_state_e state; + + type = __get_service_type(); + + err = weconn_get_service_state(connection, type, &state); + if (err < 0) { + printf("Failed to get service state [%d]", err); + return err; + } + + __print_service_state(state); + return 0; +} + +static int test_get_device_state(void) +{ + int type, err; + weconn_device_state_e state; + + type = __get_device_type(); + + err = weconn_get_device_state(connection, type, &state); + if (err < 0) { + printf("Failed to get device state [%d]", err); + return err; + } + + __print_device_state(state); + return 0; +} + +static void __test_connect_service_callback(int result, void *user_data) +{ + if (result == 0) + printf("Succeed to connect\n"); + else + printf("Failed to connect [%d]\n", result); +} + +static void __test_disconnect_service_callback(int result, void *user_data) +{ + if (result == 0) + printf("Succeed to disconnect\n"); + else + printf("Failed to disconnect [%d]\n", result); +} + +static int test_connect_service(void) +{ + int err = 0; + weconn_service_type_e type; + + type = __get_service_type(); + + err = weconn_connect_service(connection, type, + __test_connect_service_callback, NULL); + if (err < 0) { + printf("Failed to connect service [%d]", err); + return err; + } + + return err; +} + +static int test_disconnect_service(void) +{ + int err = 0; + weconn_service_type_e type; + + type = __get_service_type(); + + err = weconn_disconnect_service(connection, type, + __test_disconnect_service_callback, NULL); + if (err < 0) { + printf("Failed to get device state [%d]", err); + return err; + } + + return err; +} + +static void _test_wearable_service_state_changed_cb( + weconn_service_state_e state, void *user_data) +{ + printf("wearable service state changed = %d\n", state); +} + +static void _test_internet_service_state_changed_cb( + weconn_service_state_e state, void *user_data) +{ + printf("internet service state changed = %d\n", state); +} + +static int test_register_service_connection_cb(void) +{ + int err = 0; + weconn_service_type_e type; + + type = __get_service_type(); + + switch (type) { + case W_SERVICE_TYPE_HOST_TO_WEARABLE_CONNECTIVITY: + err = weconn_set_service_state_change_cb(connection, + _test_wearable_service_state_changed_cb, type, NULL); + if (err < 0) { + printf("Register_service_connectionn failed [%d]\n", err); + return -EIO; + } + break; + case W_SERVICE_TYPE_INTERNET_CONNECTIVITY : + err = weconn_set_service_state_change_cb(connection, + _test_internet_service_state_changed_cb, type, NULL); + if (err < 0) { + printf("Register_service_connectionn failed [%d]\n", err); + return -EIO; + } + break; + case W_SERVICE_TYPE_BT_HFP: + case W_SERVICE_TYPE_BT_SPP: + case W_SERVICE_TYPE_BT_PAN: + case W_SERVICE_TYPE_BT_GATT: + case W_SERVICE_TYPE_CELLULAR: + case W_SERVICE_TYPE_WIFI: + case W_SERVICE_TYPE_WIFI_P2P: + case W_SERVICE_TYPE_WIFI_ADHOC: + case W_SERVICE_TYPE_ETHERNET: + printf("Not support service type [%d]\n", type); + return -EINVAL; + } + + printf("Register_service_connectionn successfully\n"); + + return 0; +} + +static int test_unregister_service_connection_cb(void) +{ + int err = 0; + weconn_service_type_e type; + + type = __get_service_type(); + + if (type > W_SERVICE_TYPE_INTERNET_CONNECTIVITY) + printf("Not support service type [%d]\n", type); + + err = weconn_unset_service_state_change_cb(connection, type); + if (err < 0) { + printf("Unregister_service_connectionn failed [%d]\n", err); + return -EIO; + } + + printf("Unregister_service_connectionn successfully\n"); + + return 0; +} + +static void __print_menu(void) +{ + printf("\nWearable Connection API Test App\n\n"); + printf("Options..\n"); + printf("1 - Create Handle\n"); + printf("2 - Destroy Handle(unset callbacks automatically)\n"); + printf("3 - Get wearable service state\n"); + printf("4 - Get wearable device state\n"); + printf("5 - Connect wearable service\n"); + printf("6 - Disconnect wearable service\n"); + printf("7 - Register wearable service connection callback\n"); + printf("8 - Unregister wearable service connection callback\n"); + printf("0 - Exit \n"); + printf("ENTER - Show options menu.......\n"); +} + +int main(int argc, char **argv) +{ + GMainLoop *mainloop; + GIOChannel *channel; + mainloop = g_main_loop_new (NULL, FALSE); + + channel = g_io_channel_unix_new(0); + g_io_add_watch(channel, (G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL), + test_thread,NULL ); + + __print_menu(); + + g_main_loop_run (mainloop); + + return 0; +} + +gboolean test_thread(GIOChannel *source, GIOCondition condition, gpointer data) +{ + int rv = 0; + char cmds[100]; + char cmd; + + memset(cmds, '\0', 100); + rv = read(0, cmds, 100); + + cmd = *cmds; + + if (cmd == '\n' || cmd == '\r'){ + __print_menu(); + + return TRUE; + } + + switch (cmd) { + case '0': + if (connection != NULL) + test_deregister_client(); + + exit(1); + case '1': + rv = test_register_client(); + break; + case '2': + rv = test_deregister_client(); + break; + case '3': + rv = test_get_service_state(); + break; + case '4': + rv = test_get_device_state(); + break; + case '5': + rv = test_connect_service(); + break; + case '6': + rv = test_disconnect_service(); + break; + case '7': + rv = test_register_service_connection_cb(); + break; + case '8': + rv = test_unregister_service_connection_cb(); + break; + default: + break; + } + + if (rv == 0) + printf("Operation succeeded!\n\n"); + else + printf("Operation failed!\n\n"); + + return TRUE; +} diff --git a/weconn.manifest b/weconn.manifest new file mode 100644 index 0000000..09c7da4 --- /dev/null +++ b/weconn.manifest @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 2.7.4