history: start/stop time update poller when needed.
[profile/ivi/lemolo.git] / dialer / history.c
1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 #include <Elementary.h>
5 #include <Eet.h>
6 #include <Eina.h>
7 #include <time.h>
8 #include <limits.h>
9 #include <string.h>
10
11 #include "ofono.h"
12 #include "log.h"
13 #include "util.h"
14 #include "gui.h"
15
16 #ifndef EET_COMPRESSION_DEFAULT
17 #define EET_COMPRESSION_DEFAULT 1
18 #endif
19
20 #define HISTORY_ENTRY "history"
21
22 typedef struct _Call_Info_List {
23         Eina_List *list;
24         Eina_Bool dirty;
25 } Call_Info_List;
26
27 typedef struct _History {
28         char *path, *bkp;
29         Eet_Data_Descriptor *edd;
30         Eet_Data_Descriptor *edd_list;
31         Call_Info_List *calls;
32         Elm_Genlist_Item_Class *itc;
33         Evas_Object *self;
34         Evas_Object *genlist_all, *genlist_missed;
35         Ecore_Poller *updater;
36         double last_update;
37 } History;
38
39 typedef struct _Call_Info {
40         long long start_time;
41         long long end_time;
42         long long creation_time; /* not in edd */
43         const char *line_id;
44         const char *name;
45         Eina_Bool completed;
46         Eina_Bool incoming;
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 */
50 } Call_Info;
51
52 static OFono_Callback_List_Call_Node *callback_node_call_removed = NULL;
53 static OFono_Callback_List_Call_Node *callback_node_call_changed = NULL;
54
55 static Eina_Bool _history_time_updater(void *data)
56 {
57         History *ctx = data;
58         Elm_Object_Item *it;
59         long long update_threshold = time(NULL) - WEEK - DAY;
60         double now = ecore_loop_time_get();
61
62         if (now - ctx->last_update < 1.0)
63                 return EINA_TRUE;
64         ctx->last_update = now;
65
66         it = elm_genlist_first_item_get(ctx->genlist_all);
67         for (; it != NULL; it = elm_genlist_item_next_get(it)) {
68                 const Call_Info *call_info = elm_object_item_data_get(it);
69                 if (call_info->start_time < update_threshold)
70                         continue;
71                 elm_genlist_item_update(it);
72         }
73
74         it = elm_genlist_first_item_get(ctx->genlist_missed);
75         for (; it != NULL; it = elm_genlist_item_next_get(it)) {
76                 const Call_Info *call_info = elm_object_item_data_get(it);
77                 if (call_info->start_time < update_threshold)
78                         continue;
79                 elm_genlist_item_update(it);
80         }
81
82         return EINA_TRUE;
83 }
84
85 static void _history_time_updater_stop(History *history)
86 {
87         Evas *e = evas_object_evas_get(history->self);
88         Eina_Bool win_focused = evas_focus_state_get(e);
89         Eina_Bool obj_visible = evas_object_visible_get(history->self);
90
91         DBG("poller %p, win_focused=%hhu, obj_visible=%hhu",
92                 history->updater, win_focused, obj_visible);
93         if (!history->updater)
94                 return;
95         if (win_focused && obj_visible)
96                 return;
97
98         DBG("delete poller %p", history->updater);
99         ecore_poller_del(history->updater);
100         history->updater = NULL;
101 }
102
103 static void _history_time_updater_start(History *history)
104 {
105         Evas *e = evas_object_evas_get(history->self);
106         Eina_Bool win_focused = evas_focus_state_get(e);
107         Eina_Bool obj_visible = evas_object_visible_get(history->self);
108
109         DBG("poller %p, win_focused=%hhu, obj_visible=%hhu",
110                 history->updater, win_focused, obj_visible);
111         if (history->updater)
112                 return;
113         if ((!win_focused) || (!obj_visible))
114                 return;
115
116         DBG("start poller");
117         /* ECORE_POLLER_CORE is 1/8th of second. */
118         history->updater = ecore_poller_add(ECORE_POLLER_CORE, 8 * 60,
119                                                 _history_time_updater,
120                                                 history);
121         _history_time_updater(history);
122 }
123
124 static Call_Info *_history_call_info_search(const History *history,
125                                                 const OFono_Call *call)
126 {
127         Call_Info *call_info;
128         Eina_List *l;
129         long long t = ofono_call_full_start_time_get(call);
130         const char *line_id = ofono_call_line_id_get(call); /* stringshare */
131
132         EINA_LIST_FOREACH(history->calls->list, l, call_info) {
133                 if (call_info->call == call)
134                         return call_info;
135                 else if (!call_info->call) {
136                         if ((t > 0) && (call_info->start_time == t) &&
137                                 (line_id == call_info->line_id)) {
138                                 DBG("associated existing log %p %s (%lld) with "
139                                         "call %p %s (%lld)",
140                                         call_info,
141                                         call_info->line_id,
142                                         call_info->start_time,
143                                         call, line_id, t);
144                                 call_info->call = call;
145                                 return call_info;
146                         }
147                 }
148         }
149
150         return NULL;
151 }
152
153 static Eina_Bool _history_call_info_update(Call_Info *call_info)
154 {
155         OFono_Call_State state;
156
157         EINA_SAFETY_ON_NULL_RETURN_VAL(call_info->call, EINA_FALSE);
158         state = ofono_call_state_get(call_info->call);
159
160         if (state == OFONO_CALL_STATE_INCOMING ||
161                 state == OFONO_CALL_STATE_WAITING) {
162                 if (!call_info->incoming) {
163                         call_info->incoming = EINA_TRUE;
164                         return EINA_TRUE;
165                 }
166         } else if (state == OFONO_CALL_STATE_DIALING ||
167                         state == OFONO_CALL_STATE_ALERTING) {
168                 if (!call_info->incoming) {
169                         call_info->incoming = EINA_FALSE;
170                         return EINA_TRUE;
171                 }
172         } else if (state == OFONO_CALL_STATE_ACTIVE ||
173                         state == OFONO_CALL_STATE_HELD) {
174                 if (!call_info->completed) {
175                         call_info->start_time = ofono_call_full_start_time_get
176                                 (call_info->call);
177                         if (call_info->start_time == 0)
178                                 call_info->start_time = call_info->creation_time;
179
180                         call_info->completed = EINA_TRUE;
181                         return EINA_TRUE;
182                 }
183         }
184
185         return EINA_FALSE;
186 }
187
188 static void _dial_reply(void *data, OFono_Error err,
189                         OFono_Call *call __UNUSED__)
190 {
191         const char *number = data;
192
193         if (err != OFONO_ERROR_NONE) {
194                 char buf[1024];
195                 snprintf(buf, sizeof(buf), "Could not call: %s", number);
196                 gui_simple_popup("Error", buf);
197         }
198 }
199
200 static void _on_item_clicked(void *data, Evas_Object *obj __UNUSED__,
201                                 void *event_info)
202 {
203         Elm_Object_Item *it = event_info;
204         const char *number = data;
205
206         INF("call %s", number);
207         ofono_dial(number, NULL, _dial_reply, number);
208         elm_genlist_item_selected_set(it, EINA_FALSE);
209 }
210
211 static void _history_call_changed(void *data, OFono_Call *call)
212 {
213         History *history = data;
214         const char *line_id = ofono_call_line_id_get(call);
215         Call_Info *call_info;
216         OFono_Call_State state = ofono_call_state_get(call);
217
218         call_info = _history_call_info_search(history, call);
219         DBG("call=%p, id=%s, state=%d, completed=%d, incoming=%d, info=%p",
220                 call, line_id, state,
221                 call_info ? call_info->completed : EINA_FALSE,
222                 call_info ? call_info->incoming : EINA_FALSE,
223                 call_info);
224
225         if (call_info)
226                 goto end;
227
228         call_info = calloc(1, sizeof(Call_Info));
229         EINA_SAFETY_ON_NULL_RETURN(call_info);
230
231         call_info->call = call;
232         call_info->start_time = ofono_call_full_start_time_get(call);
233         call_info->creation_time = time(NULL);
234         call_info->line_id = eina_stringshare_add(line_id);
235         call_info->name = eina_stringshare_add(ofono_call_name_get(call));
236         history->calls->list =
237                 eina_list_prepend(history->calls->list, call_info);
238         history->calls->dirty = EINA_TRUE;
239
240 end:
241         if (_history_call_info_update(call_info))
242                 history->calls->dirty = EINA_TRUE;
243 }
244
245 static void _history_call_log_save(History *history)
246 {
247         Eet_File *efile;
248
249         EINA_SAFETY_ON_NULL_RETURN(history->calls);
250         DBG("save history (%u calls, dirty: %d) to %s",
251                 eina_list_count(history->calls->list), history->calls->dirty,
252                 history->path);
253
254         ecore_file_unlink(history->bkp);
255         ecore_file_mv(history->path, history->bkp);
256         efile = eet_open(history->path, EET_FILE_MODE_WRITE);
257         EINA_SAFETY_ON_NULL_RETURN(efile);
258         if (!(eet_data_write(efile,
259                                 history->edd_list, HISTORY_ENTRY,
260                                 history->calls, EET_COMPRESSION_DEFAULT)))
261                 ERR("Could in the history log file");
262
263         eet_close(efile);
264 }
265
266 static void _history_call_removed(void *data, OFono_Call *call)
267 {
268         Elm_Object_Item *it;
269         History *history = data;
270         const char *line_id = ofono_call_line_id_get(call);
271         Call_Info *call_info;
272         time_t start;
273         char *tm;
274
275         call_info = _history_call_info_search(history, call);
276         DBG("call=%p, id=%s, info=%p", call, line_id, call_info);
277         EINA_SAFETY_ON_NULL_RETURN(call_info);
278
279         if (call_info->start_time == 0)
280                 call_info->start_time = call_info->creation_time;
281
282         start = call_info->start_time;
283         tm = ctime(&start);
284
285         call_info->end_time = time(NULL);
286         call_info->call = NULL;
287
288         if (call_info->completed)
289                 INF("Call end:  %s at %s", line_id, tm);
290         else {
291                 if (!call_info->incoming)
292                         INF("Not answered: %s at %s", line_id, tm);
293                 else {
294                         INF("Missed: %s at %s", line_id, tm);
295                         if (call_info->it_missed)
296                                 elm_genlist_item_update(call_info->it_missed);
297                         else {
298                                 it = elm_genlist_item_prepend
299                                         (history->genlist_missed,
300                                                 history->itc,
301                                                 call_info, NULL,
302                                                 ELM_GENLIST_ITEM_NONE,
303                                                 _on_item_clicked,
304                                                 call_info->line_id);
305                                 elm_genlist_item_show
306                                         (it, ELM_GENLIST_ITEM_SCROLLTO_IN);
307                                 call_info->it_missed = it;
308                         }
309                 }
310         }
311
312         history->calls->dirty = EINA_TRUE;
313         _history_call_log_save(history);
314
315         if (call_info->it_all)
316                 elm_genlist_item_update(call_info->it_all);
317         else {
318                 it = elm_genlist_item_prepend(history->genlist_all,
319                                                 history->itc,
320                                                 call_info, NULL,
321                                                 ELM_GENLIST_ITEM_NONE,
322                                                 _on_item_clicked,
323                                                 call_info->line_id);
324                 elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_IN);
325                 call_info->it_all = it;
326         }
327 }
328
329 static void _call_info_free(Call_Info *call_info)
330 {
331         eina_stringshare_del(call_info->line_id);
332         eina_stringshare_del(call_info->name);
333         free(call_info);
334 }
335
336 static void _on_del(void *data, Evas *e __UNUSED__,
337                         Evas_Object *obj __UNUSED__, void *event __UNUSED__)
338 {
339         History *history = data;
340         Call_Info *call_info;
341
342         if (history->updater)
343                 ecore_poller_del(history->updater);
344
345         if (history->calls->dirty)
346                 _history_call_log_save(history);
347
348         ofono_call_removed_cb_del(callback_node_call_removed);
349         ofono_call_changed_cb_del(callback_node_call_changed);
350         eet_data_descriptor_free(history->edd);
351         eet_data_descriptor_free(history->edd_list);
352         EINA_LIST_FREE(history->calls->list, call_info)
353                 _call_info_free(call_info);
354         free(history->calls);
355         elm_genlist_item_class_free(history->itc);
356         free(history->path);
357         free(history->bkp);
358         free(history);
359         ecore_file_shutdown();
360         eet_shutdown();
361 }
362
363 static void _on_hide(void *data, Evas *e __UNUSED__,
364                         Evas_Object *obj __UNUSED__, void *event __UNUSED__)
365 {
366         History *history = data;
367         DBG("history became hidden");
368         _history_time_updater_stop(history);
369 }
370
371 static void _on_show(void *data, Evas *e __UNUSED__,
372                         Evas_Object *obj __UNUSED__, void *event __UNUSED__)
373 {
374         History *history = data;
375         DBG("history became visible");
376         _history_time_updater_start(history);
377 }
378
379 static void _on_win_focus_out(void *data, Evas *e __UNUSED__,
380                                 void *event_info __UNUSED__)
381 {
382         History *history = data;
383         DBG("window is unfocused");
384         _history_time_updater_stop(history);
385 }
386
387 static void _on_win_focus_in(void *data, Evas *e __UNUSED__,
388                                 void *event_info __UNUSED__)
389 {
390         History *history = data;
391         DBG("window is focused");
392         _history_time_updater_start(history);
393 }
394
395 static void _history_call_info_descriptor_init(Eet_Data_Descriptor **edd,
396                                                 Eet_Data_Descriptor **edd_list)
397 {
398         Eet_Data_Descriptor_Class eddc;
399
400         EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Call_Info);
401         *edd = eet_data_descriptor_stream_new(&eddc);
402
403         EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Call_Info_List);
404         *edd_list = eet_data_descriptor_stream_new(&eddc);
405
406         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
407                                         "completed", completed, EET_T_UCHAR);
408         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
409                                         "incoming", incoming, EET_T_UCHAR);
410
411         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
412                                         "start_time", start_time, EET_T_LONG_LONG);
413         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
414                                         "end_time", end_time, EET_T_LONG_LONG);
415         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
416                                         "line_id", line_id, EET_T_STRING);
417         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
418                                         "name", name, EET_T_STRING);
419
420         EET_DATA_DESCRIPTOR_ADD_LIST(*edd_list, Call_Info_List, "list", list,
421                                         *edd);
422 }
423
424 static void _history_call_log_read(History *history)
425 {
426         Call_Info *call_info;
427         Eina_List *l;
428         Eet_File *efile;
429         Call_Info_List *calls = NULL;
430         Elm_Object_Item *it;
431
432         efile = eet_open(history->path, EET_FILE_MODE_READ);
433
434         if (efile) {
435                 calls = eet_data_read(efile, history->edd_list, HISTORY_ENTRY);
436                 eet_close(efile);
437         }
438
439         if (!calls) {
440                 efile = eet_open(history->bkp, EET_FILE_MODE_READ);
441                 if (efile) {
442                         calls = eet_data_read(efile, history->edd_list,
443                                                 HISTORY_ENTRY);
444                         eet_close(efile);
445                 }
446         }
447
448         if (!calls)
449                 calls = calloc(1, sizeof(Call_Info_List));
450
451         history->calls = calls;
452         EINA_SAFETY_ON_NULL_RETURN(history->calls);
453
454         EINA_LIST_FOREACH(history->calls->list, l, call_info) {
455                 it = elm_genlist_item_append(history->genlist_all,
456                                                 history->itc,
457                                                 call_info, NULL,
458                                                 ELM_GENLIST_ITEM_NONE,
459                                                 _on_item_clicked,
460                                                 call_info->line_id);
461                 call_info->it_all = it;
462
463                 if (call_info->completed)
464                         continue;
465
466                 it = elm_genlist_item_append(history->genlist_missed,
467                                                 history->itc, call_info, NULL,
468                                                 ELM_GENLIST_ITEM_NONE,
469                                                 _on_item_clicked,
470                                                 call_info->line_id);
471                 call_info->it_missed = it;
472         }
473
474         it = elm_genlist_first_item_get(history->genlist_all);
475         if (it)
476                 elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_TOP);
477
478         it = elm_genlist_first_item_get(history->genlist_missed);
479         if (it)
480                 elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_TOP);
481 }
482
483 static char *_item_label_get(void *data, Evas_Object *obj __UNUSED__,
484                                 const char *part)
485 {
486         Call_Info *call_info = data;
487
488         if (strncmp(part, "text.call", strlen("text.call")))
489                 return NULL;
490
491         part += strlen("text.call.");
492
493         if (!strcmp(part, "name")) {
494                 if (!call_info->name || call_info->name[0] == '\0')
495                         return phone_format(call_info->line_id);
496                 return strdup(call_info->name);
497         }
498
499         if (!strcmp(part, "time")) {
500                 if ((call_info->completed) && (call_info->end_time))
501                         return date_format(call_info->end_time);
502                 return date_format(call_info->start_time);
503         }
504
505         /* TODO: Fetch phone type from contacts information*/
506         if (!strcmp(part, "type"))
507                 return strdup("TODO:TELEPHONE TYPE");
508
509         ERR("Unexpected text part: %s", part);
510         return NULL;
511 }
512
513
514 static Eina_Bool _item_state_get(void *data, Evas_Object *obj __UNUSED__,
515                                         const char  *part)
516 {
517         Call_Info *call_info = data;
518
519         if (!strcmp(part, "missed"))
520                 return !call_info->completed;
521         else if (!strcmp(part, "completed"))
522                 return call_info->completed;
523         else if (!strcmp(part, "outgoing"))
524                 return !call_info->incoming;
525         else if (!strcmp(part, "incoming"))
526                 return call_info->incoming;
527
528         ERR("Unexpected state part: %s", part);
529         return EINA_FALSE;
530 }
531
532 static void _on_clicked(void *data __UNUSED__, Evas_Object *obj __UNUSED__,
533                         const char *emission, const char *source __UNUSED__)
534 {
535         EINA_SAFETY_ON_NULL_RETURN(emission);
536         emission += strlen("clicked,");
537
538         if (!strcmp(emission, "all"))
539                 elm_object_signal_emit(obj, "show,all", "gui");
540         else
541                 elm_object_signal_emit(obj, "show,missed", "gui");
542 }
543
544 static void _on_more_clicked(void *data __UNUSED__, Evas_Object *obj __UNUSED__,
545                                 const char *emission __UNUSED__,
546                                 const char *source __UNUSED__)
547 {
548         DBG("TODO");
549 }
550
551 static Evas_Object *_item_content_get(void *data __UNUSED__, Evas_Object *obj,
552                                         const char *part __UNUSED__)
553 {
554         Evas_Object *btn;
555
556         btn = gui_layout_add(obj, "history/img");
557         EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL);
558         elm_object_signal_callback_add(btn, "clicked,more", "gui",
559                                         _on_more_clicked, NULL);
560         evas_object_propagate_events_set(btn, EINA_FALSE);
561
562         return btn;
563 }
564
565 Evas_Object *history_add(Evas_Object *parent)
566 {
567         int r;
568         History *history;
569         Evas *e;
570         const char *config_path;
571         char *path, base_dir[PATH_MAX];
572         Elm_Genlist_Item_Class *itc;
573         Evas_Object *obj, *genlist_all, *genlist_missed;
574
575         eet_init();
576         ecore_file_init();
577         history = calloc(1, sizeof(History));
578         EINA_SAFETY_ON_NULL_RETURN_VAL(history, NULL);
579
580         history->self = obj = gui_layout_add(parent, "history_bg");
581         EINA_SAFETY_ON_NULL_GOTO(obj, err_layout);
582
583         genlist_all = elm_genlist_add(obj);
584         EINA_SAFETY_ON_NULL_GOTO(genlist_all, err_object_new);
585         elm_object_style_set(genlist_all, "history");
586
587         genlist_missed = elm_genlist_add(obj);
588         EINA_SAFETY_ON_NULL_GOTO(genlist_missed, err_object_new);
589         elm_object_style_set(genlist_missed, "history");
590
591         itc = elm_genlist_item_class_new();
592         EINA_SAFETY_ON_NULL_GOTO(itc, err_object_new);
593         itc->item_style = "history";
594         itc->func.text_get = _item_label_get;
595         itc->func.content_get = _item_content_get;
596         itc->func.state_get = _item_state_get;
597         itc->func.del = NULL;
598         history->genlist_all = genlist_all;
599         history->genlist_missed = genlist_missed;
600         history->itc = itc;
601
602         elm_object_part_content_set(obj, "elm.swallow.all", genlist_all);
603         elm_object_part_content_set(obj, "elm.swallow.missed", genlist_missed);
604         elm_object_signal_emit(obj, "show,all", "gui");
605         elm_object_signal_callback_add(obj, "clicked,*", "gui",
606                                         _on_clicked, NULL);
607
608         config_path = efreet_config_home_get();
609         snprintf(base_dir, sizeof(base_dir), "%s/%s", config_path,
610                         PACKAGE_NAME);
611         ecore_file_mkpath(base_dir);
612         r = asprintf(&path,  "%s/%s/history.eet", config_path, PACKAGE_NAME);
613
614         if (r < 0)
615                 goto err_item_class;
616
617         history->path = path;
618         r = asprintf(&path,  "%s/%s/history.eet.bkp", config_path,
619                         PACKAGE_NAME);
620
621         if (r < 0)
622                 goto err_path;
623
624         history->bkp = path;
625
626         _history_call_info_descriptor_init(&history->edd, &history->edd_list);
627         _history_call_log_read(history);
628         EINA_SAFETY_ON_NULL_GOTO(history->calls, err_log_read);
629         evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL, _on_del,
630                                         history);
631         evas_object_event_callback_add(obj, EVAS_CALLBACK_HIDE, _on_hide,
632                                         history);
633         evas_object_event_callback_add(obj, EVAS_CALLBACK_SHOW, _on_show,
634                                         history);
635
636         e = evas_object_evas_get(obj);
637         evas_event_callback_add(e, EVAS_CALLBACK_CANVAS_FOCUS_OUT,
638                                 _on_win_focus_out, history);
639         evas_event_callback_add(e, EVAS_CALLBACK_CANVAS_FOCUS_IN,
640                                 _on_win_focus_in, history);
641
642         callback_node_call_changed =
643                 ofono_call_changed_cb_add(_history_call_changed, history);
644         callback_node_call_removed =
645                 ofono_call_removed_cb_add(_history_call_removed, history);
646
647         return obj;
648
649 err_log_read:
650         free(history->bkp);
651         eet_data_descriptor_free(history->edd);
652         eet_data_descriptor_free(history->edd_list);
653 err_path:
654         free(history->path);
655 err_item_class:
656         elm_genlist_item_class_free(itc);
657 err_object_new:
658         free(obj);
659 err_layout:
660         free(history);
661         ecore_file_shutdown();
662         eet_shutdown();
663         return NULL;
664 }