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