wifi: Add support for autoscan request
[framework/connectivity/connman.git] / src / session.c
index 08ab656..a5db7ad 100644 (file)
@@ -2,7 +2,7 @@
  *
  *  Connection Manager
  *
- *  Copyright (C) 2007-2010  Intel Corporation. All rights reserved.
+ *  Copyright (C) 2007-2012  Intel Corporation. All rights reserved.
  *  Copyright (C) 2011  BWM CarIT GmbH. All rights reserved.
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -24,6 +24,8 @@
 #include <config.h>
 #endif
 
+#include <errno.h>
+
 #include <gdbus.h>
 
 #include "connman.h"
 static DBusConnection *connection;
 static GHashTable *session_hash;
 static connman_bool_t sessionmode;
-static struct connman_session *ecall_session;
+static struct session_info *ecall_info;
+
+enum connman_session_trigger {
+       CONNMAN_SESSION_TRIGGER_UNKNOWN         = 0,
+       CONNMAN_SESSION_TRIGGER_SETTING         = 1,
+       CONNMAN_SESSION_TRIGGER_CONNECT         = 2,
+       CONNMAN_SESSION_TRIGGER_DISCONNECT      = 3,
+       CONNMAN_SESSION_TRIGGER_PERIODIC        = 4,
+       CONNMAN_SESSION_TRIGGER_SERVICE         = 5,
+       CONNMAN_SESSION_TRIGGER_ECALL           = 6,
+};
+
+enum connman_session_reason {
+       CONNMAN_SESSION_REASON_UNKNOWN          = 0,
+       CONNMAN_SESSION_REASON_CONNECT          = 1,
+       CONNMAN_SESSION_REASON_DISCONNECT       = 2,
+       CONNMAN_SESSION_REASON_FREE_RIDE        = 3,
+       CONNMAN_SESSION_REASON_PERIODIC         = 4,
+};
+
+enum connman_session_state {
+       CONNMAN_SESSION_STATE_DISCONNECTED   = 0,
+       CONNMAN_SESSION_STATE_CONNECTED      = 1,
+       CONNMAN_SESSION_STATE_ONLINE         = 2,
+};
+
+enum connman_session_type {
+       CONNMAN_SESSION_TYPE_ANY      = 0,
+       CONNMAN_SESSION_TYPE_LOCAL    = 1,
+       CONNMAN_SESSION_TYPE_INTERNET = 2,
+};
 
 enum connman_session_roaming_policy {
        CONNMAN_SESSION_ROAMING_POLICY_UNKNOWN          = 0,
@@ -42,12 +74,20 @@ enum connman_session_roaming_policy {
        CONNMAN_SESSION_ROAMING_POLICY_INTERNATIONAL    = 5,
 };
 
-struct session_info {
-       char *bearer;
+struct service_entry {
+       /* track why this service was selected */
+       enum connman_session_reason reason;
+       enum connman_service_state state;
        const char *name;
+       struct connman_service *service;
        char *ifname;
-       connman_bool_t connect;
-       connman_bool_t online;
+       const char *bearer;
+       GSList *pending_timeouts;
+};
+
+struct session_info {
+       enum connman_session_state state;
+       enum connman_session_type type;
        connman_bool_t priority;
        GSList *allowed_bearers;
        connman_bool_t avoid_handover;
@@ -58,7 +98,8 @@ struct session_info {
        enum connman_session_roaming_policy roaming_policy;
        unsigned int marker;
 
-       struct connman_service *service;
+       struct service_entry *entry;
+       enum connman_session_reason reason;
 };
 
 struct connman_session {
@@ -68,11 +109,11 @@ struct connman_session {
        guint notify_watch;
 
        connman_bool_t append_all;
-       connman_bool_t info_dirty;
-       struct session_info info;
-       struct session_info info_last;
+       struct session_info *info;
+       struct session_info *info_last;
 
        GSequence *service_list;
+       GHashTable *service_hash;
 };
 
 struct bearer_info {
@@ -81,11 +122,89 @@ struct bearer_info {
        enum connman_service_type service_type;
 };
 
+static const char *trigger2string(enum connman_session_trigger trigger)
+{
+       switch (trigger) {
+       case CONNMAN_SESSION_TRIGGER_UNKNOWN:
+               break;
+       case CONNMAN_SESSION_TRIGGER_SETTING:
+               return "setting";
+       case CONNMAN_SESSION_TRIGGER_CONNECT:
+               return "connect";
+       case CONNMAN_SESSION_TRIGGER_DISCONNECT:
+               return "disconnect";
+       case CONNMAN_SESSION_TRIGGER_PERIODIC:
+               return "periodic";
+       case CONNMAN_SESSION_TRIGGER_SERVICE:
+               return "service";
+       case CONNMAN_SESSION_TRIGGER_ECALL:
+               return "ecall";
+       }
+
+       return NULL;
+}
+
+static const char *reason2string(enum connman_session_reason reason)
+{
+       switch (reason) {
+       case CONNMAN_SESSION_REASON_UNKNOWN:
+               return "unknown";
+       case CONNMAN_SESSION_REASON_CONNECT:
+               return "connect";
+       case CONNMAN_SESSION_REASON_DISCONNECT:
+               return "disconnect";
+       case CONNMAN_SESSION_REASON_FREE_RIDE:
+               return "free-ride";
+       case CONNMAN_SESSION_REASON_PERIODIC:
+               return "periodic";
+       }
+
+       return NULL;
+}
+
+static const char *state2string(enum connman_session_state state)
+{
+       switch (state) {
+       case CONNMAN_SESSION_STATE_DISCONNECTED:
+               return "disconnected";
+       case CONNMAN_SESSION_STATE_CONNECTED:
+               return "connected";
+       case CONNMAN_SESSION_STATE_ONLINE:
+               return "online";
+       }
+
+       return NULL;
+}
+
+static const char *type2string(enum connman_session_type type)
+{
+       switch (type) {
+       case CONNMAN_SESSION_TYPE_ANY:
+               return "";
+       case CONNMAN_SESSION_TYPE_LOCAL:
+               return "local";
+       case CONNMAN_SESSION_TYPE_INTERNET:
+               return "internet";
+       }
+
+       return NULL;
+}
+
+static enum connman_session_type string2type(const char *type)
+{
+       if (g_strcmp0(type, "local") == 0)
+               return CONNMAN_SESSION_TYPE_LOCAL;
+       else if (g_strcmp0(type, "internet") == 0)
+               return CONNMAN_SESSION_TYPE_INTERNET;
+
+       return CONNMAN_SESSION_TYPE_ANY;
+}
+
 static const char *roamingpolicy2string(enum connman_session_roaming_policy policy)
 {
        switch (policy) {
        case CONNMAN_SESSION_ROAMING_POLICY_UNKNOWN:
-               break;
+               return "unknown";
        case CONNMAN_SESSION_ROAMING_POLICY_DEFAULT:
                return "default";
        case CONNMAN_SESSION_ROAMING_POLICY_ALWAYS:
@@ -130,8 +249,10 @@ static enum connman_service_type bearer2service(const char *bearer)
                return CONNMAN_SERVICE_TYPE_WIMAX;
        else if (g_strcmp0(bearer, "bluetooth") == 0)
                return CONNMAN_SERVICE_TYPE_BLUETOOTH;
-       else if (g_strcmp0(bearer, "3g") == 0)
+       else if (g_strcmp0(bearer, "cellular") == 0)
                return CONNMAN_SERVICE_TYPE_CELLULAR;
+       else if (g_strcmp0(bearer, "vpn") == 0)
+               return CONNMAN_SERVICE_TYPE_VPN;
        else
                return CONNMAN_SERVICE_TYPE_UNKNOWN;
 }
@@ -148,11 +269,12 @@ static char *service2bearer(enum connman_service_type type)
        case CONNMAN_SERVICE_TYPE_BLUETOOTH:
                return "bluetooth";
        case CONNMAN_SERVICE_TYPE_CELLULAR:
-               return "3g";
+               return "cellular";
+       case CONNMAN_SERVICE_TYPE_VPN:
+               return "vpn";
        case CONNMAN_SERVICE_TYPE_UNKNOWN:
        case CONNMAN_SERVICE_TYPE_SYSTEM:
        case CONNMAN_SERVICE_TYPE_GPS:
-       case CONNMAN_SERVICE_TYPE_VPN:
        case CONNMAN_SERVICE_TYPE_GADGET:
                return "";
        }
@@ -160,36 +282,6 @@ static char *service2bearer(enum connman_service_type type)
        return "";
 }
 
-static char *session2bearer(struct connman_session *session)
-{
-       struct session_info *info = &session->info;
-       GSList *list;
-       struct bearer_info *bearer_info;
-       enum connman_service_type type;
-
-       if (info->service == NULL)
-               return "";
-
-       type = connman_service_get_type(info->service);
-
-       for (list = info->allowed_bearers;
-                       list != NULL; list = list->next) {
-               bearer_info = list->data;
-
-               if (bearer_info->match_all)
-                       return service2bearer(type);
-
-               if (bearer_info->service_type == CONNMAN_SERVICE_TYPE_UNKNOWN)
-                       return bearer_info->name;
-
-               if (bearer_info->service_type == type)
-                       return service2bearer(type);
-       }
-
-       return "";
-
-}
-
 static void cleanup_bearer_info(gpointer data, gpointer user_data)
 {
        struct bearer_info *info = data;
@@ -280,6 +372,11 @@ static void append_ipconfig_ipv4(DBusMessageIter *iter, void *user_data)
        if (service == NULL)
                return;
 
+       if (__connman_service_is_connected_state(service,
+                               CONNMAN_IPCONFIG_TYPE_IPV4) == FALSE) {
+               return;
+       }
+
        ipconfig_ipv4 = __connman_service_get_ip4config(service);
        if (ipconfig_ipv4 == NULL)
                return;
@@ -295,6 +392,11 @@ static void append_ipconfig_ipv6(DBusMessageIter *iter, void *user_data)
        if (service == NULL)
                return;
 
+       if (__connman_service_is_connected_state(service,
+                               CONNMAN_IPCONFIG_TYPE_IPV6) == FALSE) {
+               return;
+       }
+
        ipconfig_ipv4 = __connman_service_get_ip4config(service);
        ipconfig_ipv6 = __connman_service_get_ip6config(service);
        if (ipconfig_ipv6 == NULL)
@@ -306,51 +408,67 @@ static void append_ipconfig_ipv6(DBusMessageIter *iter, void *user_data)
 static void append_notify(DBusMessageIter *dict,
                                        struct connman_session *session)
 {
-       struct session_info *info = &session->info;
-       struct session_info *info_last = &session->info_last;
+       struct session_info *info = session->info;
+       struct session_info *info_last = session->info_last;
        const char *policy;
+       struct connman_service *service;
+       const char *name, *ifname, *bearer;
 
        if (session->append_all == TRUE ||
-                       info->bearer != info_last->bearer) {
-               connman_dbus_dict_append_basic(dict, "Bearer",
+                       info->state != info_last->state) {
+               const char *state = state2string(info->state);
+
+               connman_dbus_dict_append_basic(dict, "State",
                                                DBUS_TYPE_STRING,
-                                               &info->bearer);
-               info_last->bearer = info->bearer;
+                                               &state);
+               info_last->state = info->state;
        }
 
        if (session->append_all == TRUE ||
-                       info->online != info_last->online) {
-               connman_dbus_dict_append_basic(dict, "Online",
-                                               DBUS_TYPE_BOOLEAN,
-                                               &info->online);
-               info_last->online = info->online;
-       }
+                       info->entry != info_last->entry) {
+               if (info->entry == NULL) {
+                       name = "";
+                       ifname = "";
+                       service = NULL;
+                       bearer = "";
+               } else {
+                       name = info->entry->name;
+                       ifname = info->entry->ifname;
+                       service = info->entry->service;
+                       bearer = info->entry->bearer;
+               }
 
-       if (session->append_all == TRUE ||
-                       info->name != info_last->name) {
                connman_dbus_dict_append_basic(dict, "Name",
                                                DBUS_TYPE_STRING,
-                                               &info->name);
-               info_last->name = info->name;
-       }
+                                               &name);
 
-       if (session->append_all == TRUE ||
-                       info->service != info_last->service) {
                connman_dbus_dict_append_dict(dict, "IPv4",
                                                append_ipconfig_ipv4,
-                                               info->service);
+                                               service);
 
                connman_dbus_dict_append_dict(dict, "IPv6",
                                                append_ipconfig_ipv6,
-                                               info->service);
+                                               service);
 
                connman_dbus_dict_append_basic(dict, "Interface",
                                                DBUS_TYPE_STRING,
-                                               &info->ifname);
+                                               &ifname);
 
-               info_last->service = info->service;
+               connman_dbus_dict_append_basic(dict, "Bearer",
+                                               DBUS_TYPE_STRING,
+                                               &bearer);
+
+               info_last->entry = info->entry;
        }
 
+       if (session->append_all == TRUE || info->type != info_last->type) {
+               const char *type = type2string(info->type);
+
+               connman_dbus_dict_append_basic(dict, "ConnectionType",
+                                               DBUS_TYPE_STRING,
+                                               &type);
+               info_last->type = info->type;
+       }
 
        if (session->append_all == TRUE ||
                        info->priority != info_last->priority) {
@@ -427,16 +545,68 @@ static void append_notify(DBusMessageIter *dict,
        }
 
        session->append_all = FALSE;
-       session->info_dirty = FALSE;
 }
 
-static int session_notify(struct connman_session *session)
+static connman_bool_t is_type_matching_state(enum connman_session_state *state,
+                                               enum connman_session_type type)
 {
+       switch (type) {
+       case CONNMAN_SESSION_TYPE_ANY:
+               return TRUE;
+       case CONNMAN_SESSION_TYPE_LOCAL:
+               if (*state >= CONNMAN_SESSION_STATE_CONNECTED) {
+                       *state = CONNMAN_SESSION_STATE_CONNECTED;
+                       return TRUE;
+               }
+
+               break;
+       case CONNMAN_SESSION_TYPE_INTERNET:
+               if (*state == CONNMAN_SESSION_STATE_ONLINE)
+                       return TRUE;
+               break;
+       }
+
+       return FALSE;
+}
+
+static connman_bool_t compute_notifiable_changes(struct connman_session *session)
+{
+       struct session_info *info_last = session->info_last;
+       struct session_info *info = session->info;
+
+       if (session->append_all == TRUE)
+               return TRUE;
+
+       if (info->state != info_last->state)
+               return TRUE;
+
+       if (info->entry != info_last->entry &&
+                       info->state >= CONNMAN_SESSION_STATE_CONNECTED)
+               return TRUE;
+
+       if (info->periodic_connect != info_last->periodic_connect ||
+                       info->allowed_bearers != info_last->allowed_bearers ||
+                       info->avoid_handover != info_last->avoid_handover ||
+                       info->stay_connected != info_last->stay_connected ||
+                       info->roaming_policy != info_last->roaming_policy ||
+                       info->idle_timeout != info_last->idle_timeout ||
+                       info->priority != info_last->priority ||
+                       info->marker != info_last->marker ||
+                       info->ecall != info_last->ecall ||
+                       info->type != info_last->type)
+               return TRUE;
+
+       return FALSE;
+}
+
+static gboolean session_notify(gpointer user_data)
+{
+       struct connman_session *session = user_data;
        DBusMessage *msg;
        DBusMessageIter array, dict;
 
-       if (session->info_dirty == FALSE)
-               return 0;
+       if (compute_notifiable_changes(session) == FALSE)
+               return FALSE;
 
        DBG("session %p owner %s notify_path %s", session,
                session->owner, session->notify_path);
@@ -445,7 +615,7 @@ static int session_notify(struct connman_session *session)
                                                CONNMAN_NOTIFICATION_INTERFACE,
                                                "Update");
        if (msg == NULL)
-               return -ENOMEM;
+               return FALSE;
 
        dbus_message_iter_init_append(msg, &array);
        connman_dbus_dict_open(&array, &dict);
@@ -456,33 +626,31 @@ static int session_notify(struct connman_session *session)
 
        g_dbus_send_message(connection, msg);
 
-       session->info_dirty = FALSE;
-
-       return 0;
+       return FALSE;
 }
 
 static void ipconfig_ipv4_changed(struct connman_session *session)
 {
-       struct session_info *info = &session->info;
+       struct session_info *info = session->info;
 
        connman_dbus_setting_changed_dict(session->owner, session->notify_path,
                                                "IPv4", append_ipconfig_ipv4,
-                                               info->service);
+                                               info->entry->service);
 }
 
 static void ipconfig_ipv6_changed(struct connman_session *session)
 {
-       struct session_info *info = &session->info;
+       struct session_info *info = session->info;
 
        connman_dbus_setting_changed_dict(session->owner, session->notify_path,
                                                "IPv6", append_ipconfig_ipv6,
-                                               info->service);
+                                               info->entry->service);
 }
 
 static connman_bool_t service_type_match(struct connman_session *session,
                                        struct connman_service *service)
 {
-       struct session_info *info = &session->info;
+       struct session_info *info = session->info;
        GSList *list;
 
        for (list = info->allowed_bearers;
@@ -520,7 +688,7 @@ static int service_type_weight(enum connman_service_type type)
         * 1. Ethernet
         * 2. Bluetooth
         * 3. WiFi/WiMAX
-        * 4. GSM/UTMS/3G
+        * 4. Cellular
         */
 
        switch (type) {
@@ -548,7 +716,7 @@ static gint sort_allowed_bearers(struct connman_service *service_a,
                                        struct connman_service *service_b,
                                        struct connman_session *session)
 {
-       struct session_info *info = &session->info;
+       struct session_info *info = session->info;
        GSList *list;
        enum connman_service_type type_a, type_b;
        int weight_a, weight_b;
@@ -596,208 +764,564 @@ static gint sort_allowed_bearers(struct connman_service *service_a,
 
 static gint sort_services(gconstpointer a, gconstpointer b, gpointer user_data)
 {
-       struct connman_service *service_a = (void *)a;
-       struct connman_service *service_b = (void *)b;
+       struct service_entry *entry_a = (void *)a;
+       struct service_entry *entry_b = (void *)b;
        struct connman_session *session = user_data;
 
-       return sort_allowed_bearers(service_a, service_b, session);
+       return sort_allowed_bearers(entry_a->service, entry_b->service,
+                               session);
 }
 
 static void cleanup_session(gpointer user_data)
 {
        struct connman_session *session = user_data;
-       struct session_info *info = &session->info;
+       struct session_info *info = session->info;
 
        DBG("remove %s", session->session_path);
 
+       g_hash_table_destroy(session->service_hash);
        g_sequence_free(session->service_list);
 
+       if (info->entry != NULL &&
+                       info->entry->reason == CONNMAN_SESSION_REASON_CONNECT) {
+               __connman_service_disconnect(info->entry->service);
+       }
+
        g_slist_foreach(info->allowed_bearers, cleanup_bearer_info, NULL);
        g_slist_free(info->allowed_bearers);
 
        g_free(session->owner);
        g_free(session->session_path);
        g_free(session->notify_path);
+       g_free(session->info);
+       g_free(session->info_last);
 
        g_free(session);
 }
 
-static void release_session(gpointer key, gpointer value, gpointer user_data)
+static enum connman_session_state service_to_session_state(enum connman_service_state state)
 {
-       struct connman_session *session = value;
-       DBusMessage *message;
+       switch (state) {
+       case CONNMAN_SERVICE_STATE_UNKNOWN:
+       case CONNMAN_SERVICE_STATE_IDLE:
+       case CONNMAN_SERVICE_STATE_ASSOCIATION:
+       case CONNMAN_SERVICE_STATE_CONFIGURATION:
+       case CONNMAN_SERVICE_STATE_DISCONNECT:
+       case CONNMAN_SERVICE_STATE_FAILURE:
+               break;
+       case CONNMAN_SERVICE_STATE_READY:
+               return CONNMAN_SESSION_STATE_CONNECTED;
+       case CONNMAN_SERVICE_STATE_ONLINE:
+               return CONNMAN_SESSION_STATE_ONLINE;
+       }
 
-       DBG("owner %s path %s", session->owner, session->notify_path);
+       return CONNMAN_SESSION_STATE_DISCONNECTED;
+}
 
-       if (session->notify_watch > 0)
-               g_dbus_remove_watch(connection, session->notify_watch);
+static connman_bool_t is_connected(enum connman_service_state state)
+{
+       switch (state) {
+       case CONNMAN_SERVICE_STATE_UNKNOWN:
+       case CONNMAN_SERVICE_STATE_IDLE:
+       case CONNMAN_SERVICE_STATE_ASSOCIATION:
+       case CONNMAN_SERVICE_STATE_CONFIGURATION:
+       case CONNMAN_SERVICE_STATE_DISCONNECT:
+       case CONNMAN_SERVICE_STATE_FAILURE:
+               break;
+       case CONNMAN_SERVICE_STATE_READY:
+       case CONNMAN_SERVICE_STATE_ONLINE:
+               return TRUE;
+       }
 
-       g_dbus_unregister_interface(connection, session->session_path,
-                                               CONNMAN_SESSION_INTERFACE);
+       return FALSE;
+}
 
-       message = dbus_message_new_method_call(session->owner,
-                                               session->notify_path,
-                                               CONNMAN_NOTIFICATION_INTERFACE,
-                                               "Release");
-       if (message == NULL)
-               return;
+static connman_bool_t is_connecting(enum connman_service_state state)
+{
+       switch (state) {
+       case CONNMAN_SERVICE_STATE_UNKNOWN:
+       case CONNMAN_SERVICE_STATE_IDLE:
+               break;
+       case CONNMAN_SERVICE_STATE_ASSOCIATION:
+       case CONNMAN_SERVICE_STATE_CONFIGURATION:
+               return TRUE;
+       case CONNMAN_SERVICE_STATE_DISCONNECT:
+       case CONNMAN_SERVICE_STATE_FAILURE:
+       case CONNMAN_SERVICE_STATE_READY:
+       case CONNMAN_SERVICE_STATE_ONLINE:
+               break;
+       }
 
-       dbus_message_set_no_reply(message, TRUE);
+       return FALSE;
+}
 
-       g_dbus_send_message(connection, message);
+static connman_bool_t explicit_connect(enum connman_session_reason reason)
+{
+       switch (reason) {
+       case CONNMAN_SESSION_REASON_UNKNOWN:
+       case CONNMAN_SESSION_REASON_FREE_RIDE:
+       case CONNMAN_SESSION_REASON_DISCONNECT:
+               break;
+       case CONNMAN_SESSION_REASON_CONNECT:
+       case CONNMAN_SESSION_REASON_PERIODIC:
+               return TRUE;
+       }
+
+       return FALSE;
 }
 
-static int session_disconnect(struct connman_session *session)
+static connman_bool_t explicit_disconnect(struct session_info *info)
 {
-       DBG("session %p, %s", session, session->owner);
+       if (info->entry == NULL)
+               return FALSE;
 
-       if (session->notify_watch > 0)
-               g_dbus_remove_watch(connection, session->notify_watch);
+       DBG("reason %s service %p state %d",
+               reason2string(info->entry->reason),
+               info->entry->service, info->entry->state);
 
-       g_dbus_unregister_interface(connection, session->session_path,
-                                               CONNMAN_SESSION_INTERFACE);
+       if (info->entry->reason == CONNMAN_SESSION_REASON_UNKNOWN)
+               return FALSE;
 
-       g_hash_table_remove(session_hash, session->session_path);
+       if (explicit_connect(info->entry->reason) == FALSE)
+               return FALSE;
 
-       return 0;
+       if (__connman_service_session_dec(info->entry->service) == FALSE)
+               return FALSE;
+
+       if (ecall_info != NULL && ecall_info != info)
+               return FALSE;
+
+       return TRUE;
 }
 
-static void owner_disconnect(DBusConnection *conn, void *user_data)
+struct pending_data {
+       unsigned int timeout;
+       struct service_entry *entry;
+       gboolean (*cb)(gpointer);
+};
+
+static void pending_timeout_free(gpointer data, gpointer user_data)
 {
-       struct connman_session *session = user_data;
+       struct pending_data *pending = data;
 
-       DBG("session %p, %s died", session, session->owner);
+       DBG("pending %p timeout %d", pending, pending->timeout);
+       g_source_remove(pending->timeout);
+       g_free(pending);
+}
 
-       session_disconnect(session);
+static void pending_timeout_remove_all(struct service_entry *entry)
+{
+       DBG("");
+
+       g_slist_foreach(entry->pending_timeouts, pending_timeout_free, NULL);
+       g_slist_free(entry->pending_timeouts);
+       entry->pending_timeouts = NULL;
 }
 
-static DBusMessage *destroy_session(DBusConnection *conn,
-                                       DBusMessage *msg, void *user_data)
+static gboolean pending_timeout_cb(gpointer data)
 {
-       struct connman_session *session = user_data;
+       struct pending_data *pending = data;
+       struct service_entry *entry = pending->entry;
+       gboolean ret;
+
+       DBG("pending %p timeout %d", pending, pending->timeout);
+
+       ret = pending->cb(pending->entry);
+       if (ret == FALSE) {
+               entry->pending_timeouts =
+                       g_slist_remove(entry->pending_timeouts,
+                                       pending);
+               g_free(pending);
+       }
+       return ret;
+}
 
-       DBG("session %p", session);
+static connman_bool_t pending_timeout_add(unsigned int seconds,
+                                       gboolean (*cb)(gpointer),
+                                       struct service_entry *entry)
+{
+       struct pending_data *pending = g_try_new0(struct pending_data, 1);
 
-       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+       if (pending == NULL || cb == NULL || entry == NULL) {
+               g_free(pending);
+               return FALSE;
+       }
+
+       pending->cb = cb;
+       pending->entry = entry;
+       pending->timeout = g_timeout_add_seconds(seconds, pending_timeout_cb,
+                                               pending);
+       entry->pending_timeouts = g_slist_prepend(entry->pending_timeouts,
+                                               pending);
+
+       DBG("pending %p entry %p timeout id %d", pending, entry,
+               pending->timeout);
+
+       return TRUE;
 }
 
-static gboolean session_changed_connect(gpointer user_data)
+static gboolean call_disconnect(gpointer user_data)
 {
-       struct connman_session *session = user_data;
-       struct session_info *info = &session->info;
+       struct service_entry *entry = user_data;
+       struct connman_service *service = entry->service;
 
-       __connman_service_connect(info->service);
+       /*
+        * TODO: We should mark this entry as pending work. In case
+        * disconnect fails we just unassign this session from the
+        * service and can't do anything later on it
+        */
+       DBG("disconnect service %p", service);
+       __connman_service_disconnect(service);
 
        return FALSE;
 }
 
-static void update_service(struct connman_session *session)
+static gboolean call_connect(gpointer user_data)
 {
-       struct session_info *info = &session->info;
-       int idx;
+       struct service_entry *entry = user_data;
+       struct connman_service *service = entry->service;
 
-       if (info->service != NULL) {
-               info->bearer = session2bearer(session);
-               info->online = __connman_service_is_connected(info->service);
-               info->name = __connman_service_get_name(info->service);
-               idx = __connman_service_get_index(info->service);
-               info->ifname = connman_inet_ifname(idx);
+       DBG("connect service %p", service);
+       __connman_service_connect(service);
 
-       } else {
-               info->bearer = "";
-               info->online = FALSE;
-               info->name = "";
-               info->ifname = "";
+       return FALSE;
+}
+
+static void deselect_service(struct session_info *info)
+{
+       struct service_entry *entry;
+       connman_bool_t disconnect, connected;
+
+       DBG("");
+
+       if (info->entry == NULL)
+               return;
+
+       disconnect = explicit_disconnect(info);
+
+       connected = is_connecting(info->entry->state) == TRUE ||
+                       is_connected(info->entry->state) == TRUE;
+
+       info->state = CONNMAN_SESSION_STATE_DISCONNECTED;
+       info->entry->reason = CONNMAN_SESSION_REASON_UNKNOWN;
+
+       entry = info->entry;
+       info->entry = NULL;
+
+       DBG("disconnect %d connected %d", disconnect, connected);
+
+       if (disconnect == TRUE && connected == TRUE)
+               pending_timeout_add(0, call_disconnect, entry);
+}
+
+static void deselect_and_disconnect(struct connman_session *session,
+                                       enum connman_session_reason reason)
+{
+       struct session_info *info = session->info;
+
+       deselect_service(info);
+
+       info->reason = reason;
+}
+
+static void select_connected_service(struct session_info *info,
+                                       struct service_entry *entry)
+{
+       enum connman_session_state state;
+
+       state = service_to_session_state(entry->state);
+       if (is_type_matching_state(&state, info->type) == FALSE)
+               return;
+
+       info->state = state;
+
+       info->entry = entry;
+       info->entry->reason = info->reason;
+
+       if (explicit_connect(info->reason) == FALSE)
+               return;
+
+       __connman_service_session_inc(info->entry->service);
+}
+
+static void select_offline_service(struct session_info *info,
+                                       struct service_entry *entry)
+{
+       if (explicit_connect(info->reason) == FALSE)
+               return;
+
+       info->state = service_to_session_state(entry->state);
+
+       info->entry = entry;
+       info->entry->reason = info->reason;
+
+       __connman_service_session_inc(info->entry->service);
+       pending_timeout_add(0, call_connect, entry);
+}
+
+static void select_service(struct session_info *info,
+                               struct service_entry *entry)
+{
+       DBG("service %p", entry->service);
+
+       if (is_connected(entry->state) == TRUE)
+               select_connected_service(info, entry);
+       else
+               select_offline_service(info, entry);
+}
+
+static void select_and_connect(struct connman_session *session,
+                               enum connman_session_reason reason)
+{
+       struct session_info *info = session->info;
+       struct service_entry *entry = NULL;
+       GSequenceIter *iter;
+
+       DBG("session %p reason %s", session, reason2string(reason));
+
+       info->reason = reason;
+
+       iter = g_sequence_get_begin_iter(session->service_list);
+
+       while (g_sequence_iter_is_end(iter) == FALSE) {
+               entry = g_sequence_get(iter);
+
+               switch (entry->state) {
+               case CONNMAN_SERVICE_STATE_ASSOCIATION:
+               case CONNMAN_SERVICE_STATE_CONFIGURATION:
+               case CONNMAN_SERVICE_STATE_READY:
+               case CONNMAN_SERVICE_STATE_ONLINE:
+               case CONNMAN_SERVICE_STATE_IDLE:
+               case CONNMAN_SERVICE_STATE_DISCONNECT:
+                       select_service(info, entry);
+                       return;
+               case CONNMAN_SERVICE_STATE_UNKNOWN:
+               case CONNMAN_SERVICE_STATE_FAILURE:
+                       break;
+               }
+
+               iter = g_sequence_iter_next(iter);
        }
 }
 
-static void session_changed(struct connman_session *session)
+static struct service_entry *create_service_entry(struct connman_service *service,
+                                       const char *name,
+                                       enum connman_service_state state)
+{
+       struct service_entry *entry;
+       enum connman_service_type type;
+       int idx;
+
+       entry = g_try_new0(struct service_entry, 1);
+       if (entry == NULL)
+               return entry;
+
+       entry->reason = CONNMAN_SESSION_REASON_UNKNOWN;
+       entry->state = state;
+       if (name != NULL)
+               entry->name = name;
+       else
+               entry->name = "";
+       entry->service = service;
+
+       idx = __connman_service_get_index(entry->service);
+       entry->ifname = connman_inet_ifname(idx);
+       if (entry->ifname == NULL)
+               entry->ifname = g_strdup("");
+
+       type = connman_service_get_type(entry->service);
+       entry->bearer = service2bearer(type);
+
+       return entry;
+}
+
+static void destroy_service_entry(gpointer data)
+{
+       struct service_entry *entry = data;
+
+       pending_timeout_remove_all(entry);
+       g_free(entry->ifname);
+
+       g_free(entry);
+}
+
+static void populate_service_list(struct connman_session *session)
 {
-       struct session_info *info = &session->info;
-       struct session_info *info_last = &session->info_last;
-       struct connman_service *service = NULL;
-       GSourceFunc callback = NULL;
+       struct service_entry *entry;
        GSequenceIter *iter;
 
+       session->service_hash =
+               g_hash_table_new_full(g_direct_hash, g_direct_equal,
+                                       NULL, NULL);
+       session->service_list = __connman_service_get_list(session,
+                                                       service_match,
+                                                       create_service_entry,
+                                                       destroy_service_entry);
+
+       g_sequence_sort(session->service_list, sort_services, session);
+
+       iter = g_sequence_get_begin_iter(session->service_list);
+
+       while (g_sequence_iter_is_end(iter) == FALSE) {
+               entry = g_sequence_get(iter);
+
+               DBG("service %p type %s name %s", entry->service,
+                       service2bearer(connman_service_get_type(entry->service)),
+                       entry->name);
+
+               g_hash_table_replace(session->service_hash,
+                                       entry->service, iter);
+
+               iter = g_sequence_iter_next(iter);
+       }
+}
+
+static void session_changed(struct connman_session *session,
+                               enum connman_session_trigger trigger)
+{
+       struct session_info *info = session->info;
+       struct session_info *info_last = session->info_last;
+       GSequenceIter *service_iter = NULL, *service_iter_last = NULL;
+       GSequence *service_list_last;
+       GHashTable *service_hash_last;
+
        /*
         * TODO: This only a placeholder for the 'real' algorithm to
         * play a bit around. So we are going to improve it step by step.
         */
 
-       if (info->ecall == TRUE && session != ecall_session)
-               goto out;
+       DBG("session %p trigger %s reason %s", session, trigger2string(trigger),
+                                               reason2string(info->reason));
+
+       if (info->entry != NULL) {
+               enum connman_session_state state;
 
-       if (info->connect == FALSE) {
-               if (info->service != NULL)
-                       __connman_service_disconnect(info->service);
-               info->service = NULL;
-               goto out;
+               state = service_to_session_state(info->entry->state);
+
+               if (is_type_matching_state(&state, info->type) == TRUE)
+                       info->state = state;
        }
 
-       iter = g_sequence_get_begin_iter(session->service_list);
+       switch (trigger) {
+       case CONNMAN_SESSION_TRIGGER_UNKNOWN:
+               DBG("ignore session changed event");
+               return;
+       case CONNMAN_SESSION_TRIGGER_SETTING:
+               if (info->allowed_bearers != info_last->allowed_bearers) {
 
-       while (g_sequence_iter_is_end(iter) == FALSE) {
-               service = g_sequence_get(iter);
+                       service_hash_last = session->service_hash;
+                       service_list_last = session->service_list;
+
+                       populate_service_list(session);
+
+                       if (info->entry != NULL) {
+                               service_iter_last = g_hash_table_lookup(
+                                                       service_hash_last,
+                                                       info->entry->service);
+                               service_iter = g_hash_table_lookup(
+                                                       session->service_hash,
+                                                       info->entry->service);
+                       }
 
-               if (__connman_service_is_connecting(service) == TRUE ||
-                               __connman_service_is_connected(service) == TRUE) {
+                       if (service_iter == NULL && service_iter_last != NULL) {
+                               /*
+                                * The currently selected service is
+                                * not part of this session anymore.
+                                */
+                               deselect_and_disconnect(session, info->reason);
+                       }
+
+                       g_hash_table_remove_all(service_hash_last);
+                       g_sequence_free(service_list_last);
+               }
+
+               if (info->type != info_last->type) {
+                       if (info->state >= CONNMAN_SESSION_STATE_CONNECTED &&
+                                       is_type_matching_state(&info->state,
+                                                       info->type) == FALSE)
+                               deselect_and_disconnect(session, info->reason);
+               }
+
+               if (info->state == CONNMAN_SESSION_STATE_DISCONNECTED) {
+                       select_and_connect(session,
+                                       CONNMAN_SESSION_REASON_FREE_RIDE);
+               }
+
+               break;
+       case CONNMAN_SESSION_TRIGGER_CONNECT:
+               if (info->state >= CONNMAN_SESSION_STATE_CONNECTED) {
+                       if (info->entry->reason == CONNMAN_SESSION_REASON_CONNECT)
+                               break;
+                       info->entry->reason = CONNMAN_SESSION_REASON_CONNECT;
+                       __connman_service_session_inc(info->entry->service);
                        break;
                }
 
-               if (__connman_service_is_idle(service) == TRUE &&
-                               info->connect == TRUE) {
-                       callback = session_changed_connect;
+               if (info->entry != NULL &&
+                               is_connecting(info->entry->state) == TRUE) {
                        break;
                }
 
-               service = NULL;
+               select_and_connect(session,
+                               CONNMAN_SESSION_REASON_CONNECT);
 
-               iter = g_sequence_iter_next(iter);
-       }
+               break;
+       case CONNMAN_SESSION_TRIGGER_DISCONNECT:
+               deselect_and_disconnect(session,
+                                       CONNMAN_SESSION_REASON_DISCONNECT);
 
-       if (info->service != NULL && info->service != service) {
-               __connman_service_disconnect(info->service);
-               info->service = NULL;
-       }
+               break;
+       case CONNMAN_SESSION_TRIGGER_PERIODIC:
+               if (info->state >= CONNMAN_SESSION_STATE_CONNECTED) {
+                       info->entry->reason = CONNMAN_SESSION_REASON_PERIODIC;
+                       __connman_service_session_inc(info->entry->service);
+                       break;
+               }
 
-       if (service != NULL) {
-               info->service = service;
+               select_and_connect(session,
+                               CONNMAN_SESSION_REASON_PERIODIC);
 
-               if (callback != NULL)
-                       callback(session);
-       }
+               break;
+       case CONNMAN_SESSION_TRIGGER_SERVICE:
+               if (info->entry != NULL &&
+                       (is_connecting(info->entry->state) == TRUE ||
+                               is_connected(info->entry->state) == TRUE)) {
+                       break;
+               }
 
-out:
-       if (info->service != info_last->service)
-               update_service(session);
-}
+               deselect_and_disconnect(session, info->reason);
 
-static gboolean session_cb(gpointer user_data)
-{
-       struct connman_session *session = user_data;
+               if (info->reason == CONNMAN_SESSION_REASON_FREE_RIDE ||
+                               info->stay_connected == TRUE) {
+                       select_and_connect(session, info->reason);
+               }
 
-       session_changed(session);
-       session_notify(session);
+               break;
+       case CONNMAN_SESSION_TRIGGER_ECALL:
+               if (info->state == CONNMAN_SESSION_STATE_DISCONNECTED &&
+                               info->entry != NULL &&
+                               info->entry->service != NULL) {
+                       deselect_and_disconnect(session, info->reason);
+               }
 
-       return FALSE;
+               break;
+       }
+
+       session_notify(session);
 }
 
 static DBusMessage *connect_session(DBusConnection *conn,
                                        DBusMessage *msg, void *user_data)
 {
        struct connman_session *session = user_data;
-       struct session_info *info = &session->info;
+       struct session_info *info = session->info;
 
        DBG("session %p", session);
 
-       info->connect = TRUE;
-
-       if (ecall_session != NULL && ecall_session != session)
+       if (ecall_info != NULL && ecall_info != info)
                return __connman_error_failed(msg, EBUSY);
 
-       session->info_dirty = TRUE;
-
-       g_timeout_add_seconds(0, session_cb, session);
+       session_changed(session, CONNMAN_SESSION_TRIGGER_CONNECT);
 
        return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
 }
@@ -806,47 +1330,21 @@ static DBusMessage *disconnect_session(DBusConnection *conn,
                                        DBusMessage *msg, void *user_data)
 {
        struct connman_session *session = user_data;
-       struct session_info *info = &session->info;
+       struct session_info *info = session->info;
 
        DBG("session %p", session);
 
-       info->connect = FALSE;
-
-       if (ecall_session != NULL && ecall_session != session)
+       if (ecall_info != NULL && ecall_info != info)
                return __connman_error_failed(msg, EBUSY);
 
-       session->info_dirty = TRUE;
-
-       g_timeout_add_seconds(0, session_cb, session);
+       session_changed(session, CONNMAN_SESSION_TRIGGER_DISCONNECT);
 
        return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
 }
 
-static void print_name(gpointer data, gpointer user_data)
-{
-       struct connman_service *service = data;
-
-       DBG("service %p type %s name %s", service,
-               service2bearer(connman_service_get_type(service)),
-               __connman_service_get_name(service));
-}
-
-static void update_allowed_bearers(struct connman_session *session)
-{
-       if (session->service_list != NULL)
-               g_sequence_free(session->service_list);
-
-       session->service_list = __connman_service_get_list(session,
-                                                               service_match);
-       g_sequence_sort(session->service_list, sort_services, session);
-       g_sequence_foreach(session->service_list, print_name, NULL);
-
-       session->info_dirty = TRUE;
-}
-
 static void update_ecall_sessions(struct connman_session *session)
 {
-       struct session_info *info = &session->info;
+       struct session_info *info = session->info;
        struct connman_session *session_iter;
        GHashTableIter iter;
        gpointer key, value;
@@ -859,38 +1357,36 @@ static void update_ecall_sessions(struct connman_session *session)
                if (session_iter == session)
                        continue;
 
-               session_iter->info.ecall = info->ecall;
-               session_iter->info_dirty = TRUE;
+               session_iter->info->ecall = info->ecall;
 
-               g_timeout_add_seconds(0, session_cb, session_iter);
+               session_changed(session_iter, CONNMAN_SESSION_TRIGGER_ECALL);
        }
 }
 
 static void update_ecall(struct connman_session *session)
 {
-       struct session_info *info = &session->info;
-       struct session_info *info_last = &session->info_last;
+       struct session_info *info = session->info;
+       struct session_info *info_last = session->info_last;
 
-       DBG("ecall_session %p ecall %d -> %d", ecall_session,
-               info_last->ecall, info->ecall);
+       DBG("session %p ecall_info %p ecall %d -> %d", session,
+               ecall_info, info_last->ecall, info->ecall);
 
-       if (ecall_session == NULL) {
+       if (ecall_info == NULL) {
                if (!(info_last->ecall == FALSE && info->ecall == TRUE))
                        goto err;
 
-               ecall_session = session;
-       } else if (ecall_session == session) {
+               ecall_info = info;
+       } else if (ecall_info == info) {
                if (!(info_last->ecall == TRUE && info->ecall == FALSE))
                        goto err;
 
-               ecall_session = NULL;
+               ecall_info = NULL;
        } else {
                goto err;
        }
 
        update_ecall_sessions(session);
 
-       session->info_dirty = TRUE;
        return;
 
 err:
@@ -902,10 +1398,10 @@ static DBusMessage *change_session(DBusConnection *conn,
                                        DBusMessage *msg, void *user_data)
 {
        struct connman_session *session = user_data;
-       struct session_info *info = &session->info;
-       struct session_info *info_last = &session->info_last;
+       struct session_info *info = session->info;
        DBusMessageIter iter, value;
        const char *name;
+       const char *val;
        GSList *allowed_bearers;
 
        DBG("session %p", session);
@@ -933,8 +1429,6 @@ static DBusMessage *change_session(DBusConnection *conn,
                        }
 
                        info->allowed_bearers = allowed_bearers;
-
-                       update_allowed_bearers(session);
                } else {
                        goto err;
                }
@@ -943,21 +1437,12 @@ static DBusMessage *change_session(DBusConnection *conn,
                if (g_str_equal(name, "Priority") == TRUE) {
                        dbus_message_iter_get_basic(&value,
                                        &info->priority);
-
-                       if (info_last->priority != info->priority)
-                               session->info_dirty = TRUE;
                } else if (g_str_equal(name, "AvoidHandover") == TRUE) {
                        dbus_message_iter_get_basic(&value,
                                        &info->avoid_handover);
-
-                       if (info_last->avoid_handover != info->avoid_handover)
-                               session->info_dirty = TRUE;
-                       } else if (g_str_equal(name, "StayConnected") == TRUE) {
+               } else if (g_str_equal(name, "StayConnected") == TRUE) {
                        dbus_message_iter_get_basic(&value,
                                        &info->stay_connected);
-
-                       if (info_last->stay_connected != info->stay_connected)
-                               session->info_dirty = TRUE;
                } else if (g_str_equal(name, "EmergencyCall") == TRUE) {
                        dbus_message_iter_get_basic(&value,
                                        &info->ecall);
@@ -971,28 +1456,21 @@ static DBusMessage *change_session(DBusConnection *conn,
                if (g_str_equal(name, "PeriodicConnect") == TRUE) {
                        dbus_message_iter_get_basic(&value,
                                        &info->periodic_connect);
-
-                       if (info_last->periodic_connect != info->periodic_connect)
-                               session->info_dirty = TRUE;
                } else if (g_str_equal(name, "IdleTimeout") == TRUE) {
                        dbus_message_iter_get_basic(&value,
                                        &info->idle_timeout);
-
-                       if (info_last->idle_timeout != info->idle_timeout)
-                               session->info_dirty = TRUE;
                } else {
                        goto err;
                }
                break;
        case DBUS_TYPE_STRING:
-               if (g_str_equal(name, "RoamingPolicy") == TRUE) {
-                       const char *val;
+               if (g_str_equal(name, "ConnectionType") == TRUE) {
+                       dbus_message_iter_get_basic(&value, &val);
+                       info->type = string2type(val);
+               } else if (g_str_equal(name, "RoamingPolicy") == TRUE) {
                        dbus_message_iter_get_basic(&value, &val);
                        info->roaming_policy =
                                        string2roamingpolicy(val);
-
-                       if (info_last->roaming_policy != info->roaming_policy)
-                               session->info_dirty = TRUE;
                } else {
                        goto err;
                }
@@ -1001,8 +1479,7 @@ static DBusMessage *change_session(DBusConnection *conn,
                goto err;
        }
 
-       if (session->info_dirty == TRUE)
-               session_cb(session);
+       session_changed(session, CONNMAN_SESSION_TRIGGER_SETTING);
 
        return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
 
@@ -1010,11 +1487,82 @@ err:
        return __connman_error_invalid_arguments(msg);
 }
 
-static GDBusMethodTable session_methods[] = {
-       { "Destroy",    "",   "", destroy_session    },
-       { "Connect",    "",   "", connect_session    },
-       { "Disconnect", "",   "", disconnect_session },
-       { "Change",     "sv", "", change_session     },
+static void release_session(gpointer key, gpointer value, gpointer user_data)
+{
+       struct connman_session *session = value;
+       DBusMessage *message;
+
+       DBG("owner %s path %s", session->owner, session->notify_path);
+
+       if (session->notify_watch > 0)
+               g_dbus_remove_watch(connection, session->notify_watch);
+
+       g_dbus_unregister_interface(connection, session->session_path,
+                                               CONNMAN_SESSION_INTERFACE);
+
+       message = dbus_message_new_method_call(session->owner,
+                                               session->notify_path,
+                                               CONNMAN_NOTIFICATION_INTERFACE,
+                                               "Release");
+       if (message == NULL)
+               return;
+
+       dbus_message_set_no_reply(message, TRUE);
+
+       g_dbus_send_message(connection, message);
+}
+
+static int session_disconnect(struct connman_session *session)
+{
+       DBG("session %p, %s", session, session->owner);
+
+       if (session->notify_watch > 0)
+               g_dbus_remove_watch(connection, session->notify_watch);
+
+       g_dbus_unregister_interface(connection, session->session_path,
+                                               CONNMAN_SESSION_INTERFACE);
+
+       deselect_and_disconnect(session,
+                               CONNMAN_SESSION_REASON_DISCONNECT);
+
+       g_hash_table_remove(session_hash, session->session_path);
+
+       return 0;
+}
+
+static void owner_disconnect(DBusConnection *conn, void *user_data)
+{
+       struct connman_session *session = user_data;
+
+       DBG("session %p, %s died", session, session->owner);
+
+       session_disconnect(session);
+}
+
+static DBusMessage *destroy_session(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct connman_session *session = user_data;
+       struct session_info *info = session->info;
+
+       DBG("session %p", session);
+
+       if (ecall_info != NULL && ecall_info != info)
+               return __connman_error_failed(msg, EBUSY);
+
+       session_disconnect(session);
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static const GDBusMethodTable session_methods[] = {
+       { GDBUS_METHOD("Destroy", NULL, NULL, destroy_session) },
+       { GDBUS_METHOD("Connect", NULL, NULL, connect_session) },
+       { GDBUS_METHOD("Disconnect", NULL, NULL,
+                       disconnect_session ) },
+       { GDBUS_METHOD("Change",
+                       GDBUS_ARGS({ "name", "s" }, { "value", "v" }),
+                       NULL, change_session) },
        { },
 };
 
@@ -1023,9 +1571,10 @@ int __connman_session_create(DBusMessage *msg)
        const char *owner, *notify_path;
        char *session_path = NULL;
        DBusMessageIter iter, array;
-       struct connman_session *session;
+       struct connman_session *session = NULL;
        struct session_info *info, *info_last;
 
+       enum connman_session_type type = CONNMAN_SESSION_TYPE_ANY;
        connman_bool_t priority = FALSE, avoid_handover = FALSE;
        connman_bool_t stay_connected = FALSE, ecall = FALSE;
        enum connman_session_roaming_policy roaming_policy =
@@ -1040,7 +1589,7 @@ int __connman_session_create(DBusMessage *msg)
 
        DBG("owner %s", owner);
 
-       if (ecall_session != NULL) {
+       if (ecall_info != NULL) {
                /*
                 * If there is an emergency call already going on,
                 * ignore session creation attempt
@@ -1100,7 +1649,10 @@ int __connman_session_create(DBusMessage *msg)
                        }
                        break;
                case DBUS_TYPE_STRING:
-                       if (g_str_equal(key, "RoamingPolicy") == TRUE) {
+                       if (g_str_equal(key, "ConnectionType") == TRUE) {
+                               dbus_message_iter_get_basic(&value, &val);
+                               type = string2type(val);
+                       } else if (g_str_equal(key, "RoamingPolicy") == TRUE) {
                                dbus_message_iter_get_basic(&value, &val);
                                roaming_policy = string2roamingpolicy(val);
                        } else {
@@ -1126,6 +1678,7 @@ int __connman_session_create(DBusMessage *msg)
 
        session = g_hash_table_lookup(session_hash, session_path);
        if (session != NULL) {
+               session = NULL;
                err = -EEXIST;
                goto err;
        }
@@ -1136,8 +1689,20 @@ int __connman_session_create(DBusMessage *msg)
                goto err;
        }
 
-       info = &session->info;
-       info_last = &session->info_last;
+       session->info = g_try_new0(struct session_info, 1);
+       if (session->info == NULL) {
+               err = -ENOMEM;
+               goto err;
+       }
+
+       session->info_last = g_try_new0(struct session_info, 1);
+       if (session->info_last == NULL) {
+               err = -ENOMEM;
+               goto err;
+       }
+
+       info = session->info;
+       info_last = session->info_last;
 
        session->owner = g_strdup(owner);
        session->session_path = session_path;
@@ -1146,8 +1711,8 @@ int __connman_session_create(DBusMessage *msg)
                g_dbus_add_disconnect_watch(connection, session->owner,
                                        owner_disconnect, session, NULL);
 
-       info->bearer = "";
-       info->online = FALSE;
+       info->state = CONNMAN_SESSION_STATE_DISCONNECTED;
+       info->type = type;
        info->priority = priority;
        info->avoid_handover = avoid_handover;
        info->stay_connected = stay_connected;
@@ -1155,7 +1720,7 @@ int __connman_session_create(DBusMessage *msg)
        info->idle_timeout = idle_timeout;
        info->ecall = ecall;
        info->roaming_policy = roaming_policy;
-       info->service = NULL;
+       info->entry = NULL;
        info->marker = 0;
 
        if (allowed_bearers == NULL) {
@@ -1191,15 +1756,13 @@ int __connman_session_create(DBusMessage *msg)
                                DBUS_TYPE_INVALID);
 
 
-       update_allowed_bearers(session);
-       update_service(session);
+       populate_service_list(session);
        if (info->ecall == TRUE) {
-               ecall_session = session;
+               ecall_info = info;
                update_ecall_sessions(session);
        }
 
-       info_last->bearer = info->bearer;
-       info_last->online = info->online;
+       info_last->state = info->state;
        info_last->priority = info->priority;
        info_last->avoid_handover = info->avoid_handover;
        info_last->stay_connected = info->stay_connected;
@@ -1207,19 +1770,27 @@ int __connman_session_create(DBusMessage *msg)
        info_last->idle_timeout = info->idle_timeout;
        info_last->ecall = info->ecall;
        info_last->roaming_policy = info->roaming_policy;
-       info_last->service = info->service;
+       info_last->entry = info->entry;
        info_last->marker = info->marker;
        info_last->allowed_bearers = info->allowed_bearers;
 
-       session->info_dirty = TRUE;
        session->append_all = TRUE;
 
-       g_timeout_add_seconds(0, session_cb, session);
+       session_changed(session, CONNMAN_SESSION_TRIGGER_SETTING);
 
        return 0;
 
 err:
        connman_error("Failed to create session");
+
+       if (session != NULL) {
+               if (session->info_last != NULL)
+                       g_free(session->info_last);
+               if (session->info != NULL)
+                       g_free(session->info);
+               g_free(session);
+       }
+
        g_free(session_path);
 
        g_slist_foreach(allowed_bearers, cleanup_bearer_info, NULL);
@@ -1263,20 +1834,26 @@ void __connman_session_set_mode(connman_bool_t enable)
 {
        DBG("enable %d", enable);
 
-       if (sessionmode == enable)
-               return;
+       if (sessionmode != enable) {
+               sessionmode = enable;
 
-       sessionmode = enable;
+               connman_dbus_property_changed_basic(CONNMAN_MANAGER_PATH,
+                               CONNMAN_MANAGER_INTERFACE, "SessionMode",
+                               DBUS_TYPE_BOOLEAN, &sessionmode);
+       }
 
        if (sessionmode == TRUE)
                __connman_service_disconnect_all();
 }
 
-static void service_add(struct connman_service *service)
+static void service_add(struct connman_service *service,
+                       const char *name)
 {
        GHashTableIter iter;
+       GSequenceIter *iter_service_list;
        gpointer key, value;
        struct connman_session *session;
+       struct service_entry *entry;
 
        DBG("service %p", service);
 
@@ -1288,20 +1865,21 @@ static void service_add(struct connman_service *service)
                if (service_match(session, service) == FALSE)
                        continue;
 
-               g_sequence_insert_sorted(session->service_list, service,
-                                               sort_services, session);
+               entry = create_service_entry(service, name,
+                                               CONNMAN_SERVICE_STATE_IDLE);
+               if (entry == NULL)
+                       continue;
 
-               session_cb(session);
-       }
-}
+               iter_service_list =
+                       g_sequence_insert_sorted(session->service_list,
+                                                       entry, sort_services,
+                                                       session);
 
-static gint service_in_session(gconstpointer a, gconstpointer b,
-                               gpointer user_data)
-{
-       if (a == b)
-               return 0;
+               g_hash_table_replace(session->service_hash, service,
+                                       iter_service_list);
 
-       return -1;
+               session_changed(session, CONNMAN_SESSION_TRIGGER_SERVICE);
+       }
 }
 
 static void service_remove(struct connman_service *service)
@@ -1309,35 +1887,27 @@ static void service_remove(struct connman_service *service)
 
        GHashTableIter iter;
        gpointer key, value;
-       GSequenceIter *seq_iter;
        struct connman_session *session;
        struct session_info *info;
-       struct connman_service *found_service;
 
        DBG("service %p", service);
 
        g_hash_table_iter_init(&iter, session_hash);
 
        while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
+               GSequenceIter *iter;
                session = value;
-               info = &session->info;
+               info = session->info;
 
-               if (session->service_list == NULL)
+               iter = g_hash_table_lookup(session->service_hash, service);
+               if (iter == NULL)
                        continue;
 
-               seq_iter = g_sequence_search(session->service_list, service,
-                                               service_in_session, NULL);
-               if (g_sequence_iter_is_end(seq_iter) == TRUE)
-                       continue;
-
-               g_sequence_remove(seq_iter);
-
-               found_service = g_sequence_get(seq_iter);
-               if (found_service != info->service)
-                       continue;
+               g_sequence_remove(iter);
 
-               info->service = NULL;
-               session_cb(session);
+               if (info->entry != NULL && info->entry->service == service)
+                       info->entry = NULL;
+               session_changed(session, CONNMAN_SESSION_TRIGGER_SERVICE);
        }
 }
 
@@ -1346,27 +1916,25 @@ static void service_state_changed(struct connman_service *service,
 {
        GHashTableIter iter;
        gpointer key, value;
-       struct connman_session *session;
-       struct session_info *info;
-       connman_bool_t online;
 
        DBG("service %p state %d", service, state);
 
        g_hash_table_iter_init(&iter, session_hash);
 
        while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
-               session = value;
-               info = &session->info;
+               struct connman_session *session = value;
+               GSequenceIter *service_iter;
 
-               if (info->service == service) {
-                       online = __connman_service_is_connected(service);
-                       if (info->online == online)
-                               continue;
+               service_iter = g_hash_table_lookup(session->service_hash, service);
+               if (service_iter != NULL) {
+                       struct service_entry *entry;
 
-                       info->online = online;
-                       session->info_dirty = TRUE;
-                       session_cb(session);
+                       entry = g_sequence_get(service_iter);
+                       entry->state = state;
                }
+
+               session_changed(session,
+                               CONNMAN_SESSION_TRIGGER_SERVICE);
        }
 }
 
@@ -1387,9 +1955,12 @@ static void ipconfig_changed(struct connman_service *service,
 
        while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
                session = value;
-               info = &session->info;
+               info = session->info;
+
+               if (info->state == CONNMAN_SESSION_STATE_DISCONNECTED)
+                       continue;
 
-               if (info->service == service) {
+               if (info->entry != NULL && info->entry->service == service) {
                        if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
                                ipconfig_ipv4_changed(session);
                        else if (type == CONNMAN_IPCONFIG_TYPE_IPV6)
@@ -1440,6 +2011,7 @@ void __connman_session_cleanup(void)
 
        g_hash_table_foreach(session_hash, release_session, NULL);
        g_hash_table_destroy(session_hash);
+       session_hash = NULL;
 
        dbus_connection_unref(connection);
 }