Initial Commit, basic infrastructure.
authorGustavo Sverzut Barbieri <barbieri@profusion.mobi>
Mon, 25 Jun 2012 23:13:37 +0000 (20:13 -0300)
committerGustavo Sverzut Barbieri <barbieri@profusion.mobi>
Mon, 25 Jun 2012 23:13:37 +0000 (20:13 -0300)
The phone basics work without handling of DTMF (SendTones), the theme
is pretty barebones at this point.

Supported API:
        - VoiceCall:
                - Methods: Hangup, Answer
                - Signals: PropertyChanged, DisconnectReason
        - VoiceCallManager:
                - Methods: GetCalls, Dial
                - Signals: CallAdded, CallRemoved
        - Manager:
                - Methods: GetModems
                - Signals: ModemAdded, ModemRemoved, PropertyChanged

It's fully hot-pluggable: the daemon, modems and calls can enter and
exit at anytime.

It's remote controllable using DBus. Right now just Activate and Dial
are implemented so other applications can bring up the dialer window
and request a number to be dialed. This is also used to guarantee a
single instance of the application.

22 files changed:
.gitignore [new file with mode: 0644]
AUTHORS [new file with mode: 0644]
COPYING [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
README [new file with mode: 0644]
autogen.sh [new file with mode: 0755]
configure.ac [new file with mode: 0644]
data/themes/default.edc [new file with mode: 0644]
dialer/callscreen.c [new file with mode: 0644]
dialer/callscreen.h [new file with mode: 0644]
dialer/gui.c [new file with mode: 0644]
dialer/gui.h [new file with mode: 0644]
dialer/keypad.c [new file with mode: 0644]
dialer/keypad.h [new file with mode: 0644]
dialer/log.h [new file with mode: 0644]
dialer/main.c [new file with mode: 0644]
dialer/ofono.c [new file with mode: 0644]
dialer/ofono.h [new file with mode: 0644]
dialer/rc.c [new file with mode: 0644]
dialer/rc.h [new file with mode: 0644]
m4/ac_attribute.m4 [new file with mode: 0644]
m4/efl_binary.m4 [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..c238069
--- /dev/null
@@ -0,0 +1,30 @@
+*.o
+*~
+.deps
+.dirstamp
+aclocal.m4
+autom4te.cache/
+compile
+config.guess
+config.h
+config.h.in
+config.log
+config.status
+config.sub
+configure
+depcomp
+dialer/dialer
+install-sh
+libtool
+ltmain.sh
+m4/libtool.m4
+m4/ltoptions.m4
+m4/ltsugar.m4
+m4/ltversion.m4
+m4/lt~obsolete.m4
+Makefile
+Makefile.in
+missing
+stamp-h1
+data/themes/*.edj
+ofono-efl-*.tar.*
diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..afc0ea7
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,2 @@
+Gustavo Sverzut Barbieri <barbieri@profusion.mobi>
+Guilherme Iscaro <iscaro@profusion.mobi>
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..2bbad7c
--- /dev/null
+++ b/COPYING
@@ -0,0 +1 @@
+TO BE DEFINED BY CUSTOMER - Intel
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..5be5e51
--- /dev/null
@@ -0,0 +1,63 @@
+ACLOCAL_AMFLAGS = -I m4
+AM_MAKEFLAGS = --no-print-directory
+AM_CFLAGS = \
+       -DPACKAGE_DATA_DIR=\"$(pkgdatadir)/\" \
+       -DPACKAGE_BIN_DIR=\"$(bindir)\" \
+       -DPACKAGE_LIB_DIR=\"$(libdir)\" \
+       @EFL_CFLAGS@
+
+MAINTAINERCLEANFILES = \
+       aclocal.m4 \
+       compile \
+       config.guess \
+       config.h.in \
+       config.sub \
+       configure \
+       depcomp \
+       install-sh \
+       ltmain.sh \
+       Makefile.in \
+       missing \
+       mkinstalldirs
+
+bin_PROGRAMS = dialer/dialer
+
+dialer_dialer_LDADD = @EFL_LIBS@
+dialer_dialer_SOURCES = \
+       dialer/main.c \
+       dialer/log.h \
+       dialer/ofono.c \
+       dialer/ofono.h \
+       dialer/rc.c \
+       dialer/rc.h \
+       dialer/gui.c \
+       dialer/gui.h \
+       dialer/keypad.c \
+       dialer/keypad.h \
+       dialer/callscreen.c \
+       dialer/callscreen.h
+
+# Themes are compiled with edje_cc given by user (cross-compile)
+EDJE_CC = @edje_cc@
+EDJE_FLAGS_VERBOSE_ =
+EDJE_FLAGS_VERBOSE_0 =
+EDJE_FLAGS_VERBOSE_1 = -v
+EDJE_FLAGS = $(EDJE_FLAGS_VERBOSE_$(V)) -id $(top_srcdir)/data/themes/images -fd $(top_srcdir)/data/fonts
+
+filesdir = $(pkgdatadir)/themes
+files_DATA = data/themes/default.edj
+
+EXTRA_DIST = data/themes/default.edc
+
+AM_V_EDJ = $(am__v_EDJ_$(V))
+am__v_EDJ_ = $(am__v_EDJ_$(AM_DEFAULT_VERBOSITY))
+am__v_EDJ_0 = @echo "  EDJ   " $@;
+
+data/themes/default.edj: $(top_builddir)/Makefile $(top_srcdir)/data/themes/default.edc
+       $(MKDIR_P) $(top_builddir)/data/themes
+       $(AM_V_EDJ)$(EDJE_CC) $(EDJE_FLAGS) \
+       $(top_srcdir)/data/themes/default.edc \
+       $(top_builddir)/data/themes/default.edj
+
+clean-local:
+       rm -f $(top_builddir)/data/themes/default.edj
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..379a2b6
--- /dev/null
+++ b/README
@@ -0,0 +1,32 @@
+Connection Manager
+******************
+
+Copyright (C) 2012  Intel Corporation. All rights reserved.
+
+
+ABOUT
+=====
+
+Graphical User Interface for oFono using EFL (Enlightenment Foundation
+Libraries) to be used with Tizen platform.
+
+
+DEBUGGING
+=========
+
+To help debug, the following environment variables could be set:
+
+       EINA_LOG_LEVEL=4
+               toggles debug (level=4) of whole EFL
+
+       EINA_LOG_LEVELS=dialer:4
+               toggles debug of dialer logging domain.
+
+       EINA_LOG_ABORT=1
+               make it abort on critical errors.
+
+       EINA_LOG_ABORT_LEVEL=2
+               make it also abort on errors.
+
+       EINA_LOG_BACKTRACE=2
+               make it produce backtraces whenever a log level is reached.
diff --git a/autogen.sh b/autogen.sh
new file mode 100755 (executable)
index 0000000..b43d171
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+autoreconf -f -i
+
+if [ -z "$NOCONFIGURE" ]; then
+    ./configure "$@"
+fi
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..81e236d
--- /dev/null
@@ -0,0 +1,45 @@
+AC_PREREQ([2.60])
+AC_INIT([ofono-efl], [1])
+
+AM_INIT_AUTOMAKE([foreign subdir-objects])
+AM_CONFIG_HEADER([config.h])
+AC_CONFIG_MACRO_DIR([m4])
+
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+AM_MAINTAINER_MODE
+
+PKG_PROG_PKG_CONFIG
+
+COMPILER_FLAGS
+
+AC_LANG_C
+
+AC_PROG_CC
+AM_PROG_CC_C_O
+AC_C___ATTRIBUTE__
+
+AC_DISABLE_STATIC
+AC_PROG_LIBTOOL
+
+PKG_CHECK_MODULES([EFL],
+       [
+               elementary
+               edbus
+               dbus-1
+        ])
+
+EFL_WITH_BIN([edje], [edje-cc], [edje_cc])
+
+with_max_log_level="EINA_LOG_LEVEL_DBG"
+AC_ARG_WITH(maximum-log-level,
+   [AC_HELP_STRING([--with-maximum-log-level=NUMBER],
+                   [limit log level, any call to EINA_LOG() with values greater than this will be compiled out, ignoring runtime settings, but saving function calls.])],
+   [with_max_log_level="${withval}"], [:])
+AC_DEFINE_UNQUOTED(EINA_LOG_LEVEL_MAXIMUM, ${with_max_log_level}, [if set, logging is limited to this amount.])
+
+AC_CONFIG_FILES([
+Makefile
+])
+
+AC_OUTPUT
diff --git a/data/themes/default.edc b/data/themes/default.edc
new file mode 100644 (file)
index 0000000..d34c827
--- /dev/null
@@ -0,0 +1,508 @@
+collections {
+   group {
+      name: "elm/layout/dialer/main";
+      parts {
+         part {
+            name: "bg";
+            type: RECT;
+            mouse_events: 0;
+            description {
+               state: "default" 0.0;
+               color: 0 0 0 255;
+            }
+         }
+
+         part {
+            name: "elm.swallow.keypad";
+            type: SWALLOW;
+            description {
+               state: "default" 0.0;
+               rel2.relative: 1.0 0.9;
+            }
+         }
+
+      }
+   }
+
+   group {
+      name: "elm/layout/dialer/keypad";
+      parts {
+         part {
+            name: "elm.text.display";
+            type: TEXT;
+            mouse_events: 0;
+            scale: 1;
+            description {
+               state: "default" 0.0;
+               color: 220 220 220 255;
+               rel2 {
+                  relative: 1.0 0.0;
+                  offset: -1.0 100;
+               }
+               text {
+                  text: "+1 (12) 3456-7890";
+                  font: "Verdana";
+                  size: 42;
+                  size_range: 16 42;
+                  fit: 1 1;
+                  align: 0.5 0.5;
+                  ellipsis: 1.0;
+               }
+            }
+         }
+
+         part {
+            name: "bg.buttons";
+            type: RECT;
+            mouse_events: 0;
+            description {
+               state: "default" 0.0;
+               color: 0 0 0 255;
+               rel1 {
+                  relative: 0.0 1.0;
+                  to_y: "elm.text.display";
+               }
+            }
+         }
+
+#define BUTTON(id, label, sub, r1, r2)                                  \
+         part {                                                         \
+            name: "button."##id;                                        \
+            type: RECT;                                                 \
+            mouse_events: 1;                                            \
+            description {                                               \
+               state: "default" 0.0;                                    \
+               color: 96 96 96 255;                                     \
+               rel1 {                                                   \
+                  to: "bg.buttons";                                     \
+                  relative: r1;                                         \
+               }                                                        \
+               rel2 {                                                   \
+                  to: "bg.buttons";                                     \
+                  relative: r2;                                         \
+                  offset: -2 -2;                                        \
+               }                                                        \
+            }                                                           \
+            description {                                               \
+               state: "pressed" 0.0;                                    \
+               inherit: "default" 0.0;                                  \
+               color: 128 128 128 255;                                  \
+            }                                                           \
+         }                                                              \
+         part {                                                         \
+            name: "label."##id;                                         \
+            type: TEXT;                                                 \
+            effect: SHADOW;                                             \
+            mouse_events: 0;                                            \
+            description {                                               \
+               state: "default" 0.0;                                    \
+               color: 220 220 220 255;                                  \
+               color3: 32 32 32 128;                                    \
+               rel1.to: "button."##id;                                  \
+               rel2 {                                                   \
+                  to: "button."##id;                                    \
+                  relative: 1.0 0.8;                                    \
+               }                                                        \
+               text {                                                   \
+                  text: label;                                          \
+                  font: "Verdana:style=Bold";                           \
+                  size: 36;                                             \
+                  align: 0.5 0.5;                                       \
+               }                                                        \
+            }                                                           \
+            description {                                               \
+               state: "pressed" 0.0;                                    \
+               inherit: "default" 0.0;                                  \
+               color: 255 255 255 255;                                  \
+            }                                                           \
+         }                                                              \
+         part {                                                         \
+            name: "sub."##id;                                           \
+            type: TEXT;                                                 \
+            mouse_events: 0;                                            \
+            description {                                               \
+               state: "default" 0.0;                                    \
+               color: 180 180 180 255;                                  \
+               rel1 {                                                   \
+                  to: "button."##id;                                    \
+                  relative: 0.0 0.5;                                    \
+                  offset: 0 2;                                          \
+               }                                                        \
+               rel2.to: "button."##id;                                  \
+               text {                                                   \
+                  text: sub;                                            \
+                  font: "Verdana:style=Bold";                           \
+                  size: 16;                                             \
+                  align: 0.5 0.5;                                       \
+               }                                                        \
+            }                                                           \
+         }                                                              \
+         programs {                                                     \
+            program {                                                   \
+               signal: "mouse,up,1";                                    \
+               source: "button."##id;                                   \
+               action: SIGNAL_EMIT "released,"##id "keypad";            \
+               after: "show_up_"##id;                                   \
+            }                                                           \
+            program {                                                   \
+               name: "show_up_"##id;                                    \
+               action: STATE_SET "default" 0.0;                         \
+               transition: DECELERATE 0.1;                              \
+               target: "button."##id;                                   \
+               target: "label."##id;                                    \
+               target: "sub."##id;                                      \
+            }                                                           \
+            program {                                                   \
+               signal: "mouse,down,1";                                  \
+               source: "button."##id;                                   \
+               after: "show_down_"##id;                                 \
+               action: SIGNAL_EMIT "pressed,"##id "keypad";             \
+            }                                                           \
+            program {                                                   \
+               name: "show_down_"##id;                                  \
+               action: STATE_SET "pressed" 0.0;                         \
+               transition: ACCELERATE 0.3;                              \
+               target: "button."##id;                                   \
+               target: "label."##id;                                    \
+               target: "sub."##id;                                      \
+            }                                                           \
+            program {                                                   \
+               signal: "mouse,clicked,1";                               \
+               source: "button."##id;                                   \
+               action: SIGNAL_EMIT "clicked,"##id "keypad";             \
+            }                                                           \
+         }
+
+         BUTTON("1",    "1",     "", 0.000 0.0, 0.333 0.2);
+         BUTTON("2",    "2",  "ABC", 0.333 0.0, 0.666 0.2);
+         BUTTON("3",    "3",  "DEF", 0.666 0.0, 1.000 0.2);
+
+         BUTTON("4",    "4",  "GHI", 0.000 0.2, 0.333 0.4);
+         BUTTON("5",    "5",  "JKL", 0.333 0.2, 0.666 0.4);
+         BUTTON("6",    "6",  "MNO", 0.666 0.2, 1.000 0.4);
+
+         BUTTON("7",    "7", "PQRS", 0.000 0.4, 0.333 0.6);
+         BUTTON("8",    "8",  "TUV", 0.333 0.4, 0.666 0.6);
+         BUTTON("9",    "9", "WXYZ", 0.666 0.4, 1.000 0.6);
+
+         BUTTON("star", "*",     "", 0.000 0.6, 0.333 0.8);
+         BUTTON("0",    "0",    "+", 0.333 0.6, 0.666 0.8);
+         BUTTON("hash", "#",     "", 0.666 0.6, 1.000 0.8);
+
+         /* TODO: better special buttons */
+         BUTTON("save", "Save", "", 0.000 0.8, 0.333 1.0);
+         BUTTON("call", "Call", "", 0.333 0.8, 0.666 1.0);
+         BUTTON("backspace", "<", "", 0.666 0.8, 1.000 1.0);
+#undef BUTTON
+      }
+   }
+
+   group {
+      name: "elm/layout/dialer/call";
+      parts {
+         part {
+            name: "bg";
+            type: RECT;
+            mouse_events: 0;
+            description {
+               state: "default" 0.0;
+               color: 0 0 0 255;
+            }
+         }
+
+         part {
+            name: "elm.text.name";
+            type: TEXT;
+            mouse_events: 0;
+            scale: 1;
+            description {
+               state: "default" 0.0;
+               color: 220 220 220 255;
+               rel1.offset: 15 15;
+               rel2 {
+                  relative: 1.0 0.0;
+                  offset: -16 70;
+               }
+               text {
+                  text: "Gustavo Barbieri";
+                  font: "Verdana";
+                  size: 42;
+                  size_range: 16 42;
+                  fit: 1 1;
+                  align: 0.0 0.5;
+                  ellipsis: 0.0;
+               }
+            }
+         }
+
+         part {
+            name: "elm.text.status";
+            type: TEXT;
+            mouse_events: 0;
+            scale: 1;
+            description {
+               state: "default" 0.0;
+               color: 128 128 128 255;
+               rel1 {
+                  relative: 0.0 1.0;
+                  to: "elm.text.name";
+               }
+               rel2 {
+                  to: "elm.text.name";
+                  relative: 1.0 1.0;
+                  offset: -1.0 30;
+               }
+               text {
+                  text: "calling...";
+                  font: "Verdana";
+                  size: 18;
+                  min: 0 1;
+                  align: 0.0 1.0;
+                  ellipsis: 0.0;
+               }
+            }
+         }
+
+         part {
+            name: "bg.buttons";
+            type: RECT;
+            mouse_events: 0;
+            description {
+               state: "default" 0.0;
+               color: 16 16 16 255;
+               rel1 {
+                  relative: 0.0 1.0;
+                  offset: 0 -100;
+               }
+            }
+         }
+         part {
+            name: "area.hangup";
+            type: RECT;
+            mouse_events: 0;
+            description {
+               state: "default" 0.0;
+               color: 0 0 0 0;
+               rel1 {
+                  to: "bg.buttons";
+                  relative: 0.0 0.0;
+                  offset: 15 15;
+               }
+               rel2 {
+                  to: "bg.buttons";
+                  relative: 0.5 1.0;
+                  offset: -16 -16;
+               }
+            }
+            description {
+               state: "full" 0.0;
+               inherit: "default" 0.0;
+               rel2 {
+                  to: "bg.buttons";
+                  relative: 1.0 1.0;
+                  offset: -16 -16;
+               }
+            }
+         }
+         part {
+            name: "button.hangup";
+            type: RECT;
+            mouse_events: 1;
+            description {
+               state: "default" 0.0;
+               color: 160 32 32 255;
+               rel1.to: "area.hangup";
+               rel2.to: "area.hangup";
+            }
+            description {
+               state: "pressed" 0.0;
+               inherit: "default" 0.0;
+               color: 160 32 32 128;
+            }
+         }
+         part {
+            name: "label.hangup";
+            type: TEXT;
+            effect: SHADOW;
+            mouse_events: 0;
+            description {
+               state: "default" 0.0;
+               color: 220 220 220 255;
+               color3: 32 32 32 128;
+               rel1.to: "button.hangup";
+               rel2.to: "button.hangup";
+               text {
+                  text: "Hangup";
+                  font: "Verdana:style=Bold";
+                  size: 36;
+                  align: 0.5 0.5;
+               }
+            }
+            description {
+               state: "pressed" 0.0;
+               inherit: "default" 0.0;
+               color: 255 255 255 255;
+            }
+         }
+         programs {
+            program {
+               signal: "mouse,up,1";
+               source: "button.hangup";
+               action: SIGNAL_EMIT "released,hangup" "call";
+               after: "show_up_hangup";
+            }
+            program {
+               name: "show_up_hangup";
+               action: STATE_SET "default" 0.0;
+               transition: DECELERATE 0.1;
+               target: "button.hangup";
+               target: "label.hangup";
+            }
+            program {
+               signal: "mouse,down,1";
+               source: "button.hangup";
+               after: "show_down_hangup";
+               action: SIGNAL_EMIT "pressed,hangup" "call";
+            }
+            program {
+               name: "show_down_hangup";
+               action: STATE_SET "pressed" 0.0;
+               transition: ACCELERATE 0.3;
+               target: "button.hangup";
+               target: "label.hangup";
+            }
+            program {
+               signal: "mouse,clicked,1";
+               source: "button.hangup";
+               action: SIGNAL_EMIT "clicked,hangup" "call";
+            }
+         }
+
+
+         part {
+            name: "clipper.answer";
+            type: RECT;
+            mouse_events: 0;
+            description {
+               state: "default" 0.0;
+            }
+            description {
+               state: "hidden" 0.0;
+               color: 255 255 255 0;
+               visible: 0;
+            }
+         }
+         part {
+            name: "button.answer";
+            type: RECT;
+            mouse_events: 1;
+            clip_to: "clipper.answer";
+            description {
+               state: "default" 0.0;
+               color: 32 160 32 255;
+               rel1 {
+                  to: "bg.buttons";
+                  relative: 0.5 0.0;
+                  offset: 15 15;
+               }
+               rel2 {
+                  to: "bg.buttons";
+                  relative: 1.0 1.0;
+                  offset: -16 -16;
+               }
+            }
+            description {
+               state: "pressed" 0.0;
+               inherit: "default" 0.0;
+               color: 160 32 32 128;
+            }
+         }
+         part {
+            name: "label.answer";
+            type: TEXT;
+            effect: SHADOW;
+            mouse_events: 0;
+            clip_to: "clipper.answer";
+            description {
+               state: "default" 0.0;
+               color: 220 220 220 255;
+               color3: 32 32 32 128;
+               rel1.to: "button.answer";
+               rel2.to: "button.answer";
+               text {
+                  text: "Answer";
+                  font: "Verdana:style=Bold";
+                  size: 36;
+                  align: 0.5 0.5;
+               }
+            }
+            description {
+               state: "pressed" 0.0;
+               inherit: "default" 0.0;
+               color: 255 255 255 255;
+            }
+         }
+         programs {
+            program {
+               signal: "mouse,up,1";
+               source: "button.answer";
+               action: SIGNAL_EMIT "released,answer" "call";
+               after: "show_up_answer";
+            }
+            program {
+               name: "show_up_answer";
+               action: STATE_SET "default" 0.0;
+               transition: DECELERATE 0.1;
+               target: "button.answer";
+               target: "label.answer";
+            }
+            program {
+               signal: "mouse,down,1";
+               source: "button.answer";
+               after: "show_down_answer";
+               action: SIGNAL_EMIT "pressed,answer" "call";
+            }
+            program {
+               name: "show_down_answer";
+               action: STATE_SET "pressed" 0.0;
+               transition: ACCELERATE 0.3;
+               target: "button.answer";
+               target: "label.answer";
+            }
+            program {
+               signal: "mouse,clicked,1";
+               source: "button.answer";
+               action: SIGNAL_EMIT "clicked,answer" "call";
+            }
+         }
+
+         programs {
+            program {
+               signal: "hide,answer";
+               source: "call";
+               action: STATE_SET "hidden" 0.0;
+               target: "clipper.answer";
+            }
+            program {
+               signal: "hide,answer";
+               source: "call";
+               action: STATE_SET "full" 0.0;
+               target: "area.hangup";
+            }
+
+            program {
+               signal: "show,answer";
+               source: "call";
+               action: STATE_SET "default" 0.0;
+               target: "clipper.answer";
+            }
+            program {
+               signal: "show,answer";
+               source: "call";
+               action: STATE_SET "default" 0.0;
+               target: "area.hangup";
+            }
+         }
+      }
+   }
+}
diff --git a/dialer/callscreen.c b/dialer/callscreen.c
new file mode 100644 (file)
index 0000000..d13623a
--- /dev/null
@@ -0,0 +1,309 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <Elementary.h>
+
+#include "log.h"
+#include "gui.h"
+#include "ofono.h"
+
+typedef struct _Callscreen
+{
+       Evas_Object *self;
+       OFono_Call *in_use;
+       Eina_List *calls;
+       struct {
+               const char *number;
+               Evas_Object *popup;
+       } disconnected;
+} Callscreen;
+
+static void _call_in_use_update(Callscreen *ctx)
+{
+       const Eina_List *n;
+       OFono_Call *c, *found = NULL;
+       OFono_Call_State found_state = OFONO_CALL_STATE_DISCONNECTED;
+       static const int state_priority[] = {
+               [OFONO_CALL_STATE_DISCONNECTED] = 0,
+               [OFONO_CALL_STATE_ACTIVE] = 6,
+               [OFONO_CALL_STATE_HELD] = 1,
+               [OFONO_CALL_STATE_DIALING] = 5,
+               [OFONO_CALL_STATE_ALERTING] = 4,
+               [OFONO_CALL_STATE_INCOMING] = 3,
+               [OFONO_CALL_STATE_WAITING] = 2
+       };
+
+       if (ctx->in_use)
+               return;
+
+       EINA_LIST_FOREACH(ctx->calls, n, c) {
+               OFono_Call_State state = ofono_call_state_get(c);
+
+               DBG("compare %p (%d) to %p (%d)", found, found_state,
+                       c, state);
+               if (state_priority[state] > state_priority[found_state]) {
+                       found_state = state;
+                       found = c;
+               }
+       }
+
+       if (!found) {
+               DBG("No calls usable");
+               return;
+       }
+
+       DBG("found=%p, state=%d", found, found_state);
+       ctx->in_use = found;
+       gui_call_enter();
+}
+
+static void _call_disconnected_done(Callscreen *ctx)
+{
+       if (!ctx->calls)
+               gui_call_exit();
+       else
+               _call_in_use_update(ctx);
+}
+
+static void _popup_close(void *data, Evas_Object *o __UNUSED__, void *event __UNUSED__)
+{
+       Callscreen *ctx = data;
+
+       evas_object_del(ctx->disconnected.popup);
+       ctx->disconnected.popup = NULL;
+
+       eina_stringshare_replace(&ctx->disconnected.number, NULL);
+
+       _call_disconnected_done(ctx);
+}
+
+static void _popup_redial(void *data, Evas_Object *o __UNUSED__, void *event __UNUSED__)
+{
+       Callscreen *ctx = data;
+
+       ofono_dial(ctx->disconnected.number, NULL, NULL, NULL);
+       _popup_close(ctx, NULL, NULL);
+}
+
+static void _call_disconnected_show(Callscreen *ctx, OFono_Call *c,
+                                       const char *reason)
+{
+       Evas_Object *p, *bt;
+       const char *number, *title;
+       char msg[1024];
+
+       DBG("ctx=%p, call=%p, previous=%s, disconnected=%p (%s)",
+               ctx, ctx->in_use, ctx->disconnected.number, c, reason);
+
+       if ((ctx->in_use) && (ctx->in_use != c))
+               return;
+       ctx->in_use = NULL;
+
+       if ((strcmp(reason, "local") == 0) || (strcmp(reason, "remote") == 0)) {
+               _call_disconnected_done(ctx);
+               return;
+       }
+
+       number = ofono_call_line_id_get(c);
+       if ((!number) || (number[0] == '\0')) {
+               _call_disconnected_done(ctx);
+               return;
+       }
+
+       if (ctx->disconnected.number)
+               return;
+
+       if (strcmp(reason, "network") == 0)
+               title = "Network Disconnected!";
+       else
+               title = "Disconnected!";
+
+       snprintf(msg, sizeof(msg), "Try to redial %s", number);
+
+       eina_stringshare_replace(&ctx->disconnected.number, number);
+
+       ctx->disconnected.popup = p = elm_popup_add(ctx->self);
+       evas_object_size_hint_weight_set(p, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+       elm_object_part_text_set(p, "title,text", title);
+       elm_object_text_set(p, msg);
+
+       bt = elm_button_add(p);
+       elm_object_text_set(bt, "Close");
+       elm_object_part_content_set(p, "button1", bt);
+       evas_object_smart_callback_add(bt, "clicked", _popup_close, ctx);
+
+       bt = elm_button_add(p);
+       elm_object_text_set(bt, "Redial");
+       elm_object_part_content_set(p, "button2", bt);
+       evas_object_smart_callback_add(bt, "clicked", _popup_redial, ctx);
+
+       evas_object_show(p);
+}
+
+static void _on_pressed(void *data, Evas_Object *obj __UNUSED__,
+                       const char *emission, const char *source __UNUSED__)
+{
+       Callscreen *ctx = data;
+       DBG("ctx=%p, call=%p, signal: %s", ctx, ctx->in_use, emission);
+
+       EINA_SAFETY_ON_FALSE_RETURN(eina_str_has_prefix(emission, "pressed,"));
+       emission += strlen("pressed,");
+
+}
+
+static void _on_released(void *data, Evas_Object *obj __UNUSED__,
+                               const char *emission,
+                               const char *source __UNUSED__)
+{
+       Callscreen *ctx = data;
+       DBG("ctx=%p, call=%p, signal: %s", ctx, ctx->in_use, emission);
+
+       EINA_SAFETY_ON_FALSE_RETURN(eina_str_has_prefix(emission, "released,"));
+       emission += strlen("released,");
+
+}
+
+static void _on_clicked(void *data, Evas_Object *obj __UNUSED__,
+                       const char *emission, const char *source __UNUSED__)
+{
+       Callscreen *ctx = data;
+       DBG("ctx=%p, call=%p, signal: %s", ctx, ctx->in_use, emission);
+
+       EINA_SAFETY_ON_FALSE_RETURN(eina_str_has_prefix(emission, "clicked,"));
+       emission += strlen("clicked,");
+
+       if (strcmp(emission, "hangup") == 0) {
+               if (ctx->in_use)
+                       ofono_call_hangup(ctx->in_use, NULL, NULL);
+       } else if (strcmp(emission, "answer") == 0) {
+               if (ctx->in_use)
+                       ofono_call_answer(ctx->in_use, NULL, NULL);
+       }
+}
+
+static void _call_added(void *data, OFono_Call *c)
+{
+       Callscreen *ctx = data;
+       DBG("ctx=%p, call=%p, added=%p", ctx, ctx->in_use, c);
+       ctx->calls = eina_list_append(ctx->calls, c);
+       if ((!ctx->in_use) && (ofono_call_state_valid_check(c))) {
+               ctx->in_use = c;
+               gui_call_enter();
+       }
+}
+
+static void _call_removed(void *data, OFono_Call *c)
+{
+       Callscreen *ctx = data;
+       DBG("ctx=%p, call=%p, removed=%p", ctx, ctx->in_use, c);
+       ctx->calls = eina_list_remove(ctx->calls, c);
+       _call_disconnected_show(ctx, c, "local");
+}
+
+static void _call_changed(void *data, OFono_Call *c)
+{
+       Callscreen *ctx = data;
+       OFono_Call_State state;
+       const char *contact, *status, *signal = "hide,answer";
+
+       DBG("ctx=%p, call=%p, changed=%p", ctx, ctx->in_use, c);
+
+       _call_in_use_update(ctx);
+       if (ctx->in_use != c)
+               return;
+
+       contact = ofono_call_name_get(c);
+       if ((!contact) || (contact[0] == '\0'))
+               contact = ofono_call_line_id_get(c);
+
+       state = ofono_call_state_get(c);
+       switch (state) {
+       case OFONO_CALL_STATE_DISCONNECTED:
+               status = "Disconnected";
+               break;
+       case OFONO_CALL_STATE_ACTIVE:
+               status = "Active";
+               break;
+       case OFONO_CALL_STATE_HELD:
+               status = "Held";
+               break;
+       case OFONO_CALL_STATE_DIALING:
+               status = "Dialing...";
+               break;
+       case OFONO_CALL_STATE_ALERTING:
+               status = "Alerting...";
+               break;
+       case OFONO_CALL_STATE_INCOMING:
+               status = "Incoming...";
+               signal = "show,answer";
+               break;
+       case OFONO_CALL_STATE_WAITING:
+               status = "Waiting...";
+               break;
+       default:
+               status = "?";
+       }
+
+       elm_object_part_text_set(ctx->self, "elm.text.name", contact);
+       elm_object_part_text_set(ctx->self, "elm.text.status", status);
+       elm_object_signal_emit(ctx->self, signal, "call");
+
+       if (state == OFONO_CALL_STATE_DISCONNECTED)
+               _call_disconnected_show(ctx, c, "local");
+}
+
+static void _call_disconnected(void *data, OFono_Call *c, const char *reason)
+{
+       Callscreen *ctx = data;
+       DBG("ctx=%p, call=%p, disconnected=%p (%s)",
+               ctx, ctx->in_use, c, reason);
+
+       EINA_SAFETY_ON_NULL_RETURN(reason);
+       _call_disconnected_show(ctx, c, reason);
+}
+
+static void _on_del(void *data, Evas *e __UNUSED__,
+                       Evas_Object *obj __UNUSED__, void *event __UNUSED__)
+{
+       Callscreen *ctx = data;
+
+       ofono_call_added_cb_set(NULL, NULL);
+       ofono_call_removed_cb_set(NULL, NULL);
+       ofono_call_changed_cb_set(NULL, NULL);
+       ofono_call_disconnected_cb_set(NULL, NULL);
+
+       eina_stringshare_del(ctx->disconnected.number);
+
+       eina_list_free(ctx->calls);
+       free(ctx);
+}
+
+Evas_Object *callscreen_add(Evas_Object *parent) {
+       Callscreen *ctx;
+       Evas_Object *obj = gui_layout_add(parent, "call");
+       EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL);
+
+       ctx = calloc(1, sizeof(Callscreen));
+       ctx->self = obj;
+
+       evas_object_data_set(obj, "callscreen.ctx", ctx);
+
+       evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL,
+                                       _on_del, ctx);
+       elm_object_signal_callback_add(obj, "pressed,*", "call",
+                                       _on_pressed, ctx);
+       elm_object_signal_callback_add(obj, "released,*", "call",
+                                       _on_released, ctx);
+       elm_object_signal_callback_add(obj, "clicked,*", "call",
+                                       _on_clicked, ctx);
+
+       elm_object_part_text_set(obj, "elm.text.name", "");
+       elm_object_part_text_set(obj, "elm.text.status", "");
+
+       ofono_call_added_cb_set(_call_added, ctx);
+       ofono_call_removed_cb_set(_call_removed, ctx);
+       ofono_call_changed_cb_set(_call_changed, ctx);
+       ofono_call_disconnected_cb_set(_call_disconnected, ctx);
+
+       return obj;
+}
diff --git a/dialer/callscreen.h b/dialer/callscreen.h
new file mode 100644 (file)
index 0000000..eac55fc
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef _EFL_OFONO_CALLSCREEN_H__
+#define _EFL_OFONO_CALLSCREEN_H__ 1
+
+#include "ofono.h"
+
+Evas_Object *callscreen_add(Evas_Object *parent);
+void callscreen_call_add(Evas_Object *obj, OFono_Call *call);
+
+#endif
diff --git a/dialer/gui.c b/dialer/gui.c
new file mode 100644 (file)
index 0000000..a4ccd9a
--- /dev/null
@@ -0,0 +1,161 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <Elementary.h>
+
+#include "log.h"
+#include "gui.h"
+#include "keypad.h"
+#include "callscreen.h"
+
+static Evas_Object *win = NULL;
+static Evas_Object *kp = NULL;
+static Evas_Object *cs = NULL;
+static Evas_Object *flip = NULL;
+static char def_theme[PATH_MAX] = "";
+
+/* XXX elm_flip should just do the right thing, but it does not */
+static Eina_Bool in_call = EINA_FALSE;
+static Eina_Bool in_flip_anim = EINA_FALSE;
+
+Evas_Object *gui_layout_add(Evas_Object *parent, const char *style)
+{
+       Evas_Object *layout = elm_layout_add(parent);
+       if (!elm_layout_theme_set(layout, "layout", "dialer", style)) {
+               CRITICAL("No theme for 'elm/layout/dialer/%s' at %s",
+                               style, def_theme);
+               evas_object_del(layout);
+               return NULL;
+       }
+       return layout;
+}
+
+static void _popup_close(void *data, Evas_Object *bt __UNUSED__, void *event __UNUSED__)
+{
+       Evas_Object *popup = data;
+       evas_object_del(popup);
+}
+
+void gui_simple_popup(const char *title, const char *message)
+{
+       Evas_Object *p = elm_popup_add(win);
+       Evas_Object *bt;
+
+       evas_object_size_hint_weight_set(p, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+       elm_object_text_set(p, message);
+       elm_object_part_text_set(p, "title,text", title);
+
+       bt = elm_button_add(p);
+       elm_object_text_set(bt, "Close");
+       elm_object_part_content_set(p, "button1", bt);
+       evas_object_smart_callback_add(bt, "clicked", _popup_close, p);
+       evas_object_show(p);
+}
+
+void gui_activate(void)
+{
+       elm_win_raise(win);
+       elm_win_activate(win);
+       evas_object_show(win);
+}
+
+void gui_number_set(const char *number, Eina_Bool auto_dial)
+{
+       /* TODO: show keypad */
+       keypad_number_set(kp, number, auto_dial);
+}
+
+void gui_call_enter(void)
+{
+       if (in_call)
+               return;
+       in_call = EINA_TRUE;
+       if (in_flip_anim)
+               return;
+       in_flip_anim = EINA_TRUE;
+       elm_flip_go(flip, ELM_FLIP_ROTATE_Y_CENTER_AXIS);
+}
+
+void gui_call_exit(void)
+{
+       if (!in_call)
+               return;
+       in_call = EINA_FALSE;
+       if (in_flip_anim)
+               return;
+       in_flip_anim = EINA_TRUE;
+       elm_flip_go(flip, ELM_FLIP_ROTATE_Y_CENTER_AXIS);
+}
+
+static void _gui_call_sync(void *data __UNUSED__, Evas_Object *o __UNUSED__,
+                               void *event_info __UNUSED__)
+{
+       Eina_Bool showing_call = !elm_flip_front_visible_get(flip);
+
+       if (showing_call ^ in_call) {
+               DBG("Flip back to sync");
+               elm_flip_go(flip, ELM_FLIP_ROTATE_Y_CENTER_AXIS);
+       }
+       in_flip_anim = EINA_FALSE;
+}
+
+Eina_Bool gui_init(void)
+{
+       Evas_Object *lay, *obj;
+
+       /* dialer should never, ever quit */
+       elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_NONE);
+
+       elm_app_compile_bin_dir_set(PACKAGE_BIN_DIR);
+       elm_app_compile_data_dir_set(PACKAGE_DATA_DIR);
+       elm_app_info_set(gui_init, "ofono-efl", "themes/default.edj");
+
+       snprintf(def_theme, sizeof(def_theme), "%s/themes/default.edj",
+                       elm_app_data_dir_get());
+
+       elm_theme_extension_add(NULL, def_theme);
+       elm_theme_overlay_add(NULL, def_theme);
+
+       win = elm_win_util_standard_add("ofono-dialer", "oFono Dialer");
+       EINA_SAFETY_ON_NULL_RETURN_VAL(win, EINA_FALSE);
+       elm_win_autodel_set(win, EINA_FALSE);
+
+       flip = elm_flip_add(win);
+       evas_object_size_hint_weight_set(flip,
+                               EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+       evas_object_size_hint_align_set(flip, EVAS_HINT_FILL, EVAS_HINT_FILL);
+       elm_win_resize_object_add(win, flip);
+       evas_object_smart_callback_add(flip, "animate,done",
+                                       _gui_call_sync, NULL);
+       evas_object_show(flip);
+
+       lay = gui_layout_add(win, "main");
+       EINA_SAFETY_ON_NULL_RETURN_VAL(lay, EINA_FALSE);
+       evas_object_size_hint_weight_set(lay,
+                               EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+       evas_object_size_hint_align_set(lay, EVAS_HINT_FILL, EVAS_HINT_FILL);
+       elm_object_part_content_set(flip, "front", lay);
+       evas_object_show(lay);
+
+       kp = obj = keypad_add(win);
+       EINA_SAFETY_ON_NULL_RETURN_VAL(obj, EINA_FALSE);
+       elm_object_part_content_set(lay, "elm.swallow.keypad", obj);
+
+       cs = obj = callscreen_add(win);
+       EINA_SAFETY_ON_NULL_RETURN_VAL(obj, EINA_FALSE);
+       evas_object_size_hint_weight_set(obj,
+                               EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+       evas_object_size_hint_align_set(obj, EVAS_HINT_FILL, EVAS_HINT_FILL);
+       elm_object_part_content_set(flip, "back", obj);
+       evas_object_show(obj);
+
+       /* TODO: make it match better with Tizen: icon and other properties */
+       evas_object_resize(win, 480, 800);
+
+       /* do not show it yet, RC will check if it should be visible or not */
+       return EINA_TRUE;
+}
+
+void gui_shutdown(void)
+{
+}
diff --git a/dialer/gui.h b/dialer/gui.h
new file mode 100644 (file)
index 0000000..19cb6a6
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef _EFL_OFONO_GUI_H__
+#define _EFL_OFONO_GUI_H__ 1
+
+Evas_Object *gui_layout_add(Evas_Object *parent, const char *style);
+
+void gui_simple_popup(const char *title, const char *message);
+
+void gui_activate(void);
+void gui_number_set(const char *number, Eina_Bool auto_dial);
+
+void gui_call_enter(void);
+void gui_call_exit(void);
+
+Eina_Bool gui_init(void);
+void gui_shutdown(void);
+
+#endif
diff --git a/dialer/keypad.c b/dialer/keypad.c
new file mode 100644 (file)
index 0000000..245fe2b
--- /dev/null
@@ -0,0 +1,476 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <Elementary.h>
+
+#include "log.h"
+#include "gui.h"
+#include "ofono.h"
+
+/* timeout to change keys into modified, like 0 -> + */
+#define MOD_TIMEOUT (1.0)
+
+/* timeouts to repeat key while pressed */
+#define REP_TIMEOUT_INIT (0.3)
+#define REP_TIMEOUT (0.2)
+
+typedef struct _Keypad
+{
+       Evas_Object *self;
+       Eina_Strbuf *number;
+       Ecore_Timer *mod_timeout;
+       Ecore_Timer *rep_timeout;
+} Keypad;
+
+/* TODO: find a configurable way to format the number.
+ * Right now it's: 1-234-567-8901 as per
+ * http://en.wikipedia.org/wiki/Local_conventions_for_writing_telephone_numbers#North_America
+ *
+ * TODO: when contacts are integrated, look up the contact for that number
+ * and display it as elm.text.contact. Also send "contact,show/hide".
+ */
+static void _number_display(Keypad *ctx)
+{
+       size_t i, slen = eina_strbuf_length_get(ctx->number);
+       const char *src = eina_strbuf_string_get(ctx->number);
+       Eina_Strbuf *d;
+
+       if ((!src) || (slen < 1)) {
+               elm_object_part_text_set(ctx->self, "elm.text.display", "");
+               return;
+       }
+
+       if ((slen <= 4) || (slen > 12))
+               goto show_verbatim;
+
+       if ((slen == 12) && (src[0] != '+'))
+               goto show_verbatim;
+
+       for (i = 0; i < slen; i++) {
+               if ((src[i] < '0') || (src[i] > '9')) {
+                       if ((src[i] == '+') && (i == 0))
+                               continue;
+                       goto show_verbatim;
+               }
+       }
+
+       d = eina_strbuf_new();
+       eina_strbuf_append_length(d, src, slen);
+       eina_strbuf_insert_char(d, '-', slen - 4);
+       if (slen > 7) {
+               eina_strbuf_insert_char(d, '-', slen - 7);
+               if ((slen > 10) && (src[slen - 11] != '+'))
+                       eina_strbuf_insert_char(d, '-', slen - 10);
+       }
+
+
+       elm_object_part_text_set(ctx->self, "elm.text.display",
+                                       eina_strbuf_string_get(d));
+       eina_strbuf_free(d);
+       return;
+
+show_verbatim:
+       elm_object_part_text_set(ctx->self, "elm.text.display", src);
+}
+
+
+static void _imei_show(void)
+{
+       const char *imei = ofono_modem_serial_get();
+       gui_simple_popup("IMEI Request", imei ? imei : "No modem");
+       INF("Show IMEI: %s", imei);
+}
+
+static void _pin_reply(void *data, OFono_Error error)
+{
+       const char *title = data;
+       const char *msg;
+       switch (error) {
+       case OFONO_ERROR_NONE:
+               msg = "Success";
+               break;
+       case OFONO_ERROR_INVALID_ARGS:
+               msg = "Invalid Arguments";
+               break;
+       case OFONO_ERROR_INVALID_FORMAT:
+               msg = "Invalid Format";
+               break;
+       default:
+               msg = "Failed";
+       }
+
+       gui_simple_popup(title, msg);
+       if (error == OFONO_ERROR_NONE)
+               INF("%s", title);
+       else
+               ERR("%s", title);
+}
+
+static void _change_pin(const char *what, const char *old, const char *new)
+{
+       DBG("ask change of %s from %s to %s", what, old, new);
+       ofono_modem_change_pin(what, old, new, _pin_reply, "PIN Change");
+}
+
+static void _reset_pin(const char *what, const char *old, const char *new)
+{
+       DBG("ask reset of %s using %s to %s", what, old, new);
+       ofono_modem_reset_pin(what, old, new, _pin_reply, "PIN Reset");
+}
+
+static void _parse_pin(const char *str, char **old, char **new)
+{
+       const char *p1, *p2, *p3;
+       int len1, len2, len3;
+
+       p1 = strchr(str, '*');
+       if (!p1)
+               goto error;
+
+       p2 = strchr(p1 + 1, '*');
+       if (!p2)
+               goto error;
+
+       p3 = strchr(p2 + 1, '#');
+       if (!p3)
+               goto error;
+
+       len1 = p1 - str;
+       len2 = p2 - (p1 + 1);
+       len3 = p3 - (p2 + 1);
+
+       if ((len2 != len3) || (memcmp(p1 + 1, p2 + 1, len2) != 0)) {
+               ERR("New PIN code check failed: '%.*s' '%.*s'",
+                       len2, p1 + 1,
+                       len3, p2 + 1);
+               goto error;
+       }
+
+       *old = strndup(str, len1);
+       *new = strndup(p1 + 1, len2);
+
+       return;
+
+error:
+       ERR("Invalid PIN change format: %s", str);
+}
+
+static Eina_Bool _handle_if_mmi(Keypad *ctx)
+{
+       const char *str = eina_strbuf_string_get(ctx->number);
+       size_t len = eina_strbuf_length_get(ctx->number);
+
+       if (!str)
+               return EINA_FALSE;
+
+       if (len < sizeof("*#06#") - 1)
+               return EINA_FALSE;
+
+       if (str[0] != '*')
+               return EINA_FALSE;
+       if (str[len - 1] != '#')
+               return EINA_FALSE;
+
+       DBG("Possible MMI code: %s", str);
+
+       str++;
+       len -= 2;
+
+       if (strcmp(str, "#06#") == 0) {
+               _imei_show();
+               eina_strbuf_reset(ctx->number);
+               _number_display(ctx);
+               return EINA_TRUE;
+       } else if (strncmp(str, "*0", 2) == 0) {
+               const char *what = NULL, *p = str + 2;
+               char *old = NULL, *new = NULL;
+               void (*action)(const char *, const char *, const char *) = NULL;
+
+               if (*p == '4') {
+                       /* MMI codes to change PIN:
+                        *  - **04*OLD_PIN*NEW_PIN*NEW_PIN#
+                        *  - **042*OLD-PIN2*NEW_PIN2*NEW_PIN2#
+                        */
+                       p++;
+                       action = _change_pin;
+                       if (*p == '*') {
+                               what = "pin";
+                               p++;
+                       } else if (strncmp(p, "2*", 2) == 0) {
+                               what = "pin2";
+                               p += 2;
+                       }
+
+                       if (what)
+                               _parse_pin(p, &old, &new);
+               } else if (*p == '5') {
+                       /* MMI codes to reset PIN:
+                        *  - **05*PIN_UNBLOCKING_KEY*NEW_PIN*NEW_PIN#
+                        *  - **052*PIN2_UNBLOCKING_KEY*NEW_PIN2*NEW_PIN2#
+                        */
+                       p++;
+                       action = _reset_pin;
+                       if (*p == '*') {
+                               what = "pin";
+                               p++;
+                       } else if (strncmp(p, "2*", 2) == 0) {
+                               what = "pin2";
+                               p += 2;
+                       }
+
+                       if (what)
+                               _parse_pin(p, &old, &new);
+               }
+
+               DBG("PIN management '%s' what=%s, old=%s, new=%s",
+                       str, what, old, new);
+               if (action && what && old && new) {
+                       action(what, old, new);
+                       eina_strbuf_reset(ctx->number);
+                       _number_display(ctx);
+               }
+
+               free(old);
+               free(new);
+               return EINA_TRUE;
+       }
+
+       return EINA_FALSE;
+}
+
+static void _dial_reply(void *data, OFono_Error err,
+                       OFono_Call *call __UNUSED__)
+{
+       Keypad *ctx = data;
+
+       if (err != OFONO_ERROR_NONE) {
+               char buf[1024];
+               snprintf(buf, sizeof(buf), "Could not call: %s",
+                               eina_strbuf_string_get(ctx->number));
+               gui_simple_popup("Error", buf);
+       }
+}
+
+static void _dial(Keypad *ctx)
+{
+       const char *number = eina_strbuf_string_get(ctx->number);
+
+       INF("call %s", number);
+       ofono_dial(number, NULL, _dial_reply, ctx);
+}
+
+static void _ss_initiate_reply(void *data, OFono_Error err, const char *str)
+{
+       Keypad *ctx = data;
+
+       DBG("e=%d, str=%s", err, str);
+       if ((err == OFONO_ERROR_NOT_RECOGNIZED) ||
+               (err == OFONO_ERROR_INVALID_FORMAT))
+               _dial(ctx);
+       else if (err == OFONO_ERROR_OFFLINE)
+               gui_simple_popup("Offline", "System is offline!");
+       else if (err != OFONO_ERROR_NONE) {
+               char buf[256];
+               snprintf(buf, sizeof(buf), "Could not complete.<br>Error #%d",
+                               err);
+               gui_simple_popup("Error", buf);
+       } else
+               gui_simple_popup(NULL, str);
+}
+
+/* Procedure as ofono/doc/mmi-codes.txt:
+ * - send number to SupplementaryServices.Initiate()
+ * - if NotRecognized is returned, then forward VoiceCallManager.Dial()
+ */
+static void _call(Keypad *ctx)
+{
+       const char *number = eina_strbuf_string_get(ctx->number);
+       int len = eina_strbuf_length_get(ctx->number);
+
+       INF("calling %s...", number);
+
+       if ((len > 0) && (number[len - 1] == '#'))
+               ofono_ss_initiate(number, _ss_initiate_reply, ctx);
+       else
+               _dial(ctx);
+}
+
+static Eina_Bool _on_mod_timeout(void *data)
+{
+       Keypad *ctx = data;
+       size_t len = eina_strbuf_length_get(ctx->number);
+
+       if (len > 0) {
+               const char *str = eina_strbuf_string_get(ctx->number);
+               if (str[len - 1] == '0') {
+                       eina_strbuf_remove(ctx->number, len - 1, len);
+                       eina_strbuf_append_char(ctx->number, '+');
+                       _number_display(ctx);
+               }
+       }
+
+       ctx->mod_timeout = NULL;
+       return EINA_FALSE;
+}
+
+static Eina_Bool _on_rep_timeout(void *data)
+{
+       Keypad *ctx = data;
+       size_t len = eina_strbuf_length_get(ctx->number);
+
+       if (len > 0) {
+               eina_strbuf_remove(ctx->number, len - 1, len);
+               _number_display(ctx);
+       }
+
+       if (len == 1) {
+               ctx->rep_timeout = NULL;
+               return EINA_FALSE;
+       }
+
+       ecore_timer_interval_set(ctx->rep_timeout, REP_TIMEOUT);
+       return EINA_TRUE;
+}
+
+static void _on_pressed(void *data, Evas_Object *obj __UNUSED__,
+                       const char *emission, const char *source __UNUSED__)
+{
+       Keypad *ctx = data;
+       DBG("ctx=%p, signal: %s", ctx, emission);
+
+       EINA_SAFETY_ON_FALSE_RETURN(eina_str_has_prefix(emission, "pressed,"));
+       emission += strlen("pressed,");
+
+       if ((emission[0] >= '0') && (emission[0] <= '9')) {
+               eina_strbuf_append_char(ctx->number, emission[0]);
+               _number_display(ctx);
+
+               if (emission[0] == '0') {
+                       if (ctx->mod_timeout)
+                               ecore_timer_del(ctx->mod_timeout);
+                       ctx->mod_timeout = ecore_timer_add(MOD_TIMEOUT,
+                                               _on_mod_timeout, ctx);
+               }
+
+       } else if (strcmp(emission, "backspace") == 0) {
+               size_t len = eina_strbuf_length_get(ctx->number);
+               if (len > 0) {
+                       eina_strbuf_remove(ctx->number, len - 1, len);
+                       _number_display(ctx);
+                       if (len > 1) {
+                               if (ctx->rep_timeout)
+                                       ecore_timer_del(ctx->rep_timeout);
+                               ctx->rep_timeout = ecore_timer_add(
+                                       REP_TIMEOUT_INIT, _on_rep_timeout, ctx);
+                       }
+               }
+
+       } else if (strcmp(emission, "star") == 0) {
+               eina_strbuf_append_char(ctx->number, '*');
+               _number_display(ctx);
+       } else if (strcmp(emission, "hash") == 0) {
+               eina_strbuf_append_char(ctx->number, '#');
+               _number_display(ctx);
+       }
+
+       _handle_if_mmi(ctx);
+}
+
+static void _on_released(void *data, Evas_Object *obj __UNUSED__,
+                               const char *emission,
+                               const char *source __UNUSED__)
+{
+       Keypad *ctx = data;
+       DBG("ctx=%p, signal: %s", ctx, emission);
+
+       EINA_SAFETY_ON_FALSE_RETURN(eina_str_has_prefix(emission, "released,"));
+       emission += strlen("released,");
+
+       if (ctx->mod_timeout) {
+               if (emission[0] != '0')
+                       ERR("Expected '0' but got '%s' instead", emission);
+               ecore_timer_del(ctx->mod_timeout);
+               ctx->mod_timeout = NULL;
+       }
+
+       if (ctx->rep_timeout) {
+               if (strcmp(emission, "backspace") != 0)
+                       ERR("Expected 'backspace' but got '%s' instead",
+                               emission);
+               ecore_timer_del(ctx->rep_timeout);
+               ctx->rep_timeout = NULL;
+       }
+}
+
+static void _on_clicked(void *data, Evas_Object *obj __UNUSED__,
+                       const char *emission, const char *source __UNUSED__)
+{
+       Keypad *ctx = data;
+       DBG("ctx=%p, signal: %s", ctx, emission);
+
+       EINA_SAFETY_ON_FALSE_RETURN(eina_str_has_prefix(emission, "clicked,"));
+       emission += strlen("clicked,");
+
+       if (strcmp(emission, "call") == 0) {
+               _call(ctx);
+       } else if (strcmp(emission, "save") == 0) {
+               ERR("TODO save contact %s!",
+                       eina_strbuf_string_get(ctx->number));
+       }
+}
+
+static void _on_del(void *data, Evas *e __UNUSED__,
+                       Evas_Object *obj __UNUSED__, void *event __UNUSED__)
+{
+       Keypad *ctx = data;
+
+       if (ctx->mod_timeout)
+               ecore_timer_del(ctx->mod_timeout);
+       if (ctx->rep_timeout)
+               ecore_timer_del(ctx->rep_timeout);
+
+       eina_strbuf_free(ctx->number);
+       free(ctx);
+}
+
+Evas_Object *keypad_add(Evas_Object *parent) {
+       Keypad *ctx;
+       Evas_Object *obj = gui_layout_add(parent, "keypad");
+       EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL);
+
+       ctx = calloc(1, sizeof(Keypad));
+       ctx->self = obj;
+       ctx->number = eina_strbuf_new();
+
+       evas_object_data_set(obj, "keypad.ctx", ctx);
+
+       evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL,
+                                       _on_del, ctx);
+       elm_object_signal_callback_add(obj, "pressed,*", "keypad",
+                                       _on_pressed, ctx);
+       elm_object_signal_callback_add(obj, "released,*", "keypad",
+                                       _on_released, ctx);
+       elm_object_signal_callback_add(obj, "clicked,*", "keypad",
+                                       _on_clicked, ctx);
+
+       elm_object_part_text_set(obj, "elm.text.display", "");
+
+       return obj;
+}
+
+void keypad_number_set(Evas_Object *obj, const char *number,
+                       Eina_Bool auto_dial)
+{
+       Keypad *ctx;
+       EINA_SAFETY_ON_NULL_RETURN(obj);
+       EINA_SAFETY_ON_NULL_RETURN(number);
+
+       ctx = evas_object_data_get(obj, "keypad.ctx");
+       EINA_SAFETY_ON_NULL_RETURN(ctx);
+
+       eina_strbuf_reset(ctx->number);
+       eina_strbuf_append(ctx->number, number);
+       _number_display(ctx);
+       if (auto_dial)
+               _call(ctx);
+}
diff --git a/dialer/keypad.h b/dialer/keypad.h
new file mode 100644 (file)
index 0000000..b1333e0
--- /dev/null
@@ -0,0 +1,7 @@
+#ifndef _EFL_OFONO_KEYPAD_H__
+#define _EFL_OFONO_KEYPAD_H__ 1
+
+Evas_Object *keypad_add(Evas_Object *parent);
+void keypad_number_set(Evas_Object *obj, const char *number, Eina_Bool do_auto);
+
+#endif
diff --git a/dialer/log.h b/dialer/log.h
new file mode 100644 (file)
index 0000000..46c5038
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef _EFL_OFONO_LOG_H__
+#define _EFL_OFONO_LOG_H__ 1
+
+extern int _log_domain;
+extern int _app_exit_code;
+
+#define CRITICAL(...) EINA_LOG_DOM_CRIT(_log_domain, __VA_ARGS__)
+#define ERR(...)      EINA_LOG_DOM_ERR(_log_domain, __VA_ARGS__)
+#define WRN(...)      EINA_LOG_DOM_WARN(_log_domain, __VA_ARGS__)
+#define INF(...)      EINA_LOG_DOM_INFO(_log_domain, __VA_ARGS__)
+#define DBG(...)      EINA_LOG_DOM_DBG(_log_domain, __VA_ARGS__)
+
+#endif
diff --git a/dialer/main.c b/dialer/main.c
new file mode 100644 (file)
index 0000000..6659c72
--- /dev/null
@@ -0,0 +1,114 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <Elementary.h>
+#ifndef ELM_LIB_QUICKLAUNCH
+
+#include "log.h"
+#include "ofono.h"
+#include "gui.h"
+#include "rc.h"
+
+#include <Ecore_Getopt.h>
+
+static const Ecore_Getopt options = {
+       PACKAGE_NAME,
+       "%prog [options]",
+       PACKAGE_VERSION,
+       "(C) 2012 Intel Corporation",
+       "GPL-2" /* TODO: check license with Intel */,
+       "Phone Dialer using oFono and EFL.",
+       EINA_FALSE,
+       {ECORE_GETOPT_STORE_STR('m', "modem", "Modem object path in oFono."),
+        ECORE_GETOPT_STORE_UINT('a', "api", "oFono modem API mask."),
+        ECORE_GETOPT_VERSION('V', "version"),
+        ECORE_GETOPT_COPYRIGHT('C', "copyright"),
+        ECORE_GETOPT_LICENSE('L', "license"),
+        ECORE_GETOPT_HELP('h', "help"),
+        ECORE_GETOPT_SENTINEL
+       }
+};
+
+int _log_domain = -1;
+int _app_exit_code = EXIT_SUCCESS;
+
+EAPI int elm_main(int argc, char **argv)
+{
+       int args;
+       char *modem_path = NULL;
+       unsigned int modem_api = 0;
+       Eina_Bool quit_option = EINA_FALSE;
+       Ecore_Getopt_Value values[] = {
+               ECORE_GETOPT_VALUE_STR(modem_path),
+               ECORE_GETOPT_VALUE_UINT(modem_api),
+               ECORE_GETOPT_VALUE_BOOL(quit_option),
+               ECORE_GETOPT_VALUE_BOOL(quit_option),
+               ECORE_GETOPT_VALUE_BOOL(quit_option),
+               ECORE_GETOPT_VALUE_BOOL(quit_option),
+               ECORE_GETOPT_VALUE_NONE
+       };
+
+       _log_domain = eina_log_domain_register("dialer", NULL);
+       if (_log_domain < 0)
+       {
+               EINA_LOG_CRIT("Could not create log domain 'dialer'.");
+               elm_shutdown();
+               return EXIT_FAILURE;
+       }
+
+       args = ecore_getopt_parse(&options, values, argc, argv);
+       if (args < 0)
+       {
+               ERR("Could not parse command line options.");
+               _app_exit_code = EXIT_FAILURE;
+               goto end;
+       }
+       if (quit_option)
+               goto end;
+
+       if (!rc_init()) {
+               CRITICAL("Could not setup remote control via DBus.");
+               _app_exit_code = EXIT_FAILURE;
+               goto end;
+       }
+
+       if (!ofono_init()) {
+               CRITICAL("Could not setup ofono");
+               _app_exit_code = EXIT_FAILURE;
+               goto end_rc;
+       }
+
+       if (modem_path) {
+               INF("User-defined modem path: %s", modem_path);
+               ofono_modem_path_wanted_set(modem_path);
+       }
+
+       if (modem_api) {
+               INF("User-defined modem API mask: %#x", modem_api);
+               ofono_modem_api_require(modem_api);
+       }
+
+       if (!gui_init()) {
+               CRITICAL("Could not setup graphical user interface");
+               _app_exit_code = EXIT_FAILURE;
+               goto end_ofono;
+       }
+
+       INF("Entering main loop");
+       elm_run();
+       INF("Quit main loop");
+
+       gui_shutdown();
+
+end_ofono:
+       ofono_shutdown();
+end_rc:
+       rc_shutdown();
+end:
+       elm_shutdown();
+
+       return _app_exit_code;
+}
+
+#endif
+ELM_MAIN()
diff --git a/dialer/ofono.c b/dialer/ofono.c
new file mode 100644 (file)
index 0000000..63087a4
--- /dev/null
@@ -0,0 +1,1723 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <Elementary.h>
+
+#include "ofono.h"
+#include "log.h"
+
+typedef struct _OFono_Modem OFono_Modem;
+typedef struct _OFono_Bus_Object OFono_Bus_Object;
+
+static const char bus_name[] = "org.ofono";
+
+static E_DBus_Connection *bus_conn = NULL;
+static char *bus_id = NULL;
+static Eina_Hash *modems = NULL;
+static OFono_Modem *modem_selected = NULL;
+static const char *modem_path_wanted = NULL;
+static unsigned int modem_api_mask = (OFONO_API_SIM |
+                                       OFONO_API_VOICE |
+                                       OFONO_API_MSG |
+                                       OFONO_API_STK |
+                                       OFONO_API_CALL_FW);
+static E_DBus_Signal_Handler *sig_modem_added = NULL;
+static E_DBus_Signal_Handler *sig_modem_removed = NULL;
+static E_DBus_Signal_Handler *sig_modem_prop_changed = NULL;
+static DBusPendingCall *pc_get_modems = NULL;
+
+static void (*connected_cb)(void *) = NULL;
+static const void *connected_cb_data = NULL;
+
+static void (*disconnected_cb)(void *) = NULL;
+static const void *disconnected_cb_data = NULL;
+
+static void (*call_added_cb)(void *, OFono_Call *) = NULL;
+static const void *call_added_cb_data = NULL;
+
+static void (*call_removed_cb)(void *, OFono_Call *) = NULL;
+static const void *call_removed_cb_data = NULL;
+
+static void (*call_changed_cb)(void *, OFono_Call *) = NULL;
+static const void *call_changed_cb_data = NULL;
+
+static void (*call_disconnected_cb)(void *, OFono_Call *, const char *) = NULL;
+static const void *call_disconnected_cb_data = NULL;
+
+#define OFONO_SERVICE                  "org.ofono"
+
+#define OFONO_PREFIX                   OFONO_SERVICE "."
+#define OFONO_MODEM_IFACE              "Modem"
+#define OFONO_MANAGER_IFACE            "Manager"
+#define OFONO_SIM_IFACE                        "SimManager"
+#define OFONO_NETREG_IFACE             "NetworkRegistration"
+#define OFONO_VOICE_IFACE              "VoiceCallManager"
+#define OFONO_MSG_IFACE                        "MessageManager"
+#define OFONO_MSG_WAITING_IFACE                "MessageWaiting"
+#define OFONO_SMART_MSG_IFACE          "SmartMessaging"
+#define OFONO_STK_IFACE                        "SimToolkit"
+
+#define OFONO_CALL_FW_IFACE            "CallForwarding"
+#define OFONO_CALL_VOL_IFACE           "CallVolume"
+#define OFONO_CALL_METER_IFACE         "CallMeter"
+#define OFONO_CALL_SET_IFACE           "CallSettings"
+#define OFONO_CALL_BAR_IFACE           "CallBarring"
+#define OFONO_SUPPL_SERV_IFACE         "SupplementaryServices"
+#define OFONO_TXT_TEL_IFACE            "TextTelephony"
+#define OFONO_CELL_BROAD_IFACE         "CellBroadcast"
+#define OFONO_CONNMAN_IFACE            "ConnectionManager"
+#define OFONO_PUSH_NOTIF_IFACE         "PushNotification"
+#define OFONO_PHONEBOOK_IFACE          "Phonebook"
+#define OFONO_ASN_IFACE                        "AssistedSatelliteNavigation"
+
+static const struct API_Interface_Map {
+       unsigned int bit;
+       const char *name;
+       size_t namelen;
+} api_iface_map[] = {
+#define MAP(bit, name) {bit, name, sizeof(name) - 1}
+       MAP(OFONO_API_SIM, OFONO_SIM_IFACE),
+       MAP(OFONO_API_NETREG, OFONO_NETREG_IFACE),
+       MAP(OFONO_API_VOICE, OFONO_VOICE_IFACE),
+       MAP(OFONO_API_MSG, OFONO_MSG_IFACE),
+       MAP(OFONO_API_MSG_WAITING, OFONO_MSG_WAITING_IFACE),
+       MAP(OFONO_API_SMART_MSG, OFONO_SMART_MSG_IFACE),
+       MAP(OFONO_API_STK, OFONO_STK_IFACE),
+       MAP(OFONO_API_CALL_FW, OFONO_CALL_FW_IFACE),
+       MAP(OFONO_API_CALL_VOL, OFONO_CALL_VOL_IFACE),
+       MAP(OFONO_API_CALL_METER, OFONO_CALL_METER_IFACE),
+       MAP(OFONO_API_CALL_SET, OFONO_CALL_SET_IFACE),
+       MAP(OFONO_API_CALL_BAR, OFONO_CALL_BAR_IFACE),
+       MAP(OFONO_API_SUPPL_SERV, OFONO_SUPPL_SERV_IFACE),
+       MAP(OFONO_API_TXT_TEL, OFONO_TXT_TEL_IFACE),
+       MAP(OFONO_API_CELL_BROAD, OFONO_CELL_BROAD_IFACE),
+       MAP(OFONO_API_CONNMAN, OFONO_CONNMAN_IFACE),
+       MAP(OFONO_API_PUSH_NOTIF, OFONO_PUSH_NOTIF_IFACE),
+       MAP(OFONO_API_PHONEBOOK, OFONO_PHONEBOOK_IFACE),
+       MAP(OFONO_API_ASN, OFONO_ASN_IFACE),
+#undef MAP
+       {0, NULL, 0}
+};
+
+static Eina_Bool _dbus_bool_get(DBusMessageIter *itr)
+{
+       dbus_bool_t val;
+       dbus_message_iter_get_basic(itr, &val);
+       return val;
+}
+
+static const struct Error_Map {
+       OFono_Error id;
+       const char *name;
+       size_t namelen;
+} error_map[] = {
+#define MAP(id, name) {id, name, sizeof(name) - 1}
+       MAP(OFONO_ERROR_FAILED, "Failed"),
+       MAP(OFONO_ERROR_DOES_NOT_EXIST, "DoesNotExist"),
+       MAP(OFONO_ERROR_IN_PROGRESS, "InProgress"),
+       MAP(OFONO_ERROR_IN_USE, "InUse"),
+       MAP(OFONO_ERROR_INVALID_ARGS, "InvalidArguments"),
+       MAP(OFONO_ERROR_INVALID_FORMAT, "InvalidFormat"),
+       MAP(OFONO_ERROR_ACCESS_DENIED, "AccessDenied"),
+       MAP(OFONO_ERROR_ATTACH_IN_PROGRESS, "AttachInProgress"),
+       MAP(OFONO_ERROR_INCORRECT_PASSWORD, "IncorrectPassword"),
+       MAP(OFONO_ERROR_NOT_ACTIVE, "NotActive"),
+       MAP(OFONO_ERROR_NOT_ALLOWED, "NotAllowed"),
+       MAP(OFONO_ERROR_NOT_ATTACHED, "NotAttached"),
+       MAP(OFONO_ERROR_NOT_AVAILABLE, "NotAvailable"),
+       MAP(OFONO_ERROR_NOT_FOUND, "NotFound"),
+       MAP(OFONO_ERROR_NOT_IMPLEMENTED, "NotImplemented"),
+       MAP(OFONO_ERROR_NOT_RECOGNIZED, "NotRecognized"),
+       MAP(OFONO_ERROR_NOT_REGISTERED, "NotRegistered"),
+       MAP(OFONO_ERROR_NOT_SUPPORTED, "NotSupported"),
+       MAP(OFONO_ERROR_SIM_NOT_READY, "SimNotReady"),
+       MAP(OFONO_ERROR_STK, "SimToolkit"),
+       MAP(OFONO_ERROR_TIMEDOUT, "Timedout"),
+#undef MAP
+       {0, NULL, 0}
+};
+
+static OFono_Error _ofono_error_parse(const char *name)
+{
+       size_t namelen, prefixlen = sizeof(OFONO_PREFIX) - 1;
+       const struct Error_Map *itr;
+
+       /* whenever interfaces are not there due modem being offline */
+       if (strcmp(name, "org.freedesktop.DBus.Error.UnknownMethod") == 0)
+               return OFONO_ERROR_OFFLINE;
+
+       if (strncmp(name, OFONO_PREFIX, prefixlen) != 0)
+               return OFONO_ERROR_FAILED;
+
+       name += prefixlen;
+       namelen = strlen(name);
+       for (itr = error_map; itr->name != NULL; itr++)
+               if ((itr->namelen == namelen) &&
+                       (memcmp(name, itr->name, namelen) == 0))
+                       return itr->id;
+
+       return OFONO_ERROR_FAILED;
+}
+
+typedef struct _OFono_Simple_Cb_Context
+{
+       OFono_Simple_Cb cb;
+       const void *data;
+} OFono_Simple_Cb_Context;
+
+static void _ofono_simple_reply(void *data, DBusMessage *msg __UNUSED__,
+                               DBusError *err)
+{
+       OFono_Simple_Cb_Context *ctx = data;
+       OFono_Error e = OFONO_ERROR_NONE;
+
+       if (dbus_error_is_set(err)) {
+               DBG("%s: %s", err->name, err->message);
+               e = _ofono_error_parse(err->name);
+       }
+
+       if (ctx)
+               ctx->cb((void *)ctx->data, e);
+}
+
+typedef struct _OFono_String_Cb_Context
+{
+       OFono_String_Cb cb;
+       const void *data;
+       const char *name;
+       char *(*convert)(DBusMessage *msg);
+} OFono_String_Cb_Context;
+
+static void _ofono_string_reply(void *data, DBusMessage *msg, DBusError *err)
+{
+       OFono_String_Cb_Context *ctx = data;
+       OFono_Error e = OFONO_ERROR_NONE;
+       char *str = NULL;
+
+       if (dbus_error_is_set(err)) {
+               DBG("%s: %s", err->name, err->message);
+               e = _ofono_error_parse(err->name);
+       } else {
+               str = ctx->convert(msg);
+               if (!str)
+                       e = OFONO_ERROR_NOT_SUPPORTED;
+       }
+
+       if (ctx->cb)
+               ctx->cb((void *)ctx->data, e, str);
+       else
+               DBG("%s %s", ctx->name, str);
+
+       free(str);
+}
+
+struct _OFono_Pending
+{
+       EINA_INLIST;
+       DBusPendingCall *pending;
+       E_DBus_Method_Return_Cb cb;
+       void *data;
+       void *owner;
+};
+
+struct _OFono_Bus_Object
+{
+       const char *path;
+       Eina_Inlist *dbus_pending; /* of OFono_Pending */
+       Eina_List *dbus_signals; /* of E_DBus_Signal_Handler */
+};
+
+static void _bus_object_free(OFono_Bus_Object *o)
+{
+       E_DBus_Signal_Handler *sh;
+
+       eina_stringshare_del(o->path);
+
+       while (o->dbus_pending) {
+               ofono_pending_cancel(
+                       EINA_INLIST_CONTAINER_GET(o->dbus_pending,
+                                                       OFono_Pending));
+       }
+
+       EINA_LIST_FREE(o->dbus_signals, sh)
+               e_dbus_signal_handler_del(bus_conn, sh);
+
+       free(o);
+}
+
+static void _bus_object_message_send_reply(void *data, DBusMessage *reply,
+                                               DBusError *err)
+{
+       OFono_Pending *p = data;
+       OFono_Bus_Object *o = p->owner;
+
+       if (p->cb)
+               p->cb(p->data, reply, err);
+
+       o->dbus_pending = eina_inlist_remove(o->dbus_pending,
+                                               EINA_INLIST_GET(p));
+       free(p);
+}
+
+static OFono_Pending *_bus_object_message_send(OFono_Bus_Object *o,
+                                               DBusMessage *msg,
+                                               E_DBus_Method_Return_Cb cb,
+                                               void *data)
+{
+       OFono_Pending *p;
+       EINA_SAFETY_ON_NULL_GOTO(o, error);
+       EINA_SAFETY_ON_NULL_GOTO(msg, error);
+
+       p = calloc(1, sizeof(OFono_Pending));
+       EINA_SAFETY_ON_NULL_GOTO(p, error);
+
+       p->owner = o;
+       p->cb = cb;
+       p->data = data;
+
+       p->pending = e_dbus_message_send(
+               bus_conn, msg, _bus_object_message_send_reply, -1, p);
+       EINA_SAFETY_ON_NULL_GOTO(p->pending, error_send);
+
+       o->dbus_pending = eina_inlist_append(o->dbus_pending,
+                                               EINA_INLIST_GET(p));
+       return p;
+
+error_send:
+       free(p);
+error:
+       if (cb) {
+               DBusError err;
+               dbus_error_init(&err);
+               dbus_set_error(&err, "Failed", "call setup failed.");
+               cb(data, NULL, &err);
+       }
+       return NULL;
+}
+
+void ofono_pending_cancel(OFono_Pending *p)
+{
+       OFono_Bus_Object *o;
+
+       EINA_SAFETY_ON_NULL_RETURN(p);
+
+       o = p->owner;
+       o->dbus_pending = eina_inlist_remove(o->dbus_pending,
+                                               EINA_INLIST_GET(p));
+
+       if (p->cb) {
+               DBusError err;
+
+               dbus_error_init(&err);
+               dbus_set_error(&err, "Canceled",
+                               "Pending method call was canceled.");
+               p->cb(p->data, NULL, &err);
+       }
+       dbus_pending_call_cancel(p->pending);
+       free(p);
+}
+
+static void _bus_object_signal_listen(OFono_Bus_Object *o, const char *iface,
+                                       const char *name, E_DBus_Signal_Cb cb,
+                                       void *data)
+{
+       E_DBus_Signal_Handler *sh = e_dbus_signal_handler_add(
+               bus_conn, bus_id, o->path, iface, name, cb, data);
+       EINA_SAFETY_ON_NULL_RETURN(sh);
+
+       o->dbus_signals = eina_list_append(o->dbus_signals, sh);
+}
+
+struct _OFono_Call
+{
+       OFono_Bus_Object base;
+       const char *line_id;
+       const char *incoming_line;
+       const char *name;
+       OFono_Call_State state;
+       Eina_Bool multiparty : 1;
+       Eina_Bool emergency : 1;
+};
+
+struct _OFono_Modem
+{
+       OFono_Bus_Object base;
+       const char *name;
+       const char *serial;
+       Eina_Hash *calls;
+       unsigned int interfaces;
+       unsigned char strength;
+       unsigned char data_strength;
+       Eina_Bool ignored : 1;
+       Eina_Bool powered : 1;
+       Eina_Bool online : 1;
+       Eina_Bool roaming : 1;
+};
+
+static OFono_Call *_call_new(const char *path)
+{
+       OFono_Call *c = calloc(1, sizeof(OFono_Call));
+       EINA_SAFETY_ON_NULL_RETURN_VAL(c, NULL);
+
+       c->base.path = eina_stringshare_add(path);
+       EINA_SAFETY_ON_NULL_GOTO(c->base.path, error_path);
+
+       return c;
+
+error_path:
+       free(c);
+       return NULL;
+}
+
+static void _call_free(OFono_Call *c)
+{
+       DBG("c=%p %s", c, c->base.path);
+
+       if (call_removed_cb)
+               call_removed_cb((void *)call_removed_cb_data, c);
+
+       eina_stringshare_del(c->line_id);
+       eina_stringshare_del(c->incoming_line);
+       eina_stringshare_del(c->name);
+
+       _bus_object_free(&c->base);
+}
+
+static OFono_Call_State _call_state_parse(const char *str)
+{
+       if (strcmp(str, "active") == 0)
+               return OFONO_CALL_STATE_ACTIVE;
+       else if (strcmp(str, "held") == 0)
+               return OFONO_CALL_STATE_HELD;
+       else if (strcmp(str, "dialing") == 0)
+               return OFONO_CALL_STATE_DIALING;
+       else if (strcmp(str, "alerting") == 0)
+               return OFONO_CALL_STATE_ALERTING;
+       else if (strcmp(str, "incoming") == 0)
+               return OFONO_CALL_STATE_INCOMING;
+       else if (strcmp(str, "waiting") == 0)
+               return OFONO_CALL_STATE_WAITING;
+       else if (strcmp(str, "disconnected") == 0)
+               return OFONO_CALL_STATE_DISCONNECTED;
+
+       ERR("unknown call state: %s", str);
+       return OFONO_CALL_STATE_DISCONNECTED;
+}
+
+static void _call_property_update(OFono_Call *c, const char *key,
+                                       DBusMessageIter *value)
+{
+       if (strcmp(key, "LineIdentification") == 0) {
+               const char *str;
+               dbus_message_iter_get_basic(value, &str);
+               DBG("%s LineIdentification %s", c->base.path, str);
+               eina_stringshare_replace(&c->line_id, str);
+       } else if (strcmp(key, "IncomingLine") == 0) {
+               const char *str;
+               dbus_message_iter_get_basic(value, &str);
+               DBG("%s IncomingLine %s", c->base.path, str);
+               eina_stringshare_replace(&c->incoming_line, str);
+       } else if (strcmp(key, "State") == 0) {
+               const char *str;
+               OFono_Call_State state;
+               dbus_message_iter_get_basic(value, &str);
+               state = _call_state_parse(str);
+               DBG("%s State %s (%d)", c->base.path, str, state);
+               c->state = state;
+       } else if (strcmp(key, "Name") == 0) {
+               const char *str;
+               dbus_message_iter_get_basic(value, &str);
+               DBG("%s Name %s", c->base.path, str);
+               eina_stringshare_replace(&c->name, str);
+       } else if (strcmp(key, "Multiparty") == 0) {
+               dbus_bool_t v;
+               dbus_message_iter_get_basic(value, &v);
+               DBG("%s Multiparty %d", c->base.path, v);
+               c->multiparty = v;
+       } else if (strcmp(key, "Emergency") == 0) {
+               dbus_bool_t v;
+               dbus_message_iter_get_basic(value, &v);
+               DBG("%s Emergency %d", c->base.path, v);
+               c->emergency = v;
+       } else
+               DBG("%s %s (unused property)", c->base.path, key);
+}
+
+static void _call_property_changed(void *data, DBusMessage *msg)
+{
+       OFono_Call *c = data;
+       DBusMessageIter iter, value;
+       const char *key;
+
+       if (!msg || !dbus_message_iter_init(msg, &iter)) {
+               ERR("Could not handle message %p", msg);
+               return;
+       }
+
+       DBG("path=%s", c->base.path);
+
+       dbus_message_iter_get_basic(&iter, &key);
+       dbus_message_iter_next(&iter);
+       dbus_message_iter_recurse(&iter, &value);
+       _call_property_update(c, key, &value);
+
+       if (call_changed_cb)
+               call_changed_cb((void *)call_changed_cb_data, c);
+}
+
+static void _call_disconnect_reason(void *data, DBusMessage *msg)
+{
+       OFono_Call *c = data;
+       DBusError err;
+       const char *reason;
+
+       if (!msg) {
+               ERR("No message");
+               return;
+       }
+
+       DBG("path=%s", c->base.path);
+
+       dbus_error_init(&err);
+       if (!dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &reason,
+                                       DBUS_TYPE_INVALID)) {
+               ERR("Could not get DisconnectReason arguments: %s: %s",
+                       err.name, err.message);
+               dbus_error_free(&err);
+               return;
+       }
+
+       if (call_disconnected_cb)
+               call_disconnected_cb((void *)call_disconnected_cb_data, c,
+                       reason);
+}
+
+static void _call_add(OFono_Modem *m, const char *path, DBusMessageIter *prop)
+{
+       OFono_Call *c;
+
+       DBG("path=%s", path);
+
+       c = eina_hash_find(m->calls, path);
+       if (c) {
+               DBG("Call already exists %p (%s)", c, path);
+               goto update_properties;
+       }
+
+       c = _call_new(path);
+       EINA_SAFETY_ON_NULL_RETURN(c);
+       eina_hash_add(m->calls, c->base.path, c);
+
+       _bus_object_signal_listen(&c->base,
+                                       OFONO_PREFIX "VoiceCall",
+                                       "DisconnectReason",
+                                       _call_disconnect_reason, c);
+       _bus_object_signal_listen(&c->base,
+                                       OFONO_PREFIX "VoiceCall",
+                                       "PropertyChanged",
+                                       _call_property_changed, c);
+
+       if (call_added_cb)
+               call_added_cb((void *)call_added_cb_data, c);
+
+update_properties:
+       if (!prop)
+               return;
+       for (; dbus_message_iter_get_arg_type(prop) == DBUS_TYPE_DICT_ENTRY;
+                       dbus_message_iter_next(prop)) {
+               DBusMessageIter entry, value;
+               const char *key;
+
+               dbus_message_iter_recurse(prop, &entry);
+               dbus_message_iter_get_basic(&entry, &key);
+
+               dbus_message_iter_next(&entry);
+               dbus_message_iter_recurse(&entry, &value);
+
+               _call_property_update(c, key, &value);
+       }
+
+       if (call_changed_cb)
+               call_changed_cb((void *)call_changed_cb_data, c);
+}
+
+static void _call_remove(OFono_Modem *m, const char *path)
+{
+       DBG("path=%s", path);
+       eina_hash_del_by_key(m->calls, path);
+}
+
+static void _call_added(void *data, DBusMessage *msg)
+{
+       OFono_Modem *m = data;
+       DBusMessageIter iter, properties;
+       const char *path;
+
+       if (!msg || !dbus_message_iter_init(msg, &iter)) {
+               ERR("Could not handle message %p", msg);
+               return;
+       }
+
+       dbus_message_iter_get_basic(&iter, &path);
+
+       dbus_message_iter_next(&iter);
+       dbus_message_iter_recurse(&iter, &properties);
+
+       _call_add(m, path, &properties);
+}
+
+static void _call_removed(void *data, DBusMessage *msg)
+{
+       OFono_Modem *m = data;
+       DBusError err;
+       const char *path;
+
+       if (!msg) {
+               ERR("Could not handle message %p", msg);
+               return;
+       }
+
+       dbus_error_init(&err);
+       if (!dbus_message_get_args(msg, &err, DBUS_TYPE_OBJECT_PATH,
+                                       &path, NULL)) {
+               ERR("Could not get CallRemoved arguments: %s: %s",
+                       err.name, err.message);
+               dbus_error_free(&err);
+               return;
+       }
+
+       _call_remove(m, path);
+}
+
+OFono_Pending *ofono_call_hangup(OFono_Call *c, OFono_Simple_Cb cb,
+                                       const void *data)
+{
+       OFono_Simple_Cb_Context *ctx = NULL;
+       OFono_Pending *p;
+       DBusMessage *msg;
+
+       EINA_SAFETY_ON_NULL_RETURN_VAL(c, NULL);
+
+       if (cb) {
+               ctx = calloc(1, sizeof(OFono_Simple_Cb_Context));
+               EINA_SAFETY_ON_NULL_GOTO(ctx, error);
+               ctx->cb = cb;
+               ctx->data = data;
+       }
+
+       msg = dbus_message_new_method_call(
+               bus_id, c->base.path, OFONO_PREFIX "VoiceCall", "Hangup");
+       if (!msg)
+               goto error;
+
+       INF("Hangup(%s)", c->base.path);
+       p = _bus_object_message_send(&c->base, msg, _ofono_simple_reply, ctx);
+       dbus_message_unref(msg);
+       return p;
+
+error:
+       if (cb)
+               cb((void *)data, OFONO_ERROR_FAILED);
+       free(ctx);
+       return NULL;
+}
+
+OFono_Pending *ofono_call_answer(OFono_Call *c, OFono_Simple_Cb cb,
+                                       const void *data)
+{
+       OFono_Simple_Cb_Context *ctx = NULL;
+       OFono_Pending *p;
+       DBusMessage *msg;
+
+       EINA_SAFETY_ON_NULL_RETURN_VAL(c, NULL);
+
+       if (cb) {
+               ctx = calloc(1, sizeof(OFono_Simple_Cb_Context));
+               EINA_SAFETY_ON_NULL_GOTO(ctx, error);
+               ctx->cb = cb;
+               ctx->data = data;
+       }
+
+       msg = dbus_message_new_method_call(
+               bus_id, c->base.path, OFONO_PREFIX "VoiceCall", "Answer");
+       if (!msg)
+               goto error;
+
+       INF("Answer(%s)", c->base.path);
+       p = _bus_object_message_send(&c->base, msg, _ofono_simple_reply, ctx);
+       dbus_message_unref(msg);
+       return p;
+
+error:
+       if (cb)
+               cb((void *)data, OFONO_ERROR_FAILED);
+       free(ctx);
+       return NULL;
+}
+
+OFono_Call_State ofono_call_state_get(const OFono_Call *c)
+{
+       EINA_SAFETY_ON_NULL_RETURN_VAL(c, OFONO_CALL_STATE_DISCONNECTED);
+       return c->state;
+}
+
+const char *ofono_call_name_get(const OFono_Call *c)
+{
+       EINA_SAFETY_ON_NULL_RETURN_VAL(c, NULL);
+       return c->name;
+}
+
+const char *ofono_call_line_id_get(const OFono_Call *c)
+{
+       EINA_SAFETY_ON_NULL_RETURN_VAL(c, NULL);
+       return c->line_id;
+}
+
+static OFono_Modem *_modem_selected_get(void)
+{
+       OFono_Modem *found_path = NULL, *found_api = NULL, *m;
+       unsigned int online = 0, powered = 0;
+       Eina_Iterator *itr;
+
+       if (modem_selected)
+               return modem_selected;
+
+       itr = eina_hash_iterator_data_new(modems);
+       EINA_ITERATOR_FOREACH(itr, m) {
+               if (m->ignored)
+                       continue;
+
+               if (m->online)
+                       online++;
+               if (m->powered)
+                       powered++;
+
+               if ((modem_path_wanted) && (!found_path)) {
+                       DBG("m=%s, wanted=%s", m->base.path, modem_path_wanted);
+                       if (m->base.path == modem_path_wanted) {
+                               found_path = m;
+                               break;
+                       }
+               }
+
+               if (!found_api) {
+                       DBG("m=%#x, mask=%#x", m->interfaces, modem_api_mask);
+                       if ((m->interfaces & modem_api_mask) == modem_api_mask)
+                               found_api = m;
+               }
+       }
+       eina_iterator_free(itr);
+
+       INF("found_path=%s, found_api=%s, wanted_path=%s, api_mask=%#x",
+               found_path ? found_path->base.path : "",
+               found_api ? found_api->base.path: "",
+               modem_path_wanted ? modem_path_wanted : "",
+               modem_api_mask);
+
+       if (!powered)
+               ERR("No modems powered! Run connman or test/enable-modem");
+       if (!online)
+               WRN("No modems online! Run connman or test/online-modem");
+
+       modem_selected = found_path ? found_path : found_api;
+       return modem_selected;
+}
+
+static void _ofono_calls_get_reply(void *data, DBusMessage *msg,
+                                       DBusError *err)
+{
+       OFono_Modem *m = data;
+       DBusMessageIter array, dict;
+
+       if (!msg) {
+               if (err)
+                       ERR("%s: %s", err->name, err->message);
+               else
+                       ERR("No message");
+               return;
+       }
+
+       eina_hash_free_buckets(m->calls);
+
+       if (!dbus_message_iter_init(msg, &array)) {
+               ERR("Could not get calls");
+               return;
+       }
+
+       dbus_message_iter_recurse(&array, &dict);
+       for (; dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_STRUCT;
+                       dbus_message_iter_next(&dict)) {
+               DBusMessageIter value, properties;
+               const char *path;
+
+               dbus_message_iter_recurse(&dict, &value);
+               dbus_message_iter_get_basic(&value, &path);
+
+               dbus_message_iter_next(&value);
+               dbus_message_iter_recurse(&value, &properties);
+
+               _call_add(m, path, &properties);
+       }
+}
+
+static void _modem_calls_load(OFono_Modem *m)
+{
+       DBusMessage *msg = dbus_message_new_method_call(
+               bus_id, m->base.path, OFONO_PREFIX OFONO_VOICE_IFACE,
+               "GetCalls");
+
+       DBG("Get calls of %s", m->base.path);
+       _bus_object_message_send(&m->base, msg, _ofono_calls_get_reply, m);
+       dbus_message_unref(msg);
+}
+
+static OFono_Modem *_modem_new(const char *path)
+{
+       OFono_Modem *m = calloc(1, sizeof(OFono_Modem));
+       EINA_SAFETY_ON_NULL_RETURN_VAL(m, NULL);
+
+       m->base.path = eina_stringshare_add(path);
+       EINA_SAFETY_ON_NULL_GOTO(m->base.path, error_path);
+
+       m->calls = eina_hash_string_small_new(EINA_FREE_CB(_call_free));
+       EINA_SAFETY_ON_NULL_GOTO(m->calls, error_calls);
+
+       return m;
+
+error_calls:
+       eina_stringshare_del(m->base.path);
+error_path:
+       free(m);
+       return NULL;
+}
+
+static void _modem_free(OFono_Modem *m)
+{
+       DBG("m=%p %s", m, m->base.path);
+
+       if (modem_selected == m)
+               modem_selected = NULL;
+
+       eina_stringshare_del(m->name);
+       eina_stringshare_del(m->serial);
+
+       eina_hash_free(m->calls);
+
+       _bus_object_free(&m->base);
+}
+
+static unsigned int _modem_interfaces_extract(DBusMessageIter *array)
+{
+       DBusMessageIter entry;
+       unsigned int interfaces = 0;
+
+       dbus_message_iter_recurse(array, &entry);
+       for (; dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRING;
+                       dbus_message_iter_next(&entry)) {
+               const struct API_Interface_Map *itr;
+               const char *name;
+               size_t namelen;
+
+               dbus_message_iter_get_basic(&entry, &name);
+
+               if (strncmp(name, OFONO_PREFIX, strlen(OFONO_PREFIX)) != 0)
+                       continue;
+
+               name += strlen(OFONO_PREFIX);
+               namelen = strlen(name);
+
+               DBG("interface: %s", name);
+               for (itr = api_iface_map; itr->name != NULL; itr++) {
+                       if ((itr->namelen == namelen) &&
+                               (memcmp(itr->name, name, namelen) == 0)) {
+                               interfaces |= itr->bit;
+                               break;
+                       }
+               }
+               if (itr->name == NULL)
+                       WRN("ignored %s", name);
+       }
+
+       return interfaces;
+}
+
+static void _modem_property_update(OFono_Modem *m, const char *key,
+                                       DBusMessageIter *value)
+{
+       if (strcmp(key, "Powered") == 0) {
+               m->powered = _dbus_bool_get(value);
+               DBG("%s Powered %d", m->base.path, m->powered);
+       } else if (strcmp(key, "Online") == 0) {
+               m->online = _dbus_bool_get(value);
+               DBG("%s Online %d", m->base.path, m->online);
+       } else if (strcmp(key, "Interfaces") == 0) {
+               unsigned int ifaces = _modem_interfaces_extract(value);
+               DBG("%s Interfaces 0x%02x", m->base.path, ifaces);
+               if (m->interfaces != ifaces) {
+                       m->interfaces = ifaces;
+
+                       if (modem_selected && modem_path_wanted &&
+                               modem_selected->base.path != modem_path_wanted)
+                               modem_selected = NULL;
+               }
+       } else if (strcmp(key, "Serial") == 0) {
+               const char *serial;
+               dbus_message_iter_get_basic(value, &serial);
+               DBG("%s Serial %s", m->base.path, serial);
+               eina_stringshare_replace(&m->serial, serial);
+       } else if (strcmp(key, "Type") == 0) {
+               const char *type;
+               dbus_message_iter_get_basic(value, &type);
+               DBG("%s Type %s", m->base.path, type);
+               m->ignored = strcmp(type, "hardware") != 0;
+       } else
+               DBG("%s %s (unused property)", m->base.path, key);
+}
+
+static void _modem_add(const char *path, DBusMessageIter *prop)
+{
+       OFono_Modem *m;
+
+       DBG("path=%s", path);
+
+       m = eina_hash_find(modems, path);
+       if (m) {
+               DBG("Modem already exists %p (%s)", m, path);
+               goto update_properties;
+       }
+
+       m = _modem_new(path);
+       EINA_SAFETY_ON_NULL_RETURN(m);
+       eina_hash_add(modems, m->base.path, m);
+
+       _bus_object_signal_listen(&m->base, OFONO_PREFIX OFONO_VOICE_IFACE,
+                                       "CallAdded", _call_added, m);
+       _bus_object_signal_listen(&m->base, OFONO_PREFIX OFONO_VOICE_IFACE,
+                                       "CallRemoved", _call_removed, m);
+
+       /* TODO: do we need to listen to BarringActive or Forwarded? */
+
+       if (modem_selected && modem_path_wanted &&
+               modem_selected->base.path != modem_path_wanted)
+               modem_selected = NULL;
+
+update_properties:
+       if (!prop)
+               return;
+       for (; dbus_message_iter_get_arg_type(prop) == DBUS_TYPE_DICT_ENTRY;
+                       dbus_message_iter_next(prop)) {
+               DBusMessageIter entry, value;
+               const char *key;
+
+               dbus_message_iter_recurse(prop, &entry);
+               dbus_message_iter_get_basic(&entry, &key);
+
+               dbus_message_iter_next(&entry);
+               dbus_message_iter_recurse(&entry, &value);
+
+               _modem_property_update(m, key, &value);
+       }
+
+       if (m->interfaces & OFONO_API_VOICE)
+               _modem_calls_load(m);
+}
+
+static void _modem_remove(const char *path)
+{
+       DBG("path=%s", path);
+       eina_hash_del_by_key(modems, path);
+}
+
+static void _ofono_modems_get_reply(void *data __UNUSED__, DBusMessage *msg,
+                                       DBusError *err)
+{
+       DBusMessageIter array, dict;
+
+       pc_get_modems = NULL;
+
+       if (!msg) {
+               if (err)
+                       ERR("%s: %s", err->name, err->message);
+               else
+                       ERR("No message");
+               return;
+       }
+
+       EINA_SAFETY_ON_NULL_RETURN(modems);
+       eina_hash_free_buckets(modems);
+
+       if (!dbus_message_iter_init(msg, &array)) {
+               ERR("Could not get modems");
+               return;
+       }
+
+       dbus_message_iter_recurse(&array, &dict);
+       for (; dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_STRUCT;
+                       dbus_message_iter_next(&dict)) {
+               DBusMessageIter value, properties;
+               const char *path;
+
+               dbus_message_iter_recurse(&dict, &value);
+               dbus_message_iter_get_basic(&value, &path);
+
+               dbus_message_iter_next(&value);
+               dbus_message_iter_recurse(&value, &properties);
+
+               _modem_add(path, &properties);
+       }
+}
+
+static void _modem_added(void *data __UNUSED__, DBusMessage *msg)
+{
+       DBusMessageIter iter, properties;
+       const char *path;
+
+       if (!msg || !dbus_message_iter_init(msg, &iter)) {
+               ERR("Could not handle message %p", msg);
+               return;
+       }
+
+       dbus_message_iter_get_basic(&iter, &path);
+
+       dbus_message_iter_next(&iter);
+       dbus_message_iter_recurse(&iter, &properties);
+
+       _modem_add(path, &properties);
+}
+
+static void _modem_removed(void *data __UNUSED__, DBusMessage *msg)
+{
+       DBusError err;
+       const char *path;
+
+       if (!msg) {
+               ERR("Could not handle message %p", msg);
+               return;
+       }
+
+       dbus_error_init(&err);
+       if (!dbus_message_get_args(msg, &err, DBUS_TYPE_OBJECT_PATH,
+                                       &path, NULL)) {
+               ERR("Could not get ModemRemoved arguments: %s: %s",
+                       err.name, err.message);
+               dbus_error_free(&err);
+               return;
+       }
+
+       _modem_remove(path);
+}
+
+static void _modem_property_changed(void *data __UNUSED__, DBusMessage *msg)
+{
+       const char *path;
+       OFono_Modem *m;
+       DBusMessageIter iter, value;
+       const char *key;
+
+       if (!msg || !dbus_message_iter_init(msg, &iter)) {
+               ERR("Could not handle message %p", msg);
+               return;
+       }
+
+       path = dbus_message_get_path(msg);
+       DBG("path=%s", path);
+
+       m = eina_hash_find(modems, path);
+       if (!m) {
+               DBG("Modem is unknown (%s)", path);
+               return;
+       }
+
+       dbus_message_iter_get_basic(&iter, &key);
+       dbus_message_iter_next(&iter);
+       dbus_message_iter_recurse(&iter, &value);
+       _modem_property_update(m, key, &value);
+}
+
+static void _modems_load(void)
+{
+       DBusMessage *msg = dbus_message_new_method_call(
+               bus_id, "/", OFONO_PREFIX OFONO_MANAGER_IFACE, "GetModems");
+
+       if (pc_get_modems)
+               dbus_pending_call_cancel(pc_get_modems);
+
+       DBG("Get modems");
+       pc_get_modems = e_dbus_message_send(
+               bus_conn, msg, _ofono_modems_get_reply, -1, NULL);
+       dbus_message_unref(msg);
+}
+
+static void _ofono_connected(const char *id)
+{
+       free(bus_id);
+       bus_id = strdup(id);
+
+       sig_modem_added = e_dbus_signal_handler_add(
+               bus_conn, bus_id, "/",
+               OFONO_PREFIX OFONO_MANAGER_IFACE,
+               "ModemAdded",
+               _modem_added, NULL);
+
+       sig_modem_removed = e_dbus_signal_handler_add(
+               bus_conn, bus_id, "/",
+               OFONO_PREFIX OFONO_MANAGER_IFACE,
+               "ModemRemoved",
+               _modem_removed, NULL);
+
+       sig_modem_prop_changed = e_dbus_signal_handler_add(
+               bus_conn, bus_id, NULL,
+               OFONO_PREFIX OFONO_MODEM_IFACE,
+               "PropertyChanged",
+               _modem_property_changed, NULL);
+
+       _modems_load();
+
+       if (connected_cb)
+               connected_cb((void *)connected_cb_data);
+}
+
+static void _ofono_disconnected(void)
+{
+       eina_hash_free_buckets(modems);
+
+       if (sig_modem_added) {
+               e_dbus_signal_handler_del(bus_conn, sig_modem_added);
+               sig_modem_added = NULL;
+       }
+
+       if (sig_modem_removed) {
+               e_dbus_signal_handler_del(bus_conn, sig_modem_removed);
+               sig_modem_removed = NULL;
+       }
+
+       if (sig_modem_prop_changed) {
+               e_dbus_signal_handler_del(bus_conn, sig_modem_prop_changed);
+               sig_modem_prop_changed = NULL;
+       }
+
+       if (bus_id) {
+               if (disconnected_cb)
+                       disconnected_cb((void *)disconnected_cb_data);
+
+               free(bus_id);
+               bus_id = NULL;
+       }
+}
+
+static void _name_owner_changed(void *data __UNUSED__, DBusMessage *msg)
+{
+       DBusError err;
+       const char *name, *from, *to;
+
+       dbus_error_init(&err);
+       if (!dbus_message_get_args(msg, &err,
+                                       DBUS_TYPE_STRING, &name,
+                                       DBUS_TYPE_STRING, &from,
+                                       DBUS_TYPE_STRING, &to,
+                                       DBUS_TYPE_INVALID)) {
+               ERR("Could not get NameOwnerChanged arguments: %s: %s",
+                       err.name, err.message);
+               dbus_error_free(&err);
+               return;
+       }
+
+       if (strcmp(name, bus_name) != 0)
+               return;
+
+       DBG("NameOwnerChanged %s from=%s to=%s", name, from, to);
+
+       if (from[0] == '\0' && to[0] != '\0') {
+               INF("oFono appeared as %s", to);
+               _ofono_connected(to);
+       } else if (from[0] != '\0' && to[0] == '\0') {
+               INF("oFono disappeared from %s", from);
+               _ofono_disconnected();
+       }
+}
+
+static void _ofono_get_name_owner(void *data __UNUSED__, DBusMessage *msg, DBusError *err)
+{
+       DBusMessageIter itr;
+       const char *id;
+
+       if (!msg) {
+               if (err)
+                       ERR("%s: %s", err->name, err->message);
+               else
+                       ERR("No message");
+               return;
+       }
+
+       dbus_message_iter_init(msg, &itr);
+       dbus_message_iter_get_basic(&itr, &id);
+       if (!id || id[0] == '\0') {
+               ERR("No name owner fo %s!", bus_name);
+               return;
+       }
+
+       INF("oFono bus id: %s", id);
+       _ofono_connected(id);
+}
+
+OFono_Pending *ofono_modem_change_pin(const char *what, const char *old,
+                                       const char *new, OFono_Simple_Cb cb,
+                                       const void *data)
+{
+       OFono_Simple_Cb_Context *ctx = NULL;
+       OFono_Error err = OFONO_ERROR_OFFLINE;
+       OFono_Pending *p;
+       DBusMessage *msg;
+       OFono_Modem *m = _modem_selected_get();
+       EINA_SAFETY_ON_NULL_GOTO(m, error);
+       EINA_SAFETY_ON_NULL_GOTO(what, error);
+       EINA_SAFETY_ON_NULL_GOTO(old, error);
+       EINA_SAFETY_ON_NULL_GOTO(new, error);
+
+       if ((m->interfaces & OFONO_API_SIM) == 0)
+               goto error;
+       err = OFONO_ERROR_FAILED;
+
+       if (cb) {
+               ctx = calloc(1, sizeof(OFono_Simple_Cb_Context));
+               EINA_SAFETY_ON_NULL_GOTO(ctx, error);
+               ctx->cb = cb;
+               ctx->data = data;
+       }
+
+       msg = dbus_message_new_method_call(
+               bus_id, m->base.path, OFONO_PREFIX OFONO_SIM_IFACE,
+               "ChangePin");
+       if (!msg)
+               goto error;
+
+       if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &what,
+                                       DBUS_TYPE_STRING, &old,
+                                       DBUS_TYPE_STRING, &new,
+                                       DBUS_TYPE_INVALID))
+               goto error_message;
+
+       INF("ChangePin(%s, %s, %s)", what, old, new);
+       p = _bus_object_message_send(&m->base, msg, _ofono_simple_reply, ctx);
+       dbus_message_unref(msg);
+       return p;
+
+error_message:
+       dbus_message_unref(msg);
+error:
+       if (cb)
+               cb((void *)data, err);
+       free(ctx);
+       return NULL;
+}
+
+OFono_Pending *ofono_modem_reset_pin(const char *what, const char *puk,
+                                       const char *new, OFono_Simple_Cb cb,
+                                       const void *data)
+{
+       OFono_Simple_Cb_Context *ctx = NULL;
+       OFono_Error err = OFONO_ERROR_OFFLINE;
+       OFono_Pending *p;
+       DBusMessage *msg;
+       OFono_Modem *m = _modem_selected_get();
+       EINA_SAFETY_ON_NULL_GOTO(m, error);
+       EINA_SAFETY_ON_NULL_GOTO(what, error);
+       EINA_SAFETY_ON_NULL_GOTO(puk, error);
+       EINA_SAFETY_ON_NULL_GOTO(new, error);
+
+       if ((m->interfaces & OFONO_API_SIM) == 0)
+               goto error;
+       err = OFONO_ERROR_FAILED;
+
+       if (cb) {
+               ctx = calloc(1, sizeof(OFono_Simple_Cb_Context));
+               EINA_SAFETY_ON_NULL_GOTO(ctx, error);
+               ctx->cb = cb;
+               ctx->data = data;
+       }
+
+       msg = dbus_message_new_method_call(
+               bus_id, m->base.path, OFONO_PREFIX OFONO_SIM_IFACE, "ResetPin");
+       if (!msg)
+               goto error;
+
+       if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &what,
+                                       DBUS_TYPE_STRING, &puk,
+                                       DBUS_TYPE_STRING, &new,
+                                       DBUS_TYPE_INVALID))
+               goto error_message;
+
+       INF("ResetPin(%s, %s, %s)", what, puk, new);
+       p = _bus_object_message_send(&m->base, msg, _ofono_simple_reply, ctx);
+       dbus_message_unref(msg);
+       return p;
+
+error_message:
+       dbus_message_unref(msg);
+error:
+       if (cb)
+               cb((void *)data, err);
+       free(ctx);
+       return NULL;
+}
+
+static char *_ss_initiate_convert_ussd(const char *type __UNUSED__,
+                                       DBusMessageIter *itr)
+{
+       const char *ussd_response;
+
+       if (dbus_message_iter_get_arg_type(itr) != DBUS_TYPE_STRING) {
+               ERR("Invalid type: %c (expected: %c)",
+                       dbus_message_iter_get_arg_type(itr), DBUS_TYPE_STRING);
+               return NULL;
+       }
+       dbus_message_iter_get_basic(itr, &ussd_response);
+       EINA_SAFETY_ON_NULL_RETURN_VAL(ussd_response, NULL);
+       return strdup(ussd_response);
+}
+
+static void _ss_initiate_cb_dict_convert(Eina_Strbuf *buf,
+                                               DBusMessageIter *dict)
+{
+       for (; dbus_message_iter_get_arg_type(dict) == DBUS_TYPE_DICT_ENTRY;
+                       dbus_message_iter_next(dict)) {
+               DBusMessageIter e, v;
+               const char *key, *value;
+
+               dbus_message_iter_recurse(dict, &e);
+               dbus_message_iter_get_basic(&e, &key);
+
+               dbus_message_iter_next(&e);
+               dbus_message_iter_recurse(&e, &v);
+               dbus_message_iter_get_basic(&v, &value);
+
+               eina_strbuf_append_printf(buf, "&nbsp;&nbsp;&nbsp;%s=%s<br>",
+                                               key, value);
+       }
+}
+
+static char *_ss_initiate_convert_call1(const char *type, DBusMessageIter *itr)
+{
+       DBusMessageIter array, dict;
+       const char *ss_op, *service;
+       Eina_Strbuf *buf;
+       char *str;
+
+       dbus_message_iter_recurse(itr, &array);
+
+       if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_STRING) {
+               ERR("Invalid type: %c (expected: %c)",
+                       dbus_message_iter_get_arg_type(&array),
+                       DBUS_TYPE_STRING);
+               return NULL;
+       }
+       dbus_message_iter_get_basic(&array, &ss_op);
+       EINA_SAFETY_ON_NULL_RETURN_VAL(ss_op, NULL);
+
+       if (!dbus_message_iter_next(&array)) {
+               ERR("Missing %s service", type);
+               return NULL;
+       }
+
+       if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_STRING) {
+               ERR("Invalid type: %c (expected: %c)",
+                       dbus_message_iter_get_arg_type(&array),
+                       DBUS_TYPE_STRING);
+               return NULL;
+       }
+       dbus_message_iter_get_basic(&array, &service);
+       EINA_SAFETY_ON_NULL_RETURN_VAL(service, NULL);
+
+       if (!dbus_message_iter_next(&array)) {
+               ERR("Missing %s information", type);
+               return NULL;
+       }
+
+       buf = eina_strbuf_new();
+       eina_strbuf_append_printf(buf, "<b>%s %s=%s</b><br><br>",
+                                       type, ss_op, service);
+
+       dbus_message_iter_recurse(&array, &dict);
+       _ss_initiate_cb_dict_convert(buf, &dict);
+
+       str = eina_strbuf_string_steal(buf);
+       eina_strbuf_free(buf);
+       return str;
+}
+
+static char *_ss_initiate_convert_call_waiting(const char *type,
+                                               DBusMessageIter *itr)
+{
+       DBusMessageIter array, dict;
+       const char *ss_op;
+       Eina_Strbuf *buf;
+       char *str;
+
+       dbus_message_iter_recurse(itr, &array);
+
+       if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_STRING) {
+               ERR("Invalid type: %c (expected: %c)",
+                       dbus_message_iter_get_arg_type(&array),
+                       DBUS_TYPE_STRING);
+               return NULL;
+       }
+       dbus_message_iter_get_basic(&array, &ss_op);
+       EINA_SAFETY_ON_NULL_RETURN_VAL(ss_op, NULL);
+
+       if (!dbus_message_iter_next(&array)) {
+               ERR("Missing %s information", type);
+               return NULL;
+       }
+
+       buf = eina_strbuf_new();
+       eina_strbuf_append_printf(buf, "<b>%s %s</b><br><br>",
+                                       type, ss_op);
+
+       dbus_message_iter_recurse(&array, &dict);
+       _ss_initiate_cb_dict_convert(buf, &dict);
+
+       str = eina_strbuf_string_steal(buf);
+       eina_strbuf_free(buf);
+       return str;
+}
+
+static char *_ss_initiate_convert_call2(const char *type,
+                                               DBusMessageIter *itr)
+{
+       DBusMessageIter array;
+       const char *ss_op, *status;
+       Eina_Strbuf *buf;
+       char *str;
+
+       dbus_message_iter_recurse(itr, &array);
+
+       if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_STRING) {
+               ERR("Invalid type: %c (expected: %c)",
+                       dbus_message_iter_get_arg_type(&array),
+                       DBUS_TYPE_STRING);
+               return NULL;
+       }
+       dbus_message_iter_get_basic(&array, &ss_op);
+       EINA_SAFETY_ON_NULL_RETURN_VAL(ss_op, NULL);
+
+       if (!dbus_message_iter_next(&array)) {
+               ERR("Missing %s status", type);
+               return NULL;
+       }
+
+       if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_STRING) {
+               ERR("Invalid type: %c (expected: %c)",
+                       dbus_message_iter_get_arg_type(&array),
+                       DBUS_TYPE_STRING);
+               return NULL;
+       }
+       dbus_message_iter_get_basic(&array, &status);
+       EINA_SAFETY_ON_NULL_RETURN_VAL(status, NULL);
+
+       buf = eina_strbuf_new();
+       eina_strbuf_append_printf(buf, "<b>%s:</b><br><br>%s=%s",
+                                       type, ss_op, status);
+
+       str = eina_strbuf_string_steal(buf);
+       eina_strbuf_free(buf);
+       return str;
+}
+
+static const struct SS_Initiate_Convert_Map {
+       const char *type;
+       size_t typelen;
+       char *(*convert)(const char *type, DBusMessageIter *itr);
+} ss_initiate_convert_map[] = {
+#define MAP(type, conv) {type, sizeof(type) - 1, conv}
+       MAP("USSD", _ss_initiate_convert_ussd),
+       MAP("CallBarring", _ss_initiate_convert_call1),
+       MAP("CallForwarding", _ss_initiate_convert_call1),
+       MAP("CallWaiting", _ss_initiate_convert_call_waiting),
+       MAP("CallingLinePresentation", _ss_initiate_convert_call2),
+       MAP("ConnectedLinePresentation", _ss_initiate_convert_call2),
+       MAP("CallingLineRestriction", _ss_initiate_convert_call2),
+       MAP("ConnectedLineRestriction", _ss_initiate_convert_call2),
+#undef MAP
+       {NULL, 0, NULL}
+};
+
+static char *_ss_initiate_convert(DBusMessage *msg)
+{
+       DBusMessageIter array, variant;
+       const struct SS_Initiate_Convert_Map *citr;
+       const char *type = NULL;
+       size_t typelen;
+
+       if (!dbus_message_iter_init(msg, &array))
+               goto error;
+
+       if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_STRING) {
+               ERR("Invalid type for first argument: %c (expected: %c)",
+                       dbus_message_iter_get_arg_type(&array),
+                       DBUS_TYPE_STRING);
+               goto error;
+       }
+       dbus_message_iter_get_basic(&array, &type);
+       if (!type) {
+               ERR("Couldn't get SupplementaryServices.Initiate type");
+               goto error;
+       }
+       DBG("type: %s", type);
+
+       if (!dbus_message_iter_next(&array)) {
+               ERR("Couldn't get SupplementaryServices.Initiate payload");
+               goto error;
+       }
+       dbus_message_iter_recurse(&array, &variant);
+
+       typelen = strlen(type);
+       for (citr = ss_initiate_convert_map; citr->type != NULL; citr++) {
+               if ((citr->typelen == typelen) &&
+                       (memcmp(citr->type, type, typelen) == 0)) {
+                       return citr->convert(type, &variant);
+               }
+       }
+       ERR("Could not convert SupplementaryServices.Initiate type %s", type);
+
+error:
+       return NULL;
+}
+
+OFono_Pending *ofono_ss_initiate(const char *command, OFono_String_Cb cb, const void *data)
+{
+       OFono_String_Cb_Context *ctx = NULL;
+       OFono_Error err = OFONO_ERROR_OFFLINE;
+       OFono_Pending *p;
+       DBusMessage *msg;
+       OFono_Modem *m = _modem_selected_get();
+       EINA_SAFETY_ON_NULL_GOTO(m, error);
+       EINA_SAFETY_ON_NULL_GOTO(command, error);
+
+       if ((m->interfaces & OFONO_API_SUPPL_SERV) == 0)
+               goto error;
+       err = OFONO_ERROR_FAILED;
+
+       ctx = calloc(1, sizeof(OFono_String_Cb_Context));
+       EINA_SAFETY_ON_NULL_GOTO(ctx, error);
+       ctx->cb = cb;
+       ctx->data = data;
+       ctx->name = OFONO_PREFIX OFONO_SUPPL_SERV_IFACE ".Initiate";
+       ctx->convert = _ss_initiate_convert;
+
+       msg = dbus_message_new_method_call(
+               bus_id, m->base.path, OFONO_PREFIX OFONO_SUPPL_SERV_IFACE,
+               "Initiate");
+       if (!msg)
+               goto error;
+
+       if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &command,
+                                       DBUS_TYPE_INVALID))
+               goto error_message;
+
+       INF("SupplementaryServices.Initiate(%s)", command);
+       p = _bus_object_message_send(&m->base, msg, _ofono_string_reply, ctx);
+       dbus_message_unref(msg);
+       return p;
+
+error_message:
+       dbus_message_unref(msg);
+error:
+       if (cb)
+               cb((void *)data, err, NULL);
+       free(ctx);
+       return NULL;
+}
+
+typedef struct _OFono_Call_Cb_Context
+{
+       OFono_Call_Cb cb;
+       OFono_Modem *modem;
+       const void *data;
+       const char *name;
+} OFono_Call_Cb_Context;
+
+static void _ofono_dial_reply(void *data, DBusMessage *msg, DBusError *err)
+{
+       OFono_Call_Cb_Context *ctx = data;
+       OFono_Call *c = NULL;
+       OFono_Error oe = OFONO_ERROR_NONE;
+
+       if (!msg) {
+               DBG("%s: %s", err->name, err->message);
+               oe = _ofono_error_parse(err->name);
+       } else {
+               DBusError e;
+               const char *path;
+               dbus_error_init(&e);
+               if (!dbus_message_get_args(msg, &e, DBUS_TYPE_OBJECT_PATH,
+                                               &path, DBUS_TYPE_INVALID)) {
+                       ERR("Could not get Dial reply: %s: %s",
+                               e.name, e.message);
+                       dbus_error_free(&e);
+                       oe = OFONO_ERROR_FAILED;
+               } else {
+                       c = eina_hash_find(ctx->modem->calls, path);
+                       if (!c) {
+                               _call_add(ctx->modem, path, NULL);
+                               c = eina_hash_find(ctx->modem->calls, path);
+                       }
+                       if (!c) {
+                               ERR("Could not find call %s", path);
+                               oe = OFONO_ERROR_FAILED;
+                       }
+               }
+       }
+
+       if (ctx->cb)
+               ctx->cb((void *)ctx->data, oe, c);
+}
+
+OFono_Pending *ofono_dial(const char *number, const char *hide_callerid,
+                               OFono_Call_Cb cb, const void *data)
+{
+       OFono_Call_Cb_Context *ctx = NULL;
+       OFono_Error err = OFONO_ERROR_OFFLINE;
+       OFono_Pending *p;
+       DBusMessage *msg;
+       OFono_Modem *m = _modem_selected_get();
+       EINA_SAFETY_ON_NULL_GOTO(m, error);
+
+
+       if ((m->interfaces & OFONO_API_VOICE) == 0)
+               goto error;
+       err = OFONO_ERROR_FAILED;
+
+       if (!hide_callerid)
+               hide_callerid = "";
+
+       ctx = calloc(1, sizeof(OFono_Call_Cb_Context));
+       EINA_SAFETY_ON_NULL_GOTO(ctx, error);
+       ctx->cb = cb;
+       ctx->data = data;
+       ctx->name = OFONO_PREFIX OFONO_VOICE_IFACE ".Dial";
+       ctx->modem = m;
+
+       msg = dbus_message_new_method_call(
+               bus_id, m->base.path, OFONO_PREFIX OFONO_VOICE_IFACE, "Dial");
+       if (!msg)
+               goto error;
+
+       if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &number,
+                                       DBUS_TYPE_STRING, &hide_callerid,
+                                       DBUS_TYPE_INVALID))
+               goto error_message;
+
+       INF("Dial(%s, %s)", number, hide_callerid);
+       p = _bus_object_message_send(&m->base, msg, _ofono_dial_reply, ctx);
+       dbus_message_unref(msg);
+       return p;
+
+error_message:
+       dbus_message_unref(msg);
+error:
+       if (cb)
+               cb((void *)data, err, NULL);
+       free(ctx);
+       return NULL;
+}
+
+const char *ofono_modem_serial_get(void)
+{
+       OFono_Modem *m = _modem_selected_get();
+       EINA_SAFETY_ON_NULL_RETURN_VAL(m, NULL);
+       return m->serial;
+}
+
+void ofono_modem_api_require(unsigned int api_mask)
+{
+       if (modem_api_mask == api_mask)
+               return;
+       modem_api_mask = api_mask;
+       modem_selected = NULL;
+}
+
+void ofono_modem_path_wanted_set(const char *path)
+{
+       if (eina_stringshare_replace(&modem_path_wanted, path))
+               modem_selected = NULL;
+}
+
+void ofono_connected_cb_set(void (*cb)(void *data), const void *data)
+{
+       connected_cb = cb;
+       connected_cb_data = data;
+}
+
+void ofono_disconnected_cb_set(void (*cb)(void *data), const void *data)
+{
+       disconnected_cb = cb;
+       disconnected_cb_data = data;
+}
+
+void ofono_call_added_cb_set(void (*cb)(void *data, OFono_Call *call),
+                               const void *data)
+{
+       call_added_cb = cb;
+       call_added_cb_data = data;
+}
+
+void ofono_call_removed_cb_set(void (*cb)(void *data, OFono_Call *call),
+                               const void *data)
+{
+       call_removed_cb = cb;
+       call_removed_cb_data = data;
+}
+
+void ofono_call_changed_cb_set(void (*cb)(void *data, OFono_Call *call),
+                               const void *data)
+{
+       call_changed_cb = cb;
+       call_changed_cb_data = data;
+}
+
+void ofono_call_disconnected_cb_set(void (*cb)(void *data, OFono_Call *call, const char *reason),
+                               const void *data)
+{
+       call_disconnected_cb = cb;
+       call_disconnected_cb_data = data;
+}
+
+Eina_Bool ofono_init(void)
+{
+       if (!elm_need_e_dbus()) {
+               CRITICAL("Elementary does not support DBus.");
+               return EINA_FALSE;
+       }
+
+       bus_conn = e_dbus_bus_get(DBUS_BUS_SYSTEM);
+       if (!bus_conn) {
+               CRITICAL("Could not get DBus System Bus");
+               return EINA_FALSE;
+       }
+
+       modems = eina_hash_string_small_new(EINA_FREE_CB(_modem_free));
+       EINA_SAFETY_ON_NULL_RETURN_VAL(modems, EINA_FALSE);
+
+       e_dbus_signal_handler_add(bus_conn, E_DBUS_FDO_BUS, E_DBUS_FDO_PATH,
+                                       E_DBUS_FDO_INTERFACE,
+                                       "NameOwnerChanged",
+                                       _name_owner_changed, NULL);
+
+       e_dbus_get_name_owner(bus_conn, bus_name, _ofono_get_name_owner, NULL);
+
+       return EINA_TRUE;
+}
+
+void ofono_shutdown(void)
+{
+       if (pc_get_modems) {
+               dbus_pending_call_cancel(pc_get_modems);
+               pc_get_modems = NULL;
+       }
+
+       _ofono_disconnected();
+       eina_stringshare_replace(&modem_path_wanted, NULL);
+
+       eina_hash_free(modems);
+       modems = NULL;
+}
diff --git a/dialer/ofono.h b/dialer/ofono.h
new file mode 100644 (file)
index 0000000..111273d
--- /dev/null
@@ -0,0 +1,121 @@
+#ifndef _EFL_OFONO_H__
+#define _EFL_OFONO_H__
+
+typedef enum
+{
+       OFONO_API_SIM =         (1 <<  0),
+       OFONO_API_NETREG =      (1 <<  1),
+       OFONO_API_VOICE =       (1 <<  2),
+       OFONO_API_MSG =         (1 <<  3),
+       OFONO_API_MSG_WAITING = (1 <<  4),
+       OFONO_API_SMART_MSG =   (1 <<  5),
+       OFONO_API_STK =         (1 <<  6),
+       OFONO_API_CALL_FW =     (1 <<  7),
+       OFONO_API_CALL_VOL =    (1 <<  8),
+       OFONO_API_CALL_METER =  (1 <<  9),
+       OFONO_API_CALL_SET =    (1 << 10),
+       OFONO_API_CALL_BAR =    (1 << 11),
+       OFONO_API_SUPPL_SERV =  (1 << 12),
+       OFONO_API_TXT_TEL =     (1 << 13),
+       OFONO_API_CELL_BROAD =  (1 << 14),
+       OFONO_API_CONNMAN =     (1 << 15),
+       OFONO_API_PUSH_NOTIF =  (1 << 16),
+       OFONO_API_PHONEBOOK =   (1 << 17),
+       OFONO_API_ASN =         (1 << 18)
+} OFono_API;
+
+typedef enum
+{
+       OFONO_ERROR_NONE = 0,
+       OFONO_ERROR_FAILED,
+       OFONO_ERROR_DOES_NOT_EXIST,
+       OFONO_ERROR_IN_PROGRESS,
+       OFONO_ERROR_IN_USE,
+       OFONO_ERROR_INVALID_ARGS,
+       OFONO_ERROR_INVALID_FORMAT,
+       OFONO_ERROR_ACCESS_DENIED,
+       OFONO_ERROR_ATTACH_IN_PROGRESS,
+       OFONO_ERROR_INCORRECT_PASSWORD,
+       OFONO_ERROR_NOT_ACTIVE,
+       OFONO_ERROR_NOT_ALLOWED,
+       OFONO_ERROR_NOT_ATTACHED,
+       OFONO_ERROR_NOT_AVAILABLE,
+       OFONO_ERROR_NOT_FOUND,
+       OFONO_ERROR_NOT_IMPLEMENTED,
+       OFONO_ERROR_NOT_RECOGNIZED,
+       OFONO_ERROR_NOT_REGISTERED,
+       OFONO_ERROR_NOT_SUPPORTED,
+       OFONO_ERROR_SIM_NOT_READY,
+       OFONO_ERROR_STK,
+       OFONO_ERROR_TIMEDOUT,
+       OFONO_ERROR_OFFLINE
+} OFono_Error;
+
+typedef enum
+{
+       OFONO_CALL_STATE_DISCONNECTED = 0,
+       OFONO_CALL_STATE_ACTIVE,
+       OFONO_CALL_STATE_HELD,
+       OFONO_CALL_STATE_DIALING,
+       OFONO_CALL_STATE_ALERTING,
+       OFONO_CALL_STATE_INCOMING,
+       OFONO_CALL_STATE_WAITING
+} OFono_Call_State;
+
+typedef struct _OFono_Call OFono_Call;
+typedef struct _OFono_Pending OFono_Pending;
+
+typedef void (*OFono_Simple_Cb)(void *data, OFono_Error error);
+typedef void (*OFono_String_Cb)(void *data, OFono_Error error, const char *str);
+typedef void (*OFono_Call_Cb)(void *data, OFono_Error error, OFono_Call *call);
+
+
+/* Voice Call: */
+OFono_Pending *ofono_call_hangup(OFono_Call *c, OFono_Simple_Cb cb,
+                                       const void *data);
+OFono_Pending *ofono_call_answer(OFono_Call *c, OFono_Simple_Cb cb,
+                                       const void *data);
+
+OFono_Call_State ofono_call_state_get(const OFono_Call *c);
+const char *ofono_call_name_get(const OFono_Call *c);
+const char *ofono_call_line_id_get(const OFono_Call *c);
+
+#define ofono_call_state_valid_check(c) \
+       (ofono_call_state_get(c) != OFONO_CALL_STATE_DISCONNECTED)
+
+void ofono_call_added_cb_set(void (*cb)(void *data, OFono_Call *call),
+                               const void *data);
+void ofono_call_removed_cb_set(void (*cb)(void *data, OFono_Call *call),
+                               const void *data);
+void ofono_call_changed_cb_set(void (*cb)(void *data, OFono_Call *call),
+                               const void *data);
+void ofono_call_disconnected_cb_set(void (*cb)(void *data, OFono_Call *call, const char *reason),
+                               const void *data);
+
+/* Modem: */
+const char *ofono_modem_serial_get(void);
+
+OFono_Pending *ofono_modem_change_pin(const char *what, const char *old, const char *new,
+                               OFono_Simple_Cb cb, const void *data);
+OFono_Pending *ofono_modem_reset_pin(const char *what, const char *puk, const char *new,
+                               OFono_Simple_Cb cb, const void *data);
+
+OFono_Pending *ofono_ss_initiate(const char *command, OFono_String_Cb cb, const void *data);
+
+OFono_Pending *ofono_dial(const char *number, const char *hide_callerid,
+                               OFono_Call_Cb cb, const void *data);
+
+/* Setup: */
+void ofono_modem_api_require(unsigned int api_mask);
+void ofono_modem_path_wanted_set(const char *path);
+
+/* TODO: unique listener or multiple? set x add */
+void ofono_connected_cb_set(void (*cb)(void *data), const void *data);
+void ofono_disconnected_cb_set(void (*cb)(void *data), const void *data);
+
+void ofono_pending_cancel(OFono_Pending *pending);
+
+Eina_Bool ofono_init(void);
+void ofono_shutdown(void);
+
+#endif
diff --git a/dialer/rc.c b/dialer/rc.c
new file mode 100644 (file)
index 0000000..290efee
--- /dev/null
@@ -0,0 +1,140 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <Elementary.h>
+
+#include "log.h"
+#include "gui.h"
+
+static E_DBus_Connection *bus_conn = NULL;
+static E_DBus_Object *bus_obj = NULL;
+static E_DBus_Interface *bus_iface = NULL;
+
+#define RC_SERVICE "org.tizen.dialer"
+#define RC_IFACE "org.tizen.dialer.Control"
+#define RC_PATH "/"
+
+static DBusMessage *
+_rc_activate(E_DBus_Object *obj __UNUSED__, DBusMessage *msg)
+{
+       INF("Remotely activated!");
+       gui_activate();
+       return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *
+_rc_dial(E_DBus_Object *obj __UNUSED__, DBusMessage *msg)
+{
+       DBusError err;
+       dbus_bool_t do_auto;
+       const char *number;
+
+       dbus_error_init(&err);
+       dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &number,
+                               DBUS_TYPE_BOOLEAN, &do_auto, DBUS_TYPE_INVALID);
+       if (dbus_error_is_set(&err)) {
+               ERR("Could not parse message: %s: %s", err.name, err.message);
+               return dbus_message_new_error(msg, err.name, err.message);
+       }
+
+       INF("dial '%s' auto=%d!", number, do_auto);
+       gui_activate();
+       gui_number_set(number, do_auto);
+       return dbus_message_new_method_return(msg);
+}
+
+static void _rc_object_register(void)
+{
+       bus_obj = e_dbus_object_add(bus_conn, RC_PATH, NULL);
+       if (!bus_obj) {
+               CRITICAL("Could not create "RC_PATH" DBus object.");
+               return;
+       }
+       bus_iface = e_dbus_interface_new(RC_IFACE);
+       e_dbus_object_interface_attach(bus_obj, bus_iface);
+
+#define IF_ADD(name, par, ret, cb)             \
+       e_dbus_interface_method_add(bus_iface, name, par, ret, cb)
+
+       IF_ADD("Activate", "", "", _rc_activate);
+       IF_ADD("Dial", "sb", "", _rc_dial);
+#undef IF_ADD
+}
+
+static void _rc_activate_existing_reply(void *data __UNUSED__,
+                                       DBusMessage *msg __UNUSED__,
+                                       DBusError *err)
+{
+       if (dbus_error_is_set(err)) {
+               CRITICAL("Failed to activate existing dialer: %s: %s",
+                               err->name, err->message);
+               _app_exit_code = EXIT_FAILURE;
+               ecore_main_loop_quit();
+               return;
+       }
+
+       INF("Activated the existing dialer!");
+       ecore_main_loop_quit();
+}
+
+static void _rc_activate_existing(void)
+{
+       DBusMessage *msg = dbus_message_new_method_call(
+               RC_SERVICE, RC_PATH, RC_IFACE, "Activate");
+       e_dbus_message_send(bus_conn, msg,  _rc_activate_existing_reply,
+                               -1, NULL);
+       dbus_message_unref(msg);
+}
+
+static void _rc_request_name_reply(void *data __UNUSED__, DBusMessage *msg,
+                                       DBusError *err)
+{
+       DBusError e;
+       dbus_uint32_t t;
+
+       if (!msg) {
+               if (err)
+                       WRN("%s: %s", err->name, err->message);
+               else
+                       WRN("No message");
+               _rc_activate_existing();
+               return;
+       }
+
+       dbus_error_init(&e);
+       dbus_message_get_args(msg, &e, DBUS_TYPE_UINT32, &t, DBUS_TYPE_INVALID);
+       if (t == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+               _rc_object_register();
+               gui_activate();
+       } else {
+               WRN("Dialer already running! Activate it!");
+               _rc_activate_existing();
+       }
+}
+
+Eina_Bool rc_init(void)
+{
+       if (!elm_need_e_dbus()) {
+               CRITICAL("Elementary does not support DBus.");
+               return EINA_FALSE;
+       }
+
+       bus_conn = e_dbus_bus_get(DBUS_BUS_SESSION);
+       if (!bus_conn) {
+               CRITICAL("Could not get DBus System Bus");
+               return EINA_FALSE;
+       }
+
+       e_dbus_request_name(bus_conn, RC_SERVICE, DBUS_NAME_FLAG_DO_NOT_QUEUE,
+                               _rc_request_name_reply, NULL);
+       return EINA_TRUE;
+}
+
+void rc_shutdown(void)
+{
+       if (bus_obj)
+               e_dbus_object_free(bus_obj);
+       if (bus_iface)
+               e_dbus_interface_unref(bus_iface);
+       bus_conn = NULL;
+}
diff --git a/dialer/rc.h b/dialer/rc.h
new file mode 100644 (file)
index 0000000..b67dc72
--- /dev/null
@@ -0,0 +1,7 @@
+#ifndef _EFL_OFONO_RC_H__
+#define _EFL_OFONO_RC_H__ 1
+
+Eina_Bool rc_init(void);
+void rc_shutdown(void);
+
+#endif
diff --git a/m4/ac_attribute.m4 b/m4/ac_attribute.m4
new file mode 100644 (file)
index 0000000..23479a9
--- /dev/null
@@ -0,0 +1,47 @@
+dnl Copyright (C) 2004-2008 Kim Woelders
+dnl Copyright (C) 2008 Vincent Torri <vtorri at univ-evry dot fr>
+dnl That code is public domain and can be freely used or copied.
+dnl Originally snatched from somewhere...
+
+dnl Macro for checking if the compiler supports __attribute__
+
+dnl Usage: AC_C___ATTRIBUTE__
+dnl call AC_DEFINE for HAVE___ATTRIBUTE__ and __UNUSED__
+dnl if the compiler supports __attribute__, HAVE___ATTRIBUTE__ is
+dnl defined to 1 and __UNUSED__ is defined to __attribute__((unused))
+dnl otherwise, HAVE___ATTRIBUTE__ is not defined and __UNUSED__ is
+dnl defined to nothing.
+
+AC_DEFUN([AC_C___ATTRIBUTE__],
+[
+
+AC_MSG_CHECKING([for __attribute__])
+
+AC_CACHE_VAL([ac_cv___attribute__],
+   [AC_TRY_COMPILE(
+       [
+#include <stdlib.h>
+
+int func(int x);
+int foo(int x __attribute__ ((unused)))
+{
+   exit(1);
+}
+       ],
+       [],
+       [ac_cv___attribute__="yes"],
+       [ac_cv___attribute__="no"]
+    )])
+
+AC_MSG_RESULT($ac_cv___attribute__)
+
+if test "x${ac_cv___attribute__}" = "xyes" ; then
+   AC_DEFINE([HAVE___ATTRIBUTE__], [1], [Define to 1 if your compiler has __attribute__])
+   AC_DEFINE([__UNUSED__], [__attribute__((unused))], [Macro declaring a function argument to be unused])
+  else
+    AC_DEFINE([__UNUSED__], [], [Macro declaring a function argument to be unused])
+fi
+
+])
+
+dnl End of ac_attribute.m4
diff --git a/m4/efl_binary.m4 b/m4/efl_binary.m4
new file mode 100644 (file)
index 0000000..c774688
--- /dev/null
@@ -0,0 +1,71 @@
+dnl Copyright (C) 2010 Vincent Torri <vtorri at univ-evry dot fr>
+dnl That code is public domain and can be freely used or copied.
+
+dnl Macro that check if a binary is built or not
+
+dnl Usage: EFL_ENABLE_BIN(binary)
+dnl Call AC_SUBST(BINARY_PRG) (BINARY is the uppercase of binary, - being transformed into _)
+dnl Define have_binary (- is transformed into _)
+dnl Define conditional BUILD_BINARY (BINARY is the uppercase of binary, - being transformed into _)
+
+AC_DEFUN([EFL_ENABLE_BIN],
+[
+
+m4_pushdef([UP], m4_translit([[$1]], [-a-z], [_A-Z]))dnl
+m4_pushdef([DOWN], m4_translit([[$1]], [-A-Z], [_a-z]))dnl
+
+have_[]m4_defn([DOWN])="yes"
+
+dnl configure option
+
+AC_ARG_ENABLE([$1],
+   [AC_HELP_STRING([--disable-$1], [disable building of ]DOWN)],
+   [
+    if test "x${enableval}" = "xyes" ; then
+       have_[]m4_defn([DOWN])="yes"
+    else
+       have_[]m4_defn([DOWN])="no"
+    fi
+   ])
+
+AC_MSG_CHECKING([whether to build ]DOWN[ binary])
+AC_MSG_RESULT([$have_[]m4_defn([DOWN])])
+
+if test "x$have_[]m4_defn([DOWN])" = "xyes"; then
+   UP[]_PRG=DOWN[${EXEEXT}]
+fi
+
+AC_SUBST(UP[]_PRG)
+
+AM_CONDITIONAL(BUILD_[]UP, test "x$have_[]m4_defn([DOWN])" = "xyes")
+
+AS_IF([test "x$have_[]m4_defn([DOWN])" = "xyes"], [$2], [$3])
+
+])
+
+
+dnl Macro that check if a binary is built or not
+
+dnl Usage: EFL_WITH_BIN(package, binary, default_value)
+dnl Call AC_SUBST(_binary) (_binary is the lowercase of binary, - being transformed into _ by default, or the value set by the user)
+
+AC_DEFUN([EFL_WITH_BIN],
+[
+
+m4_pushdef([DOWN], m4_translit([[$2]], [-A-Z], [_a-z]))dnl
+
+dnl configure option
+
+AC_ARG_WITH([$2],
+   [AC_HELP_STRING([--with-$2=PATH], [specify a specific path to ]DOWN[ @<:@default=$3@:>@])],
+   [_efl_with_binary=${withval}],
+   [_efl_with_binary=$(pkg-config --variable=prefix $1)/bin/$3])
+
+DOWN=${_efl_with_binary}
+AC_MSG_NOTICE(DOWN[ set to ${_efl_with_binary}])
+
+with_binary_[]m4_defn([DOWN])=${_efl_with_binary}
+
+AC_SUBST(DOWN)
+
+])