--- /dev/null
+/*
+ * deviced
+ *
+ * Copyright (c) 2019 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 <unistd.h>
+#include <libsyscommon/dbus-system.h>
+
+#include "battery-monitor.h"
+
+#include "core/log.h"
+#include "core/devices.h"
+#include "core/common.h"
+#include "display/core.h"
+
+#define DBUS_DEVICED "org.tizen.system.deviced"
+#define DBUS_DEVICED_BM_PATH "/Org/Tizen/System/DeviceD/BatteryMonitor"
+#define DBUS_DEVICED_BM_IFACE "org.tizen.system.deviced.BatteryMonitor"
+#define DBUS_DEVICED_BM_MEMBER "GetBMData"
+
+/* battery-monitor interface */
+_battery_monitor_ops bm_ops;
+
+typedef enum {
+ B_LOW = 0,
+ B_MED,
+ B_HIGH,
+ B_UNKNOWN,
+} brightness_level;
+
+static char *prev_appid;
+static char *cur_appid;
+
+static enum state_t prev_lcd_state = S_LCDOFF;
+
+/*
+ * hash map for app_time_map_st1
+ * key(appid) : (char *)
+ * val(elapsed time/ms) : (unsigned int *)
+ */
+GHashTable *ht_apptime;
+
+static bool bm_started = false;
+static time_t bm_time_start;
+static time_t bm_time_end;
+
+/* elapsed time per brightness level(milliseconds) */
+static unsigned int brightness_time[B_UNKNOWN];
+static brightness_level prev_brightness_level = B_UNKNOWN;
+
+#define timeval_to_ms(time) ((time.tv_sec * 1000) + (time.tv_usec / 1000))
+#define timeval_diff_ms(tend, tstart) (timeval_to_ms(tend) - timeval_to_ms(tstart))
+
+/* monitoring time */
+#define bds_init_monitoring_time() (bm_time_start = bm_time_end = 0)
+#define bds_set_start_time() (time(&bm_time_start))
+#define bds_set_end_time() (time(&bm_time_end))
+
+static struct timeval bds_time_prev;
+#define bds_timer_init() (bds_time_prev.tv_sec = bds_time_prev.tv_usec = 0)
+#define bds_timer_start() (gettimeofday(&bds_time_prev, NULL))
+
+/* dbus signal subscription id : AppStatusChange */
+guint dbus_sub_id;
+
+static void _init_bds_brightness_time()
+{
+ int i;
+
+ for (i = 0; i < B_UNKNOWN; ++i)
+ brightness_time[i] = 0;
+}
+
+static void _update_bds_brightness_time(unsigned long elapsed_ms)
+{
+ if (prev_brightness_level >= B_UNKNOWN)
+ return;
+
+ _I("update brightness time %lu elapsed", elapsed_ms);
+
+ brightness_time[prev_brightness_level] += elapsed_ms;
+}
+
+static unsigned long bds_timer_get_elapsed_ms()
+{
+ struct timeval cur_time;
+ unsigned long ret;
+
+ if (bds_time_prev.tv_sec == 0) {
+ bds_timer_start();
+ return 0;
+ }
+
+ gettimeofday(&cur_time, NULL);
+
+ ret = timeval_diff_ms(cur_time, bds_time_prev);
+
+ /* update prev bds time */
+ bds_time_prev.tv_sec = cur_time.tv_sec;
+ bds_time_prev.tv_usec = cur_time.tv_usec;
+
+ return ret;
+}
+
+static void update_apptime(const char *appid, unsigned long elapsed)
+{
+ unsigned int *ptime;
+
+ if (!appid) {
+ _E("wrong input %s %lu", appid, elapsed);
+ return ;
+ }
+ if (elapsed == 0) {
+ _D("elaped 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 apptime. %lu elapsed", elapsed);
+
+ ptime = g_hash_table_lookup(ht_apptime, appid);
+ 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(appid), ptime);
+}
+
+static void update_appid(void)
+{
+ g_free(prev_appid);
+ prev_appid = g_strdup(cur_appid);
+}
+
+int update_bds_record(enum state_t cur_lcd_state)
+{
+ unsigned long elapsed = 0;
+
+ if (!bm_started) {
+ prev_lcd_state = cur_lcd_state;
+ update_appid();
+
+ bds_set_start_time();
+ bds_timer_start();
+ bm_started = true;
+
+ _I("battery monitor started");
+
+ return 0;
+ }
+
+ elapsed = bds_timer_get_elapsed_ms();
+
+ /* no effect : prev state(lcd off) -> on or off */
+ if (prev_lcd_state >= S_LCDOFF) {
+ prev_lcd_state = cur_lcd_state;
+ _D("LCD OFF State. prev(%d)->cur(%d)", prev_lcd_state, cur_lcd_state);
+
+ update_appid();
+ _D("foreground app has changed %s->%s", prev_appid, cur_appid);
+
+ return 0;
+ }
+
+ if (prev_appid)
+ update_apptime(prev_appid, elapsed);
+
+ _update_bds_brightness_time(elapsed);
+
+ /* update previous state */
+ prev_lcd_state = cur_lcd_state;
+
+ update_appid();
+
+ return 0;
+}
+
+static brightness_level get_brightness_level(unsigned int val)
+{
+ if (val > 100)
+ return B_UNKNOWN;
+
+ val = val / 33;
+ if (val > 2)
+ val = 2;
+
+ return (brightness_level)val;
+}
+
+int update_bds_brightness_record(unsigned int val)
+{
+ brightness_level level = get_brightness_level(val);
+
+ if (level == B_UNKNOWN)
+ return -1;
+
+ _I("update brightness %u %d", val, (int)level);
+
+ update_bds_record(prev_lcd_state);
+
+ /* update previous state */
+ prev_brightness_level = level;
+
+ return 0;
+}
+
+static void _set_current_appid(const char *appid, int foreground)
+{
+ if (!appid)
+ return;
+
+ /* appid is not changed */
+ if (cur_appid && strcmp(cur_appid, appid) == 0)
+ return;
+
+ if (!foreground) {
+ /* if bg notification is different with cur_appid, ignore */
+ if (cur_appid && strcmp(cur_appid, appid) != 0) {
+ _D("different appid has changed to bg. ignore");
+ return;
+ }
+
+ /* if bg, set null */
+ appid = NULL;
+ }
+
+ g_free(cur_appid);
+ cur_appid = g_strdup(appid);
+
+ update_bds_record(prev_lcd_state);
+}
+
+static void _builder_add_atm_data(GVariantBuilder *atm_builder)
+{
+ 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));
+ _D("value added %s, %u", (const char *)key, (*(unsigned int*)value));
+ }
+ g_hash_table_remove_all(ht_apptime);
+}
+
+static GVariant *_convert_bds_data_to_gvariant(void)
+{
+ GVariant *out_variant = NULL;
+ GVariantBuilder *bds_builder = NULL;
+ GVariantBuilder *atm_builder = NULL;
+ gint64 t_start;
+ gint64 t_end;
+
+ t_start = bm_time_start;
+ t_end = bm_time_end;
+
+ /* convert bm_display_st data to gvariant */
+ bds_builder = g_variant_builder_new(G_VARIANT_TYPE("a(uuuxxa(su))"));
+
+ for (int i = 0 ; i < 1; ++i) {
+ /* 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, "(uuuxxa(su))",
+ brightness_time[B_HIGH],
+ brightness_time[B_LOW],
+ brightness_time[B_MED],
+ t_start,
+ t_end,
+ atm_builder);
+ g_variant_builder_unref(atm_builder);
+ }
+
+ out_variant = g_variant_new("(a(uuuxxa(su)))", bds_builder);
+ g_variant_builder_unref(bds_builder);
+
+ _init_bds_brightness_time();
+
+ return out_variant;
+}
+
+static void _init_bm_ops(void)
+{
+ bm_ops.update_bds_record = update_bds_record;
+}
+
+static GVariant *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)
+{
+ GVariant *reply;
+ gchar *ret;
+
+ update_bds_record(prev_lcd_state);
+
+ bds_set_end_time();
+ reply = _convert_bds_data_to_gvariant();
+ bds_set_start_time();
+
+ ret = g_variant_print(reply, true);
+ _I("Reply battery monitor data:%s:%s", g_variant_get_type_string(reply), ret);
+ g_free(ret);
+
+ return reply;
+}
+
+/* dbus signal handler : AppStatusChange */
+static void _dbus_cb_AppStatusChange(GDBusConnection *conn,
+ const gchar *sender,
+ const gchar *path,
+ const gchar *iface,
+ const gchar *name,
+ GVariant *param,
+ gpointer data)
+{
+ int pid;
+ char *appid;
+ char *status;
+
+ /* (issss) : pid, appid, pkgid, status, type */
+ g_variant_get(param, "(issss)", &pid, &appid, NULL, &status, NULL);
+
+ _I("pid:%d, appid:%s, status:%s", pid, appid, status);
+
+ _set_current_appid(appid, strcmp(status, "fg") == 0);
+
+ g_free(appid);
+ g_free(status);
+}
+
+static const dbus_method_s bm_dbus_methods[] = {
+ {"GetBMData", NULL, "a(uuuxxa(su))", dbus_get_bm_data},
+};
+
+static const dbus_interface_u bm_dbus_interface = {
+ .oh = NULL,
+ .name = DBUS_DEVICED_BM_IFACE,
+ .methods = bm_dbus_methods,
+ .nr_methods = ARRAY_SIZE(bm_dbus_methods),
+};
+
+static void bm_data_init(void)
+{
+ if (ht_apptime) {
+ g_hash_table_destroy(ht_apptime);
+ ht_apptime = NULL;
+ }
+
+ bm_started = false;
+
+ g_free(cur_appid);
+ cur_appid = NULL;
+
+ g_free(prev_appid);
+ prev_appid = NULL;
+
+ bds_timer_init();
+ bds_init_monitoring_time();
+ _init_bds_brightness_time();
+}
+
+static int bm_probe(void *data)
+{
+ _init_bm_ops();
+
+ return 0;
+}
+
+static void _ht_key_destroy(gpointer data)
+{
+ char *pdata = (char *)data;
+
+ if (!pdata)
+ return;
+
+ g_free(pdata);
+}
+
+static void _ht_val_destroy(gpointer data)
+{
+ unsigned int *pdata = (unsigned int *)data;
+
+ if (!pdata)
+ return;
+
+ free(pdata);
+}
+
+static void bm_init(void *data)
+{
+ 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");
+
+ ret = dbus_handle_add_dbus_object(NULL, DBUS_DEVICED_BM_PATH, &bm_dbus_interface);
+ if (ret < 0)
+ _E("Failed to init dbus method: %d", ret);
+
+ dbus_sub_id = subscribe_dbus_signal(NULL,
+ "/Org/Tizen/Aul/AppStatus",
+ "org.tizen.aul.AppStatus",
+ "AppStatusChange",
+ _dbus_cb_AppStatusChange,
+ NULL, NULL);
+ if (dbus_sub_id <= 0)
+ _E("Failed to register signal handler: %d", dbus_sub_id);
+}
+
+static void bm_exit(void *data)
+{
+ int ret;
+
+ ret = dbus_handle_unregister_dbus_object(NULL, DBUS_DEVICED_BM_PATH);
+ if (ret < 0)
+ _E("Failed to unregister dbus object: %d", ret);
+
+ if (dbus_sub_id > 0)
+ unsubscribe_dbus_signal(NULL, dbus_sub_id);
+ dbus_sub_id = 0;
+
+ bm_data_init();
+}
+
+static const struct device_ops battery_monitor_ops = {
+ DECLARE_NAME_LEN("battery-monitor"),
+ .probe = bm_probe,
+ .init = bm_init,
+ .exit = bm_exit,
+};
+
+DEVICE_OPS_REGISTER(&battery_monitor_ops)