--- /dev/null
+/*
+ * lbs-server
+ *
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * 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 <sys/time.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <libsyscommon/dbus-system.h>
+#include <lbs_dbus_server.h>
+#include <stdbool.h>
+#include <vconf.h>
+#include <vconf-keys.h>
+#include <setting.h>
+
+#include "battery-monitor.h"
+#include "battery-monitor_log.h"
+
+#define DBUS_LBSSERVER "org.tizen.lbs.Providers.LbsServer"
+#define DBUS_LBSSERVER_BM_PATH "/org/tizen/lbs/Providers/LbsServer/BatteryMonitor"
+#define DBUS_LBSSERVER_BM_IFACE "org.tizen.lbs.Providers.LbsServer.BatteryMonitor"
+#define DBUS_LBSSERVER_BM_MEMBER "GetBMData"
+
+#define ARRAY_SIZE(name) (sizeof(name)/sizeof(name[0]))
+
+/* battery-monitor interface */
+//static _battery_monitor_ops bm_ops;
+
+typedef enum {
+ GPS_OFF = 0,
+ GPS_SEARCHING,
+ GPS_CONNECTED,
+} gps_state_t;
+
+static uint gps_state;
+
+/* hash map to store app start time and running status */
+
+static GHashTable *ht_appstatus;
+
+/* to manage session timings */
+
+static struct timespec gps_session_start_t;
+static struct timespec gps_session_end_t;
+static bool is_session_ongoing;
+
+/* to track elapsed time in searching */
+static struct timespec gps_searching_start_t;
+static unsigned long searching_elapsed_time;
+
+
+/*
+ * hash map for app_time_map_st1
+ * key(appid) : (char *)
+ * val(elapsed time/ms) : (unsigned int *)
+*/
+static GHashTable *ht_apptime;
+static GHashTable *ht_lbs_client;
+
+/* gvariant builder for bds sessions */
+static GVariantBuilder *bds_builder = NULL;
+
+//static void bds_builder_update_bds_data(void);
+/*
+ * Set max session to prevent unknown behavior
+ * Session : monitoring data between gps tracking started and ended.
+ */
+#define MAX_SESSION (1000)
+static unsigned int session_count = 0;
+
+#define timespec_init(val) val.tv_sec = val.tv_nsec = 0;
+#define timespec_to_ms(val) ((val.tv_sec * 1000) + (val.tv_nsec / (1000 * 1000)))
+#define timespec_diff_ms(tend, tstart) (timespec_to_ms(tend) - timespec_to_ms(tstart))
+
+#define gps_get_start_time_sec() (gps_session_start_t.tv_sec)
+#define gps_get_end_time_sec() (gps_session_end_t.tv_sec)
+
+
+static void gps_set_searching_start_time(void)
+{
+ ENTER;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &gps_searching_start_t) < 0) {
+ _E("clock_gettime failed: %m");
+ timespec_init(gps_searching_start_t);
+ }
+
+ EXIT;
+}
+
+static void gps_update_searching_elapsed_time(void)
+{
+ ENTER;
+
+ struct timespec cur_time;
+ unsigned long elapsed;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &cur_time) != 0) {
+ _E("clock_gettime failed: %m");
+ return;
+ }
+ elapsed = timespec_diff_ms(cur_time, gps_searching_start_t);
+ searching_elapsed_time += elapsed;
+ timespec_init(gps_searching_start_t);
+
+ EXIT;
+}
+
+/*
+ * Set start and end time of GPS session.
+ * Reset when the GetBMData method call is received.
+ * Use CLOCK_REALTIME to follow specifications of battery monitor framework
+ */
+
+static void gps_init_session_time(void)
+{
+ ENTER;
+
+ if (clock_gettime(CLOCK_REALTIME, &gps_session_start_t) < 0) {
+ _E("clock_gettime failed: %m");
+ timespec_init(gps_session_start_t);
+ }
+
+ EXIT;
+}
+static void gps_end_session_time(void)
+{
+ ENTER;
+
+ if (clock_gettime(CLOCK_REALTIME, &gps_session_end_t) < 0) {
+ _E("clock_gettime failed: %m");
+ timespec_init(gps_session_end_t);
+ }
+
+ EXIT;
+}
+
+static void gps_set_start_time(app_status* curr_app_status)
+{
+ ENTER;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &(curr_app_status->start_time)) < 0) {
+ _E("clock_gettime failed: %m");
+ timespec_init(curr_app_status->start_time);
+ }
+
+ EXIT;
+}
+
+static unsigned long gps_timer_get_elapsed_ms(const app_status* curr_app_status)
+{
+ ENTER;
+
+ struct timespec cur_time;
+ unsigned long ret;
+
+ if (curr_app_status->is_running == false) {
+ return 0;
+ }
+
+ if (clock_gettime(CLOCK_MONOTONIC, &cur_time) != 0) {
+ _E("clock_gettime failed: %m");
+ return 0;
+ }
+
+ ret = timespec_diff_ms(cur_time, curr_app_status->start_time);
+
+ EXIT;
+ return ret;
+}
+
+static app_status* get_new_app_status_node()
+{
+ ENTER;
+
+ app_status* new_app_status_node = (app_status*)malloc(sizeof(app_status));
+ if (!new_app_status_node) {
+ _E("failed to alloc memory");
+ return NULL;
+ }
+ new_app_status_node->is_running = false;
+ timespec_init(new_app_status_node->start_time);
+
+ EXIT;
+ return new_app_status_node;
+}
+
+static void update_apptime(const char *app_id, unsigned long elapsed)
+{
+ ENTER;
+ unsigned int *ptime;
+
+ if (!app_id) {
+ _E("wrong input appid(null) %lu", elapsed);
+ return ;
+ }
+ if (elapsed == 0) {
+ _E("elapsed time is 0, no update.");
+ return ;
+ }
+ /* display core is initialized at first */
+ if (!ht_apptime) {
+ _D("battery-monitor moulde is not initialized");
+ return ;
+ }
+
+ _I("update gps service usage by app(%s) by duration(%lu)",app_id, elapsed);
+
+ ptime = g_hash_table_lookup(ht_apptime, app_id);
+ if (ptime) {
+ *ptime += elapsed;
+ return ;
+ }
+
+ ptime = (unsigned int*)malloc(sizeof(unsigned int));
+ if (!ptime) {
+ _E("failed to alloc memory");
+ return ;
+ }
+
+ *ptime = elapsed;
+
+ g_hash_table_insert(ht_apptime, g_strdup(app_id), ptime);
+
+ EXIT;
+}
+
+static void bm_batch_update_gps_record_on_gps_off()
+{
+ ENTER;
+
+ GHashTableIter iter;
+ gpointer key, value;
+ unsigned long elapsed = 0;
+
+ g_hash_table_iter_init (&iter, ht_appstatus);
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ if(((const app_status*)value)->is_running) {
+ elapsed = gps_timer_get_elapsed_ms((const app_status*)value);
+ update_apptime((const char *)key,elapsed);
+ _D("app(%s) used gps service for duration(%lu)",(const char *)key, elapsed);
+ ((app_status*)value)->is_running = false;
+ timespec_init(((app_status*)value)->start_time);
+ }
+ }
+ g_hash_table_remove_all(ht_appstatus);
+ EXIT;
+}
+
+static void bm_batch_update_gps_record_on_demand()
+{
+ ENTER;
+
+ GHashTableIter iter;
+ gpointer key, value;
+ unsigned long elapsed = 0;
+
+ g_hash_table_iter_init (&iter, ht_appstatus);
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ if(((const app_status*)value)->is_running) {
+ elapsed = gps_timer_get_elapsed_ms((const app_status*)value);
+ update_apptime((const char *)key,elapsed);
+ _D("app(%s) used gps service for duration(%lu)",(const char *)key, elapsed);
+ gps_set_start_time(((app_status*)value));
+ }
+ }
+
+ EXIT;
+}
+
+void bm_update_gps_record_on_client_removal(const gchar *client)
+{
+ ENTER;
+ unsigned long elapsed = 0;
+ gchar *app_id = (gchar*) g_hash_table_lookup(ht_lbs_client, client);
+ if(!app_id) {
+ _D("no app found for client(%s)",client);
+ return;
+ }
+ _I("client of app(%s) removed from lbs-server", app_id);
+ app_status *curr_app = (app_status*) g_hash_table_lookup(ht_appstatus, app_id);
+ if(!curr_app) {
+ _D("no app status found for client(%s)",client);
+ return;
+ }
+ if(curr_app->is_running) {
+ elapsed = gps_timer_get_elapsed_ms(curr_app);
+ update_apptime((const char *)app_id, elapsed);
+ curr_app->is_running = false;
+ timespec_init(curr_app->start_time);
+ }
+ if (g_hash_table_remove(ht_lbs_client, client) != TRUE)
+ _D("g_hash_table_remove is failed.");
+
+ EXIT;
+}
+
+void bm_update_gps_record(const gchar *app_id, const gchar *client, const char* cmd)
+{
+ ENTER;
+
+ unsigned long elapsed = 0;
+ app_status* curr_app_status;
+ if (!app_id) {
+ _E("wrong input app_id(null) %lu", elapsed);
+ return ;
+ }
+ /* gps-sensor core should be initialized at first */
+ if (!ht_apptime) {
+ _E("battery-monitor module is not initialized");
+ return ;
+ }
+ if (!ht_appstatus) {
+ _E("app status table is not initialized");
+ return ;
+ }
+ if(!g_strcmp0(cmd, "START")) {
+ curr_app_status = get_new_app_status_node();
+ curr_app_status->is_running = true;
+ gps_set_start_time(curr_app_status);
+ g_hash_table_insert(ht_appstatus, g_strdup(app_id),curr_app_status);
+ g_hash_table_insert(ht_lbs_client, g_strdup(client),g_strdup(app_id));
+ _I("start record updated for app(%s) ", app_id);
+ }
+ if(is_session_ongoing && !g_strcmp0(cmd, "STOP")) {
+ curr_app_status = g_hash_table_lookup(ht_appstatus, app_id);
+ if(!curr_app_status) {
+ _D("no app(%s) found in hash table", app_id);
+ return;
+ }
+ elapsed = gps_timer_get_elapsed_ms(curr_app_status);
+ update_apptime(app_id, elapsed);
+ curr_app_status->is_running = false;
+ /* reset start time of this app to zero */
+ timespec_init(curr_app_status->start_time);
+ if (g_hash_table_remove(ht_lbs_client, client) != TRUE)
+ _D("g_hash_table_remove is failed.");
+ if (g_hash_table_remove(ht_appstatus, app_id) != TRUE)
+ _D("g_hash_table_remove is failed.");
+ _I("stop record updated for app(%s)", app_id);
+ }
+
+ EXIT;
+}
+
+static void builder_add_atm_data(GVariantBuilder *atm_builder)
+{
+ ENTER;
+
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init(&iter, ht_apptime);
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ g_variant_builder_add(atm_builder, "(su)", (const char *)key, (*(unsigned int*)value));
+ }
+ g_hash_table_remove_all(ht_apptime);
+
+ EXIT;
+}
+
+static void gps_builder_update_gps_data(void)
+{
+ ENTER;
+
+ GVariantBuilder *atm_builder = NULL;
+ gint64 t_start = 0;
+ gint64 t_end = 0;
+
+ _D("update session data");
+
+ if (!bds_builder)
+ bds_builder = g_variant_builder_new(G_VARIANT_TYPE("a(xxua(su))"));
+
+ if (session_count > MAX_SESSION) {
+ _E("Abnormal behavior, there are too many sessions(%d)", session_count);
+ return ;
+ }
+
+ /* if size(ht_apptime) is 0, there is no app that is using GPS */
+ if (g_hash_table_size(ht_apptime)) {
+ t_start = timespec_to_ms(gps_session_start_t);
+ t_end = timespec_to_ms(gps_session_end_t);
+ }
+
+ /* convert app_time_map_st1 to gvariant array */
+ atm_builder = g_variant_builder_new(G_VARIANT_TYPE("a(su)"));
+ builder_add_atm_data(atm_builder);
+
+ g_variant_builder_add(bds_builder, "(xxua(su))",
+ t_start,
+ t_end,
+ searching_elapsed_time,
+ atm_builder);
+ g_variant_builder_unref(atm_builder);
+ ++session_count;
+
+ EXIT;
+}
+
+static GVariant *gps_builder_get_gvariant(void)
+{
+ ENTER;
+
+ GVariant *out_variant = NULL;
+
+ /* builder has released by GetBMData dbus call within gps off state
+ if null, make empty gvariant and reply */
+ if (!bds_builder)
+ gps_builder_update_gps_data();
+
+ out_variant = g_variant_new("(a(xxua(su)))", bds_builder);
+
+ g_variant_builder_unref(bds_builder);
+ bds_builder = NULL;
+ session_count = 0;
+
+ EXIT;
+ return out_variant;
+}
+
+static GVariant *lbs_dbus_get_bm_data(GDBusConnection *conn,
+ const gchar *sender,
+ const gchar *path,
+ const gchar *iface,
+ const gchar *name,
+ GVariant *param,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ ENTER;
+
+ GVariant *reply = NULL;
+ if(is_session_ongoing && gps_session_end_t.tv_sec == 0 && gps_session_end_t.tv_nsec == 0) {
+ _D("gps session ongoing - service periodical on demand call for data from battery monitor");
+ gps_end_session_time();
+ bm_batch_update_gps_record_on_demand();
+ if(gps_state == GPS_SEARCHING) {
+ gps_update_searching_elapsed_time();
+ }
+ gps_builder_update_gps_data();
+ }
+
+ reply = gps_builder_get_gvariant();
+
+ if(is_session_ongoing) {
+ _D("gps session on going");
+ gps_init_session_time();
+ }
+ else {
+ _D("gps session turned off");
+ timespec_init(gps_session_start_t);
+ }
+ searching_elapsed_time = 0;
+ timespec_init(gps_session_end_t);
+
+ EXIT;
+ return reply;
+}
+
+void bm_update_gps_status(int status)
+{
+ ENTER;
+
+ _I("gps state is changing from (%d) to (%d)", gps_state, status);
+ /* GPS became active, start new session */
+ if((status == GPS_SEARCHING || status == GPS_CONNECTED) && !is_session_ongoing) {
+ gps_init_session_time();
+ is_session_ongoing = true;
+ }
+
+ /* GPS is in searching state, start searching time */
+ if(status == GPS_SEARCHING && gps_state != GPS_SEARCHING)
+ gps_set_searching_start_time();
+
+ /* GPS state changed to other than searching state, update searching time for this cycle */
+ if(gps_state == GPS_SEARCHING && (status == GPS_CONNECTED || status == GPS_OFF))
+ gps_update_searching_elapsed_time();
+
+ /* GPS became inactive, end existing session */
+ if(status == GPS_OFF) {
+ gps_end_session_time();
+ bm_batch_update_gps_record_on_gps_off();
+ /* add another session tuple in reply data */
+ gps_builder_update_gps_data();
+ searching_elapsed_time = 0;
+ is_session_ongoing = false;
+ }
+ gps_state = status;
+
+ EXIT;
+}
+
+static int bm_get_gps_state()
+{
+ ENTER;
+
+ int val;
+ if (setting_get_int(VCONFKEY_LOCATION_GPS_STATE, &val) == FALSE)
+ val = GPS_OFF;
+
+ EXIT;
+ return val;
+}
+
+static void _ht_key_destroy(gpointer data)
+{
+ ENTER;
+
+ char *pdata = (char *)data;
+
+ if (!pdata)
+ return;
+
+ g_free(pdata);
+
+ EXIT;
+}
+
+static void _ht_val_destroy(gpointer data)
+{
+ ENTER;
+
+ unsigned int *pdata = (unsigned int *)data;
+
+ if (!pdata)
+ return;
+
+ free(pdata);
+
+ EXIT;
+}
+
+static const dbus_method_s bm_lbs_dbus_methods[] = {
+ {"GetBMData", NULL, "a(xxua(su))", lbs_dbus_get_bm_data},
+};
+
+static const dbus_interface_u bm_lbs_dbus_interface = {
+ .oh = NULL,
+ .name = DBUS_LBSSERVER_BM_IFACE,
+ .methods = bm_lbs_dbus_methods,
+ .nr_methods = ARRAY_SIZE(bm_lbs_dbus_methods),
+};
+
+
+void bm_init(void *data)
+{
+ ENTER;
+
+ int ret;
+
+ ht_apptime = g_hash_table_new_full(g_str_hash, g_str_equal, _ht_key_destroy, _ht_val_destroy);
+ if (!ht_apptime)
+ _E("Failed to init hash table");
+
+ ht_appstatus = g_hash_table_new_full(g_str_hash, g_str_equal, _ht_key_destroy, _ht_val_destroy);
+ if (!ht_appstatus)
+ _E("Failed to init app status hash table");
+ ht_lbs_client = g_hash_table_new_full(g_str_hash, g_str_equal, _ht_key_destroy, _ht_val_destroy);
+ if (!ht_lbs_client)
+ _E("Failed to client to app_id hash table");
+
+ ret = dbus_handle_add_dbus_object(NULL, DBUS_LBSSERVER_BM_PATH, &bm_lbs_dbus_interface);
+ if (ret < 0)
+ _E("Failed to init dbus method: %d", ret);
+
+ if (dbus_handle_register_dbus_object_all(NULL) < 0)
+ _E("Failed to register dbus method: %d", ret);
+
+ gps_state = bm_get_gps_state();
+ searching_elapsed_time = 0;
+ is_session_ongoing = false;
+ timespec_init(gps_session_start_t);
+ timespec_init(gps_session_end_t);
+ timespec_init(gps_searching_start_t);
+
+ EXIT;
+}
+
+
+static void bm_data_init(void)
+{
+ ENTER;
+
+ if (ht_apptime) {
+ g_hash_table_destroy(ht_apptime);
+ ht_apptime = NULL;
+ }
+ if (ht_appstatus) {
+ g_hash_table_destroy(ht_appstatus);
+ ht_appstatus = NULL;
+ }
+ if (ht_lbs_client) {
+ g_hash_table_destroy(ht_lbs_client);
+ ht_lbs_client = NULL;
+ }
+ if (bds_builder)
+ g_variant_builder_unref(bds_builder);
+
+
+ timespec_init(gps_session_start_t);
+ timespec_init(gps_session_end_t);
+ timespec_init(gps_searching_start_t);
+ gps_state = bm_get_gps_state();
+ is_session_ongoing = false;
+ bds_builder = NULL;
+ session_count = 0;
+ searching_elapsed_time = 0;
+
+ EXIT;
+}
+
+void bm_exit(void *data)
+{
+ ENTER;
+
+ int ret;
+
+ ret = dbus_handle_unregister_dbus_object(NULL, DBUS_LBSSERVER_BM_PATH);
+ if (ret < 0)
+ _E("Failed to unregister dbus object: %d", ret);
+
+ bm_data_init();
+
+ EXIT;
+}