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