From 9c00d85fa44c81bf7717c2ce2a755feab1d7101e Mon Sep 17 00:00:00 2001 From: Adam Malinowski Date: Wed, 10 Dec 2014 10:17:06 +0100 Subject: [PATCH] Add UI backend based on tizen notifications Change-Id: Iec59517b8d5a55f54ca831394a12793c2ef2ae4c --- packaging/askuser.spec | 1 + src/agent/CMakeLists.txt | 2 + src/agent/main/Agent.cpp | 5 +- src/agent/ui/AskUIInterface.h | 2 +- src/agent/ui/AskUINotificationBackend.cpp | 246 ++++++++++++++++++++++++++++++ src/agent/ui/AskUINotificationBackend.h | 65 ++++++++ 6 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 src/agent/ui/AskUINotificationBackend.cpp create mode 100644 src/agent/ui/AskUINotificationBackend.h diff --git a/packaging/askuser.spec b/packaging/askuser.spec index 2653053..b044e24 100644 --- a/packaging/askuser.spec +++ b/packaging/askuser.spec @@ -16,6 +16,7 @@ BuildRequires: pkgconfig(cynara-agent) BuildRequires: pkgconfig(cynara-plugin) BuildRequires: pkgconfig(libsystemd-daemon) BuildRequires: pkgconfig(libsystemd-journal) +BuildRequires: pkgconfig(notification) BuildRequires: zip %{?systemd_requires} diff --git a/src/agent/CMakeLists.txt b/src/agent/CMakeLists.txt index 0f57e63..a9b5c13 100644 --- a/src/agent/CMakeLists.txt +++ b/src/agent/CMakeLists.txt @@ -20,6 +20,7 @@ PKG_CHECK_MODULES(AGENT_DEP REQUIRED cynara-agent cynara-plugin + notification ) SET(ASKUSER_AGENT_PATH ${ASKUSER_PATH}/agent) @@ -28,6 +29,7 @@ SET(ASKUSER_SOURCES ${ASKUSER_AGENT_PATH}/main/Agent.cpp ${ASKUSER_AGENT_PATH}/main/CynaraTalker.cpp ${ASKUSER_AGENT_PATH}/main/main.cpp + ${ASKUSER_AGENT_PATH}/ui/AskUINotificationBackend.cpp ) INCLUDE_DIRECTORIES( diff --git a/src/agent/main/Agent.cpp b/src/agent/main/Agent.cpp index 89aaa04..e8aaba9 100644 --- a/src/agent/main/Agent.cpp +++ b/src/agent/main/Agent.cpp @@ -31,6 +31,8 @@ #include #include +#include + #include "Agent.h" namespace AskUser { @@ -182,7 +184,7 @@ void Agent::processUIResponse(const Response &response) { bool Agent::startUIForRequest(Request *request) { auto data = Translator::Agent::dataToRequest(request->data()); - AskUIInterfacePtr ui; // TODO: create pointer to backend + AskUIInterfacePtr ui(new AskUINotificationBackend()); auto handler = [&](RequestId requestId, UIResponseType resultType) -> void { UIResponseHandler(requestId, resultType); @@ -191,6 +193,7 @@ bool Agent::startUIForRequest(Request *request) { if (ret) { m_UIs.insert(std::make_pair(request->id(), std::move(ui))); } + return ret; } diff --git a/src/agent/ui/AskUIInterface.h b/src/agent/ui/AskUIInterface.h index b78c35a..e00ba53 100644 --- a/src/agent/ui/AskUIInterface.h +++ b/src/agent/ui/AskUIInterface.h @@ -49,7 +49,7 @@ public: const std::string &privilege, RequestId requestId, UIResponseCallback) = 0; virtual bool setOutdated() = 0; virtual bool dismiss() = 0; - virtual bool isDismissing() = 0; + virtual bool isDismissing() const = 0; }; typedef std::unique_ptr AskUIInterfacePtr; diff --git a/src/agent/ui/AskUINotificationBackend.cpp b/src/agent/ui/AskUINotificationBackend.cpp new file mode 100644 index 0000000..746081d --- /dev/null +++ b/src/agent/ui/AskUINotificationBackend.cpp @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2015 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 + */ +/** + * @file AskUINotificationBackend.cpp + * @author Adam Malinowski + * @brief This file implements class for ask user window + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "AskUINotificationBackend.h" + +namespace { + +const char *errorToString(notification_error_e error) { + if (error == NOTIFICATION_ERROR_INVALID_DATA) + return "NOTIFICATION_ERROR_INVALID_DATA"; + if (error == NOTIFICATION_ERROR_NO_MEMORY) + return "NOTIFICATION_ERROR_NO_MEMORY"; + if (error == NOTIFICATION_ERROR_FROM_DB) + return "NOTIFICATION_ERROR_FROM_DB"; + if (error == NOTIFICATION_ERROR_ALREADY_EXIST_ID) + return "NOTIFICATION_ERROR_ALREADY_EXIST_ID"; + if (error == NOTIFICATION_ERROR_FROM_DBUS) + return "NOTIFICATION_ERROR_FROM_DBUS"; + if (error == NOTIFICATION_ERROR_NOT_EXIST_ID) + return "NOTIFICATION_ERROR_NOT_EXIST_ID"; + if (error == NOTIFICATION_ERROR_IO) + return "NOTIFICATION_ERROR_IO"; + if (error == NOTIFICATION_ERROR_SERVICE_NOT_READY) + return "NOTIFICATION_ERROR_SERVICE_NOT_READY"; + if (error == NOTIFICATION_ERROR_NONE) + return "NOTIFICATION_ERROR_NONE"; + + return "UNHANDLED ERROR"; +} + +} + +namespace AskUser { + +namespace Agent { + +AskUINotificationBackend::AskUINotificationBackend() : m_notification(nullptr), + m_dismissing(false) { + m_future = m_threadFinished.get_future(); +} + +AskUINotificationBackend::~AskUINotificationBackend() { + notification_free(m_notification); +} + +bool AskUINotificationBackend::start(const std::string &client, const std::string &user, + const std::string &privilege, RequestId requestId, + UIResponseCallback responseCallback) { + if (!responseCallback) { + LOGE("Empty response callback is not allowed"); + return false; + } + + if (!createUI(client, user, privilege)) { + LOGE("UI window for request could not be created!"); + return false; + } + + m_requestId = requestId; + m_responseCallback = responseCallback; + m_thread = std::thread(&AskUINotificationBackend::run, this); + return true; +} + +bool AskUINotificationBackend::createUI(const std::string &client, const std::string &user, + const std::string &privilege) { + notification_error_e err; + + m_notification = notification_new(NOTIFICATION_TYPE_NOTI, NOTIFICATION_GROUP_ID_NONE, + NOTIFICATION_PRIV_ID_NONE); + if (m_notification == nullptr) { + LOGE("Failed to create notification."); + return false; + } + + err = notification_set_pkgname(m_notification, "cynara-askuser"); + if (err != NOTIFICATION_ERROR_NONE) { + LOGE("Unable to set notification pkgname: <" << errorToString(err) << ">"); + return false; + } + + char *dialogTitle = dgettext(PROJECT_NAME, "SID_PRIVILEGE_REQUEST_DIALOG_TITLE"); + err = notification_set_text(m_notification, NOTIFICATION_TEXT_TYPE_TITLE, dialogTitle, nullptr, + NOTIFICATION_VARIABLE_TYPE_NONE); + if (err != NOTIFICATION_ERROR_NONE) { + LOGE("Unable to set notification title: <" << errorToString(err) << ">"); + return false; + } + + char *messageFormat = dgettext(PROJECT_NAME, "SID_PRIVILEGE_REQUEST_DIALOG_MESSAGE"); + char *privilegeDisplayName; + int ret = privilege_info_get_privilege_display_name(privilege.c_str(), &privilegeDisplayName); + if (ret != PRVMGR_ERR_NONE) { + LOGE("Unable to get privilege display name, err: [" << ret << "]"); + privilegeDisplayName = strdup(privilege.c_str()); + } + LOGD("privilege_info_get_privilege_display_name: [" << ret << "]," + " <" << privilegeDisplayName << ">"); + + char tmpBuffer[BUFSIZ]; + ret = std::snprintf(tmpBuffer, sizeof(tmpBuffer), messageFormat, client.c_str(), user.c_str(), + privilegeDisplayName); + free(privilegeDisplayName); + if (ret < 0) { + int erryes = errno; + LOGE("sprintf failed with error: <" << strerror(erryes) << ">"); + return false; + } + + err = notification_set_text(m_notification, NOTIFICATION_TEXT_TYPE_CONTENT, tmpBuffer, nullptr, + NOTIFICATION_VARIABLE_TYPE_NONE); + if (err != NOTIFICATION_ERROR_NONE) { + LOGE("Unable to set notification content: <" << errorToString(err) << ">"); + return false; + } + + ret = std::snprintf(tmpBuffer, sizeof(tmpBuffer), "%s,%s,%s", + dgettext(PROJECT_NAME, "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_NO"), + dgettext(PROJECT_NAME, "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_YES"), + dgettext(PROJECT_NAME, "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_YES_FOR_SESSION")); + if (ret < 0) { + int erryes = errno; + LOGE("sprintf failed with error: <" << strerror(erryes) << ">"); + return false; + } + + bundle *b = bundle_create(); + if (!b) { + int erryes = errno; + LOGE("Unable to create bundle: <" << strerror(erryes) << ">"); + return false; + } + + if (bundle_add(b, "buttons", tmpBuffer)) { + int erryes = errno; + LOGE("Unable to add button to bundle: <" << strerror(erryes) << ">"); + bundle_free(b); + return false; + } + + err = notification_set_execute_option(m_notification, NOTIFICATION_EXECUTE_TYPE_RESPONDING, + nullptr, nullptr, b); + if (err != NOTIFICATION_ERROR_NONE) { + LOGE("Unable to set execute option: <" << errorToString(err) << ">"); + bundle_free(b); + return false; + } + + bundle_free(b); + + err = notification_insert(m_notification, nullptr); + if (err != NOTIFICATION_ERROR_NONE) { + LOGE("Unable to insert notification: <" << errorToString(err) << ">"); + return false; + } + + return true; +} + +bool AskUINotificationBackend::setOutdated() { + // There is no possibility to update window using notifications framework - at least for now + return true; +} + +bool AskUINotificationBackend::dismiss() { + // There is no possibility to dismiss window using notifications framework + // We can only try to get rid of thread + m_dismissing = true; + auto status = m_future.wait_for(std::chrono::milliseconds(10)); + if (status == std::future_status::ready) { + LOGD("UI thread, for request: [" << m_requestId << "], finished and ready to join."); + m_thread.join(); + return true; + } + + LOGD("UI thread, for request: [" << m_requestId << "], not finished."); + return false; +} + +void AskUINotificationBackend::run() { + try { + int buttonClicked = 0; + notification_error_e ret = notification_wait_response(m_notification, m_responseTimeout, + &buttonClicked, nullptr); + LOGD("notification_wait_response finished with ret code: [" << ret << "]"); + + UIResponseType response = URT_ERROR; + if (ret == NOTIFICATION_ERROR_NONE) { + if (buttonClicked) { + static UIResponseType responseType[] = {URT_NO, URT_YES, URT_SESSION}; + LOGD("Got response from user: [" << buttonClicked << "]"); + if (static_cast(buttonClicked) > + sizeof(responseType) / sizeof(responseType[0])) { + LOGE("Wrong code of response: [" << buttonClicked << "]"); + response = URT_NO; + } else { + response = responseType[buttonClicked - 1]; + } + } else { + LOGD("notification_wait_response, for request ID: [" << m_requestId << + "] timeouted"); + response = URT_TIMEOUT; + } + } + m_responseCallback(m_requestId, response); + LOGD("UI thread for request ID: [" << m_requestId << "] stopped execution"); + } catch (const std::exception &e) { + LOGC("Unexpected exception: <" << e.what() << ">"); + } catch (...) { + LOGE("Unexpected unknown exception caught!"); + } + m_threadFinished.set_value(true); +} + +} // namespace Agent + +} // namespace AskUser diff --git a/src/agent/ui/AskUINotificationBackend.h b/src/agent/ui/AskUINotificationBackend.h new file mode 100644 index 0000000..1f77bd0 --- /dev/null +++ b/src/agent/ui/AskUINotificationBackend.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2015 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. + */ +/** + * @file AskUINotificationBackend.h + * @author Adam Malinowski + * @brief This file declares class for ask user window + */ + +#pragma once + +#include +#include +#include +#include + +#include + +namespace AskUser { + +namespace Agent { + +class AskUINotificationBackend : public AskUIInterface { +public: + AskUINotificationBackend(); + virtual ~AskUINotificationBackend(); + + virtual bool start(const std::string &client, const std::string &user, + const std::string &privilege, RequestId requestId, + UIResponseCallback responseCallback); + virtual bool setOutdated(); + virtual bool dismiss(); + virtual bool isDismissing() const { + return m_dismissing; + } + +private: + notification_h m_notification; + std::thread m_thread; + RequestId m_requestId; + UIResponseCallback m_responseCallback; + static const int m_responseTimeout = 60; // seconds + std::promise m_threadFinished; + std::future m_future; + std::atomic m_dismissing; + + void run(); + bool createUI(const std::string &client, const std::string &user, const std::string &privilege); +}; + +} // namespace Agent + +} // namespace AskUser -- 2.7.4