notifier: Don't unnecessarily report "online" state on disconnect
[platform/upstream/connman.git] / src / notifier.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2010  Intel Corporation. All rights reserved.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <gdbus.h>
27
28 #include "connman.h"
29
30 static DBusConnection *connection = NULL;
31
32 static GSList *notifier_list = NULL;
33 static GHashTable *service_hash = NULL;
34
35 static gint compare_priority(gconstpointer a, gconstpointer b)
36 {
37         const struct connman_notifier *notifier1 = a;
38         const struct connman_notifier *notifier2 = b;
39
40         return notifier2->priority - notifier1->priority;
41 }
42
43 /**
44  * connman_notifier_register:
45  * @notifier: notifier module
46  *
47  * Register a new notifier module
48  *
49  * Returns: %0 on success
50  */
51 int connman_notifier_register(struct connman_notifier *notifier)
52 {
53         DBG("notifier %p name %s", notifier, notifier->name);
54
55         notifier_list = g_slist_insert_sorted(notifier_list, notifier,
56                                                         compare_priority);
57
58         return 0;
59 }
60
61 /**
62  * connman_notifier_unregister:
63  * @notifier: notifier module
64  *
65  * Remove a previously registered notifier module
66  */
67 void connman_notifier_unregister(struct connman_notifier *notifier)
68 {
69         DBG("notifier %p name %s", notifier, notifier->name);
70
71         notifier_list = g_slist_remove(notifier_list, notifier);
72 }
73
74 #define MAX_TECHNOLOGIES 10
75
76 static volatile gint registered[MAX_TECHNOLOGIES];
77 static volatile gint enabled[MAX_TECHNOLOGIES];
78 static volatile gint connected[MAX_TECHNOLOGIES];
79
80 void __connman_notifier_list_registered(DBusMessageIter *iter, void *user_data)
81 {
82         int i;
83
84         for (i = 0; i < MAX_TECHNOLOGIES; i++) {
85                 const char *type = __connman_service_type2string(i);
86
87                 if (type == NULL)
88                         continue;
89
90                 if (g_atomic_int_get(&registered[i]) > 0)
91                         dbus_message_iter_append_basic(iter,
92                                                 DBUS_TYPE_STRING, &type);
93         }
94 }
95
96 void __connman_notifier_list_enabled(DBusMessageIter *iter, void *user_data)
97 {
98         int i;
99
100         for (i = 0; i < MAX_TECHNOLOGIES; i++) {
101                 const char *type = __connman_service_type2string(i);
102
103                 if (type == NULL)
104                         continue;
105
106                 if (g_atomic_int_get(&enabled[i]) > 0)
107                         dbus_message_iter_append_basic(iter,
108                                                 DBUS_TYPE_STRING, &type);
109         }
110 }
111
112 void __connman_notifier_list_connected(DBusMessageIter *iter, void *user_data)
113 {
114         int i;
115
116         for (i = 0; i < MAX_TECHNOLOGIES; i++) {
117                 const char *type = __connman_service_type2string(i);
118
119                 if (type == NULL)
120                         continue;
121
122                 if (g_atomic_int_get(&connected[i]) > 0)
123                         dbus_message_iter_append_basic(iter,
124                                                 DBUS_TYPE_STRING, &type);
125         }
126 }
127
128 static void technology_registered(enum connman_service_type type,
129                                                 connman_bool_t registered)
130 {
131         DBG("type %d registered %d", type, registered);
132
133         connman_dbus_property_changed_array(CONNMAN_MANAGER_PATH,
134                 CONNMAN_MANAGER_INTERFACE, "AvailableTechnologies",
135                 DBUS_TYPE_STRING, __connman_notifier_list_registered, NULL);
136 }
137
138 static void technology_enabled(enum connman_service_type type,
139                                                 connman_bool_t enabled)
140 {
141         GSList *list;
142
143         DBG("type %d enabled %d", type, enabled);
144
145         connman_dbus_property_changed_array(CONNMAN_MANAGER_PATH,
146                 CONNMAN_MANAGER_INTERFACE, "EnabledTechnologies",
147                 DBUS_TYPE_STRING, __connman_notifier_list_enabled, NULL);
148
149         for (list = notifier_list; list; list = list->next) {
150                 struct connman_notifier *notifier = list->data;
151
152                 if (notifier->service_enabled)
153                         notifier->service_enabled(type, enabled);
154         }
155 }
156
157 unsigned int __connman_notifier_count_connected(void)
158 {
159         unsigned int i, count = 0;
160
161         for (i = 0; i < MAX_TECHNOLOGIES; i++) {
162                 if (g_atomic_int_get(&connected[i]) > 0)
163                         count++;
164         }
165
166         return count;
167 }
168
169 const char *__connman_notifier_get_state(void)
170 {
171         unsigned int count = __connman_notifier_count_connected();
172
173         if (count > 0)
174                 return "online";
175
176         return "offline";
177 }
178
179 static void state_changed(connman_bool_t connected)
180 {
181         unsigned int count = __connman_notifier_count_connected();
182         char *state = "offline";
183         DBusMessage *signal;
184
185         if (count > 1)
186                 return;
187
188         if (count == 1) {
189                 if (connected == FALSE)
190                         return;
191
192                 state = "online";
193         }
194
195         connman_dbus_property_changed_basic(CONNMAN_MANAGER_PATH,
196                                 CONNMAN_MANAGER_INTERFACE, "State",
197                                                 DBUS_TYPE_STRING, &state);
198
199         signal = dbus_message_new_signal(CONNMAN_MANAGER_PATH,
200                                 CONNMAN_MANAGER_INTERFACE, "StateChanged");
201         if (signal == NULL)
202                 return;
203
204         dbus_message_append_args(signal, DBUS_TYPE_STRING, &state,
205                                                         DBUS_TYPE_INVALID);
206
207         g_dbus_send_message(connection, signal);
208 }
209
210 static void technology_connected(enum connman_service_type type,
211                                                 connman_bool_t connected)
212 {
213         DBG("type %d connected %d", type, connected);
214
215         connman_dbus_property_changed_array(CONNMAN_MANAGER_PATH,
216                 CONNMAN_MANAGER_INTERFACE, "ConnectedTechnologies",
217                 DBUS_TYPE_STRING, __connman_notifier_list_connected, NULL);
218
219         state_changed(connected);
220 }
221
222 void __connman_notifier_register(enum connman_service_type type)
223 {
224         DBG("type %d", type);
225
226         switch (type) {
227         case CONNMAN_SERVICE_TYPE_UNKNOWN:
228         case CONNMAN_SERVICE_TYPE_SYSTEM:
229         case CONNMAN_SERVICE_TYPE_GPS:
230         case CONNMAN_SERVICE_TYPE_VPN:
231         case CONNMAN_SERVICE_TYPE_GADGET:
232                 return;
233         case CONNMAN_SERVICE_TYPE_ETHERNET:
234         case CONNMAN_SERVICE_TYPE_WIFI:
235         case CONNMAN_SERVICE_TYPE_WIMAX:
236         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
237         case CONNMAN_SERVICE_TYPE_CELLULAR:
238                 break;
239         }
240
241         if (g_atomic_int_exchange_and_add(&registered[type], 1) == 0)
242                 technology_registered(type, TRUE);
243 }
244
245 void __connman_notifier_unregister(enum connman_service_type type)
246 {
247         DBG("type %d", type);
248
249         if (g_atomic_int_get(&registered[type]) == 0) {
250                 connman_error("notifier unregister underflow");
251                 return;
252         }
253
254         switch (type) {
255         case CONNMAN_SERVICE_TYPE_UNKNOWN:
256         case CONNMAN_SERVICE_TYPE_SYSTEM:
257         case CONNMAN_SERVICE_TYPE_GPS:
258         case CONNMAN_SERVICE_TYPE_VPN:
259         case CONNMAN_SERVICE_TYPE_GADGET:
260                 return;
261         case CONNMAN_SERVICE_TYPE_ETHERNET:
262         case CONNMAN_SERVICE_TYPE_WIFI:
263         case CONNMAN_SERVICE_TYPE_WIMAX:
264         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
265         case CONNMAN_SERVICE_TYPE_CELLULAR:
266                 break;
267         }
268
269         if (g_atomic_int_dec_and_test(&registered[type]) == TRUE)
270                 technology_registered(type, FALSE);
271 }
272
273 void __connman_notifier_enable(enum connman_service_type type)
274 {
275         DBG("type %d", type);
276
277         switch (type) {
278         case CONNMAN_SERVICE_TYPE_UNKNOWN:
279         case CONNMAN_SERVICE_TYPE_SYSTEM:
280         case CONNMAN_SERVICE_TYPE_GPS:
281         case CONNMAN_SERVICE_TYPE_VPN:
282         case CONNMAN_SERVICE_TYPE_GADGET:
283                 return;
284         case CONNMAN_SERVICE_TYPE_ETHERNET:
285         case CONNMAN_SERVICE_TYPE_WIFI:
286         case CONNMAN_SERVICE_TYPE_WIMAX:
287         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
288         case CONNMAN_SERVICE_TYPE_CELLULAR:
289                 break;
290         }
291
292         if (g_atomic_int_exchange_and_add(&enabled[type], 1) == 0)
293                 technology_enabled(type, TRUE);
294 }
295
296 void __connman_notifier_disable(enum connman_service_type type)
297 {
298         DBG("type %d", type);
299
300         if (g_atomic_int_get(&enabled[type]) == 0) {
301                 connman_error("notifier disable underflow");
302                 return;
303         }
304
305         switch (type) {
306         case CONNMAN_SERVICE_TYPE_UNKNOWN:
307         case CONNMAN_SERVICE_TYPE_SYSTEM:
308         case CONNMAN_SERVICE_TYPE_GPS:
309         case CONNMAN_SERVICE_TYPE_VPN:
310         case CONNMAN_SERVICE_TYPE_GADGET:
311                 return;
312         case CONNMAN_SERVICE_TYPE_ETHERNET:
313         case CONNMAN_SERVICE_TYPE_WIFI:
314         case CONNMAN_SERVICE_TYPE_WIMAX:
315         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
316         case CONNMAN_SERVICE_TYPE_CELLULAR:
317                 break;
318         }
319
320         if (g_atomic_int_dec_and_test(&enabled[type]) == TRUE)
321                 technology_enabled(type, FALSE);
322 }
323
324 void __connman_notifier_connect(enum connman_service_type type)
325 {
326         DBG("type %d", type);
327
328         switch (type) {
329         case CONNMAN_SERVICE_TYPE_UNKNOWN:
330         case CONNMAN_SERVICE_TYPE_SYSTEM:
331         case CONNMAN_SERVICE_TYPE_GPS:
332         case CONNMAN_SERVICE_TYPE_VPN:
333         case CONNMAN_SERVICE_TYPE_GADGET:
334                 return;
335         case CONNMAN_SERVICE_TYPE_ETHERNET:
336         case CONNMAN_SERVICE_TYPE_WIFI:
337         case CONNMAN_SERVICE_TYPE_WIMAX:
338         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
339         case CONNMAN_SERVICE_TYPE_CELLULAR:
340                 break;
341         }
342
343         if (g_atomic_int_exchange_and_add(&connected[type], 1) == 0)
344                 technology_connected(type, TRUE);
345 }
346
347 void __connman_notifier_disconnect(enum connman_service_type type)
348 {
349         DBG("type %d", type);
350
351         if (g_atomic_int_get(&connected[type]) == 0) {
352                 connman_error("notifier disconnect underflow");
353                 return;
354         }
355
356         switch (type) {
357         case CONNMAN_SERVICE_TYPE_UNKNOWN:
358         case CONNMAN_SERVICE_TYPE_SYSTEM:
359         case CONNMAN_SERVICE_TYPE_GPS:
360         case CONNMAN_SERVICE_TYPE_VPN:
361         case CONNMAN_SERVICE_TYPE_GADGET:
362                 return;
363         case CONNMAN_SERVICE_TYPE_ETHERNET:
364         case CONNMAN_SERVICE_TYPE_WIFI:
365         case CONNMAN_SERVICE_TYPE_WIMAX:
366         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
367         case CONNMAN_SERVICE_TYPE_CELLULAR:
368                 break;
369         }
370
371         if (g_atomic_int_dec_and_test(&connected[type]) == TRUE)
372                 technology_connected(type, FALSE);
373 }
374
375 static void technology_default(enum connman_service_type type)
376 {
377         const char *str;
378
379         str = __connman_service_type2string(type);
380         if (str == NULL)
381                 str = "";
382
383         connman_dbus_property_changed_basic(CONNMAN_MANAGER_PATH,
384                         CONNMAN_MANAGER_INTERFACE, "DefaultTechnology",
385                                                 DBUS_TYPE_STRING, &str);
386 }
387
388 void __connman_notifier_default_changed(struct connman_service *service)
389 {
390         enum connman_service_type type = connman_service_get_type(service);
391         char *interface;
392         GSList *list;
393
394         technology_default(type);
395
396         interface = connman_service_get_interface(service);
397         __connman_tethering_update_interface(interface);
398         g_free(interface);
399
400         for (list = notifier_list; list; list = list->next) {
401                 struct connman_notifier *notifier = list->data;
402
403                 if (notifier->default_changed)
404                         notifier->default_changed(service);
405         }
406 }
407
408 void __connman_notifier_service_add(struct connman_service *service,
409                                         const char *name)
410 {
411         GSList *list;
412
413         for (list = notifier_list; list; list = list->next) {
414                 struct connman_notifier *notifier = list->data;
415
416                 if (notifier->service_add)
417                         notifier->service_add(service, name);
418         }
419 }
420
421 void __connman_notifier_service_remove(struct connman_service *service)
422 {
423         GSList *list;
424
425         if (g_hash_table_lookup(service_hash, service) != NULL) {
426                 /*
427                  * This is a tempory check for consistency. It can be
428                  * removed when there are no reports for the following
429                  * error message.
430                  */
431                 connman_error("Service state machine inconsistency detected.");
432
433                 g_hash_table_remove(service_hash, service);
434         }
435
436         for (list = notifier_list; list; list = list->next) {
437                 struct connman_notifier *notifier = list->data;
438
439                 if (notifier->service_remove)
440                         notifier->service_remove(service);
441         }
442 }
443
444 void __connman_notifier_proxy_changed(struct connman_service *service)
445 {
446         GSList *list;
447
448         for (list = notifier_list; list; list = list->next) {
449                 struct connman_notifier *notifier = list->data;
450
451                 if (notifier->proxy_changed)
452                         notifier->proxy_changed(service);
453         }
454 }
455
456 static void offlinemode_changed(dbus_bool_t enabled)
457 {
458         DBG("enabled %d", enabled);
459
460         connman_dbus_property_changed_basic(CONNMAN_MANAGER_PATH,
461                                 CONNMAN_MANAGER_INTERFACE, "OfflineMode",
462                                                 DBUS_TYPE_BOOLEAN, &enabled);
463 }
464
465 void __connman_notifier_offlinemode(connman_bool_t enabled)
466 {
467         GSList *list;
468
469         DBG("enabled %d", enabled);
470
471         offlinemode_changed(enabled);
472
473         for (list = notifier_list; list; list = list->next) {
474                 struct connman_notifier *notifier = list->data;
475
476                 if (notifier->offline_mode)
477                         notifier->offline_mode(enabled);
478         }
479 }
480
481 static void notify_idle_state(connman_bool_t idle)
482 {
483         GSList *list;
484
485         DBG("idle %d", idle);
486
487         for (list = notifier_list; list; list = list->next) {
488                 struct connman_notifier *notifier = list->data;
489
490                 if (notifier->idle_state)
491                         notifier->idle_state(idle);
492         }
493 }
494
495 void __connman_notifier_service_state_changed(struct connman_service *service,
496                                         enum connman_service_state state)
497 {
498         GSList *list;
499         unsigned int old_size;
500         connman_bool_t found;
501
502         for (list = notifier_list; list; list = list->next) {
503                 struct connman_notifier *notifier = list->data;
504
505                 if (notifier->service_state_changed)
506                         notifier->service_state_changed(service, state);
507         }
508
509         old_size = g_hash_table_size(service_hash);
510         found = g_hash_table_lookup(service_hash, service) != NULL;
511
512         switch (state) {
513         case CONNMAN_SERVICE_STATE_UNKNOWN:
514         case CONNMAN_SERVICE_STATE_DISCONNECT:
515         case CONNMAN_SERVICE_STATE_IDLE:
516                 if (found == FALSE)
517                         break;
518
519                 g_hash_table_remove(service_hash, service);
520                 if (old_size == 1)
521                         notify_idle_state(TRUE);
522
523                 break;
524         case CONNMAN_SERVICE_STATE_ASSOCIATION:
525         case CONNMAN_SERVICE_STATE_CONFIGURATION:
526         case CONNMAN_SERVICE_STATE_READY:
527         case CONNMAN_SERVICE_STATE_ONLINE:
528         case CONNMAN_SERVICE_STATE_FAILURE:
529                 if (found == TRUE)
530                         break;
531
532                 g_hash_table_insert(service_hash, service, service);
533                 if (old_size == 0)
534                         notify_idle_state(FALSE);
535
536                 break;
537         }
538 }
539
540 void __connman_notifier_ipconfig_changed(struct connman_service *service,
541                                         struct connman_ipconfig *ipconfig)
542 {
543         GSList *list;
544
545         for (list = notifier_list; list; list = list->next) {
546                 struct connman_notifier *notifier = list->data;
547
548                 if (notifier->ipconfig_changed)
549                         notifier->ipconfig_changed(service, ipconfig);
550         }
551 }
552
553 static connman_bool_t technology_supported(enum connman_service_type type)
554 {
555         switch (type) {
556         case CONNMAN_SERVICE_TYPE_UNKNOWN:
557         case CONNMAN_SERVICE_TYPE_SYSTEM:
558         case CONNMAN_SERVICE_TYPE_GPS:
559         case CONNMAN_SERVICE_TYPE_VPN:
560         case CONNMAN_SERVICE_TYPE_GADGET:
561                 return FALSE;
562         case CONNMAN_SERVICE_TYPE_ETHERNET:
563         case CONNMAN_SERVICE_TYPE_WIFI:
564         case CONNMAN_SERVICE_TYPE_WIMAX:
565         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
566         case CONNMAN_SERVICE_TYPE_CELLULAR:
567                 break;
568         }
569
570         return TRUE;
571 }
572
573 connman_bool_t __connman_notifier_is_registered(enum connman_service_type type)
574 {
575         DBG("type %d", type);
576
577         if (technology_supported(type) == FALSE)
578                 return FALSE;
579
580         if (g_atomic_int_get(&registered[type]) > 0)
581                 return TRUE;
582
583         return FALSE;
584 }
585
586 connman_bool_t __connman_notifier_is_enabled(enum connman_service_type type)
587 {
588         DBG("type %d", type);
589
590         if (technology_supported(type) == FALSE)
591                 return FALSE;
592
593         if (g_atomic_int_get(&enabled[type]) > 0)
594                 return TRUE;
595
596         return FALSE;
597 }
598
599 int __connman_notifier_init(void)
600 {
601         DBG("");
602
603         connection = connman_dbus_get_connection();
604
605         service_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
606                                                 NULL, NULL);
607
608
609         return 0;
610 }
611
612 void __connman_notifier_cleanup(void)
613 {
614         DBG("");
615
616         g_hash_table_destroy(service_hash);
617         service_hash = NULL;
618
619         dbus_connection_unref(connection);
620 }