4 #include <Elementary.h>
16 #ifndef EET_COMPRESSION_DEFAULT
17 #define EET_COMPRESSION_DEFAULT 1
20 #define HISTORY_ENTRY "history"
22 typedef struct _Call_Info_List {
27 typedef struct _History {
29 Eet_Data_Descriptor *edd;
30 Eet_Data_Descriptor *edd_list;
31 Call_Info_List *calls;
32 Elm_Genlist_Item_Class *itc;
34 Evas_Object *genlist_all, *genlist_missed;
35 Ecore_Poller *updater;
39 typedef struct _Call_Info {
42 long long creation_time; /* not in edd */
47 const OFono_Call *call; /* not in edd */
48 Elm_Object_Item *it_all; /*not in edd */
49 Elm_Object_Item *it_missed; /*not in edd */
52 static OFono_Callback_List_Call_Node *callback_node_call_removed = NULL;
53 static OFono_Callback_List_Call_Node *callback_node_call_changed = NULL;
55 static Eina_Bool _history_time_updater(void *data)
59 double now = ecore_loop_time_get();
60 const double interval_threshold = 30.0;
61 const long long update_threshold = time(NULL) - WEEK - DAY;
64 * NOTE ABOUT CONSTANTS:
66 * - interval_threshold: to avoid updating too often (window
67 * lost and gained focus, object hidden or shown), we limit
68 * updates to this minimum interval. The poller should run
71 * - update_threshold: since we format strings over a week as
72 * fixed string (often day-month-year, not relative to
73 * today), we can stop flagging list items as updated. We
74 * give it a day of slack so we can be sure to update every
75 * item (for held and conferences, you may have items that
76 * are close in time but slightly out of order as items are
77 * prepended as the calls are removed from ofono, then
78 * history is not strictly in 'time' order). We must
79 * stop iterating after update_threshold so users that never
80 * deleted history and have thousand items will not
81 * uselessly update all the thousand items.
84 if (now - ctx->last_update < interval_threshold)
86 ctx->last_update = now;
88 it = elm_genlist_first_item_get(ctx->genlist_all);
89 for (; it != NULL; it = elm_genlist_item_next_get(it)) {
90 const Call_Info *call_info = elm_object_item_data_get(it);
91 long long t = call_info->end_time;
92 if (EINA_UNLIKELY(t == 0)) {
93 t = call_info->start_time;
94 if (EINA_UNLIKELY(t == 0))
95 t = call_info->creation_time;
97 if (EINA_UNLIKELY(t < update_threshold))
99 elm_genlist_item_update(it);
102 it = elm_genlist_first_item_get(ctx->genlist_missed);
103 for (; it != NULL; it = elm_genlist_item_next_get(it)) {
104 const Call_Info *call_info = elm_object_item_data_get(it);
105 long long t = call_info->end_time;
106 if (EINA_UNLIKELY(t == 0)) {
107 t = call_info->start_time;
108 if (EINA_UNLIKELY(t == 0))
109 t = call_info->creation_time;
111 if (EINA_UNLIKELY(t < update_threshold))
113 elm_genlist_item_update(it);
119 static void _history_time_updater_stop(History *history)
121 Evas *e = evas_object_evas_get(history->self);
122 Eina_Bool win_focused = evas_focus_state_get(e);
123 Eina_Bool obj_visible = evas_object_visible_get(history->self);
125 DBG("poller %p, win_focused=%hhu, obj_visible=%hhu",
126 history->updater, win_focused, obj_visible);
127 if (!history->updater)
129 if (win_focused && obj_visible)
132 DBG("delete poller %p", history->updater);
133 ecore_poller_del(history->updater);
134 history->updater = NULL;
137 static void _history_time_updater_start(History *history)
139 Evas *e = evas_object_evas_get(history->self);
140 Eina_Bool win_focused = evas_focus_state_get(e);
141 Eina_Bool obj_visible = evas_object_visible_get(history->self);
143 DBG("poller %p, win_focused=%hhu, obj_visible=%hhu",
144 history->updater, win_focused, obj_visible);
145 if (history->updater)
147 if ((!win_focused) || (!obj_visible))
151 /* ECORE_POLLER_CORE is 1/8th of second. */
152 history->updater = ecore_poller_add(ECORE_POLLER_CORE, 8 * 60,
153 _history_time_updater,
155 _history_time_updater(history);
158 static Call_Info *_history_call_info_search(const History *history,
159 const OFono_Call *call)
161 Call_Info *call_info;
163 long long t = ofono_call_full_start_time_get(call);
164 const char *line_id = ofono_call_line_id_get(call); /* stringshare */
166 EINA_LIST_FOREACH(history->calls->list, l, call_info) {
167 if (call_info->call == call)
169 else if (!call_info->call) {
170 if ((t > 0) && (call_info->start_time == t) &&
171 (line_id == call_info->line_id)) {
172 DBG("associated existing log %p %s (%lld) with "
176 call_info->start_time,
178 call_info->call = call;
187 static Eina_Bool _history_call_info_update(Call_Info *call_info)
189 OFono_Call_State state;
191 EINA_SAFETY_ON_NULL_RETURN_VAL(call_info->call, EINA_FALSE);
192 state = ofono_call_state_get(call_info->call);
194 if (state == OFONO_CALL_STATE_INCOMING ||
195 state == OFONO_CALL_STATE_WAITING) {
196 if (!call_info->incoming) {
197 call_info->incoming = EINA_TRUE;
200 } else if (state == OFONO_CALL_STATE_DIALING ||
201 state == OFONO_CALL_STATE_ALERTING) {
202 if (!call_info->incoming) {
203 call_info->incoming = EINA_FALSE;
206 } else if (state == OFONO_CALL_STATE_ACTIVE ||
207 state == OFONO_CALL_STATE_HELD) {
208 if (!call_info->completed) {
209 call_info->start_time = ofono_call_full_start_time_get
211 if (call_info->start_time == 0)
212 call_info->start_time = call_info->creation_time;
214 call_info->completed = EINA_TRUE;
222 static void _dial_reply(void *data, OFono_Error err,
223 OFono_Call *call __UNUSED__)
225 const char *number = data;
227 if (err != OFONO_ERROR_NONE) {
229 snprintf(buf, sizeof(buf), "Could not call: %s", number);
230 gui_simple_popup("Error", buf);
234 static void _on_item_clicked(void *data, Evas_Object *obj __UNUSED__,
237 Elm_Object_Item *it = event_info;
238 const char *number = data;
240 INF("call %s", number);
241 ofono_dial(number, NULL, _dial_reply, number);
242 elm_genlist_item_selected_set(it, EINA_FALSE);
245 static void _history_call_changed(void *data, OFono_Call *call)
247 History *history = data;
248 const char *line_id = ofono_call_line_id_get(call);
249 Call_Info *call_info;
250 OFono_Call_State state = ofono_call_state_get(call);
252 call_info = _history_call_info_search(history, call);
253 DBG("call=%p, id=%s, state=%d, completed=%d, incoming=%d, info=%p",
254 call, line_id, state,
255 call_info ? call_info->completed : EINA_FALSE,
256 call_info ? call_info->incoming : EINA_FALSE,
262 call_info = calloc(1, sizeof(Call_Info));
263 EINA_SAFETY_ON_NULL_RETURN(call_info);
265 call_info->call = call;
266 call_info->start_time = ofono_call_full_start_time_get(call);
267 call_info->creation_time = time(NULL);
268 if (call_info->start_time == 0)
269 call_info->start_time = call_info->creation_time;
270 call_info->line_id = eina_stringshare_add(line_id);
271 call_info->name = eina_stringshare_add(ofono_call_name_get(call));
272 history->calls->list =
273 eina_list_prepend(history->calls->list, call_info);
274 history->calls->dirty = EINA_TRUE;
277 if (_history_call_info_update(call_info))
278 history->calls->dirty = EINA_TRUE;
281 static void _history_call_log_save(History *history)
285 EINA_SAFETY_ON_NULL_RETURN(history->calls);
286 DBG("save history (%u calls, dirty: %d) to %s",
287 eina_list_count(history->calls->list), history->calls->dirty,
290 ecore_file_unlink(history->bkp);
291 ecore_file_mv(history->path, history->bkp);
292 efile = eet_open(history->path, EET_FILE_MODE_WRITE);
293 EINA_SAFETY_ON_NULL_RETURN(efile);
294 if (!(eet_data_write(efile,
295 history->edd_list, HISTORY_ENTRY,
296 history->calls, EET_COMPRESSION_DEFAULT)))
297 ERR("Could in the history log file");
302 static void _history_call_removed(void *data, OFono_Call *call)
305 History *history = data;
306 const char *line_id = ofono_call_line_id_get(call);
307 Call_Info *call_info;
311 call_info = _history_call_info_search(history, call);
312 DBG("call=%p, id=%s, info=%p", call, line_id, call_info);
313 EINA_SAFETY_ON_NULL_RETURN(call_info);
315 if (call_info->start_time == 0)
316 call_info->start_time = call_info->creation_time;
318 start = call_info->start_time;
321 call_info->end_time = time(NULL);
322 call_info->call = NULL;
324 if (call_info->completed)
325 INF("Call end: %s at %s", line_id, tm);
327 if (!call_info->incoming)
328 INF("Not answered: %s at %s", line_id, tm);
330 INF("Missed: %s at %s", line_id, tm);
331 if (call_info->it_missed)
332 elm_genlist_item_update(call_info->it_missed);
334 it = elm_genlist_item_prepend
335 (history->genlist_missed,
338 ELM_GENLIST_ITEM_NONE,
341 elm_genlist_item_show
342 (it, ELM_GENLIST_ITEM_SCROLLTO_IN);
343 call_info->it_missed = it;
348 history->calls->dirty = EINA_TRUE;
349 _history_call_log_save(history);
351 if (call_info->it_all)
352 elm_genlist_item_update(call_info->it_all);
354 it = elm_genlist_item_prepend(history->genlist_all,
357 ELM_GENLIST_ITEM_NONE,
360 elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_IN);
361 call_info->it_all = it;
365 static void _call_info_free(Call_Info *call_info)
367 eina_stringshare_del(call_info->line_id);
368 eina_stringshare_del(call_info->name);
372 static void _on_del(void *data, Evas *e __UNUSED__,
373 Evas_Object *obj __UNUSED__, void *event __UNUSED__)
375 History *history = data;
376 Call_Info *call_info;
378 if (history->updater)
379 ecore_poller_del(history->updater);
381 if (history->calls->dirty)
382 _history_call_log_save(history);
384 ofono_call_removed_cb_del(callback_node_call_removed);
385 ofono_call_changed_cb_del(callback_node_call_changed);
386 eet_data_descriptor_free(history->edd);
387 eet_data_descriptor_free(history->edd_list);
388 EINA_LIST_FREE(history->calls->list, call_info)
389 _call_info_free(call_info);
390 free(history->calls);
391 elm_genlist_item_class_free(history->itc);
395 ecore_file_shutdown();
399 static void _on_hide(void *data, Evas *e __UNUSED__,
400 Evas_Object *obj __UNUSED__, void *event __UNUSED__)
402 History *history = data;
403 DBG("history became hidden");
404 _history_time_updater_stop(history);
407 static void _on_show(void *data, Evas *e __UNUSED__,
408 Evas_Object *obj __UNUSED__, void *event __UNUSED__)
410 History *history = data;
411 DBG("history became visible");
412 _history_time_updater_start(history);
415 static void _on_win_focus_out(void *data, Evas *e __UNUSED__,
416 void *event_info __UNUSED__)
418 History *history = data;
419 DBG("window is unfocused");
420 _history_time_updater_stop(history);
423 static void _on_win_focus_in(void *data, Evas *e __UNUSED__,
424 void *event_info __UNUSED__)
426 History *history = data;
427 DBG("window is focused");
428 _history_time_updater_start(history);
431 static void _history_call_info_descriptor_init(Eet_Data_Descriptor **edd,
432 Eet_Data_Descriptor **edd_list)
434 Eet_Data_Descriptor_Class eddc;
436 EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Call_Info);
437 *edd = eet_data_descriptor_stream_new(&eddc);
439 EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Call_Info_List);
440 *edd_list = eet_data_descriptor_stream_new(&eddc);
442 EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
443 "completed", completed, EET_T_UCHAR);
444 EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
445 "incoming", incoming, EET_T_UCHAR);
447 EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
448 "start_time", start_time, EET_T_LONG_LONG);
449 EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
450 "end_time", end_time, EET_T_LONG_LONG);
451 EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
452 "line_id", line_id, EET_T_STRING);
453 EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
454 "name", name, EET_T_STRING);
456 EET_DATA_DESCRIPTOR_ADD_LIST(*edd_list, Call_Info_List, "list", list,
460 static void _history_call_log_read(History *history)
462 Call_Info *call_info;
465 Call_Info_List *calls = NULL;
468 efile = eet_open(history->path, EET_FILE_MODE_READ);
471 calls = eet_data_read(efile, history->edd_list, HISTORY_ENTRY);
476 efile = eet_open(history->bkp, EET_FILE_MODE_READ);
478 calls = eet_data_read(efile, history->edd_list,
485 calls = calloc(1, sizeof(Call_Info_List));
487 history->calls = calls;
488 EINA_SAFETY_ON_NULL_RETURN(history->calls);
490 EINA_LIST_FOREACH(history->calls->list, l, call_info) {
491 it = elm_genlist_item_append(history->genlist_all,
494 ELM_GENLIST_ITEM_NONE,
497 call_info->it_all = it;
499 if (call_info->completed)
502 it = elm_genlist_item_append(history->genlist_missed,
503 history->itc, call_info, NULL,
504 ELM_GENLIST_ITEM_NONE,
507 call_info->it_missed = it;
510 it = elm_genlist_first_item_get(history->genlist_all);
512 elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_TOP);
514 it = elm_genlist_first_item_get(history->genlist_missed);
516 elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_TOP);
519 static char *_item_label_get(void *data, Evas_Object *obj __UNUSED__,
522 Call_Info *call_info = data;
524 if (strncmp(part, "text.call", strlen("text.call")))
527 part += strlen("text.call.");
529 if (!strcmp(part, "name")) {
530 if (!call_info->name || call_info->name[0] == '\0')
531 return phone_format(call_info->line_id);
532 return strdup(call_info->name);
535 if (!strcmp(part, "time")) {
536 if ((call_info->completed) && (call_info->end_time))
537 return date_format(call_info->end_time);
538 return date_format(call_info->start_time);
541 /* TODO: Fetch phone type from contacts information*/
542 if (!strcmp(part, "type"))
543 return strdup("TODO:TELEPHONE TYPE");
545 ERR("Unexpected text part: %s", part);
550 static Eina_Bool _item_state_get(void *data, Evas_Object *obj __UNUSED__,
553 Call_Info *call_info = data;
555 if (!strcmp(part, "missed"))
556 return !call_info->completed;
557 else if (!strcmp(part, "completed"))
558 return call_info->completed;
559 else if (!strcmp(part, "outgoing"))
560 return !call_info->incoming;
561 else if (!strcmp(part, "incoming"))
562 return call_info->incoming;
564 ERR("Unexpected state part: %s", part);
568 static void _on_clicked(void *data __UNUSED__, Evas_Object *obj __UNUSED__,
569 const char *emission, const char *source __UNUSED__)
571 EINA_SAFETY_ON_NULL_RETURN(emission);
572 emission += strlen("clicked,");
574 if (!strcmp(emission, "all"))
575 elm_object_signal_emit(obj, "show,all", "gui");
577 elm_object_signal_emit(obj, "show,missed", "gui");
580 static void _on_more_clicked(void *data __UNUSED__, Evas_Object *obj __UNUSED__,
581 const char *emission __UNUSED__,
582 const char *source __UNUSED__)
587 static Evas_Object *_item_content_get(void *data __UNUSED__, Evas_Object *obj,
588 const char *part __UNUSED__)
592 btn = gui_layout_add(obj, "history/img");
593 EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL);
594 elm_object_signal_callback_add(btn, "clicked,more", "gui",
595 _on_more_clicked, NULL);
596 evas_object_propagate_events_set(btn, EINA_FALSE);
601 Evas_Object *history_add(Evas_Object *parent)
606 const char *config_path;
607 char *path, base_dir[PATH_MAX];
608 Elm_Genlist_Item_Class *itc;
609 Evas_Object *obj, *genlist_all, *genlist_missed;
613 history = calloc(1, sizeof(History));
614 EINA_SAFETY_ON_NULL_RETURN_VAL(history, NULL);
616 history->self = obj = gui_layout_add(parent, "history_bg");
617 EINA_SAFETY_ON_NULL_GOTO(obj, err_layout);
619 genlist_all = elm_genlist_add(obj);
620 EINA_SAFETY_ON_NULL_GOTO(genlist_all, err_object_new);
621 elm_object_style_set(genlist_all, "history");
623 genlist_missed = elm_genlist_add(obj);
624 EINA_SAFETY_ON_NULL_GOTO(genlist_missed, err_object_new);
625 elm_object_style_set(genlist_missed, "history");
627 itc = elm_genlist_item_class_new();
628 EINA_SAFETY_ON_NULL_GOTO(itc, err_object_new);
629 itc->item_style = "history";
630 itc->func.text_get = _item_label_get;
631 itc->func.content_get = _item_content_get;
632 itc->func.state_get = _item_state_get;
633 itc->func.del = NULL;
634 history->genlist_all = genlist_all;
635 history->genlist_missed = genlist_missed;
638 elm_object_part_content_set(obj, "elm.swallow.all", genlist_all);
639 elm_object_part_content_set(obj, "elm.swallow.missed", genlist_missed);
640 elm_object_signal_emit(obj, "show,all", "gui");
641 elm_object_signal_callback_add(obj, "clicked,*", "gui",
644 config_path = efreet_config_home_get();
645 snprintf(base_dir, sizeof(base_dir), "%s/%s", config_path,
647 ecore_file_mkpath(base_dir);
648 r = asprintf(&path, "%s/%s/history.eet", config_path, PACKAGE_NAME);
653 history->path = path;
654 r = asprintf(&path, "%s/%s/history.eet.bkp", config_path,
662 _history_call_info_descriptor_init(&history->edd, &history->edd_list);
663 _history_call_log_read(history);
664 EINA_SAFETY_ON_NULL_GOTO(history->calls, err_log_read);
665 evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL, _on_del,
667 evas_object_event_callback_add(obj, EVAS_CALLBACK_HIDE, _on_hide,
669 evas_object_event_callback_add(obj, EVAS_CALLBACK_SHOW, _on_show,
672 e = evas_object_evas_get(obj);
673 evas_event_callback_add(e, EVAS_CALLBACK_CANVAS_FOCUS_OUT,
674 _on_win_focus_out, history);
675 evas_event_callback_add(e, EVAS_CALLBACK_CANVAS_FOCUS_IN,
676 _on_win_focus_in, history);
678 callback_node_call_changed =
679 ofono_call_changed_cb_add(_history_call_changed, history);
680 callback_node_call_removed =
681 ofono_call_removed_cb_add(_history_call_removed, history);
687 eet_data_descriptor_free(history->edd);
688 eet_data_descriptor_free(history->edd_list);
692 elm_genlist_item_class_free(itc);
697 ecore_file_shutdown();