Adding license and copyright info
[profile/ivi/lemolo.git] / dialer / keypad.c
1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 #include <Elementary.h>
5
6 #include "log.h"
7 #include "gui.h"
8 #include "ofono.h"
9 #include "ussd.h"
10 #include "util.h"
11 #include "simple-popup.h"
12
13 /* timeout before a popup is show for supplementary services.  It is
14  * not shown immediately as the call may fail as "not supported" in
15  * that case the dial will be done and we don't want a popup before
16  * it.
17  */
18 #define SS_POPUP_SHOW_TIMEOUT (0.5)
19
20 /* timeout to change keys into modified, like 0 -> + */
21 #define MOD_TIMEOUT (1.0)
22
23 /* timeouts to repeat key while pressed */
24 #define REP_TIMEOUT_INIT (0.3)
25 #define REP_TIMEOUT (0.2)
26
27 typedef struct _Keypad
28 {
29         Evas_Object *self;
30         Evas_Object *ss_popup;
31         Ecore_Timer *ss_popup_show_timeout;
32         OFono_Pending *ss_pending;
33         Eina_Strbuf *number;
34         const char *last;
35         Ecore_Timer *mod_timeout;
36         Ecore_Timer *rep_timeout;
37 } Keypad;
38
39 static void _number_display(Keypad *ctx)
40 {
41         const char *number = eina_strbuf_string_get(ctx->number);
42         char *s = phone_format(number);
43         const char *type;
44         if (!s) {
45                 elm_object_part_text_set(ctx->self, "elm.text.display", "");
46                 elm_object_part_text_set(ctx->self, "elm.text.contact-and-type",
47                                                 "");
48                 elm_object_part_text_set(ctx->self, "elm.text.contact", "");
49                 elm_object_part_text_set(ctx->self, "elm.text.phone.type", "");
50                 elm_object_signal_emit(ctx->self, "disable,save", "keypad");
51                 elm_object_signal_emit(ctx->self, "disable,backspace",
52                                         "keypad");
53                 elm_object_signal_emit(ctx->self, "hide,contact", "keypad");
54                 return;
55         }
56
57         Contact_Info *info = gui_contact_search(number, &type);
58         if (info) {
59                 const char *name = contact_info_full_name_get(info);
60                 char buf[1024];
61
62                 snprintf(buf, sizeof(buf), "%s - %s", name, type);
63
64                 elm_object_part_text_set(ctx->self, "elm.text.contact-and-type",
65                                                 buf);
66                 elm_object_part_text_set(ctx->self, "elm.text.contact", name);
67                 elm_object_part_text_set(ctx->self, "elm.text.phone.type", type);
68                 elm_object_signal_emit(ctx->self, "show,contact", "keypad");
69         } else {
70                 elm_object_signal_emit(ctx->self, "hide,contact", "keypad");
71                 elm_object_part_text_set(ctx->self, "elm.text.contact-and-type",
72                                                 "");
73                 elm_object_part_text_set(ctx->self, "elm.text.contact", "");
74                 elm_object_part_text_set(ctx->self, "elm.text.phone.type", "");
75         }
76
77         elm_object_part_text_set(ctx->self, "elm.text.display", s);
78         free(s);
79
80         elm_object_signal_emit(ctx->self, "enable,save", "keypad");
81         elm_object_signal_emit(ctx->self, "enable,backspace", "keypad");
82 }
83
84
85 static void _imei_show(void)
86 {
87         const char *imei = ofono_modem_serial_get();
88         gui_simple_popup("IMEI Request", imei ? imei : "No modem");
89         INF("Show IMEI: %s", imei);
90 }
91
92 static void _pin_reply(void *data, OFono_Error error)
93 {
94         const char *title = data;
95         const char *msg = ofono_error_message_get(error);
96
97         gui_simple_popup(title, msg);
98         if (error == OFONO_ERROR_NONE)
99                 INF("%s", title);
100         else
101                 ERR("%s", title);
102 }
103
104 static void _change_pin(const char *what, const char *old, const char *new)
105 {
106         DBG("ask change of %s from %s to %s", what, old, new);
107         ofono_modem_change_pin(what, old, new, _pin_reply, "PIN Change");
108 }
109
110 static void _reset_pin(const char *what, const char *old, const char *new)
111 {
112         DBG("ask reset of %s using %s to %s", what, old, new);
113         ofono_modem_reset_pin(what, old, new, _pin_reply, "PIN Reset");
114 }
115
116 static void _parse_pin(const char *str, char **old, char **new)
117 {
118         const char *p1, *p2, *p3;
119         int len1, len2, len3;
120
121         p1 = strchr(str, '*');
122         if (!p1)
123                 goto error;
124
125         p2 = strchr(p1 + 1, '*');
126         if (!p2)
127                 goto error;
128
129         p3 = strchr(p2 + 1, '#');
130         if (!p3)
131                 goto error;
132
133         len1 = p1 - str;
134         len2 = p2 - (p1 + 1);
135         len3 = p3 - (p2 + 1);
136
137         if ((len2 != len3) || (memcmp(p1 + 1, p2 + 1, len2) != 0)) {
138                 ERR("New PIN code check failed: '%.*s' '%.*s'",
139                         len2, p1 + 1,
140                         len3, p2 + 1);
141                 goto error;
142         }
143
144         *old = strndup(str, len1);
145         *new = strndup(p1 + 1, len2);
146
147         return;
148
149 error:
150         ERR("Invalid PIN change format: %s", str);
151 }
152
153 static Eina_Bool _handle_if_mmi(Keypad *ctx)
154 {
155         const char *str = eina_strbuf_string_get(ctx->number);
156         size_t len = eina_strbuf_length_get(ctx->number);
157
158         if (!str)
159                 return EINA_FALSE;
160
161         if (len < sizeof("*#06#") - 1)
162                 return EINA_FALSE;
163
164         if (str[0] != '*')
165                 return EINA_FALSE;
166         if (str[len - 1] != '#')
167                 return EINA_FALSE;
168
169         DBG("Possible MMI code: %s", str);
170
171         str++;
172         len -= 2;
173
174         if (strcmp(str, "#06#") == 0) {
175                 _imei_show();
176                 eina_strbuf_reset(ctx->number);
177                 _number_display(ctx);
178                 return EINA_TRUE;
179         } else if (strncmp(str, "*0", 2) == 0) {
180                 const char *what = NULL, *p = str + 2;
181                 char *old = NULL, *new = NULL;
182                 void (*action)(const char *, const char *, const char *) = NULL;
183
184                 if (*p == '4') {
185                         /* MMI codes to change PIN:
186                          *  - **04*OLD_PIN*NEW_PIN*NEW_PIN#
187                          *  - **042*OLD-PIN2*NEW_PIN2*NEW_PIN2#
188                          */
189                         p++;
190                         action = _change_pin;
191                         if (*p == '*') {
192                                 what = "pin";
193                                 p++;
194                         } else if (strncmp(p, "2*", 2) == 0) {
195                                 what = "pin2";
196                                 p += 2;
197                         }
198
199                         if (what)
200                                 _parse_pin(p, &old, &new);
201                 } else if (*p == '5') {
202                         /* MMI codes to reset PIN:
203                          *  - **05*PIN_UNBLOCKING_KEY*NEW_PIN*NEW_PIN#
204                          *  - **052*PIN2_UNBLOCKING_KEY*NEW_PIN2*NEW_PIN2#
205                          */
206                         p++;
207                         action = _reset_pin;
208                         if (*p == '*') {
209                                 what = "pin";
210                                 p++;
211                         } else if (strncmp(p, "2*", 2) == 0) {
212                                 what = "pin2";
213                                 p += 2;
214                         }
215
216                         if (what)
217                                 _parse_pin(p, &old, &new);
218                 }
219
220                 DBG("PIN management '%s' what=%s, old=%s, new=%s",
221                         str, what, old, new);
222                 if (action && what && old && new) {
223                         action(what, old, new);
224                         eina_strbuf_reset(ctx->number);
225                         _number_display(ctx);
226                 }
227
228                 free(old);
229                 free(new);
230                 return EINA_TRUE;
231         }
232
233         return EINA_FALSE;
234 }
235
236 static void _dial(Keypad *ctx)
237 {
238         const char *number = eina_strbuf_string_get(ctx->number);
239
240         INF("call %s", number);
241         gui_dial(number);
242         eina_stringshare_replace(&(ctx->last), number);
243         eina_strbuf_reset(ctx->number);
244         _number_display(ctx);
245 }
246
247 static void _ss_initiate_reply(void *data, OFono_Error err, const char *str)
248 {
249         Keypad *ctx = data;
250
251         DBG("e=%d, str=%s, ss_popup_show_timeout=%p, ss_popup=%p",
252                 err, str, ctx->ss_popup_show_timeout, ctx->ss_popup);
253
254         ctx->ss_pending = NULL;
255
256         if (ctx->ss_popup_show_timeout) {
257                 ecore_timer_del(ctx->ss_popup_show_timeout);
258                 ctx->ss_popup_show_timeout = NULL;
259         }
260
261         if ((err == OFONO_ERROR_NOT_RECOGNIZED) ||
262                 (err == OFONO_ERROR_OFFLINE) ||
263                 (err == OFONO_ERROR_INVALID_FORMAT)) {
264                 _dial(ctx);
265                 evas_object_del(ctx->ss_popup);
266         } else if (err != OFONO_ERROR_NONE) {
267                 char buf[256];
268
269                 /* no popup? then it was canceled */
270                 if (!ctx->ss_popup)
271                         return;
272
273                 snprintf(buf, sizeof(buf), "Could not complete.<br>Error: %s",
274                                 ofono_error_message_get(err));
275                 simple_popup_title_set(ctx->ss_popup, "Error");
276                 simple_popup_message_set(ctx->ss_popup, buf);
277                 simple_popup_button_dismiss_set(ctx->ss_popup);
278                 evas_object_show(ctx->ss_popup);
279         } else {
280                 evas_object_del(ctx->ss_popup);
281                 eina_strbuf_reset(ctx->number);
282                 _number_display(ctx);
283
284                 ussd_start(str);
285         }
286 }
287
288 static Eina_Bool _on_ss_popup_show_timeout(void *data)
289 {
290         Keypad *ctx = data;
291
292         if (ctx->ss_popup)
293                 evas_object_show(ctx->ss_popup);
294
295         ctx->ss_popup_show_timeout = NULL;
296         return EINA_FALSE;
297 }
298
299 static void _on_ss_popup_del(void *data, Evas *e __UNUSED__,
300                                 Evas_Object *o __UNUSED__,
301                                 void *event_info __UNUSED__)
302 {
303         Keypad *ctx = data;
304
305         ctx->ss_popup = NULL;
306
307         if (ctx->ss_popup_show_timeout) {
308                 ecore_timer_del(ctx->ss_popup_show_timeout);
309                 ctx->ss_popup_show_timeout = NULL;
310         }
311
312         if (ctx->ss_pending) {
313                 ofono_pending_cancel(ctx->ss_pending);
314                 ctx->ss_pending = NULL;
315         }
316 }
317
318 /* Procedure as ofono/doc/mmi-codes.txt:
319  * - send number to SupplementaryServices.Initiate()
320  * - if NotRecognized is returned, then forward VoiceCallManager.Dial()
321  */
322 static void _call(Keypad *ctx)
323 {
324         const char *number = eina_strbuf_string_get(ctx->number);
325         int len = eina_strbuf_length_get(ctx->number);
326
327         INF("calling %s...", number);
328
329         if (len < 1)
330                 return;
331
332         if (ctx->ss_popup)
333                 evas_object_del(ctx->ss_popup);
334
335         if (ctx->ss_pending)
336                 ofono_pending_cancel(ctx->ss_pending);
337
338         ctx->ss_popup = gui_simple_popup(NULL, NULL);
339         evas_object_event_callback_add(ctx->ss_popup, EVAS_CALLBACK_DEL,
340                                         _on_ss_popup_del, ctx);
341         evas_object_hide(ctx->ss_popup);
342         if (ctx->ss_popup_show_timeout)
343                 ecore_timer_del(ctx->ss_popup_show_timeout);
344         ctx->ss_popup_show_timeout = ecore_timer_add(
345                 SS_POPUP_SHOW_TIMEOUT, _on_ss_popup_show_timeout, ctx);
346
347         ctx->ss_pending = ofono_ss_initiate(number, _ss_initiate_reply, ctx);
348 }
349
350 static Eina_Bool _on_mod_timeout(void *data)
351 {
352         Keypad *ctx = data;
353         size_t len = eina_strbuf_length_get(ctx->number);
354
355         if (len > 0) {
356                 const char *str = eina_strbuf_string_get(ctx->number);
357                 if (str[len - 1] == '0') {
358                         eina_strbuf_remove(ctx->number, len - 1, len);
359                         eina_strbuf_append_char(ctx->number, '+');
360                         _number_display(ctx);
361                 }
362         }
363
364         ctx->mod_timeout = NULL;
365         return EINA_FALSE;
366 }
367
368 static Eina_Bool _on_rep_timeout(void *data)
369 {
370         Keypad *ctx = data;
371         size_t len = eina_strbuf_length_get(ctx->number);
372
373         if (len > 0) {
374                 eina_strbuf_remove(ctx->number, len - 1, len);
375                 _number_display(ctx);
376         }
377
378         if (len == 1) {
379                 ctx->rep_timeout = NULL;
380                 return EINA_FALSE;
381         }
382
383         ecore_timer_interval_set(ctx->rep_timeout, REP_TIMEOUT);
384         return EINA_TRUE;
385 }
386
387 static void _on_pressed(void *data, Evas_Object *obj __UNUSED__,
388                         const char *emission, const char *source __UNUSED__)
389 {
390         Keypad *ctx = data;
391         DBG("ctx=%p, signal: %s", ctx, emission);
392
393         EINA_SAFETY_ON_FALSE_RETURN(eina_str_has_prefix(emission, "pressed,"));
394         emission += strlen("pressed,");
395
396         if ((emission[0] >= '0') && (emission[0] <= '9')) {
397                 eina_strbuf_append_char(ctx->number, emission[0]);
398                 _number_display(ctx);
399
400                 if ((emission[0] == '0') &&
401                         (eina_strbuf_length_get(ctx->number) == 1)) {
402                         if (ctx->mod_timeout)
403                                 ecore_timer_del(ctx->mod_timeout);
404                         ctx->mod_timeout = ecore_timer_add(MOD_TIMEOUT,
405                                                 _on_mod_timeout, ctx);
406                 }
407
408         } else if (strcmp(emission, "backspace") == 0) {
409                 size_t len = eina_strbuf_length_get(ctx->number);
410                 if (len > 0) {
411                         eina_strbuf_remove(ctx->number, len - 1, len);
412                         _number_display(ctx);
413                         if (len > 1) {
414                                 if (ctx->rep_timeout)
415                                         ecore_timer_del(ctx->rep_timeout);
416                                 ctx->rep_timeout = ecore_timer_add(
417                                         REP_TIMEOUT_INIT, _on_rep_timeout, ctx);
418                         }
419                 }
420
421         } else if (strcmp(emission, "star") == 0) {
422                 eina_strbuf_append_char(ctx->number, '*');
423                 _number_display(ctx);
424         } else if (strcmp(emission, "hash") == 0) {
425                 eina_strbuf_append_char(ctx->number, '#');
426                 _number_display(ctx);
427         }
428
429         _handle_if_mmi(ctx);
430 }
431
432 static void _on_released(void *data, Evas_Object *obj __UNUSED__,
433                                 const char *emission,
434                                 const char *source __UNUSED__)
435 {
436         Keypad *ctx = data;
437         DBG("ctx=%p, signal: %s", ctx, emission);
438
439         EINA_SAFETY_ON_FALSE_RETURN(eina_str_has_prefix(emission, "released,"));
440         emission += strlen("released,");
441
442         if (ctx->mod_timeout) {
443                 if (emission[0] != '0')
444                         ERR("Expected '0' but got '%s' instead", emission);
445                 ecore_timer_del(ctx->mod_timeout);
446                 ctx->mod_timeout = NULL;
447         }
448
449         if (ctx->rep_timeout) {
450                 if (strcmp(emission, "backspace") != 0)
451                         ERR("Expected 'backspace' but got '%s' instead",
452                                 emission);
453                 ecore_timer_del(ctx->rep_timeout);
454                 ctx->rep_timeout = NULL;
455         }
456 }
457
458 static void _on_clicked(void *data, Evas_Object *obj __UNUSED__,
459                         const char *emission, const char *source __UNUSED__)
460 {
461         Keypad *ctx = data;
462         DBG("ctx=%p, signal: %s", ctx, emission);
463
464         EINA_SAFETY_ON_FALSE_RETURN(eina_str_has_prefix(emission, "clicked,"));
465         emission += strlen("clicked,");
466
467         if (strcmp(emission, "call") == 0) {
468                 if (eina_strbuf_length_get(ctx->number) > 0)
469                         _call(ctx);
470                 else if (ctx->last) {
471                         eina_strbuf_append(ctx->number, ctx->last);
472                         _number_display(ctx);
473                 }
474         } else if (strcmp(emission, "save") == 0) {
475                 ERR("TODO save contact %s!",
476                         eina_strbuf_string_get(ctx->number));
477         }
478 }
479
480 static void _on_del(void *data, Evas *e __UNUSED__,
481                         Evas_Object *obj __UNUSED__, void *event __UNUSED__)
482 {
483         Keypad *ctx = data;
484
485         if (ctx->ss_pending)
486                 ofono_pending_cancel(ctx->ss_pending);
487
488         if (ctx->mod_timeout)
489                 ecore_timer_del(ctx->mod_timeout);
490         if (ctx->rep_timeout)
491                 ecore_timer_del(ctx->rep_timeout);
492         if (ctx->ss_popup_show_timeout)
493                 ecore_timer_del(ctx->ss_popup_show_timeout);
494
495         eina_strbuf_free(ctx->number);
496         eina_stringshare_del(ctx->last);
497         free(ctx);
498 }
499
500 Evas_Object *keypad_add(Evas_Object *parent)
501 {
502         Keypad *ctx;
503         Evas_Object *obj = layout_add(parent, "keypad");
504         EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL);
505
506         ctx = calloc(1, sizeof(Keypad));
507         ctx->self = obj;
508         ctx->number = eina_strbuf_new();
509
510         evas_object_data_set(obj, "keypad.ctx", ctx);
511
512         evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL,
513                                         _on_del, ctx);
514         elm_object_signal_callback_add(obj, "pressed,*", "keypad",
515                                         _on_pressed, ctx);
516         elm_object_signal_callback_add(obj, "released,*", "keypad",
517                                         _on_released, ctx);
518         elm_object_signal_callback_add(obj, "clicked,*", "keypad",
519                                         _on_clicked, ctx);
520
521         elm_object_part_text_set(obj, "elm.text.display", "");
522         elm_object_part_text_set(ctx->self, "elm.text.contact-and-type", "");
523         elm_object_part_text_set(ctx->self, "elm.text.contact", "");
524         elm_object_part_text_set(ctx->self, "elm.text.phone.type", "");
525         elm_object_signal_emit(obj, "hide,contact", "keypad");
526         elm_object_signal_emit(obj, "disable,save", "keypad");
527         elm_object_signal_emit(obj, "disable,backspace", "keypad");
528
529         elm_object_focus_allow_set(obj, EINA_TRUE);
530
531         return obj;
532 }
533
534 void keypad_number_set(Evas_Object *obj, const char *number,
535                         Eina_Bool auto_dial)
536 {
537         Keypad *ctx;
538         EINA_SAFETY_ON_NULL_RETURN(obj);
539         EINA_SAFETY_ON_NULL_RETURN(number);
540
541         ctx = evas_object_data_get(obj, "keypad.ctx");
542         EINA_SAFETY_ON_NULL_RETURN(ctx);
543
544         eina_strbuf_reset(ctx->number);
545         eina_strbuf_append(ctx->number, number);
546         _number_display(ctx);
547         if (auto_dial)
548                 _call(ctx);
549 }