history: use call start time if available
[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 *genlist_all, *genlist_missed;
34         Ecore_Poller *updater;
35 } History;
36
37 typedef struct _Call_Info {
38         long long start_time;
39         long long end_time;
40         long long creation_time; /* not in edd */
41         const char *line_id;
42         const char *name;
43         Eina_Bool completed;
44         Eina_Bool incoming;
45         const OFono_Call *call; /* not in edd */
46 } Call_Info;
47
48 static OFono_Callback_List_Call_Node *callback_node_call_removed = NULL;
49 static OFono_Callback_List_Call_Node *callback_node_call_changed = NULL;
50
51 static Eina_Bool _history_time_updater(void *data)
52 {
53         History *ctx = data;
54         Elm_Object_Item *it;
55
56         it = elm_genlist_first_item_get(ctx->genlist_all);
57         for (; it != NULL; it = elm_genlist_item_next_get(it))
58                 elm_genlist_item_update(it);
59
60         it = elm_genlist_first_item_get(ctx->genlist_missed);
61         for (; it != NULL; it = elm_genlist_item_next_get(it))
62                 elm_genlist_item_update(it);
63
64         return EINA_TRUE;
65 }
66
67 static Call_Info *_history_call_info_search(const History *history,
68                                                 const OFono_Call *call)
69 {
70         Call_Info *call_info;
71         Eina_List *l;
72
73         EINA_LIST_FOREACH(history->calls->list, l, call_info)
74                 if (call_info->call == call)
75                         return call_info;
76
77         return NULL;
78 }
79
80 static Eina_Bool _history_call_info_update(Call_Info *call_info)
81 {
82         OFono_Call_State state;
83
84         EINA_SAFETY_ON_NULL_RETURN_VAL(call_info->call, EINA_FALSE);
85         state = ofono_call_state_get(call_info->call);
86
87         if (state == OFONO_CALL_STATE_INCOMING ||
88                 state == OFONO_CALL_STATE_WAITING) {
89                 if (!call_info->incoming) {
90                         call_info->incoming = EINA_TRUE;
91                         return EINA_TRUE;
92                 }
93         } else if (state == OFONO_CALL_STATE_DIALING ||
94                         state == OFONO_CALL_STATE_ALERTING) {
95                 if (!call_info->incoming) {
96                         call_info->incoming = EINA_FALSE;
97                         return EINA_TRUE;
98                 }
99         } else if (state == OFONO_CALL_STATE_ACTIVE ||
100                         state == OFONO_CALL_STATE_HELD) {
101                 if (!call_info->completed) {
102                         call_info->start_time = ofono_call_full_start_time_get
103                                 (call_info->call);
104                         if (call_info->start_time == 0)
105                                 call_info->start_time = call_info->creation_time;
106
107                         call_info->completed = EINA_TRUE;
108                         return EINA_TRUE;
109                 }
110         }
111
112         return EINA_FALSE;
113 }
114
115 static void _on_item_clicked(void *data, Evas_Object *obj __UNUSED__,
116                                 void *event_info)
117 {
118         Elm_Object_Item *it = event_info;
119         const char *number = data;
120         gui_number_set(number, EINA_TRUE);
121         elm_genlist_item_selected_set(it, EINA_FALSE);
122 }
123
124 static void _history_call_changed(void *data, OFono_Call *call)
125 {
126         History *history = data;
127         const char *line_id = ofono_call_line_id_get(call);
128         Call_Info *call_info;
129         OFono_Call_State state = ofono_call_state_get(call);
130
131         call_info = _history_call_info_search(history, call);
132         DBG("call=%p, id=%s, state=%d, completed=%d, incoming=%d, info=%p",
133                 call, line_id, state,
134                 call_info ? call_info->completed : EINA_FALSE,
135                 call_info ? call_info->incoming : EINA_FALSE,
136                 call_info);
137
138         if (call_info)
139                 goto end;
140
141         call_info = calloc(1, sizeof(Call_Info));
142         EINA_SAFETY_ON_NULL_RETURN(call_info);
143
144         call_info->call = call;
145         call_info->start_time = ofono_call_full_start_time_get(call);
146         call_info->creation_time = time(NULL);
147         call_info->line_id = eina_stringshare_add(line_id);
148         call_info->name = eina_stringshare_add(ofono_call_name_get(call));
149         history->calls->list =
150                 eina_list_prepend(history->calls->list, call_info);
151         history->calls->dirty = EINA_TRUE;
152
153 end:
154         if (_history_call_info_update(call_info))
155                 history->calls->dirty = EINA_TRUE;
156 }
157
158 static void _history_call_log_save(History *history)
159 {
160         Eet_File *efile;
161
162         EINA_SAFETY_ON_NULL_RETURN(history->calls);
163         DBG("save history (%u calls, dirty: %d) to %s",
164                 eina_list_count(history->calls->list), history->calls->dirty,
165                 history->path);
166
167         ecore_file_unlink(history->bkp);
168         ecore_file_mv(history->path, history->bkp);
169         efile = eet_open(history->path, EET_FILE_MODE_WRITE);
170         EINA_SAFETY_ON_NULL_RETURN(efile);
171         if (!(eet_data_write(efile,
172                                 history->edd_list, HISTORY_ENTRY,
173                                 history->calls, EET_COMPRESSION_DEFAULT)))
174                 ERR("Could in the history log file");
175
176         eet_close(efile);
177 }
178
179 static void _history_call_removed(void *data, OFono_Call *call)
180 {
181         Elm_Object_Item *it;
182         History *history = data;
183         const char *line_id = ofono_call_line_id_get(call);
184         Call_Info *call_info;
185         time_t start;
186         char *tm;
187
188         call_info = _history_call_info_search(history, call);
189         DBG("call=%p, id=%s, info=%p", call, line_id, call_info);
190         EINA_SAFETY_ON_NULL_RETURN(call_info);
191
192         if (call_info->start_time == 0)
193                 call_info->start_time = call_info->creation_time;
194
195         start = call_info->start_time;
196         tm = ctime(&start);
197
198         if (call_info->completed)
199                 INF("Call end:  %s at %s", line_id, tm);
200         else {
201                 if (!call_info->incoming)
202                         INF("Not answered: %s at %s", line_id, tm);
203                 else {
204                         INF("Missed: %s at %s", line_id, tm);
205                         it = elm_genlist_item_prepend(history->genlist_missed,
206                                                         history->itc,
207                                                         call_info, NULL,
208                                                         ELM_GENLIST_ITEM_NONE,
209                                                         _on_item_clicked,
210                                                         call_info->line_id);
211                         elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_IN);
212                 }
213         }
214
215         call_info->end_time = time(NULL);
216         call_info->call = NULL;
217         history->calls->dirty = EINA_TRUE;
218         _history_call_log_save(history);
219
220         it = elm_genlist_item_prepend(history->genlist_all, history->itc,
221                                         call_info, NULL, ELM_GENLIST_ITEM_NONE,
222                                         _on_item_clicked, call_info->line_id);
223         elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_IN);
224 }
225
226 static void _call_info_free(Call_Info *call_info)
227 {
228         eina_stringshare_del(call_info->line_id);
229         eina_stringshare_del(call_info->name);
230         free(call_info);
231 }
232
233 static void _on_del(void *data, Evas *e __UNUSED__,
234                         Evas_Object *obj __UNUSED__, void *event __UNUSED__)
235 {
236         History *history = data;
237         Call_Info *call_info;
238
239         ecore_poller_del(history->updater);
240
241         if (history->calls->dirty)
242                 _history_call_log_save(history);
243
244         ofono_call_removed_cb_del(callback_node_call_removed);
245         ofono_call_changed_cb_del(callback_node_call_changed);
246         eet_data_descriptor_free(history->edd);
247         eet_data_descriptor_free(history->edd_list);
248         EINA_LIST_FREE(history->calls->list, call_info)
249                 _call_info_free(call_info);
250         free(history->calls);
251         elm_genlist_item_class_free(history->itc);
252         free(history->path);
253         free(history->bkp);
254         free(history);
255         ecore_file_shutdown();
256         eet_shutdown();
257 }
258
259 static void _history_call_info_descriptor_init(Eet_Data_Descriptor **edd,
260                                                 Eet_Data_Descriptor **edd_list)
261 {
262         Eet_Data_Descriptor_Class eddc;
263
264         EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Call_Info);
265         *edd = eet_data_descriptor_stream_new(&eddc);
266
267         EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Call_Info_List);
268         *edd_list = eet_data_descriptor_stream_new(&eddc);
269
270         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
271                                         "completed", completed, EET_T_UCHAR);
272         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
273                                         "incoming", incoming, EET_T_UCHAR);
274
275         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
276                                         "start_time", start_time, EET_T_LONG_LONG);
277         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
278                                         "end_time", end_time, EET_T_LONG_LONG);
279         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
280                                         "line_id", line_id, EET_T_STRING);
281         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
282                                         "name", name, EET_T_STRING);
283
284         EET_DATA_DESCRIPTOR_ADD_LIST(*edd_list, Call_Info_List, "list", list,
285                                         *edd);
286 }
287
288 static void _history_call_log_read(History *history)
289 {
290         Call_Info *call_info;
291         Eina_List *l;
292         Eet_File *efile;
293         Call_Info_List *calls = NULL;
294         Elm_Object_Item *it;
295
296         efile = eet_open(history->path, EET_FILE_MODE_READ);
297
298         if (efile) {
299                 calls = eet_data_read(efile, history->edd_list, HISTORY_ENTRY);
300                 eet_close(efile);
301         }
302
303         if (!calls) {
304                 efile = eet_open(history->bkp, EET_FILE_MODE_READ);
305                 if (efile) {
306                         calls = eet_data_read(efile, history->edd_list,
307                                                 HISTORY_ENTRY);
308                         eet_close(efile);
309                 }
310         }
311
312         if (!calls)
313                 calls = calloc(1, sizeof(Call_Info_List));
314
315         history->calls = calls;
316         EINA_SAFETY_ON_NULL_RETURN(history->calls);
317
318         EINA_LIST_FOREACH(history->calls->list, l, call_info) {
319                 elm_genlist_item_append(history->genlist_all, history->itc,
320                                         call_info, NULL, ELM_GENLIST_ITEM_NONE,
321                                         _on_item_clicked, call_info->line_id);
322                 if (!call_info->completed)
323                         elm_genlist_item_append(history->genlist_missed,
324                                                 history->itc, call_info, NULL,
325                                                 ELM_GENLIST_ITEM_NONE,
326                                                 _on_item_clicked,
327                                                 call_info->line_id);
328         }
329
330         it = elm_genlist_first_item_get(history->genlist_all);
331         if (it)
332                 elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_TOP);
333
334         it = elm_genlist_first_item_get(history->genlist_missed);
335         if (it)
336                 elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_TOP);
337 }
338
339 static char *_item_label_get(void *data, Evas_Object *obj __UNUSED__,
340                                 const char *part)
341 {
342         Call_Info *call_info = data;
343
344         if (strncmp(part, "text.call", strlen("text.call")))
345                 return NULL;
346
347         part += strlen("text.call.");
348
349         if (!strcmp(part, "name")) {
350                 if (!call_info->name || call_info->name[0] == '\0')
351                         return phone_format(call_info->line_id);
352                 return strdup(call_info->name);
353         }
354
355         if (!strcmp(part, "time")) {
356                 if ((call_info->completed) && (call_info->end_time))
357                         return date_format(call_info->end_time);
358                 return date_format(call_info->start_time);
359         }
360
361         /* TODO: Fetch phone type from contacts information*/
362         if (!strcmp(part, "type"))
363                 return strdup("TODO:TELEPHONE TYPE");
364
365         ERR("Unexpected text part: %s", part);
366         return NULL;
367 }
368
369
370 static Eina_Bool _item_state_get(void *data, Evas_Object *obj __UNUSED__,
371                                         const char  *part)
372 {
373         Call_Info *call_info = data;
374
375         if (!strcmp(part, "missed"))
376                 return !call_info->completed;
377         else if (!strcmp(part, "completed"))
378                 return call_info->completed;
379         else if (!strcmp(part, "outgoing"))
380                 return !call_info->incoming;
381         else if (!strcmp(part, "incoming"))
382                 return call_info->incoming;
383
384         ERR("Unexpected state part: %s", part);
385         return EINA_FALSE;
386 }
387
388 static void _on_clicked(void *data __UNUSED__, Evas_Object *obj __UNUSED__,
389                         const char *emission, const char *source __UNUSED__)
390 {
391         EINA_SAFETY_ON_NULL_RETURN(emission);
392         emission += strlen("clicked,");
393
394         if (!strcmp(emission, "all"))
395                 elm_object_signal_emit(obj, "show,all", "gui");
396         else
397                 elm_object_signal_emit(obj, "show,missed", "gui");
398 }
399
400 static void _on_more_clicked(void *data __UNUSED__, Evas_Object *obj __UNUSED__,
401                                 const char *emission __UNUSED__,
402                                 const char *source __UNUSED__)
403 {
404         DBG("TODO");
405 }
406
407 static Evas_Object *_item_content_get(void *data __UNUSED__, Evas_Object *obj,
408                                         const char *part __UNUSED__)
409 {
410         Evas_Object *btn;
411
412         btn = gui_layout_add(obj, "history/img");
413         EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL);
414         elm_object_signal_callback_add(btn, "clicked,more", "gui",
415                                         _on_more_clicked, NULL);
416         evas_object_propagate_events_set(btn, EINA_FALSE);
417
418         return btn;
419 }
420
421 Evas_Object *history_add(Evas_Object *parent)
422 {
423         int r;
424         History *history;
425         const char *config_path;
426         char *path, base_dir[PATH_MAX];
427         Elm_Genlist_Item_Class *itc;
428         Evas_Object *obj, *genlist_all, *genlist_missed;
429
430         eet_init();
431         ecore_file_init();
432         history = calloc(1, sizeof(History));
433         EINA_SAFETY_ON_NULL_RETURN_VAL(history, NULL);
434
435         obj = gui_layout_add(parent, "history_bg");
436         EINA_SAFETY_ON_NULL_GOTO(obj, err_layout);
437
438         genlist_all = elm_genlist_add(obj);
439         EINA_SAFETY_ON_NULL_GOTO(genlist_all, err_object_new);
440         elm_object_style_set(genlist_all, "history");
441
442         genlist_missed = elm_genlist_add(obj);
443         EINA_SAFETY_ON_NULL_GOTO(genlist_missed, err_object_new);
444         elm_object_style_set(genlist_missed, "history");
445
446         itc = elm_genlist_item_class_new();
447         EINA_SAFETY_ON_NULL_GOTO(itc, err_object_new);
448         itc->item_style = "history";
449         itc->func.text_get = _item_label_get;
450         itc->func.content_get = _item_content_get;
451         itc->func.state_get = _item_state_get;
452         itc->func.del = NULL;
453         history->genlist_all = genlist_all;
454         history->genlist_missed = genlist_missed;
455         history->itc = itc;
456
457         elm_object_part_content_set(obj, "elm.swallow.all", genlist_all);
458         elm_object_part_content_set(obj, "elm.swallow.missed", genlist_missed);
459         elm_object_signal_emit(obj, "show,all", "gui");
460         elm_object_signal_callback_add(obj, "clicked,*", "gui",
461                                         _on_clicked, NULL);
462
463         config_path = efreet_config_home_get();
464         snprintf(base_dir, sizeof(base_dir), "%s/%s", config_path,
465                         PACKAGE_NAME);
466         ecore_file_mkpath(base_dir);
467         r = asprintf(&path,  "%s/%s/history.eet", config_path, PACKAGE_NAME);
468
469         if (r < 0)
470                 goto err_item_class;
471
472         history->path = path;
473         r = asprintf(&path,  "%s/%s/history.eet.bkp", config_path,
474                         PACKAGE_NAME);
475
476         if (r < 0)
477                 goto err_path;
478
479         history->bkp = path;
480
481         _history_call_info_descriptor_init(&history->edd, &history->edd_list);
482         _history_call_log_read(history);
483         EINA_SAFETY_ON_NULL_GOTO(history->calls, err_log_read);
484         evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL, _on_del,
485                                         history);
486         callback_node_call_changed =
487                 ofono_call_changed_cb_add(_history_call_changed, history);
488         callback_node_call_removed =
489                 ofono_call_removed_cb_add(_history_call_removed, history);
490
491         /* ECORE_POLLER_CORE is 1/8th of second. */
492         history->updater = ecore_poller_add(ECORE_POLLER_CORE, 8 * 60,
493                                                 _history_time_updater,
494                                                 history);
495         return obj;
496
497 err_log_read:
498         free(history->bkp);
499         eet_data_descriptor_free(history->edd);
500         eet_data_descriptor_free(history->edd_list);
501 err_path:
502         free(history->path);
503 err_item_class:
504         elm_genlist_item_class_free(itc);
505 err_object_new:
506         free(obj);
507 err_layout:
508         free(history);
509         ecore_file_shutdown();
510         eet_shutdown();
511         return NULL;
512 }