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