am a01cbee5: am 0bb87fe9: Merge "Signal client thread in error cases."

* commit 'a01cbee5f2b1457d7a4ce4848bf341dc2eccd5f6':
  Signal client thread in error cases.
diff --git a/.gitignore b/.gitignore
index 7430b9c..67b20dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,11 +27,8 @@
 
 ylwrap
 bluez.pc
-lib/bluetooth
 src/builtin.h
 src/bluetoothd
-src/bluetooth.exp
-src/bluetooth.ver
 audio/telephony.c
 scripts/bluetooth.rules
 scripts/97-bluetooth.rules
@@ -42,6 +39,7 @@
 sbc/sbcinfo
 sbc/sbctester
 
+attrib/gatttool
 tracer/hcitrace
 tools/avctrl
 tools/avinfo
diff --git a/.mailmap b/.mailmap
index 2d7ce47..cfd415f 100644
--- a/.mailmap
+++ b/.mailmap
@@ -3,3 +3,5 @@
 Elvis Pfützenreuter <epx@signove.com>			<epx@signove.com>
 Santiago Carot-Nemesio <scarot@libresoft.es>		<scarot@libresoft.es>
 José Antonio Santos Cadenas <santoscadenas@gmail.com>	<santoscadenas@gmail.com>
+Waldemar Rymarkiewicz <waldemar.rymarkiewicz@tieto.com>	<Waldemar.Rymarkiewicz@tieto.com>
+Alok Barsode <alokbarsode@gmail.com>			<alok@greatbear.(none)>
diff --git a/AUTHORS b/AUTHORS
index 8e0a012..939a0a8 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -35,7 +35,7 @@
 Siarhei Siamashka <siarhei.siamashka@nokia.com>
 Nick Pelly <npelly@google.com>
 Lennart Poettering <lennart@poettering.net>
-Gustavo F. Padovan <gustavo@las.ic.unicamp.br>
+Gustavo F. Padovan <padovan@profusion.mobi>
 Marc-Andre Lureau <marc-andre.lureau@nokia.com>
 Bea Lam <bea.lam@nokia.com>
 Zygo Blaxell <zygo.blaxell@xandros.com>
@@ -50,3 +50,7 @@
 José Antonio Santos Cadenas <jcaden@libresoft.es>
 Francisco Alecrim <francisco.alecrim@openbossa.org>
 Daniel Orstadius <daniel.orstadius@gmail.com>
+Anderson Briglia <anderson.briglia@openbossa.org>
+Anderson Lizardo <anderson.lizardo@openbossa.org>
+Bruna Moreira <bruna.moreira@openbossa.org>
+Brian Gix <bgix@codeaurora.org>
diff --git a/ChangeLog b/ChangeLog
index 4aa4589..427ce25 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,187 @@
+ver 4.89:
+	Fix issue with name resolving when discovery is suspended.
+	Fix issue with parsing flags of advertising report.
+	Fix issue with SEP handling if interface is disabled.
+	Fix issue with device object creation on disconnect event.
+	Fix issue with indicators whenever the driver is initialized.
+	Fix issue with call indicator when parsing call info reply.
+	Fix issue with crash and allowed GATT MTU was too large.
+	Add support for SDP record of Primary GATT services.
+	Add support for interactive mode for GATT utility.
+
+ver 4.88:
+	Fix issue with HID channel reference count handling.
+	Fix issue with daemon exit on badly formatted AT+VTS.
+	Fix issue with crash while parsing of endpoint properties.
+	Fix issue with possible crash on AVDTP Suspend request timeout.
+	Fix issue with stopping inquiry before adapter is initialized.
+	Fix issue with creating device object when connection fails.
+	Fix issue with sending HCIDEVUP when adapter is already up.
+	Fix issue with handling bonding IO channel closing.
+	Fix agent cancellation in security mode 3 situations.
+	Update pairing code to support management interface.
+
+ver 4.87:
+	Fix issue with initialization when adapter is already up.
+	Fix issue with attribute server MTU and incoming connections.
+	Fix issue with duplicate characteristics after discovery.
+
+ver 4.86:
+	Revert wrong fix for SDP PDU size error response.
+	Fix various memory leaks in A2DP and AVDTP support.
+	Add Routing property to MediaTransport interface
+	Add proper tracking mechanism to NREC status.
+	Add READ_BLOB_REQUEST support to attribute server.
+
+ver 4.85:
+	Fix issue with event mask setting for older adapters.
+	Fix issue with device creation and pairing failures.
+	Add support for telephony support via oFono.
+	Add support for characteristic security level.
+	Update support for service registration.
+
+ver 4.84:
+	Fix issue with wrong parameters and device found signals.
+	Fix issue with leaking EIR data if RSSI does not change.
+	Fix issue with adapter initialization state.
+	Fix issue with closing of SDP server sockets.
+
+ver 4.83:
+	Fix issue with already connected HFP/HSP endpoints.
+	Fix missing reply when create device is canceled.
+	Fix memory leak within the attribute server.
+	Fix memory leak with unused extended inquiry name.
+	Fix setting paired state when device->authr is false.
+	Fix clearing authentication request for renewed keys.
+	Add support for storing link keys in runtime memory.
+	Update support for primary service discovery.
+
+ver 4.82:
+	Fix crash with mmap of files with multiples of page size.
+	Fix HFP response and hold (AT+BTRH) command response.
+	Fix device creation error response when powered off.
+	Fix device removal when connecting/browsing fails.
+	Add initial attribute permission implementation.
+	Add AVDTP SRC stream send buffer size verification.
+	Add support for setting link policy based on features.
+
+ver 4.81:
+	Fix issue with telephony driver initialization.
+	Fix issue with adapter services list initialization.
+	Fix crash after simultaneous authentication requests.
+	Add support for primary service search on device creation.
+
+ver 4.80:
+	Fix legacy link key storing for some buggy adapters.
+	Fix invalid memory access when EIR field length is zero.
+	Fix adapter initialization to wait for kernel HCI commands.
+	Fix initialization of adapters which are already up.
+	Fix possible race condition when initializing adapters.
+	Fix possible crashes when attempting to connect AVDTP.
+	Fix not aborting sink stream configuration on disconnect.
+	Fix not indicating disconnected state when connecting to AVDTP.
+	Fix not dropping AVDTP session when canceling stream setup.
+	Fix AVDTP abort not being send when the state is idle.
+	Fix regression with Low Energy and interleave discovery.
+	Add a new configuration option to disable Low Energy support.
+	Add iwmmxt optimization for SBC for ARM PXA series CPUs.
+	Update support for GATT Primary Service Discovery.
+	Update MCAP and HDP support.
+
+ver 4.79:
+	Fix issue with adapter initialization race condition.
+	Update new Bluetooth Management interface support.
+
+ver 4.78:
+	Fix various issues with AVDTP timer handling.
+	Fix various issues with handling of mode changes.
+	Fix issue with audio disconnect watch in connecting state.
+	Fix issue with handling call waiting indicators in telephony.
+	Fix issue with handling UUID parameter and RegisterEndpoint.
+	Add initial support for Bluetooth Management interface.
+	Add support for Application property to HealthChannel.
+
+ver 4.77:
+	Fix issue with device name and accessing already freed memory.
+	Fix issue with handling CHLD=0 command for handsfree.
+	Fix issue with manager properties and no adapters.
+	Fix issue with properties and broken service records.
+	Fix issue with A2DP playback and sample rate changes.
+	Update MCAP and HDP support.
+
+ver 4.76:
+	Fix issue in telephony driver with hanging up held call.
+	Fix issue in telephony driver with notifications when on hold.
+	Fix issue with blocking on setconf confirmation callback.
+	Fix issue with not always signaling new streams as sinks.
+	Fix issue with errors in case of endpoint request timeout.
+	Fix issue with HFP/HSP microphone and speaker gain values.
+	Add source if the device attempt to configure local sink stream.
+	Add PSM option for GATT/ATT over BR/EDR on gatttool.
+	Add support for GATT/ATT Attribute Write Request.
+	Update MCAP and HDP support.
+
+ver 4.75:
+	Fix use of uninitialized variable on legacy pairing.
+	Fix mismatch of attribute protocol opcode.
+
+ver 4.74:
+	Fix regression for Legacy Pairing.
+	Fix wrong PSM value for attribute protocol.
+	Fix issue with RSSI field in advertising reports.
+	Add support for Add BR/EDR and LE interleaved discovery.
+	Add support for GATT write characteristic value option.
+	Add support for specifying download address for AR300x.
+
+ver 4.73:
+	Fix problem with EIR data when setting the name.
+	Fix reading local name from command complete event.
+	Fix registering local endpoints with disabled socket interface.
+	Add support for more HCI operations using ops infrastructure.
+	Add support for GATT characteristic hierarchy.
+	Add support for GATT indications.
+
+ver 4.72:
+	Fix memory leak while connecting BTIO channels.
+	Fix crash with GStreamer plugin if SBC is not supported.
+	Fix issue with GATT server stop sending notifications.
+	Fix issue with GATT and dealing with the minimum MTU size.
+	Fix issue with file descriptor leak in GATT client.
+	Add support for UUID 128-bit handling in attribute client.
+	Add support for encoders/decoders for MTU Exchange.
+	Add support for the MTU Exchange procedure to the server.
+	Add support for a per channel MTU to the ATT server.
+	Add support for Characteristic interface.
+	Add support for new Media API and framework.
+	Add initial support for HDP plugin.
+
+ver 4.71:
+	Fix compilation when SBC support in not enabled.
+	Fix crash with RequestSession and application disconnects.
+	Fix memory leak and possible crash when removing audio device.
+	Fix issue with closing stream of locked sep when reconfiguring.
+	Fix issue where discovery could interfere with bonding.
+	Fix issue with Connected status when PS3 BD remote connects.
+	Fix issue with lifetime of fake input devices.
+	Add support for compile time option of oui.txt path.
+	Add support for printing IEEE1284 device ID for CUPS.
+	Add plugin for setting adapter class via DMI.
+	Add more features for attribute protocol and profile.
+	Add initial support for MCAP.
+
+ver 4.70:
+	Fix incoming call indication handling when in WAITING state.
+	Fix various SDP related qualification test case issues.
+	Fix logic to write EIR when SDP records are changed.
+	Fix UTF-8 validity check for remote names in EIR.
+	Add support for UUID-128 extended inquiry response.
+	Add service UUIDs from EIR to the DeviceFound signal.
+	Add fast connectable feature for Handsfree profile.
+	Add HCI command and event definitions for AMP support.
+	Add firmware download support for Qualcommh devices.
+	Add host level support for Atheros AR300x device.
+	Add initial support of ATT and GATT for basic rate.
+
 ver 4.69:
 	Fix issue with calling g_option_context_free() twice.
 	Fix inconsistencies with initial LE commands and events.
diff --git a/Makefile.am b/Makefile.am
index f4bf87d..ec1ca97 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -42,7 +42,7 @@
 plugin_LTLIBRARIES =
 
 
-lib_headers = lib/bluetooth.h lib/hci.h lib/hci_lib.h \
+lib_headers = lib/bluetooth.h lib/hci.h lib/hci_lib.h lib/mgmt.h \
 			lib/sco.h lib/l2cap.h lib/sdp.h lib/sdp_lib.h \
 				lib/rfcomm.h lib/bnep.h lib/cmtp.h lib/hidp.h
 local_headers = $(foreach file,$(lib_headers), lib/bluetooth/$(notdir $(file)))
@@ -53,7 +53,7 @@
 
 lib_libbluetooth_la_SOURCES = $(lib_headers) \
 					lib/bluetooth.c lib/hci.c lib/sdp.c
-lib_libbluetooth_la_LDFLAGS = -version-info 12:0:9
+lib_libbluetooth_la_LDFLAGS = -version-info 13:5:10
 lib_libbluetooth_la_DEPENDENCIES = $(local_headers)
 
 CLEANFILES += $(local_headers)
@@ -65,6 +65,7 @@
 sbc_libsbc_la_SOURCES = sbc/sbc.h sbc/sbc.c sbc/sbc_math.h sbc/sbc_tables.h \
 			sbc/sbc_primitives.h sbc/sbc_primitives.c \
 			sbc/sbc_primitives_mmx.h sbc/sbc_primitives_mmx.c \
+			sbc/sbc_primitives_iwmmxt.h sbc/sbc_primitives_iwmmxt.c \
 			sbc/sbc_primitives_neon.h sbc/sbc_primitives_neon.c \
 			sbc/sbc_primitives_armv6.h sbc/sbc_primitives_armv6.c
 
@@ -87,21 +88,24 @@
 endif
 endif
 
+attrib_sources = attrib/att.h attrib/att.c attrib/gatt.h attrib/gatt.c \
+		attrib/gattrib.h attrib/gattrib.c
 
-if NETLINK
-plugin_LTLIBRARIES += plugins/netlink.la
+gdbus_sources = gdbus/gdbus.h gdbus/mainloop.c gdbus/watch.c \
+					gdbus/object.c gdbus/polkit.c
 
-plugins_netlink_la_LIBADD = @NETLINK_LIBS@
-plugins_netlink_la_LDFLAGS = -module -avoid-version -no-undefined
-plugins_netlink_la_CFLAGS = -fvisibility=hidden @DBUS_CFLAGS@ \
-					@GLIB_CFLAGS@ @NETLINK_CFLAGS@
-endif
-
-gdbus_sources = gdbus/gdbus.h gdbus/mainloop.c gdbus/object.c gdbus/watch.c
+btio_sources = btio/btio.h btio/btio.c
 
 builtin_modules =
 builtin_sources =
 builtin_nodist =
+mcap_sources =
+
+if MCAP
+mcap_sources += health/mcap_lib.h health/mcap_internal.h \
+		health/mcap.h health/mcap.c \
+		health/mcap_sync.c
+endif
 
 if PNATPLUGIN
 builtin_modules += pnat
@@ -127,7 +131,9 @@
 			audio/avdtp.h audio/avdtp.c \
 			audio/ipc.h audio/ipc.c \
 			audio/unix.h audio/unix.c \
-			audio/telephony.h
+			audio/media.h audio/media.c \
+			audio/transport.h audio/transport.c \
+			audio/telephony.h audio/a2dp-codecs.h
 builtin_nodist += audio/telephony.c
 
 noinst_LIBRARIES = audio/libtelephony.a
@@ -168,11 +174,46 @@
 builtin_sources += plugins/service.c
 endif
 
-builtin_modules += hciops
-builtin_sources += plugins/hciops.c
+if ATTRIBPLUGIN
 
+if READLINE
+bin_PROGRAMS += attrib/gatttool
+
+attrib_gatttool_SOURCES = attrib/gatttool.c attrib/att.c attrib/gatt.c \
+				attrib/gattrib.c btio/btio.c \
+				src/glib-helper.h src/glib-helper.c \
+				attrib/gatttool.h attrib/interactive.c \
+				attrib/utils.c
+attrib_gatttool_LDADD = lib/libbluetooth.la @GLIB_LIBS@ @READLINE_LIBS@
+endif
+
+builtin_modules += attrib
+builtin_sources += attrib/main.c \
+		attrib/manager.h attrib/manager.c \
+		attrib/client.h attrib/client.c \
+		attrib/example.h attrib/example.c
+endif
+
+if HEALTHPLUGIN
+builtin_modules += health
+builtin_sources += health/hdp_main.c health/hdp_types.h \
+			health/hdp_manager.h health/hdp_manager.c \
+			health/hdp.h health/hdp.c \
+			health/hdp_util.h health/hdp_util.c
+endif
+
+builtin_modules += hciops mgmtops
+builtin_sources += plugins/hciops.c plugins/mgmtops.c
+
+if HAL
 builtin_modules += hal
 builtin_sources += plugins/hal.c
+else
+builtin_modules += formfactor
+builtin_sources += plugins/formfactor.c
+endif
+
+EXTRA_DIST += plugins/hal.c plugins/formfactor.c
 
 builtin_modules += storage
 builtin_sources += plugins/storage.c
@@ -185,11 +226,14 @@
 sbin_PROGRAMS += src/bluetoothd
 
 src_bluetoothd_SOURCES = $(gdbus_sources) $(builtin_sources) \
+			$(attrib_sources) $(btio_sources) \
+			$(mcap_sources) src/bluetooth.ver \
 			src/main.c src/log.h src/log.c \
-			src/security.c src/rfkill.c src/hcid.h src/sdpd.h \
+			src/rfkill.c src/hcid.h src/sdpd.h \
 			src/sdpd-server.c src/sdpd-request.c \
 			src/sdpd-service.c src/sdpd-database.c \
-			src/sdp-xml.h src/sdp-xml.c src/btio.h src/btio.c \
+			src/attrib-server.h src/attrib-server.c \
+			src/sdp-xml.h src/sdp-xml.c \
 			src/textfile.h src/textfile.c \
 			src/glib-helper.h src/glib-helper.c \
 			src/oui.h src/oui.c src/uinput.h src/ppoll.h \
@@ -201,18 +245,19 @@
 			src/adapter.h src/adapter.c \
 			src/device.h src/device.c \
 			src/dbus-common.c src/dbus-common.h \
-			src/dbus-hci.h src/dbus-hci.c
+			src/event.h src/event.c
 src_bluetoothd_LDADD = lib/libbluetooth.la @GLIB_LIBS@ @DBUS_LIBS@ \
-							@CAPNG_LIBS@ -ldl
+							@CAPNG_LIBS@ -ldl -lrt
 src_bluetoothd_LDFLAGS = -Wl,--export-dynamic \
-					-Wl,--version-script=src/bluetooth.ver
-src_bluetoothd_DEPENDENCIES = src/bluetooth.ver lib/libbluetooth.la
+				-Wl,--version-script=$(srcdir)/src/bluetooth.ver
+
+src_bluetoothd_DEPENDENCIES = lib/libbluetooth.la
 
 builtin_files = src/builtin.h $(builtin_nodist)
 
 nodist_src_bluetoothd_SOURCES = $(builtin_files)
 
-CLEANFILES += src/bluetooth.ver src/bluetooth.exp $(builtin_files)
+CLEANFILES += $(builtin_files)
 
 man_MANS = src/bluetoothd.8
 
@@ -248,7 +293,7 @@
 audio_libasound_module_ctl_bluetooth_la_CFLAGS = @ALSA_CFLAGS@
 
 if CONFIGFILES
-alsaconfdir = $(sysconfdir)/alsa
+alsaconfdir = $(datadir)/alsa
 
 alsaconf_DATA = audio/bluetooth.conf
 endif
@@ -271,9 +316,9 @@
 				audio/rtp.h audio/ipc.h audio/ipc.c
 audio_libgstbluetooth_la_LDFLAGS = -module -avoid-version
 audio_libgstbluetooth_la_LIBADD = sbc/libsbc.la lib/libbluetooth.la \
-				@GSTREAMER_LIBS@ -lgstaudio-0.10 -lgstrtp-0.10
+				@DBUS_LIBS@ @GSTREAMER_LIBS@ -lgstaudio-0.10 -lgstrtp-0.10
 audio_libgstbluetooth_la_CFLAGS = -fvisibility=hidden -fno-strict-aliasing \
-						$(AM_CFLAGS) @GSTREAMER_CFLAGS@
+						$(AM_CFLAGS) @DBUS_CFLAGS@ @GSTREAMER_CFLAGS@
 endif
 endif
 
@@ -313,7 +358,8 @@
 		doc/adapter-api.txt doc/device-api.txt \
 		doc/service-api.txt doc/agent-api.txt doc/attribute-api.txt \
 		doc/serial-api.txt doc/network-api.txt \
-		doc/input-api.txt doc/audio-api.txt doc/control-api.txt
+		doc/input-api.txt doc/audio-api.txt doc/control-api.txt \
+		doc/hfp-api.txt doc/health-api.txt doc/assigned-numbers.txt
 
 AM_YFLAGS = -d
 
@@ -321,14 +367,18 @@
 		-DBLUETOOTH_PLUGIN_BUILTIN -DPLUGINDIR=\""$(plugindir)"\"
 
 INCLUDES = -I$(builddir)/lib -I$(builddir)/src -I$(srcdir)/src \
-			-I$(srcdir)/audio -I$(srcdir)/sbc -I$(srcdir)/gdbus
+			-I$(srcdir)/audio -I$(srcdir)/sbc -I$(srcdir)/gdbus \
+			-I$(srcdir)/attrib -I$(srcdir)/btio
 
+if MCAP
+INCLUDES += -I$(builddir)/health
+endif
 
 pkgconfigdir = $(libdir)/pkgconfig
 
 pkgconfig_DATA = bluez.pc
 
-DISTCHECK_CONFIGURE_FLAGS = --disable-udevrules
+DISTCHECK_CONFIGURE_FLAGS = --disable-udevrules --enable-attrib
 
 DISTCLEANFILES = $(pkgconfig_DATA)
 
@@ -341,18 +391,6 @@
 src/builtin.h: src/genbuiltin $(builtin_sources)
 	$(AM_V_GEN)$(srcdir)/src/genbuiltin $(builtin_modules) > $@
 
-src/bluetooth.exp: $(src_bluetoothd_OBJECTS)
-	$(AM_V_GEN)$(NM) $^ | $(AWK) '{ print $$3 }' | sort -u | \
-				$(EGREP) -e '^btd_' -e '^g_dbus_' > $@
-	$(AM_V_at)echo -e "info" >> $@
-	$(AM_V_at)echo -e "error" >> $@
-	$(AM_V_at)echo -e "debug" >> $@
-
-src/bluetooth.ver: src/bluetooth.exp
-	$(AM_V_at)echo "{ global:" > $@
-	$(AM_V_GEN)$(SED) -e "s/\(.*\)/\1;/" $< >> $@
-	$(AM_V_at)echo "local: *; };" >> $@
-
 audio/telephony.c: audio/@TELEPHONY_DRIVER@
 	$(AM_V_GEN)$(LN_S) $(abs_top_srcdir)/$< $@
 
@@ -365,5 +403,5 @@
 	$(AM_V_at)$(MKDIR_P) lib/bluetooth
 	$(AM_V_GEN)$(LN_S) $(abs_top_srcdir)/$< $@
 
-clean-local: lib/bluetooth
-	@$(RM) -r $<
+clean-local:
+	$(RM) -r lib/bluetooth
diff --git a/Makefile.tools b/Makefile.tools
index 2dbf925..9c43202 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -12,7 +12,9 @@
 noinst_PROGRAMS += tools/avinfo tools/ppporc \
 				tools/hcieventmask tools/hcisecfilter
 
-tools_rfcomm_SOURCES = tools/main.c tools/parser.y tools/lexer.l \
+tools/kword.c: tools/parser.h
+
+tools_rfcomm_SOURCES = tools/rfcomm.c tools/parser.y tools/lexer.l \
 					tools/kword.h tools/kword.c
 EXTRA_tools_rfcomm_SOURCES = tools/parser.h tools/parser.c \
 							tools/lexer.c
@@ -23,7 +25,9 @@
 tools_hciattach_SOURCES = tools/hciattach.c tools/hciattach.h \
 						tools/hciattach_st.c \
 						tools/hciattach_ti.c \
-						tools/hciattach_tialt.c
+						tools/hciattach_tialt.c \
+						tools/hciattach_ath3k.c \
+						tools/hciattach_qualcomm.c
 tools_hciattach_LDADD = lib/libbluetooth.la
 
 tools_hciconfig_SOURCES = tools/hciconfig.c tools/csr.h tools/csr.c \
@@ -168,7 +172,7 @@
 
 test_agent_LDADD = @DBUS_LIBS@
 
-test_btiotest_SOURCES = test/btiotest.c src/btio.h src/btio.c
+test_btiotest_SOURCES = test/btiotest.c btio/btio.h btio/btio.c
 test_btiotest_LDADD = @GLIB_LIBS@ lib/libbluetooth.la
 
 test_test_textfile_SOURCES = test/test-textfile.c src/textfile.h src/textfile.c
@@ -185,8 +189,8 @@
 		test/test-manager test/test-adapter test/test-device \
 		test/test-service test/test-serial test/test-telephony \
 		test/test-network test/simple-agent test/simple-service \
-		test/test-audio test/test-input \
-		test/service-record.dtd test/service-did.xml \
+		test/simple-endpoint test/test-audio test/test-input \
+		test/test-attrib test/service-record.dtd test/service-did.xml \
 		test/service-spp.xml test/service-opp.xml test/service-ftp.xml
 
 
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..1357db9
--- /dev/null
+++ b/TODO
@@ -0,0 +1,185 @@
+Background
+==========
+
+- Priority scale: High, Medium and Low
+
+- Complexity scale: C1, C2, C4 and C8.  The complexity scale is exponential,
+  with complexity 1 being the lowest complexity.  Complexity is a function
+  of both task 'complexity' and task 'scope'.
+
+  The general rule of thumb is that a complexity 1 task should take 1-2 weeks
+  for a person very familiar with BlueZ codebase.  Higher complexity tasks
+  require more time and have higher uncertainty.
+
+  Higher complexity tasks should be refined into several lower complexity tasks
+  once the task is better understood.
+
+General
+==========
+
+- Rename glib-helper file to a more convenient name. The ideia is try to keep
+  only sdp helpers functions. bt_* prefix shall be also changed.
+
+  Priority: Low
+  Complexity: C1
+
+Low Energy
+==========
+
+- Advertising management. Adapter interface needs to be changed to manage
+  connection modes, adapter type and advertising policy. See Volume 3,
+  Part C, section 9.3. If Attribute Server is enabled the LE capable
+  adapter shall to start advertising. Further investigation is necessary
+  to define which connectable mode needs to be supported: Non-connectable,
+  directed connectable and undirected connectable. Basically, two connectable
+  scenarios shall be addressed:
+  1. GATT client is disconnected, but intends to become a Peripheral to
+     receive indications/notifications.
+  2. GATT server intends to accept connections.
+
+  Priority: Medium
+  Complexity: C2
+
+- Define Auto Connection Establishment Procedure. Some profiles such as
+  Proximity requires an active link to identify path lost situation. It is
+  necessary to define how to manage connections, it seems that White List
+  is appropriated to address auto connections, however is not clear if the
+  this procedure shall be a profile specific detail or if the remote device
+  object can expose a property "WhiteList", maybe "Trusted" property can be
+  also used for this purpose. Another alternative is to define a method to
+  allow application to request/register the wanted scanning/connection
+  parameters. Before start this task, a RFC/PATCH shall be sent to the ML.
+  See Volume 3, Part C, section 9.3.5 for more information.
+
+  Priority: Medium
+  Complexity: C2
+
+- Implement a tool(or extend hciconfig) to setup the advertising parameters
+  and data. Extend hciconfig passing extra arguments when enabling the
+  advertises is not the right approach, it will be almost impossible to
+  address all arguments needed in an acceptable way. For testing, we need
+  a tool to change easily the AD Flags, the UUIDs and other data that can be
+  exported through the advertising data field. Suggestions: 1) extend hciconfig
+  passing a config file when enabling advertises; 2) write a ncurses based tool
+
+  Priority: Medium
+  Complexity: C2
+
+- Device Name Characteristic is a GAP characteristic for Low Energy. This
+  characteristic shall be integrated/used in the discovery procedure. The
+  ideia is to report the value of this characteristic using DeviceFound signals.
+  Discussion with the community is needed before to start this task. Other GAP
+  characteristics for LE needs to follow a similar approach. It is not clear
+  if all GAP characteristics can be exposed using properties instead of a primary
+  service characteristics.
+  See Volume 3, Part C, section 12.1 for more information.
+
+  Priority: Low
+  Complexity: C2
+
+ATT/GATT
+========
+
+- For BR/EDR, Discover All Primary Services shall be started after SDP if the
+  remote device exports a Generic Attribute Profile service record. It is
+  applied to CreateDevice and CreatePairedDevice.
+
+  Priority: Medium
+  Complexity: C1
+
+- Add ATT/GATT parsing to hcidump
+
+  Priority: Medium
+  Complexity: C2
+
+- GATT server: fix MTU exchange
+
+  Priority: Medium
+  Complexity: C2
+
+- gatttool: add an interactive command prompt mode. Many LE devices
+  expect the connection to stay up a long time and disable advertising
+  after a disconnection so it's inconvenient to use gatttool in the
+  current "single operation at a time" mode.
+
+  Priority: Medium
+  Complexity: C2
+
+- gatttool should have the ability to wait for req responses before
+  quitting (some servers require a small sleep even with cmd's). Maybe a
+  --delay-exit or --timeout command line switch.
+
+  Priority: Low
+  Complexity: C1
+
+- Refactoring of gatt.c functions. Currently, the callbacks of the services
+  and characteristics discovery functions return the ATT PDU and the caller
+  needs to call again the same function to fetch the remaining data when
+  necessary. Investigate if all results can be returned in the callback
+  result to avoid repeated code. Before change the code, please analyze
+  if this change will not break the GATT/ATT qualification tests. Maybe
+  an interactive fetch/query is necessary to pass the tests.
+
+  Priority: Low
+  Complexity: C1
+
+- Client needs to export a property in the Device Characteristic hierarchy
+  to manage characteristic value changes reports in the remote device.
+  Currently, Client Characteristic Configuration attribute is not exposed
+  as an object. The user needs to use gatttool to change the value of the
+  this attribute to receive notification/indications. Export this attribute
+  as a property is a proposal that needs further discussion.
+
+  Priority: Low
+  Complexity: C1
+
+- Attribute server should process queued GATT/ATT commands if the
+  client disconnects. The client can simply send a command and quit,
+  without wait for a response(ex: Write Command). For this scenario
+  that the client disconnects the link quickly the queued received
+  command is ignored.
+
+  Priority: Low
+  Complecity: C1
+
+- Add sdp discovery support to gattool with BR (--sdp, default is 0x1f)
+
+  Priority: Low
+  Complexity: C1
+
+- Implement Server characteristic Configuration support in the attribute
+  server to manage characteristic value broadcasting. There is a single
+  instance of the Server Characteristic Configuration for all clients.
+  See Volume 3, Part G, section 3.3.3.4 for more information.
+
+  Priority: Low
+  Complexity: C1
+
+- Long reads/writes don't work (consisting of multiple request packets)
+
+  Priority: Low
+  Complexity: C2
+
+- Implement Client Characteristic Configuration support in the attribute
+  server to manage indications and notications. This is a per client attribute
+  to control how the client wants to receive reports of changes in a given
+  characteristic value.
+  See Volume 3, Part G, section 3.3.3.3 for more information
+
+  Priority: Low
+  Complexity: C2
+
+- Define attribute server API. External applications needs to register,
+  change attributes and to be notified about changes. Example: Proximity,
+  Time and Alert Profiles. "Local Service hierarchy" in the attribute-api
+  needs to be proposed and a RFC shall be sent to the ML.
+
+  Priority: Low
+  Complexity: C2
+
+- gattrib needs to be extended to handle Attribute Protocol Transactions
+  timeout. See Volume 3, Part F, section 3.3.3 and Part G, section 4.14
+  for more information.
+
+  Priority: Low
+  Complexity: C2
diff --git a/ThirdPartyProject.prop b/ThirdPartyProject.prop
index b6bef4a..8a9f578 100644
--- a/ThirdPartyProject.prop
+++ b/ThirdPartyProject.prop
@@ -1,7 +1,7 @@
 # Copyright 2010 Google Inc. All Rights Reserved.
 #Fri Jul 16 10:03:08 PDT 2010
 currentVersion=Unknown
-version=4.69
+version=4.89
 isNative=true
 feedurl=ChangeLog
 name=bluez-utils
diff --git a/acinclude.m4 b/acinclude.m4
index f5fdd66..91e0956 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -22,6 +22,7 @@
 		CFLAGS+=" -Wmissing-declarations"
 		CFLAGS+=" -Wredundant-decls"
 		CFLAGS+=" -Wcast-align"
+		CFLAGS+=" -DG_DISABLE_DEPRECATED"
 	fi
 ])
 
@@ -107,8 +108,8 @@
 ])
 
 AC_DEFUN([AC_PATH_GLIB], [
-	PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.14, dummy=yes,
-				AC_MSG_ERROR(GLib library version 2.14 or later is required))
+	PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.16, dummy=yes,
+				AC_MSG_ERROR(GLib library version 2.16 or later is required))
 	AC_SUBST(GLIB_CFLAGS)
 	AC_SUBST(GLIB_LIBS)
 ])
@@ -152,13 +153,30 @@
 	AC_SUBST(SNDFILE_LIBS)
 ])
 
+AC_DEFUN([AC_PATH_READLINE], [
+	AC_CHECK_HEADER(readline/readline.h,
+		AC_CHECK_LIB(readline, main,
+			[ readline_found=yes
+			AC_SUBST(READLINE_LIBS, "-lreadline")
+			], readline_found=no),
+		[])
+])
+
+AC_DEFUN([AC_PATH_OUI], [
+	AC_ARG_WITH(ouifile,
+		    AS_HELP_STRING([--with-ouifile=PATH],[Path to the oui.txt file @<:@auto@:>@]),
+		    [ac_with_ouifile=$withval],
+		    [ac_with_ouifile="/var/lib/misc/oui.txt"])
+	AC_DEFINE_UNQUOTED(OUIFILE, ["$ac_with_ouifile"], [Define the OUI file path])
+])
+
 AC_DEFUN([AC_ARG_BLUEZ], [
 	debug_enable=no
 	optimization_enable=yes
 	fortify_enable=yes
 	pie_enable=yes
 	sndfile_enable=${sndfile_found}
-	hal_enable=${hal_found}
+	hal_enable=no
 	usb_enable=${usb_found}
 	alsa_enable=${alsa_found}
 	gstreamer_enable=${gstreamer_found}
@@ -167,7 +185,9 @@
 	serial_enable=yes
 	network_enable=yes
 	service_enable=yes
+	health_enable=no
 	pnat_enable=no
+	attrib_enable=no
 	tracer_enable=no
 	tools_enable=yes
 	hidd_enable=no
@@ -216,10 +236,18 @@
 		service_enable=${enableval}
 	])
 
+	AC_ARG_ENABLE(health, AC_HELP_STRING([--enable-health], [enable health plugin]), [
+		health_enable=${enableval}
+	])
+
 	AC_ARG_ENABLE(pnat, AC_HELP_STRING([--enable-pnat], [enable pnat plugin]), [
 		pnat_enable=${enableval}
 	])
 
+	AC_ARG_ENABLE(attrib, AC_HELP_STRING([--enable-attrib], [enable attrib plugin]), [
+		attrib_enable=${enableval}
+	])
+
 	AC_ARG_ENABLE(gstreamer, AC_HELP_STRING([--enable-gstreamer], [enable GStreamer support]), [
 		gstreamer_enable=${enableval}
 	])
@@ -298,6 +326,10 @@
 		maemo6_enable=${enableval}
 	])
 
+	AC_ARG_ENABLE(hal, AC_HELP_STRING([--enable-hal], [Use HAL to determine adapter class]), [
+		hal_enable=${enableval}
+	])
+
 	if (test "${fortify_enable}" = "yes"); then
 		CFLAGS="$CFLAGS -D_FORTIFY_SOURCE=2"
 	fi
@@ -330,6 +362,11 @@
 	AM_CONDITIONAL(SERIALPLUGIN, test "${serial_enable}" = "yes")
 	AM_CONDITIONAL(NETWORKPLUGIN, test "${network_enable}" = "yes")
 	AM_CONDITIONAL(SERVICEPLUGIN, test "${service_enable}" = "yes")
+	AM_CONDITIONAL(HEALTHPLUGIN, test "${health_enable}" = "yes")
+	AM_CONDITIONAL(MCAP, test "${health_enable}" = "yes")
+	AM_CONDITIONAL(HAL, test "${hal_enable}" = "yes")
+	AM_CONDITIONAL(READLINE, test "${readline_found}" = "yes")
+	AM_CONDITIONAL(ATTRIBPLUGIN, test "${attrib_enable}" = "yes")
 	AM_CONDITIONAL(ECHOPLUGIN, test "no" = "yes")
 	AM_CONDITIONAL(PNATPLUGIN, test "${pnat_enable}" = "yes")
 	AM_CONDITIONAL(TRACER, test "${tracer_enable}" = "yes")
diff --git a/attrib/NOTICE b/attrib/NOTICE
new file mode 100644
index 0000000..e1862f3
--- /dev/null
+++ b/attrib/NOTICE
@@ -0,0 +1,903 @@
+Licensing for libaudio
+ *  Copyright (C) 2004-2008  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2006-2009  Nokia Corporation
+ *  Copyright (C) 2008	Joao Paulo Rechi Vita
+ *  Copyright (C) 2008-2009  Leonid Movshovich <event.riga@gmail.org>
+ *  Copyright (C) 2008-2009  Nokia Corporation
+ *  Copyright (C) 2009	Lennart Poettering
+ *  Copyright (C) 2009       Intel Corporation
+ *  Copyright (C) 2009  Joao Paulo Rechi Vita
+ *  Copyright (C) 2009-2010  Motorola Inc.
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+Licensing for liba2dp
+ *  Copyright (C) 2004-2005  Henryk Ploetz <henryk@ploetzli.ch>
+ *  Copyright (C) 2004-2008  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2005-2006  Brad Midgley <bmidgley@xmission.com>
+ *  Copyright (C) 2005-2008  Brad Midgley <bmidgley@xmission.com>
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+
+--------------------------------------------------------------
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
+--------------------------------------------------------------
+		  GNU LESSER GENERAL PUBLIC LICENSE
+		       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+		  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+			    NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/attrib/att.c b/attrib/att.c
new file mode 100644
index 0000000..3259fca
--- /dev/null
+++ b/attrib/att.c
@@ -0,0 +1,961 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+
+#include "att.h"
+
+const char *att_ecode2str(uint8_t status)
+{
+	switch (status)  {
+	case ATT_ECODE_INVALID_HANDLE:
+		return "Invalid handle";
+	case ATT_ECODE_READ_NOT_PERM:
+		return "Atribute can't be read";
+	case ATT_ECODE_WRITE_NOT_PERM:
+		return "Attribute can't be written";
+	case ATT_ECODE_INVALID_PDU:
+		return "Attribute PDU was invalid";
+	case ATT_ECODE_INSUFF_AUTHEN:
+		return "Attribute requires authentication before read/write";
+	case ATT_ECODE_REQ_NOT_SUPP:
+		return "Server doesn't support the request received";
+	case ATT_ECODE_INVALID_OFFSET:
+		return "Offset past the end of the attribute";
+	case ATT_ECODE_INSUFF_AUTHO:
+		return "Attribute requires authorization before read/write";
+	case ATT_ECODE_PREP_QUEUE_FULL:
+		return "Too many prepare writes have been queued";
+	case ATT_ECODE_ATTR_NOT_FOUND:
+		return "No attribute found within the given range";
+	case ATT_ECODE_ATTR_NOT_LONG:
+		return "Attribute can't be read/written using Read Blob Req";
+	case ATT_ECODE_INSUFF_ENCR_KEY_SIZE:
+		return "Encryption Key Size is insufficient";
+	case ATT_ECODE_INVAL_ATTR_VALUE_LEN:
+		return "Attribute value length is invalid";
+	case ATT_ECODE_UNLIKELY:
+		return "Request attribute has encountered an unlikely error";
+	case ATT_ECODE_INSUFF_ENC:
+		return "Encryption required before read/write";
+	case ATT_ECODE_UNSUPP_GRP_TYPE:
+		return "Attribute type is not a supported grouping attribute";
+	case ATT_ECODE_INSUFF_RESOURCES:
+		return "Insufficient Resources to complete the request";
+	case ATT_ECODE_IO:
+		return "Internal application error: I/O";
+	default:
+		return "Unexpected error code";
+	}
+}
+
+void att_data_list_free(struct att_data_list *list)
+{
+	int i;
+
+	for (i = 0; i < list->num; i++)
+		free(list->data[i]);
+
+	free(list->data);
+	free(list);
+}
+
+uint16_t enc_read_by_grp_req(uint16_t start, uint16_t end, uuid_t *uuid,
+							uint8_t *pdu, int len)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end);
+	uint16_t length;
+
+	if (!uuid)
+		return 0;
+
+	if (uuid->type == SDP_UUID16)
+		length = 2;
+	else if (uuid->type == SDP_UUID128)
+		length = 16;
+	else
+		return 0;
+
+	if (len < min_len + length)
+		return 0;
+
+	pdu[0] = ATT_OP_READ_BY_GROUP_REQ;
+	att_put_u16(start, &pdu[1]);
+	att_put_u16(end, &pdu[3]);
+
+	if (uuid->type == SDP_UUID16)
+		att_put_u16(uuid->value.uuid16, &pdu[5]);
+	else
+		memcpy(&pdu[5], &uuid->value.uuid128, length);
+
+	return min_len + length;
+}
+
+uint16_t dec_read_by_grp_req(const uint8_t *pdu, int len, uint16_t *start,
+						uint16_t *end, uuid_t *uuid)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (start == NULL || end == NULL || uuid == NULL)
+		return 0;
+
+	if (pdu[0] != ATT_OP_READ_BY_GROUP_REQ)
+		return 0;
+
+	if (len < min_len + 2)
+		return 0;
+
+	*start = att_get_u16(&pdu[1]);
+	*end = att_get_u16(&pdu[3]);
+	if (len == min_len + 2)
+		sdp_uuid16_create(uuid, att_get_u16(&pdu[5]));
+	else
+		sdp_uuid128_create(uuid, &pdu[5]);
+
+	return len;
+}
+
+uint16_t enc_read_by_grp_resp(struct att_data_list *list, uint8_t *pdu,
+								int len)
+{
+	int i;
+	uint16_t w;
+	uint8_t *ptr;
+
+	if (list == NULL)
+		return 0;
+
+	if (len < list->len + 2)
+		return 0;
+
+	pdu[0] = ATT_OP_READ_BY_GROUP_RESP;
+	pdu[1] = list->len;
+
+	ptr = &pdu[2];
+
+	for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) {
+		memcpy(ptr, list->data[i], list->len);
+		ptr += list->len;
+		w += list->len;
+	}
+
+	return w;
+}
+
+struct att_data_list *dec_read_by_grp_resp(const uint8_t *pdu, int len)
+{
+	struct att_data_list *list;
+	const uint8_t *ptr;
+	int i;
+
+	if (pdu[0] != ATT_OP_READ_BY_GROUP_RESP)
+		return NULL;
+
+	list = malloc(sizeof(struct att_data_list));
+	list->len = pdu[1];
+	list->num = (len - 2) / list->len;
+
+	list->data = malloc(sizeof(uint8_t *) * list->num);
+	ptr = &pdu[2];
+
+	for (i = 0; i < list->num; i++) {
+		list->data[i] = malloc(sizeof(uint8_t) * list->len);
+		memcpy(list->data[i], ptr, list->len);
+		ptr += list->len;
+	}
+
+	return list;
+}
+
+uint16_t enc_find_by_type_req(uint16_t start, uint16_t end, uuid_t *uuid,
+			const uint8_t *value, int vlen, uint8_t *pdu, int len)
+{
+	uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end) +
+							sizeof(uint16_t);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (!uuid)
+		return 0;
+
+	if (uuid->type != SDP_UUID16)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	if (vlen > len - min_len)
+		vlen = len - min_len;
+
+	pdu[0] = ATT_OP_FIND_BY_TYPE_REQ;
+	att_put_u16(start, &pdu[1]);
+	att_put_u16(end, &pdu[3]);
+	att_put_u16(uuid->value.uuid16, &pdu[5]);
+
+	if (vlen > 0) {
+		memcpy(&pdu[7], value, vlen);
+		return min_len + vlen;
+	}
+
+	return min_len;
+}
+
+uint16_t dec_find_by_type_req(const uint8_t *pdu, int len, uint16_t *start,
+			uint16_t *end, uuid_t *uuid, uint8_t *value, int *vlen)
+{
+	int valuelen;
+	uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) +
+						sizeof(*end) + sizeof(uint16_t);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	if (pdu[0] != ATT_OP_FIND_BY_TYPE_REQ)
+		return 0;
+
+	/* First requested handle number */
+	if (start)
+		*start = att_get_u16(&pdu[1]);
+
+	/* Last requested handle number */
+	if (end)
+		*end = att_get_u16(&pdu[3]);
+
+	/* Always UUID16 */
+	if (uuid)
+		sdp_uuid16_create(uuid, att_get_u16(&pdu[5]));
+
+	valuelen = len - min_len;
+
+	/* Attribute value to find */
+	if (valuelen > 0 && value)
+		memcpy(value, pdu + min_len, valuelen);
+
+	if (vlen)
+		*vlen = valuelen;
+
+	return len;
+}
+
+uint16_t enc_find_by_type_resp(GSList *matches, uint8_t *pdu, int len)
+{
+	GSList *l;
+	uint16_t offset;
+
+	if (pdu == NULL || len < 5)
+		return 0;
+
+	pdu[0] = ATT_OP_FIND_BY_TYPE_RESP;
+
+	for (l = matches, offset = 1; l && len >= (offset + 4);
+					l = l->next, offset += 4) {
+		struct att_range *range = l->data;
+
+		att_put_u16(range->start, &pdu[offset]);
+		att_put_u16(range->end, &pdu[offset + 2]);
+	}
+
+	return offset;
+}
+
+GSList *dec_find_by_type_resp(const uint8_t *pdu, int len)
+{
+	struct att_range *range;
+	GSList *matches;
+	int offset;
+
+	if (pdu == NULL || len < 5)
+		return NULL;
+
+	if (pdu[0] != ATT_OP_FIND_BY_TYPE_RESP)
+		return NULL;
+
+	for (offset = 1, matches = NULL; len >= (offset + 4); offset += 4) {
+		range = malloc(sizeof(struct att_range));
+		range->start = att_get_u16(&pdu[offset]);
+		range->end = att_get_u16(&pdu[offset + 2]);
+
+		matches = g_slist_append(matches, range);
+	}
+
+	return matches;
+}
+
+uint16_t enc_read_by_type_req(uint16_t start, uint16_t end, uuid_t *uuid,
+							uint8_t *pdu, int len)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end);
+	uint16_t length;
+
+	if (!uuid)
+		return 0;
+
+	if (uuid->type == SDP_UUID16)
+		length = 2;
+	else if (uuid->type == SDP_UUID128)
+		length = 16;
+	else
+		return 0;
+
+	if (len < min_len + length)
+		return 0;
+
+	pdu[0] = ATT_OP_READ_BY_TYPE_REQ;
+	att_put_u16(start, &pdu[1]);
+	att_put_u16(end, &pdu[3]);
+
+	if (uuid->type == SDP_UUID16)
+		att_put_u16(uuid->value.uuid16, &pdu[5]);
+	else
+		memcpy(&pdu[5], &uuid->value.uuid128, length);
+
+	return min_len + length;
+}
+
+uint16_t dec_read_by_type_req(const uint8_t *pdu, int len, uint16_t *start,
+						uint16_t *end, uuid_t *uuid)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (start == NULL || end == NULL || uuid == NULL)
+		return 0;
+
+	if (len < min_len + 2)
+		return 0;
+
+	if (pdu[0] != ATT_OP_READ_BY_TYPE_REQ)
+		return 0;
+
+	*start = att_get_u16(&pdu[1]);
+	*end = att_get_u16(&pdu[3]);
+
+	if (len == min_len + 2)
+		sdp_uuid16_create(uuid, att_get_u16(&pdu[5]));
+	else
+		sdp_uuid128_create(uuid, &pdu[5]);
+
+	return len;
+}
+
+uint16_t enc_read_by_type_resp(struct att_data_list *list, uint8_t *pdu, int len)
+{
+	uint8_t *ptr;
+	int i, w, l;
+
+	if (list == NULL)
+		return 0;
+
+	if (pdu == NULL)
+		return 0;
+
+	l = MIN(len - 2, list->len);
+
+	pdu[0] = ATT_OP_READ_BY_TYPE_RESP;
+	pdu[1] = l;
+	ptr = &pdu[2];
+
+	for (i = 0, w = 2; i < list->num && w + l <= len; i++) {
+		memcpy(ptr, list->data[i], l);
+		ptr += l;
+		w += l;
+	}
+
+	return w;
+}
+
+struct att_data_list *dec_read_by_type_resp(const uint8_t *pdu, int len)
+{
+	struct att_data_list *list;
+	const uint8_t *ptr;
+	int i;
+
+	if (pdu[0] != ATT_OP_READ_BY_TYPE_RESP)
+		return NULL;
+
+	list = malloc(sizeof(struct att_data_list));
+	list->len = pdu[1];
+	list->num = (len - 2) / list->len;
+
+	list->data = malloc(sizeof(uint8_t *) * list->num);
+	ptr = &pdu[2];
+
+	for (i = 0; i < list->num; i++) {
+		list->data[i] = malloc(sizeof(uint8_t) * list->len);
+		memcpy(list->data[i], ptr, list->len);
+		ptr += list->len;
+	}
+
+	return list;
+}
+
+uint16_t enc_write_cmd(uint16_t handle, const uint8_t *value, int vlen,
+							uint8_t *pdu, int len)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	if (vlen > len - min_len)
+		vlen = len - min_len;
+
+	pdu[0] = ATT_OP_WRITE_CMD;
+	att_put_u16(handle, &pdu[1]);
+
+	if (vlen > 0) {
+		memcpy(&pdu[3], value, vlen);
+		return min_len + vlen;
+	}
+
+	return min_len;
+}
+
+uint16_t dec_write_cmd(const uint8_t *pdu, int len, uint16_t *handle,
+						uint8_t *value, int *vlen)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (value == NULL || vlen == NULL || handle == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	if (pdu[0] != ATT_OP_WRITE_CMD)
+		return 0;
+
+	*handle = att_get_u16(&pdu[1]);
+	memcpy(value, pdu + min_len, len - min_len);
+	*vlen = len - min_len;
+
+	return len;
+}
+
+uint16_t enc_write_req(uint16_t handle, const uint8_t *value, int vlen,
+							uint8_t *pdu, int len)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	if (vlen > len - min_len)
+		vlen = len - min_len;
+
+	pdu[0] = ATT_OP_WRITE_REQ;
+	att_put_u16(handle, &pdu[1]);
+
+	if (vlen > 0) {
+		memcpy(&pdu[3], value, vlen);
+		return min_len + vlen;
+	}
+
+	return min_len;
+}
+
+uint16_t dec_write_req(const uint8_t *pdu, int len, uint16_t *handle,
+						uint8_t *value, int *vlen)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (value == NULL || vlen == NULL || handle == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	if (pdu[0] != ATT_OP_WRITE_REQ)
+		return 0;
+
+	*handle = att_get_u16(&pdu[1]);
+	*vlen = len - min_len;
+	if (*vlen > 0)
+		memcpy(value, pdu + min_len, *vlen);
+
+	return len;
+}
+
+uint16_t enc_write_resp(uint8_t *pdu, int len)
+{
+	if (pdu == NULL)
+		return 0;
+
+	pdu[0] = ATT_OP_WRITE_RESP;
+
+	return sizeof(pdu[0]);
+}
+
+uint16_t dec_write_resp(const uint8_t *pdu, int len)
+{
+	if (pdu == NULL)
+		return 0;
+
+	if (pdu[0] != ATT_OP_WRITE_RESP)
+		return 0;
+
+	return len;
+}
+
+uint16_t enc_read_req(uint16_t handle, uint8_t *pdu, int len)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	pdu[0] = ATT_OP_READ_REQ;
+	att_put_u16(handle, &pdu[1]);
+
+	return min_len;
+}
+
+uint16_t enc_read_blob_req(uint16_t handle, uint16_t offset, uint8_t *pdu,
+									int len)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle) +
+							sizeof(offset);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	pdu[0] = ATT_OP_READ_BLOB_REQ;
+	att_put_u16(handle, &pdu[1]);
+	att_put_u16(offset, &pdu[3]);
+
+	return min_len;
+}
+
+uint16_t dec_read_req(const uint8_t *pdu, int len, uint16_t *handle)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (handle == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	if (pdu[0] != ATT_OP_READ_REQ)
+		return 0;
+
+	*handle = att_get_u16(&pdu[1]);
+
+	return min_len;
+}
+
+uint16_t dec_read_blob_req(const uint8_t *pdu, int len, uint16_t *handle,
+							uint16_t *offset)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) +
+							sizeof(*offset);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (handle == NULL)
+		return 0;
+
+	if (offset == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	if (pdu[0] != ATT_OP_READ_BLOB_REQ)
+		return 0;
+
+	*handle = att_get_u16(&pdu[1]);
+	*offset = att_get_u16(&pdu[3]);
+
+	return min_len;
+}
+
+uint16_t enc_read_resp(uint8_t *value, int vlen, uint8_t *pdu, int len)
+{
+	if (pdu == NULL)
+		return 0;
+
+	/* If the attribute value length is longer than the allowed PDU size,
+	 * send only the octets that fit on the PDU. The remaining octets can
+	 * be requested using the Read Blob Request. */
+	if (vlen > len - 1)
+		vlen = len - 1;
+
+	pdu[0] = ATT_OP_READ_RESP;
+
+	memcpy(pdu + 1, value, vlen);
+
+	return vlen + 1;
+}
+
+uint16_t enc_read_blob_resp(uint8_t *value, int vlen, uint16_t offset,
+							uint8_t *pdu, int len)
+{
+	if (pdu == NULL)
+		return 0;
+
+	vlen -= offset;
+	if (vlen > len - 1)
+		vlen = len - 1;
+
+	pdu[0] = ATT_OP_READ_BLOB_RESP;
+
+	memcpy(pdu + 1, &value[offset], vlen);
+
+	return vlen + 1;
+}
+
+uint16_t dec_read_resp(const uint8_t *pdu, int len, uint8_t *value, int *vlen)
+{
+	if (pdu == NULL)
+		return 0;
+
+	if (value == NULL || vlen == NULL)
+		return 0;
+
+	if (pdu[0] != ATT_OP_READ_RESP)
+		return 0;
+
+	memcpy(value, pdu + 1, len - 1);
+
+	*vlen = len - 1;
+
+	return len;
+}
+
+uint16_t enc_error_resp(uint8_t opcode, uint16_t handle, uint8_t status,
+							uint8_t *pdu, int len)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(opcode) +
+						sizeof(handle) + sizeof(status);
+	uint16_t u16;
+
+	if (len < min_len)
+		return 0;
+
+	u16 = htobs(handle);
+	pdu[0] = ATT_OP_ERROR;
+	pdu[1] = opcode;
+	memcpy(&pdu[2], &u16, sizeof(u16));
+	pdu[4] = status;
+
+	return min_len;
+}
+
+uint16_t enc_find_info_req(uint16_t start, uint16_t end, uint8_t *pdu, int len)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	pdu[0] = ATT_OP_FIND_INFO_REQ;
+	att_put_u16(start, &pdu[1]);
+	att_put_u16(end, &pdu[3]);
+
+	return min_len;
+}
+
+uint16_t dec_find_info_req(const uint8_t *pdu, int len, uint16_t *start,
+								uint16_t *end)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	if (start == NULL || end == NULL)
+		return 0;
+
+	if (pdu[0] != ATT_OP_FIND_INFO_REQ)
+		return 0;
+
+	*start = att_get_u16(&pdu[1]);
+	*end = att_get_u16(&pdu[3]);
+
+	return min_len;
+}
+
+uint16_t enc_find_info_resp(uint8_t format, struct att_data_list *list,
+							uint8_t *pdu, int len)
+{
+	uint8_t *ptr;
+	int i, w;
+
+	if (pdu == NULL)
+		return 0;
+
+	if (list == NULL)
+		return 0;
+
+	if (len < list->len + 2)
+		return 0;
+
+	pdu[0] = ATT_OP_FIND_INFO_RESP;
+	pdu[1] = format;
+	ptr = (void *) &pdu[2];
+
+	for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) {
+		memcpy(ptr, list->data[i], list->len);
+		ptr += list->len;
+		w += list->len;
+	}
+
+	return w;
+}
+
+struct att_data_list *dec_find_info_resp(const uint8_t *pdu, int len,
+							uint8_t *format)
+{
+	struct att_data_list *list;
+	uint8_t *ptr;
+	int i;
+
+	if (pdu == NULL)
+		return 0;
+
+	if (format == NULL)
+		return 0;
+
+	if (pdu[0] != ATT_OP_FIND_INFO_RESP)
+		return 0;
+
+	*format = pdu[1];
+
+	list = malloc(sizeof(struct att_data_list));
+
+	list->len = sizeof(pdu[0]) + sizeof(*format);
+	if (*format == 0x01)
+		list->len += 2;
+	else if (*format == 0x02)
+		list->len += 16;
+
+	list->num = (len - 2) / list->len;
+	list->data = malloc(sizeof(uint8_t *) * list->num);
+
+	ptr = (void *) &pdu[2];
+
+	for (i = 0; i < list->num; i++) {
+		list->data[i] = malloc(list->len);
+		memcpy(list->data[i], ptr, list->len);
+		ptr += list->len;
+	}
+
+	return list;
+}
+
+uint16_t enc_notification(struct attribute *a, uint8_t *pdu, int len)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (len < (a->len + min_len))
+		return 0;
+
+	pdu[0] = ATT_OP_HANDLE_NOTIFY;
+	att_put_u16(a->handle, &pdu[1]);
+	memcpy(&pdu[3], a->data, a->len);
+
+	return a->len + min_len;
+}
+
+uint16_t enc_indication(struct attribute *a, uint8_t *pdu, int len)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (len < (a->len + min_len))
+		return 0;
+
+	pdu[0] = ATT_OP_HANDLE_IND;
+	att_put_u16(a->handle, &pdu[1]);
+	memcpy(&pdu[3], a->data, a->len);
+
+	return a->len + min_len;
+}
+
+struct attribute *dec_indication(const uint8_t *pdu, int len)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t);
+
+	struct attribute *a;
+
+	if (pdu == NULL)
+		return NULL;
+
+	if (pdu[0] != ATT_OP_HANDLE_IND)
+		return NULL;
+
+	if (len < min_len)
+		return NULL;
+
+	a = malloc(sizeof(struct attribute) + len - min_len);
+	if (a == NULL)
+		return NULL;
+
+	a->len = len - min_len;
+
+	a->handle = att_get_u16(&pdu[1]);
+	memcpy(a->data, &pdu[3], a->len);
+
+	return a;
+}
+
+uint16_t enc_confirmation(uint8_t *pdu, int len)
+{
+	const uint16_t min_len = sizeof(pdu[0]);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	pdu[0] = ATT_OP_HANDLE_CNF;
+
+	return min_len;
+}
+
+uint16_t enc_mtu_req(uint16_t mtu, uint8_t *pdu, int len)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(mtu);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	pdu[0] = ATT_OP_MTU_REQ;
+	att_put_u16(mtu, &pdu[1]);
+
+	return min_len;
+}
+
+uint16_t dec_mtu_req(const uint8_t *pdu, int len, uint16_t *mtu)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (mtu == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	if (pdu[0] != ATT_OP_MTU_REQ)
+		return 0;
+
+	*mtu = att_get_u16(&pdu[1]);
+
+	return min_len;
+}
+
+uint16_t enc_mtu_resp(uint16_t mtu, uint8_t *pdu, int len)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(mtu);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	pdu[0] = ATT_OP_MTU_RESP;
+	att_put_u16(mtu, &pdu[1]);
+
+	return min_len;
+}
+
+uint16_t dec_mtu_resp(const uint8_t *pdu, int len, uint16_t *mtu)
+{
+	const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu);
+
+	if (pdu == NULL)
+		return 0;
+
+	if (mtu == NULL)
+		return 0;
+
+	if (len < min_len)
+		return 0;
+
+	if (pdu[0] != ATT_OP_MTU_RESP)
+		return 0;
+
+	*mtu = att_get_u16(&pdu[1]);
+
+	return min_len;
+}
diff --git a/attrib/att.h b/attrib/att.h
new file mode 100644
index 0000000..7d9afeb
--- /dev/null
+++ b/attrib/att.h
@@ -0,0 +1,247 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+/* GATT Profile Attribute types */
+#define GATT_PRIM_SVC_UUID		0x2800
+#define GATT_SND_SVC_UUID		0x2801
+#define GATT_INCLUDE_UUID		0x2802
+#define GATT_CHARAC_UUID		0x2803
+
+/* GATT Characteristic Types */
+#define GATT_CHARAC_DEVICE_NAME			0x2A00
+#define GATT_CHARAC_APPEARANCE			0x2A01
+#define GATT_CHARAC_PERIPHERAL_PRIV_FLAG	0x2A02
+#define GATT_CHARAC_RECONNECTION_ADDRESS	0x2A03
+#define GATT_CHARAC_PERIPHERAL_PREF_CONN	0x2A04
+#define GATT_CHARAC_SERVICE_CHANGED		0x2A05
+
+/* GATT Characteristic Descriptors */
+#define GATT_CHARAC_EXT_PROPER_UUID	0x2900
+#define GATT_CHARAC_USER_DESC_UUID	0x2901
+#define GATT_CLIENT_CHARAC_CFG_UUID	0x2902
+#define GATT_SERVER_CHARAC_CFG_UUID	0x2903
+#define GATT_CHARAC_FMT_UUID		0x2904
+#define GATT_CHARAC_AGREG_FMT_UUID	0x2905
+
+/* Attribute Protocol Opcodes */
+#define ATT_OP_ERROR			0x01
+#define ATT_OP_MTU_REQ			0x02
+#define ATT_OP_MTU_RESP			0x03
+#define ATT_OP_FIND_INFO_REQ		0x04
+#define ATT_OP_FIND_INFO_RESP		0x05
+#define ATT_OP_FIND_BY_TYPE_REQ		0x06
+#define ATT_OP_FIND_BY_TYPE_RESP	0x07
+#define ATT_OP_READ_BY_TYPE_REQ		0x08
+#define ATT_OP_READ_BY_TYPE_RESP	0x09
+#define ATT_OP_READ_REQ			0x0A
+#define ATT_OP_READ_RESP		0x0B
+#define ATT_OP_READ_BLOB_REQ		0x0C
+#define ATT_OP_READ_BLOB_RESP		0x0D
+#define ATT_OP_READ_MULTI_REQ		0x0E
+#define ATT_OP_READ_MULTI_RESP		0x0F
+#define ATT_OP_READ_BY_GROUP_REQ	0x10
+#define ATT_OP_READ_BY_GROUP_RESP	0x11
+#define ATT_OP_WRITE_REQ		0x12
+#define ATT_OP_WRITE_RESP		0x13
+#define ATT_OP_WRITE_CMD		0x52
+#define ATT_OP_PREP_WRITE_REQ		0x16
+#define ATT_OP_PREP_WRITE_RESP		0x17
+#define ATT_OP_EXEC_WRITE_REQ		0x18
+#define ATT_OP_EXEC_WRITE_RESP		0x19
+#define ATT_OP_HANDLE_NOTIFY		0x1B
+#define ATT_OP_HANDLE_IND		0x1D
+#define ATT_OP_HANDLE_CNF		0x1E
+#define ATT_OP_SIGNED_WRITE_CMD		0xD2
+
+/* Error codes for Error response PDU */
+#define ATT_ECODE_INVALID_HANDLE		0x01
+#define ATT_ECODE_READ_NOT_PERM			0x02
+#define ATT_ECODE_WRITE_NOT_PERM		0x03
+#define ATT_ECODE_INVALID_PDU			0x04
+#define ATT_ECODE_INSUFF_AUTHEN			0x05
+#define ATT_ECODE_REQ_NOT_SUPP			0x06
+#define ATT_ECODE_INVALID_OFFSET		0x07
+#define ATT_ECODE_INSUFF_AUTHO			0x08
+#define ATT_ECODE_PREP_QUEUE_FULL		0x09
+#define ATT_ECODE_ATTR_NOT_FOUND		0x0A
+#define ATT_ECODE_ATTR_NOT_LONG			0x0B
+#define ATT_ECODE_INSUFF_ENCR_KEY_SIZE		0x0C
+#define ATT_ECODE_INVAL_ATTR_VALUE_LEN		0x0D
+#define ATT_ECODE_UNLIKELY			0x0E
+#define ATT_ECODE_INSUFF_ENC			0x0F
+#define ATT_ECODE_UNSUPP_GRP_TYPE		0x10
+#define ATT_ECODE_INSUFF_RESOURCES		0x11
+/* Application error */
+#define ATT_ECODE_IO				0xFF
+
+/* Characteristic Property bit field */
+#define ATT_CHAR_PROPER_BROADCAST		0x01
+#define ATT_CHAR_PROPER_READ			0x02
+#define ATT_CHAR_PROPER_WRITE_WITHOUT_RESP	0x04
+#define ATT_CHAR_PROPER_WRITE			0x08
+#define ATT_CHAR_PROPER_NOTIFY			0x10
+#define ATT_CHAR_PROPER_INDICATE		0x20
+#define ATT_CHAR_PROPER_AUTH			0x40
+#define ATT_CHAR_PROPER_EXT_PROPER		0x80
+
+
+#define ATT_MAX_MTU				256
+#define ATT_DEFAULT_L2CAP_MTU			48
+#define ATT_DEFAULT_LE_MTU			23
+
+/* Requirements for read/write operations */
+enum {
+	ATT_NONE,		/* No restrictions */
+	ATT_AUTHENTICATION,	/* Authentication required */
+	ATT_AUTHORIZATION,	/* Authorization required */
+	ATT_NOT_PERMITTED,	/* Operation not permitted */
+};
+
+struct attribute {
+	uint16_t handle;
+	uuid_t uuid;
+	int read_reqs;
+	int write_reqs;
+	int len;
+	uint8_t data[0];
+};
+
+struct att_data_list {
+	uint16_t num;
+	uint16_t len;
+	uint8_t **data;
+};
+
+struct att_range {
+	uint16_t start;
+	uint16_t end;
+};
+
+struct att_primary {
+	char uuid[MAX_LEN_UUID_STR + 1];
+	uint16_t start;
+	uint16_t end;
+};
+
+struct att_char {
+	char uuid[MAX_LEN_UUID_STR + 1];
+	uint16_t handle;
+	uint8_t properties;
+	uint16_t value_handle;
+};
+
+/* These functions do byte conversion */
+static inline uint8_t att_get_u8(const void *ptr)
+{
+	const uint8_t *u8_ptr = ptr;
+	return bt_get_unaligned(u8_ptr);
+}
+
+static inline uint16_t att_get_u16(const void *ptr)
+{
+	const uint16_t *u16_ptr = ptr;
+	return btohs(bt_get_unaligned(u16_ptr));
+}
+
+static inline uint32_t att_get_u32(const void *ptr)
+{
+	const uint32_t *u32_ptr = ptr;
+	return btohl(bt_get_unaligned(u32_ptr));
+}
+
+static inline void att_put_u8(uint8_t src, void *dst)
+{
+	bt_put_unaligned(src, (uint8_t *) dst);
+}
+
+static inline void att_put_u16(uint16_t src, void *dst)
+{
+	bt_put_unaligned(htobs(src), (uint16_t *) dst);
+}
+
+static inline void att_put_u32(uint32_t src, void *dst)
+{
+	bt_put_unaligned(htobl(src), (uint32_t *) dst);
+}
+
+void att_data_list_free(struct att_data_list *list);
+
+const char *att_ecode2str(uint8_t status);
+uint16_t enc_read_by_grp_req(uint16_t start, uint16_t end, uuid_t *uuid,
+							uint8_t *pdu, int len);
+uint16_t dec_read_by_grp_req(const uint8_t *pdu, int len, uint16_t *start,
+						uint16_t *end, uuid_t *uuid);
+uint16_t enc_read_by_grp_resp(struct att_data_list *list, uint8_t *pdu, int len);
+uint16_t enc_find_by_type_req(uint16_t start, uint16_t end, uuid_t *uuid,
+			const uint8_t *value, int vlen, uint8_t *pdu, int len);
+uint16_t dec_find_by_type_req(const uint8_t *pdu, int len, uint16_t *start,
+		uint16_t *end, uuid_t *uuid, uint8_t *value, int *vlen);
+uint16_t enc_find_by_type_resp(GSList *ranges, uint8_t *pdu, int len);
+GSList *dec_find_by_type_resp(const uint8_t *pdu, int len);
+struct att_data_list *dec_read_by_grp_resp(const uint8_t *pdu, int len);
+uint16_t enc_read_by_type_req(uint16_t start, uint16_t end, uuid_t *uuid,
+							uint8_t *pdu, int len);
+uint16_t dec_read_by_type_req(const uint8_t *pdu, int len, uint16_t *start,
+						uint16_t *end, uuid_t *uuid);
+uint16_t enc_read_by_type_resp(struct att_data_list *list, uint8_t *pdu,
+								int len);
+uint16_t enc_write_cmd(uint16_t handle, const uint8_t *value, int vlen,
+							uint8_t *pdu, int len);
+uint16_t dec_write_cmd(const uint8_t *pdu, int len, uint16_t *handle,
+						uint8_t *value, int *vlen);
+struct att_data_list *dec_read_by_type_resp(const uint8_t *pdu, int len);
+uint16_t enc_write_req(uint16_t handle, const uint8_t *value, int vlen,
+							uint8_t *pdu, int len);
+uint16_t dec_write_req(const uint8_t *pdu, int len, uint16_t *handle,
+						uint8_t *value, int *vlen);
+uint16_t enc_write_resp(uint8_t *pdu, int len);
+uint16_t dec_write_resp(const uint8_t *pdu, int len);
+uint16_t enc_read_req(uint16_t handle, uint8_t *pdu, int len);
+uint16_t enc_read_blob_req(uint16_t handle, uint16_t offset, uint8_t *pdu,
+								int len);
+uint16_t dec_read_req(const uint8_t *pdu, int len, uint16_t *handle);
+uint16_t dec_read_blob_req(const uint8_t *pdu, int len, uint16_t *handle,
+							uint16_t *offset);
+uint16_t enc_read_resp(uint8_t *value, int vlen, uint8_t *pdu, int len);
+uint16_t enc_read_blob_resp(uint8_t *value, int vlen, uint16_t offset,
+							uint8_t *pdu, int len);
+uint16_t dec_read_resp(const uint8_t *pdu, int len, uint8_t *value, int *vlen);
+uint16_t enc_error_resp(uint8_t opcode, uint16_t handle, uint8_t status,
+							uint8_t *pdu, int len);
+uint16_t enc_find_info_req(uint16_t start, uint16_t end, uint8_t *pdu, int len);
+uint16_t dec_find_info_req(const uint8_t *pdu, int len, uint16_t *start,
+								uint16_t *end);
+uint16_t enc_find_info_resp(uint8_t format, struct att_data_list *list,
+							uint8_t *pdu, int len);
+struct att_data_list *dec_find_info_resp(const uint8_t *pdu, int len,
+							uint8_t *format);
+uint16_t enc_notification(struct attribute *a, uint8_t *pdu, int len);
+uint16_t enc_indication(struct attribute *a, uint8_t *pdu, int len);
+struct attribute *dec_indication(const uint8_t *pdu, int len);
+uint16_t enc_confirmation(uint8_t *pdu, int len);
+
+uint16_t enc_mtu_req(uint16_t mtu, uint8_t *pdu, int len);
+uint16_t dec_mtu_req(const uint8_t *pdu, int len, uint16_t *mtu);
+uint16_t enc_mtu_resp(uint16_t mtu, uint8_t *pdu, int len);
+uint16_t dec_mtu_resp(const uint8_t *pdu, int len, uint16_t *mtu);
diff --git a/attrib/client.c b/attrib/client.c
new file mode 100644
index 0000000..0f9ba3e
--- /dev/null
+++ b/attrib/client.c
@@ -0,0 +1,1081 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "adapter.h"
+#include "device.h"
+#include "log.h"
+#include "gdbus.h"
+#include "error.h"
+#include "glib-helper.h"
+#include "dbus-common.h"
+#include "btio.h"
+#include "storage.h"
+
+#include "att.h"
+#include "gattrib.h"
+#include "gatt.h"
+#include "client.h"
+
+#define CHAR_INTERFACE "org.bluez.Characteristic"
+
+struct gatt_service {
+	struct btd_device *dev;
+	bdaddr_t sba;
+	bdaddr_t dba;
+	char *path;
+	GSList *primary;
+	GAttrib *attrib;
+	int psm;
+	gboolean listen;
+};
+
+struct format {
+	guint8 format;
+	guint8 exponent;
+	guint16 unit;
+	guint8 namespace;
+	guint16 desc;
+} __attribute__ ((packed));
+
+struct primary {
+	struct gatt_service *gatt;
+	struct att_primary *att;
+	char *path;
+	GSList *chars;
+	GSList *watchers;
+};
+
+struct characteristic {
+	struct primary *prim;
+	char *path;
+	uint16_t handle;
+	uint16_t end;
+	uint8_t perm;
+	char type[MAX_LEN_UUID_STR + 1];
+	char *name;
+	char *desc;
+	struct format *format;
+	uint8_t *value;
+	size_t vlen;
+};
+
+struct query_data {
+	struct primary *prim;
+	struct characteristic *chr;
+	uint16_t handle;
+};
+
+struct watcher {
+	guint id;
+	char *name;
+	char *path;
+	struct primary *prim;
+};
+
+static GSList *gatt_services = NULL;
+
+static DBusConnection *connection;
+
+static void characteristic_free(void *user_data)
+{
+	struct characteristic *chr = user_data;
+
+	g_free(chr->path);
+	g_free(chr->desc);
+	g_free(chr->format);
+	g_free(chr->value);
+	g_free(chr->name);
+	g_free(chr);
+}
+
+static void watcher_free(void *user_data)
+{
+	struct watcher *watcher = user_data;
+
+	g_free(watcher->path);
+	g_free(watcher->name);
+	g_free(watcher);
+}
+
+static void primary_free(void *user_data)
+{
+	struct primary *prim = user_data;
+	GSList *l;
+
+	for (l = prim->watchers; l; l = l->next) {
+		struct watcher *watcher = l->data;
+		g_dbus_remove_watch(connection, watcher->id);
+	}
+
+	g_slist_foreach(prim->chars, (GFunc) characteristic_free, NULL);
+	g_slist_free(prim->chars);
+	g_free(prim->path);
+	g_free(prim);
+}
+
+static void gatt_service_free(void *user_data)
+{
+	struct gatt_service *gatt = user_data;
+
+	g_slist_foreach(gatt->primary, (GFunc) primary_free, NULL);
+	g_slist_free(gatt->primary);
+	g_attrib_unref(gatt->attrib);
+	g_free(gatt->path);
+	btd_device_unref(gatt->dev);
+	g_free(gatt);
+}
+
+static int gatt_dev_cmp(gconstpointer a, gconstpointer b)
+{
+	const struct gatt_service *gatt = a;
+	const struct btd_device *dev = b;
+
+	return gatt->dev != dev;
+}
+
+static int characteristic_handle_cmp(gconstpointer a, gconstpointer b)
+{
+	const struct characteristic *chr = a;
+	uint16_t handle = GPOINTER_TO_UINT(b);
+
+	return chr->handle - handle;
+}
+
+static int watcher_cmp(gconstpointer a, gconstpointer b)
+{
+	const struct watcher *watcher = a;
+	const struct watcher *match = b;
+	int ret;
+
+	ret = g_strcmp0(watcher->name, match->name);
+	if (ret != 0)
+		return ret;
+
+	return g_strcmp0(watcher->path, match->path);
+}
+
+static void append_char_dict(DBusMessageIter *iter, struct characteristic *chr)
+{
+	DBusMessageIter dict;
+	const char *name = "";
+	char *uuid;
+
+	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+	uuid = g_strdup(chr->type);
+	dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid);
+	g_free(uuid);
+
+	/* FIXME: Translate UUID to name. */
+	dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &name);
+
+	if (chr->desc)
+		dict_append_entry(&dict, "Description", DBUS_TYPE_STRING,
+								&chr->desc);
+
+	if (chr->value)
+		dict_append_array(&dict, "Value", DBUS_TYPE_BYTE, &chr->value,
+								chr->vlen);
+
+	/* FIXME: Missing Format, Value and Representation */
+
+	dbus_message_iter_close_container(iter, &dict);
+}
+
+static void watcher_exit(DBusConnection *conn, void *user_data)
+{
+	struct watcher *watcher = user_data;
+	struct primary *prim = watcher->prim;
+	struct gatt_service *gatt = prim->gatt;
+
+	DBG("%s watcher %s exited", prim->path, watcher->name);
+
+	prim->watchers = g_slist_remove(prim->watchers, watcher);
+
+	g_attrib_unref(gatt->attrib);
+}
+
+static int characteristic_set_value(struct characteristic *chr,
+					const uint8_t *value, size_t vlen)
+{
+	chr->value = g_try_realloc(chr->value, vlen);
+	if (chr->value == NULL)
+		return -ENOMEM;
+
+	memcpy(chr->value, value, vlen);
+	chr->vlen = vlen;
+
+	return 0;
+}
+
+static void update_watchers(gpointer data, gpointer user_data)
+{
+	struct watcher *w = data;
+	struct characteristic *chr = user_data;
+	DBusMessage *msg;
+
+	msg = dbus_message_new_method_call(w->name, w->path,
+				"org.bluez.Watcher", "ValueChanged");
+	if (msg == NULL)
+		return;
+
+	dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &chr->path,
+			DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+			&chr->value, chr->vlen, DBUS_TYPE_INVALID);
+
+	dbus_message_set_no_reply(msg, TRUE);
+	g_dbus_send_message(connection, msg);
+}
+
+static void events_handler(const uint8_t *pdu, uint16_t len,
+							gpointer user_data)
+{
+	struct gatt_service *gatt = user_data;
+	struct characteristic *chr;
+	struct primary *prim;
+	GSList *lprim, *lchr;
+	uint8_t opdu[ATT_MAX_MTU];
+	guint handle = att_get_u16(&pdu[1]);
+	uint16_t olen;
+
+	for (lprim = gatt->primary, prim = NULL, chr = NULL; lprim;
+						lprim = lprim->next) {
+		prim = lprim->data;
+
+		lchr = g_slist_find_custom(prim->chars,
+			GUINT_TO_POINTER(handle), characteristic_handle_cmp);
+		if (lchr) {
+			chr = lchr->data;
+			break;
+		}
+	}
+
+	if (chr == NULL) {
+		DBG("Attribute handle 0x%02x not found", handle);
+		return;
+	}
+
+	switch (pdu[0]) {
+	case ATT_OP_HANDLE_IND:
+		olen = enc_confirmation(opdu, sizeof(opdu));
+		g_attrib_send(gatt->attrib, 0, opdu[0], opdu, olen,
+						NULL, NULL, NULL);
+	case ATT_OP_HANDLE_NOTIFY:
+		if (characteristic_set_value(chr, &pdu[3], len - 3) < 0)
+			DBG("Can't change Characteristic %0x02x", handle);
+
+		g_slist_foreach(prim->watchers, update_watchers, chr);
+		break;
+	}
+}
+
+static void attrib_destroy(gpointer user_data)
+{
+	struct gatt_service *gatt = user_data;
+
+	gatt->attrib = NULL;
+}
+
+static void attrib_disconnect(gpointer user_data)
+{
+	struct gatt_service *gatt = user_data;
+
+	/* Remote initiated disconnection only */
+	g_attrib_unref(gatt->attrib);
+}
+
+static void connect_cb(GIOChannel *chan, GError *gerr, gpointer user_data)
+{
+	struct gatt_service *gatt = user_data;
+
+	if (gerr) {
+		error("%s", gerr->message);
+		goto fail;
+	}
+
+	if (gatt->attrib == NULL)
+		return;
+
+	/* Listen mode: used for notification and indication */
+	if (gatt->listen == TRUE) {
+		g_attrib_register(gatt->attrib,
+					ATT_OP_HANDLE_NOTIFY,
+					events_handler, gatt, NULL);
+		g_attrib_register(gatt->attrib,
+					ATT_OP_HANDLE_IND,
+					events_handler, gatt, NULL);
+		return;
+	}
+
+	return;
+fail:
+	g_attrib_unref(gatt->attrib);
+}
+
+static int l2cap_connect(struct gatt_service *gatt, GError **gerr,
+								gboolean listen)
+{
+	GIOChannel *io;
+
+	if (gatt->attrib != NULL) {
+		gatt->attrib = g_attrib_ref(gatt->attrib);
+		gatt->listen = listen;
+		return 0;
+	}
+
+	/*
+	 * FIXME: If the service doesn't support Client Characteristic
+	 * Configuration it is necessary to poll the server from time
+	 * to time checking for modifications.
+	 */
+	if (gatt->psm < 0)
+		io = bt_io_connect(BT_IO_L2CAP, connect_cb, gatt, NULL, gerr,
+			BT_IO_OPT_SOURCE_BDADDR, &gatt->sba,
+			BT_IO_OPT_DEST_BDADDR, &gatt->dba,
+			BT_IO_OPT_CID, GATT_CID,
+			BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+			BT_IO_OPT_INVALID);
+	else
+		io = bt_io_connect(BT_IO_L2CAP, connect_cb, gatt, NULL, gerr,
+			BT_IO_OPT_SOURCE_BDADDR, &gatt->sba,
+			BT_IO_OPT_DEST_BDADDR, &gatt->dba,
+			BT_IO_OPT_PSM, gatt->psm,
+			BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+			BT_IO_OPT_INVALID);
+	if (!io)
+		return -1;
+
+	gatt->attrib = g_attrib_new(io);
+	g_io_channel_unref(io);
+	gatt->listen = listen;
+
+	g_attrib_set_destroy_function(gatt->attrib, attrib_destroy, gatt);
+	g_attrib_set_disconnect_function(gatt->attrib, attrib_disconnect,
+									gatt);
+
+	return 0;
+}
+
+static DBusMessage *register_watcher(DBusConnection *conn,
+						DBusMessage *msg, void *data)
+{
+	const char *sender = dbus_message_get_sender(msg);
+	struct primary *prim = data;
+	struct watcher *watcher;
+	GError *gerr = NULL;
+	char *path;
+
+	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+							DBUS_TYPE_INVALID))
+		return btd_error_invalid_args(msg);
+
+	if (l2cap_connect(prim->gatt, &gerr, TRUE) < 0) {
+		DBusMessage *reply = btd_error_failed(msg, gerr->message);
+		g_error_free(gerr);
+		return reply;
+	}
+
+	watcher = g_new0(struct watcher, 1);
+	watcher->name = g_strdup(sender);
+	watcher->prim = prim;
+	watcher->path = g_strdup(path);
+	watcher->id = g_dbus_add_disconnect_watch(conn, sender, watcher_exit,
+							watcher, watcher_free);
+
+	prim->watchers = g_slist_append(prim->watchers, watcher);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_watcher(DBusConnection *conn,
+						DBusMessage *msg, void *data)
+{
+	const char *sender = dbus_message_get_sender(msg);
+	struct primary *prim = data;
+	struct watcher *watcher, *match;
+	GSList *l;
+	char *path;
+
+	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+							DBUS_TYPE_INVALID))
+		return btd_error_invalid_args(msg);
+
+	match = g_new0(struct watcher, 1);
+	match->name = g_strdup(sender);
+	match->path = g_strdup(path);
+	l = g_slist_find_custom(prim->watchers, match, watcher_cmp);
+	watcher_free(match);
+	if (!l)
+		return btd_error_not_authorized(msg);
+
+	watcher = l->data;
+	g_dbus_remove_watch(conn, watcher->id);
+	prim->watchers = g_slist_remove(prim->watchers, watcher);
+	watcher_free(watcher);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *set_value(DBusConnection *conn, DBusMessage *msg,
+			DBusMessageIter *iter, struct characteristic *chr)
+{
+	struct gatt_service *gatt = chr->prim->gatt;
+	DBusMessageIter sub;
+	GError *gerr = NULL;
+	uint8_t *value;
+	int len;
+
+	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY ||
+			dbus_message_iter_get_element_type(iter) != DBUS_TYPE_BYTE)
+		return btd_error_invalid_args(msg);
+
+	dbus_message_iter_recurse(iter, &sub);
+
+	dbus_message_iter_get_fixed_array(&sub, &value, &len);
+
+	if (l2cap_connect(gatt, &gerr, FALSE) < 0) {
+		DBusMessage *reply = btd_error_failed(msg, gerr->message);
+		g_error_free(gerr);
+		return reply;
+	}
+
+	gatt_write_cmd(gatt->attrib, chr->handle, value, len, NULL, NULL);
+
+	characteristic_set_value(chr, value, len);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *get_properties(DBusConnection *conn, DBusMessage *msg,
+								void *data)
+{
+	struct characteristic *chr = data;
+	DBusMessage *reply;
+	DBusMessageIter iter;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	dbus_message_iter_init_append(reply, &iter);
+
+	append_char_dict(&iter, chr);
+
+	return reply;
+}
+
+static DBusMessage *set_property(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct characteristic *chr = data;
+	DBusMessageIter iter;
+	DBusMessageIter sub;
+	const char *property;
+
+	if (!dbus_message_iter_init(msg, &iter))
+		return btd_error_invalid_args(msg);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+		return btd_error_invalid_args(msg);
+
+	dbus_message_iter_get_basic(&iter, &property);
+	dbus_message_iter_next(&iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+		return btd_error_invalid_args(msg);
+
+	dbus_message_iter_recurse(&iter, &sub);
+
+	if (g_str_equal("Value", property))
+		return set_value(conn, msg, &sub, chr);
+
+	return btd_error_invalid_args(msg);
+}
+
+static GDBusMethodTable char_methods[] = {
+	{ "GetProperties",	"",	"a{sv}", get_properties },
+	{ "SetProperty",	"sv",	"",	set_property,
+						G_DBUS_METHOD_FLAG_ASYNC},
+	{ }
+};
+
+static char *characteristic_list_to_string(GSList *chars)
+{
+	GString *characteristics;
+	GSList *l;
+
+	characteristics = g_string_new(NULL);
+
+	for (l = chars; l; l = l->next) {
+		struct characteristic *chr = l->data;
+		char chr_str[64];
+
+		memset(chr_str, 0, sizeof(chr_str));
+
+		snprintf(chr_str, sizeof(chr_str), "%04X#%02X#%04X#%s ",
+				chr->handle, chr->perm, chr->end, chr->type);
+
+		characteristics = g_string_append(characteristics, chr_str);
+	}
+
+	return g_string_free(characteristics, FALSE);
+}
+
+static void store_characteristics(struct gatt_service *gatt,
+							struct primary *prim)
+{
+	char *characteristics;
+	struct att_primary *att = prim->att;
+
+	characteristics = characteristic_list_to_string(prim->chars);
+
+	write_device_characteristics(&gatt->sba, &gatt->dba, att->start,
+							characteristics);
+
+	g_free(characteristics);
+}
+
+static void register_characteristics(struct primary *prim)
+{
+	GSList *lc;
+
+	for (lc = prim->chars; lc; lc = lc->next) {
+		struct characteristic *chr = lc->data;
+		g_dbus_register_interface(connection, chr->path,
+				CHAR_INTERFACE, char_methods,
+				NULL, NULL, chr, NULL);
+		DBG("Registered: %s", chr->path);
+	}
+}
+
+static GSList *string_to_characteristic_list(struct primary *prim,
+							const char *str)
+{
+	GSList *l = NULL;
+	char **chars;
+	int i;
+
+	if (str == NULL)
+		return NULL;
+
+	chars = g_strsplit(str, " ", 0);
+	if (chars == NULL)
+		return NULL;
+
+	for (i = 0; chars[i]; i++) {
+		struct characteristic *chr;
+		int ret;
+
+		chr = g_new0(struct characteristic, 1);
+
+		ret = sscanf(chars[i], "%04hX#%02hhX#%04hX#%s", &chr->handle,
+				&chr->perm, &chr->end, chr->type);
+		if (ret < 4) {
+			g_free(chr);
+			continue;
+		}
+
+		chr->prim = prim;
+		chr->path = g_strdup_printf("%s/characteristic%04x",
+						prim->path, chr->handle);
+
+		l = g_slist_append(l, chr);
+	}
+
+	g_strfreev(chars);
+
+	return l;
+}
+
+static void load_characteristics(gpointer data, gpointer user_data)
+{
+	struct primary *prim = data;
+	struct att_primary *att = prim->att;
+	struct gatt_service *gatt = user_data;
+	GSList *chrs_list;
+	char *str;
+
+	if (prim->chars) {
+		DBG("Characteristics already loaded");
+		return;
+	}
+
+	str = read_device_characteristics(&gatt->sba, &gatt->dba, att->start);
+	if (str == NULL)
+		return;
+
+	chrs_list = string_to_characteristic_list(prim, str);
+
+	free(str);
+
+	if (chrs_list == NULL)
+		return;
+
+	prim->chars = chrs_list;
+	register_characteristics(prim);
+
+	return;
+}
+
+static void store_attribute(struct gatt_service *gatt, uint16_t handle,
+				uint16_t type, uint8_t *value, gsize len)
+{
+	uuid_t uuid;
+	char *str, *uuidstr, *tmp;
+	guint i;
+
+	str = g_malloc0(MAX_LEN_UUID_STR + len * 2 + 1);
+
+	sdp_uuid16_create(&uuid, type);
+	uuidstr = bt_uuid2string(&uuid);
+	strcpy(str, uuidstr);
+	g_free(uuidstr);
+
+	str[MAX_LEN_UUID_STR - 1] = '#';
+
+	for (i = 0, tmp = str + MAX_LEN_UUID_STR; i < len; i++, tmp += 2)
+		sprintf(tmp, "%02X", value[i]);
+
+	write_device_attribute(&gatt->sba, &gatt->dba, handle, str);
+	g_free(str);
+}
+
+static void update_char_desc(guint8 status, const guint8 *pdu, guint16 len,
+							gpointer user_data)
+{
+	struct query_data *current = user_data;
+	struct gatt_service *gatt = current->prim->gatt;
+	struct characteristic *chr = current->chr;
+
+	if (status == 0) {
+
+		g_free(chr->desc);
+
+		chr->desc = g_malloc(len);
+		memcpy(chr->desc, pdu + 1, len - 1);
+		chr->desc[len - 1] = '\0';
+
+		store_attribute(gatt, current->handle,
+				GATT_CHARAC_USER_DESC_UUID,
+				(void *) chr->desc, len);
+	} else if (status == ATT_ECODE_INSUFF_ENC) {
+		GIOChannel *io = g_attrib_get_channel(gatt->attrib);
+
+		if (bt_io_set(io, BT_IO_L2CAP, NULL,
+				BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_HIGH,
+				BT_IO_OPT_INVALID)) {
+			gatt_read_char(gatt->attrib, current->handle,
+					update_char_desc, current);
+			return;
+		}
+	}
+
+	g_attrib_unref(gatt->attrib);
+	g_free(current);
+}
+
+static void update_char_format(guint8 status, const guint8 *pdu, guint16 len,
+								gpointer user_data)
+{
+	struct query_data *current = user_data;
+	struct gatt_service *gatt = current->prim->gatt;
+	struct characteristic *chr = current->chr;
+
+	if (status != 0)
+		goto done;
+
+	if (len < 8)
+		goto done;
+
+	g_free(chr->format);
+
+	chr->format = g_new0(struct format, 1);
+	memcpy(chr->format, pdu + 1, 7);
+
+	store_attribute(gatt, current->handle, GATT_CHARAC_FMT_UUID,
+				(void *) chr->format, sizeof(*chr->format));
+
+done:
+	g_attrib_unref(gatt->attrib);
+	g_free(current);
+}
+
+static void update_char_value(guint8 status, const guint8 *pdu,
+					guint16 len, gpointer user_data)
+{
+	struct query_data *current = user_data;
+	struct gatt_service *gatt = current->prim->gatt;
+	struct characteristic *chr = current->chr;
+
+	if (status == 0)
+		characteristic_set_value(chr, pdu + 1, len - 1);
+	else if (status == ATT_ECODE_INSUFF_ENC) {
+		GIOChannel *io = g_attrib_get_channel(gatt->attrib);
+
+		if (bt_io_set(io, BT_IO_L2CAP, NULL,
+				BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_HIGH,
+				BT_IO_OPT_INVALID)) {
+			gatt_read_char(gatt->attrib, chr->handle,
+					update_char_value, current);
+			return;
+		}
+	}
+
+	g_attrib_unref(gatt->attrib);
+	g_free(current);
+}
+
+static int uuid_desc16_cmp(uuid_t *uuid, guint16 desc)
+{
+	uuid_t u16;
+
+	sdp_uuid16_create(&u16, desc);
+
+	return sdp_uuid_cmp(uuid, &u16);
+}
+
+static void descriptor_cb(guint8 status, const guint8 *pdu, guint16 plen,
+							gpointer user_data)
+{
+	struct query_data *current = user_data;
+	struct gatt_service *gatt = current->prim->gatt;
+	struct att_data_list *list;
+	guint8 format;
+	int i;
+
+	if (status != 0)
+		goto done;
+
+	DBG("Find Information Response received");
+
+	list = dec_find_info_resp(pdu, plen, &format);
+	if (list == NULL)
+		goto done;
+
+	for (i = 0; i < list->num; i++) {
+		guint16 handle;
+		uuid_t uuid;
+		uint8_t *info = list->data[i];
+		struct query_data *qfmt;
+
+		handle = att_get_u16(info);
+
+		if (format == 0x01) {
+			sdp_uuid16_create(&uuid, att_get_u16(&info[2]));
+		} else {
+			/* Currently, only "user description" and "presentation
+			 * format" descriptors are used, and both have 16-bit
+			 * UUIDs. Therefore there is no need to support format
+			 * 0x02 yet. */
+			continue;
+		}
+		qfmt = g_new0(struct query_data, 1);
+		qfmt->prim = current->prim;
+		qfmt->chr = current->chr;
+		qfmt->handle = handle;
+
+		if (uuid_desc16_cmp(&uuid, GATT_CHARAC_USER_DESC_UUID) == 0) {
+			gatt->attrib = g_attrib_ref(gatt->attrib);
+			gatt_read_char(gatt->attrib, handle, update_char_desc,
+									qfmt);
+		} else if (uuid_desc16_cmp(&uuid, GATT_CHARAC_FMT_UUID) == 0) {
+			gatt->attrib = g_attrib_ref(gatt->attrib);
+			gatt_read_char(gatt->attrib, handle,
+						update_char_format, qfmt);
+		} else
+			g_free(qfmt);
+	}
+
+	att_data_list_free(list);
+done:
+	g_attrib_unref(gatt->attrib);
+	g_free(current);
+}
+
+static void update_all_chars(gpointer data, gpointer user_data)
+{
+	struct query_data *qdesc, *qvalue;
+	struct characteristic *chr = data;
+	struct primary *prim = user_data;
+	struct gatt_service *gatt = prim->gatt;
+
+	qdesc = g_new0(struct query_data, 1);
+	qdesc->prim = prim;
+	qdesc->chr = chr;
+
+	gatt->attrib = g_attrib_ref(gatt->attrib);
+	gatt_find_info(gatt->attrib, chr->handle + 1, chr->end, descriptor_cb,
+									qdesc);
+
+	qvalue = g_new0(struct query_data, 1);
+	qvalue->prim = prim;
+	qvalue->chr = chr;
+
+	gatt->attrib = g_attrib_ref(gatt->attrib);
+	gatt_read_char(gatt->attrib, chr->handle, update_char_value, qvalue);
+}
+
+static void char_discovered_cb(GSList *characteristics, guint8 status,
+							gpointer user_data)
+{
+	struct query_data *current = user_data;
+	struct primary *prim = current->prim;
+	struct att_primary *att = prim->att;
+	struct gatt_service *gatt = prim->gatt;
+	uint16_t *previous_end = NULL;
+	GSList *l;
+
+	if (status != 0) {
+		DBG("Discover all characteristics failed: %s",
+						att_ecode2str(status));
+		goto fail;
+	}
+
+	for (l = characteristics; l; l = l->next) {
+		struct att_char *current_chr = l->data;
+		struct characteristic *chr;
+		guint handle = current_chr->value_handle;
+		GSList *lchr;
+
+		lchr = g_slist_find_custom(prim->chars,
+			GUINT_TO_POINTER(handle), characteristic_handle_cmp);
+		if (lchr)
+			continue;
+
+		chr = g_new0(struct characteristic, 1);
+		chr->prim = prim;
+		chr->perm = current_chr->properties;
+		chr->handle = current_chr->value_handle;
+		chr->path = g_strdup_printf("%s/characteristic%04x",
+						prim->path, chr->handle);
+		strncpy(chr->type, current_chr->uuid, sizeof(chr->type));
+
+		if (previous_end)
+			*previous_end = current_chr->handle;
+
+		previous_end = &chr->end;
+
+		prim->chars = g_slist_append(prim->chars, chr);
+	}
+
+	if (previous_end)
+		*previous_end = att->end;
+
+	store_characteristics(gatt, prim);
+	register_characteristics(prim);
+
+	g_slist_foreach(prim->chars, update_all_chars, prim);
+
+fail:
+	g_attrib_unref(gatt->attrib);
+	g_free(current);
+}
+
+static DBusMessage *discover_char(DBusConnection *conn, DBusMessage *msg,
+								void *data)
+{
+	struct primary *prim = data;
+	struct att_primary *att = prim->att;
+	struct gatt_service *gatt = prim->gatt;
+	struct query_data *qchr;
+	GError *gerr = NULL;
+
+	if (l2cap_connect(prim->gatt, &gerr, FALSE) < 0) {
+		DBusMessage *reply = btd_error_failed(msg, gerr->message);
+		g_error_free(gerr);
+		return reply;
+	}
+
+	qchr = g_new0(struct query_data, 1);
+	qchr->prim = prim;
+
+	gatt_discover_char(gatt->attrib, att->start, att->end,
+						char_discovered_cb, qchr);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *prim_get_properties(DBusConnection *conn, DBusMessage *msg,
+								void *data)
+{
+	struct primary *prim = data;
+	DBusMessage *reply;
+	DBusMessageIter iter;
+	DBusMessageIter dict;
+	GSList *l;
+	char **chars;
+	const char *uuid;
+	int i;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	dbus_message_iter_init_append(reply, &iter);
+
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+	chars = g_new0(char *, g_slist_length(prim->chars) + 1);
+
+	for (i = 0, l = prim->chars; l; l = l->next, i++) {
+		struct characteristic *chr = l->data;
+		chars[i] = chr->path;
+	}
+
+	dict_append_array(&dict, "Characteristics", DBUS_TYPE_OBJECT_PATH,
+								&chars, i);
+	uuid = prim->att->uuid;
+	dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid);
+
+	g_free(chars);
+
+	dbus_message_iter_close_container(&iter, &dict);
+
+	return reply;
+}
+
+static GDBusMethodTable prim_methods[] = {
+	{ "Discover",		"",	"",		discover_char	},
+	{ "RegisterCharacteristicsWatcher",	"o", "",
+						register_watcher	},
+	{ "UnregisterCharacteristicsWatcher",	"o", "",
+						unregister_watcher	},
+	{ "GetProperties",	"",	"a{sv}",prim_get_properties	},
+	{ }
+};
+
+static void register_primaries(struct gatt_service *gatt, GSList *primaries)
+{
+	GSList *l;
+
+	for (l = primaries; l; l = l->next) {
+		struct att_primary *att = l->data;
+		struct primary *prim;
+
+		prim = g_new0(struct primary, 1);
+		prim->att = att;
+		prim->gatt = gatt;
+		prim->path = g_strdup_printf("%s/service%04x", gatt->path,
+								att->start);
+
+		g_dbus_register_interface(connection, prim->path,
+				CHAR_INTERFACE, prim_methods,
+				NULL, NULL, prim, NULL);
+		DBG("Registered: %s", prim->path);
+
+		gatt->primary = g_slist_append(gatt->primary, prim);
+		btd_device_add_service(gatt->dev, prim->path);
+		load_characteristics(prim, gatt);
+	}
+}
+
+int attrib_client_register(struct btd_device *device, int psm)
+{
+	struct btd_adapter *adapter = device_get_adapter(device);
+	const char *path = device_get_path(device);
+	struct gatt_service *gatt;
+	GSList *primaries = btd_device_get_primaries(device);
+	bdaddr_t sba, dba;
+
+	adapter_get_address(adapter, &sba);
+	device_get_address(device, &dba);
+
+	gatt = g_new0(struct gatt_service, 1);
+	gatt->dev = btd_device_ref(device);
+	gatt->listen = FALSE;
+	gatt->path = g_strdup(path);
+	bacpy(&gatt->sba, &sba);
+	bacpy(&gatt->dba, &dba);
+	gatt->psm = psm;
+
+	register_primaries(gatt, primaries);
+
+	gatt_services = g_slist_append(gatt_services, gatt);
+
+	return 0;
+}
+
+void attrib_client_unregister(struct btd_device *device)
+{
+	struct gatt_service *gatt;
+	GSList *l, *lp, *lc;
+
+	l = g_slist_find_custom(gatt_services, device, gatt_dev_cmp);
+	if (!l)
+		return;
+
+	gatt = l->data;
+	gatt_services = g_slist_remove(gatt_services, gatt);
+
+	for (lp = gatt->primary; lp; lp = lp->next) {
+		struct primary *prim = lp->data;
+		for (lc = prim->chars; lc; lc = lc->next) {
+			struct characteristic *chr = lc->data;
+			g_dbus_unregister_interface(connection, chr->path,
+								CHAR_INTERFACE);
+		}
+		g_dbus_unregister_interface(connection, prim->path,
+								CHAR_INTERFACE);
+	}
+
+	gatt_service_free(gatt);
+}
+
+int attrib_client_init(DBusConnection *conn)
+{
+
+	connection = dbus_connection_ref(conn);
+
+	/*
+	 * FIXME: if the adapter supports BLE start scanning. Temporary
+	 * solution, this approach doesn't allow to control scanning based
+	 * on the discoverable property.
+	 */
+
+	return 0;
+}
+
+void attrib_client_exit(void)
+{
+	dbus_connection_unref(connection);
+}
diff --git a/attrib/client.h b/attrib/client.h
new file mode 100644
index 0000000..50e2b5f
--- /dev/null
+++ b/attrib/client.h
@@ -0,0 +1,28 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+int attrib_client_init(DBusConnection *conn);
+void attrib_client_exit(void);
+int attrib_client_register(struct btd_device *device, int psm);
+void attrib_client_unregister(struct btd_device *device);
diff --git a/attrib/example.c b/attrib/example.c
new file mode 100644
index 0000000..395650a
--- /dev/null
+++ b/attrib/example.c
@@ -0,0 +1,337 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <arpa/inet.h>
+
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+
+#include "log.h"
+#include "attrib-server.h"
+
+#include "att.h"
+#include "example.h"
+
+/* FIXME: Not defined by SIG? UUID128? */
+#define OPCODES_SUPPORTED_UUID          0xA001
+#define BATTERY_STATE_SVC_UUID		0xA002
+#define BATTERY_STATE_UUID		0xA003
+#define THERM_HUMIDITY_SVC_UUID		0xA004
+#define MANUFACTURER_SVC_UUID		0xA005
+#define TEMPERATURE_UUID		0xA006
+#define FMT_CELSIUS_UUID		0xA007
+#define FMT_OUTSIDE_UUID		0xA008
+#define RELATIVE_HUMIDITY_UUID		0xA009
+#define FMT_PERCENT_UUID		0xA00A
+#define BLUETOOTH_SIG_UUID		0xA00B
+#define MANUFACTURER_NAME_UUID		0xA00C
+#define MANUFACTURER_SERIAL_UUID	0xA00D
+#define VENDOR_SPECIFIC_SVC_UUID	0xA00E
+#define VENDOR_SPECIFIC_TYPE_UUID	0xA00F
+#define FMT_KILOGRAM_UUID		0xA010
+#define FMT_HANGING_UUID		0xA011
+
+static GSList *sdp_handles = NULL;
+
+static int register_attributes(void)
+{
+	const char *desc_out_temp = "Outside Temperature";
+	const char *desc_out_hum = "Outside Relative Humidity";
+	const char *desc_weight = "Rucksack Weight";
+	const char *manufacturer_name1 = "ACME Temperature Sensor";
+	const char *manufacturer_name2 = "ACME Weighing Scales";
+	const char *serial1 = "237495-3282-A";
+	const char *serial2 = "11267-2327A00239";
+	const unsigned char char_weight_uuid[] = { 0x80, 0x88, 0xF2, 0x18, 0x90,
+		0x2C, 0x45, 0x0B, 0xB6, 0xC4, 0x62, 0x89, 0x1E, 0x8C, 0x25,
+		0xE9 };
+	const unsigned char prim_weight_uuid[] = { 0x4F, 0x0A, 0xC0, 0x96, 0x35,
+		0xD4, 0x49, 0x11, 0x96, 0x31, 0xDE, 0xA8, 0xDC, 0x74, 0xEE,
+		0xFE };
+	uint8_t atval[256];
+	uint32_t handle;
+	uuid_t uuid;
+	int len;
+
+	/* Battery state service: primary service definition */
+	sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID);
+	att_put_u16(BATTERY_STATE_SVC_UUID, &atval[0]);
+	attrib_db_add(0x0100, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+	/* Battery: battery state characteristic */
+	sdp_uuid16_create(&uuid, GATT_CHARAC_UUID);
+	atval[0] = ATT_CHAR_PROPER_READ;
+	att_put_u16(0x0110, &atval[1]);
+	att_put_u16(BATTERY_STATE_UUID, &atval[3]);
+	attrib_db_add(0x0106, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+	/* Battery: battery state attribute */
+	sdp_uuid16_create(&uuid, BATTERY_STATE_UUID);
+	atval[0] = 0x04;
+	attrib_db_add(0x0110, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 1);
+
+	/* Battery: Client Characteristic Configuration */
+	sdp_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
+	atval[0] = 0x00;
+	atval[1] = 0x00;
+	attrib_db_add(0x0111, &uuid, ATT_NONE, ATT_AUTHENTICATION, atval, 2);
+
+	/* Add an SDP record for the above service */
+	handle = attrib_create_sdp(0x0100, "Battery State Service");
+	if (handle)
+		sdp_handles = g_slist_prepend(sdp_handles, GUINT_TO_POINTER(handle));
+
+	/* Thermometer: primary service definition */
+	sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID);
+	att_put_u16(THERM_HUMIDITY_SVC_UUID, &atval[0]);
+	attrib_db_add(0x0200, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+	/* Thermometer: Include */
+	sdp_uuid16_create(&uuid, GATT_INCLUDE_UUID);
+	att_put_u16(0x0500, &atval[0]);
+	att_put_u16(0x0504, &atval[2]);
+	att_put_u16(MANUFACTURER_SVC_UUID, &atval[4]);
+	attrib_db_add(0x0201, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6);
+
+	/* Thermometer: Include */
+	att_put_u16(0x0550, &atval[0]);
+	att_put_u16(0x0568, &atval[2]);
+	att_put_u16(VENDOR_SPECIFIC_SVC_UUID, &atval[4]);
+	attrib_db_add(0x0202, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6);
+
+	/* Thermometer: temperature characteristic */
+	sdp_uuid16_create(&uuid, GATT_CHARAC_UUID);
+	atval[0] = ATT_CHAR_PROPER_READ;
+	att_put_u16(0x0204, &atval[1]);
+	att_put_u16(TEMPERATURE_UUID, &atval[3]);
+	attrib_db_add(0x0203, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+	/* Thermometer: temperature characteristic value */
+	sdp_uuid16_create(&uuid, TEMPERATURE_UUID);
+	atval[0] = 0x8A;
+	atval[1] = 0x02;
+	attrib_db_add(0x0204, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+	/* Thermometer: temperature characteristic format */
+	sdp_uuid16_create(&uuid, GATT_CHARAC_FMT_UUID);
+	atval[0] = 0x0E;
+	atval[1] = 0xFE;
+	att_put_u16(FMT_CELSIUS_UUID, &atval[2]);
+	atval[4] = 0x01;
+	att_put_u16(FMT_OUTSIDE_UUID, &atval[5]);
+	attrib_db_add(0x0205, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 7);
+
+	/* Thermometer: characteristic user description */
+	sdp_uuid16_create(&uuid, GATT_CHARAC_USER_DESC_UUID);
+	len = strlen(desc_out_temp);
+	strncpy((char *) atval, desc_out_temp, len);
+	attrib_db_add(0x0206, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+	/* Thermometer: relative humidity characteristic */
+	sdp_uuid16_create(&uuid, GATT_CHARAC_UUID);
+	atval[0] = ATT_CHAR_PROPER_READ;
+	att_put_u16(0x0212, &atval[1]);
+	att_put_u16(RELATIVE_HUMIDITY_UUID, &atval[3]);
+	attrib_db_add(0x0210, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+	/* Thermometer: relative humidity value */
+	sdp_uuid16_create(&uuid, RELATIVE_HUMIDITY_UUID);
+	atval[0] = 0x27;
+	attrib_db_add(0x0212, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 1);
+
+	/* Thermometer: relative humidity characteristic format */
+	sdp_uuid16_create(&uuid, GATT_CHARAC_FMT_UUID);
+	atval[0] = 0x04;
+	atval[1] = 0x00;
+	att_put_u16(FMT_PERCENT_UUID, &atval[2]);
+	att_put_u16(BLUETOOTH_SIG_UUID, &atval[4]);
+	att_put_u16(FMT_OUTSIDE_UUID, &atval[6]);
+	attrib_db_add(0x0213, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 8);
+
+	/* Thermometer: characteristic user description */
+	sdp_uuid16_create(&uuid, GATT_CHARAC_USER_DESC_UUID);
+	len = strlen(desc_out_hum);
+	strncpy((char *) atval, desc_out_hum, len);
+	attrib_db_add(0x0214, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+	/* Add an SDP record for the above service */
+	handle = attrib_create_sdp(0x0200, "Thermometer");
+	if (handle)
+		sdp_handles = g_slist_prepend(sdp_handles, GUINT_TO_POINTER(handle));
+
+	/* Secondary Service: Manufacturer Service */
+	sdp_uuid16_create(&uuid, GATT_SND_SVC_UUID);
+	att_put_u16(MANUFACTURER_SVC_UUID, &atval[0]);
+	attrib_db_add(0x0500, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+	/* Manufacturer name characteristic definition */
+	sdp_uuid16_create(&uuid, GATT_CHARAC_UUID);
+	atval[0] = ATT_CHAR_PROPER_READ;
+	att_put_u16(0x0502, &atval[1]);
+	att_put_u16(MANUFACTURER_NAME_UUID, &atval[3]);
+	attrib_db_add(0x0501, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+	/* Manufacturer name characteristic value */
+	sdp_uuid16_create(&uuid, MANUFACTURER_NAME_UUID);
+	len = strlen(manufacturer_name1);
+	strncpy((char *) atval, manufacturer_name1, len);
+	attrib_db_add(0x0502, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+	/* Manufacturer serial number characteristic */
+	sdp_uuid16_create(&uuid, GATT_CHARAC_UUID);
+	atval[0] = ATT_CHAR_PROPER_READ;
+	att_put_u16(0x0504, &atval[1]);
+	att_put_u16(MANUFACTURER_SERIAL_UUID, &atval[3]);
+	attrib_db_add(0x0503, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+	/* Manufacturer serial number characteristic value */
+	sdp_uuid16_create(&uuid, MANUFACTURER_SERIAL_UUID);
+	len = strlen(serial1);
+	strncpy((char *) atval, serial1, len);
+	attrib_db_add(0x0504, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+	/* Secondary Service: Manufacturer Service */
+	sdp_uuid16_create(&uuid, GATT_SND_SVC_UUID);
+	att_put_u16(MANUFACTURER_SVC_UUID, &atval[0]);
+	attrib_db_add(0x0505, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+	/* Manufacturer name characteristic definition */
+	sdp_uuid16_create(&uuid, GATT_CHARAC_UUID);
+	atval[0] = ATT_CHAR_PROPER_READ;
+	att_put_u16(0x0507, &atval[1]);
+	att_put_u16(MANUFACTURER_NAME_UUID, &atval[3]);
+	attrib_db_add(0x0506, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+	/* Secondary Service: Vendor Specific Service */
+	sdp_uuid16_create(&uuid, GATT_SND_SVC_UUID);
+	att_put_u16(VENDOR_SPECIFIC_SVC_UUID, &atval[0]);
+	attrib_db_add(0x0550, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+	/* Vendor Specific Type characteristic definition */
+	sdp_uuid16_create(&uuid, GATT_CHARAC_UUID);
+	atval[0] = ATT_CHAR_PROPER_READ;
+	att_put_u16(0x0568, &atval[1]);
+	att_put_u16(VENDOR_SPECIFIC_TYPE_UUID, &atval[3]);
+	attrib_db_add(0x0560, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+	/* Vendor Specific Type characteristic value */
+	sdp_uuid16_create(&uuid, VENDOR_SPECIFIC_TYPE_UUID);
+	atval[0] = 0x56;
+	atval[1] = 0x65;
+	atval[2] = 0x6E;
+	atval[3] = 0x64;
+	atval[4] = 0x6F;
+	atval[5] = 0x72;
+	attrib_db_add(0x0568, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6);
+
+	/* Manufacturer name attribute */
+	sdp_uuid16_create(&uuid, MANUFACTURER_NAME_UUID);
+	len = strlen(manufacturer_name2);
+	strncpy((char *) atval, manufacturer_name2, len);
+	attrib_db_add(0x0507, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+	/* Characteristic: serial number */
+	sdp_uuid16_create(&uuid, GATT_CHARAC_UUID);
+	atval[0] = ATT_CHAR_PROPER_READ;
+	att_put_u16(0x0509, &atval[1]);
+	att_put_u16(MANUFACTURER_SERIAL_UUID, &atval[3]);
+	attrib_db_add(0x0508, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+	/* Serial number characteristic value */
+	sdp_uuid16_create(&uuid, MANUFACTURER_SERIAL_UUID);
+	len = strlen(serial2);
+	strncpy((char *) atval, serial2, len);
+	attrib_db_add(0x0509, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+	/* Weight service: primary service definition */
+	sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID);
+	memcpy(atval, prim_weight_uuid, 16);
+	attrib_db_add(0x0680, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 16);
+
+	/* Weight: include */
+	sdp_uuid16_create(&uuid, GATT_INCLUDE_UUID);
+	att_put_u16(0x0505, &atval[0]);
+	att_put_u16(0x0509, &atval[2]);
+	att_put_u16(MANUFACTURER_SVC_UUID, &atval[4]);
+	attrib_db_add(0x0681, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6);
+
+	/* Weight: characteristic */
+	sdp_uuid16_create(&uuid, GATT_CHARAC_UUID);
+	atval[0] = ATT_CHAR_PROPER_READ;
+	att_put_u16(0x0683, &atval[1]);
+	memcpy(&atval[3], char_weight_uuid, 16);
+	attrib_db_add(0x0682, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 19);
+
+	/* Weight: characteristic value */
+	sdp_uuid128_create(&uuid, char_weight_uuid);
+	atval[0] = 0x82;
+	atval[1] = 0x55;
+	atval[2] = 0x00;
+	atval[3] = 0x00;
+	attrib_db_add(0x0683, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 4);
+
+	/* Weight: characteristic format */
+	sdp_uuid16_create(&uuid, GATT_CHARAC_FMT_UUID);
+	atval[0] = 0x08;
+	atval[1] = 0xFD;
+	att_put_u16(FMT_KILOGRAM_UUID, &atval[2]);
+	att_put_u16(BLUETOOTH_SIG_UUID, &atval[4]);
+	att_put_u16(FMT_HANGING_UUID, &atval[6]);
+	attrib_db_add(0x0684, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 8);
+
+	/* Weight: characteristic user description */
+	sdp_uuid16_create(&uuid, GATT_CHARAC_USER_DESC_UUID);
+	len = strlen(desc_weight);
+	strncpy((char *) atval, desc_weight, len);
+	attrib_db_add(0x0685, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+	/* Add an SDP record for the above service */
+	handle = attrib_create_sdp(0x0680, "Weight Service");
+	if (handle)
+		sdp_handles = g_slist_prepend(sdp_handles, GUINT_TO_POINTER(handle));
+
+	return 0;
+}
+
+int server_example_init(void)
+{
+	return register_attributes();
+}
+
+void server_example_exit(void)
+{
+	while (sdp_handles) {
+		uint32_t handle = GPOINTER_TO_UINT(sdp_handles->data);
+
+		attrib_free_sdp(handle);
+		sdp_handles = g_slist_remove(sdp_handles, sdp_handles->data);
+	}
+}
diff --git a/attrib/example.h b/attrib/example.h
new file mode 100644
index 0000000..a2b07fe
--- /dev/null
+++ b/attrib/example.h
@@ -0,0 +1,26 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+int server_example_init(void);
+void server_example_exit(void);
diff --git a/attrib/gatt.c b/attrib/gatt.c
new file mode 100644
index 0000000..20bb96f
--- /dev/null
+++ b/attrib/gatt.c
@@ -0,0 +1,526 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdint.h>
+#include <glib.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "att.h"
+#include "gattrib.h"
+#include "gatt.h"
+
+struct discover_primary {
+	GAttrib *attrib;
+	uuid_t uuid;
+	GSList *primaries;
+	gatt_cb_t cb;
+	void *user_data;
+};
+
+struct discover_char {
+	GAttrib *attrib;
+	uuid_t uuid;
+	uint16_t end;
+	GSList *characteristics;
+	gatt_cb_t cb;
+	void *user_data;
+};
+
+static void discover_primary_free(struct discover_primary *dp)
+{
+	g_slist_free(dp->primaries);
+	g_attrib_unref(dp->attrib);
+	g_free(dp);
+}
+
+static void discover_char_free(struct discover_char *dc)
+{
+	g_slist_foreach(dc->characteristics, (GFunc) g_free, NULL);
+	g_slist_free(dc->characteristics);
+	g_attrib_unref(dc->attrib);
+	g_free(dc);
+}
+
+static guint16 encode_discover_primary(uint16_t start, uint16_t end,
+					uuid_t *uuid, uint8_t *pdu, size_t len)
+{
+	uuid_t prim;
+	guint16 plen;
+	uint8_t op;
+
+	sdp_uuid16_create(&prim, GATT_PRIM_SVC_UUID);
+
+	if (uuid == NULL) {
+		/* Discover all primary services */
+		op = ATT_OP_READ_BY_GROUP_REQ;
+		plen = enc_read_by_grp_req(start, end, &prim, pdu, len);
+	} else {
+		const void *value;
+		int vlen;
+
+		/* Discover primary service by service UUID */
+		op = ATT_OP_FIND_BY_TYPE_REQ;
+
+		if (uuid->type == SDP_UUID16) {
+			value = &uuid->value.uuid16;
+			vlen = sizeof(uuid->value.uuid16);
+		} else {
+			value = &uuid->value.uuid128;
+			vlen = sizeof(uuid->value.uuid128);
+		}
+
+		plen = enc_find_by_type_req(start, end, &prim, value, vlen,
+								pdu, len);
+	}
+
+	return plen;
+}
+
+static void primary_by_uuid_cb(guint8 status, const guint8 *ipdu,
+					guint16 iplen, gpointer user_data)
+
+{
+	struct discover_primary *dp = user_data;
+	GSList *ranges, *last;
+	struct att_range *range;
+	uint8_t opdu[ATT_DEFAULT_LE_MTU];
+	guint16 oplen;
+	int err = 0;
+
+	if (status) {
+		err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status;
+		goto done;
+	}
+
+	ranges = dec_find_by_type_resp(ipdu, iplen);
+	if (ranges == NULL)
+		goto done;
+
+	dp->primaries = g_slist_concat(dp->primaries, ranges);
+
+	last = g_slist_last(ranges);
+	range = last->data;
+
+	if (range->end == 0xffff)
+		goto done;
+
+	oplen = encode_discover_primary(range->end + 1, 0xffff, &dp->uuid,
+							opdu, sizeof(opdu));
+
+	if (oplen == 0)
+		goto done;
+
+	g_attrib_send(dp->attrib, 0, opdu[0], opdu, oplen, primary_by_uuid_cb,
+								dp, NULL);
+	return;
+
+done:
+	dp->cb(dp->primaries, err, dp->user_data);
+	discover_primary_free(dp);
+}
+
+static void primary_all_cb(guint8 status, const guint8 *ipdu, guint16 iplen,
+							gpointer user_data)
+{
+	struct discover_primary *dp = user_data;
+	struct att_data_list *list;
+	unsigned int i, err;
+	uint16_t start, end;
+
+	if (status) {
+		err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status;
+		goto done;
+	}
+
+	list = dec_read_by_grp_resp(ipdu, iplen);
+	if (list == NULL) {
+		err = ATT_ECODE_IO;
+		goto done;
+	}
+
+	for (i = 0, end = 0; i < list->num; i++) {
+		const uint8_t *data = list->data[i];
+		struct att_primary *primary;
+		uuid_t u128, u16;
+
+		start = att_get_u16(&data[0]);
+		end = att_get_u16(&data[2]);
+
+		if (list->len == 6) {
+			sdp_uuid16_create(&u16,
+					att_get_u16(&data[4]));
+			sdp_uuid16_to_uuid128(&u128, &u16);
+
+		} else if (list->len == 20)
+			sdp_uuid128_create(&u128, &data[4]);
+		else
+			/* Skipping invalid data */
+			continue;
+
+		primary = g_try_new0(struct att_primary, 1);
+		if (!primary) {
+			err = ATT_ECODE_INSUFF_RESOURCES;
+			goto done;
+		}
+		primary->start = start;
+		primary->end = end;
+		sdp_uuid2strn(&u128, primary->uuid, sizeof(primary->uuid));
+		dp->primaries = g_slist_append(dp->primaries, primary);
+	}
+
+	att_data_list_free(list);
+	err = 0;
+
+	if (end != 0xffff) {
+		uint8_t opdu[ATT_DEFAULT_LE_MTU];
+		guint16 oplen = encode_discover_primary(end + 1, 0xffff, NULL,
+							opdu, sizeof(opdu));
+
+		g_attrib_send(dp->attrib, 0, opdu[0], opdu, oplen,
+						primary_all_cb, dp, NULL);
+
+		return;
+	}
+
+done:
+	dp->cb(dp->primaries, err, dp->user_data);
+	discover_primary_free(dp);
+}
+
+guint gatt_discover_primary(GAttrib *attrib, uuid_t *uuid, gatt_cb_t func,
+							gpointer user_data)
+{
+	struct discover_primary *dp;
+	uint8_t pdu[ATT_DEFAULT_LE_MTU];
+	GAttribResultFunc cb;
+	guint16 plen;
+
+	plen = encode_discover_primary(0x0001, 0xffff, uuid, pdu, sizeof(pdu));
+	if (plen == 0)
+		return 0;
+
+	dp = g_try_new0(struct discover_primary, 1);
+	if (dp == NULL)
+		return 0;
+
+	dp->attrib = g_attrib_ref(attrib);
+	dp->cb = func;
+	dp->user_data = user_data;
+
+	if (uuid) {
+		memcpy(&dp->uuid, uuid, sizeof(uuid_t));
+		cb = primary_by_uuid_cb;
+	} else
+		cb = primary_all_cb;
+
+	return g_attrib_send(attrib, 0, pdu[0], pdu, plen, cb, dp, NULL);
+}
+
+static void char_discovered_cb(guint8 status, const guint8 *ipdu, guint16 iplen,
+							gpointer user_data)
+{
+	struct discover_char *dc = user_data;
+	struct att_data_list *list;
+	unsigned int i, err;
+	uint8_t opdu[ATT_DEFAULT_LE_MTU];
+	guint16 oplen;
+	uuid_t uuid;
+	uint16_t last = 0;
+
+	if (status) {
+		err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status;
+		goto done;
+	}
+
+	list = dec_read_by_type_resp(ipdu, iplen);
+	if (list == NULL) {
+		err = ATT_ECODE_IO;
+		goto done;
+	}
+
+	for (i = 0; i < list->num; i++) {
+		uint8_t *value = list->data[i];
+		struct att_char *chars;
+		uuid_t u128, u16;
+
+		last = att_get_u16(value);
+
+		if (list->len == 7) {
+			sdp_uuid16_create(&u16, att_get_u16(&value[5]));
+			sdp_uuid16_to_uuid128(&u128, &u16);
+		} else
+			sdp_uuid128_create(&u128, &value[5]);
+
+		chars = g_try_new0(struct att_char, 1);
+		if (!chars) {
+			err = ATT_ECODE_INSUFF_RESOURCES;
+			goto done;
+		}
+
+		chars->handle = last;
+		chars->properties = value[2];
+		chars->value_handle = att_get_u16(&value[3]);
+		sdp_uuid2strn(&u128, chars->uuid, sizeof(chars->uuid));
+		dc->characteristics = g_slist_append(dc->characteristics,
+									chars);
+	}
+
+	att_data_list_free(list);
+	err = 0;
+
+	if (last != 0) {
+		sdp_uuid16_create(&uuid, GATT_CHARAC_UUID);
+
+		oplen = enc_read_by_type_req(last + 1, dc->end, &uuid, opdu,
+								sizeof(opdu));
+
+		if (oplen == 0)
+			return;
+
+		g_attrib_send(dc->attrib, 0, opdu[0], opdu, oplen,
+						char_discovered_cb, dc, NULL);
+
+		return;
+	}
+
+done:
+	dc->cb(dc->characteristics, err, dc->user_data);
+	discover_char_free(dc);
+}
+
+guint gatt_discover_char(GAttrib *attrib, uint16_t start, uint16_t end,
+					gatt_cb_t func, gpointer user_data)
+{
+	uint8_t pdu[ATT_DEFAULT_LE_MTU];
+	struct discover_char *dc;
+	guint16 plen;
+	uuid_t uuid;
+
+	sdp_uuid16_create(&uuid, GATT_CHARAC_UUID);
+
+	plen = enc_read_by_type_req(start, end, &uuid, pdu, sizeof(pdu));
+	if (plen == 0)
+		return 0;
+
+	dc = g_try_new0(struct discover_char, 1);
+	if (dc == NULL)
+		return 0;
+
+	dc->attrib = g_attrib_ref(attrib);
+	dc->cb = func;
+	dc->user_data = user_data;
+	dc->end = end;
+
+	return g_attrib_send(attrib, 0, pdu[0], pdu, plen, char_discovered_cb,
+								dc, NULL);
+}
+
+guint gatt_read_char_by_uuid(GAttrib *attrib, uint16_t start, uint16_t end,
+					uuid_t *uuid, GAttribResultFunc func,
+					gpointer user_data)
+{
+	uint8_t pdu[ATT_DEFAULT_LE_MTU];
+	guint16 plen;
+
+	plen = enc_read_by_type_req(start, end, uuid, pdu, sizeof(pdu));
+	if (plen == 0)
+		return 0;
+
+	return g_attrib_send(attrib, 0, ATT_OP_READ_BY_TYPE_REQ,
+					pdu, plen, func, user_data, NULL);
+}
+
+struct read_long_data {
+	GAttrib *attrib;
+	GAttribResultFunc func;
+	gpointer user_data;
+	guint8 *buffer;
+	guint16 size;
+	guint16 handle;
+	guint id;
+	gint ref;
+};
+
+static void read_long_destroy(gpointer user_data)
+{
+	struct read_long_data *long_read = user_data;
+
+	if (g_atomic_int_dec_and_test(&long_read->ref) == FALSE)
+		return;
+
+	if (long_read->buffer != NULL)
+		g_free(long_read->buffer);
+
+	g_free(long_read);
+}
+
+static void read_blob_helper(guint8 status, const guint8 *rpdu, guint16 rlen,
+							gpointer user_data)
+{
+	struct read_long_data *long_read = user_data;
+	uint8_t pdu[ATT_DEFAULT_LE_MTU];
+	guint8 *tmp;
+	guint16 plen;
+	guint id;
+
+	if (status != 0 || rlen == 1) {
+		status = 0;
+		goto done;
+	}
+
+	tmp = g_try_realloc(long_read->buffer, long_read->size + rlen - 1);
+
+	if (tmp == NULL) {
+		status = ATT_ECODE_INSUFF_RESOURCES;
+		goto done;
+	}
+
+	memcpy(&tmp[long_read->size], &rpdu[1], rlen - 1);
+	long_read->buffer = tmp;
+	long_read->size += rlen - 1;
+
+	if (rlen < ATT_DEFAULT_LE_MTU)
+		goto done;
+
+	plen = enc_read_blob_req(long_read->handle, long_read->size - 1,
+							pdu, sizeof(pdu));
+	id = g_attrib_send(long_read->attrib, long_read->id,
+				ATT_OP_READ_BLOB_REQ, pdu, plen,
+				read_blob_helper, long_read, read_long_destroy);
+
+	if (id != 0) {
+		g_atomic_int_inc(&long_read->ref);
+		return;
+	}
+
+	status = ATT_ECODE_IO;
+
+done:
+	long_read->func(status, long_read->buffer, long_read->size,
+							long_read->user_data);
+}
+
+static void read_char_helper(guint8 status, const guint8 *rpdu,
+					guint16 rlen, gpointer user_data)
+{
+	struct read_long_data *long_read = user_data;
+	uint8_t pdu[ATT_DEFAULT_LE_MTU];
+	guint16 plen;
+	guint id;
+
+	if (status != 0 || rlen < ATT_DEFAULT_LE_MTU)
+		goto done;
+
+	long_read->buffer = g_malloc(rlen);
+
+	if (long_read->buffer == NULL)
+		goto done;
+
+	memcpy(long_read->buffer, rpdu, rlen);
+	long_read->size = rlen;
+
+	plen = enc_read_blob_req(long_read->handle, rlen - 1, pdu, sizeof(pdu));
+	id = g_attrib_send(long_read->attrib, long_read->id,
+			ATT_OP_READ_BLOB_REQ, pdu, plen, read_blob_helper,
+			long_read, read_long_destroy);
+
+	if (id != 0) {
+		g_atomic_int_inc(&long_read->ref);
+		return;
+	}
+
+	status = ATT_ECODE_IO;
+
+done:
+	long_read->func(status, rpdu, rlen, long_read->user_data);
+}
+
+guint gatt_read_char(GAttrib *attrib, uint16_t handle, GAttribResultFunc func,
+							gpointer user_data)
+{
+	uint8_t pdu[ATT_DEFAULT_LE_MTU];
+	guint16 plen;
+	guint id;
+	struct read_long_data *long_read;
+
+	long_read = g_try_new0(struct read_long_data, 1);
+
+	if (long_read == NULL)
+		return 0;
+
+	long_read->attrib = attrib;
+	long_read->func = func;
+	long_read->user_data = user_data;
+	long_read->handle = handle;
+
+	plen = enc_read_req(handle, pdu, sizeof(pdu));
+	id = g_attrib_send(attrib, 0, ATT_OP_READ_REQ, pdu, plen,
+				read_char_helper, long_read, read_long_destroy);
+
+	if (id == 0)
+		g_free(long_read);
+	else {
+		g_atomic_int_inc(&long_read->ref);
+		long_read->id = id;
+	}
+
+	return id;
+}
+
+guint gatt_write_char(GAttrib *attrib, uint16_t handle, uint8_t *value,
+			int vlen, GAttribResultFunc func, gpointer user_data)
+{
+	uint8_t pdu[ATT_DEFAULT_LE_MTU];
+	guint16 plen;
+
+	plen = enc_write_req(handle, value, vlen, pdu, sizeof(pdu));
+	return g_attrib_send(attrib, 0, ATT_OP_WRITE_REQ, pdu, plen, func,
+							user_data, NULL);
+}
+
+guint gatt_find_info(GAttrib *attrib, uint16_t start, uint16_t end,
+				GAttribResultFunc func, gpointer user_data)
+{
+	uint8_t pdu[ATT_DEFAULT_LE_MTU];
+	guint16 plen;
+
+	plen = enc_find_info_req(start, end, pdu, sizeof(pdu));
+	if (plen == 0)
+		return 0;
+
+	return g_attrib_send(attrib, 0, ATT_OP_FIND_INFO_REQ, pdu, plen, func,
+							user_data, NULL);
+}
+
+guint gatt_write_cmd(GAttrib *attrib, uint16_t handle, uint8_t *value, int vlen,
+				GDestroyNotify notify, gpointer user_data)
+{
+	uint8_t pdu[ATT_DEFAULT_LE_MTU];
+	guint16 plen;
+
+	plen = enc_write_cmd(handle, value, vlen, pdu, sizeof(pdu));
+	return g_attrib_send(attrib, 0, ATT_OP_WRITE_CMD, pdu, plen, NULL,
+							user_data, notify);
+}
diff --git a/attrib/gatt.h b/attrib/gatt.h
new file mode 100644
index 0000000..9f69646
--- /dev/null
+++ b/attrib/gatt.h
@@ -0,0 +1,49 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#define GATT_CID 4
+
+typedef void (*gatt_cb_t) (GSList *l, guint8 status, gpointer user_data);
+
+guint gatt_discover_primary(GAttrib *attrib, uuid_t *uuid, gatt_cb_t func,
+							gpointer user_data);
+
+guint gatt_discover_char(GAttrib *attrib, uint16_t start, uint16_t end,
+					gatt_cb_t func, gpointer user_data);
+
+guint gatt_read_char(GAttrib *attrib, uint16_t handle, GAttribResultFunc func,
+							gpointer user_data);
+
+guint gatt_write_char(GAttrib *attrib, uint16_t handle, uint8_t *value,
+			int vlen, GAttribResultFunc func, gpointer user_data);
+
+guint gatt_find_info(GAttrib *attrib, uint16_t start, uint16_t end,
+				GAttribResultFunc func, gpointer user_data);
+
+guint gatt_write_cmd(GAttrib *attrib, uint16_t handle, uint8_t *value, int vlen,
+				GDestroyNotify notify, gpointer user_data);
+
+guint gatt_read_char_by_uuid(GAttrib *attrib, uint16_t start, uint16_t end,
+				uuid_t *uuid, GAttribResultFunc func,
+				gpointer user_data);
diff --git a/attrib/gattrib.c b/attrib/gattrib.c
new file mode 100644
index 0000000..779a471
--- /dev/null
+++ b/attrib/gattrib.c
@@ -0,0 +1,565 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <glib.h>
+
+#include <stdio.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "att.h"
+#include "btio.h"
+#include "gattrib.h"
+
+struct _GAttrib {
+	GIOChannel *io;
+	gint refs;
+	gint mtu;
+	guint read_watch;
+	guint write_watch;
+	GQueue *queue;
+	GSList *events;
+	guint next_cmd_id;
+	guint next_evt_id;
+	GDestroyNotify destroy;
+	GAttribDisconnectFunc disconnect;
+	gpointer destroy_user_data;
+	gpointer disc_user_data;
+};
+
+struct command {
+	guint id;
+	guint8 opcode;
+	guint8 *pdu;
+	guint16 len;
+	guint8 expected;
+	gboolean sent;
+	GAttribResultFunc func;
+	gpointer user_data;
+	GDestroyNotify notify;
+};
+
+struct event {
+	guint id;
+	guint8 expected;
+	GAttribNotifyFunc func;
+	gpointer user_data;
+	GDestroyNotify notify;
+};
+
+static guint8 opcode2expected(guint8 opcode)
+{
+	switch (opcode) {
+	case ATT_OP_MTU_REQ:
+		return ATT_OP_MTU_RESP;
+
+	case ATT_OP_FIND_INFO_REQ:
+		return ATT_OP_FIND_INFO_RESP;
+
+	case ATT_OP_FIND_BY_TYPE_REQ:
+		return ATT_OP_FIND_BY_TYPE_RESP;
+
+	case ATT_OP_READ_BY_TYPE_REQ:
+		return ATT_OP_READ_BY_TYPE_RESP;
+
+	case ATT_OP_READ_REQ:
+		return ATT_OP_READ_RESP;
+
+	case ATT_OP_READ_BLOB_REQ:
+		return ATT_OP_READ_BLOB_RESP;
+
+	case ATT_OP_READ_MULTI_REQ:
+		return ATT_OP_READ_MULTI_RESP;
+
+	case ATT_OP_READ_BY_GROUP_REQ:
+		return ATT_OP_READ_BY_GROUP_RESP;
+
+	case ATT_OP_WRITE_REQ:
+		return ATT_OP_WRITE_RESP;
+
+	case ATT_OP_PREP_WRITE_REQ:
+		return ATT_OP_PREP_WRITE_RESP;
+
+	case ATT_OP_EXEC_WRITE_REQ:
+		return ATT_OP_EXEC_WRITE_RESP;
+
+	case ATT_OP_HANDLE_IND:
+		return ATT_OP_HANDLE_CNF;
+	}
+
+	return 0;
+}
+
+static gboolean is_response(guint8 opcode)
+{
+	switch (opcode) {
+	case ATT_OP_ERROR:
+	case ATT_OP_MTU_RESP:
+	case ATT_OP_FIND_INFO_RESP:
+	case ATT_OP_FIND_BY_TYPE_RESP:
+	case ATT_OP_READ_BY_TYPE_RESP:
+	case ATT_OP_READ_RESP:
+	case ATT_OP_READ_BLOB_RESP:
+	case ATT_OP_READ_MULTI_RESP:
+	case ATT_OP_READ_BY_GROUP_RESP:
+	case ATT_OP_WRITE_RESP:
+	case ATT_OP_PREP_WRITE_RESP:
+	case ATT_OP_EXEC_WRITE_RESP:
+	case ATT_OP_HANDLE_CNF:
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+GAttrib *g_attrib_ref(GAttrib *attrib)
+{
+	if (!attrib)
+		return NULL;
+
+	g_atomic_int_inc(&attrib->refs);
+
+	return attrib;
+}
+
+static void command_destroy(struct command *cmd)
+{
+	if (cmd->notify)
+		cmd->notify(cmd->user_data);
+
+	g_free(cmd->pdu);
+	g_free(cmd);
+}
+
+static void event_destroy(struct event *evt)
+{
+	if (evt->notify)
+		evt->notify(evt->user_data);
+
+	g_free(evt);
+}
+
+void g_attrib_unref(GAttrib *attrib)
+{
+	GSList *l;
+	struct command *c;
+
+	if (!attrib)
+		return;
+
+	if (g_atomic_int_dec_and_test(&attrib->refs) == FALSE)
+		return;
+
+	while ((c = g_queue_pop_head(attrib->queue)))
+		command_destroy(c);
+
+	g_queue_free(attrib->queue);
+	attrib->queue = NULL;
+
+	for (l = attrib->events; l; l = l->next)
+		event_destroy(l->data);
+
+	g_slist_free(attrib->events);
+	attrib->events = NULL;
+
+	if (attrib->write_watch > 0)
+		g_source_remove(attrib->write_watch);
+
+	if (attrib->read_watch > 0) {
+		g_source_remove(attrib->read_watch);
+		g_io_channel_unref(attrib->io);
+	}
+
+
+	if (attrib->destroy)
+		attrib->destroy(attrib->destroy_user_data);
+
+	g_free(attrib);
+}
+
+GIOChannel *g_attrib_get_channel(GAttrib *attrib)
+{
+	if (!attrib)
+		return NULL;
+
+	return attrib->io;
+}
+
+gboolean g_attrib_set_disconnect_function(GAttrib *attrib,
+		GAttribDisconnectFunc disconnect, gpointer user_data)
+{
+	if (attrib == NULL)
+		return FALSE;
+
+	attrib->disconnect = disconnect;
+	attrib->disc_user_data = user_data;
+
+	return TRUE;
+}
+
+gboolean g_attrib_set_destroy_function(GAttrib *attrib,
+		GDestroyNotify destroy, gpointer user_data)
+{
+	if (attrib == NULL)
+		return FALSE;
+
+	attrib->destroy = destroy;
+	attrib->destroy_user_data = user_data;
+
+	return TRUE;
+}
+
+static gboolean can_write_data(GIOChannel *io, GIOCondition cond,
+								gpointer data)
+{
+	struct _GAttrib *attrib = data;
+	struct command *cmd;
+	GError *gerr = NULL;
+	gsize len;
+	GIOStatus iostat;
+
+	if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
+		if (attrib->disconnect)
+			attrib->disconnect(attrib->disc_user_data);
+
+		return FALSE;
+	}
+
+	cmd = g_queue_peek_head(attrib->queue);
+	if (cmd == NULL)
+		return FALSE;
+
+	iostat = g_io_channel_write_chars(io, (gchar *) cmd->pdu, cmd->len,
+								&len, &gerr);
+	if (iostat != G_IO_STATUS_NORMAL)
+		return FALSE;
+
+	if (cmd->expected == 0) {
+		g_queue_pop_head(attrib->queue);
+		command_destroy(cmd);
+
+		return TRUE;
+	}
+
+	cmd->sent = TRUE;
+
+	return FALSE;
+}
+
+static void destroy_sender(gpointer data)
+{
+	struct _GAttrib *attrib = data;
+
+	attrib->write_watch = 0;
+}
+
+static void wake_up_sender(struct _GAttrib *attrib)
+{
+	if (attrib->write_watch == 0)
+		attrib->write_watch = g_io_add_watch_full(attrib->io,
+			G_PRIORITY_DEFAULT, G_IO_OUT, can_write_data,
+			attrib, destroy_sender);
+}
+
+static gboolean received_data(GIOChannel *io, GIOCondition cond, gpointer data)
+{
+	struct _GAttrib *attrib = data;
+	struct command *cmd = NULL;
+	GSList *l;
+	uint8_t buf[512], status;
+	gsize len;
+	GIOStatus iostat;
+	gboolean qempty;
+
+	if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
+		attrib->read_watch = 0;
+		if (attrib->disconnect)
+			attrib->disconnect(attrib->disc_user_data);
+		return FALSE;
+	}
+
+	memset(buf, 0, sizeof(buf));
+
+	iostat = g_io_channel_read_chars(io, (gchar *) buf, sizeof(buf),
+								&len, NULL);
+	if (iostat != G_IO_STATUS_NORMAL) {
+		status = ATT_ECODE_IO;
+		goto done;
+	}
+
+	for (l = attrib->events; l; l = l->next) {
+		struct event *evt = l->data;
+
+		if (evt->expected == buf[0] ||
+					evt->expected == GATTRIB_ALL_EVENTS)
+			evt->func(buf, len, evt->user_data);
+	}
+
+	if (is_response(buf[0]) == FALSE)
+		return TRUE;
+
+	cmd = g_queue_pop_head(attrib->queue);
+	if (cmd == NULL) {
+		/* Keep the watch if we have events to report */
+		return attrib->events != NULL;
+	}
+
+	if (buf[0] == ATT_OP_ERROR) {
+		status = buf[4];
+		goto done;
+	}
+
+	if (cmd->expected != buf[0]) {
+		status = ATT_ECODE_IO;
+		goto done;
+	}
+
+	status = 0;
+
+done:
+	qempty = attrib->queue == NULL || g_queue_is_empty(attrib->queue);
+
+	if (cmd) {
+		if (cmd->func)
+			cmd->func(status, buf, len, cmd->user_data);
+
+		command_destroy(cmd);
+	}
+
+	if (!qempty)
+		wake_up_sender(attrib);
+
+	return TRUE;
+}
+
+GAttrib *g_attrib_new(GIOChannel *io)
+{
+	struct _GAttrib *attrib;
+
+	g_io_channel_set_encoding(io, NULL, NULL);
+	g_io_channel_set_buffered(io, FALSE);
+
+	attrib = g_try_new0(struct _GAttrib, 1);
+	if (attrib == NULL)
+		return NULL;
+
+	attrib->io = g_io_channel_ref(io);
+	attrib->mtu = 512;
+	attrib->queue = g_queue_new();
+
+	attrib->read_watch = g_io_add_watch(attrib->io,
+			G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+			received_data, attrib);
+
+	return g_attrib_ref(attrib);
+}
+
+guint g_attrib_send(GAttrib *attrib, guint id, guint8 opcode,
+			const guint8 *pdu, guint16 len, GAttribResultFunc func,
+			gpointer user_data, GDestroyNotify notify)
+{
+	struct command *c;
+
+	c = g_try_new0(struct command, 1);
+	if (c == NULL)
+		return 0;
+
+	c->opcode = opcode;
+	c->expected = opcode2expected(opcode);
+	c->pdu = g_malloc(len);
+	memcpy(c->pdu, pdu, len);
+	c->len = len;
+	c->func = func;
+	c->user_data = user_data;
+	c->notify = notify;
+
+	if (id) {
+		c->id = id;
+		g_queue_push_head(attrib->queue, c);
+	} else {
+		c->id = ++attrib->next_cmd_id;
+		g_queue_push_tail(attrib->queue, c);
+	}
+
+	if (g_queue_get_length(attrib->queue) == 1)
+		wake_up_sender(attrib);
+
+	return c->id;
+}
+
+static gint command_cmp_by_id(gconstpointer a, gconstpointer b)
+{
+	const struct command *cmd = a;
+	guint id = GPOINTER_TO_UINT(b);
+
+	return cmd->id - id;
+}
+
+gboolean g_attrib_cancel(GAttrib *attrib, guint id)
+{
+	GList *l;
+	struct command *cmd;
+
+	if (attrib == NULL || attrib->queue == NULL)
+		return FALSE;
+
+	l = g_queue_find_custom(attrib->queue, GUINT_TO_POINTER(id),
+							command_cmp_by_id);
+	if (l == NULL)
+		return FALSE;
+
+	cmd = l->data;
+
+	if (cmd == g_queue_peek_head(attrib->queue) && cmd->sent)
+		cmd->func = NULL;
+	else {
+		g_queue_remove(attrib->queue, cmd);
+		command_destroy(cmd);
+	}
+
+	return TRUE;
+}
+
+gboolean g_attrib_cancel_all(GAttrib *attrib)
+{
+	struct command *c, *head = NULL;
+	gboolean first = TRUE;
+
+	if (attrib == NULL || attrib->queue == NULL)
+		return FALSE;
+
+	while ((c = g_queue_pop_head(attrib->queue))) {
+		if (first && c->sent) {
+			/* If the command was sent ignore its callback ... */
+			c->func = NULL;
+			head = c;
+			continue;
+		}
+
+		first = FALSE;
+		command_destroy(c);
+	}
+
+	if (head) {
+		/* ... and put it back in the queue */
+		g_queue_push_head(attrib->queue, head);
+	}
+
+	return TRUE;
+}
+
+gboolean g_attrib_set_debug(GAttrib *attrib,
+		GAttribDebugFunc func, gpointer user_data)
+{
+	return TRUE;
+}
+
+guint g_attrib_register(GAttrib *attrib, guint8 opcode,
+				GAttribNotifyFunc func, gpointer user_data,
+				GDestroyNotify notify)
+{
+	struct event *event;
+
+	event = g_try_new0(struct event, 1);
+	if (event == NULL)
+		return 0;
+
+	event->expected = opcode;
+	event->func = func;
+	event->user_data = user_data;
+	event->notify = notify;
+	event->id = ++attrib->next_evt_id;
+
+	attrib->events = g_slist_append(attrib->events, event);
+
+	return event->id;
+}
+
+static gint event_cmp_by_id(gconstpointer a, gconstpointer b)
+{
+	const struct event *evt = a;
+	guint id = GPOINTER_TO_UINT(b);
+
+	return evt->id - id;
+}
+
+gboolean g_attrib_is_encrypted(GAttrib *attrib)
+{
+	BtIOSecLevel sec_level;
+
+	if (!bt_io_get(attrib->io, BT_IO_L2CAP, NULL,
+			BT_IO_OPT_SEC_LEVEL, &sec_level,
+			BT_IO_OPT_INVALID))
+		return FALSE;
+
+	return sec_level > BT_IO_SEC_LOW;
+}
+
+gboolean g_attrib_unregister(GAttrib *attrib, guint id)
+{
+	struct event *evt;
+	GSList *l;
+
+	l = g_slist_find_custom(attrib->events, GUINT_TO_POINTER(id),
+							event_cmp_by_id);
+	if (l == NULL)
+		return FALSE;
+
+	evt = l->data;
+
+	attrib->events = g_slist_remove(attrib->events, evt);
+
+	if (evt->notify)
+		evt->notify(evt->user_data);
+
+	g_free(evt);
+
+	return TRUE;
+}
+
+gboolean g_attrib_unregister_all(GAttrib *attrib)
+{
+	GSList *l;
+
+	if (attrib->events == NULL)
+		return FALSE;
+
+	for (l = attrib->events; l; l = l->next) {
+		struct event *evt = l->data;
+
+		if (evt->notify)
+			evt->notify(evt->user_data);
+
+		g_free(evt);
+	}
+
+	g_slist_free(attrib->events);
+	attrib->events = NULL;
+
+	return TRUE;
+}
diff --git a/attrib/gattrib.h b/attrib/gattrib.h
new file mode 100644
index 0000000..f25208d
--- /dev/null
+++ b/attrib/gattrib.h
@@ -0,0 +1,77 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+#ifndef __GATTRIB_H
+#define __GATTRIB_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define GATTRIB_ALL_EVENTS 0xFF
+
+struct _GAttrib;
+typedef struct _GAttrib GAttrib;
+
+typedef void (*GAttribResultFunc) (guint8 status, const guint8 *pdu,
+					guint16 len, gpointer user_data);
+typedef void (*GAttribDisconnectFunc)(gpointer user_data);
+typedef void (*GAttribDebugFunc)(const char *str, gpointer user_data);
+typedef void (*GAttribNotifyFunc)(const guint8 *pdu, guint16 len,
+							gpointer user_data);
+
+GAttrib *g_attrib_new(GIOChannel *io);
+GAttrib *g_attrib_ref(GAttrib *attrib);
+void g_attrib_unref(GAttrib *attrib);
+
+GIOChannel *g_attrib_get_channel(GAttrib *attrib);
+
+gboolean g_attrib_set_disconnect_function(GAttrib *attrib,
+		GAttribDisconnectFunc disconnect, gpointer user_data);
+
+gboolean g_attrib_set_destroy_function(GAttrib *attrib,
+		GDestroyNotify destroy, gpointer user_data);
+
+guint g_attrib_send(GAttrib *attrib, guint id, guint8 opcode,
+			const guint8 *pdu, guint16 len, GAttribResultFunc func,
+			gpointer user_data, GDestroyNotify notify);
+
+gboolean g_attrib_cancel(GAttrib *attrib, guint id);
+gboolean g_attrib_cancel_all(GAttrib *attrib);
+
+gboolean g_attrib_set_debug(GAttrib *attrib,
+		GAttribDebugFunc func, gpointer user_data);
+
+guint g_attrib_register(GAttrib *attrib, guint8 opcode,
+		GAttribNotifyFunc func, gpointer user_data,
+					GDestroyNotify notify);
+
+gboolean g_attrib_is_encrypted(GAttrib *attrib);
+
+gboolean g_attrib_unregister(GAttrib *attrib, guint id);
+gboolean g_attrib_unregister_all(GAttrib *attrib);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/attrib/gatttool.c b/attrib/gatttool.c
new file mode 100644
index 0000000..7478043
--- /dev/null
+++ b/attrib/gatttool.c
@@ -0,0 +1,651 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <glib.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "att.h"
+#include "btio.h"
+#include "gattrib.h"
+#include "glib-helper.h"
+#include "gatt.h"
+#include "gatttool.h"
+
+static gchar *opt_src = NULL;
+static gchar *opt_dst = NULL;
+static gchar *opt_value = NULL;
+static gchar *opt_sec_level = NULL;
+static uuid_t *opt_uuid = NULL;
+static int opt_start = 0x0001;
+static int opt_end = 0xffff;
+static int opt_handle = -1;
+static int opt_mtu = 0;
+static int opt_psm = 0;
+static gboolean opt_primary = FALSE;
+static gboolean opt_characteristics = FALSE;
+static gboolean opt_char_read = FALSE;
+static gboolean opt_listen = FALSE;
+static gboolean opt_char_desc = FALSE;
+static gboolean opt_char_write = FALSE;
+static gboolean opt_char_write_req = FALSE;
+static gboolean opt_interactive = FALSE;
+static GMainLoop *event_loop;
+static gboolean got_error = FALSE;
+
+struct characteristic_data {
+	GAttrib *attrib;
+	uint16_t start;
+	uint16_t end;
+};
+
+static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
+{
+	if (err) {
+		g_printerr("%s\n", err->message);
+		got_error = TRUE;
+		g_main_loop_quit(event_loop);
+	}
+}
+
+static void primary_all_cb(GSList *services, guint8 status, gpointer user_data)
+{
+	GSList *l;
+
+	if (status) {
+		g_printerr("Discover all primary services failed: %s\n",
+							att_ecode2str(status));
+		goto done;
+	}
+
+	for (l = services; l; l = l->next) {
+		struct att_primary *prim = l->data;
+		g_print("attr handle = 0x%04x, end grp handle = 0x%04x "
+			"uuid: %s\n", prim->start, prim->end, prim->uuid);
+	}
+
+done:
+	g_main_loop_quit(event_loop);
+}
+
+static void primary_by_uuid_cb(GSList *ranges, guint8 status,
+							gpointer user_data)
+{
+	GSList *l;
+
+	if (status != 0) {
+		g_printerr("Discover primary services by UUID failed: %s\n",
+							att_ecode2str(status));
+		goto done;
+	}
+
+	for (l = ranges; l; l = l->next) {
+		struct att_range *range = l->data;
+		g_print("Starting handle: %04x Ending handle: %04x\n",
+						range->start, range->end);
+	}
+
+done:
+	g_main_loop_quit(event_loop);
+}
+
+static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
+{
+	GAttrib *attrib = user_data;
+	uint8_t opdu[ATT_MAX_MTU];
+	uint16_t handle, i, olen = 0;
+
+	handle = att_get_u16(&pdu[1]);
+
+	switch (pdu[0]) {
+	case ATT_OP_HANDLE_NOTIFY:
+		g_print("Notification handle = 0x%04x value: ", handle);
+		break;
+	case ATT_OP_HANDLE_IND:
+		g_print("Indication   handle = 0x%04x value: ", handle);
+		break;
+	default:
+		g_print("Invalid opcode\n");
+		return;
+	}
+
+	for (i = 3; i < len; i++)
+		g_print("%02x ", pdu[i]);
+
+	g_print("\n");
+
+	if (pdu[0] == ATT_OP_HANDLE_NOTIFY)
+		return;
+
+	olen = enc_confirmation(opdu, sizeof(opdu));
+
+	if (olen > 0)
+		g_attrib_send(attrib, 0, opdu[0], opdu, olen, NULL, NULL, NULL);
+}
+
+static gboolean listen_start(gpointer user_data)
+{
+	GAttrib *attrib = user_data;
+
+	g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, events_handler,
+							attrib, NULL);
+	g_attrib_register(attrib, ATT_OP_HANDLE_IND, events_handler,
+							attrib, NULL);
+
+	return FALSE;
+}
+
+static gboolean primary(gpointer user_data)
+{
+	GAttrib *attrib = user_data;
+
+	if (opt_uuid)
+		gatt_discover_primary(attrib, opt_uuid, primary_by_uuid_cb,
+									NULL);
+	else
+		gatt_discover_primary(attrib, NULL, primary_all_cb, NULL);
+
+	return FALSE;
+}
+
+static void char_discovered_cb(GSList *characteristics, guint8 status,
+							gpointer user_data)
+{
+	GSList *l;
+
+	if (status) {
+		g_printerr("Discover all characteristics failed: %s\n",
+							att_ecode2str(status));
+		goto done;
+	}
+
+	for (l = characteristics; l; l = l->next) {
+		struct att_char *chars = l->data;
+
+		g_print("handle = 0x%04x, char properties = 0x%02x, char value "
+			"handle = 0x%04x, uuid = %s\n", chars->handle,
+			chars->properties, chars->value_handle, chars->uuid);
+	}
+
+done:
+	g_main_loop_quit(event_loop);
+}
+
+static gboolean characteristics(gpointer user_data)
+{
+	GAttrib *attrib = user_data;
+
+	gatt_discover_char(attrib, opt_start, opt_end, char_discovered_cb, NULL);
+
+	return FALSE;
+}
+
+static void char_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
+							gpointer user_data)
+{
+	uint8_t value[ATT_MAX_MTU];
+	int i, vlen;
+
+	if (status != 0) {
+		g_printerr("Characteristic value/descriptor read failed: %s\n",
+							att_ecode2str(status));
+		goto done;
+	}
+	if (!dec_read_resp(pdu, plen, value, &vlen)) {
+		g_printerr("Protocol error\n");
+		goto done;
+	}
+	g_print("Characteristic value/descriptor: ");
+	for (i = 0; i < vlen; i++)
+		g_print("%02x ", value[i]);
+	g_print("\n");
+
+done:
+	if (opt_listen == FALSE)
+		g_main_loop_quit(event_loop);
+}
+
+static void char_read_by_uuid_cb(guint8 status, const guint8 *pdu,
+					guint16 plen, gpointer user_data)
+{
+	struct characteristic_data *char_data = user_data;
+	struct att_data_list *list;
+	int i;
+
+	if (status == ATT_ECODE_ATTR_NOT_FOUND &&
+					char_data->start != opt_start)
+		goto done;
+
+	if (status != 0) {
+		g_printerr("Read characteristics by UUID failed: %s\n",
+							att_ecode2str(status));
+		goto done;
+	}
+
+	list = dec_read_by_type_resp(pdu, plen);
+	if (list == NULL)
+		goto done;
+
+	for (i = 0; i < list->num; i++) {
+		uint8_t *value = list->data[i];
+		int j;
+
+		char_data->start = att_get_u16(value) + 1;
+
+		g_print("handle: 0x%04x \t value: ", att_get_u16(value));
+		value += 2;
+		for (j = 0; j < list->len - 2; j++, value++)
+			g_print("%02x ", *value);
+		g_print("\n");
+	}
+
+	att_data_list_free(list);
+
+	gatt_read_char_by_uuid(char_data->attrib, char_data->start,
+					char_data->end, opt_uuid,
+					char_read_by_uuid_cb,
+					char_data);
+
+	return;
+done:
+	g_free(char_data);
+	g_main_loop_quit(event_loop);
+}
+
+static gboolean characteristics_read(gpointer user_data)
+{
+	GAttrib *attrib = user_data;
+
+	if (opt_uuid != NULL) {
+		struct characteristic_data *char_data;
+
+		char_data = g_new(struct characteristic_data, 1);
+		char_data->attrib = attrib;
+		char_data->start = opt_start;
+		char_data->end = opt_end;
+
+		gatt_read_char_by_uuid(attrib, opt_start, opt_end, opt_uuid,
+						char_read_by_uuid_cb, char_data);
+
+		return FALSE;
+	}
+
+	if (opt_handle <= 0) {
+		g_printerr("A valid handle is required\n");
+		g_main_loop_quit(event_loop);
+		return FALSE;
+	}
+
+	gatt_read_char(attrib, opt_handle, char_read_cb, attrib);
+
+	return FALSE;
+}
+
+static size_t attr_data_from_string(const char *str, uint8_t **data)
+{
+	char tmp[3];
+	size_t size, i;
+
+	size = strlen(str) / 2;
+	*data = g_try_malloc0(size);
+	if (*data == NULL)
+		return 0;
+
+	tmp[2] = '\0';
+	for (i = 0; i < size; i++) {
+		memcpy(tmp, str + (i * 2), 2);
+		(*data)[i] = (uint8_t) strtol(tmp, NULL, 16);
+	}
+
+	return size;
+}
+
+static void mainloop_quit(gpointer user_data)
+{
+	uint8_t *value = user_data;
+
+	g_free(value);
+	g_main_loop_quit(event_loop);
+}
+
+static gboolean characteristics_write(gpointer user_data)
+{
+	GAttrib *attrib = user_data;
+	uint8_t *value;
+	size_t len;
+
+	if (opt_handle <= 0) {
+		g_printerr("A valid handle is required\n");
+		goto error;
+	}
+
+	if (opt_value == NULL || opt_value[0] == '\0') {
+		g_printerr("A value is required\n");
+		goto error;
+	}
+
+	len = attr_data_from_string(opt_value, &value);
+	if (len == 0) {
+		g_printerr("Invalid value\n");
+		goto error;
+	}
+
+	gatt_write_cmd(attrib, opt_handle, value, len, mainloop_quit, value);
+
+	return FALSE;
+
+error:
+	g_main_loop_quit(event_loop);
+	return FALSE;
+}
+
+static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen,
+							gpointer user_data)
+{
+	if (status != 0) {
+		g_printerr("Characteristic Write Request failed: "
+						"%s\n", att_ecode2str(status));
+		goto done;
+	}
+
+	if (!dec_write_resp(pdu, plen)) {
+		g_printerr("Protocol error\n");
+		goto done;
+	}
+
+	g_print("Characteristic value was written sucessfully\n");
+
+done:
+	if (opt_listen == FALSE)
+		g_main_loop_quit(event_loop);
+}
+
+static gboolean characteristics_write_req(gpointer user_data)
+{
+	GAttrib *attrib = user_data;
+	uint8_t *value;
+	size_t len;
+
+	if (opt_handle <= 0) {
+		g_printerr("A valid handle is required\n");
+		goto error;
+	}
+
+	if (opt_value == NULL || opt_value[0] == '\0') {
+		g_printerr("A value is required\n");
+		goto error;
+	}
+
+	len = attr_data_from_string(opt_value, &value);
+	if (len == 0) {
+		g_printerr("Invalid value\n");
+		goto error;
+	}
+
+	gatt_write_char(attrib, opt_handle, value, len, char_write_req_cb,
+									NULL);
+
+	return FALSE;
+
+error:
+	g_main_loop_quit(event_loop);
+	return FALSE;
+}
+
+static void char_desc_cb(guint8 status, const guint8 *pdu, guint16 plen,
+							gpointer user_data)
+{
+	struct att_data_list *list;
+	guint8 format;
+	int i;
+
+	if (status != 0) {
+		g_printerr("Discover all characteristic descriptors failed: "
+						"%s\n", att_ecode2str(status));
+		goto done;
+	}
+
+	list = dec_find_info_resp(pdu, plen, &format);
+	if (list == NULL)
+		goto done;
+
+	for (i = 0; i < list->num; i++) {
+		char uuidstr[MAX_LEN_UUID_STR];
+		uint16_t handle;
+		uint8_t *value;
+		uuid_t uuid;
+
+		value = list->data[i];
+		handle = att_get_u16(value);
+
+		if (format == 0x01)
+			sdp_uuid16_create(&uuid, att_get_u16(&value[2]));
+		else
+			sdp_uuid128_create(&uuid, &value[2]);
+
+		sdp_uuid2strn(&uuid, uuidstr, MAX_LEN_UUID_STR);
+		g_print("handle = 0x%04x, uuid = %s\n", handle, uuidstr);
+	}
+
+	att_data_list_free(list);
+
+done:
+	if (opt_listen == FALSE)
+		g_main_loop_quit(event_loop);
+}
+
+static gboolean characteristics_desc(gpointer user_data)
+{
+	GAttrib *attrib = user_data;
+
+	gatt_find_info(attrib, opt_start, opt_end, char_desc_cb, NULL);
+
+	return FALSE;
+}
+
+static gboolean parse_uuid(const char *key, const char *value,
+				gpointer user_data, GError **error)
+{
+	if (!value)
+		return FALSE;
+
+	opt_uuid = g_try_malloc(sizeof(uuid_t));
+	if (opt_uuid == NULL)
+		return FALSE;
+
+	if (bt_string2uuid(opt_uuid, value) < 0)
+		return FALSE;
+
+	return TRUE;
+}
+
+static GOptionEntry primary_char_options[] = {
+	{ "start", 's' , 0, G_OPTION_ARG_INT, &opt_start,
+		"Starting handle(optional)", "0x0001" },
+	{ "end", 'e' , 0, G_OPTION_ARG_INT, &opt_end,
+		"Ending handle(optional)", "0xffff" },
+	{ "uuid", 'u', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
+		parse_uuid, "UUID16 or UUID128(optional)", "0x1801"},
+	{ NULL },
+};
+
+static GOptionEntry char_rw_options[] = {
+	{ "handle", 'a' , 0, G_OPTION_ARG_INT, &opt_handle,
+		"Read/Write characteristic by handle(required)", "0x0001" },
+	{ "value", 'n' , 0, G_OPTION_ARG_STRING, &opt_value,
+		"Write characteristic value (required for write operation)",
+		"0x0001" },
+	{NULL},
+};
+
+static GOptionEntry gatt_options[] = {
+	{ "primary", 0, 0, G_OPTION_ARG_NONE, &opt_primary,
+		"Primary Service Discovery", NULL },
+	{ "characteristics", 0, 0, G_OPTION_ARG_NONE, &opt_characteristics,
+		"Characteristics Discovery", NULL },
+	{ "char-read", 0, 0, G_OPTION_ARG_NONE, &opt_char_read,
+		"Characteristics Value/Descriptor Read", NULL },
+	{ "char-write", 0, 0, G_OPTION_ARG_NONE, &opt_char_write,
+		"Characteristics Value Write Without Response (Write Command)",
+		NULL },
+	{ "char-write-req", 0, 0, G_OPTION_ARG_NONE, &opt_char_write_req,
+		"Characteristics Value Write (Write Request)", NULL },
+	{ "char-desc", 0, 0, G_OPTION_ARG_NONE, &opt_char_desc,
+		"Characteristics Descriptor Discovery", NULL },
+	{ "listen", 0, 0, G_OPTION_ARG_NONE, &opt_listen,
+		"Listen for notifications and indications", NULL },
+	{ "interactive", 'I', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE,
+		&opt_interactive, "Use interactive mode", NULL },
+	{ NULL },
+};
+
+static GOptionEntry options[] = {
+	{ "adapter", 'i', 0, G_OPTION_ARG_STRING, &opt_src,
+		"Specify local adapter interface", "hciX" },
+	{ "device", 'b', 0, G_OPTION_ARG_STRING, &opt_dst,
+		"Specify remote Bluetooth address", "MAC" },
+	{ "mtu", 'm', 0, G_OPTION_ARG_INT, &opt_mtu,
+		"Specify the MTU size", "MTU" },
+	{ "psm", 'p', 0, G_OPTION_ARG_INT, &opt_psm,
+		"Specify the PSM for GATT/ATT over BR/EDR", "PSM" },
+	{ "sec-level", 'l', 0, G_OPTION_ARG_STRING, &opt_sec_level,
+		"Set security level. Default: low", "[low | medium | high]"},
+	{ NULL },
+};
+
+int main(int argc, char *argv[])
+{
+	GOptionContext *context;
+	GOptionGroup *gatt_group, *params_group, *char_rw_group;
+	GError *gerr = NULL;
+	GAttrib *attrib;
+	GIOChannel *chan;
+	GSourceFunc callback;
+
+	opt_sec_level = strdup("low");
+
+	context = g_option_context_new(NULL);
+	g_option_context_add_main_entries(context, options, NULL);
+
+	/* GATT commands */
+	gatt_group = g_option_group_new("gatt", "GATT commands",
+					"Show all GATT commands", NULL, NULL);
+	g_option_context_add_group(context, gatt_group);
+	g_option_group_add_entries(gatt_group, gatt_options);
+
+	/* Primary Services and Characteristics arguments */
+	params_group = g_option_group_new("params",
+			"Primary Services/Characteristics arguments",
+			"Show all Primary Services/Characteristics arguments",
+			NULL, NULL);
+	g_option_context_add_group(context, params_group);
+	g_option_group_add_entries(params_group, primary_char_options);
+
+	/* Characteristics value/descriptor read/write arguments */
+	char_rw_group = g_option_group_new("char-read-write",
+		"Characteristics Value/Descriptor Read/Write arguments",
+		"Show all Characteristics Value/Descriptor Read/Write "
+		"arguments",
+		NULL, NULL);
+	g_option_context_add_group(context, char_rw_group);
+	g_option_group_add_entries(char_rw_group, char_rw_options);
+
+	if (g_option_context_parse(context, &argc, &argv, &gerr) == FALSE) {
+		g_printerr("%s\n", gerr->message);
+		g_error_free(gerr);
+	}
+
+	if (opt_interactive) {
+		interactive(opt_dst, opt_psm);
+		goto done;
+	}
+
+	if (opt_primary)
+		callback = primary;
+	else if (opt_characteristics)
+		callback = characteristics;
+	else if (opt_char_read)
+		callback = characteristics_read;
+	else if (opt_char_write)
+		callback = characteristics_write;
+	else if (opt_char_write_req)
+		callback = characteristics_write_req;
+	else if (opt_char_desc)
+		callback = characteristics_desc;
+	else {
+		gchar *help = g_option_context_get_help(context, TRUE, NULL);
+		g_print("%s\n", help);
+		g_free(help);
+		got_error = TRUE;
+		goto done;
+	}
+
+	chan = gatt_connect(opt_src, opt_dst, opt_sec_level,
+					opt_psm, opt_mtu, connect_cb);
+	if (chan == NULL) {
+		got_error = TRUE;
+		goto done;
+	}
+
+	attrib = g_attrib_new(chan);
+
+	event_loop = g_main_loop_new(NULL, FALSE);
+
+	if (opt_listen)
+		g_idle_add(listen_start, attrib);
+
+	g_idle_add(callback, attrib);
+
+	g_main_loop_run(event_loop);
+
+	g_attrib_unregister_all(attrib);
+
+	g_main_loop_unref(event_loop);
+
+	g_io_channel_unref(chan);
+	g_attrib_unref(attrib);
+
+done:
+	g_option_context_free(context);
+	g_free(opt_src);
+	g_free(opt_dst);
+	g_free(opt_uuid);
+	g_free(opt_sec_level);
+
+	if (got_error)
+		exit(EXIT_FAILURE);
+	else
+		exit(EXIT_SUCCESS);
+}
diff --git a/attrib/gatttool.h b/attrib/gatttool.h
new file mode 100644
index 0000000..7eae18d
--- /dev/null
+++ b/attrib/gatttool.h
@@ -0,0 +1,27 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2011  Nokia Corporation
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+int interactive(gchar *dst, gboolean le);
+GIOChannel *gatt_connect(const gchar *src, const gchar *dst,
+			const gchar *sec_level, int psm, int mtu,
+			BtIOConnect connect_cb);
diff --git a/attrib/interactive.c b/attrib/interactive.c
new file mode 100644
index 0000000..edc465a
--- /dev/null
+++ b/attrib/interactive.c
@@ -0,0 +1,317 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2011  Nokia Corporation
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <glib.h>
+
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#include "att.h"
+#include "btio.h"
+#include "gattrib.h"
+#include "glib-helper.h"
+#include "gatt.h"
+#include "gatttool.h"
+
+static GIOChannel *iochannel = NULL;
+static GAttrib *attrib = NULL;
+static GMainLoop *event_loop;
+static GString *prompt;
+
+static gchar *opt_src = NULL;
+static gchar *opt_dst = NULL;
+static gchar *opt_sec_level = NULL;
+static int opt_psm = 0;
+static int opt_mtu = 0;
+
+static void cmd_help(int argcp, char **argvp);
+
+enum state {
+	STATE_DISCONNECTED,
+	STATE_CONNECTING,
+	STATE_CONNECTED
+} conn_state;
+
+static char *get_prompt(void)
+{
+	if (conn_state == STATE_CONNECTING) {
+		g_string_assign(prompt, "Connecting... ");
+		return prompt->str;
+	}
+
+	if (conn_state == STATE_CONNECTED)
+		g_string_assign(prompt, "[CON]");
+	else
+		g_string_assign(prompt, "[   ]");
+
+	if (opt_dst)
+		g_string_append_printf(prompt, "[%17s]", opt_dst);
+	else
+		g_string_append_printf(prompt, "[%17s]", "");
+
+	if (opt_psm)
+		g_string_append(prompt, "[BR]");
+	else
+		g_string_append(prompt, "[LE]");
+
+	g_string_append(prompt, "> ");
+
+	return prompt->str;
+}
+
+
+static void set_state(enum state st)
+{
+	conn_state = st;
+	rl_set_prompt(get_prompt());
+	rl_redisplay();
+}
+
+static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
+{
+	if (err) {
+		printf("connect error: %s\n", err->message);
+		set_state(STATE_DISCONNECTED);
+		return;
+	}
+
+	attrib = g_attrib_new(iochannel);
+	set_state(STATE_CONNECTED);
+}
+
+static void primary_all_cb(GSList *services, guint8 status, gpointer user_data)
+{
+	GSList *l;
+
+	if (status) {
+		printf("Discover all primary services failed: %s\n",
+							att_ecode2str(status));
+		return;
+	}
+
+	printf("\n");
+	for (l = services; l; l = l->next) {
+		struct att_primary *prim = l->data;
+		printf("attr handle: 0x%04x, end grp handle: 0x%04x "
+			"uuid: %s\n", prim->start, prim->end, prim->uuid);
+	}
+
+	rl_forced_update_display();
+}
+
+static void primary_by_uuid_cb(GSList *ranges, guint8 status,
+							gpointer user_data)
+{
+	GSList *l;
+
+	if (status) {
+		printf("Discover primary services by UUID failed: %s\n",
+							att_ecode2str(status));
+		return;
+	}
+
+	printf("\n");
+	for (l = ranges; l; l = l->next) {
+		struct att_range *range = l->data;
+		g_print("Starting handle: 0x%04x Ending handle: 0x%04x\n",
+						range->start, range->end);
+	}
+
+	rl_forced_update_display();
+}
+
+static void cmd_exit(int argcp, char **argvp)
+{
+	rl_callback_handler_remove();
+	g_main_loop_quit(event_loop);
+}
+
+static void cmd_connect(int argcp, char **argvp)
+{
+	if (conn_state != STATE_DISCONNECTED)
+		return;
+
+	if (argcp > 1) {
+		g_free(opt_dst);
+		opt_dst = strdup(argvp[1]);
+	}
+
+	if (opt_dst == NULL) {
+		printf("Remote Bluetooth address required\n");
+		return;
+	}
+
+	set_state(STATE_CONNECTING);
+	iochannel = gatt_connect(opt_src, opt_dst, opt_sec_level, opt_psm,
+						opt_mtu, connect_cb);
+	if (iochannel == NULL)
+		set_state(STATE_DISCONNECTED);
+
+	return;
+}
+
+static void cmd_disconnect(int argcp, char **argvp)
+{
+	if (conn_state == STATE_DISCONNECTED)
+		return;
+
+	g_attrib_unref(attrib);
+	attrib = NULL;
+
+	g_io_channel_shutdown(iochannel, FALSE, NULL);
+	g_io_channel_unref(iochannel);
+	iochannel = NULL;
+
+	set_state(STATE_DISCONNECTED);
+
+	return;
+}
+
+static void cmd_primary(int argcp, char **argvp)
+{
+	uuid_t uuid;
+
+	if (conn_state != STATE_CONNECTED) {
+		printf("Command failed: disconnected\n");
+		return;
+	}
+
+	if (argcp == 1) {
+		gatt_discover_primary(attrib, NULL, primary_all_cb, NULL);
+		return;
+	}
+
+	if (bt_string2uuid(&uuid, argvp[1]) < 0) {
+		printf("Invalid UUID\n");
+		return;
+	}
+
+	gatt_discover_primary(attrib, &uuid, primary_by_uuid_cb, NULL);
+}
+
+static struct {
+	const char *cmd;
+	void (*func)(int argcp, char **argvp);
+	const char *desc;
+} commands[] = {
+	{ "help",	cmd_help,	"Show this help"},
+	{ "exit",	cmd_exit,	"Exit interactive mode"},
+	{ "connect",	cmd_connect,	"Connect to a remote device"},
+	{ "disconnect",	cmd_disconnect,	"Disconnect from a remote device"},
+	{ "primary",	cmd_primary,	"Primary Service Discovery"},
+	{ NULL, NULL, NULL}
+};
+
+static void cmd_help(int argcp, char **argvp)
+{
+	int i;
+
+	for (i = 0; commands[i].cmd; i++)
+		printf("%-12s\t%s\n", commands[i].cmd, commands[i].desc);
+}
+
+static void parse_line(char *line_read)
+{
+	gchar **argvp;
+	int argcp;
+	int i;
+
+	if (line_read == NULL) {
+		printf("\n");
+		cmd_exit(0, NULL);
+		return;
+	}
+
+	line_read = g_strstrip(line_read);
+
+	if (*line_read == '\0')
+		return;
+
+	add_history(line_read);
+
+	g_shell_parse_argv(line_read, &argcp, &argvp, NULL);
+
+	for (i = 0; commands[i].cmd; i++)
+		if (strcasecmp(commands[i].cmd, argvp[0]) == 0)
+			break;
+
+	if (commands[i].cmd)
+		commands[i].func(argcp, argvp);
+	else
+		printf("%s: command not found\n", argvp[0]);
+
+	g_strfreev(argvp);
+}
+
+static gboolean prompt_read(GIOChannel *chan, GIOCondition cond,
+							gpointer user_data)
+{
+	if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
+		g_io_channel_unref(chan);
+		return FALSE;
+	}
+
+	rl_callback_read_char();
+
+	return TRUE;
+}
+
+int interactive(gchar *dst, int psm)
+{
+	GIOChannel *pchan;
+	gint events;
+
+	opt_sec_level = strdup("low");
+
+	opt_dst = strdup(dst);
+	opt_psm = psm;
+
+	prompt = g_string_new(NULL);
+
+	event_loop = g_main_loop_new(NULL, FALSE);
+
+	pchan = g_io_channel_unix_new(fileno(stdin));
+	g_io_channel_set_close_on_unref(pchan, TRUE);
+	events = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
+	g_io_add_watch(pchan, events, prompt_read, NULL);
+
+	rl_callback_handler_install(get_prompt(), parse_line);
+
+	g_main_loop_run(event_loop);
+
+	rl_callback_handler_remove();
+	cmd_disconnect(0, NULL);
+	g_io_channel_unref(pchan);
+	g_main_loop_unref(event_loop);
+	g_string_free(prompt, TRUE);
+
+	g_free(opt_dst);
+	g_free(opt_sec_level);
+
+	return 0;
+}
diff --git a/attrib/main.c b/attrib/main.c
new file mode 100644
index 0000000..6c946be
--- /dev/null
+++ b/attrib/main.c
@@ -0,0 +1,60 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+
+#include <gdbus.h>
+
+#include "plugin.h"
+#include "manager.h"
+
+static DBusConnection *connection;
+
+static int attrib_init(void)
+{
+	connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+	if (connection == NULL)
+		return -EIO;
+
+	if (attrib_manager_init(connection) < 0) {
+		dbus_connection_unref(connection);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static void attrib_exit(void)
+{
+	attrib_manager_exit();
+
+	dbus_connection_unref(connection);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(attrib, VERSION,
+		BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, attrib_init, attrib_exit)
diff --git a/attrib/manager.c b/attrib/manager.c
new file mode 100644
index 0000000..f991f8e
--- /dev/null
+++ b/attrib/manager.c
@@ -0,0 +1,103 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#include "manager.h"
+#include "client.h"
+#include "example.h"
+
+#define GATT_UUID	"00001801-0000-1000-8000-00805f9b34fb"
+
+static DBusConnection *connection;
+
+static int client_probe(struct btd_device *device, GSList *uuids)
+{
+	const sdp_record_t *rec;
+	int psm = -1;
+
+	rec = btd_device_get_record(device, GATT_UUID);
+	if (rec) {
+		sdp_list_t *list;
+		if (sdp_get_access_protos(rec, &list) < 0)
+			return -1;
+
+		psm = sdp_get_proto_port(list, L2CAP_UUID);
+
+		sdp_list_foreach(list, (sdp_list_func_t) sdp_list_free, NULL);
+		sdp_list_free(list, NULL);
+
+		if (psm < 0)
+			return -1;
+	}
+
+	return attrib_client_register(device, psm);
+}
+
+static void client_remove(struct btd_device *device)
+{
+	attrib_client_unregister(device);
+}
+
+static struct btd_device_driver client_driver = {
+	.name = "gatt-client",
+	.uuids = BTD_UUIDS(GATT_UUID),
+	.probe = client_probe,
+	.remove = client_remove,
+};
+
+int attrib_manager_init(DBusConnection *conn)
+{
+	connection = dbus_connection_ref(conn);
+
+	attrib_client_init(connection);
+
+	btd_register_device_driver(&client_driver);
+
+	/*
+	 * FIXME: Add config file option to allow
+	 * enable/disable the GATT server and client.
+	 */
+
+	return server_example_init();
+}
+
+void attrib_manager_exit(void)
+{
+	btd_unregister_device_driver(&client_driver);
+
+	server_example_exit();
+	attrib_client_exit();
+
+	dbus_connection_unref(connection);
+}
diff --git a/attrib/manager.h b/attrib/manager.h
new file mode 100644
index 0000000..fabf342
--- /dev/null
+++ b/attrib/manager.h
@@ -0,0 +1,26 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2010  Nokia Corporation
+ *  Copyright (C) 2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+int attrib_manager_init(DBusConnection *conn);
+void attrib_manager_exit(void);
diff --git a/attrib/utils.c b/attrib/utils.c
new file mode 100644
index 0000000..4d0000d
--- /dev/null
+++ b/attrib/utils.c
@@ -0,0 +1,106 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2011  Nokia Corporation
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdlib.h>
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+
+#include "gattrib.h"
+#include "gatt.h"
+#include "btio.h"
+#include "gatttool.h"
+
+/* Minimum MTU for ATT connections */
+#define ATT_MIN_MTU_LE		23
+#define ATT_MIN_MTU_L2CAP	48
+
+GIOChannel *gatt_connect(const gchar *src, const gchar *dst,
+				const gchar *sec_level, int psm, int mtu,
+				BtIOConnect connect_cb)
+{
+	GIOChannel *chan;
+	bdaddr_t sba, dba;
+	GError *err = NULL;
+	BtIOSecLevel sec;
+	int minimum_mtu;
+
+	/* This check is required because currently setsockopt() returns no
+	 * errors for MTU values smaller than the allowed minimum. */
+	minimum_mtu = psm ? ATT_MIN_MTU_L2CAP : ATT_MIN_MTU_LE;
+	if (mtu != 0 && mtu < minimum_mtu) {
+		g_printerr("MTU cannot be smaller than %d\n", minimum_mtu);
+		return NULL;
+	}
+
+	/* Remote device */
+	if (dst == NULL) {
+		g_printerr("Remote Bluetooth address required\n");
+		return NULL;
+	}
+	str2ba(dst, &dba);
+
+	/* Local adapter */
+	if (src != NULL) {
+		if (!strncmp(src, "hci", 3))
+			hci_devba(atoi(src + 3), &sba);
+		else
+			str2ba(src, &sba);
+	} else
+		bacpy(&sba, BDADDR_ANY);
+
+	if (strcmp(sec_level, "medium") == 0)
+		sec = BT_IO_SEC_MEDIUM;
+	else if (strcmp(sec_level, "high") == 0)
+		sec = BT_IO_SEC_HIGH;
+	else
+		sec = BT_IO_SEC_LOW;
+
+	if (psm == 0)
+		chan = bt_io_connect(BT_IO_L2CAP, connect_cb, NULL, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &sba,
+				BT_IO_OPT_DEST_BDADDR, &dba,
+				BT_IO_OPT_CID, GATT_CID,
+				BT_IO_OPT_OMTU, mtu,
+				BT_IO_OPT_SEC_LEVEL, sec,
+				BT_IO_OPT_INVALID);
+	else
+		chan = bt_io_connect(BT_IO_L2CAP, connect_cb, NULL, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &sba,
+				BT_IO_OPT_DEST_BDADDR, &dba,
+				BT_IO_OPT_PSM, psm,
+				BT_IO_OPT_OMTU, mtu,
+				BT_IO_OPT_SEC_LEVEL, sec,
+				BT_IO_OPT_INVALID);
+
+	if (err) {
+		g_printerr("%s\n", err->message);
+		g_error_free(err);
+		return NULL;
+	}
+
+	return chan;
+}
diff --git a/audio/Android.mk b/audio/Android.mk
index dbe7969..1385831 100755
--- a/audio/Android.mk
+++ b/audio/Android.mk
@@ -14,14 +14,16 @@
 	ipc.c \
 	main.c \
 	manager.c \
+	media.c \
 	module-bluetooth-sink.c \
 	sink.c \
 	source.c \
 	telephony-dummy.c \
+	transport.c \
 	unix.c
 
 LOCAL_CFLAGS:= \
-	-DVERSION=\"4.69\" \
+	-DVERSION=\"4.89\" \
 	-DSTORAGEDIR=\"/data/misc/bluetoothd\" \
 	-DCONFIGDIR=\"/etc/bluetooth\" \
 	-DANDROID \
@@ -31,12 +33,14 @@
 	$(LOCAL_PATH)/../lib \
 	$(LOCAL_PATH)/../gdbus \
 	$(LOCAL_PATH)/../src \
+	$(LOCAL_PATH)/../btio \
 	$(call include-path-for, glib) \
 	$(call include-path-for, dbus)
 
 LOCAL_SHARED_LIBRARIES := \
 	libbluetooth \
 	libbluetoothd \
+	libbtio \
 	libdbus \
 	libglib
 
@@ -54,6 +58,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
+	android_audio_hw.c \
 	liba2dp.c \
 	ipc.c \
 	../sbc/sbc_primitives.c \
@@ -77,8 +82,11 @@
 	system/bluetooth/bluez-clean-headers
 
 LOCAL_SHARED_LIBRARIES := \
+	libpower \
 	libcutils
 
-LOCAL_MODULE := liba2dp
+LOCAL_MODULE := audio.a2dp.default
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+LOCAL_MODULE_TAGS := optional
 
 include $(BUILD_SHARED_LIBRARY)
diff --git a/audio/a2dp-codecs.h b/audio/a2dp-codecs.h
new file mode 100644
index 0000000..e44634e
--- /dev/null
+++ b/audio/a2dp-codecs.h
@@ -0,0 +1,116 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#define A2DP_CODEC_SBC			0x00
+#define A2DP_CODEC_MPEG12		0x01
+#define A2DP_CODEC_MPEG24		0x02
+#define A2DP_CODEC_ATRAC		0x03
+
+#define SBC_SAMPLING_FREQ_16000		(1 << 3)
+#define SBC_SAMPLING_FREQ_32000		(1 << 2)
+#define SBC_SAMPLING_FREQ_44100		(1 << 1)
+#define SBC_SAMPLING_FREQ_48000		1
+
+#define SBC_CHANNEL_MODE_MONO		(1 << 3)
+#define SBC_CHANNEL_MODE_DUAL_CHANNEL	(1 << 2)
+#define SBC_CHANNEL_MODE_STEREO		(1 << 1)
+#define SBC_CHANNEL_MODE_JOINT_STEREO	1
+
+#define SBC_BLOCK_LENGTH_4		(1 << 3)
+#define SBC_BLOCK_LENGTH_8		(1 << 2)
+#define SBC_BLOCK_LENGTH_12		(1 << 1)
+#define SBC_BLOCK_LENGTH_16		1
+
+#define SBC_SUBBANDS_4			(1 << 1)
+#define SBC_SUBBANDS_8			1
+
+#define SBC_ALLOCATION_SNR		(1 << 1)
+#define SBC_ALLOCATION_LOUDNESS		1
+
+#define MPEG_CHANNEL_MODE_MONO		(1 << 3)
+#define MPEG_CHANNEL_MODE_DUAL_CHANNEL	(1 << 2)
+#define MPEG_CHANNEL_MODE_STEREO	(1 << 1)
+#define MPEG_CHANNEL_MODE_JOINT_STEREO	1
+
+#define MPEG_LAYER_MP1			(1 << 2)
+#define MPEG_LAYER_MP2			(1 << 1)
+#define MPEG_LAYER_MP3			1
+
+#define MPEG_SAMPLING_FREQ_16000	(1 << 5)
+#define MPEG_SAMPLING_FREQ_22050	(1 << 4)
+#define MPEG_SAMPLING_FREQ_24000	(1 << 3)
+#define MPEG_SAMPLING_FREQ_32000	(1 << 2)
+#define MPEG_SAMPLING_FREQ_44100	(1 << 1)
+#define MPEG_SAMPLING_FREQ_48000	1
+
+#define MAX_BITPOOL 64
+#define MIN_BITPOOL 2
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+typedef struct {
+	uint8_t channel_mode:4;
+	uint8_t frequency:4;
+	uint8_t allocation_method:2;
+	uint8_t subbands:2;
+	uint8_t block_length:4;
+	uint8_t min_bitpool;
+	uint8_t max_bitpool;
+} __attribute__ ((packed)) a2dp_sbc_t;
+
+typedef struct {
+	uint8_t channel_mode:4;
+	uint8_t crc:1;
+	uint8_t layer:3;
+	uint8_t frequency:6;
+	uint8_t mpf:1;
+	uint8_t rfa:1;
+	uint16_t bitrate;
+} __attribute__ ((packed)) a2dp_mpeg_t;
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+typedef struct {
+	uint8_t frequency:4;
+	uint8_t channel_mode:4;
+	uint8_t block_length:4;
+	uint8_t subbands:2;
+	uint8_t allocation_method:2;
+	uint8_t min_bitpool;
+	uint8_t max_bitpool;
+} __attribute__ ((packed)) a2dp_sbc_t;
+
+typedef struct {
+	uint8_t layer:3;
+	uint8_t crc:1;
+	uint8_t channel_mode:4;
+	uint8_t rfa:1;
+	uint8_t mpf:1;
+	uint8_t frequency:6;
+	uint16_t bitrate;
+} __attribute__ ((packed)) a2dp_mpeg_t;
+
+#else
+#error "Unknown byte order"
+#endif
diff --git a/audio/a2dp.c b/audio/a2dp.c
index 4e5d78b..949388b 100644
--- a/audio/a2dp.c
+++ b/audio/a2dp.c
@@ -43,6 +43,8 @@
 #include "sink.h"
 #include "source.h"
 #include "unix.h"
+#include "media.h"
+#include "transport.h"
 #include "a2dp.h"
 #include "sdpd.h"
 
@@ -60,9 +62,11 @@
 #endif
 
 struct a2dp_sep {
+	struct a2dp_server *server;
+	struct media_endpoint *endpoint;
 	uint8_t type;
 	uint8_t codec;
-	struct avdtp_local_sep *sep;
+	struct avdtp_local_sep *lsep;
 	struct avdtp *session;
 	struct avdtp_stream *stream;
 	guint suspend_timer;
@@ -73,6 +77,8 @@
 };
 
 struct a2dp_setup_cb {
+	struct a2dp_setup *setup;
+	a2dp_select_cb_t select_cb;
 	a2dp_config_cb_t config_cb;
 	a2dp_stream_cb_t resume_cb;
 	a2dp_stream_cb_t suspend_cb;
@@ -84,9 +90,11 @@
 	struct audio_device *dev;
 	struct avdtp *session;
 	struct a2dp_sep *sep;
+	struct avdtp_remote_sep *rsep;
 	struct avdtp_stream *stream;
 	struct avdtp_error *err;
-	GSList *client_caps;
+	avdtp_set_configuration_cb setconf_cb;
+	GSList *caps;
 	gboolean reconfigure;
 	gboolean start;
 	GSList *cb;
@@ -102,6 +110,8 @@
 	uint32_t source_record_id;
 	uint32_t sink_record_id;
 	uint16_t version;
+	gboolean sink_enabled;
+	gboolean source_enabled;
 };
 
 static GSList *servers = NULL;
@@ -117,27 +127,6 @@
 	return setup;
 }
 
-static void setup_free(struct a2dp_setup *s)
-{
-	DBG("%p", s);
-	setups = g_slist_remove(setups, s);
-	if (s->session)
-		avdtp_unref(s->session);
-	g_slist_foreach(s->cb, (GFunc) g_free, NULL);
-	g_slist_free(s->cb);
-	g_free(s);
-}
-
-static void setup_unref(struct a2dp_setup *setup)
-{
-	setup->ref--;
-
-	DBG("%p: ref=%d", setup, setup->ref);
-
-	if (setup->ref <= 0)
-		setup_free(setup);
-}
-
 static struct audio_device *a2dp_get_dev(struct avdtp *session)
 {
 	bdaddr_t src, dst;
@@ -147,25 +136,90 @@
 	return manager_find_device(NULL, &src, &dst, NULL, FALSE);
 }
 
+static struct a2dp_setup *setup_new(struct avdtp *session)
+{
+	struct audio_device *dev;
+	struct a2dp_setup *setup;
+
+	dev = a2dp_get_dev(session);
+	if (!dev) {
+		error("Unable to create setup");
+		return NULL;
+	}
+
+	setup = g_new0(struct a2dp_setup, 1);
+	setup->session = avdtp_ref(session);
+	setup->dev = a2dp_get_dev(session);
+	setups = g_slist_append(setups, setup);
+
+	return setup;
+}
+
+static void setup_free(struct a2dp_setup *s)
+{
+	DBG("%p", s);
+
+	setups = g_slist_remove(setups, s);
+	if (s->session)
+		avdtp_unref(s->session);
+	g_slist_foreach(s->cb, (GFunc) g_free, NULL);
+	g_slist_free(s->cb);
+	g_slist_foreach(s->caps, (GFunc) g_free, NULL);
+	g_slist_free(s->caps);
+	g_free(s);
+}
+
+static void setup_unref(struct a2dp_setup *setup)
+{
+	setup->ref--;
+
+	DBG("%p: ref=%d", setup, setup->ref);
+
+	if (setup->ref > 0)
+		return;
+
+	setup_free(setup);
+}
+
+static struct a2dp_setup_cb *setup_cb_new(struct a2dp_setup *setup)
+{
+	struct a2dp_setup_cb *cb;
+
+	cb = g_new0(struct a2dp_setup_cb, 1);
+	cb->setup = setup;
+	cb->id = ++cb_id;
+
+	setup->cb = g_slist_append(setup->cb, cb);
+	return cb;
+}
+
+static void setup_cb_free(struct a2dp_setup_cb *cb)
+{
+	struct a2dp_setup *setup = cb->setup;
+
+	setup->cb = g_slist_remove(setup->cb, cb);
+	setup_unref(cb->setup);
+	g_free(cb);
+}
+
 static gboolean finalize_config(struct a2dp_setup *s)
 {
 	GSList *l;
+	struct avdtp_stream *stream = s->err ? NULL : s->stream;
 
-	setup_ref(s);
-	for (l = s->cb; l != NULL; l = l->next) {
+	for (l = s->cb; l != NULL; ) {
 		struct a2dp_setup_cb *cb = l->data;
-		struct avdtp_stream *stream = s->err ? NULL : s->stream;
+
+		l = l->next;
 
 		if (!cb->config_cb)
 			continue;
 
 		cb->config_cb(s->session, s->sep, stream, s->err,
 							cb->user_data);
-		cb->config_cb = NULL;
-		setup_unref(s);
+		setup_cb_free(cb);
 	}
 
-	setup_unref(s);
 	return FALSE;
 }
 
@@ -173,7 +227,7 @@
 {
 	struct avdtp_error avdtp_err;
 
-	avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err);
+	avdtp_error_init(&avdtp_err, AVDTP_ERRNO, -err);
 	s->err = err ? &avdtp_err : NULL;
 
 	return finalize_config(s);
@@ -183,18 +237,18 @@
 {
 	GSList *l;
 
-	setup_ref(s);
-	for (l = s->cb; l != NULL; l = l->next) {
+	for (l = s->cb; l != NULL; ) {
 		struct a2dp_setup_cb *cb = l->data;
 
-		if (cb && cb->resume_cb) {
-			cb->resume_cb(s->session, s->err, cb->user_data);
-			cb->resume_cb = NULL;
-			setup_unref(s);
-		}
+		l = l->next;
+
+		if (!cb->resume_cb)
+			continue;
+
+		cb->resume_cb(s->session, s->err, cb->user_data);
+		setup_cb_free(cb);
 	}
 
-	setup_unref(s);
 	return FALSE;
 }
 
@@ -202,18 +256,18 @@
 {
 	GSList *l;
 
-	setup_ref(s);
-	for (l = s->cb; l != NULL; l = l->next) {
+	for (l = s->cb; l != NULL; ) {
 		struct a2dp_setup_cb *cb = l->data;
 
-		if (cb->suspend_cb) {
-			cb->suspend_cb(s->session, s->err, cb->user_data);
-			cb->suspend_cb = NULL;
-			setup_unref(s);
-		}
+		l = l->next;
+
+		if (!cb->suspend_cb)
+			continue;
+
+		cb->suspend_cb(s->session, s->err, cb->user_data);
+		setup_cb_free(cb);
 	}
 
-	setup_unref(s);
 	return FALSE;
 }
 
@@ -221,12 +275,31 @@
 {
 	struct avdtp_error avdtp_err;
 
-	avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err);
+	avdtp_error_init(&avdtp_err, AVDTP_ERRNO, -err);
 	s->err = err ? &avdtp_err : NULL;
 
 	return finalize_suspend(s);
 }
 
+static gboolean finalize_select(struct a2dp_setup *s)
+{
+	GSList *l;
+
+	for (l = s->cb; l != NULL; ) {
+		struct a2dp_setup_cb *cb = l->data;
+
+		l = l->next;
+
+		if (!cb->select_cb)
+			continue;
+
+		cb->select_cb(s->session, s->sep, s->caps, cb->user_data);
+		setup_cb_free(cb);
+	}
+
+	return FALSE;
+}
+
 static struct a2dp_setup *find_setup_by_session(struct avdtp *session)
 {
 	GSList *l;
@@ -241,6 +314,20 @@
 	return NULL;
 }
 
+static struct a2dp_setup *a2dp_setup_get(struct avdtp *session)
+{
+	struct a2dp_setup *setup;
+
+	setup = find_setup_by_session(session);
+	if (!setup) {
+		setup = setup_new(session);
+		if (!setup)
+			return NULL;
+	}
+
+	return setup_ref(setup);
+}
+
 static struct a2dp_setup *find_setup_by_dev(struct audio_device *dev)
 {
 	GSList *l;
@@ -276,30 +363,72 @@
 		sep->session = NULL;
 	}
 
+	if (sep->endpoint)
+		media_endpoint_clear_configuration(sep->endpoint);
+
 	sep->stream = NULL;
 
 }
 
+static gboolean auto_config(gpointer data)
+{
+	struct a2dp_setup *setup = data;
+	struct avdtp_error *err = NULL;
+
+	/* Check if configuration was aborted */
+	if (setup->sep->stream == NULL)
+		return FALSE;
+
+	if (setup->err != NULL) {
+		err = setup->err;
+		goto done;
+	}
+
+	avdtp_stream_add_cb(setup->session, setup->stream,
+				stream_state_changed, setup->sep);
+
+	if (setup->sep->type == AVDTP_SEP_TYPE_SOURCE)
+		sink_new_stream(setup->dev, setup->session, setup->stream);
+	else
+		source_new_stream(setup->dev, setup->session, setup->stream);
+
+done:
+	if (setup->setconf_cb)
+		setup->setconf_cb(setup->session, setup->stream, setup->err);
+
+	finalize_config(setup);
+
+	if (err)
+		g_free(err);
+
+	setup_unref(setup);
+
+	return FALSE;
+}
+
 static gboolean sbc_setconf_ind(struct avdtp *session,
-				struct avdtp_local_sep *sep,
-				struct avdtp_stream *stream,
-				GSList *caps, uint8_t *err,
-				uint8_t *category, void *user_data)
+					struct avdtp_local_sep *sep,
+					struct avdtp_stream *stream,
+					GSList *caps,
+					avdtp_set_configuration_cb cb,
+					void *user_data)
 {
 	struct a2dp_sep *a2dp_sep = user_data;
-	struct audio_device *dev;
+	struct a2dp_setup *setup;
 
 	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
 		DBG("Sink %p: Set_Configuration_Ind", sep);
 	else
 		DBG("Source %p: Set_Configuration_Ind", sep);
 
-	dev = a2dp_get_dev(session);
-	if (!dev) {
-		*err = AVDTP_UNSUPPORTED_CONFIGURATION;
-		*category = 0x00;
+	setup = a2dp_setup_get(session);
+	if (!setup)
 		return FALSE;
-	}
+
+	a2dp_sep->stream = stream;
+	setup->sep = a2dp_sep;
+	setup->stream = stream;
+	setup->setconf_cb = cb;
 
 	/* Check valid settings */
 	for (; caps != NULL; caps = g_slist_next(caps)) {
@@ -309,9 +438,10 @@
 
 		if (cap->category == AVDTP_DELAY_REPORTING &&
 					!a2dp_sep->delay_reporting) {
-			*err = AVDTP_UNSUPPORTED_CONFIGURATION;
-			*category = AVDTP_DELAY_REPORTING;
-			return FALSE;
+			setup->err = g_new(struct avdtp_error, 1);
+			avdtp_error_init(setup->err, AVDTP_DELAY_REPORTING,
+						AVDTP_UNSUPPORTED_CONFIGURATION);
+			goto done;
 		}
 
 		if (cap->category != AVDTP_MEDIA_CODEC)
@@ -329,18 +459,15 @@
 
 		if (sbc_cap->min_bitpool < MIN_BITPOOL ||
 					sbc_cap->max_bitpool > MAX_BITPOOL) {
-			*err = AVDTP_UNSUPPORTED_CONFIGURATION;
-			*category = AVDTP_MEDIA_CODEC;
-			return FALSE;
+			setup->err = g_new(struct avdtp_error, 1);
+			avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC,
+					AVDTP_UNSUPPORTED_CONFIGURATION);
+			goto done;
 		}
 	}
 
-	avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep);
-	a2dp_sep->stream = stream;
-
-	if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE)
-		sink_new_stream(dev, session, stream);
-
+done:
+	g_idle_add(auto_config, setup);
 	return TRUE;
 }
 
@@ -412,43 +539,43 @@
 }
 
 static gboolean mpeg_setconf_ind(struct avdtp *session,
-				struct avdtp_local_sep *sep,
-				struct avdtp_stream *stream,
-				GSList *caps, uint8_t *err,
-				uint8_t *category, void *user_data)
+					struct avdtp_local_sep *sep,
+					struct avdtp_stream *stream,
+					GSList *caps,
+					avdtp_set_configuration_cb cb,
+					void *user_data)
 {
 	struct a2dp_sep *a2dp_sep = user_data;
-	struct audio_device *dev;
+	struct a2dp_setup *setup;
 
 	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
 		DBG("Sink %p: Set_Configuration_Ind", sep);
 	else
 		DBG("Source %p: Set_Configuration_Ind", sep);
 
-	dev = a2dp_get_dev(session);
-	if (!dev) {
-		*err = AVDTP_UNSUPPORTED_CONFIGURATION;
-		*category = 0x00;
+	setup = a2dp_setup_get(session);
+	if (!setup)
 		return FALSE;
-	}
+
+	a2dp_sep->stream = stream;
+	setup->sep = a2dp_sep;
+	setup->stream = stream;
+	setup->setconf_cb = cb;
 
 	for (; caps != NULL; caps = g_slist_next(caps)) {
 		struct avdtp_service_capability *cap = caps->data;
 
 		if (cap->category == AVDTP_DELAY_REPORTING &&
 					!a2dp_sep->delay_reporting) {
-			*err = AVDTP_UNSUPPORTED_CONFIGURATION;
-			*category = AVDTP_DELAY_REPORTING;
-			return FALSE;
+			setup->err = g_new(struct avdtp_error, 1);
+			avdtp_error_init(setup->err, AVDTP_DELAY_REPORTING,
+					AVDTP_UNSUPPORTED_CONFIGURATION);
+			goto done;
 		}
 	}
 
-	avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep);
-	a2dp_sep->stream = stream;
-
-	if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE)
-		sink_new_stream(dev, session, stream);
-
+done:
+	g_idle_add(auto_config, setup);
 	return TRUE;
 }
 
@@ -509,6 +636,154 @@
 	return TRUE;
 }
 
+static void endpoint_setconf_cb(struct media_endpoint *endpoint, void *ret,
+						int size, void *user_data)
+{
+	struct a2dp_setup *setup = user_data;
+
+	if (ret == NULL) {
+		setup->err = g_new(struct avdtp_error, 1);
+		avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC,
+					AVDTP_UNSUPPORTED_CONFIGURATION);
+	}
+
+	auto_config(setup);
+}
+
+static gboolean endpoint_setconf_ind(struct avdtp *session,
+						struct avdtp_local_sep *sep,
+						struct avdtp_stream *stream,
+						GSList *caps,
+						avdtp_set_configuration_cb cb,
+						void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		DBG("Sink %p: Set_Configuration_Ind", sep);
+	else
+		DBG("Source %p: Set_Configuration_Ind", sep);
+
+	setup = a2dp_setup_get(session);
+	if (!session)
+		return FALSE;
+
+	a2dp_sep->stream = stream;
+	setup->sep = a2dp_sep;
+	setup->stream = stream;
+	setup->setconf_cb = cb;
+
+	for (; caps != NULL; caps = g_slist_next(caps)) {
+		struct avdtp_service_capability *cap = caps->data;
+		struct avdtp_media_codec_capability *codec;
+		gboolean ret;
+
+		if (cap->category == AVDTP_DELAY_REPORTING &&
+					!a2dp_sep->delay_reporting) {
+			setup->err = g_new(struct avdtp_error, 1);
+			avdtp_error_init(setup->err, AVDTP_DELAY_REPORTING,
+					AVDTP_UNSUPPORTED_CONFIGURATION);
+			goto done;
+		}
+
+		if (cap->category != AVDTP_MEDIA_CODEC)
+			continue;
+
+		codec = (struct avdtp_media_codec_capability *) cap->data;
+
+		if (codec->media_codec_type != a2dp_sep->codec) {
+			setup->err = g_new(struct avdtp_error, 1);
+			avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC,
+					AVDTP_UNSUPPORTED_CONFIGURATION);
+			goto done;
+		}
+
+		ret = media_endpoint_set_configuration(a2dp_sep->endpoint,
+						setup->dev, codec->data,
+						cap->length - sizeof(*codec),
+						endpoint_setconf_cb, setup);
+		if (ret)
+			return TRUE;
+
+		avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC,
+					AVDTP_UNSUPPORTED_CONFIGURATION);
+		break;
+	}
+
+done:
+	g_idle_add(auto_config, setup);
+	return TRUE;
+}
+
+static gboolean endpoint_getcap_ind(struct avdtp *session,
+					struct avdtp_local_sep *sep,
+					gboolean get_all, GSList **caps,
+					uint8_t *err, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct avdtp_service_capability *media_transport, *media_codec;
+	struct avdtp_media_codec_capability *codec_caps;
+	uint8_t *capabilities;
+	size_t length;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		DBG("Sink %p: Get_Capability_Ind", sep);
+	else
+		DBG("Source %p: Get_Capability_Ind", sep);
+
+	*caps = NULL;
+
+	media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+						NULL, 0);
+
+	*caps = g_slist_append(*caps, media_transport);
+
+	length = media_endpoint_get_capabilities(a2dp_sep->endpoint,
+						&capabilities);
+
+	codec_caps = g_malloc0(sizeof(*codec_caps) + length);
+	codec_caps->media_type = AVDTP_MEDIA_TYPE_AUDIO;
+	codec_caps->media_codec_type = a2dp_sep->codec;
+	memcpy(codec_caps->data, capabilities, length);
+
+	media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, codec_caps,
+						sizeof(*codec_caps) + length);
+
+	*caps = g_slist_append(*caps, media_codec);
+	g_free(codec_caps);
+
+	if (get_all) {
+		struct avdtp_service_capability *delay_reporting;
+		delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+								NULL, 0);
+		*caps = g_slist_append(*caps, delay_reporting);
+	}
+
+	return TRUE;
+}
+
+static void endpoint_open_cb(struct media_endpoint *endpoint, void *ret,
+						int size, void *user_data)
+{
+	struct a2dp_setup *setup = user_data;
+	int err;
+
+	if (ret == NULL) {
+		setup->stream = NULL;
+		finalize_config_errno(setup, -EPERM);
+		return;
+	}
+
+	err = avdtp_open(setup->session, setup->stream);
+	if (err == 0)
+		return;
+
+	error("Error on avdtp_open %s (%d)", strerror(-err), -err);
+	setup->stream = NULL;
+	finalize_config_errno(setup, err);
+}
+
 static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
 				struct avdtp_stream *stream,
 				struct avdtp_error *err, void *user_data)
@@ -547,6 +822,26 @@
 	else
 		source_new_stream(dev, session, setup->stream);
 
+	/* Notify Endpoint */
+	if (a2dp_sep->endpoint) {
+		struct avdtp_service_capability *service;
+		struct avdtp_media_codec_capability *codec;
+
+		service = avdtp_stream_get_codec(stream);
+		codec = (struct avdtp_media_codec_capability *) service->data;
+
+		if (media_endpoint_set_configuration(a2dp_sep->endpoint, dev,
+						codec->data, service->length -
+						sizeof(*codec),
+						endpoint_open_cb, setup) ==
+						TRUE)
+			return;
+
+		setup->stream = NULL;
+		finalize_config_errno(setup, -EPERM);
+		return;
+	}
+
 	ret = avdtp_open(session, stream);
 	if (ret < 0) {
 		error("Error on avdtp_open %s (%d)", strerror(-ret), -ret);
@@ -742,7 +1037,7 @@
 	} else if (avdtp_start(session, a2dp_sep->stream) < 0) {
 		struct avdtp_error start_err;
 		error("avdtp_start failed");
-		avdtp_error_init(&start_err, AVDTP_ERROR_ERRNO, EIO);
+		avdtp_error_init(&start_err, AVDTP_ERRNO, EIO);
 		setup->err = err;
 		finalize_suspend(setup);
 	}
@@ -765,40 +1060,15 @@
 static gboolean a2dp_reconfigure(gpointer data)
 {
 	struct a2dp_setup *setup = data;
-	struct avdtp_local_sep *lsep;
-	struct avdtp_remote_sep *rsep;
-	struct avdtp_service_capability *cap;
-	struct avdtp_media_codec_capability *codec_cap = NULL;
-	GSList *l;
+	struct a2dp_sep *sep = setup->sep;
 	int posix_err;
 
-	for (l = setup->client_caps; l != NULL; l = l->next) {
-		cap = l->data;
+	if (!setup->rsep)
+		setup->rsep = avdtp_find_remote_sep(setup->session, sep->lsep);
 
-		if (cap->category != AVDTP_MEDIA_CODEC)
-			continue;
-
-		codec_cap = (void *) cap->data;
-		break;
-	}
-
-	if (!codec_cap) {
-		error("Cannot find capabilities to reconfigure");
-		posix_err = -EINVAL;
-		goto failed;
-	}
-
-	posix_err = avdtp_get_seps(setup->session, AVDTP_SEP_TYPE_SINK,
-					codec_cap->media_type,
-					codec_cap->media_codec_type,
-					&lsep, &rsep);
-	if (posix_err < 0) {
-		error("No matching ACP and INT SEPs found");
-		goto failed;
-	}
-
-	posix_err = avdtp_set_configuration(setup->session, rsep, lsep,
-						setup->client_caps,
+	posix_err = avdtp_set_configuration(setup->session, setup->rsep,
+						sep->lsep,
+						setup->caps,
 						&setup->stream);
 	if (posix_err < 0) {
 		error("avdtp_set_configuration: %s", strerror(-posix_err));
@@ -835,6 +1105,9 @@
 		return;
 	}
 
+	if (!setup->rsep)
+		setup->rsep = avdtp_stream_get_remote_sep(stream);
+
 	if (setup->reconfigure)
 		g_timeout_add(RECONFIGURE_TIMEOUT, a2dp_reconfigure, setup);
 }
@@ -905,6 +1178,28 @@
 	return TRUE;
 }
 
+static gboolean endpoint_delayreport_ind(struct avdtp *session,
+						struct avdtp_local_sep *sep,
+						uint8_t rseid, uint16_t delay,
+						uint8_t *err, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct media_transport *transport;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		DBG("Sink %p: DelayReport_Ind", sep);
+	else
+		DBG("Source %p: DelayReport_Ind", sep);
+
+	transport = media_endpoint_get_transport(a2dp_sep->endpoint);
+	if (transport == NULL)
+		return FALSE;
+
+	media_transport_update_delay(transport, delay);
+
+	return TRUE;
+}
+
 static void reconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
 			struct avdtp_stream *stream, struct avdtp_error *err,
 			void *user_data)
@@ -979,6 +1274,19 @@
 	.delayreport		= delayreport_ind,
 };
 
+static struct avdtp_sep_ind endpoint_ind = {
+	.get_capability		= endpoint_getcap_ind,
+	.set_configuration	= endpoint_setconf_ind,
+	.get_configuration	= getconf_ind,
+	.open			= open_ind,
+	.start			= start_ind,
+	.suspend		= suspend_ind,
+	.close			= close_ind,
+	.abort			= abort_ind,
+	.reconfigure		= reconf_ind,
+	.delayreport		= endpoint_delayreport_ind,
+};
+
 static sdp_record_t *a2dp_record(uint8_t type, uint16_t avdtp_ver)
 {
 	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
@@ -1049,64 +1357,6 @@
 	return record;
 }
 
-static struct a2dp_sep *a2dp_add_sep(struct a2dp_server *server, uint8_t type,
-					uint8_t codec, gboolean delay_reporting)
-{
-	struct a2dp_sep *sep;
-	GSList **l;
-	uint32_t *record_id;
-	sdp_record_t *record;
-	struct avdtp_sep_ind *ind;
-
-	sep = g_new0(struct a2dp_sep, 1);
-
-	ind = (codec == A2DP_CODEC_MPEG12) ? &mpeg_ind : &sbc_ind;
-	sep->sep = avdtp_register_sep(&server->src, type,
-					AVDTP_MEDIA_TYPE_AUDIO, codec,
-					delay_reporting, ind, &cfm, sep);
-	if (sep->sep == NULL) {
-		g_free(sep);
-		return NULL;
-	}
-
-	sep->codec = codec;
-	sep->type = type;
-	sep->delay_reporting = delay_reporting;
-
-	if (type == AVDTP_SEP_TYPE_SOURCE) {
-		l = &server->sources;
-		record_id = &server->source_record_id;
-	} else {
-		l = &server->sinks;
-		record_id = &server->sink_record_id;
-	}
-
-	if (*record_id != 0)
-		goto add;
-
-	record = a2dp_record(type, server->version);
-	if (!record) {
-		error("Unable to allocate new service record");
-		avdtp_unregister_sep(sep->sep);
-		g_free(sep);
-		return NULL;
-	}
-
-	if (add_record_to_server(&server->src, record) < 0) {
-		error("Unable to register A2DP service record");\
-		sdp_record_free(record);
-		avdtp_unregister_sep(sep->sep);
-		g_free(sep);
-		return NULL;
-	}
-	*record_id = record->handle;
-
-add:
-	*l = g_slist_append(*l, sep);
-
-	return sep;
-}
-
 static struct a2dp_server *find_server(GSList *list, const bdaddr_t *src)
 {
 	GSList *l;
@@ -1125,7 +1375,8 @@
 {
 	int sbc_srcs = 1, sbc_sinks = 1;
 	int mpeg12_srcs = 0, mpeg12_sinks = 0;
-	gboolean source = TRUE, sink = FALSE, delay_reporting;
+	gboolean source = TRUE, sink = FALSE, socket = TRUE;
+	gboolean delay_reporting = FALSE;
 	char *str;
 	GError *err = NULL;
 	int i;
@@ -1157,9 +1408,20 @@
 			source = FALSE;
 		if (strstr(str, "Source"))
 			sink = FALSE;
+		if (strstr(str, "Socket"))
+			socket = FALSE;
 		g_free(str);
 	}
 
+	/* Don't register any local sep if Socket is disabled */
+	if (socket == FALSE) {
+		sbc_srcs = 0;
+		sbc_sinks = 0;
+		mpeg12_srcs = 0;
+		mpeg12_sinks = 0;
+		goto proceed;
+	}
+
 	str = g_key_file_get_string(config, "A2DP", "SBCSources", &err);
 	if (err) {
 		DBG("audio.conf: %s", err->message);
@@ -1218,31 +1480,35 @@
 		servers = g_slist_append(servers, server);
 	}
 
-	delay_reporting = g_key_file_get_boolean(config, "A2DP",
+	if (config)
+		delay_reporting = g_key_file_get_boolean(config, "A2DP",
 						"DelayReporting", NULL);
+
 	if (delay_reporting)
 		server->version = 0x0103;
 	else
 		server->version = 0x0102;
 
+	server->source_enabled = source;
 	if (source) {
 		for (i = 0; i < sbc_srcs; i++)
-			a2dp_add_sep(server, AVDTP_SEP_TYPE_SOURCE,
-					A2DP_CODEC_SBC, delay_reporting);
+			a2dp_add_sep(src, AVDTP_SEP_TYPE_SOURCE,
+					A2DP_CODEC_SBC, delay_reporting, NULL);
 
 		for (i = 0; i < mpeg12_srcs; i++)
-			a2dp_add_sep(server, AVDTP_SEP_TYPE_SOURCE,
-					A2DP_CODEC_MPEG12, delay_reporting);
+			a2dp_add_sep(src, AVDTP_SEP_TYPE_SOURCE,
+					A2DP_CODEC_MPEG12, delay_reporting, NULL);
 	}
-
+	server->sink_enabled = sink;
 	if (sink) {
 		for (i = 0; i < sbc_sinks; i++)
-			a2dp_add_sep(server, AVDTP_SEP_TYPE_SINK,
-					A2DP_CODEC_SBC, delay_reporting);
+			a2dp_add_sep(src, AVDTP_SEP_TYPE_SINK,
+					A2DP_CODEC_SBC, delay_reporting, NULL);
 
 		for (i = 0; i < mpeg12_sinks; i++)
-			a2dp_add_sep(server, AVDTP_SEP_TYPE_SINK,
-					A2DP_CODEC_MPEG12, delay_reporting);
+			a2dp_add_sep(src, AVDTP_SEP_TYPE_SINK,
+					A2DP_CODEC_MPEG12, delay_reporting,
+					NULL);
 	}
 
 	return 0;
@@ -1250,7 +1516,7 @@
 
 static void a2dp_unregister_sep(struct a2dp_sep *sep)
 {
-	avdtp_unregister_sep(sep->sep);
+	avdtp_unregister_sep(sep->lsep);
 	g_free(sep);
 }
 
@@ -1262,20 +1528,14 @@
 	if (!server)
 		return;
 
-	g_slist_foreach(server->sinks, (GFunc) a2dp_unregister_sep, NULL);
+	g_slist_foreach(server->sinks, (GFunc) a2dp_remove_sep, NULL);
 	g_slist_free(server->sinks);
 
-	g_slist_foreach(server->sources, (GFunc) a2dp_unregister_sep, NULL);
+	g_slist_foreach(server->sources, (GFunc) a2dp_remove_sep, NULL);
 	g_slist_free(server->sources);
 
 	avdtp_exit(src);
 
-	if (server->source_record_id)
-		remove_record_from_server(server->source_record_id);
-
-	if (server->sink_record_id)
-		remove_record_from_server(server->sink_record_id);
-
 	servers = g_slist_remove(servers, server);
 	g_free(server);
 
@@ -1286,6 +1546,106 @@
 	connection = NULL;
 }
 
+struct a2dp_sep *a2dp_add_sep(const bdaddr_t *src, uint8_t type,
+				uint8_t codec, gboolean delay_reporting,
+				struct media_endpoint *endpoint)
+{
+	struct a2dp_server *server;
+	struct a2dp_sep *sep;
+	GSList **l;
+	uint32_t *record_id;
+	sdp_record_t *record;
+	struct avdtp_sep_ind *ind;
+
+	server = find_server(servers, src);
+	if (server == NULL)
+		return NULL;
+
+	if (type == AVDTP_SEP_TYPE_SINK && !server->sink_enabled)
+		return NULL;
+
+	if (type == AVDTP_SEP_TYPE_SOURCE && !server->source_enabled)
+		return NULL;
+
+	sep = g_new0(struct a2dp_sep, 1);
+
+	if (endpoint) {
+		ind = &endpoint_ind;
+		goto proceed;
+	}
+
+	ind = (codec == A2DP_CODEC_MPEG12) ? &mpeg_ind : &sbc_ind;
+
+proceed:
+	sep->lsep = avdtp_register_sep(&server->src, type,
+					AVDTP_MEDIA_TYPE_AUDIO, codec,
+					delay_reporting, ind, &cfm, sep);
+	if (sep->lsep == NULL) {
+		g_free(sep);
+		return NULL;
+	}
+
+	sep->server = server;
+	sep->endpoint = endpoint;
+	sep->codec = codec;
+	sep->type = type;
+	sep->delay_reporting = delay_reporting;
+
+	if (type == AVDTP_SEP_TYPE_SOURCE) {
+		l = &server->sources;
+		record_id = &server->source_record_id;
+	} else {
+		l = &server->sinks;
+		record_id = &server->sink_record_id;
+	}
+
+	if (*record_id != 0)
+		goto add;
+
+	record = a2dp_record(type, server->version);
+	if (!record) {
+		error("Unable to allocate new service record");
+		avdtp_unregister_sep(sep->lsep);
+		g_free(sep);
+		return NULL;
+	}
+
+	if (add_record_to_server(&server->src, record) < 0) {
+		error("Unable to register A2DP service record");\
+		sdp_record_free(record);
+		avdtp_unregister_sep(sep->lsep);
+		g_free(sep);
+		return NULL;
+	}
+	*record_id = record->handle;
+
+add:
+	*l = g_slist_append(*l, sep);
+
+	return sep;
+}
+
+void a2dp_remove_sep(struct a2dp_sep *sep)
+{
+	struct a2dp_server *server = sep->server;
+
+	if (sep->type == AVDTP_SEP_TYPE_SOURCE) {
+		server->sources = g_slist_remove(server->sources, sep);
+		if (server->sources == NULL && server->source_record_id) {
+			remove_record_from_server(server->source_record_id);
+			server->source_record_id = 0;
+		}
+	} else {
+		server->sinks = g_slist_remove(server->sinks, sep);
+		if (server->sinks == NULL && server->sink_record_id) {
+			remove_record_from_server(server->sink_record_id);
+			server->sink_record_id = 0;
+		}
+	}
+
+	a2dp_unregister_sep(sep);
+}
+
 struct a2dp_sep *a2dp_get(struct avdtp *session,
 				struct avdtp_remote_sep *rsep)
 {
@@ -1324,6 +1684,301 @@
 	return NULL;
 }
 
+static uint8_t default_bitpool(uint8_t freq, uint8_t mode)
+{
+	switch (freq) {
+	case SBC_SAMPLING_FREQ_16000:
+	case SBC_SAMPLING_FREQ_32000:
+		return 53;
+	case SBC_SAMPLING_FREQ_44100:
+		switch (mode) {
+		case SBC_CHANNEL_MODE_MONO:
+		case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+			return 31;
+		case SBC_CHANNEL_MODE_STEREO:
+		case SBC_CHANNEL_MODE_JOINT_STEREO:
+			return 53;
+		default:
+			error("Invalid channel mode %u", mode);
+			return 53;
+		}
+	case SBC_SAMPLING_FREQ_48000:
+		switch (mode) {
+		case SBC_CHANNEL_MODE_MONO:
+		case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+			return 29;
+		case SBC_CHANNEL_MODE_STEREO:
+		case SBC_CHANNEL_MODE_JOINT_STEREO:
+			return 51;
+		default:
+			error("Invalid channel mode %u", mode);
+			return 51;
+		}
+	default:
+		error("Invalid sampling freq %u", freq);
+		return 53;
+	}
+}
+
+static gboolean select_sbc_params(struct sbc_codec_cap *cap,
+					struct sbc_codec_cap *supported)
+{
+	unsigned int max_bitpool, min_bitpool;
+
+	memset(cap, 0, sizeof(struct sbc_codec_cap));
+
+	cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+	cap->cap.media_codec_type = A2DP_CODEC_SBC;
+
+	if (supported->frequency & SBC_SAMPLING_FREQ_44100)
+		cap->frequency = SBC_SAMPLING_FREQ_44100;
+	else if (supported->frequency & SBC_SAMPLING_FREQ_48000)
+		cap->frequency = SBC_SAMPLING_FREQ_48000;
+	else if (supported->frequency & SBC_SAMPLING_FREQ_32000)
+		cap->frequency = SBC_SAMPLING_FREQ_32000;
+	else if (supported->frequency & SBC_SAMPLING_FREQ_16000)
+		cap->frequency = SBC_SAMPLING_FREQ_16000;
+	else {
+		error("No supported frequencies");
+		return FALSE;
+	}
+
+	if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+		cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+	else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO)
+		cap->channel_mode = SBC_CHANNEL_MODE_STEREO;
+	else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+		cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+	else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO)
+		cap->channel_mode = SBC_CHANNEL_MODE_MONO;
+	else {
+		error("No supported channel modes");
+		return FALSE;
+	}
+
+	if (supported->block_length & SBC_BLOCK_LENGTH_16)
+		cap->block_length = SBC_BLOCK_LENGTH_16;
+	else if (supported->block_length & SBC_BLOCK_LENGTH_12)
+		cap->block_length = SBC_BLOCK_LENGTH_12;
+	else if (supported->block_length & SBC_BLOCK_LENGTH_8)
+		cap->block_length = SBC_BLOCK_LENGTH_8;
+	else if (supported->block_length & SBC_BLOCK_LENGTH_4)
+		cap->block_length = SBC_BLOCK_LENGTH_4;
+	else {
+		error("No supported block lengths");
+		return FALSE;
+	}
+
+	if (supported->subbands & SBC_SUBBANDS_8)
+		cap->subbands = SBC_SUBBANDS_8;
+	else if (supported->subbands & SBC_SUBBANDS_4)
+		cap->subbands = SBC_SUBBANDS_4;
+	else {
+		error("No supported subbands");
+		return FALSE;
+	}
+
+	if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS)
+		cap->allocation_method = SBC_ALLOCATION_LOUDNESS;
+	else if (supported->allocation_method & SBC_ALLOCATION_SNR)
+		cap->allocation_method = SBC_ALLOCATION_SNR;
+
+	min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool);
+	max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode),
+							supported->max_bitpool);
+
+	cap->min_bitpool = min_bitpool;
+	cap->max_bitpool = max_bitpool;
+
+	return TRUE;
+}
+
+static gboolean select_capabilities(struct avdtp *session,
+					struct avdtp_remote_sep *rsep,
+					GSList **caps)
+{
+	struct avdtp_service_capability *media_transport, *media_codec;
+	struct sbc_codec_cap sbc_cap;
+
+	media_codec = avdtp_get_codec(rsep);
+	if (!media_codec)
+		return FALSE;
+
+	select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data);
+
+	media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+						NULL, 0);
+
+	*caps = g_slist_append(*caps, media_transport);
+
+	media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
+						sizeof(sbc_cap));
+
+	*caps = g_slist_append(*caps, media_codec);
+
+	if (avdtp_get_delay_reporting(rsep)) {
+		struct avdtp_service_capability *delay_reporting;
+		delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+								NULL, 0);
+		*caps = g_slist_append(*caps, delay_reporting);
+	}
+
+	return TRUE;
+}
+
+static void select_cb(struct media_endpoint *endpoint, void *ret, int size,
+			void *user_data)
+{
+	struct a2dp_setup *setup = user_data;
+	struct avdtp_service_capability *media_transport, *media_codec;
+	struct avdtp_media_codec_capability *cap;
+
+	if (size < 0) {
+		DBG("Endpoint replied an invalid configuration");
+		goto done;
+	}
+
+	media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+						NULL, 0);
+
+	setup->caps = g_slist_append(setup->caps, media_transport);
+
+	cap = g_malloc0(sizeof(*cap) + size);
+	cap->media_type = AVDTP_MEDIA_TYPE_AUDIO;
+	cap->media_codec_type = setup->sep->codec;
+	memcpy(cap->data, ret, size);
+
+	media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, cap,
+						sizeof(*cap) + size);
+
+	setup->caps = g_slist_append(setup->caps, media_codec);
+	g_free(cap);
+
+done:
+	finalize_select(setup);
+}
+
+static gboolean auto_select(gpointer data)
+{
+	struct a2dp_setup *setup = data;
+
+	finalize_select(setup);
+
+	return FALSE;
+}
+
+static struct a2dp_sep *a2dp_find_sep(struct avdtp *session, GSList *list,
+					const char *sender)
+{
+	for (; list; list = list->next) {
+		struct a2dp_sep *sep = list->data;
+
+		/* Use sender's endpoint if available */
+		if (sender) {
+			const char *name;
+
+			if (sep->endpoint == NULL)
+				continue;
+
+			name = media_endpoint_get_sender(sep->endpoint);
+			if (g_strcmp0(sender, name) != 0)
+				continue;
+		}
+
+		if (avdtp_find_remote_sep(session, sep->lsep) == NULL)
+			continue;
+
+		return sep;
+	}
+
+	return NULL;
+}
+
+static struct a2dp_sep *a2dp_select_sep(struct avdtp *session, uint8_t type,
+					const char *sender)
+{
+	struct a2dp_server *server;
+	struct a2dp_sep *sep;
+	GSList *l;
+	bdaddr_t src;
+
+	avdtp_get_peers(session, &src, NULL);
+	server = find_server(servers, &src);
+	if (!server)
+		return NULL;
+
+	l = type == AVDTP_SEP_TYPE_SINK ? server->sources : server->sinks;
+
+	/* Check sender's seps first */
+	sep = a2dp_find_sep(session, l, sender);
+	if (sep != NULL)
+		return sep;
+
+	return a2dp_find_sep(session, l, NULL);
+}
+
+unsigned int a2dp_select_capabilities(struct avdtp *session,
+					uint8_t type, const char *sender,
+					a2dp_select_cb_t cb,
+					void *user_data)
+{
+	struct a2dp_setup *setup;
+	struct a2dp_setup_cb *cb_data;
+	struct a2dp_sep *sep;
+	struct avdtp_service_capability *service;
+	struct avdtp_media_codec_capability *codec;
+
+	sep = a2dp_select_sep(session, type, sender);
+	if (!sep) {
+		error("Unable to select SEP");
+		return 0;
+	}
+
+	setup = a2dp_setup_get(session);
+	if (!setup)
+		return 0;
+
+	cb_data = setup_cb_new(setup);
+	cb_data->select_cb = cb;
+	cb_data->user_data = user_data;
+
+	setup->sep = sep;
+	setup->rsep = avdtp_find_remote_sep(session, sep->lsep);
+
+	if (setup->rsep == NULL) {
+		error("Could not find remote sep");
+		goto fail;
+	}
+
+	/* FIXME: Remove auto select when it is not longer possible to register
+	endpoint in the configuration file */
+	if (sep->endpoint == NULL) {
+		if (!select_capabilities(session, setup->rsep,
+					&setup->caps)) {
+			error("Unable to auto select remote SEP capabilities");
+			goto fail;
+		}
+
+		g_idle_add(auto_select, setup);
+
+		return cb_data->id;
+	}
+
+	service = avdtp_get_codec(setup->rsep);
+	codec = (struct avdtp_media_codec_capability *) service->data;
+
+	if (media_endpoint_select_configuration(sep->endpoint, codec->data,
+						service->length - sizeof(*codec),
+						select_cb, setup) ==
+						TRUE)
+		return cb_data->id;
+
+fail:
+	setup_cb_free(cb_data);
+	return 0;
+
+}
+
 unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
 				a2dp_config_cb_t cb, GSList *caps,
 				void *user_data)
@@ -1333,8 +1988,6 @@
 	struct a2dp_server *server;
 	struct a2dp_setup *setup;
 	struct a2dp_sep *tmp;
-	struct avdtp_local_sep *lsep;
-	struct avdtp_remote_sep *rsep;
 	struct avdtp_service_capability *cap;
 	struct avdtp_media_codec_capability *codec_cap = NULL;
 	int posix_err;
@@ -1362,28 +2015,27 @@
 	if (sep->codec != codec_cap->media_codec_type)
 		return 0;
 
-	DBG("a2dp_config: selected SEP %p", sep->sep);
+	DBG("a2dp_config: selected SEP %p", sep->lsep);
 
-	cb_data = g_new0(struct a2dp_setup_cb, 1);
+	setup = a2dp_setup_get(session);
+	if (!setup)
+		return 0;
+
+	cb_data = setup_cb_new(setup);
 	cb_data->config_cb = cb;
 	cb_data->user_data = user_data;
-	cb_data->id = ++cb_id;
 
-	setup = find_setup_by_session(session);
-	if (!setup) {
-		setup = g_new0(struct a2dp_setup, 1);
-		setup->session = avdtp_ref(session);
-		setup->dev = a2dp_get_dev(session);
-		setups = g_slist_append(setups, setup);
-	}
-
-	setup_ref(setup);
-	setup->cb = g_slist_append(setup->cb, cb_data);
 	setup->sep = sep;
 	setup->stream = sep->stream;
-	setup->client_caps = caps;
 
-	switch (avdtp_sep_get_state(sep->sep)) {
+	/* Copy given caps if they are different than current caps */
+	if (setup->caps != caps) {
+		g_slist_foreach(setup->caps, (GFunc) g_free, NULL);
+		g_slist_free(setup->caps);
+		setup->caps = g_slist_copy(caps);
+	}
+
+	switch (avdtp_sep_get_state(sep->lsep)) {
 	case AVDTP_STATE_IDLE:
 		if (sep->type == AVDTP_SEP_TYPE_SOURCE) {
 			l = server->sources;
@@ -1400,6 +2052,8 @@
 		}
 
 		if (l != NULL) {
+			if (a2dp_sep_get_lock(tmp))
+				goto failed;
 			setup->reconfigure = TRUE;
 			if (avdtp_close(session, tmp->stream, FALSE) < 0) {
 				error("avdtp_close failed");
@@ -1408,16 +2062,15 @@
 			break;
 		}
 
-		if (avdtp_get_seps(session, remote_type,
-				codec_cap->media_type,
-				codec_cap->media_codec_type,
-				&lsep, &rsep) < 0) {
+		setup->rsep = avdtp_find_remote_sep(session, sep->lsep);
+		if (setup->rsep == NULL) {
 			error("No matching ACP and INT SEPs found");
 			goto failed;
 		}
 
-		posix_err = avdtp_set_configuration(session, rsep, lsep,
-							caps, &setup->stream);
+		posix_err = avdtp_set_configuration(session, setup->rsep,
+							sep->lsep, caps,
+							&setup->stream);
 		if (posix_err < 0) {
 			error("avdtp_set_configuration: %s",
 				strerror(-posix_err));
@@ -1445,8 +2098,7 @@
 	return cb_data->id;
 
 failed:
-	setup_unref(setup);
-	cb_id--;
+	setup_cb_free(cb_data);
 	return 0;
 }
 
@@ -1456,25 +2108,18 @@
 	struct a2dp_setup_cb *cb_data;
 	struct a2dp_setup *setup;
 
-	cb_data = g_new0(struct a2dp_setup_cb, 1);
+	setup = a2dp_setup_get(session);
+	if (!setup)
+		return 0;
+
+	cb_data = setup_cb_new(setup);
 	cb_data->resume_cb = cb;
 	cb_data->user_data = user_data;
-	cb_data->id = ++cb_id;
 
-	setup = find_setup_by_session(session);
-	if (!setup) {
-		setup = g_new0(struct a2dp_setup, 1);
-		setup->session = avdtp_ref(session);
-		setup->dev = a2dp_get_dev(session);
-		setups = g_slist_append(setups, setup);
-	}
-
-	setup_ref(setup);
-	setup->cb = g_slist_append(setup->cb, cb_data);
 	setup->sep = sep;
 	setup->stream = sep->stream;
 
-	switch (avdtp_sep_get_state(sep->sep)) {
+	switch (avdtp_sep_get_state(sep->lsep)) {
 	case AVDTP_STATE_IDLE:
 		goto failed;
 		break;
@@ -1504,8 +2149,7 @@
 	return cb_data->id;
 
 failed:
-	setup_unref(setup);
-	cb_id--;
+	setup_cb_free(cb_data);
 	return 0;
 }
 
@@ -1515,25 +2159,18 @@
 	struct a2dp_setup_cb *cb_data;
 	struct a2dp_setup *setup;
 
-	cb_data = g_new0(struct a2dp_setup_cb, 1);
+	setup = a2dp_setup_get(session);
+	if (!setup)
+		return 0;
+
+	cb_data = setup_cb_new(setup);
 	cb_data->suspend_cb = cb;
 	cb_data->user_data = user_data;
-	cb_data->id = ++cb_id;
 
-	setup = find_setup_by_session(session);
-	if (!setup) {
-		setup = g_new0(struct a2dp_setup, 1);
-		setup->session = avdtp_ref(session);
-		setup->dev = a2dp_get_dev(session);
-		setups = g_slist_append(setups, setup);
-	}
-
-	setup_ref(setup);
-	setup->cb = g_slist_append(setup->cb, cb_data);
 	setup->sep = sep;
 	setup->stream = sep->stream;
 
-	switch (avdtp_sep_get_state(sep->sep)) {
+	switch (avdtp_sep_get_state(sep->lsep)) {
 	case AVDTP_STATE_IDLE:
 		error("a2dp_suspend: no stream to suspend");
 		goto failed;
@@ -1546,6 +2183,7 @@
 			error("avdtp_suspend failed");
 			goto failed;
 		}
+		sep->suspending = TRUE;
 		break;
 	default:
 		error("SEP in bad state for suspend");
@@ -1555,44 +2193,39 @@
 	return cb_data->id;
 
 failed:
-	setup_unref(setup);
-	cb_id--;
+	setup_cb_free(cb_data);
 	return 0;
 }
 
 gboolean a2dp_cancel(struct audio_device *dev, unsigned int id)
 {
-	struct a2dp_setup_cb *cb_data;
 	struct a2dp_setup *setup;
 	GSList *l;
 
-	DBG("a2dp_cancel()");
-
 	setup = find_setup_by_dev(dev);
 	if (!setup)
 		return FALSE;
 
-	for (cb_data = NULL, l = setup->cb; l != NULL; l = g_slist_next(l)) {
+	for (l = setup->cb; l != NULL; l = g_slist_next(l)) {
 		struct a2dp_setup_cb *cb = l->data;
 
-		if (cb->id == id) {
-			cb_data = cb;
-			break;
+		if (cb->id != id)
+			continue;
+
+		setup_ref(setup);
+		setup_cb_free(cb);
+
+		if (!setup->cb) {
+			DBG("aborting setup %p", setup);
+			avdtp_abort(setup->session, setup->stream);
+			return TRUE;
 		}
+
+		setup_unref(setup);
+		return TRUE;
 	}
 
-	if (!cb_data)
-		error("a2dp_cancel: no matching callback with id %u", id);
-
-	setup->cb = g_slist_remove(setup->cb, cb_data);
-	g_free(cb_data);
-
-	if (setup->cb)
-		return TRUE;
-
-	avdtp_abort(setup->session, setup->stream);
-
-	return TRUE;
+	return FALSE;
 }
 
 gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session)
@@ -1600,7 +2233,7 @@
 	if (sep->locked)
 		return FALSE;
 
-	DBG("SEP %p locked", sep->sep);
+	DBG("SEP %p locked", sep->lsep);
 	sep->locked = TRUE;
 
 	return TRUE;
@@ -1610,11 +2243,11 @@
 {
 	avdtp_state_t state;
 
-	state = avdtp_sep_get_state(sep->sep);
+	state = avdtp_sep_get_state(sep->lsep);
 
 	sep->locked = FALSE;
 
-	DBG("SEP %p unlocked", sep->sep);
+	DBG("SEP %p unlocked", sep->lsep);
 
 	if (!sep->stream || state == AVDTP_STATE_IDLE)
 		return TRUE;
@@ -1676,3 +2309,8 @@
 
 	return NULL;
 }
+
+struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep)
+{
+	return sep->stream;
+}
diff --git a/audio/a2dp.h b/audio/a2dp.h
index fa81776..21fccaa 100644
--- a/audio/a2dp.h
+++ b/audio/a2dp.h
@@ -121,6 +121,10 @@
 
 struct a2dp_sep;
 
+
+typedef void (*a2dp_select_cb_t) (struct avdtp *session,
+					struct a2dp_sep *sep, GSList *caps,
+					void *user_data);
 typedef void (*a2dp_config_cb_t) (struct avdtp *session, struct a2dp_sep *sep,
 					struct avdtp_stream *stream,
 					struct avdtp_error *err,
@@ -132,7 +136,17 @@
 int a2dp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config);
 void a2dp_unregister(const bdaddr_t *src);
 
+struct a2dp_sep *a2dp_add_sep(const bdaddr_t *src, uint8_t type,
+				uint8_t codec, gboolean delay_reporting,
+				struct media_endpoint *endpoint);
+void a2dp_remove_sep(struct a2dp_sep *sep);
+
 struct a2dp_sep *a2dp_get(struct avdtp *session, struct avdtp_remote_sep *sep);
+
+unsigned int a2dp_select_capabilities(struct avdtp *session,
+					uint8_t type, const char *sender,
+					a2dp_select_cb_t cb,
+					void *user_data);
 unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
 				a2dp_config_cb_t cb, GSList *caps,
 				void *user_data);
@@ -145,5 +159,6 @@
 gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session);
 gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session);
 gboolean a2dp_sep_get_lock(struct a2dp_sep *sep);
+struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep);
 struct a2dp_sep *a2dp_get_sep(struct avdtp *session,
 				struct avdtp_stream *stream);
diff --git a/audio/android_audio_hw.c b/audio/android_audio_hw.c
new file mode 100644
index 0000000..1c3591d
--- /dev/null
+++ b/audio/android_audio_hw.c
@@ -0,0 +1,743 @@
+/*
+ *  Copyright (C) 2008-2011 The Android Open Source Project
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#define LOG_TAG "a2dp_audio_hw"
+//#define LOG_NDEBUG 0
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <sys/time.h>
+
+#include <cutils/log.h>
+#include <cutils/str_parms.h>
+
+#include <hardware/hardware.h>
+#include <system/audio.h>
+#include <hardware/audio_hal.h>
+
+#include <hardware_legacy/power.h>
+
+#include "liba2dp.h"
+
+#define A2DP_WAKE_LOCK_NAME            "A2dpOutputStream"
+#define MAX_WRITE_RETRIES              5
+
+#define A2DP_SUSPENDED_PARM            "A2dpSuspended"
+#define BLUETOOOTH_ENABLED_PARM        "bluetooth_enabled"
+
+#define OUT_SINK_ADDR_PARM             "a2dp_sink_address"
+
+/* NOTE: If you need to hold the adev_a2dp->lock AND the astream_out->lock,
+   you MUST take adev_a2dp lock first!!
+ */
+
+struct astream_out;
+struct adev_a2dp {
+    struct audio_hw_device  device;
+
+    audio_mode_t            mode;
+    bool                    bt_enabled;
+    bool                    suspended;
+
+    pthread_mutex_t         lock;
+
+    struct astream_out      *output;
+};
+
+struct astream_out {
+    struct audio_stream_out stream;
+
+    uint32_t                sample_rate;
+    size_t                  buffer_size;
+    uint32_t                channels;
+    int                     format;
+
+    int                     fd;
+    bool                    standby;
+    int                     start_count;
+    int                     retry_count;
+    void*                   data;
+
+    pthread_mutex_t         lock;
+
+    audio_devices_t         device;
+    uint64_t                 last_write_time;
+    uint32_t                buffer_duration_us;
+
+    bool                    bt_enabled;
+    bool                    suspended;
+    char                    a2dp_addr[20];
+};
+
+static uint64_t system_time(void)
+{
+    struct timespec t;
+
+    t.tv_sec = t.tv_nsec = 0;
+    clock_gettime(CLOCK_MONOTONIC, &t);
+
+    return t.tv_sec*1000000000LL + t.tv_nsec;
+}
+
+/** audio_stream_out implementation **/
+static uint32_t out_get_sample_rate(const struct audio_stream *stream)
+{
+    const struct astream_out *out = (const struct astream_out *)stream;
+    return out->sample_rate;
+}
+
+static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate)
+{
+    struct astream_out *out = (struct astream_out *)stream;
+
+    LOGE("(%s:%d) %s: Implement me!", __FILE__, __LINE__, __func__);
+    return 0;
+}
+
+static size_t out_get_buffer_size(const struct audio_stream *stream)
+{
+    const struct astream_out *out = (const struct astream_out *)stream;
+    return out->buffer_size;
+}
+
+static uint32_t out_get_channels(const struct audio_stream *stream)
+{
+    const struct astream_out *out = (const struct astream_out *)stream;
+    return out->channels;
+}
+
+static int out_get_format(const struct audio_stream *stream)
+{
+    const struct astream_out *out = (const struct astream_out *)stream;
+    return out->format;
+}
+
+static int out_set_format(struct audio_stream *stream, int format)
+{
+    struct astream_out *out = (struct astream_out *)stream;
+    LOGE("(%s:%d) %s: Implement me!", __FILE__, __LINE__, __func__);
+    return 0;
+}
+
+static int out_dump(const struct audio_stream *stream, int fd)
+{
+    return 0;
+}
+
+static uint32_t out_get_latency(const struct audio_stream_out *stream)
+{
+    const struct astream_out *out = (const struct astream_out *)stream;
+
+    return (out->buffer_duration_us / 1000) + 200;
+}
+
+static int out_set_volume(struct audio_stream_out *stream, float left,
+                          float right)
+{
+    return -ENOSYS;
+}
+
+static int out_get_render_position(const struct audio_stream_out *stream,
+                                   uint32_t *dsp_frames)
+{
+    return -ENOSYS;
+}
+
+static int _out_init_locked(struct astream_out *out, const char *addr)
+{
+    int ret;
+
+    if (out->data)
+        return 0;
+
+    /* XXX: shouldn't this use the sample_rate/channel_count from 'out'? */
+    ret = a2dp_init(44100, 2, &out->data);
+    if (ret < 0) {
+        LOGE("a2dp_init failed err: %d\n", ret);
+        out->data = NULL;
+        return ret;
+    }
+
+    /* XXX: is this even necessary? */
+    if (addr)
+        strlcpy(out->a2dp_addr, addr, sizeof(out->a2dp_addr));
+    a2dp_set_sink(out->data, out->a2dp_addr);
+
+    return 0;
+}
+
+static bool _out_validate_parms(struct astream_out *out, int format,
+                                uint32_t chans, uint32_t rate)
+{
+    if ((format && (format != out->format)) ||
+        (chans && (chans != out->channels)) ||
+        (rate && (rate != out->sample_rate)))
+        return false;
+    return true;
+}
+
+static int out_standby_stream_locked(struct astream_out *out)
+{
+    int ret = 0;
+
+    if (out->standby || !out->data)
+        return 0;
+
+    LOGV_IF(!out->bt_enabled, "Standby skip stop: enabled %d", out->bt_enabled);
+    if (out->bt_enabled)
+        ret = a2dp_stop(out->data);
+    release_wake_lock(A2DP_WAKE_LOCK_NAME);
+    out->standby = true;
+
+    return ret;
+}
+
+static int out_close_stream_locked(struct astream_out *out)
+{
+    out_standby_stream_locked(out);
+
+    if (out->data) {
+        LOGV("%s: calling a2dp_cleanup()", __func__);
+        a2dp_cleanup(out->data);
+        out->data = NULL;
+    }
+
+    return 0;
+}
+
+static int out_standby(struct audio_stream *stream)
+{
+    struct astream_out *out = (struct astream_out *)stream;
+
+    pthread_mutex_lock(&out->lock);
+    out_standby_stream_locked(out);
+    pthread_mutex_unlock(&out->lock);
+
+    return 0;
+}
+
+static int out_set_parameters(struct audio_stream *stream, const char *kvpairs)
+{
+    struct astream_out *out = (struct astream_out *)stream;
+    struct str_parms *parms;
+    char *str;
+    char value[32];
+    int ret;
+
+    parms = str_parms_create_str(kvpairs);
+
+    pthread_mutex_lock(&out->lock);
+
+    ret = str_parms_get_str(parms, OUT_SINK_ADDR_PARM, value, sizeof(value));
+    if (ret >= 0) {
+        /* strlen(00:00:00:00:00:00) == 17 */
+        if (strlen(value) == 17) {
+            strlcpy(out->a2dp_addr, value, sizeof(out->a2dp_addr));
+            if (out->data)
+                a2dp_set_sink(out->data, out->a2dp_addr);
+        } else
+            ret = -EINVAL;
+    }
+
+    pthread_mutex_unlock(&out->lock);
+    str_parms_destroy(parms);
+    return ret;
+}
+
+static audio_devices_t out_get_device(const struct audio_stream *stream)
+{
+    const struct astream_out *out = (const struct astream_out *)stream;
+    return out->device;
+}
+
+
+static int out_set_device(struct audio_stream *stream, audio_devices_t device)
+{
+    struct astream_out *out = (struct astream_out *)stream;
+
+    if (!audio_is_a2dp_device(device))
+        return -EINVAL;
+
+    /* XXX: if out->device ever starts getting used for anything, need to
+     * grab the out->lock */
+    out->device = device;
+    return 0;
+}
+
+static char * out_get_parameters(const struct audio_stream *stream,
+                                 const char *keys)
+{
+    struct astream_out *out = (struct astream_out *)stream;
+    struct str_parms *parms;
+    struct str_parms *out_parms;
+    char *str;
+    char value[20];
+    int ret;
+
+    parms = str_parms_create_str(keys);
+    out_parms = str_parms_create();
+
+    pthread_mutex_lock(&out->lock);
+
+    ret = str_parms_get_str(parms, OUT_SINK_ADDR_PARM, value, sizeof(value));
+    if (ret >= 0)
+        str_parms_add_str(out_parms, OUT_SINK_ADDR_PARM, out->a2dp_addr);
+
+    pthread_mutex_unlock(&out->lock);
+
+    str = str_parms_to_str(out_parms);
+    str_parms_destroy(out_parms);
+    str_parms_destroy(parms);
+
+    return str;
+}
+
+static ssize_t out_write(struct audio_stream_out *stream, const void* buffer,
+                         size_t bytes)
+{
+    struct astream_out *out = (struct astream_out *)stream;
+    int ret;
+    int cnt = bytes;
+    int retries = MAX_WRITE_RETRIES;
+    uint64_t now;
+    uint32_t elapsed_us;
+    const uint8_t *buf = buffer;
+
+    pthread_mutex_lock(&out->lock);
+    if (!out->bt_enabled || out->suspended) {
+        LOGV("a2dp %s: bluetooth disabled bt_en %d, suspended %d",
+             out->bt_enabled, out->suspended);
+        ret = -1;
+        goto err_bt_disabled;
+    }
+
+    if (out->standby) {
+        acquire_wake_lock(PARTIAL_WAKE_LOCK, A2DP_WAKE_LOCK_NAME);
+        out->standby = false;
+        out->last_write_time = system_time();
+    }
+
+    ret = _out_init_locked(out, NULL);
+    if (ret < 0)
+        goto err_init;
+
+    while (cnt > 0 && retries > 0) {
+        ret = a2dp_write(out->data, buf, cnt);
+        if (ret < 0) {
+            LOGE("%s: a2dp_write failed (%d)\n", __func__, ret);
+            goto err_write;
+        } else if (ret == 0) {
+            retries--;
+            continue;
+        }
+
+        cnt -= ret;
+        buf += ret;
+    }
+
+    /* XXX: PLEASE FIX ME!!!! */
+
+    /* if A2DP sink runs abnormally fast, sleep a little so that
+     * audioflinger mixer thread does no spin and starve other threads. */
+    /* NOTE: It is likely that the A2DP headset is being disconnected */
+    now = system_time();
+    elapsed_us = (now - out->last_write_time) / 1000UL;
+    if (elapsed_us < (out->buffer_duration_us / 4)) {
+        LOGV("A2DP sink runs too fast");
+        usleep(out->buffer_duration_us - elapsed_us);
+    }
+    out->last_write_time = now;
+
+    pthread_mutex_unlock(&out->lock);
+
+    return bytes;
+
+err_write:
+err_init:
+err_bt_disabled:
+    out_standby_stream_locked(out);
+    pthread_mutex_unlock(&out->lock);
+
+    /* XXX: simulate audio output timing in case of error?!?! */
+    usleep(out->buffer_duration_us);
+    return ret;
+}
+
+static int _out_bt_enable(struct astream_out *out, bool enable)
+{
+    int ret = 0;
+
+    pthread_mutex_lock(&out->lock);
+    out->bt_enabled = enable;
+    if (!enable)
+        ret = out_close_stream_locked(out);
+    pthread_mutex_unlock(&out->lock);
+
+    return ret;
+}
+
+static int _out_a2dp_suspend(struct astream_out *out, bool suspend)
+{
+    pthread_mutex_lock(&out->lock);
+    out->suspended = suspend;
+    out_standby_stream_locked(out);
+    pthread_mutex_unlock(&out->lock);
+
+    return 0;
+}
+
+static int adev_open_output_stream(struct audio_hw_device *dev,
+                                   uint32_t devices, int *format,
+                                   uint32_t *channels, uint32_t *sample_rate,
+                                   struct audio_stream_out **stream_out)
+{
+    struct adev_a2dp *adev = (struct adev_a2dp *)dev;
+    struct astream_out *out;
+    int ret;
+
+    pthread_mutex_lock(&adev->lock);
+
+    /* one output stream at a time */
+    if (adev->output) {
+        LOGV("output exists");
+        ret = -EBUSY;
+        goto err_output_exists;
+    }
+
+    out = calloc(1, sizeof(struct astream_out));
+    if (!out) {
+        ret = -ENOMEM;
+        goto err_alloc;
+    }
+
+    pthread_mutex_init(&out->lock, NULL);
+
+    out->stream.common.get_sample_rate = out_get_sample_rate;
+    out->stream.common.set_sample_rate = out_set_sample_rate;
+    out->stream.common.get_buffer_size = out_get_buffer_size;
+    out->stream.common.get_channels = out_get_channels;
+    out->stream.common.get_format = out_get_format;
+    out->stream.common.set_format = out_set_format;
+    out->stream.common.standby = out_standby;
+    out->stream.common.dump = out_dump;
+    out->stream.common.set_parameters = out_set_parameters;
+    out->stream.common.get_parameters = out_get_parameters;
+    out->stream.common.set_device = out_set_device;
+    out->stream.common.get_device = out_get_device;
+    out->stream.get_latency = out_get_latency;
+    out->stream.set_volume = out_set_volume;
+    out->stream.write = out_write;
+    out->stream.get_render_position = out_get_render_position;
+
+    out->sample_rate = 44100;
+    out->buffer_size = 512 * 20;
+    out->channels = AUDIO_CHANNEL_OUT_STEREO;
+    out->format = AUDIO_FORMAT_PCM_16_BIT;
+
+    out->fd = -1;
+    out->device = devices;
+    out->bt_enabled = adev->bt_enabled;
+    out->suspended = adev->suspended;
+
+    /* for now, buffer_duration_us is precalculated and never changed.
+     * if the sample rate or the format ever changes on the fly, we'd have
+     * to recalculate this */
+    out->buffer_duration_us = ((out->buffer_size * 1000 ) /
+                               audio_stream_frame_size(&out->stream.common) /
+                               out->sample_rate) * 1000;
+    if (!_out_validate_parms(out, format ? *format : 0,
+                             channels ? *channels : 0,
+                             sample_rate ? *sample_rate : 0)) {
+        LOGV("invalid parameters");
+        ret = -EINVAL;
+        goto err_validate_parms;
+    }
+
+    /* XXX: check return code? */
+    if (adev->bt_enabled)
+        _out_init_locked(out, "00:00:00:00:00:00");
+
+    adev->output = out;
+
+    if (format)
+        *format = out->format;
+    if (channels)
+        *channels = out->channels;
+    if (sample_rate)
+        *sample_rate = out->sample_rate;
+
+    pthread_mutex_unlock(&adev->lock);
+
+    *stream_out = &out->stream;
+
+    return 0;
+
+err_validate_parms:
+    free(out);
+err_alloc:
+err_output_exists:
+    pthread_mutex_unlock(&adev->lock);
+    *stream_out = NULL;
+    return ret;
+}
+
+/* needs the adev->lock held */
+static void adev_close_output_stream_locked(struct adev_a2dp *dev,
+                                            struct astream_out *stream)
+{
+    struct adev_a2dp *adev = (struct adev_a2dp *)dev;
+    struct astream_out *out = (struct astream_out *)stream;
+
+    /* invalid stream? */
+    if (!adev->output || adev->output != out) {
+        LOGE("%s: unknown stream %p (ours is %p)", __func__, out, adev->output);
+        return;
+    }
+
+    pthread_mutex_lock(&out->lock);
+    out_close_stream_locked(out);
+    pthread_mutex_unlock(&out->lock);
+
+    adev->output = NULL;
+    free(out);
+}
+
+static void adev_close_output_stream(struct audio_hw_device *dev,
+                                     struct audio_stream_out *stream)
+{
+    struct adev_a2dp *adev = (struct adev_a2dp *)dev;
+    struct astream_out *out = (struct astream_out *)stream;
+
+    pthread_mutex_lock(&adev->lock);
+    adev_close_output_stream_locked(adev, out);
+    pthread_mutex_unlock(&adev->lock);
+}
+
+static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs)
+{
+    struct adev_a2dp *adev = (struct adev_a2dp *)dev;
+    struct str_parms *parms;
+    char *str;
+    char value[8];
+    int ret;
+
+    parms = str_parms_create_str(kvpairs);
+
+    pthread_mutex_lock(&adev->lock);
+
+    ret = str_parms_get_str(parms, BLUETOOOTH_ENABLED_PARM, value,
+                            sizeof(value));
+    if (ret >= 0) {
+        adev->bt_enabled = !strcmp(value, "true");
+        if (adev->output)
+            _out_bt_enable(adev->output, adev->bt_enabled);
+    }
+
+    ret = str_parms_get_str(parms, A2DP_SUSPENDED_PARM, value, sizeof(value));
+    if (ret >= 0) {
+        adev->suspended = !strcmp(value, "true");
+        if (adev->output)
+            _out_a2dp_suspend(adev->output, adev->suspended);
+    }
+
+    pthread_mutex_unlock(&adev->lock);
+
+    str_parms_destroy(parms);
+
+    return ret;
+}
+
+static char * adev_get_parameters(const struct audio_hw_device *dev,
+                                  const char *keys)
+{
+    struct adev_a2dp *adev = (struct adev_a2dp *)dev;
+    struct str_parms *parms;
+    struct str_parms *out_parms;
+    char *str;
+    char value[8];
+    int ret;
+
+    parms = str_parms_create_str(keys);
+    out_parms = str_parms_create();
+
+    pthread_mutex_lock(&adev->lock);
+
+    ret = str_parms_get_str(parms, BLUETOOOTH_ENABLED_PARM, value,
+                            sizeof(value));
+    if (ret >= 0)
+        str_parms_add_str(out_parms, BLUETOOOTH_ENABLED_PARM,
+                          adev->bt_enabled ? "true" : "false");
+
+    ret = str_parms_get_str(parms, A2DP_SUSPENDED_PARM, value, sizeof(value));
+    if (ret >= 0)
+        str_parms_add_str(out_parms, A2DP_SUSPENDED_PARM,
+                          adev->suspended ? "true" : "false");
+
+    pthread_mutex_unlock(&adev->lock);
+
+    str = str_parms_to_str(out_parms);
+    str_parms_destroy(out_parms);
+    str_parms_destroy(parms);
+
+    return str;
+}
+
+static int adev_init_check(const struct audio_hw_device *dev)
+{
+    return 0;
+}
+
+static int adev_set_voice_volume(struct audio_hw_device *dev, float volume)
+{
+    return -ENOSYS;
+}
+
+static int adev_set_master_volume(struct audio_hw_device *dev, float volume)
+{
+    return -ENOSYS;
+}
+
+static int adev_set_mode(struct audio_hw_device *dev, int mode)
+{
+    /* TODO: do we care for the mode? */
+    return 0;
+}
+
+static int adev_set_mic_mute(struct audio_hw_device *dev, bool state)
+{
+    return -ENOSYS;
+}
+
+static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state)
+{
+    return -ENOSYS;
+}
+
+static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev,
+                                         uint32_t sample_rate, int format,
+                                         int channel_count)
+{
+    /* no input */
+    return 0;
+}
+
+static int adev_open_input_stream(struct audio_hw_device *dev, uint32_t devices,
+                                  int *format, uint32_t *channels,
+                                  uint32_t *sample_rate,
+                                  audio_in_acoustics_t acoustics,
+                                  struct audio_stream_in **stream_in)
+{
+    return -ENOSYS;
+}
+
+static void adev_close_input_stream(struct audio_hw_device *dev,
+                                   struct audio_stream_in *in)
+{
+    return;
+}
+
+static int adev_dump(const audio_hw_device_t *device, int fd)
+{
+    return 0;
+}
+
+static int adev_close(hw_device_t *device)
+{
+    struct adev_a2dp *adev = (struct adev_a2dp *)device;
+
+    pthread_mutex_lock(&adev->lock);
+    if (adev->output)
+        adev_close_output_stream_locked(adev, adev->output);
+    pthread_mutex_unlock(&adev->lock);
+    free(adev);
+
+    return 0;
+}
+
+static uint32_t adev_get_supported_devices(const struct audio_hw_device *dev)
+{
+    return AUDIO_DEVICE_OUT_ALL_A2DP;
+}
+
+static int adev_open(const hw_module_t* module, const char* name,
+                     hw_device_t** device)
+{
+    struct adev_a2dp *adev;
+    int ret;
+
+    if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0)
+        return -EINVAL;
+
+    adev = calloc(1, sizeof(struct adev_a2dp));
+    if (!adev)
+        return -ENOMEM;
+
+    adev->bt_enabled = true;
+    adev->suspended = false;
+    pthread_mutex_init(&adev->lock, NULL);
+    adev->output = NULL;
+
+    adev->device.common.tag = HARDWARE_DEVICE_TAG;
+    adev->device.common.version = 0;
+    adev->device.common.module = (struct hw_module_t *) module;
+    adev->device.common.close = adev_close;
+
+    adev->device.get_supported_devices = adev_get_supported_devices;
+    adev->device.init_check = adev_init_check;
+    adev->device.set_voice_volume = adev_set_voice_volume;
+    adev->device.set_master_volume = adev_set_master_volume;
+    adev->device.set_mode = adev_set_mode;
+    adev->device.set_mic_mute = adev_set_mic_mute;
+    adev->device.get_mic_mute = adev_get_mic_mute;
+    adev->device.set_parameters = adev_set_parameters;
+    adev->device.get_parameters = adev_get_parameters;
+    adev->device.get_input_buffer_size = adev_get_input_buffer_size;
+    adev->device.open_output_stream = adev_open_output_stream;
+    adev->device.close_output_stream = adev_close_output_stream;
+    adev->device.open_input_stream = adev_open_input_stream;
+    adev->device.close_input_stream = adev_close_input_stream;
+    adev->device.dump = adev_dump;
+
+    *device = &adev->device.common;
+
+    return 0;
+
+err_str_parms_create:
+    free(adev);
+    return ret;
+}
+
+static struct hw_module_methods_t hal_module_methods = {
+    .open = adev_open,
+};
+
+struct audio_module HAL_MODULE_INFO_SYM = {
+    .common = {
+        .tag = HARDWARE_MODULE_TAG,
+        .version_major = 1,
+        .version_minor = 0,
+        .id = AUDIO_HARDWARE_MODULE_ID,
+        .name = "A2DP Audio HW HAL",
+        .author = "The Android Open Source Project",
+        .methods = &hal_module_methods,
+    },
+};
+
+
diff --git a/audio/audio.conf b/audio/audio.conf
index a093ffb..e44e768 100644
--- a/audio/audio.conf
+++ b/audio/audio.conf
@@ -34,6 +34,13 @@
 # Maximum number of connected HSP/HFP devices per adapter. Defaults to 1
 #MaxConnections=1
 
+# Set to true to enable use of fast connectable mode (faster page scanning)
+# for HFP when incomming call starts. Default settings are restored after
+# call is answered or rejected. Page scan interval is much shorter and page
+# scan type changed to interlaced. Such allows faster connection initiated
+# by a headset.
+FastConnectable=false
+
 # Just an example of potential config options for the other interfaces
 [A2DP]
 SBCSources=1
diff --git a/audio/avdtp.c b/audio/avdtp.c
index cf07f6c..6ba9794 100644
--- a/audio/avdtp.c
+++ b/audio/avdtp.c
@@ -44,8 +44,8 @@
 
 #include "log.h"
 
-#include "../src/manager.h"
 #include "../src/adapter.h"
+#include "../src/manager.h"
 #include "../src/device.h"
 
 #include "device.h"
@@ -57,8 +57,6 @@
 #include "sink.h"
 #include "source.h"
 
-#include <bluetooth/l2cap.h>
-
 #define AVDTP_PSM 25
 
 #define MAX_SEID 0x3E
@@ -394,6 +392,9 @@
 	/* True if the session should be automatically disconnected */
 	gboolean auto_dc;
 
+	/* True if the entire device is being disconnected */
+	gboolean device_disconnect;
+
 	GIOChannel *io;
 	guint io_id;
 
@@ -537,6 +538,12 @@
 		return try_send(sock, session->buf, sizeof(single) + len);
 	}
 
+	/* Check if there is enough space to start packet */
+	if (session->omtu < sizeof(start)) {
+		error("No enough space to fragment packet");
+		return FALSE;
+	}
+
 	/* Count the number of needed fragments */
 	cont_fragments = (len - (session->omtu - sizeof(start))) /
 					(session->omtu - sizeof(cont)) + 1;
@@ -679,38 +686,40 @@
 	if (session->dc_timer)
 		remove_disconnect_timer(session);
 
+	if (session->device_disconnect) {
+		g_idle_add(disconnect_timeout, session);
+		return;
+	}
+
 	session->dc_timer = g_timeout_add_seconds(DISCONNECT_TIMEOUT,
 						disconnect_timeout,
 						session);
 }
 
-void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id)
+void avdtp_error_init(struct avdtp_error *err, uint8_t category, int id)
 {
-	err->type = type;
-	switch (type) {
-	case AVDTP_ERROR_ERRNO:
+	err->category = category;
+
+	if (category == AVDTP_ERRNO)
 		err->err.posix_errno = id;
-		break;
-	case AVDTP_ERROR_ERROR_CODE:
+	else
 		err->err.error_code = id;
-		break;
-	}
 }
 
-avdtp_error_type_t avdtp_error_type(struct avdtp_error *err)
+uint8_t avdtp_error_category(struct avdtp_error *err)
 {
-	return err->type;
+	return err->category;
 }
 
 int avdtp_error_error_code(struct avdtp_error *err)
 {
-	assert(err->type == AVDTP_ERROR_ERROR_CODE);
+	assert(err->category != AVDTP_ERRNO);
 	return err->err.error_code;
 }
 
 int avdtp_error_posix_errno(struct avdtp_error *err)
 {
-	assert(err->type == AVDTP_ERROR_ERRNO);
+	assert(err->category == AVDTP_ERRNO);
 	return err->err.posix_errno;
 }
 
@@ -781,7 +790,7 @@
 		g_source_remove(stream->timer);
 
 	if (stream->io)
-		g_io_channel_unref(stream->io);
+		close_stream(stream);
 
 	if (stream->io_id)
 		g_source_remove(stream->io_id);
@@ -829,6 +838,40 @@
 	return FALSE;
 }
 
+static int get_send_buffer_size(int sk)
+{
+	int size;
+	socklen_t optlen = sizeof(size);
+
+	if (getsockopt(sk, SOL_SOCKET, SO_SNDBUF, &size, &optlen) < 0) {
+		int err = -errno;
+		error("getsockopt(SO_SNDBUF) failed: %s (%d)", strerror(-err),
+									-err);
+		return err;
+	}
+
+	/*
+	 * Doubled value is returned by getsockopt since kernel uses that
+	 * space for its own purposes (see man 7 socket, bookkeeping overhead
+	 * for SO_SNDBUF).
+	 */
+	return size / 2;
+}
+
+static int set_send_buffer_size(int sk, int size)
+{
+	socklen_t optlen = sizeof(size);
+
+	if (setsockopt(sk, SOL_SOCKET, SO_SNDBUF, &size, optlen) < 0) {
+		int err = -errno;
+		error("setsockopt(SO_SNDBUF) failed: %s (%d)", strerror(-err),
+									-err);
+		return err;
+	}
+
+	return 0;
+}
+
 static void handle_transport_connect(struct avdtp *session, GIOChannel *io,
 					uint16_t imtu, uint16_t omtu)
 {
@@ -845,17 +888,38 @@
 	if (io == NULL) {
 		if (!stream->open_acp && sep->cfm && sep->cfm->open) {
 			struct avdtp_error err;
-			avdtp_error_init(&err, AVDTP_ERROR_ERRNO, EIO);
+			avdtp_error_init(&err, AVDTP_ERRNO, EIO);
 			sep->cfm->open(session, sep, NULL, &err,
 					sep->user_data);
 		}
 		return;
 	}
 
-	stream->io = g_io_channel_ref(io);
+	if (stream->io == NULL)
+		stream->io = g_io_channel_ref(io);
+
 	stream->omtu = omtu;
 	stream->imtu = imtu;
 
+	/* only if local SEP is of type SRC */
+	if (sep->info.type == AVDTP_SEP_TYPE_SOURCE) {
+		int sk, buf_size, min_buf_size;
+
+		sk = g_io_channel_unix_get_fd(stream->io);
+		buf_size = get_send_buffer_size(sk);
+		if (buf_size < 0)
+			goto proceed;
+
+		DBG("sk %d, omtu %d, send buffer size %d", sk, omtu, buf_size);
+		min_buf_size = omtu * 2;
+		if (buf_size < min_buf_size) {
+			DBG("send buffer size to be increassed to %d",
+								min_buf_size);
+			set_send_buffer_size(sk, min_buf_size);
+		}
+	}
+
+proceed:
 	if (!stream->open_acp && sep->cfm && sep->cfm->open)
 		sep->cfm->open(session, sep, stream, NULL, sep->user_data);
 
@@ -913,7 +977,7 @@
 	req = session->req;
 	session->req = NULL;
 
-	avdtp_error_init(&err, AVDTP_ERROR_ERRNO, EIO);
+	avdtp_error_init(&err, AVDTP_ERRNO, EIO);
 
 	lsep = stream->lsep;
 
@@ -973,7 +1037,7 @@
 	}
 
 	if (sep->state == state) {
-		avdtp_error_init(&err, AVDTP_ERROR_ERRNO, EIO);
+		avdtp_error_init(&err, AVDTP_ERRNO, EIO);
 		DBG("stream state change failed: %s", avdtp_strerror(&err));
 		err_ptr = &err;
 	} else {
@@ -1023,8 +1087,6 @@
 		/* Remove pending commands for this stream from the queue */
 		cleanup_queue(session, stream);
 		stream_free(stream);
-		if (session->ref == 1 && !session->streams)
-			set_disconnect_timer(session);
 		break;
 	default:
 		break;
@@ -1035,7 +1097,7 @@
 {
 	struct avdtp_error avdtp_err;
 
-	avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, err);
+	avdtp_error_init(&avdtp_err, AVDTP_ERRNO, err);
 
 	if (!session->discov_cb)
 		return;
@@ -1129,6 +1191,8 @@
 			g_io_channel_shutdown(session->io, TRUE, NULL);
 			g_io_channel_unref(session->io);
 			session->io = NULL;
+			avdtp_set_state(session,
+					AVDTP_SESSION_STATE_DISCONNECTED);
 		}
 
 		if (session->io)
@@ -1185,22 +1249,36 @@
 	return NULL;
 }
 
-static struct avdtp_local_sep *find_local_sep(struct avdtp_server *server,
-						uint8_t type,
-						uint8_t media_type,
-						uint8_t codec)
+struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session,
+						struct avdtp_local_sep *lsep)
 {
 	GSList *l;
 
-	for (l = server->seps; l != NULL; l = g_slist_next(l)) {
-		struct avdtp_local_sep *sep = l->data;
+	if (lsep->info.inuse)
+		return NULL;
 
-		if (sep->info.inuse)
+	for (l = session->seps; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_remote_sep *sep = l->data;
+		struct avdtp_service_capability *cap;
+		struct avdtp_media_codec_capability *codec_data;
+
+		/* Type must be different: source <-> sink */
+		if (sep->type == lsep->info.type)
 			continue;
 
-		if (sep->info.type == type &&
-				sep->info.media_type == media_type &&
-				sep->codec == codec)
+		if (sep->media_type != lsep->info.media_type)
+			continue;
+
+		if (!sep->codec)
+			continue;
+
+		cap = sep->codec;
+		codec_data = (void *) cap->data;
+
+		if (codec_data->media_codec_type != lsep->codec)
+			continue;
+
+		if (sep->stream == NULL)
 			return sep;
 	}
 
@@ -1342,6 +1420,35 @@
 							&err, sizeof(err));
 }
 
+static void setconf_cb(struct avdtp *session, struct avdtp_stream *stream,
+						struct avdtp_error *err)
+{
+	struct conf_rej rej;
+	struct avdtp_local_sep *sep;
+
+	if (err != NULL) {
+		rej.error = AVDTP_UNSUPPORTED_CONFIGURATION;
+		rej.category = err->err.error_code;
+		avdtp_send(session, session->in.transaction,
+				AVDTP_MSG_TYPE_REJECT, AVDTP_SET_CONFIGURATION,
+				&rej, sizeof(rej));
+		return;
+	}
+
+	if (!avdtp_send(session, session->in.transaction, AVDTP_MSG_TYPE_ACCEPT,
+					AVDTP_SET_CONFIGURATION, NULL, 0)) {
+		stream_free(stream);
+		return;
+	}
+
+	sep = stream->lsep;
+	sep->stream = stream;
+	sep->info.inuse = 1;
+	session->streams = g_slist_append(session->streams, stream);
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED);
+}
+
 static gboolean avdtp_setconf_cmd(struct avdtp *session, uint8_t transaction,
 				struct setconf_req *req, unsigned int size)
 {
@@ -1389,7 +1496,14 @@
 		}
 		break;
 	case AVDTP_SEP_TYPE_SINK:
-		/* Do source_init() here when it's implemented */
+		if (!dev->source) {
+			btd_device_add_uuid(dev->btd_dev, A2DP_SOURCE_UUID);
+			if (!dev->sink) {
+				error("Unable to get a audio source object");
+				err = AVDTP_BAD_STATE;
+				goto failed;
+			}
+		}
 		break;
 	}
 
@@ -1417,24 +1531,27 @@
 
 	if (sep->ind && sep->ind->set_configuration) {
 		if (!sep->ind->set_configuration(session, sep, stream,
-							stream->caps, &err,
-							&category,
-							sep->user_data))
+							stream->caps,
+							setconf_cb,
+							sep->user_data)) {
+			err = AVDTP_UNSUPPORTED_CONFIGURATION;
+			category = 0x00;
 			goto failed_stream;
-	}
-
-	if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+		}
+	} else {
+		if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
 					AVDTP_SET_CONFIGURATION, NULL, 0)) {
-		stream_free(stream);
-		return FALSE;
+			stream_free(stream);
+			return FALSE;
+		}
+
+		sep->stream = stream;
+		sep->info.inuse = 1;
+		session->streams = g_slist_append(session->streams, stream);
+
+		avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED);
 	}
 
-	sep->stream = stream;
-	sep->info.inuse = 1;
-	session->streams = g_slist_append(session->streams, stream);
-
-	avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED);
-
 	return TRUE;
 
 failed_stream:
@@ -1974,7 +2091,8 @@
 {
 	struct avdtp *session = data;
 	struct avdtp_common_header *header;
-	gsize size;
+	ssize_t size;
+	int fd;
 
 	DBG("");
 
@@ -1986,13 +2104,14 @@
 	if (cond & (G_IO_HUP | G_IO_ERR))
 		goto failed;
 
-	if (g_io_channel_read(chan, session->buf, session->imtu, &size)
-							!= G_IO_ERROR_NONE) {
+	fd = g_io_channel_unix_get_fd(chan);
+	size = read(fd, session->buf, session->imtu);
+	if (size < 0) {
 		error("IO Channel read error");
 		goto failed;
 	}
 
-	if (size < sizeof(struct avdtp_common_header)) {
+	if ((size_t) size < sizeof(struct avdtp_common_header)) {
 		error("Received too small packet (%zu bytes)", size);
 		goto failed;
 	}
@@ -2372,7 +2491,7 @@
 	g_io_channel_shutdown(chan, TRUE, NULL);
 }
 
-static int l2cap_connect(struct avdtp *session)
+static GIOChannel *l2cap_connect(struct avdtp *session)
 {
 	GError *err = NULL;
 	GIOChannel *io;
@@ -2386,12 +2505,10 @@
 	if (!io) {
 		error("%s", err->message);
 		g_error_free(err);
-		return -EIO;
+		return NULL;
 	}
 
-	g_io_channel_unref(io);
-
-	return 0;
+	return io;
 }
 
 static void queue_request(struct avdtp *session, struct pending_req *req,
@@ -2423,7 +2540,7 @@
 	req = session->req;
 	session->req = NULL;
 
-	avdtp_error_init(&averr, AVDTP_ERROR_ERRNO, err);
+	avdtp_error_init(&averr, AVDTP_ERRNO, err);
 
 	seid = req_get_seid(req);
 	if (seid)
@@ -2526,8 +2643,8 @@
 	int err;
 
 	if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) {
-		err = l2cap_connect(session);
-		if (err < 0)
+		session->io = l2cap_connect(session);
+		if (!session->io)
 			goto failed;
 		avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTING);
 	}
@@ -2549,7 +2666,6 @@
 		goto failed;
 	}
 
-
 	session->req = req;
 
 	req->timeout = g_timeout_add_seconds(req->signal_id == AVDTP_ABORT ?
@@ -2570,8 +2686,10 @@
 {
 	struct pending_req *req;
 
-	if (stream && stream->abort_int && signal_id != AVDTP_ABORT)
+	if (stream && stream->abort_int && signal_id != AVDTP_ABORT) {
+		DBG("Unable to send requests while aborting");
 		return -EINVAL;
+	}
 
 	req = g_new0(struct pending_req, 1);
 	req->signal_id = signal_id;
@@ -2703,7 +2821,8 @@
 {
 	struct avdtp_local_sep *sep = stream->lsep;
 
-	if (l2cap_connect(session) < 0) {
+	stream->io = l2cap_connect(session);
+	if (!stream->io) {
 		avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE);
 		return FALSE;
 	}
@@ -2863,23 +2982,20 @@
 		return FALSE;
 	}
 
-	avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error);
+	avdtp_error_init(err, 0x00, rej->error);
 
 	return TRUE;
 }
 
 static gboolean conf_rej_to_err(struct conf_rej *rej, unsigned int size,
-				struct avdtp_error *err, uint8_t *category)
+				struct avdtp_error *err)
 {
 	if (size < sizeof(struct conf_rej)) {
 		error("Too small packet for conf_rej");
 		return FALSE;
 	}
 
-	avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error);
-
-	if (category)
-		*category = rej->category;
+	avdtp_error_init(err, rej->category, rej->error);
 
 	return TRUE;
 }
@@ -2893,7 +3009,7 @@
 		return FALSE;
 	}
 
-	avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error);
+	avdtp_error_init(err, 0x00, rej->error);
 
 	if (acp_seid)
 		*acp_seid = rej->acp_seid;
@@ -2907,7 +3023,7 @@
 					void *buf, int size)
 {
 	struct avdtp_error err;
-	uint8_t acp_seid, category;
+	uint8_t acp_seid;
 	struct avdtp_local_sep *sep = stream ? stream->lsep : NULL;
 
 	switch (signal_id) {
@@ -2934,7 +3050,7 @@
 					sep->user_data);
 		return TRUE;
 	case AVDTP_SET_CONFIGURATION:
-		if (!conf_rej_to_err(buf, size, &err, &category))
+		if (!conf_rej_to_err(buf, size, &err))
 			return FALSE;
 		error("SET_CONFIGURATION request rejected: %s (%d)",
 				avdtp_strerror(&err), err.err.error_code);
@@ -2943,7 +3059,7 @@
 							&err, sep->user_data);
 		return TRUE;
 	case AVDTP_RECONFIGURE:
-		if (!conf_rej_to_err(buf, size, &err, &category))
+		if (!conf_rej_to_err(buf, size, &err))
 			return FALSE;
 		error("RECONFIGURE request rejected: %s (%d)",
 				avdtp_strerror(&err), err.err.error_code);
@@ -3073,6 +3189,12 @@
 	return TRUE;
 }
 
+struct avdtp_remote_sep *avdtp_stream_get_remote_sep(
+						struct avdtp_stream *stream)
+{
+	return avdtp_get_remote_sep(stream->session, stream->rseid);
+}
+
 gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock,
 					uint16_t *imtu, uint16_t *omtu,
 					GSList **caps)
@@ -3208,49 +3330,6 @@
 	return err;
 }
 
-int avdtp_get_seps(struct avdtp *session, uint8_t acp_type, uint8_t media_type,
-			uint8_t codec, struct avdtp_local_sep **lsep,
-			struct avdtp_remote_sep **rsep)
-{
-	GSList *l;
-	uint8_t int_type;
-
-	int_type = acp_type == AVDTP_SEP_TYPE_SINK ?
-				AVDTP_SEP_TYPE_SOURCE : AVDTP_SEP_TYPE_SINK;
-
-	*lsep = find_local_sep(session->server, int_type, media_type, codec);
-	if (!*lsep)
-		return -EINVAL;
-
-	for (l = session->seps; l != NULL; l = g_slist_next(l)) {
-		struct avdtp_remote_sep *sep = l->data;
-		struct avdtp_service_capability *cap;
-		struct avdtp_media_codec_capability *codec_data;
-
-		if (sep->type != acp_type)
-			continue;
-
-		if (sep->media_type != media_type)
-			continue;
-
-		if (!sep->codec)
-			continue;
-
-		cap = sep->codec;
-		codec_data = (void *) cap->data;
-
-		if (codec_data->media_codec_type != codec)
-			continue;
-
-		if (!sep->stream) {
-			*rsep = sep;
-			return 0;
-		}
-	}
-
-	return -EINVAL;
-}
-
 gboolean avdtp_stream_remove_cb(struct avdtp *session,
 				struct avdtp_stream *stream,
 				unsigned int id)
@@ -3348,8 +3427,14 @@
 	new_stream->lsep = lsep;
 	new_stream->rseid = rsep->seid;
 
-	if (rsep->delay_reporting && lsep->delay_reporting)
+	if (rsep->delay_reporting && lsep->delay_reporting) {
+		struct avdtp_service_capability *delay_reporting;
+
+		delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+								NULL, 0);
+		caps = g_slist_append(caps, delay_reporting);
 		new_stream->delay_reporting = TRUE;
+	}
 
 	g_slist_foreach(caps, copy_capabilities, &new_stream->caps);
 
@@ -3524,8 +3609,7 @@
 	if (!g_slist_find(session->streams, stream))
 		return -EINVAL;
 
-	if (stream->lsep->state == AVDTP_STATE_IDLE ||
-			stream->lsep->state == AVDTP_STATE_ABORTING)
+	if (stream->lsep->state == AVDTP_STATE_ABORTING)
 		return -EINVAL;
 
 	if (session->req && stream == session->req->stream)
@@ -3619,6 +3703,9 @@
 	if (sep->stream)
 		release_stream(sep->stream, sep->stream->session);
 
+	DBG("SEP %p unregistered: type:%d codec:%d seid:%d", sep,
+			sep->info.type, sep->codec, sep->info.seid);
+
 	g_free(sep);
 
 	return 0;
@@ -3646,7 +3733,7 @@
 
 const char *avdtp_strerror(struct avdtp_error *err)
 {
-	if (err->type == AVDTP_ERROR_ERRNO)
+	if (err->category == AVDTP_ERRNO)
 		return strerror(err->err.posix_errno);
 
 	switch(err->err.error_code) {
@@ -3790,6 +3877,11 @@
 	return session->stream_setup;
 }
 
+void avdtp_set_device_disconnect(struct avdtp *session, gboolean dev_dc)
+{
+	session->device_disconnect = dev_dc;
+}
+
 unsigned int avdtp_add_state_cb(avdtp_session_state_cb cb, void *user_data)
 {
 	struct avdtp_state_callback *state_cb;
diff --git a/audio/avdtp.h b/audio/avdtp.h
index 3fe682b..5f37dc3 100644
--- a/audio/avdtp.h
+++ b/audio/avdtp.h
@@ -23,11 +23,6 @@
  */
 
 typedef enum {
-	AVDTP_ERROR_ERRNO,
-	AVDTP_ERROR_ERROR_CODE
-} avdtp_error_type_t;
-
-typedef enum {
 	AVDTP_SESSION_STATE_DISCONNECTED,
 	AVDTP_SESSION_STATE_CONNECTING,
 	AVDTP_SESSION_STATE_CONNECTED
@@ -38,7 +33,7 @@
 struct avdtp_local_sep;
 struct avdtp_remote_sep;
 struct avdtp_error {
-	avdtp_error_type_t type;
+	uint8_t category;
 	union {
 		uint8_t error_code;
 		int posix_errno;
@@ -54,6 +49,7 @@
 #define AVDTP_MULTIPLEXING			0x06
 #define AVDTP_MEDIA_CODEC			0x07
 #define AVDTP_DELAY_REPORTING			0x08
+#define AVDTP_ERRNO				0xff
 
 /* AVDTP error definitions */
 #define AVDTP_BAD_HEADER_FORMAT			0x01
@@ -132,6 +128,10 @@
 					struct avdtp_error *err,
 					void *user_data);
 
+typedef void (*avdtp_set_configuration_cb) (struct avdtp *session,
+						struct avdtp_stream *stream,
+						struct avdtp_error *err);
+
 /* Callbacks for when a reply is received to a command that we sent */
 struct avdtp_sep_cfm {
 	void (*set_configuration) (struct avdtp *session,
@@ -179,8 +179,9 @@
 	gboolean (*set_configuration) (struct avdtp *session,
 					struct avdtp_local_sep *lsep,
 					struct avdtp_stream *stream,
-					GSList *caps, uint8_t *err,
-					uint8_t *category, void *user_data);
+					GSList *caps,
+					avdtp_set_configuration_cb cb,
+					void *user_data);
 	gboolean (*get_configuration) (struct avdtp *session,
 					struct avdtp_local_sep *lsep,
 					uint8_t *err, void *user_data);
@@ -256,6 +257,8 @@
 				struct avdtp_service_capability *cap);
 gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream,
 					GSList *caps);
+struct avdtp_remote_sep *avdtp_stream_get_remote_sep(
+						struct avdtp_stream *stream);
 
 unsigned int avdtp_add_state_cb(avdtp_session_state_cb cb, void *user_data);
 
@@ -290,9 +293,8 @@
 						void *user_data);
 
 /* Find a matching pair of local and remote SEP ID's */
-int avdtp_get_seps(struct avdtp *session, uint8_t type, uint8_t media,
-			uint8_t codec, struct avdtp_local_sep **lsep,
-			struct avdtp_remote_sep **rsep);
+struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session,
+						struct avdtp_local_sep *lsep);
 
 int avdtp_unregister_sep(struct avdtp_local_sep *sep);
 
@@ -300,7 +302,7 @@
 
 void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id);
 const char *avdtp_strerror(struct avdtp_error *err);
-avdtp_error_type_t avdtp_error_type(struct avdtp_error *err);
+uint8_t avdtp_error_category(struct avdtp_error *err);
 int avdtp_error_error_code(struct avdtp_error *err);
 int avdtp_error_posix_errno(struct avdtp_error *err);
 
@@ -308,6 +310,7 @@
 
 void avdtp_set_auto_disconnect(struct avdtp *session, gboolean auto_dc);
 gboolean avdtp_stream_setup_active(struct avdtp *session);
+void avdtp_set_device_disconnect(struct avdtp *session, gboolean dev_dc);
 
 int avdtp_init(const bdaddr_t *src, GKeyFile *config, uint16_t *version);
 void avdtp_exit(const bdaddr_t *src);
diff --git a/audio/control.c b/audio/control.c
index 1674b0d..2fe5e5e 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -40,7 +40,6 @@
 #include <bluetooth/bluetooth.h>
 #include <bluetooth/sdp.h>
 #include <bluetooth/sdp_lib.h>
-#include <bluetooth/l2cap.h>
 
 #include <glib.h>
 #include <dbus/dbus.h>
@@ -442,7 +441,7 @@
 {
 	GSList *l;
 	struct audio_device *dev = control->dev;
-	avdtp_session_state_t old_state = control->state;
+	avctp_state_t old_state = control->state;
 	gboolean value;
 
 	switch (new_state) {
@@ -998,7 +997,7 @@
 	struct avctp_header *avctp = (void *) buf;
 	struct avrcp_header *avrcp = (void *) &buf[AVCTP_HEADER_LENGTH];
 	uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH];
-	int err, sk = g_io_channel_unix_get_fd(control->io);
+	int sk = g_io_channel_unix_get_fd(control->io);
 	static uint8_t transaction = 0;
 
 	memset(buf, 0, sizeof(buf));
@@ -1015,15 +1014,17 @@
 	operands[0] = op & 0x7f;
 	operands[1] = 0;
 
-	err = write(sk, buf, sizeof(buf));
-	if (err < 0)
-		return err;
+	if (write(sk, buf, sizeof(buf)) < 0)
+		return -errno;
 
 	/* Button release */
 	avctp->transaction = transaction++;
 	operands[0] |= 0x80;
 
-	return write(sk, buf, sizeof(buf));
+	if (write(sk, buf, sizeof(buf)) < 0)
+		return -errno;
+
+	return 0;
 }
 
 static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg,
@@ -1039,19 +1040,14 @@
 		return NULL;
 
 	if (control->state != AVCTP_STATE_CONNECTED)
-		return g_dbus_create_error(msg,
-					ERROR_INTERFACE ".NotConnected",
-					"Device not Connected");
+		return btd_error_not_connected(msg);
 
 	if (!control->target)
-		return g_dbus_create_error(msg,
-					ERROR_INTERFACE ".NotSupported",
-					"AVRCP Target role not supported");
+		return btd_error_not_supported(msg);
 
 	err = avctp_send_passthrough(control, VOL_UP_OP);
 	if (err < 0)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-							strerror(-err));
+		return btd_error_failed(msg, strerror(-err));
 
 	return dbus_message_new_method_return(msg);
 }
@@ -1069,19 +1065,14 @@
 		return NULL;
 
 	if (control->state != AVCTP_STATE_CONNECTED)
-		return g_dbus_create_error(msg,
-					ERROR_INTERFACE ".NotConnected",
-					"Device not Connected");
+		return btd_error_not_connected(msg);
 
 	if (!control->target)
-		return g_dbus_create_error(msg,
-					ERROR_INTERFACE ".NotSupported",
-					"AVRCP Target role not supported");
+		return btd_error_not_supported(msg);
 
 	err = avctp_send_passthrough(control, VOL_DOWN_OP);
 	if (err < 0)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-							strerror(-err));
+		return btd_error_failed(msg, strerror(-err));
 
 	return dbus_message_new_method_return(msg);
 }
diff --git a/audio/device.c b/audio/device.c
index ea54f18..bc80227 100644
--- a/audio/device.c
+++ b/audio/device.c
@@ -34,8 +34,6 @@
 #include <netinet/in.h>
 
 #include <bluetooth/bluetooth.h>
-#include <bluetooth/hci.h>
-#include <bluetooth/hci_lib.h>
 #include <bluetooth/sdp.h>
 #include <bluetooth/sdp_lib.h>
 
@@ -91,7 +89,9 @@
 	guint control_timer;
 	guint avdtp_timer;
 	guint headset_timer;
+	guint dc_id;
 
+	gboolean disconnecting;
 	gboolean authorized;
 	guint auth_idle_id;
 };
@@ -111,6 +111,8 @@
 	btd_device_unref(dev->btd_dev);
 
 	if (priv) {
+		if (priv->auths)
+			audio_device_cancel_authorization(dev, NULL, NULL);
 		if (priv->control_timer)
 			g_source_remove(priv->control_timer);
 		if (priv->avdtp_timer)
@@ -121,6 +123,9 @@
 			dbus_message_unref(priv->dc_req);
 		if (priv->conn_req)
 			dbus_message_unref(priv->conn_req);
+		if (priv->dc_id)
+			device_remove_disconnect_watch(dev->btd_dev,
+							priv->dc_id);
 		g_free(priv);
 	}
 
@@ -143,52 +148,6 @@
 	}
 }
 
-static void device_set_state(struct audio_device *dev, audio_state_t new_state)
-{
-	struct dev_priv *priv = dev->priv;
-	const char *state_str;
-	DBusMessage *reply = NULL;
-
-	state_str = state2str(new_state);
-	if (!state_str)
-		return;
-
-	if (new_state == AUDIO_STATE_DISCONNECTED)
-		priv->authorized = FALSE;
-
-	if (dev->priv->state == new_state) {
-		DBG("state change attempted from %s to %s",
-							state_str, state_str);
-		return;
-	}
-
-	dev->priv->state = new_state;
-
-	if (priv->dc_req && new_state == AUDIO_STATE_DISCONNECTED) {
-		reply = dbus_message_new_method_return(priv->dc_req);
-		dbus_message_unref(priv->dc_req);
-		priv->dc_req = NULL;
-		g_dbus_send_message(dev->conn, reply);
-	}
-
-	if (priv->conn_req && new_state != AUDIO_STATE_CONNECTING) {
-		if (new_state == AUDIO_STATE_CONNECTED)
-			reply = dbus_message_new_method_return(priv->conn_req);
-		else
-			reply = g_dbus_create_error(priv->conn_req,
-							ERROR_INTERFACE
-							".ConnectFailed",
-							"Connecting failed");
-		dbus_message_unref(priv->conn_req);
-		priv->conn_req = NULL;
-		g_dbus_send_message(dev->conn, reply);
-	}
-
-	emit_property_changed(dev->conn, dev->path,
-				AUDIO_INTERFACE, "State",
-				DBUS_TYPE_STRING, &state_str);
-}
-
 static gboolean control_connect_timeout(gpointer user_data)
 {
 	struct audio_device *dev = user_data;
@@ -225,6 +184,106 @@
 	dev->priv->control_timer = 0;
 }
 
+static void device_remove_avdtp_timer(struct audio_device *dev)
+{
+	if (dev->priv->avdtp_timer)
+		g_source_remove(dev->priv->avdtp_timer);
+	dev->priv->avdtp_timer = 0;
+}
+
+static void device_remove_headset_timer(struct audio_device *dev)
+{
+	if (dev->priv->headset_timer)
+		g_source_remove(dev->priv->headset_timer);
+	dev->priv->headset_timer = 0;
+}
+
+static void disconnect_cb(struct btd_device *btd_dev, gboolean removal,
+				void *user_data)
+{
+	struct audio_device *dev = user_data;
+	struct dev_priv *priv = dev->priv;
+
+	if (priv->state == AUDIO_STATE_DISCONNECTED)
+		return;
+
+	if (priv->disconnecting)
+		return;
+
+	priv->disconnecting = TRUE;
+
+	device_remove_control_timer(dev);
+	device_remove_avdtp_timer(dev);
+	device_remove_headset_timer(dev);
+
+	if (dev->control)
+		avrcp_disconnect(dev);
+
+	if (dev->sink && priv->sink_state != SINK_STATE_DISCONNECTED)
+		sink_shutdown(dev->sink);
+	else if (priv->hs_state != HEADSET_STATE_DISCONNECTED)
+		headset_shutdown(dev);
+	else
+		priv->disconnecting = FALSE;
+}
+
+static void device_set_state(struct audio_device *dev, audio_state_t new_state)
+{
+	struct dev_priv *priv = dev->priv;
+	const char *state_str;
+	DBusMessage *reply = NULL;
+
+	state_str = state2str(new_state);
+	if (!state_str)
+		return;
+
+	if (new_state == AUDIO_STATE_DISCONNECTED) {
+		priv->authorized = FALSE;
+
+		if (priv->dc_id) {
+			device_remove_disconnect_watch(dev->btd_dev,
+							priv->dc_id);
+			priv->dc_id = 0;
+		}
+	} else if (new_state == AUDIO_STATE_CONNECTING)
+		priv->dc_id = device_add_disconnect_watch(dev->btd_dev,
+						disconnect_cb, dev, NULL);
+
+	if (dev->priv->state == new_state) {
+		DBG("state change attempted from %s to %s",
+							state_str, state_str);
+		return;
+	}
+
+	dev->priv->state = new_state;
+
+	if (new_state == AUDIO_STATE_DISCONNECTED) {
+		if (priv->dc_req) {
+			reply = dbus_message_new_method_return(priv->dc_req);
+			dbus_message_unref(priv->dc_req);
+			priv->dc_req = NULL;
+			g_dbus_send_message(dev->conn, reply);
+		}
+		priv->disconnecting = FALSE;
+	}
+
+	if (priv->conn_req && new_state != AUDIO_STATE_CONNECTING) {
+		if (new_state == AUDIO_STATE_CONNECTED)
+			reply = dbus_message_new_method_return(priv->conn_req);
+		else
+			reply = btd_error_failed(priv->conn_req,
+							"Connect Failed");
+
+		dbus_message_unref(priv->conn_req);
+		priv->conn_req = NULL;
+		g_dbus_send_message(dev->conn, reply);
+	}
+
+	emit_property_changed(dev->conn, dev->path,
+				AUDIO_INTERFACE, "State",
+				DBUS_TYPE_STRING, &state_str);
+}
+
 static gboolean avdtp_connect_timeout(gpointer user_data)
 {
 	struct audio_device *dev = user_data;
@@ -261,13 +320,6 @@
 	return TRUE;
 }
 
-static void device_remove_avdtp_timer(struct audio_device *dev)
-{
-	if (dev->priv->avdtp_timer)
-		g_source_remove(dev->priv->avdtp_timer);
-	dev->priv->avdtp_timer = 0;
-}
-
 static gboolean headset_connect_timeout(gpointer user_data)
 {
 	struct audio_device *dev = user_data;
@@ -304,13 +356,6 @@
 	return TRUE;
 }
 
-static void device_remove_headset_timer(struct audio_device *dev)
-{
-	if (dev->priv->headset_timer)
-		g_source_remove(dev->priv->headset_timer);
-	dev->priv->headset_timer = 0;
-}
-
 static void device_avdtp_cb(struct audio_device *dev, struct avdtp *session,
 				avdtp_session_state_t old_state,
 				avdtp_session_state_t new_state,
@@ -346,7 +391,7 @@
 			avrcp_disconnect(dev);
 		}
 		if (priv->hs_state != HEADSET_STATE_DISCONNECTED &&
-								priv->dc_req) {
+				(priv->dc_req || priv->disconnecting)) {
 			headset_shutdown(dev);
 			break;
 		}
@@ -428,8 +473,8 @@
 	switch (new_state) {
 	case HEADSET_STATE_DISCONNECTED:
 		device_remove_avdtp_timer(dev);
-		if (priv->sink_state != SINK_STATE_DISCONNECTED &&
-						dev->sink && priv->dc_req) {
+		if (priv->sink_state != SINK_STATE_DISCONNECTED && dev->sink &&
+				(priv->dc_req || priv->disconnecting)) {
 			sink_shutdown(dev->sink);
 			break;
 		}
@@ -476,12 +521,9 @@
 	struct dev_priv *priv = dev->priv;
 
 	if (priv->state == AUDIO_STATE_CONNECTING)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress",
-						"Connect in Progress");
+		return btd_error_in_progress(msg);
 	else if (priv->state == AUDIO_STATE_CONNECTED)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".AlreadyConnected",
-						"Already Connected");
+		return btd_error_already_connected(msg);
 
 	dev->auto_connect = TRUE;
 
@@ -492,8 +534,7 @@
 		struct avdtp *session = avdtp_get(&dev->src, &dev->dst);
 
 		if (!session)
-			return g_dbus_create_error(msg, ERROR_INTERFACE
-					".Failed",
+			return btd_error_failed(msg,
 					"Failed to get AVDTP session");
 
 		sink_setup_stream(dev->sink, session);
@@ -503,9 +544,7 @@
 	/* The previous calls should cause a call to the state callback to
 	 * indicate AUDIO_STATE_CONNECTING */
 	if (priv->state != AUDIO_STATE_CONNECTING)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-				".ConnectFailed",
-				"Headset connect failed");
+		return btd_error_failed(msg, "Connect Failed");
 
 	priv->conn_req = dbus_message_ref(msg);
 
@@ -519,8 +558,7 @@
 	struct dev_priv *priv = dev->priv;
 
 	if (priv->state == AUDIO_STATE_DISCONNECTED)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".NotConnected",
-						"Not connected");
+		return btd_error_not_connected(msg);
 
 	if (priv->dc_req)
 		return dbus_message_new_method_return(msg);
diff --git a/audio/device.h b/audio/device.h
index 6ec3938..35af788 100644
--- a/audio/device.h
+++ b/audio/device.h
@@ -22,21 +22,21 @@
  *
  */
 
-#define GENERIC_AUDIO_UUID	"00001203-0000-1000-8000-00805F9B34FB"
+#define GENERIC_AUDIO_UUID	"00001203-0000-1000-8000-00805f9b34fb"
 
-#define HSP_HS_UUID		"00001108-0000-1000-8000-00805F9B34FB"
-#define HSP_AG_UUID		"00001112-0000-1000-8000-00805F9B34FB"
+#define HSP_HS_UUID		"00001108-0000-1000-8000-00805f9b34fb"
+#define HSP_AG_UUID		"00001112-0000-1000-8000-00805f9b34fb"
 
-#define HFP_HS_UUID		"0000111E-0000-1000-8000-00805F9B34FB"
-#define HFP_AG_UUID		"0000111F-0000-1000-8000-00805F9B34FB"
+#define HFP_HS_UUID		"0000111e-0000-1000-8000-00805f9b34fb"
+#define HFP_AG_UUID		"0000111f-0000-1000-8000-00805f9b34fb"
 
-#define ADVANCED_AUDIO_UUID	"0000110D-0000-1000-8000-00805F9B34FB"
+#define ADVANCED_AUDIO_UUID	"0000110d-0000-1000-8000-00805f9b34fb"
 
-#define A2DP_SOURCE_UUID	"0000110A-0000-1000-8000-00805F9B34FB"
-#define A2DP_SINK_UUID		"0000110B-0000-1000-8000-00805F9B34FB"
+#define A2DP_SOURCE_UUID	"0000110a-0000-1000-8000-00805f9b34fb"
+#define A2DP_SINK_UUID		"0000110b-0000-1000-8000-00805f9b34fb"
 
-#define AVRCP_REMOTE_UUID	"0000110E-0000-1000-8000-00805F9B34FB"
-#define AVRCP_TARGET_UUID	"0000110C-0000-1000-8000-00805F9B34FB"
+#define AVRCP_REMOTE_UUID	"0000110e-0000-1000-8000-00805f9b34fb"
+#define AVRCP_TARGET_UUID	"0000110c-0000-1000-8000-00805f9b34fb"
 
 /* Move these to respective .h files once they exist */
 #define AUDIO_SOURCE_INTERFACE		"org.bluez.AudioSource"
diff --git a/audio/gateway.c b/audio/gateway.c
index 07ebdd4..ec0ec5d 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -39,9 +39,6 @@
 #include <gdbus.h>
 
 #include <bluetooth/bluetooth.h>
-#include <bluetooth/hci.h>
-#include <bluetooth/hci_lib.h>
-#include <bluetooth/sco.h>
 #include <bluetooth/sdp.h>
 #include <bluetooth/sdp_lib.h>
 
@@ -250,18 +247,18 @@
 	if (ret)
 		reply = dbus_message_new_method_return(gw->msg);
 	else
-		reply = g_dbus_create_error(gw->msg, ERROR_INTERFACE ".Failed",
-					"Can not pass file descriptor");
+		reply = btd_error_failed(gw->msg, "Can't pass file descriptor");
 
 	g_dbus_send_message(dev->conn, reply);
 
 	return;
 
 fail:
-	if (gw->msg)
-		error_common_reply(dev->conn, gw->msg,
-						ERROR_INTERFACE ".Failed",
-						"Connection attempt failed");
+	if (gw->msg) {
+		DBusMessage *reply;
+		reply = btd_error_failed(gw->msg, "Connect failed");
+		g_dbus_send_message(dev->conn, reply);
+	}
 
 	change_state(dev, GATEWAY_STATE_DISCONNECTED);
 }
@@ -337,10 +334,11 @@
 	return;
 
 fail:
-	if (gw->msg)
-		error_common_reply(dev->conn, gw->msg,
-					ERROR_INTERFACE ".NotSupported",
-					"Not supported");
+	if (gw->msg) {
+		DBusMessage *reply = btd_error_failed(gw->msg,
+					gerr ? gerr->message : strerror(-err));
+		g_dbus_send_message(dev->conn, reply);
+	}
 
 	change_state(dev, GATEWAY_STATE_DISCONNECTED);
 
@@ -368,15 +366,14 @@
 {
 	struct audio_device *au_dev = (struct audio_device *) data;
 	struct gateway *gw = au_dev->gateway;
+	int err;
 
 	if (!gw->agent)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-				".Failed", "Agent not assigned");
+		return btd_error_agent_not_available(msg);
 
-	if (get_records(au_dev) < 0)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-					".ConnectAttemptFailed",
-					"Connect Attempt Failed");
+	err = get_records(au_dev);
+	if (err < 0)
+		return btd_error_failed(msg, strerror(-err));
 
 	gw->msg = dbus_message_ref(msg);
 
@@ -426,9 +423,7 @@
 		return NULL;
 
 	if (!gw->rfcomm)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".NotConnected",
-						"Device not Connected");
+		return  btd_error_not_connected(msg);
 
 	gateway_close(device);
 	ba2str(&device->dst, gw_addr);
@@ -488,15 +483,11 @@
 	const char *path, *name;
 
 	if (gw->agent)
-		return g_dbus_create_error(msg,
-					ERROR_INTERFACE ".AlreadyExists",
-					"Agent already exists");
+		return btd_error_already_exists(msg);
 
 	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
 						DBUS_TYPE_INVALID))
-		return g_dbus_create_error(msg,
-					ERROR_INTERFACE ".InvalidArguments",
-					"Invalid argument");
+		return btd_error_invalid_args(msg);
 
 	name = dbus_message_get_sender(msg);
 	agent = g_new0(struct hf_agent, 1);
@@ -523,20 +514,15 @@
 		goto done;
 
 	if (strcmp(gw->agent->name, dbus_message_get_sender(msg)) != 0)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-							"Permission denied");
+		return btd_error_not_authorized(msg);
 
 	if (!dbus_message_get_args(msg, NULL,
 				DBUS_TYPE_OBJECT_PATH, &path,
 				DBUS_TYPE_INVALID))
-		return g_dbus_create_error(msg,
-				ERROR_INTERFACE ".InvalidArguments",
-				"Invalid argument");
+		return btd_error_invalid_args(msg);
 
 	if (strcmp(gw->agent->path, path) != 0)
-		return g_dbus_create_error(msg,
-				ERROR_INTERFACE ".Failed",
-				"Unknown object path");
+		return btd_error_does_not_exist(msg);
 
 	g_dbus_remove_watch(device->conn, gw->agent->watch);
 
@@ -721,4 +707,5 @@
 	gw->sco = NULL;
 	gw->sco_start_cb = NULL;
 	gw->sco_start_cb_data = NULL;
+	change_state(dev, GATEWAY_STATE_CONNECTED);
 }
diff --git a/audio/gsta2dpsink.c b/audio/gsta2dpsink.c
index 492fc63..930d14e 100644
--- a/audio/gsta2dpsink.c
+++ b/audio/gsta2dpsink.c
@@ -31,8 +31,6 @@
 #include "gstpragma.h"
 #include "gsta2dpsink.h"
 
-GType gst_avdtp_sink_get_type(void);
-
 GST_DEBUG_CATEGORY_STATIC(gst_a2dp_sink_debug);
 #define GST_CAT_DEFAULT gst_a2dp_sink_debug
 
@@ -44,7 +42,8 @@
 enum {
 	PROP_0,
 	PROP_DEVICE,
-	PROP_AUTOCONNECT
+	PROP_AUTOCONNECT,
+	PROP_TRANSPORT
 };
 
 GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBin, GST_TYPE_BIN);
@@ -175,6 +174,16 @@
 		self->device = g_value_dup_string(value);
 		break;
 
+	case PROP_TRANSPORT:
+		if (self->sink != NULL)
+			gst_avdtp_sink_set_transport(self->sink,
+				g_value_get_string(value));
+
+		if (self->transport != NULL)
+			g_free(self->transport);
+		self->transport = g_value_dup_string(value);
+		break;
+
 	case PROP_AUTOCONNECT:
 		self->autoconnect = g_value_get_boolean(value);
 
@@ -193,7 +202,7 @@
 					GValue *value, GParamSpec *pspec)
 {
 	GstA2dpSink *self = GST_A2DP_SINK(object);
-	gchar *device;
+	gchar *device, *transport;
 
 	switch (prop_id) {
 	case PROP_DEVICE:
@@ -210,6 +219,13 @@
 
 		g_value_set_boolean(value, self->autoconnect);
 		break;
+	case PROP_TRANSPORT:
+		if (self->sink != NULL) {
+			transport = gst_avdtp_sink_get_transport(self->sink);
+			if (transport != NULL)
+				g_value_take_string(value, transport);
+		}
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
 		break;
@@ -285,6 +301,10 @@
 			gst_avdtp_sink_set_device(self->sink,
 					self->device);
 
+		if (self->transport != NULL)
+			gst_avdtp_sink_set_transport(self->sink,
+					self->transport);
+
 		g_object_set(G_OBJECT(self->sink), "auto-connect",
 					self->autoconnect, NULL);
 
@@ -365,6 +385,11 @@
 			"Automatically attempt to connect to device",
 			DEFAULT_AUTOCONNECT, G_PARAM_READWRITE));
 
+	g_object_class_install_property(object_class, PROP_TRANSPORT,
+			g_param_spec_string("transport", "Transport",
+			"Use configured transport",
+			NULL, G_PARAM_READWRITE));
+
 	GST_DEBUG_CATEGORY_INIT(gst_a2dp_sink_debug, "a2dpsink", 0,
 				"A2DP sink element");
 }
@@ -437,6 +462,7 @@
 	self->sink = GST_AVDTP_SINK(sink);
 	self->sink_is_in_bin = TRUE;
 	g_object_set(G_OBJECT(self->sink), "device", self->device, NULL);
+	g_object_set(G_OBJECT(self->sink), "transport", self->transport, NULL);
 
 	gst_element_set_state(sink, GST_STATE_PAUSED);
 
@@ -674,6 +700,7 @@
 	self->fakesink = NULL;
 	self->rtp = NULL;
 	self->device = NULL;
+	self->transport = NULL;
 	self->autoconnect = DEFAULT_AUTOCONNECT;
 	self->capsfilter = NULL;
 	self->newseg_event = NULL;
diff --git a/audio/gsta2dpsink.h b/audio/gsta2dpsink.h
index 3ff4e45..5f96a3e 100644
--- a/audio/gsta2dpsink.h
+++ b/audio/gsta2dpsink.h
@@ -53,6 +53,7 @@
 	GstElement *fakesink;
 
 	gchar *device;
+	gchar *transport;
 	gboolean autoconnect;
 	gboolean sink_is_in_bin;
 
@@ -72,7 +73,7 @@
 	GstBinClass parent_class;
 };
 
-//GType gst_a2dp_sink_get_type(void);
+GType gst_a2dp_sink_get_type(void);
 
 gboolean gst_a2dp_sink_plugin_init (GstPlugin * plugin);
 
diff --git a/audio/gstavdtpsink.c b/audio/gstavdtpsink.c
index f0e5b76..ce1365a 100644
--- a/audio/gstavdtpsink.c
+++ b/audio/gstavdtpsink.c
@@ -37,8 +37,11 @@
 
 #include <gst/rtp/gstrtpbuffer.h>
 
+#include <dbus/dbus.h>
+
 #include "ipc.h"
 #include "rtp.h"
+#include "a2dp-codecs.h"
 
 #include "gstpragma.h"
 #include "gstavdtpsink.h"
@@ -61,11 +64,20 @@
 		g_mutex_unlock(s->sink_lock);		\
 	} G_STMT_END
 
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
 
 struct bluetooth_data {
 	struct bt_get_capabilities_rsp *caps; /* Bluetooth device caps */
 	guint link_mtu;
 
+	DBusConnection *conn;
+	guint8 codec; /* Bluetooth transport configuration */
+	gchar *uuid;
+	guint8 *config;
+	gint config_size;
+
 	gchar buffer[BUFFER_SIZE];	/* Codec transfer buffer */
 };
 
@@ -75,7 +87,8 @@
 enum {
 	PROP_0,
 	PROP_DEVICE,
-	PROP_AUTOCONNECT
+	PROP_AUTOCONNECT,
+	PROP_TRANSPORT
 };
 
 GST_BOILERPLATE(GstAvdtpSink, gst_avdtp_sink, GstBaseSink,
@@ -109,12 +122,11 @@
 				"encoding-name = (string) \"MPA\""
 				));
 
-static GIOError gst_avdtp_sink_audioservice_send(GstAvdtpSink *self,
+static int gst_avdtp_sink_audioservice_send(GstAvdtpSink *self,
 					const bt_audio_msg_header_t *msg);
-static GIOError gst_avdtp_sink_audioservice_expect(
-				GstAvdtpSink *self,
-				bt_audio_msg_header_t *outmsg,
-				guint8 expected_name);
+static int gst_avdtp_sink_audioservice_expect(GstAvdtpSink *self,
+					bt_audio_msg_header_t *outmsg,
+					guint8 expected_name);
 
 
 static void gst_avdtp_sink_base_init(gpointer g_class)
@@ -127,6 +139,23 @@
 	gst_element_class_set_details(element_class, &avdtp_sink_details);
 }
 
+static void gst_avdtp_sink_transport_release(GstAvdtpSink *self)
+{
+	DBusMessage *msg;
+	const char *access_type = "w";
+
+	msg = dbus_message_new_method_call("org.bluez", self->transport,
+						"org.bluez.MediaTransport",
+						"Release");
+
+	dbus_message_append_args(msg, DBUS_TYPE_STRING, &access_type,
+					DBUS_TYPE_INVALID);
+
+	dbus_connection_send(self->data->conn, msg, NULL);
+
+	dbus_message_unref(msg);
+}
+
 static gboolean gst_avdtp_sink_stop(GstBaseSink *basesink)
 {
 	GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
@@ -151,6 +180,10 @@
 	}
 
 	if (self->data) {
+		if (self->transport)
+			gst_avdtp_sink_transport_release(self);
+		if (self->data->conn)
+			dbus_connection_unref(self->data->conn);
 		g_free(self->data);
 		self->data = NULL;
 	}
@@ -178,6 +211,9 @@
 	if (self->device)
 		g_free(self->device);
 
+	if (self->transport)
+		g_free(self->transport);
+
 	g_mutex_free(self->sink_lock);
 
 	G_OBJECT_CLASS(parent_class)->finalize(object);
@@ -198,6 +234,13 @@
 	case PROP_AUTOCONNECT:
 		sink->autoconnect = g_value_get_boolean(value);
 		break;
+
+	case PROP_TRANSPORT:
+		if (sink->transport)
+			g_free(sink->transport);
+		sink->transport = g_value_dup_string(value);
+		break;
+
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
 		break;
@@ -217,6 +260,11 @@
 	case PROP_AUTOCONNECT:
 		g_value_set_boolean(value, sink->autoconnect);
 		break;
+
+	case PROP_TRANSPORT:
+		g_value_set_string(value, sink->transport);
+		break;
+
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
 		break;
@@ -238,6 +286,7 @@
 	}
 
 	sink->stream = g_io_channel_unix_new(ret);
+	g_io_channel_set_encoding(sink->stream, NULL, NULL);
 	GST_DEBUG_OBJECT(sink, "stream_fd=%d", ret);
 
 	return 0;
@@ -364,11 +413,14 @@
 {
 	struct bluetooth_data *data = self->data;
 	gint ret;
-	GIOError err;
 	GError *gerr = NULL;
 	GIOStatus status;
 	GIOFlags flags;
-	gsize read;
+	int fd;
+
+	/* Proceed if stream was already acquired */
+	if (self->stream != NULL)
+		goto proceed;
 
 	ret = gst_avdtp_sink_bluetooth_recvmsg_fd(self);
 	if (ret < 0)
@@ -380,6 +432,7 @@
 		return FALSE;
 	}
 
+proceed:
 	/* set stream socket to nonblock */
 	GST_LOG_OBJECT(self, "setting stream socket to nonblock");
 	flags = g_io_channel_get_flags(self->stream);
@@ -396,14 +449,14 @@
 					"socket to nonblock");
 	}
 
+	fd = g_io_channel_unix_get_fd(self->stream);
+
 	/* It is possible there is some outstanding
 	data in the pipe - we have to empty it */
 	GST_LOG_OBJECT(self, "emptying stream pipe");
 	while (1) {
-		err = g_io_channel_read(self->stream, data->buffer,
-					(gsize) data->link_mtu,
-					&read);
-		if (err != G_IO_ERROR_NONE || read <= 0)
+		ssize_t bread = read(fd, data->buffer, data->link_mtu);
+		if (bread <= 0)
 			break;
 	}
 
@@ -448,6 +501,9 @@
 	GValue *list;
 	gboolean mono, stereo;
 
+	if (sbc == NULL)
+		return NULL;
+
 	structure = gst_structure_empty_new("audio/x-sbc");
 	value = g_value_init(g_new0(GValue, 1), G_TYPE_STRING);
 
@@ -730,6 +786,325 @@
 	return structure;
 }
 
+static GstStructure *gst_avdtp_sink_parse_sbc_raw(GstAvdtpSink *self)
+{
+	a2dp_sbc_t *sbc = (a2dp_sbc_t *) self->data->config;
+	GstStructure *structure;
+	GValue *value;
+	GValue *list;
+	gboolean mono, stereo;
+
+	structure = gst_structure_empty_new("audio/x-sbc");
+	value = g_value_init(g_new0(GValue, 1), G_TYPE_STRING);
+
+	/* mode */
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) {
+		g_value_set_static_string(value, "mono");
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) {
+		g_value_set_static_string(value, "stereo");
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) {
+		g_value_set_static_string(value, "dual");
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) {
+		g_value_set_static_string(value, "joint");
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "mode", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* subbands */
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	value = g_value_init(value, G_TYPE_INT);
+	if (sbc->subbands & BT_A2DP_SUBBANDS_4) {
+		g_value_set_int(value, 4);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->subbands & BT_A2DP_SUBBANDS_8) {
+		g_value_set_int(value, 8);
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "subbands", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* blocks */
+	value = g_value_init(value, G_TYPE_INT);
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_16) {
+		g_value_set_int(value, 16);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_12) {
+		g_value_set_int(value, 12);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_8) {
+		g_value_set_int(value, 8);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_4) {
+		g_value_set_int(value, 4);
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "blocks", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* allocation */
+	g_value_init(value, G_TYPE_STRING);
+	list = g_value_init(g_new0(GValue,1), GST_TYPE_LIST);
+	if (sbc->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) {
+		g_value_set_static_string(value, "loudness");
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->allocation_method & BT_A2DP_ALLOCATION_SNR) {
+		g_value_set_static_string(value, "snr");
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "allocation", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* rate */
+	g_value_init(value, G_TYPE_INT);
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	if (sbc->frequency & BT_SBC_SAMPLING_FREQ_48000) {
+		g_value_set_int(value, 48000);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->frequency & BT_SBC_SAMPLING_FREQ_44100) {
+		g_value_set_int(value, 44100);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->frequency & BT_SBC_SAMPLING_FREQ_32000) {
+		g_value_set_int(value, 32000);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->frequency & BT_SBC_SAMPLING_FREQ_16000) {
+		g_value_set_int(value, 16000);
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "rate", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* bitpool */
+	value = g_value_init(value, GST_TYPE_INT_RANGE);
+	gst_value_set_int_range(value,
+			MIN(sbc->min_bitpool, TEMPLATE_MAX_BITPOOL),
+			MIN(sbc->max_bitpool, TEMPLATE_MAX_BITPOOL));
+	gst_structure_set_value(structure, "bitpool", value);
+	g_value_unset(value);
+
+	/* channels */
+	mono = FALSE;
+	stereo = FALSE;
+	if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+		mono = TRUE;
+	if ((sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) ||
+			(sbc->channel_mode &
+			BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) ||
+			(sbc->channel_mode &
+			BT_A2DP_CHANNEL_MODE_JOINT_STEREO))
+		stereo = TRUE;
+
+	if (mono && stereo) {
+		g_value_init(value, GST_TYPE_INT_RANGE);
+		gst_value_set_int_range(value, 1, 2);
+	} else {
+		g_value_init(value, G_TYPE_INT);
+		if (mono)
+			g_value_set_int(value, 1);
+		else if (stereo)
+			g_value_set_int(value, 2);
+		else {
+			GST_ERROR_OBJECT(self,
+				"Unexpected number of channels");
+			g_value_set_int(value, 0);
+		}
+	}
+
+	gst_structure_set_value(structure, "channels", value);
+	g_free(value);
+
+	return structure;
+}
+
+static GstStructure *gst_avdtp_sink_parse_mpeg_raw(GstAvdtpSink *self)
+{
+	a2dp_mpeg_t *mpeg = (a2dp_mpeg_t *) self->data->config;
+	GstStructure *structure;
+	GValue *value;
+	GValue *list;
+	gboolean valid_layer = FALSE;
+	gboolean mono, stereo;
+
+	GST_LOG_OBJECT(self, "parsing mpeg caps");
+
+	structure = gst_structure_empty_new("audio/mpeg");
+	value = g_new0(GValue, 1);
+	g_value_init(value, G_TYPE_INT);
+
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	g_value_set_int(value, 1);
+	gst_value_list_prepend_value(list, value);
+	g_value_set_int(value, 2);
+	gst_value_list_prepend_value(list, value);
+	gst_structure_set_value(structure, "mpegversion", list);
+	g_free(list);
+
+	/* layer */
+	GST_LOG_OBJECT(self, "setting mpeg layer");
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	if (mpeg->layer & BT_MPEG_LAYER_1) {
+		g_value_set_int(value, 1);
+		gst_value_list_prepend_value(list, value);
+		valid_layer = TRUE;
+	}
+	if (mpeg->layer & BT_MPEG_LAYER_2) {
+		g_value_set_int(value, 2);
+		gst_value_list_prepend_value(list, value);
+		valid_layer = TRUE;
+	}
+	if (mpeg->layer & BT_MPEG_LAYER_3) {
+		g_value_set_int(value, 3);
+		gst_value_list_prepend_value(list, value);
+		valid_layer = TRUE;
+	}
+	if (list) {
+		gst_structure_set_value(structure, "layer", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	if (!valid_layer) {
+		gst_structure_free(structure);
+		g_free(value);
+		return NULL;
+	}
+
+	/* rate */
+	GST_LOG_OBJECT(self, "setting mpeg rate");
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_48000) {
+		g_value_set_int(value, 48000);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_44100) {
+		g_value_set_int(value, 44100);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_32000) {
+		g_value_set_int(value, 32000);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_24000) {
+		g_value_set_int(value, 24000);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_22050) {
+		g_value_set_int(value, 22050);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_16000) {
+		g_value_set_int(value, 16000);
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "rate", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* channels */
+	GST_LOG_OBJECT(self, "setting mpeg channels");
+	mono = FALSE;
+	stereo = FALSE;
+	if (mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+		mono = TRUE;
+	if ((mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) ||
+			(mpeg->channel_mode &
+			BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) ||
+			(mpeg->channel_mode &
+			BT_A2DP_CHANNEL_MODE_JOINT_STEREO))
+		stereo = TRUE;
+
+	if (mono && stereo) {
+		g_value_init(value, GST_TYPE_INT_RANGE);
+		gst_value_set_int_range(value, 1, 2);
+	} else {
+		g_value_init(value, G_TYPE_INT);
+		if (mono)
+			g_value_set_int(value, 1);
+		else if (stereo)
+			g_value_set_int(value, 2);
+		else {
+			GST_ERROR_OBJECT(self,
+				"Unexpected number of channels");
+			g_value_set_int(value, 0);
+		}
+	}
+	gst_structure_set_value(structure, "channels", value);
+	g_free(value);
+
+	return structure;
+}
+
+static gboolean gst_avdtp_sink_update_config(GstAvdtpSink *self)
+{
+	GstStructure *structure;
+	gchar *tmp;
+
+	switch (self->data->codec) {
+	case A2DP_CODEC_SBC:
+		structure = gst_avdtp_sink_parse_sbc_raw(self);
+		break;
+	case A2DP_CODEC_MPEG12:
+		structure = gst_avdtp_sink_parse_mpeg_raw(self);
+		break;
+	default:
+		GST_ERROR_OBJECT(self, "Unsupported configuration");
+		return FALSE;
+	}
+
+	if (structure == NULL)
+		return FALSE;
+
+	if (self->dev_caps != NULL)
+		gst_caps_unref(self->dev_caps);
+
+	self->dev_caps = gst_caps_new_full(structure, NULL);
+
+	tmp = gst_caps_to_string(self->dev_caps);
+	GST_DEBUG_OBJECT(self, "Transport configuration: %s", tmp);
+	g_free(tmp);
+
+	return TRUE;
+}
+
 static gboolean gst_avdtp_sink_update_caps(GstAvdtpSink *self)
 {
 	sbc_capabilities_t *sbc;
@@ -740,6 +1115,9 @@
 
 	GST_LOG_OBJECT(self, "updating device caps");
 
+	if (self->data->config_size != 0 && self->data->config != NULL)
+		return gst_avdtp_sink_update_config(self);
+
 	sbc = (void *) gst_avdtp_find_caps(self, BT_A2DP_SBC_SINK);
 	mpeg = (void *) gst_avdtp_find_caps(self, BT_A2DP_MPEG12_SINK);
 
@@ -764,7 +1142,7 @@
 	gchar *buf[BT_SUGGESTED_BUFFER_SIZE];
 	struct bt_get_capabilities_req *req = (void *) buf;
 	struct bt_get_capabilities_rsp *rsp = (void *) buf;
-	GIOError io_error;
+	int err;
 
 	memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
 
@@ -778,16 +1156,16 @@
 	if (self->autoconnect)
 		req->flags |= BT_FLAG_AUTOCONNECT;
 
-	io_error = gst_avdtp_sink_audioservice_send(self, &req->h);
-	if (io_error != G_IO_ERROR_NONE) {
+	err = gst_avdtp_sink_audioservice_send(self, &req->h);
+	if (err < 0) {
 		GST_ERROR_OBJECT(self, "Error while asking device caps");
 		return FALSE;
 	}
 
 	rsp->h.length = 0;
-	io_error = gst_avdtp_sink_audioservice_expect(self,
-			&rsp->h, BT_GET_CAPABILITIES);
-	if (io_error != G_IO_ERROR_NONE) {
+	err = gst_avdtp_sink_audioservice_expect(self,
+					&rsp->h, BT_GET_CAPABILITIES);
+	if (err < 0) {
 		GST_ERROR_OBJECT(self, "Error while getting device caps");
 		return FALSE;
 	}
@@ -867,6 +1245,188 @@
 	return TRUE;
 }
 
+static gboolean gst_avdtp_sink_transport_parse_property(GstAvdtpSink *self,
+							DBusMessageIter *i)
+{
+	const char *key;
+	DBusMessageIter variant_i;
+
+	if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
+		GST_ERROR_OBJECT(self, "Property name not a string.");
+		return FALSE;
+	}
+
+	dbus_message_iter_get_basic(i, &key);
+
+	if (!dbus_message_iter_next(i))  {
+		GST_ERROR_OBJECT(self, "Property value missing");
+		return FALSE;
+	}
+
+	if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
+		GST_ERROR_OBJECT(self, "Property value not a variant.");
+		return FALSE;
+	}
+
+	dbus_message_iter_recurse(i, &variant_i);
+
+	switch (dbus_message_iter_get_arg_type(&variant_i)) {
+	case DBUS_TYPE_BYTE: {
+		uint8_t value;
+		dbus_message_iter_get_basic(&variant_i, &value);
+
+		if (g_str_equal(key, "Codec") == TRUE)
+			self->data->codec = value;
+
+		break;
+	}
+	case DBUS_TYPE_STRING: {
+		const char *value;
+		dbus_message_iter_get_basic(&variant_i, &value);
+
+		if (g_str_equal(key, "UUID") == TRUE) {
+			g_free(self->data->uuid);
+			self->data->uuid = g_strdup(value);
+		}
+
+		break;
+	}
+	case DBUS_TYPE_ARRAY: {
+		DBusMessageIter array_i;
+		char *value;
+		int size;
+
+		dbus_message_iter_recurse(&variant_i, &array_i);
+		dbus_message_iter_get_fixed_array(&array_i, &value, &size);
+
+		if (g_str_equal(key, "Configuration")) {
+			g_free(self->data->config);
+			self->data->config = g_new0(guint8, size);
+			self->data->config_size = size;
+			memcpy(self->data->config, value, size);
+		}
+
+		break;
+	}
+	}
+
+	return TRUE;
+}
+
+static gboolean gst_avdtp_sink_transport_acquire(GstAvdtpSink *self)
+{
+	DBusMessage *msg, *reply;
+	DBusError err;
+	const char *access_type = "w";
+	int fd;
+	uint16_t imtu, omtu;
+
+	dbus_error_init(&err);
+
+	if (self->data->conn == NULL)
+		self->data->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
+
+	msg = dbus_message_new_method_call("org.bluez", self->transport,
+						"org.bluez.MediaTransport",
+						"Acquire");
+
+	dbus_message_append_args(msg, DBUS_TYPE_STRING, &access_type,
+					DBUS_TYPE_INVALID);
+
+	reply = dbus_connection_send_with_reply_and_block(self->data->conn,
+							msg, -1, &err);
+
+	if (dbus_error_is_set(&err))
+		goto fail;
+
+	if (dbus_message_get_args(reply, &err, DBUS_TYPE_UNIX_FD, &fd,
+						DBUS_TYPE_UINT16, &imtu,
+						DBUS_TYPE_UINT16, &omtu,
+						DBUS_TYPE_INVALID) == FALSE)
+		goto fail;
+
+	dbus_message_unref(reply);
+
+	self->stream = g_io_channel_unix_new(fd);
+	g_io_channel_set_encoding(self->stream, NULL, NULL);
+	g_io_channel_set_close_on_unref(self->stream, TRUE);
+	self->data->link_mtu = omtu;
+	GST_DEBUG_OBJECT(self, "stream_fd=%d mtu=%d", fd, omtu);
+
+	return TRUE;
+
+fail:
+	GST_ERROR_OBJECT(self, "Failed to acquire transport stream: %s",
+				err.message);
+
+	dbus_error_free(&err);
+
+	if (reply)
+		dbus_message_unref(msg);
+
+	return FALSE;
+}
+
+static gboolean gst_avdtp_sink_transport_get_properties(GstAvdtpSink *self)
+{
+	DBusMessage *msg, *reply;
+	DBusMessageIter arg_i, ele_i;
+	DBusError err;
+
+	dbus_error_init(&err);
+
+	/* Transport need to be acquire first to make sure the MTUs are
+	   available */
+	if (gst_avdtp_sink_transport_acquire(self) == FALSE)
+		return FALSE;
+
+	msg = dbus_message_new_method_call("org.bluez", self->transport,
+						"org.bluez.MediaTransport",
+						"GetProperties");
+	reply = dbus_connection_send_with_reply_and_block(self->data->conn,
+							msg, -1, &err);
+
+	if (dbus_error_is_set(&err) || reply == NULL) {
+		GST_ERROR_OBJECT(self, "Failed to get transport properties: %s",
+					err.message);
+		goto fail;
+	}
+
+	if (!dbus_message_iter_init(reply, &arg_i)) {
+		GST_ERROR_OBJECT(self, "GetProperties reply has no arguments.");
+		goto fail;
+	}
+
+	if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
+		GST_ERROR_OBJECT(self, "GetProperties argument is not an array.");
+		goto fail;
+	}
+
+	dbus_message_iter_recurse(&arg_i, &ele_i);
+	while (dbus_message_iter_get_arg_type(&ele_i) != DBUS_TYPE_INVALID) {
+
+		if (dbus_message_iter_get_arg_type(&ele_i) ==
+				DBUS_TYPE_DICT_ENTRY) {
+			DBusMessageIter dict_i;
+
+			dbus_message_iter_recurse(&ele_i, &dict_i);
+
+			gst_avdtp_sink_transport_parse_property(self, &dict_i);
+		}
+
+		if (!dbus_message_iter_next(&ele_i))
+			break;
+	}
+
+	return gst_avdtp_sink_update_caps(self);
+
+fail:
+	dbus_message_unref(msg);
+	dbus_message_unref(reply);
+	return FALSE;
+
+}
+
 static gboolean gst_avdtp_sink_start(GstBaseSink *basesink)
 {
 	GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
@@ -875,6 +1435,16 @@
 
 	GST_INFO_OBJECT(self, "start");
 
+	self->data = g_new0(struct bluetooth_data, 1);
+
+	self->stream = NULL;
+	self->stream_caps = NULL;
+	self->mp3_using_crc = -1;
+	self->channel_mode = -1;
+
+	if (self->transport != NULL)
+		return gst_avdtp_sink_transport_get_properties(self);
+
 	self->watch_id = 0;
 
 	sk = bt_audio_service_open();
@@ -886,16 +1456,10 @@
 	}
 
 	self->server = g_io_channel_unix_new(sk);
+	g_io_channel_set_encoding(self->server, NULL, NULL);
 	self->watch_id = g_io_add_watch(self->server, G_IO_HUP | G_IO_ERR |
 					G_IO_NVAL, server_callback, self);
 
-	self->data = g_new0(struct bluetooth_data, 1);
-
-	self->stream = NULL;
-	self->stream_caps = NULL;
-	self->mp3_using_crc = -1;
-	self->channel_mode = -1;
-
 	if (!gst_avdtp_sink_get_capabilities(self)) {
 		GST_ERROR_OBJECT(self, "failed to get capabilities "
 				"from device");
@@ -915,33 +1479,36 @@
 	struct bt_start_stream_req *req = (void *) buf;
 	struct bt_start_stream_rsp *rsp = (void *) buf;
 	struct bt_new_stream_ind *ind = (void *) buf;
-	GIOError io_error;
+	int err;
+
+	if (self->transport != NULL)
+		return gst_avdtp_sink_conf_recv_stream_fd(self);
 
 	memset(req, 0, sizeof(buf));
 	req->h.type = BT_REQUEST;
 	req->h.name = BT_START_STREAM;
 	req->h.length = sizeof(*req);
 
-	io_error = gst_avdtp_sink_audioservice_send(self, &req->h);
-	if (io_error != G_IO_ERROR_NONE) {
+	err = gst_avdtp_sink_audioservice_send(self, &req->h);
+	if (err < 0) {
 		GST_ERROR_OBJECT(self, "Error ocurred while sending "
 					"start packet");
 		return FALSE;
 	}
 
 	rsp->h.length = sizeof(*rsp);
-	io_error = gst_avdtp_sink_audioservice_expect(self,
-			&rsp->h, BT_START_STREAM);
-	if (io_error != G_IO_ERROR_NONE) {
+	err = gst_avdtp_sink_audioservice_expect(self, &rsp->h,
+							BT_START_STREAM);
+	if (err < 0) {
 		GST_ERROR_OBJECT(self, "Error while stream "
 			"start confirmation");
 		return FALSE;
 	}
 
 	ind->h.length = sizeof(*ind);
-	io_error = gst_avdtp_sink_audioservice_expect(self, &ind->h,
-			BT_NEW_STREAM);
-	if (io_error != G_IO_ERROR_NONE) {
+	err = gst_avdtp_sink_audioservice_expect(self, &ind->h,
+							BT_NEW_STREAM);
+	if (err < 0) {
 		GST_ERROR_OBJECT(self, "Error while receiving "
 			"stream filedescriptor");
 		return FALSE;
@@ -1039,15 +1606,19 @@
 	struct bt_set_configuration_req *req = (void *) buf;
 	struct bt_set_configuration_rsp *rsp = (void *) buf;
 	gboolean ret;
-	GIOError io_error;
 	gchar *temp;
 	GstStructure *structure;
 	codec_capabilities_t *codec = NULL;
+	int err;
 
 	temp = gst_caps_to_string(caps);
 	GST_DEBUG_OBJECT(self, "configuring device with caps: %s", temp);
 	g_free(temp);
 
+	/* Transport already configured */
+	if (self->transport != NULL)
+		return TRUE;
+
 	structure = gst_caps_get_structure(caps, 0);
 
 	if (gst_structure_has_name(structure, "audio/x-sbc"))
@@ -1070,17 +1641,17 @@
 	open_req->seid = codec->seid;
 	open_req->lock = BT_WRITE_LOCK;
 
-	io_error = gst_avdtp_sink_audioservice_send(self, &open_req->h);
-	if (io_error != G_IO_ERROR_NONE) {
+	err = gst_avdtp_sink_audioservice_send(self, &open_req->h);
+	if (err < 0) {
 		GST_ERROR_OBJECT(self, "Error ocurred while sending "
 					"open packet");
 		return FALSE;
 	}
 
 	open_rsp->h.length = sizeof(*open_rsp);
-	io_error = gst_avdtp_sink_audioservice_expect(self,
-			&open_rsp->h, BT_OPEN);
-	if (io_error != G_IO_ERROR_NONE) {
+	err = gst_avdtp_sink_audioservice_expect(self, &open_rsp->h,
+								BT_OPEN);
+	if (err < 0) {
 		GST_ERROR_OBJECT(self, "Error while receiving device "
 					"confirmation");
 		return FALSE;
@@ -1106,17 +1677,17 @@
 	}
 
 	req->h.length += req->codec.length - sizeof(req->codec);
-	io_error = gst_avdtp_sink_audioservice_send(self, &req->h);
-	if (io_error != G_IO_ERROR_NONE) {
+	err = gst_avdtp_sink_audioservice_send(self, &req->h);
+	if (err < 0) {
 		GST_ERROR_OBJECT(self, "Error ocurred while sending "
 					"configurarion packet");
 		return FALSE;
 	}
 
 	rsp->h.length = sizeof(*rsp);
-	io_error = gst_avdtp_sink_audioservice_expect(self,
-			&rsp->h, BT_SET_CONFIGURATION);
-	if (io_error != G_IO_ERROR_NONE) {
+	err = gst_avdtp_sink_audioservice_expect(self, &rsp->h,
+							BT_SET_CONFIGURATION);
+	if (err < 0) {
 		GST_ERROR_OBJECT(self, "Error while receiving device "
 					"confirmation");
 		return FALSE;
@@ -1149,16 +1720,15 @@
 					GstBuffer *buffer)
 {
 	GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
-	gsize ret;
-	GIOError err;
+	ssize_t ret;
+	int fd;
 
-	err = g_io_channel_write(self->stream,
-				(gchar *) GST_BUFFER_DATA(buffer),
-				(gsize) (GST_BUFFER_SIZE(buffer)), &ret);
+	fd = g_io_channel_unix_get_fd(self->stream);
 
-	if (err != G_IO_ERROR_NONE) {
-		GST_ERROR_OBJECT(self, "Error while writting to socket: %d %s",
-				errno, strerror(errno));
+	ret = write(fd, GST_BUFFER_DATA(buffer), GST_BUFFER_SIZE(buffer));
+	if (ret < 0) {
+		GST_ERROR_OBJECT(self, "Error while writting to socket: %s",
+							strerror(errno));
 		return GST_FLOW_ERROR;
 	}
 
@@ -1234,6 +1804,12 @@
 					"to device", DEFAULT_AUTOCONNECT,
 					G_PARAM_READWRITE));
 
+	g_object_class_install_property(object_class, PROP_TRANSPORT,
+					g_param_spec_string("transport",
+					"Transport",
+					"Use configured transport",
+					NULL, G_PARAM_READWRITE));
+
 	GST_DEBUG_CATEGORY_INIT(avdtp_sink_debug, "avdtpsink", 0,
 				"A2DP headset sink element");
 }
@@ -1242,6 +1818,7 @@
 			GstAvdtpSinkClass *klass)
 {
 	self->device = NULL;
+	self->transport = NULL;
 	self->data = NULL;
 
 	self->stream = NULL;
@@ -1258,53 +1835,55 @@
 	*/
 }
 
-static GIOError gst_avdtp_sink_audioservice_send(
-					GstAvdtpSink *self,
+static int gst_avdtp_sink_audioservice_send(GstAvdtpSink *self,
 					const bt_audio_msg_header_t *msg)
 {
-	GIOError error;
-	gsize written;
+	ssize_t written;
 	const char *type, *name;
 	uint16_t length;
+	int fd;
 
 	length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE;
 
-	error = g_io_channel_write(self->server, (const gchar *) msg, length,
-								&written);
-	if (error != G_IO_ERROR_NONE)
+	fd = g_io_channel_unix_get_fd(self->server);
+
+	written = write(fd, msg, length);
+	if (written < 0) {
 		GST_ERROR_OBJECT(self, "Error sending data to audio service:"
-			" %s(%d)", strerror(errno), errno);
+			" %s", strerror(errno));
+		return -errno;
+	}
 
 	type = bt_audio_strtype(msg->type);
 	name = bt_audio_strname(msg->name);
 
 	GST_DEBUG_OBJECT(self, "sent: %s -> %s", type, name);
 
-	return error;
+	return 0;
 }
 
-static GIOError gst_avdtp_sink_audioservice_recv(
-					GstAvdtpSink *self,
-					bt_audio_msg_header_t *inmsg)
+static int gst_avdtp_sink_audioservice_recv(GstAvdtpSink *self,
+						bt_audio_msg_header_t *inmsg)
 {
-	GIOError status;
-	gsize bytes_read;
+	ssize_t bytes_read;
 	const char *type, *name;
 	uint16_t length;
+	int fd, err;
 
 	length = inmsg->length ? inmsg->length : BT_SUGGESTED_BUFFER_SIZE;
 
-	status = g_io_channel_read(self->server, (gchar *) inmsg, length,
-								&bytes_read);
-	if (status != G_IO_ERROR_NONE) {
+	fd = g_io_channel_unix_get_fd(self->server);
+
+	bytes_read = read(fd, inmsg, length);
+	if (bytes_read < 0) {
 		GST_ERROR_OBJECT(self, "Error receiving data from "
-				"audio service");
-		return status;
+				"audio service: %s", strerror(errno));
+		return -errno;
 	}
 
 	type = bt_audio_strtype(inmsg->type);
 	if (!type) {
-		status = G_IO_ERROR_INVAL;
+		err = -EINVAL;
 		GST_ERROR_OBJECT(self, "Bogus message type %d "
 				"received from audio service",
 				inmsg->type);
@@ -1312,41 +1891,41 @@
 
 	name = bt_audio_strname(inmsg->name);
 	if (!name) {
-		status = G_IO_ERROR_INVAL;
+		err = -EINVAL;
 		GST_ERROR_OBJECT(self, "Bogus message name %d "
 				"received from audio service",
 				inmsg->name);
 	}
 
 	if (inmsg->type == BT_ERROR) {
-		bt_audio_error_t *err = (void *) inmsg;
-		status = G_IO_ERROR_INVAL;
+		bt_audio_error_t *msg = (void *) inmsg;
+		err = -EINVAL;
 		GST_ERROR_OBJECT(self, "%s failed : "
 					"%s(%d)",
 					name,
-					strerror(err->posix_errno),
-					err->posix_errno);
+					strerror(msg->posix_errno),
+					msg->posix_errno);
 	}
 
 	GST_DEBUG_OBJECT(self, "received: %s <- %s", type, name);
 
-	return status;
+	return err;
 }
 
-static GIOError gst_avdtp_sink_audioservice_expect(
-			GstAvdtpSink *self, bt_audio_msg_header_t *outmsg,
-			guint8 expected_name)
+static int gst_avdtp_sink_audioservice_expect(GstAvdtpSink *self,
+						bt_audio_msg_header_t *outmsg,
+						guint8 expected_name)
 {
-	GIOError status;
+	int err;
 
-	status = gst_avdtp_sink_audioservice_recv(self, outmsg);
-	if (status != G_IO_ERROR_NONE)
-		return status;
+	err = gst_avdtp_sink_audioservice_recv(self, outmsg);
+	if (err < 0)
+		return err;
 
 	if (outmsg->name != expected_name)
-		status = G_IO_ERROR_INVAL;
+		return -EINVAL;
 
-	return status;
+	return 0;
 }
 
 gboolean gst_avdtp_sink_plugin_init(GstPlugin *plugin)
@@ -1397,11 +1976,25 @@
 	self->device = g_strdup(dev);
 }
 
+void gst_avdtp_sink_set_transport(GstAvdtpSink *self, const gchar *trans)
+{
+	if (self->transport != NULL)
+		g_free(self->transport);
+
+	GST_LOG_OBJECT(self, "Setting transport: %s", trans);
+	self->transport = g_strdup(trans);
+}
+
 gchar *gst_avdtp_sink_get_device(GstAvdtpSink *self)
 {
 	return g_strdup(self->device);
 }
 
+gchar *gst_avdtp_sink_get_transport(GstAvdtpSink *self)
+{
+	return g_strdup(self->transport);
+}
+
 void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc)
 {
 	gint new_crc;
diff --git a/audio/gstavdtpsink.h b/audio/gstavdtpsink.h
index eb78248..c4e5645 100644
--- a/audio/gstavdtpsink.h
+++ b/audio/gstavdtpsink.h
@@ -51,6 +51,7 @@
 	GstBaseSink sink;
 
 	gchar *device;
+	gchar *transport;
 	GIOChannel *stream;
 
 	struct bluetooth_data *data;
@@ -75,7 +76,7 @@
 	GstBaseSinkClass parent_class;
 };
 
-//GType gst_avdtp_sink_get_type(void);
+GType gst_avdtp_sink_get_type(void);
 
 GstCaps *gst_avdtp_sink_get_device_caps(GstAvdtpSink *sink);
 gboolean gst_avdtp_sink_set_device_caps(GstAvdtpSink *sink,
@@ -86,8 +87,13 @@
 void gst_avdtp_sink_set_device(GstAvdtpSink *sink,
 		const gchar* device);
 
+void gst_avdtp_sink_set_transport(GstAvdtpSink *sink,
+		const gchar *transport);
+
 gchar *gst_avdtp_sink_get_device(GstAvdtpSink *sink);
 
+gchar *gst_avdtp_sink_get_transport(GstAvdtpSink *sink);
+
 gboolean gst_avdtp_sink_plugin_init(GstPlugin *plugin);
 
 void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc);
diff --git a/audio/gstpragma.h b/audio/gstpragma.h
index 00eb408..626311c 100644
--- a/audio/gstpragma.h
+++ b/audio/gstpragma.h
@@ -21,4 +21,4 @@
  *
  */
 
-#pragma GCC diagnostic warning "-Wmissing-declarations"
+//#pragma GCC diagnostic warning "-Wmissing-declarations"
diff --git a/audio/gstrtpsbcpay.h b/audio/gstrtpsbcpay.h
index a444b49..398de82 100644
--- a/audio/gstrtpsbcpay.h
+++ b/audio/gstrtpsbcpay.h
@@ -59,7 +59,7 @@
 	GstBaseRTPPayloadClass parent_class;
 };
 
-//GType gst_rtp_sbc_pay_get_type(void);
+GType gst_rtp_sbc_pay_get_type(void);
 
 gboolean gst_rtp_sbc_pay_plugin_init (GstPlugin * plugin);
 
diff --git a/audio/gstsbcdec.h b/audio/gstsbcdec.h
index 6a15fb0..c77feae 100644
--- a/audio/gstsbcdec.h
+++ b/audio/gstsbcdec.h
@@ -59,7 +59,7 @@
 	GstElementClass parent_class;
 };
 
-//GType gst_sbc_dec_get_type(void);
+GType gst_sbc_dec_get_type(void);
 
 gboolean gst_sbc_dec_plugin_init(GstPlugin *plugin);
 
diff --git a/audio/gstsbcenc.h b/audio/gstsbcenc.h
index 0c8508b..0329351 100644
--- a/audio/gstsbcenc.h
+++ b/audio/gstsbcenc.h
@@ -68,7 +68,7 @@
 	GstElementClass parent_class;
 };
 
-//GType gst_sbc_enc_get_type(void);
+GType gst_sbc_enc_get_type(void);
 
 gboolean gst_sbc_enc_plugin_init(GstPlugin *plugin);
 
diff --git a/audio/gstsbcparse.h b/audio/gstsbcparse.h
index d7331ad..ecb8be4 100644
--- a/audio/gstsbcparse.h
+++ b/audio/gstsbcparse.h
@@ -62,7 +62,7 @@
 	GstElementClass parent_class;
 };
 
-//GType gst_sbc_parse_get_type(void);
+GType gst_sbc_parse_get_type(void);
 
 gboolean gst_sbc_parse_plugin_init(GstPlugin *plugin);
 
diff --git a/audio/headset.c b/audio/headset.c
index 99d4c7a..bdaa8da 100644
--- a/audio/headset.c
+++ b/audio/headset.c
@@ -40,10 +40,6 @@
 #include <assert.h>
 
 #include <bluetooth/bluetooth.h>
-#include <bluetooth/hci.h>
-#include <bluetooth/hci_lib.h>
-#include <bluetooth/sco.h>
-#include <bluetooth/rfcomm.h>
 #include <bluetooth/sdp.h>
 #include <bluetooth/sdp_lib.h>
 
@@ -88,13 +84,14 @@
 	.features = 0,
 	.er_mode = 3,
 	.er_ind = 0,
-	.rh = -1,
+	.rh = BTRH_NOT_SUPPORTED,
 	.number = NULL,
 	.number_type = 0,
 	.ring_timer = 0,
 };
 
 static gboolean sco_hci = TRUE;
+static gboolean fast_connectable = FALSE;
 
 static GSList *active_devices = NULL;
 
@@ -112,6 +109,12 @@
 	unsigned int id;
 };
 
+struct headset_nrec_callback {
+	unsigned int id;
+	headset_nrec_cb cb;
+	void *user_data;
+};
+
 struct connect_cb {
 	unsigned int id;
 	headset_stream_cb_t cb;
@@ -157,7 +160,6 @@
 	GIOChannel *tmp_rfcomm;
 	GIOChannel *sco;
 	guint sco_id;
-	guint dc_id;
 
 	gboolean auto_dc;
 
@@ -171,6 +173,7 @@
 
 	headset_lock_t lock;
 	struct headset_slc *slc;
+	GSList *nrec_cbs;
 };
 
 struct event {
@@ -180,25 +183,12 @@
 
 static GSList *headset_callbacks = NULL;
 
-static inline DBusMessage *invalid_args(DBusMessage *msg)
+static void error_connect_failed(DBusConnection *conn, DBusMessage *msg,
+								int err)
 {
-	return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
-			"Invalid arguments in method call");
-}
-
-static DBusHandlerResult error_not_supported(DBusConnection *conn,
-							DBusMessage *msg)
-{
-	return error_common_reply(conn, msg, ERROR_INTERFACE ".NotSupported",
-							"Not supported");
-}
-
-static DBusHandlerResult error_connection_attempt_failed(DBusConnection *conn,
-						DBusMessage *msg, int err)
-{
-	return error_common_reply(conn, msg,
-			ERROR_INTERFACE ".ConnectionAttemptFailed",
-			err < 0 ? strerror(-err) : "Connection attempt failed");
+	DBusMessage *reply = btd_error_failed(msg,
+			err < 0 ? strerror(-err) : "Connect failed");
+	g_dbus_send_message(conn, reply);
 }
 
 static int rfcomm_connect(struct audio_device *device, headset_stream_cb_t cb,
@@ -577,9 +567,7 @@
 		if (p != NULL) {
 			p->err = -errno;
 			if (p->msg)
-				error_connection_attempt_failed(dev->conn,
-								p->msg,
-								p->err);
+				error_connect_failed(dev->conn, p->msg, p->err);
 			pending_connect_finalize(dev);
 		}
 
@@ -689,7 +677,7 @@
 	p->err = sco_connect(dev, NULL, NULL, NULL);
 	if (p->err < 0) {
 		if (p->msg)
-			error_connection_attempt_failed(dev->conn, p->msg, p->err);
+			error_connect_failed(dev->conn, p->msg, p->err);
 		pending_connect_finalize(dev);
 	}
 }
@@ -902,6 +890,9 @@
 	if (strlen(buf) < 8)
 		return -EINVAL;
 
+	if (ag.rh == BTRH_NOT_SUPPORTED)
+		return telephony_generic_rsp(device, CME_ERROR_NOT_SUPPORTED);
+
 	if (buf[7] == '=') {
 		telephony_response_and_hold_req(device, atoi(&buf[8]) < 0);
 		return 0;
@@ -965,7 +956,7 @@
 	case HEADSET_GAIN_SPEAKER:
 		if (slc->sp_gain == gain) {
 			DBG("Ignoring no-change in speaker gain");
-			return 0;
+			return -EALREADY;
 		}
 		name = "SpeakerGainChanged";
 		property = "SpeakerGain";
@@ -974,7 +965,7 @@
 	case HEADSET_GAIN_MICROPHONE:
 		if (slc->mic_gain == gain) {
 			DBG("Ignoring no-change in microphone gain");
-			return 0;
+			return -EALREADY;
 		}
 		name = "MicrophoneGainChanged";
 		property = "MicrophoneGain";
@@ -1011,7 +1002,7 @@
 	gain = (dbus_uint16_t) strtol(&buf[7], NULL, 10);
 
 	err = headset_set_gain(device, gain, buf[5]);
-	if (err < 0)
+	if (err < 0 && err != -EALREADY)
 		return err;
 
 	return headset_send(hs, "\r\nOK\r\n");
@@ -1024,12 +1015,18 @@
 
 static int dtmf_tone(struct audio_device *device, const char *buf)
 {
+	char tone;
+
 	if (strlen(buf) < 8) {
 		error("Too short string for DTMF tone");
 		return -EINVAL;
 	}
 
-	telephony_transmit_dtmf_req(device, buf[7]);
+	tone = buf[7];
+	if (tone >= '#' && tone <= 'D')
+		telephony_transmit_dtmf_req(device, tone);
+	else
+		return -EINVAL;
 
 	return 0;
 }
@@ -1112,8 +1109,17 @@
 	struct headset *hs = device->headset;
 	struct headset_slc *slc = hs->slc;
 
-	if (err == CME_ERROR_NONE)
+	if (err == CME_ERROR_NONE) {
+		GSList *l;
+
+		for (l = hs->nrec_cbs; l; l = l->next) {
+			struct headset_nrec_callback *nrec_cb = l->data;
+
+			nrec_cb->cb(device, slc->nrec_req, nrec_cb->user_data);
+		}
+
 		slc->nrec = hs->slc->nrec_req;
+	}
 
 	return telephony_generic_rsp(telephony_device, err);
 }
@@ -1251,8 +1257,9 @@
 	struct headset *hs;
 	struct headset_slc *slc;
 	unsigned char buf[BUF_SIZE];
-	gsize bytes_read = 0;
-	gsize free_space;
+	ssize_t bytes_read;
+	size_t free_space;
+	int fd;
 
 	if (cond & G_IO_NVAL)
 		return FALSE;
@@ -1265,14 +1272,16 @@
 		goto failed;
 	}
 
-	if (g_io_channel_read(chan, (gchar *) buf, sizeof(buf) - 1,
-				&bytes_read) != G_IO_ERROR_NONE)
+	fd = g_io_channel_unix_get_fd(chan);
+
+	bytes_read = read(fd, buf, sizeof(buf) - 1);
+	if (bytes_read < 0)
 		return TRUE;
 
 	free_space = sizeof(slc->buf) - slc->data_start -
 			slc->data_length - 1;
 
-	if (free_space < bytes_read) {
+	if (free_space < (size_t) bytes_read) {
 		/* Very likely that the HS is sending us garbage so
 		 * just ignore the data and disconnect */
 		error("Too much data to fit incomming buffer");
@@ -1376,6 +1385,8 @@
 	DBG("%s: Connected to %s", dev->path, hs_address);
 
 	hs->slc = g_new0(struct headset_slc, 1);
+	hs->slc->sp_gain = 15;
+	hs->slc->mic_gain = 15;
 	hs->slc->nrec = TRUE;
 
 	/* In HFP mode wait for Service Level Connection */
@@ -1402,7 +1413,7 @@
 
 failed:
 	if (p && p->msg)
-		error_connection_attempt_failed(dev->conn, p->msg, p->err);
+		error_connect_failed(dev->conn, p->msg, p->err);
 	pending_connect_finalize(dev);
 	if (hs->rfcomm)
 		headset_set_state(dev, HEADSET_STATE_CONNECTED);
@@ -1459,7 +1470,8 @@
 		error("Unable to get service record: %s (%d)",
 							strerror(-err), -err);
 		p->err = -err;
-		error_connection_attempt_failed(dev->conn, p->msg, p->err);
+		if (p->msg)
+			error_connect_failed(dev->conn, p->msg, p->err);
 		goto failed;
 	}
 
@@ -1508,7 +1520,7 @@
 	if (err < 0) {
 		error("Unable to connect: %s (%d)", strerror(-err), -err);
 		p->err = -err;
-		error_connection_attempt_failed(dev->conn, p->msg, p->err);
+		error_connect_failed(dev->conn, p->msg, p->err);
 		goto failed;
 	}
 
@@ -1518,8 +1530,10 @@
 	if (p->svclass == HANDSFREE_SVCLASS_ID &&
 			get_records(dev, NULL, NULL, NULL) == 0)
 		return;
-	if (p->msg)
-		error_not_supported(dev->conn, p->msg);
+	if (p->msg) {
+		DBusMessage *reply = btd_error_not_supported(p->msg);
+		g_dbus_send_message(dev->conn, reply);
+	}
 failed:
 	p->svclass = 0;
 	pending_connect_finalize(dev);
@@ -1626,9 +1640,7 @@
 	DBusMessage *reply = NULL;
 
 	if (hs->state < HEADSET_STATE_PLAY_IN_PROGRESS)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".NotConnected",
-						"Device not Connected");
+		return btd_error_not_connected(msg);
 
 	reply = dbus_message_new_method_return(msg);
 	if (!reply)
@@ -1667,9 +1679,7 @@
 	char hs_address[18];
 
 	if (hs->state == HEADSET_STATE_DISCONNECTED)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".NotConnected",
-						"Device not Connected");
+		return btd_error_not_connected(msg);
 
 	headset_shutdown(device);
 	ba2str(&device->dst, hs_address);
@@ -1707,27 +1717,18 @@
 	int err;
 
 	if (hs->state == HEADSET_STATE_CONNECTING)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress",
-						"Connect in Progress");
+		return btd_error_in_progress(msg);
 	else if (hs->state > HEADSET_STATE_CONNECTING)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".AlreadyConnected",
-						"Already Connected");
+		return btd_error_already_connected(msg);
 
 	if (hs->hfp_handle && !ag.telephony_ready)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady",
-					"Telephony subsystem not ready");
+		return btd_error_not_ready(msg);
 
 	device->auto_connect = FALSE;
 
 	err = rfcomm_connect(device, NULL, NULL, NULL);
-	if (err == -ECONNREFUSED)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAllowed",
-						"Too many connected devices");
-	else if (err < 0)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".ConnectAttemptFailed",
-						"Connect Attempt Failed");
+	if (err < 0)
+		return btd_error_failed(msg, strerror(-err));
 
 	hs->auto_dc = FALSE;
 
@@ -1745,9 +1746,7 @@
 	int err;
 
 	if (hs->state < HEADSET_STATE_CONNECTED)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".NotConnected",
-						"Device not Connected");
+		return btd_error_not_connected(msg);
 
 	reply = dbus_message_new_method_return(msg);
 	if (!reply)
@@ -1761,8 +1760,7 @@
 	err = headset_send(hs, "\r\nRING\r\n");
 	if (err < 0) {
 		dbus_message_unref(reply);
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-						"%s", strerror(-err));
+		return btd_error_failed(msg, strerror(-err));
 	}
 
 	ring_timer_cb(NULL);
@@ -1782,9 +1780,7 @@
 	DBusMessage *reply = NULL;
 
 	if (hs->state < HEADSET_STATE_CONNECTED)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".NotConnected",
-						"Device not Connected");
+		return btd_error_not_connected(msg);
 
 	reply = dbus_message_new_method_return(msg);
 	if (!reply)
@@ -1809,28 +1805,21 @@
 	if (sco_hci) {
 		error("Refusing Headset.Play() because SCO HCI routing "
 				"is enabled");
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable",
-						"Operation not Available");
+		return btd_error_not_available(msg);
 	}
 
 	switch (hs->state) {
 	case HEADSET_STATE_DISCONNECTED:
 	case HEADSET_STATE_CONNECTING:
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".NotConnected",
-						"Device not Connected");
+		return btd_error_not_connected(msg);
 	case HEADSET_STATE_PLAY_IN_PROGRESS:
 		if (hs->pending && hs->pending->msg == NULL) {
 			hs->pending->msg = dbus_message_ref(msg);
 			return NULL;
 		}
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".InProgress",
-						"Play in Progress");
+		return btd_error_busy(msg);
 	case HEADSET_STATE_PLAYING:
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".AlreadyConnected",
-						"Device Already Connected");
+		return btd_error_already_connected(msg);
 	case HEADSET_STATE_CONNECTED:
 	default:
 		break;
@@ -1838,8 +1827,7 @@
 
 	err = sco_connect(device, NULL, NULL, NULL);
 	if (err < 0)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-						"%s", strerror(-err));
+		return btd_error_failed(msg, strerror(-err));
 
 	hs->pending->msg = dbus_message_ref(msg);
 
@@ -1857,8 +1845,7 @@
 	dbus_uint16_t gain;
 
 	if (hs->state < HEADSET_STATE_CONNECTED)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable",
-						"Operation not Available");
+		return btd_error_not_available(msg);
 
 	reply = dbus_message_new_method_return(msg);
 	if (!reply)
@@ -1883,8 +1870,7 @@
 	dbus_uint16_t gain;
 
 	if (hs->state < HEADSET_STATE_CONNECTED || slc == NULL)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable",
-						"Operation not Available");
+		return btd_error_not_available(msg);
 
 	reply = dbus_message_new_method_return(msg);
 	if (!reply)
@@ -1909,15 +1895,11 @@
 	int err;
 
 	if (hs->state < HEADSET_STATE_CONNECTED)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".NotConnected",
-						"Device not Connected");
+		return btd_error_not_connected(msg);
 
 	err = headset_set_gain(device, gain, type);
 	if (err < 0)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".InvalidArgument",
-						"Must be less than or equal to 15");
+		return btd_error_invalid_args(msg);
 
 	reply = dbus_message_new_method_return(msg);
 	if (!reply)
@@ -1927,8 +1909,7 @@
 		err = headset_send(hs, "\r\n+VG%c=%u\r\n", type, gain);
 		if (err < 0) {
 			dbus_message_unref(reply);
-			return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-						"%s", strerror(-err));
+			return btd_error_failed(msg, strerror(-err));
 		}
 	}
 
@@ -2024,35 +2005,35 @@
 	uint16_t gain;
 
 	if (!dbus_message_iter_init(msg, &iter))
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 
 	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 
 	dbus_message_iter_get_basic(&iter, &property);
 	dbus_message_iter_next(&iter);
 
 	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 	dbus_message_iter_recurse(&iter, &sub);
 
 	if (g_str_equal("SpeakerGain", property)) {
 		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16)
-			return invalid_args(msg);
+			return btd_error_invalid_args(msg);
 
 		dbus_message_iter_get_basic(&sub, &gain);
 		return hs_set_gain(conn, msg, data, gain,
 					HEADSET_GAIN_SPEAKER);
 	} else if (g_str_equal("MicrophoneGain", property)) {
 		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16)
-			return invalid_args(msg);
+			return btd_error_invalid_args(msg);
 
 		dbus_message_iter_get_basic(&sub, &gain);
 		return hs_set_gain(conn, msg, data, gain,
 					HEADSET_GAIN_MICROPHONE);
 	}
 
-	return invalid_args(msg);
+	return btd_error_invalid_args(msg);
 }
 static GDBusMethodTable headset_methods[] = {
 	{ "Connect",		"",	"",	hs_connect,
@@ -2161,13 +2142,13 @@
 		hs->dc_timer = 0;
 	}
 
-	if (hs->dc_id)
-		device_remove_disconnect_watch(dev->btd_dev, hs->dc_id);
-
 	close_sco(dev);
 
 	headset_close_rfcomm(dev);
 
+	g_slist_foreach(hs->nrec_cbs, (GFunc) g_free, NULL);
+	g_slist_free(hs->nrec_cbs);
+
 	g_free(hs);
 	dev->headset = NULL;
 }
@@ -2262,6 +2243,19 @@
 		g_free(str);
 	}
 
+	/* Init fast connectable option */
+	str = g_key_file_get_string(config, "Headset", "FastConnectable",
+					&err);
+	if (err) {
+		DBG("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else {
+		fast_connectable = strcmp(str, "true") == 0;
+		if (fast_connectable)
+			manager_set_fast_connectable(FALSE);
+		g_free(str);
+	}
+
 	return ag.features;
 }
 
@@ -2463,16 +2457,6 @@
 	return 0;
 }
 
-static void disconnect_cb(struct btd_device *btd_dev, gboolean removal,
-				void *user_data)
-{
-	struct audio_device *device = user_data;
-
-	info("Headset: disconnect %s", device->path);
-
-	headset_shutdown(device);
-}
-
 void headset_set_state(struct audio_device *dev, headset_state_t state)
 {
 	struct headset *hs = dev->headset;
@@ -2506,8 +2490,6 @@
 			telephony_device_disconnected(dev);
 		}
 		active_devices = g_slist_remove(active_devices, dev);
-		device_remove_disconnect_watch(dev->btd_dev, hs->dc_id);
-		hs->dc_id = 0;
 		break;
 	case HEADSET_STATE_CONNECTING:
 		emit_property_changed(dev->conn, dev->path,
@@ -2536,9 +2518,6 @@
 						DBUS_TYPE_BOOLEAN, &value);
 			active_devices = g_slist_append(active_devices, dev);
 			telephony_device_connected(dev);
-			hs->dc_id = device_add_disconnect_watch(dev->btd_dev,
-								disconnect_cb,
-								dev, NULL);
 		} else if (hs->state == HEADSET_STATE_PLAYING) {
 			value = FALSE;
 			g_dbus_emit_signal(dev->conn, dev->path,
@@ -2687,6 +2666,50 @@
 	return hs->slc->nrec;
 }
 
+unsigned int headset_add_nrec_cb(struct audio_device *dev,
+					headset_nrec_cb cb, void *user_data)
+{
+	struct headset *hs = dev->headset;
+	struct headset_nrec_callback *nrec_cb;
+	static unsigned int id = 0;
+
+	nrec_cb = g_new(struct headset_nrec_callback, 1);
+	nrec_cb->cb = cb;
+	nrec_cb->user_data = user_data;
+	nrec_cb->id = ++id;
+
+	hs->nrec_cbs = g_slist_prepend(hs->nrec_cbs, nrec_cb);
+
+	return nrec_cb->id;
+}
+
+gboolean headset_remove_nrec_cb(struct audio_device *dev, unsigned int id)
+{
+	struct headset *hs = dev->headset;
+	GSList *l;
+
+	for (l = hs->nrec_cbs; l != NULL; l = l->next) {
+		struct headset_nrec_callback *cb = l->data;
+		if (cb && cb->id == id) {
+			hs->nrec_cbs = g_slist_remove(hs->nrec_cbs, cb);
+			g_free(cb);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+gboolean headset_get_inband(struct audio_device *dev)
+{
+	struct headset *hs = dev->headset;
+
+	if (!hs->slc)
+		return TRUE;
+
+	return hs->slc->inband_ring;
+}
+
 gboolean headset_get_sco_hci(struct audio_device *dev)
 {
 	return sco_hci;
@@ -2697,7 +2720,7 @@
 	struct pending_connect *p = dev->headset->pending;
 
 	if (p && p->msg)
-		error_connection_attempt_failed(dev->conn, p->msg, ECANCELED);
+		error_connect_failed(dev->conn, p->msg, ECANCELED);
 
 	pending_connect_finalize(dev);
 	headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
@@ -2743,6 +2766,9 @@
 	struct headset *hs;
 	struct headset_slc *slc;
 
+	if (fast_connectable)
+		manager_set_fast_connectable(TRUE);
+
 	if (!active_devices)
 		return -ENODEV;
 
@@ -2782,6 +2808,9 @@
 {
 	struct audio_device *dev;
 
+	if (fast_connectable)
+		manager_set_fast_connectable(FALSE);
+
 	if (ag.ring_timer) {
 		g_source_remove(ag.ring_timer);
 		ag.ring_timer = 0;
@@ -2809,7 +2838,7 @@
 	ag.features = features;
 	ag.indicators = indicators;
 	ag.rh = rh;
-	ag.chld = g_strdup(chld);
+	ag.chld = chld;
 
 	DBG("Telephony plugin initialized");
 
@@ -2818,6 +2847,20 @@
 	return 0;
 }
 
+int telephony_deinit(void)
+{
+	g_free(ag.number);
+
+	memset(&ag, 0, sizeof(ag));
+
+	ag.er_mode = 3;
+	ag.rh = BTRH_NOT_SUPPORTED;
+
+	DBG("Telephony deinitialized");
+
+	return 0;
+}
+
 int telephony_list_current_call_ind(int idx, int dir, int status, int mode,
 					int mprty, const char *number,
 					int type)
diff --git a/audio/headset.h b/audio/headset.h
index 8b7c738..7ce88c8 100644
--- a/audio/headset.h
+++ b/audio/headset.h
@@ -44,6 +44,9 @@
 					headset_state_t old_state,
 					headset_state_t new_state,
 					void *user_data);
+typedef void (*headset_nrec_cb) (struct audio_device *dev,
+					gboolean nrec,
+					void *user_data);
 
 unsigned int headset_add_state_cb(headset_state_cb cb, void *user_data);
 gboolean headset_remove_state_cb(unsigned int id);
@@ -90,6 +93,10 @@
 
 int headset_get_sco_fd(struct audio_device *dev);
 gboolean headset_get_nrec(struct audio_device *dev);
+unsigned int headset_add_nrec_cb(struct audio_device *dev,
+					headset_nrec_cb cb, void *user_data);
+gboolean headset_remove_nrec_cb(struct audio_device *dev, unsigned int id);
+gboolean headset_get_inband(struct audio_device *dev);
 gboolean headset_get_sco_hci(struct audio_device *dev);
 
 gboolean headset_is_active(struct audio_device *dev);
diff --git a/audio/liba2dp.c b/audio/liba2dp.c
index 3d4e90f..62f52d4 100755
--- a/audio/liba2dp.c
+++ b/audio/liba2dp.c
@@ -194,20 +194,20 @@
 	socklen_t len;
 
 	len = sizeof(flags);
-	if (getsockopt(fd, SOL_L2CAP, L2CAP_LM, &flags, &len) < 0)
+	if (getsockopt(fd, SOL_BLUETOOTH, BT_FLUSHABLE, &flags, &len) < 0)
 		return -errno;
 
 	if (flushable) {
-		if (flags & L2CAP_LM_FLUSHABLE)
+		if (flags == BT_FLUSHABLE_ON)
 			return 0;
-		flags |= L2CAP_LM_FLUSHABLE;
+		flags = BT_FLUSHABLE_ON;
 	} else {
-		if (!(flags & L2CAP_LM_FLUSHABLE))
+		if (flags == BT_FLUSHABLE_OFF)
 			return 0;
-		flags &= ~L2CAP_LM_FLUSHABLE;
+		flags = BT_FLUSHABLE_OFF;
 	}
 
-	if (setsockopt(fd, SOL_L2CAP, L2CAP_LM, &flags, sizeof(flags)) < 0)
+	if (setsockopt(fd, SOL_BLUETOOTH, L2CAP_LM, &flags, sizeof(flags)) < 0)
 		return -errno;
 
 	return 0;
diff --git a/audio/main.c b/audio/main.c
index 9d316ec..745c307 100644
--- a/audio/main.c
+++ b/audio/main.c
@@ -42,7 +42,6 @@
 #include "plugin.h"
 #include "log.h"
 #include "device.h"
-#include "unix.h"
 #include "headset.h"
 #include "manager.h"
 #include "gateway.h"
@@ -151,11 +150,6 @@
 
 	config = load_config_file(CONFIGDIR "/audio.conf");
 
-	if (unix_init() < 0) {
-		error("Unable to setup unix socket");
-		goto failed;
-	}
-
 	if (audio_manager_init(connection, config, &enable_sco) < 0)
 		goto failed;
 
@@ -174,7 +168,6 @@
 
 failed:
 	audio_manager_exit();
-	unix_exit();
 
 	if (connection) {
 		dbus_connection_unref(connection);
@@ -194,8 +187,6 @@
 
 	audio_manager_exit();
 
-	unix_exit();
-
 	dbus_connection_unref(connection);
 }
 
diff --git a/audio/manager.c b/audio/manager.c
index 6dd0f87..7e206be 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -39,9 +39,6 @@
 #include <signal.h>
 
 #include <bluetooth/bluetooth.h>
-#include <bluetooth/hci.h>
-#include <bluetooth/hci_lib.h>
-#include <bluetooth/rfcomm.h>
 #include <bluetooth/sdp.h>
 #include <bluetooth/sdp_lib.h>
 
@@ -51,8 +48,8 @@
 
 #include "glib-helper.h"
 #include "btio.h"
-#include "../src/manager.h"
 #include "../src/adapter.h"
+#include "../src/manager.h"
 #include "../src/device.h"
 
 #include "log.h"
@@ -61,6 +58,7 @@
 #include "device.h"
 #include "error.h"
 #include "avdtp.h"
+#include "media.h"
 #include "a2dp.h"
 #include "headset.h"
 #include "gateway.h"
@@ -70,6 +68,11 @@
 #include "manager.h"
 #include "sdpd.h"
 #include "telephony.h"
+#include "unix.h"
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
 
 typedef enum {
 	HEADSET	= 1 << 0,
@@ -90,6 +93,7 @@
 
 struct audio_adapter {
 	struct btd_adapter *btd_adapter;
+	gboolean powered;
 	uint32_t hsp_ag_record_id;
 	uint32_t hfp_ag_record_id;
 	uint32_t hfp_hs_record_id;
@@ -113,6 +117,8 @@
 	.sink		= TRUE,
 	.source		= FALSE,
 	.control	= TRUE,
+	.socket		= TRUE,
+	.media		= FALSE
 };
 
 static struct audio_adapter *find_adapter(GSList *list,
@@ -607,7 +613,6 @@
 
 drop:
 	g_io_channel_shutdown(chan, TRUE, NULL);
-	g_io_channel_unref(chan);
 }
 
 static int headset_server_init(struct audio_adapter *adapter)
@@ -850,6 +855,49 @@
 	return adp;
 }
 
+static void state_changed(struct btd_adapter *adapter, gboolean powered)
+{
+	struct audio_adapter *adp;
+	static gboolean telephony = FALSE;
+	GSList *l;
+
+	DBG("%s powered %s", adapter_get_path(adapter),
+						powered ? "on" : "off");
+
+	/* ignore powered change, adapter is powering down */
+	if (powered && adapter_powering_down(adapter))
+		return;
+
+	adp = find_adapter(adapters, adapter);
+	if (!adp)
+		return;
+
+	adp->powered = powered;
+
+	if (powered) {
+		/* telephony driver already initialized*/
+		if (telephony == TRUE)
+			return;
+		telephony_init();
+		telephony = TRUE;
+		return;
+	}
+
+	/* telephony not initialized just ignore power down */
+	if (telephony == FALSE)
+		return;
+
+	for (l = adapters; l; l = l->next) {
+		adp = l->data;
+
+		if (adp->powered == TRUE)
+			return;
+	}
+
+	telephony_exit();
+	telephony = FALSE;
+}
+
 static int headset_server_probe(struct btd_adapter *adapter)
 {
 	struct audio_adapter *adp;
@@ -863,10 +911,15 @@
 		return -EINVAL;
 
 	err = headset_server_init(adp);
-	if (err < 0)
+	if (err < 0) {
 		audio_adapter_unref(adp);
+		return err;
+	}
 
-	return err;
+	btd_adapter_register_powered_callback(adapter, state_changed);
+	state_changed(adapter, TRUE);
+
+	return 0;
 }
 
 static void headset_server_remove(struct btd_adapter *adapter)
@@ -902,6 +955,8 @@
 		adp->hfp_ag_server = NULL;
 	}
 
+	btd_adapter_unregister_powered_callback(adapter, state_changed);
+
 	audio_adapter_unref(adp);
 }
 
@@ -1013,6 +1068,38 @@
 	audio_adapter_unref(adp);
 }
 
+static int media_server_probe(struct btd_adapter *adapter)
+{
+	struct audio_adapter *adp;
+	const gchar *path = adapter_get_path(adapter);
+	bdaddr_t src;
+
+	DBG("path %s", path);
+
+	adp = audio_adapter_get(adapter);
+	if (!adp)
+		return -EINVAL;
+
+	adapter_get_address(adapter, &src);
+
+	return media_register(connection, path, &src);
+}
+
+static void media_server_remove(struct btd_adapter *adapter)
+{
+	struct audio_adapter *adp;
+	const gchar *path = adapter_get_path(adapter);
+
+	DBG("path %s", path);
+
+	adp = find_adapter(adapters, adapter);
+	if (!adp)
+		return;
+
+	media_unregister(path);
+	audio_adapter_unref(adp);
+}
+
 static struct btd_device_driver audio_driver = {
 	.name	= "audio",
 	.uuids	= BTD_UUIDS(HSP_HS_UUID, HFP_HS_UUID, HSP_AG_UUID, HFP_AG_UUID,
@@ -1046,6 +1133,12 @@
 	.remove	= avrcp_server_remove,
 };
 
+static struct btd_adapter_driver media_server_driver = {
+	.name	= "media",
+	.probe	= media_server_probe,
+	.remove	= media_server_remove,
+};
+
 int audio_manager_init(DBusConnection *conn, GKeyFile *conf,
 							gboolean *enable_sco)
 {
@@ -1074,6 +1167,10 @@
 			enabled.source = TRUE;
 		else if (g_str_equal(list[i], "Control"))
 			enabled.control = TRUE;
+		else if (g_str_equal(list[i], "Socket"))
+			enabled.socket = TRUE;
+		else if (g_str_equal(list[i], "Media"))
+			enabled.media = TRUE;
 	}
 	g_strfreev(list);
 
@@ -1090,6 +1187,10 @@
 			enabled.source = FALSE;
 		else if (g_str_equal(list[i], "Control"))
 			enabled.control = FALSE;
+		else if (g_str_equal(list[i], "Socket"))
+			enabled.socket = FALSE;
+		else if (g_str_equal(list[i], "Media"))
+			enabled.media = FALSE;
 	}
 	g_strfreev(list);
 
@@ -1117,10 +1218,14 @@
 		max_connected_headsets = i;
 
 proceed:
-	if (enabled.headset) {
-		telephony_init();
+	if (enabled.socket)
+		unix_init();
+
+	if (enabled.media)
+		btd_register_adapter_driver(&media_server_driver);
+
+	if (enabled.headset)
 		btd_register_adapter_driver(&headset_server_driver);
-	}
 
 	if (enabled.gateway)
 		btd_register_adapter_driver(&gateway_server_driver);
@@ -1152,10 +1257,14 @@
 		config = NULL;
 	}
 
-	if (enabled.headset) {
+	if (enabled.socket)
+		unix_exit();
+
+	if (enabled.media)
+		btd_unregister_adapter_driver(&media_server_driver);
+
+	if (enabled.headset)
 		btd_unregister_adapter_driver(&headset_server_driver);
-		telephony_exit();
-	}
 
 	if (enabled.gateway)
 		btd_unregister_adapter_driver(&gateway_server_driver);
@@ -1290,3 +1399,17 @@
 
 	return TRUE;
 }
+
+void manager_set_fast_connectable(gboolean enable)
+{
+	GSList *l;
+
+	for (l = adapters; l != NULL; l = l->next) {
+		struct audio_adapter *adapter = l->data;
+
+		if (btd_adapter_set_fast_connectable(adapter->btd_adapter,
+								enable))
+			error("Changing fast connectable for hci%d failed",
+				adapter_get_dev_id(adapter->btd_adapter));
+	}
+}
diff --git a/audio/manager.h b/audio/manager.h
index 8e1abf4..0bf7663 100644
--- a/audio/manager.h
+++ b/audio/manager.h
@@ -29,6 +29,8 @@
 	gboolean sink;
 	gboolean source;
 	gboolean control;
+	gboolean socket;
+	gboolean media;
 };
 
 int audio_manager_init(DBusConnection *conn, GKeyFile *config,
@@ -48,3 +50,7 @@
 					gboolean create);
 
 gboolean manager_allow_headset_connection(struct audio_device *device);
+
+/* TRUE to enable fast connectable and FALSE to disable fast connectable for all
+ * audio adapters. */
+void manager_set_fast_connectable(gboolean enable);
diff --git a/audio/media.c b/audio/media.c
new file mode 100644
index 0000000..9cfbe0e
--- /dev/null
+++ b/audio/media.c
@@ -0,0 +1,700 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#include "../src/adapter.h"
+#include "../src/dbus-common.h"
+
+#include "log.h"
+#include "error.h"
+#include "device.h"
+#include "avdtp.h"
+#include "media.h"
+#include "transport.h"
+#include "a2dp.h"
+#include "headset.h"
+#include "manager.h"
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+#define MEDIA_INTERFACE "org.bluez.Media"
+#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint"
+
+#define REQUEST_TIMEOUT (3 * 1000)		/* 3 seconds */
+
+struct media_adapter {
+	bdaddr_t		src;		/* Adapter address */
+	char			*path;		/* Adapter path */
+	DBusConnection		*conn;		/* Adapter connection */
+	GSList			*endpoints;	/* Endpoints list */
+};
+
+struct endpoint_request {
+	DBusMessage		*msg;
+	DBusPendingCall		*call;
+	media_endpoint_cb_t	cb;
+	void			*user_data;
+};
+
+struct media_endpoint {
+	struct a2dp_sep		*sep;
+	char			*sender;	/* Endpoint DBus bus id */
+	char			*path;		/* Endpoint object path */
+	char			*uuid;		/* Endpoint property UUID */
+	uint8_t			codec;		/* Endpoint codec */
+	uint8_t			*capabilities;	/* Endpoint property capabilities */
+	size_t			size;		/* Endpoint capabilities size */
+	guint			hs_watch;
+	guint			watch;
+	struct endpoint_request *request;
+	struct media_transport	*transport;
+	struct media_adapter	*adapter;
+};
+
+static GSList *adapters = NULL;
+
+static void endpoint_request_free(struct endpoint_request *request)
+{
+	if (request->call)
+		dbus_pending_call_unref(request->call);
+
+	dbus_message_unref(request->msg);
+	g_free(request);
+}
+
+static void media_endpoint_cancel(struct media_endpoint *endpoint)
+{
+	struct endpoint_request *request = endpoint->request;
+
+	if (request->call)
+		dbus_pending_call_cancel(request->call);
+
+	endpoint_request_free(request);
+	endpoint->request = NULL;
+}
+
+static void media_endpoint_remove(struct media_endpoint *endpoint)
+{
+	struct media_adapter *adapter = endpoint->adapter;
+
+	info("Endpoint unregistered: sender=%s path=%s", endpoint->sender,
+			endpoint->path);
+
+	adapter->endpoints = g_slist_remove(adapter->endpoints, endpoint);
+
+	if (endpoint->sep)
+		a2dp_remove_sep(endpoint->sep);
+
+	if (endpoint->hs_watch)
+		headset_remove_state_cb(endpoint->hs_watch);
+
+	if (endpoint->request)
+		media_endpoint_cancel(endpoint);
+
+	if (endpoint->transport)
+		media_transport_remove(endpoint->transport);
+
+	g_dbus_remove_watch(adapter->conn, endpoint->watch);
+	g_free(endpoint->capabilities);
+	g_free(endpoint->sender);
+	g_free(endpoint->path);
+	g_free(endpoint->uuid);
+	g_free(endpoint);
+}
+
+static void media_endpoint_exit(DBusConnection *connection, void *user_data)
+{
+	struct media_endpoint *endpoint = user_data;
+
+	endpoint->watch = 0;
+	media_endpoint_remove(endpoint);
+}
+
+static void headset_setconf_cb(struct media_endpoint *endpoint, void *ret,
+						int size, void *user_data)
+{
+	struct audio_device *dev = user_data;
+
+	if (ret != NULL)
+		return;
+
+	headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+}
+
+static void headset_state_changed(struct audio_device *dev,
+					headset_state_t old_state,
+					headset_state_t new_state,
+					void *user_data)
+{
+	struct media_endpoint *endpoint = user_data;
+
+	DBG("");
+
+	switch (new_state) {
+	case HEADSET_STATE_DISCONNECTED:
+		media_endpoint_clear_configuration(endpoint);
+		break;
+	case HEADSET_STATE_CONNECTING:
+		media_endpoint_set_configuration(endpoint, dev, NULL, 0,
+						headset_setconf_cb, dev);
+		break;
+	case HEADSET_STATE_CONNECTED:
+		break;
+	case HEADSET_STATE_PLAY_IN_PROGRESS:
+		break;
+	case HEADSET_STATE_PLAYING:
+		break;
+	}
+}
+
+static struct media_endpoint *media_endpoint_create(struct media_adapter *adapter,
+						const char *sender,
+						const char *path,
+						const char *uuid,
+						gboolean delay_reporting,
+						uint8_t codec,
+						uint8_t *capabilities,
+						int size)
+{
+	struct media_endpoint *endpoint;
+
+	endpoint = g_new0(struct media_endpoint, 1);
+	endpoint->sender = g_strdup(sender);
+	endpoint->path = g_strdup(path);
+	endpoint->uuid = g_strdup(uuid);
+	endpoint->codec = codec;
+
+	if (size > 0) {
+		endpoint->capabilities = g_new(uint8_t, size);
+		memcpy(endpoint->capabilities, capabilities, size);
+		endpoint->size = size;
+	}
+
+	endpoint->adapter = adapter;
+
+	if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) {
+		endpoint->sep = a2dp_add_sep(&adapter->src,
+					AVDTP_SEP_TYPE_SOURCE, codec,
+					delay_reporting, endpoint);
+		if (endpoint->sep == NULL)
+			goto failed;
+	} else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) {
+		endpoint->sep = a2dp_add_sep(&adapter->src,
+						AVDTP_SEP_TYPE_SINK, codec,
+						delay_reporting, endpoint);
+		if (endpoint->sep == NULL)
+			goto failed;
+	} else if (strcasecmp(uuid, HFP_AG_UUID) == 0 ||
+					g_strcmp0(uuid, HSP_AG_UUID) == 0) {
+		struct audio_device *dev;
+
+		endpoint->hs_watch = headset_add_state_cb(headset_state_changed,
+								endpoint);
+		dev = manager_find_device(NULL, &adapter->src, BDADDR_ANY,
+						AUDIO_HEADSET_INTERFACE, TRUE);
+		if (dev)
+			media_endpoint_set_configuration(endpoint, dev, NULL,
+							0, headset_setconf_cb,
+							dev);
+	} else
+		goto failed;
+
+	endpoint->watch = g_dbus_add_disconnect_watch(adapter->conn, sender,
+						media_endpoint_exit, endpoint,
+						NULL);
+
+	adapter->endpoints = g_slist_append(adapter->endpoints, endpoint);
+	info("Endpoint registered: sender=%s path=%s", sender, path);
+
+	return endpoint;
+
+failed:
+	g_free(endpoint);
+	return NULL;
+}
+
+static struct media_endpoint *media_adapter_find_endpoint(
+						struct media_adapter *adapter,
+						const char *sender,
+						const char *path,
+						const char *uuid)
+{
+	GSList *l;
+
+	for (l = adapter->endpoints; l; l = l->next) {
+		struct media_endpoint *endpoint = l->data;
+
+		if (sender && g_strcmp0(endpoint->sender, sender) != 0)
+			continue;
+
+		if (path && g_strcmp0(endpoint->path, path) != 0)
+			continue;
+
+		if (uuid && g_strcmp0(endpoint->uuid, uuid) != 0)
+			continue;
+
+		return endpoint;
+	}
+
+	return NULL;
+}
+
+const char *media_endpoint_get_sender(struct media_endpoint *endpoint)
+{
+	return endpoint->sender;
+}
+
+static int parse_properties(DBusMessageIter *props, const char **uuid,
+				gboolean *delay_reporting, uint8_t *codec,
+				uint8_t **capabilities, int *size)
+{
+	gboolean has_uuid = FALSE;
+	gboolean has_codec = FALSE;
+
+	while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) {
+		const char *key;
+		DBusMessageIter value, entry;
+		int var;
+
+		dbus_message_iter_recurse(props, &entry);
+		dbus_message_iter_get_basic(&entry, &key);
+
+		dbus_message_iter_next(&entry);
+		dbus_message_iter_recurse(&entry, &value);
+
+		var = dbus_message_iter_get_arg_type(&value);
+		if (strcasecmp(key, "UUID") == 0) {
+			if (var != DBUS_TYPE_STRING)
+				return -EINVAL;
+			dbus_message_iter_get_basic(&value, uuid);
+			has_uuid = TRUE;
+		} else if (strcasecmp(key, "Codec") == 0) {
+			if (var != DBUS_TYPE_BYTE)
+				return -EINVAL;
+			dbus_message_iter_get_basic(&value, codec);
+			has_codec = TRUE;
+		} else if (strcasecmp(key, "DelayReporting") == 0) {
+			if (var != DBUS_TYPE_BOOLEAN)
+				return -EINVAL;
+			dbus_message_iter_get_basic(&value, delay_reporting);
+		} else if (strcasecmp(key, "Capabilities") == 0) {
+			DBusMessageIter array;
+
+			if (var != DBUS_TYPE_ARRAY)
+				return -EINVAL;
+
+			dbus_message_iter_recurse(&value, &array);
+			dbus_message_iter_get_fixed_array(&array, capabilities,
+							size);
+		}
+
+		dbus_message_iter_next(props);
+	}
+
+	return (has_uuid && has_codec) ? 0 : -EINVAL;
+}
+
+static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	struct media_adapter *adapter = data;
+	DBusMessageIter args, props;
+	const char *sender, *path, *uuid;
+	gboolean delay_reporting = FALSE;
+	uint8_t codec;
+	uint8_t *capabilities;
+	int size = 0;
+
+	sender = dbus_message_get_sender(msg);
+
+	dbus_message_iter_init(msg, &args);
+
+	dbus_message_iter_get_basic(&args, &path);
+	dbus_message_iter_next(&args);
+
+	if (media_adapter_find_endpoint(adapter, sender, path, NULL) != NULL)
+		return btd_error_already_exists(msg);
+
+	dbus_message_iter_recurse(&args, &props);
+	if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
+		return btd_error_invalid_args(msg);
+
+	if (parse_properties(&props, &uuid, &delay_reporting, &codec,
+						&capabilities, &size) < 0)
+		return btd_error_invalid_args(msg);
+
+	if (media_endpoint_create(adapter, sender, path, uuid, delay_reporting,
+				codec, capabilities, size) == FALSE)
+		return btd_error_invalid_args(msg);
+
+	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *unregister_endpoint(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	struct media_adapter *adapter = data;
+	struct media_endpoint *endpoint;
+	const char *sender, *path;
+
+	if (!dbus_message_get_args(msg, NULL,
+				DBUS_TYPE_OBJECT_PATH, &path,
+				DBUS_TYPE_INVALID))
+		return NULL;
+
+	sender = dbus_message_get_sender(msg);
+
+	endpoint = media_adapter_find_endpoint(adapter, sender, path, NULL);
+	if (endpoint == NULL)
+		return btd_error_does_not_exist(msg);
+
+	media_endpoint_remove(endpoint);
+
+	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static GDBusMethodTable media_methods[] = {
+	{ "RegisterEndpoint",	"oa{sv}",	"",	register_endpoint },
+	{ "UnregisterEndpoint",	"o",		"",	unregister_endpoint },
+	{ },
+};
+
+static void path_free(void *data)
+{
+	struct media_adapter *adapter = data;
+
+	g_slist_foreach(adapter->endpoints, (GFunc) media_endpoint_release,
+									NULL);
+	g_slist_free(adapter->endpoints);
+
+	dbus_connection_unref(adapter->conn);
+
+	adapters = g_slist_remove(adapters, adapter);
+
+	g_free(adapter->path);
+	g_free(adapter);
+}
+
+int media_register(DBusConnection *conn, const char *path, const bdaddr_t *src)
+{
+	struct media_adapter *adapter;
+
+	if (DBUS_TYPE_UNIX_FD < 0)
+		return -EPERM;
+
+	adapter = g_new0(struct media_adapter, 1);
+	adapter->conn = dbus_connection_ref(conn);
+	bacpy(&adapter->src, src);
+	adapter->path = g_strdup(path);
+
+	if (!g_dbus_register_interface(conn, path, MEDIA_INTERFACE,
+					media_methods, NULL, NULL,
+					adapter, path_free)) {
+		error("D-Bus failed to register %s path", path);
+		path_free(adapter);
+		return -1;
+	}
+
+	adapters = g_slist_append(adapters, adapter);
+
+	return 0;
+}
+
+void media_unregister(const char *path)
+{
+	GSList *l;
+
+	for (l = adapters; l; l = l->next) {
+		struct media_adapter *adapter = l->data;
+
+		if (g_strcmp0(path, adapter->path) == 0) {
+			g_dbus_unregister_interface(adapter->conn, path,
+							MEDIA_INTERFACE);
+			return;
+		}
+	}
+}
+
+size_t media_endpoint_get_capabilities(struct media_endpoint *endpoint,
+					uint8_t **capabilities)
+{
+	*capabilities = endpoint->capabilities;
+	return endpoint->size;
+}
+
+static void endpoint_reply(DBusPendingCall *call, void *user_data)
+{
+	struct media_endpoint *endpoint = user_data;
+	struct endpoint_request *request = endpoint->request;
+	DBusMessage *reply;
+	DBusError err;
+	gboolean value;
+	void *ret = NULL;
+	int size = -1;
+
+	/* steal_reply will always return non-NULL since the callback
+	 * is only called after a reply has been received */
+	reply = dbus_pending_call_steal_reply(call);
+
+	dbus_error_init(&err);
+	if (dbus_set_error_from_message(&err, reply)) {
+		error("Endpoint replied with an error: %s",
+				err.name);
+
+		/* Clear endpoint configuration in case of NO_REPLY error */
+		if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) {
+			if (request->cb)
+				request->cb(endpoint, NULL, size,
+							request->user_data);
+			media_endpoint_clear_configuration(endpoint);
+			dbus_message_unref(reply);
+			dbus_error_free(&err);
+			return;
+		}
+
+		dbus_error_free(&err);
+		goto done;
+	}
+
+	dbus_error_init(&err);
+	if (dbus_message_is_method_call(request->msg, MEDIA_ENDPOINT_INTERFACE,
+				"SelectConfiguration")) {
+		DBusMessageIter args, array;
+		uint8_t *configuration;
+
+		dbus_message_iter_init(reply, &args);
+
+		dbus_message_iter_recurse(&args, &array);
+
+		dbus_message_iter_get_fixed_array(&array, &configuration, &size);
+
+		ret = configuration;
+		goto done;
+	} else  if (!dbus_message_get_args(reply, &err, DBUS_TYPE_INVALID)) {
+		error("Wrong reply signature: %s", err.message);
+		dbus_error_free(&err);
+		goto done;
+	}
+
+	size = 1;
+	value = TRUE;
+	ret = &value;
+
+done:
+	dbus_message_unref(reply);
+
+	if (request->cb)
+		request->cb(endpoint, ret, size, request->user_data);
+
+	endpoint_request_free(request);
+	endpoint->request = NULL;
+}
+
+static gboolean media_endpoint_async_call(DBusConnection *conn,
+					DBusMessage *msg,
+					struct media_endpoint *endpoint,
+					media_endpoint_cb_t cb,
+					void *user_data)
+{
+	struct endpoint_request *request;
+
+	if (endpoint->request)
+		return FALSE;
+
+	request = g_new0(struct endpoint_request, 1);
+
+	/* Timeout should be less than avdtp request timeout (4 seconds) */
+	if (dbus_connection_send_with_reply(conn, msg, &request->call,
+						REQUEST_TIMEOUT) == FALSE) {
+		error("D-Bus send failed");
+		g_free(request);
+		return FALSE;
+	}
+
+	dbus_pending_call_set_notify(request->call, endpoint_reply, endpoint, NULL);
+
+	request->msg = msg;
+	request->cb = cb;
+	request->user_data = user_data;
+	endpoint->request = request;
+
+	DBG("Calling %s: name = %s path = %s", dbus_message_get_member(msg),
+			dbus_message_get_destination(msg),
+			dbus_message_get_path(msg));
+
+	return TRUE;
+}
+
+gboolean media_endpoint_set_configuration(struct media_endpoint *endpoint,
+					struct audio_device *device,
+					uint8_t *configuration, size_t size,
+					media_endpoint_cb_t cb,
+					void *user_data)
+{
+	DBusConnection *conn;
+	DBusMessage *msg;
+	const char *path;
+	DBusMessageIter iter;
+
+	if (endpoint->transport != NULL || endpoint->request != NULL)
+		return FALSE;
+
+	conn = endpoint->adapter->conn;
+
+	endpoint->transport = media_transport_create(conn, endpoint, device,
+						configuration, size);
+	if (endpoint->transport == NULL)
+		return FALSE;
+
+	msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
+						MEDIA_ENDPOINT_INTERFACE,
+						"SetConfiguration");
+	if (msg == NULL) {
+		error("Couldn't allocate D-Bus message");
+		return FALSE;
+	}
+
+	dbus_message_iter_init_append(msg, &iter);
+
+	path = media_transport_get_path(endpoint->transport);
+	dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
+
+	transport_get_properties(endpoint->transport, &iter);
+
+	return media_endpoint_async_call(conn, msg, endpoint, cb, user_data);
+}
+
+gboolean media_endpoint_select_configuration(struct media_endpoint *endpoint,
+						uint8_t *capabilities,
+						size_t length,
+						media_endpoint_cb_t cb,
+						void *user_data)
+{
+	DBusConnection *conn;
+	DBusMessage *msg;
+
+	if (endpoint->request != NULL)
+		return FALSE;
+
+	conn = endpoint->adapter->conn;
+
+	msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
+						MEDIA_ENDPOINT_INTERFACE,
+						"SelectConfiguration");
+	if (msg == NULL) {
+		error("Couldn't allocate D-Bus message");
+		return FALSE;
+	}
+
+	dbus_message_append_args(msg, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+					&capabilities, length,
+					DBUS_TYPE_INVALID);
+
+	return media_endpoint_async_call(conn, msg, endpoint, cb, user_data);
+}
+
+void media_endpoint_clear_configuration(struct media_endpoint *endpoint)
+{
+	DBusConnection *conn;
+	DBusMessage *msg;
+	const char *path;
+
+	if (endpoint->transport == NULL)
+		return;
+
+	if (endpoint->request)
+		media_endpoint_cancel(endpoint);
+
+	conn = endpoint->adapter->conn;
+
+	msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
+						MEDIA_ENDPOINT_INTERFACE,
+						"ClearConfiguration");
+	if (msg == NULL) {
+		error("Couldn't allocate D-Bus message");
+		goto done;
+	}
+
+	path = media_transport_get_path(endpoint->transport);
+	dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path,
+							DBUS_TYPE_INVALID);
+	g_dbus_send_message(conn, msg);
+done:
+	media_transport_remove(endpoint->transport);
+	endpoint->transport = NULL;
+}
+
+void media_endpoint_release(struct media_endpoint *endpoint)
+{
+	DBusMessage *msg;
+
+	DBG("sender=%s path=%s", endpoint->sender, endpoint->path);
+
+	/* already exit */
+	if (endpoint->watch == 0)
+		return;
+
+	msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
+						MEDIA_ENDPOINT_INTERFACE,
+						"Release");
+	if (msg == NULL) {
+		error("Couldn't allocate D-Bus message");
+		return;
+	}
+
+	g_dbus_send_message(endpoint->adapter->conn, msg);
+
+	media_endpoint_remove(endpoint);
+}
+
+struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint)
+{
+	return endpoint->sep;
+}
+
+const char *media_endpoint_get_uuid(struct media_endpoint *endpoint)
+{
+	return endpoint->uuid;
+}
+
+uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint)
+{
+	return endpoint->codec;
+}
+
+struct media_transport *media_endpoint_get_transport(
+					struct media_endpoint *endpoint)
+{
+	return endpoint->transport;
+}
diff --git a/audio/media.h b/audio/media.h
new file mode 100644
index 0000000..d089103
--- /dev/null
+++ b/audio/media.h
@@ -0,0 +1,54 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+struct media_endpoint;
+
+typedef void (*media_endpoint_cb_t) (struct media_endpoint *endpoint,
+					void *ret, int size, void *user_data);
+
+int media_register(DBusConnection *conn, const char *path, const bdaddr_t *src);
+void media_unregister(const char *path);
+
+const char *media_endpoint_get_sender(struct media_endpoint *endpoint);
+
+size_t media_endpoint_get_capabilities(struct media_endpoint *endpoint,
+					uint8_t **capabilities);
+gboolean media_endpoint_set_configuration(struct media_endpoint *endpoint,
+					struct audio_device *device,
+					uint8_t *configuration, size_t size,
+					media_endpoint_cb_t cb,
+					void *user_data);
+gboolean media_endpoint_select_configuration(struct media_endpoint *endpoint,
+						uint8_t *capabilities,
+						size_t length,
+						media_endpoint_cb_t cb,
+						void *user_data);
+void media_endpoint_clear_configuration(struct media_endpoint *endpoint);
+void media_endpoint_release(struct media_endpoint *endpoint);
+
+struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint);
+const char *media_endpoint_get_uuid(struct media_endpoint *endpoint);
+uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint);
+struct media_transport *media_endpoint_get_transport(
+					struct media_endpoint *endpoint);
diff --git a/audio/pcm_bluetooth.c b/audio/pcm_bluetooth.c
index 4c0ab6f..799f17f 100644
--- a/audio/pcm_bluetooth.c
+++ b/audio/pcm_bluetooth.c
@@ -237,9 +237,11 @@
 		ret = poll(fds, 2, poll_timeout);
 
 		if (ret < 0) {
-			SNDERR("poll error: %s (%d)", strerror(errno), errno);
-			if (errno != EINTR)
+			if (errno != EINTR) {
+				SNDERR("poll error: %s (%d)", strerror(errno),
+								errno);
 				break;
+			}
 		} else if (ret > 0) {
 			ret = (fds[0].revents) ? 0 : 1;
 			SNDERR("poll fd %d revents %d", ret, fds[ret].revents);
@@ -1050,8 +1052,11 @@
 	}
 
 	/* Check if we have any left over data from the last write */
-	if (data->count > 0 && (bytes_left - data->count) >= a2dp->codesize) {
-		int additional_bytes_needed = a2dp->codesize - data->count;
+	if (data->count > 0) {
+		unsigned int additional_bytes_needed =
+						a2dp->codesize - data->count;
+		if (additional_bytes_needed > bytes_left)
+			goto out;
 
 		memcpy(data->buffer + data->count, buff,
 						additional_bytes_needed);
@@ -1122,6 +1127,7 @@
 		}
 	}
 
+out:
 	/* Copy the extra to our temp buffer for the next write */
 	if (bytes_left > 0) {
 		memcpy(data->buffer + data->count, buff, bytes_left);
diff --git a/audio/sink.c b/audio/sink.c
index 94fe9f2..97c6004 100644
--- a/audio/sink.c
+++ b/audio/sink.c
@@ -40,6 +40,7 @@
 
 #include "device.h"
 #include "avdtp.h"
+#include "media.h"
 #include "a2dp.h"
 #include "error.h"
 #include "sink.h"
@@ -60,7 +61,6 @@
 	struct avdtp *session;
 	struct avdtp_stream *stream;
 	unsigned int cb_id;
-	guint dc_id;
 	guint retry_id;
 	avdtp_session_state_t session_state;
 	avdtp_state_t stream_state;
@@ -80,6 +80,13 @@
 
 static unsigned int avdtp_callback_id = 0;
 
+static char *str_state[] = {
+	"SINK_STATE_DISCONNECTED",
+	"SINK_STATE_CONNECTING",
+	"SINK_STATE_CONNECTED",
+	"SINK_STATE_PLAYING",
+};
+
 static const char *state2str(sink_state_t state)
 {
 	switch (state) {
@@ -112,6 +119,9 @@
 					AUDIO_SINK_INTERFACE, "State",
 					DBUS_TYPE_STRING, &state_str);
 
+	DBG("State changed %s: %s -> %s", dev->path, str_state[old_state],
+		str_state[new_state]);
+
 	for (l = sink_callbacks; l != NULL; l = l->next) {
 		struct sink_state_callback *cb = l->data;
 		cb->cb(dev, old_state, new_state, cb->user_data);
@@ -139,11 +149,6 @@
 			emit_property_changed(dev->conn, dev->path,
 					AUDIO_SINK_INTERFACE, "Connected",
 					DBUS_TYPE_BOOLEAN, &value);
-			if (sink->dc_id) {
-				device_remove_disconnect_watch(dev->btd_dev,
-								sink->dc_id);
-				sink->dc_id = 0;
-			}
 		}
 		sink_set_state(dev, SINK_STATE_DISCONNECTED);
 		break;
@@ -170,17 +175,6 @@
 	g_free(pending);
 }
 
-static void disconnect_cb(struct btd_device *btd_dev, gboolean removal,
-				void *user_data)
-{
-	struct audio_device *device = user_data;
-	struct sink *sink = device->sink;
-
-	DBG("Sink: disconnect %s", device->path);
-
-	avdtp_close(sink->session, sink->stream, TRUE);
-}
-
 static void stream_state_changed(struct avdtp_stream *stream,
 					avdtp_state_t old_state,
 					avdtp_state_t new_state,
@@ -208,12 +202,6 @@
 			pending_request_free(dev, p);
 		}
 
-		if (sink->dc_id) {
-			device_remove_disconnect_watch(dev->btd_dev,
-							sink->dc_id);
-			sink->dc_id = 0;
-		}
-
 		if (sink->session) {
 			avdtp_unref(sink->session);
 			sink->session = NULL;
@@ -233,9 +221,6 @@
 						AUDIO_SINK_INTERFACE,
 						"Connected",
 						DBUS_TYPE_BOOLEAN, &value);
-			sink->dc_id = device_add_disconnect_watch(dev->btd_dev,
-								disconnect_cb,
-								dev, NULL);
 		} else if (old_state == AVDTP_STATE_STREAMING) {
 			value = FALSE;
 			g_dbus_emit_signal(dev->conn, dev->path,
@@ -268,10 +253,11 @@
 	sink->stream_state = new_state;
 }
 
-static DBusHandlerResult error_failed(DBusConnection *conn,
-					DBusMessage *msg, const char * desc)
+static void error_failed(DBusConnection *conn, DBusMessage *msg,
+							const char *desc)
 {
-	return error_common_reply(conn, msg, ERROR_INTERFACE ".Failed", desc);
+	DBusMessage *reply = btd_error_failed(msg, desc);
+	g_dbus_send_message(conn, reply);
 }
 
 static gboolean stream_setup_retry(gpointer user_data)
@@ -328,7 +314,7 @@
 
 	avdtp_unref(sink->session);
 	sink->session = NULL;
-	if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO
+	if (avdtp_error_category(err) == AVDTP_ERRNO
 			&& avdtp_error_posix_errno(err) != EHOSTDOWN) {
 		DBG("connect:connect XCASE detected");
 		sink->retry_id = g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER,
@@ -343,146 +329,30 @@
 	}
 }
 
-static uint8_t default_bitpool(uint8_t freq, uint8_t mode)
+static void select_complete(struct avdtp *session, struct a2dp_sep *sep,
+			GSList *caps, void *user_data)
 {
-	switch (freq) {
-	case SBC_SAMPLING_FREQ_16000:
-	case SBC_SAMPLING_FREQ_32000:
-		return 53;
-	case SBC_SAMPLING_FREQ_44100:
-		switch (mode) {
-		case SBC_CHANNEL_MODE_MONO:
-		case SBC_CHANNEL_MODE_DUAL_CHANNEL:
-			return 31;
-		case SBC_CHANNEL_MODE_STEREO:
-		case SBC_CHANNEL_MODE_JOINT_STEREO:
-			return 53;
-		default:
-			error("Invalid channel mode %u", mode);
-			return 53;
-		}
-	case SBC_SAMPLING_FREQ_48000:
-		switch (mode) {
-		case SBC_CHANNEL_MODE_MONO:
-		case SBC_CHANNEL_MODE_DUAL_CHANNEL:
-			return 29;
-		case SBC_CHANNEL_MODE_STEREO:
-		case SBC_CHANNEL_MODE_JOINT_STEREO:
-			return 51;
-		default:
-			error("Invalid channel mode %u", mode);
-			return 51;
-		}
-	default:
-		error("Invalid sampling freq %u", freq);
-		return 53;
-	}
-}
+	struct sink *sink = user_data;
+	struct pending_request *pending;
+	int id;
 
-static gboolean select_sbc_params(struct sbc_codec_cap *cap,
-					struct sbc_codec_cap *supported)
-{
-	unsigned int max_bitpool, min_bitpool;
+	pending = sink->connect;
+	pending->id = 0;
 
-	memset(cap, 0, sizeof(struct sbc_codec_cap));
+	id = a2dp_config(session, sep, stream_setup_complete, caps, sink);
+	if (id == 0)
+		goto failed;
 
-	cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
-	cap->cap.media_codec_type = A2DP_CODEC_SBC;
+	pending->id = id;
+	return;
 
-	if (supported->frequency & SBC_SAMPLING_FREQ_44100)
-		cap->frequency = SBC_SAMPLING_FREQ_44100;
-	else if (supported->frequency & SBC_SAMPLING_FREQ_48000)
-		cap->frequency = SBC_SAMPLING_FREQ_48000;
-	else if (supported->frequency & SBC_SAMPLING_FREQ_32000)
-		cap->frequency = SBC_SAMPLING_FREQ_32000;
-	else if (supported->frequency & SBC_SAMPLING_FREQ_16000)
-		cap->frequency = SBC_SAMPLING_FREQ_16000;
-	else {
-		error("No supported frequencies");
-		return FALSE;
-	}
-
-	if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
-		cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
-	else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO)
-		cap->channel_mode = SBC_CHANNEL_MODE_STEREO;
-	else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
-		cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
-	else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO)
-		cap->channel_mode = SBC_CHANNEL_MODE_MONO;
-	else {
-		error("No supported channel modes");
-		return FALSE;
-	}
-
-	if (supported->block_length & SBC_BLOCK_LENGTH_16)
-		cap->block_length = SBC_BLOCK_LENGTH_16;
-	else if (supported->block_length & SBC_BLOCK_LENGTH_12)
-		cap->block_length = SBC_BLOCK_LENGTH_12;
-	else if (supported->block_length & SBC_BLOCK_LENGTH_8)
-		cap->block_length = SBC_BLOCK_LENGTH_8;
-	else if (supported->block_length & SBC_BLOCK_LENGTH_4)
-		cap->block_length = SBC_BLOCK_LENGTH_4;
-	else {
-		error("No supported block lengths");
-		return FALSE;
-	}
-
-	if (supported->subbands & SBC_SUBBANDS_8)
-		cap->subbands = SBC_SUBBANDS_8;
-	else if (supported->subbands & SBC_SUBBANDS_4)
-		cap->subbands = SBC_SUBBANDS_4;
-	else {
-		error("No supported subbands");
-		return FALSE;
-	}
-
-	if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS)
-		cap->allocation_method = SBC_ALLOCATION_LOUDNESS;
-	else if (supported->allocation_method & SBC_ALLOCATION_SNR)
-		cap->allocation_method = SBC_ALLOCATION_SNR;
-
-	min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool);
-	max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode),
-							supported->max_bitpool);
-
-	cap->min_bitpool = min_bitpool;
-	cap->max_bitpool = max_bitpool;
-
-	return TRUE;
-}
-
-static gboolean select_capabilities(struct avdtp *session,
-					struct avdtp_remote_sep *rsep,
-					GSList **caps)
-{
-	struct avdtp_service_capability *media_transport, *media_codec;
-	struct sbc_codec_cap sbc_cap;
-
-	media_codec = avdtp_get_codec(rsep);
-	if (!media_codec)
-		return FALSE;
-
-	select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data);
-
-	media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
-						NULL, 0);
-
-	*caps = g_slist_append(*caps, media_transport);
-
-	media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
-						sizeof(sbc_cap));
-
-	*caps = g_slist_append(*caps, media_codec);
-
-	if (avdtp_get_delay_reporting(rsep)) {
-		struct avdtp_service_capability *delay_reporting;
-		delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
-								NULL, 0);
-		*caps = g_slist_append(*caps, delay_reporting);
-	}
-
-	return TRUE;
+failed:
+	if (pending->msg)
+		error_failed(pending->conn, pending->msg, "Stream setup failed");
+	pending_request_free(sink->dev, pending);
+	sink->connect = NULL;
+	avdtp_unref(sink->session);
+	sink->session = NULL;
 }
 
 static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err,
@@ -490,18 +360,20 @@
 {
 	struct sink *sink = user_data;
 	struct pending_request *pending;
-	struct avdtp_local_sep *lsep;
-	struct avdtp_remote_sep *rsep;
-	struct a2dp_sep *sep;
-	GSList *caps = NULL;
 	int id;
 
+	if (!sink->connect) {
+		avdtp_unref(sink->session);
+		sink->session = NULL;
+		return;
+	}
+
 	pending = sink->connect;
 
 	if (err) {
 		avdtp_unref(sink->session);
 		sink->session = NULL;
-		if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO
+		if (avdtp_error_category(err) == AVDTP_ERRNO
 				&& avdtp_error_posix_errno(err) != EHOSTDOWN) {
 			DBG("connect:connect XCASE detected");
 			sink->retry_id =
@@ -515,24 +387,8 @@
 
 	DBG("Discovery complete");
 
-	if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SINK, AVDTP_MEDIA_TYPE_AUDIO,
-				A2DP_CODEC_SBC, &lsep, &rsep) < 0) {
-		error("No matching ACP and INT SEPs found");
-		goto failed;
-	}
-
-	if (!select_capabilities(session, rsep, &caps)) {
-		error("Unable to select remote SEP capabilities");
-		goto failed;
-	}
-
-	sep = a2dp_get(session, rsep);
-	if (!sep) {
-		error("Unable to get a local source SEP");
-		goto failed;
-	}
-
-	id = a2dp_config(sink->session, sep, stream_setup_complete, caps, sink);
+	id = a2dp_select_capabilities(sink->session, AVDTP_SEP_TYPE_SINK, NULL,
+						select_complete, sink);
 	if (id == 0)
 		goto failed;
 
@@ -580,21 +436,16 @@
 		sink->session = avdtp_get(&dev->src, &dev->dst);
 
 	if (!sink->session)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-						"Unable to get a session");
+		return btd_error_failed(msg, "Unable to get a session");
 
 	if (sink->connect || sink->disconnect)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-						"%s", strerror(EBUSY));
+		return btd_error_busy(msg);
 
 	if (sink->stream_state >= AVDTP_STATE_OPEN)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".AlreadyConnected",
-						"Device Already Connected");
+		return btd_error_already_connected(msg);
 
 	if (!sink_setup_stream(sink, NULL))
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-						"Failed to create a stream");
+		return btd_error_failed(msg, "Failed to create a stream");
 
 	dev->auto_connect = FALSE;
 
@@ -617,13 +468,10 @@
 	int err;
 
 	if (!sink->session)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".NotConnected",
-						"Device not Connected");
+		return btd_error_not_connected(msg);
 
 	if (sink->connect || sink->disconnect)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-						"%s", strerror(EBUSY));
+		return btd_error_busy(msg);
 
 	if (sink->stream_state < AVDTP_STATE_OPEN) {
 		DBusMessage *reply = dbus_message_new_method_return(msg);
@@ -636,8 +484,7 @@
 
 	err = avdtp_close(sink->session, sink->stream, FALSE);
 	if (err < 0)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-						"%s", strerror(-err));
+		return btd_error_failed(msg, strerror(-err));
 
 	pending = g_new0(struct pending_request, 1);
 	pending->conn = dbus_connection_ref(conn);
@@ -808,9 +655,6 @@
 		avdtp_stream_remove_cb(sink->session, sink->stream,
 					sink->cb_id);
 
-	if (sink->dc_id)
-		device_remove_disconnect_watch(dev->btd_dev, sink->dc_id);
-
 	if (sink->session)
 		avdtp_unref(sink->session);
 
@@ -905,6 +749,31 @@
 
 gboolean sink_shutdown(struct sink *sink)
 {
+	if (!sink->session)
+		return FALSE;
+
+	avdtp_set_device_disconnect(sink->session, TRUE);
+
+	/* cancel pending connect */
+	if (sink->connect) {
+		struct pending_request *pending = sink->connect;
+
+		if (pending->msg)
+			error_failed(pending->conn, pending->msg,
+							"Stream setup failed");
+		pending_request_free(sink->dev, pending);
+		sink->connect = NULL;
+
+		avdtp_unref(sink->session);
+		sink->session = NULL;
+
+		return TRUE;
+	}
+
+	/* disconnect already ongoing */
+	if (sink->disconnect)
+		return TRUE;
+
 	if (!sink->stream)
 		return FALSE;
 
diff --git a/audio/source.c b/audio/source.c
index 35d8136..6d266f2 100644
--- a/audio/source.c
+++ b/audio/source.c
@@ -41,6 +41,7 @@
 
 #include "device.h"
 #include "avdtp.h"
+#include "media.h"
 #include "a2dp.h"
 #include "error.h"
 #include "source.h"
@@ -61,7 +62,6 @@
 	struct avdtp *session;
 	struct avdtp_stream *stream;
 	unsigned int cb_id;
-	guint dc_id;
 	guint retry_id;
 	avdtp_session_state_t session_state;
 	avdtp_state_t stream_state;
@@ -132,12 +132,6 @@
 
 	switch (new_state) {
 	case AVDTP_SESSION_STATE_DISCONNECTED:
-		if (source->state != SOURCE_STATE_CONNECTING &&
-				source->dc_id) {
-			device_remove_disconnect_watch(dev->btd_dev,
-							source->dc_id);
-			source->dc_id = 0;
-		}
 		source_set_state(dev, SOURCE_STATE_DISCONNECTED);
 		break;
 	case AVDTP_SESSION_STATE_CONNECTING:
@@ -163,17 +157,6 @@
 	g_free(pending);
 }
 
-static void disconnect_cb(struct btd_device *btd_dev, gboolean removal,
-				void *user_data)
-{
-	struct audio_device *device = user_data;
-	struct source *source = device->source;
-
-	DBG("Source: disconnect %s", device->path);
-
-	avdtp_close(source->session, source->stream, TRUE);
-}
-
 static void stream_state_changed(struct avdtp_stream *stream,
 					avdtp_state_t old_state,
 					avdtp_state_t new_state,
@@ -200,12 +183,6 @@
 			pending_request_free(dev, p);
 		}
 
-		if (source->dc_id) {
-			device_remove_disconnect_watch(dev->btd_dev,
-							source->dc_id);
-			source->dc_id = 0;
-		}
-
 		if (source->session) {
 			avdtp_unref(source->session);
 			source->session = NULL;
@@ -214,12 +191,6 @@
 		source->cb_id = 0;
 		break;
 	case AVDTP_STATE_OPEN:
-		if (old_state == AVDTP_STATE_CONFIGURED &&
-				source->state == SOURCE_STATE_CONNECTING) {
-			source->dc_id = device_add_disconnect_watch(dev->btd_dev,
-								disconnect_cb,
-								dev, NULL);
-		}
 		source_set_state(dev, SOURCE_STATE_CONNECTED);
 		break;
 	case AVDTP_STATE_STREAMING:
@@ -235,10 +206,11 @@
 	source->stream_state = new_state;
 }
 
-static DBusHandlerResult error_failed(DBusConnection *conn,
-					DBusMessage *msg, const char * desc)
+static void error_failed(DBusConnection *conn, DBusMessage *msg,
+							const char *desc)
 {
-	return error_common_reply(conn, msg, ERROR_INTERFACE ".Failed", desc);
+	DBusMessage *reply = btd_error_failed(msg, desc);
+	g_dbus_send_message(conn, reply);
 }
 
 static gboolean stream_setup_retry(gpointer user_data)
@@ -295,7 +267,7 @@
 
 	avdtp_unref(source->session);
 	source->session = NULL;
-	if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO
+	if (avdtp_error_category(err) == AVDTP_ERRNO
 			&& avdtp_error_posix_errno(err) != EHOSTDOWN) {
 		DBG("connect:connect XCASE detected");
 		source->retry_id = g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER,
@@ -310,140 +282,34 @@
 	}
 }
 
-static uint8_t default_bitpool(uint8_t freq, uint8_t mode)
+static void select_complete(struct avdtp *session, struct a2dp_sep *sep,
+			GSList *caps, void *user_data)
 {
-	switch (freq) {
-	case SBC_SAMPLING_FREQ_16000:
-	case SBC_SAMPLING_FREQ_32000:
-		return 53;
-	case SBC_SAMPLING_FREQ_44100:
-		switch (mode) {
-		case SBC_CHANNEL_MODE_MONO:
-		case SBC_CHANNEL_MODE_DUAL_CHANNEL:
-			return 31;
-		case SBC_CHANNEL_MODE_STEREO:
-		case SBC_CHANNEL_MODE_JOINT_STEREO:
-			return 53;
-		default:
-			error("Invalid channel mode %u", mode);
-			return 53;
-		}
-	case SBC_SAMPLING_FREQ_48000:
-		switch (mode) {
-		case SBC_CHANNEL_MODE_MONO:
-		case SBC_CHANNEL_MODE_DUAL_CHANNEL:
-			return 29;
-		case SBC_CHANNEL_MODE_STEREO:
-		case SBC_CHANNEL_MODE_JOINT_STEREO:
-			return 51;
-		default:
-			error("Invalid channel mode %u", mode);
-			return 51;
-		}
-	default:
-		error("Invalid sampling freq %u", freq);
-		return 53;
-	}
-}
+	struct source *source = user_data;
+	struct pending_request *pending;
+	int id;
 
-static gboolean select_sbc_params(struct sbc_codec_cap *cap,
-					struct sbc_codec_cap *supported)
-{
-	unsigned int max_bitpool, min_bitpool;
+	pending = source->connect;
 
-	memset(cap, 0, sizeof(struct sbc_codec_cap));
+	pending->id = 0;
 
-	cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
-	cap->cap.media_codec_type = A2DP_CODEC_SBC;
+	if (caps == NULL)
+		goto failed;
 
-	if (supported->frequency & SBC_SAMPLING_FREQ_44100)
-		cap->frequency = SBC_SAMPLING_FREQ_44100;
-	else if (supported->frequency & SBC_SAMPLING_FREQ_48000)
-		cap->frequency = SBC_SAMPLING_FREQ_48000;
-	else if (supported->frequency & SBC_SAMPLING_FREQ_32000)
-		cap->frequency = SBC_SAMPLING_FREQ_32000;
-	else if (supported->frequency & SBC_SAMPLING_FREQ_16000)
-		cap->frequency = SBC_SAMPLING_FREQ_16000;
-	else {
-		error("No supported frequencies");
-		return FALSE;
-	}
+	id = a2dp_config(session, sep, stream_setup_complete, caps, source);
+	if (id == 0)
+		goto failed;
 
-	if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
-		cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
-	else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO)
-		cap->channel_mode = SBC_CHANNEL_MODE_STEREO;
-	else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
-		cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
-	else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO)
-		cap->channel_mode = SBC_CHANNEL_MODE_MONO;
-	else {
-		error("No supported channel modes");
-		return FALSE;
-	}
+	pending->id = id;
+	return;
 
-	if (supported->block_length & SBC_BLOCK_LENGTH_16)
-		cap->block_length = SBC_BLOCK_LENGTH_16;
-	else if (supported->block_length & SBC_BLOCK_LENGTH_12)
-		cap->block_length = SBC_BLOCK_LENGTH_12;
-	else if (supported->block_length & SBC_BLOCK_LENGTH_8)
-		cap->block_length = SBC_BLOCK_LENGTH_8;
-	else if (supported->block_length & SBC_BLOCK_LENGTH_4)
-		cap->block_length = SBC_BLOCK_LENGTH_4;
-	else {
-		error("No supported block lengths");
-		return FALSE;
-	}
-
-	if (supported->subbands & SBC_SUBBANDS_8)
-		cap->subbands = SBC_SUBBANDS_8;
-	else if (supported->subbands & SBC_SUBBANDS_4)
-		cap->subbands = SBC_SUBBANDS_4;
-	else {
-		error("No supported subbands");
-		return FALSE;
-	}
-
-	if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS)
-		cap->allocation_method = SBC_ALLOCATION_LOUDNESS;
-	else if (supported->allocation_method & SBC_ALLOCATION_SNR)
-		cap->allocation_method = SBC_ALLOCATION_SNR;
-
-	min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool);
-	max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode),
-							supported->max_bitpool);
-
-	cap->min_bitpool = min_bitpool;
-	cap->max_bitpool = max_bitpool;
-
-	return TRUE;
-}
-
-static gboolean select_capabilities(struct avdtp *session,
-					struct avdtp_remote_sep *rsep,
-					GSList **caps)
-{
-	struct avdtp_service_capability *media_transport, *media_codec;
-	struct sbc_codec_cap sbc_cap;
-
-	media_codec = avdtp_get_codec(rsep);
-	if (!media_codec)
-		return FALSE;
-
-	select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data);
-
-	media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
-						NULL, 0);
-
-	*caps = g_slist_append(*caps, media_transport);
-
-	media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
-						sizeof(sbc_cap));
-
-	*caps = g_slist_append(*caps, media_codec);
-
-
-	return TRUE;
+failed:
+	if (pending->msg)
+		error_failed(pending->conn, pending->msg, "Stream setup failed");
+	pending_request_free(source->dev, pending);
+	source->connect = NULL;
+	avdtp_unref(source->session);
+	source->session = NULL;
 }
 
 static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err,
@@ -451,10 +317,6 @@
 {
 	struct source *source = user_data;
 	struct pending_request *pending;
-	struct avdtp_local_sep *lsep;
-	struct avdtp_remote_sep *rsep;
-	struct a2dp_sep *sep;
-	GSList *caps = NULL;
 	int id;
 
 	pending = source->connect;
@@ -462,7 +324,7 @@
 	if (err) {
 		avdtp_unref(source->session);
 		source->session = NULL;
-		if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO
+		if (avdtp_error_category(err) == AVDTP_ERRNO
 				&& avdtp_error_posix_errno(err) != EHOSTDOWN) {
 			DBG("connect:connect XCASE detected");
 			source->retry_id =
@@ -476,25 +338,8 @@
 
 	DBG("Discovery complete");
 
-	if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SOURCE, AVDTP_MEDIA_TYPE_AUDIO,
-				A2DP_CODEC_SBC, &lsep, &rsep) < 0) {
-		error("No matching ACP and INT SEPs found");
-		goto failed;
-	}
-
-	if (!select_capabilities(session, rsep, &caps)) {
-		error("Unable to select remote SEP capabilities");
-		goto failed;
-	}
-
-	sep = a2dp_get(session, rsep);
-	if (!sep) {
-		error("Unable to get a local sink SEP");
-		goto failed;
-	}
-
-	id = a2dp_config(source->session, sep, stream_setup_complete, caps,
-				source);
+	id = a2dp_select_capabilities(source->session, AVDTP_SEP_TYPE_SOURCE, NULL,
+						select_complete, source);
 	if (id == 0)
 		goto failed;
 
@@ -542,21 +387,16 @@
 		source->session = avdtp_get(&dev->src, &dev->dst);
 
 	if (!source->session)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-						"Unable to get a session");
+		return btd_error_failed(msg, "Unable to get a session");
 
 	if (source->connect || source->disconnect)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-						"%s", strerror(EBUSY));
+		return btd_error_busy(msg);
 
 	if (source->stream_state >= AVDTP_STATE_OPEN)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".AlreadyConnected",
-						"Device Already Connected");
+		return btd_error_already_connected(msg);
 
 	if (!source_setup_stream(source, NULL))
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-						"Failed to create a stream");
+		return btd_error_failed(msg, "Failed to create a stream");
 
 	dev->auto_connect = FALSE;
 
@@ -579,13 +419,10 @@
 	int err;
 
 	if (!source->session)
-		return g_dbus_create_error(msg, ERROR_INTERFACE
-						".NotConnected",
-						"Device not Connected");
+		return btd_error_not_connected(msg);
 
 	if (source->connect || source->disconnect)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-						"%s", strerror(EBUSY));
+		return btd_error_busy(msg);
 
 	if (source->stream_state < AVDTP_STATE_OPEN) {
 		DBusMessage *reply = dbus_message_new_method_return(msg);
@@ -598,8 +435,7 @@
 
 	err = avdtp_close(source->session, source->stream, FALSE);
 	if (err < 0)
-		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
-						"%s", strerror(-err));
+		return btd_error_failed(msg, strerror(-err));
 
 	pending = g_new0(struct pending_request, 1);
 	pending->conn = dbus_connection_ref(conn);
@@ -662,9 +498,6 @@
 		avdtp_stream_remove_cb(source->session, source->stream,
 					source->cb_id);
 
-	if (source->dc_id)
-		device_remove_disconnect_watch(dev->btd_dev, source->dc_id);
-
 	if (source->session)
 		avdtp_unref(source->session);
 
diff --git a/audio/telephony-dummy.c b/audio/telephony-dummy.c
index 06cb798..1f89079 100644
--- a/audio/telephony-dummy.c
+++ b/audio/telephony-dummy.c
@@ -35,6 +35,7 @@
 
 #include "log.h"
 #include "telephony.h"
+#include "error.h"
 
 #define TELEPHONY_DUMMY_IFACE "org.bluez.TelephonyTest"
 #define TELEPHONY_DUMMY_PATH "/org/bluez/test"
@@ -49,14 +50,6 @@
 
 static gboolean events_enabled = FALSE;
 
-/* Response and hold state
- * -1 = none
- *  0 = incoming call is put on hold in the AG
- *  1 = held incoming call is accepted in the AG
- *  2 = held incoming call is rejected in the AG
- */
-static int response_and_hold = -1;
-
 static struct indicator dummy_indicators[] =
 {
 	{ "battchg",	"0-5",	5,	TRUE },
@@ -69,12 +62,6 @@
 	{ NULL }
 };
 
-static inline DBusMessage *invalid_args(DBusMessage *msg)
-{
-	return g_dbus_create_error(msg, "org.bluez.Error.InvalidArguments",
-					"Invalid arguments in method call");
-}
-
 void telephony_device_connected(void *telephony_device)
 {
 	DBG("telephony-dummy: device %p connected", telephony_device);
@@ -95,11 +82,8 @@
 
 void telephony_response_and_hold_req(void *telephony_device, int rh)
 {
-	response_and_hold = rh;
-
-	telephony_response_and_hold_ind(response_and_hold);
-
-	telephony_response_and_hold_rsp(telephony_device, CME_ERROR_NONE);
+	telephony_response_and_hold_rsp(telephony_device,
+						CME_ERROR_NOT_SUPPORTED);
 }
 
 void telephony_last_dialed_number_req(void *telephony_device)
@@ -236,7 +220,7 @@
 
 	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
 						DBUS_TYPE_INVALID))
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 
 	DBG("telephony-dummy: outgoing call to %s", number);
 
@@ -261,7 +245,7 @@
 
 	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
 						DBUS_TYPE_INVALID))
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 
 	DBG("telephony-dummy: incoming call to %s", number);
 
@@ -307,10 +291,10 @@
 
 	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &strength,
 						DBUS_TYPE_INVALID))
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 
 	if (strength > 5)
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 
 	telephony_update_indicator(dummy_indicators, "signal", strength);
 
@@ -326,10 +310,10 @@
 
 	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &level,
 						DBUS_TYPE_INVALID))
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 
 	if (level > 5)
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 
 	telephony_update_indicator(dummy_indicators, "battchg", level);
 
@@ -346,7 +330,7 @@
 
 	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &roaming,
 						DBUS_TYPE_INVALID))
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 
 	val = roaming ? EV_ROAM_ACTIVE : EV_ROAM_INACTIVE;
 
@@ -365,7 +349,7 @@
 
 	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &registration,
 						DBUS_TYPE_INVALID))
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 
 	val = registration ? EV_SERVICE_PRESENT : EV_SERVICE_NONE;
 
@@ -384,7 +368,7 @@
 
 	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
 						DBUS_TYPE_INVALID))
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 
 	g_free(subscriber_number);
 	subscriber_number = g_strdup(number);
@@ -417,6 +401,8 @@
 				AG_FEATURE_ENHANCED_CALL_STATUS |
 				AG_FEATURE_EXTENDED_ERROR_RESULT_CODES;
 
+	DBG("");
+
 	connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
 
 	if (g_dbus_register_interface(connection, TELEPHONY_DUMMY_PATH,
@@ -428,14 +414,20 @@
 		return -1;
 	}
 
-	telephony_ready_ind(features, dummy_indicators, response_and_hold,
-				chld_str);
+	telephony_ready_ind(features, dummy_indicators, BTRH_NOT_SUPPORTED,
+								chld_str);
 
 	return 0;
 }
 
 void telephony_exit(void)
 {
+	DBG("");
+
+	g_dbus_unregister_interface(connection, TELEPHONY_DUMMY_PATH,
+						TELEPHONY_DUMMY_IFACE);
 	dbus_connection_unref(connection);
 	connection = NULL;
+
+	telephony_deinit();
 }
diff --git a/audio/telephony-maemo5.c b/audio/telephony-maemo5.c
index 4d0134c..350df9e 100644
--- a/audio/telephony-maemo5.c
+++ b/audio/telephony-maemo5.c
@@ -38,6 +38,7 @@
 
 #include "log.h"
 #include "telephony.h"
+#include "error.h"
 
 /* SSC D-Bus definitions */
 #define SSC_DBUS_NAME  "com.nokia.phone.SSC"
@@ -209,14 +210,6 @@
 /* Supported set of call hold operations */
 static const char *chld_str = "0,1,1x,2,2x,3,4";
 
-/* Response and hold state
- * -1 = none
- *  0 = incoming call is put on hold in the AG
- *  1 = held incoming call is accepted in the AG
- *  2 = held incoming call is rejected in the AG
- */
-static int response_and_hold = -1;
-
 static char *last_dialed_number = NULL;
 
 /* Timer for tracking call creation requests */
@@ -516,11 +509,8 @@
 
 void telephony_response_and_hold_req(void *telephony_device, int rh)
 {
-	response_and_hold = rh;
-
-	telephony_response_and_hold_ind(response_and_hold);
-
-	telephony_response_and_hold_rsp(telephony_device, CME_ERROR_NONE);
+	telephony_response_and_hold_rsp(telephony_device,
+						CME_ERROR_NOT_SUPPORTED);
 }
 
 void telephony_last_dialed_number_req(void *telephony_device)
@@ -1880,12 +1870,6 @@
 	}
 }
 
-static inline DBusMessage *invalid_args(DBusMessage *msg)
-{
-	return g_dbus_create_error(msg,"org.bluez.Error.InvalidArguments",
-					"Invalid arguments in method call");
-}
-
 static uint32_t get_callflag(const char *callerid_setting)
 {
 	if (callerid_setting != NULL) {
@@ -1950,7 +1934,7 @@
 	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING,
 						&callerid_setting,
 						DBUS_TYPE_INVALID) == FALSE)
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 
 	if (g_str_equal(callerid_setting, "allowed") ||
 			g_str_equal(callerid_setting, "restricted") ||
@@ -1964,7 +1948,7 @@
 
 	error("telephony-maemo: invalid argument %s for method call"
 					" SetCallerId", callerid_setting);
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 }
 
 static GDBusMethodTable telephony_maemo_methods[] = {
@@ -2091,7 +2075,7 @@
 	DBG("telephony-maemo registering %s interface on path %s",
 			TELEPHONY_MAEMO_INTERFACE, TELEPHONY_MAEMO_PATH);
 
-	telephony_ready_ind(features, maemo_indicators, response_and_hold,
+	telephony_ready_ind(features, maemo_indicators, BTRH_NOT_SUPPORTED,
 								chld_str);
 	if (send_method_call("org.freedesktop.Hal",
 				"/org/freedesktop/Hal/Manager",
@@ -2115,4 +2099,6 @@
 
 	dbus_connection_unref(connection);
 	connection = NULL;
+
+	telephony_deinit();
 }
diff --git a/audio/telephony-maemo6.c b/audio/telephony-maemo6.c
index 046620c..b0f314c 100644
--- a/audio/telephony-maemo6.c
+++ b/audio/telephony-maemo6.c
@@ -38,6 +38,7 @@
 
 #include "log.h"
 #include "telephony.h"
+#include "error.h"
 
 /* SSC D-Bus definitions */
 #define SSC_DBUS_NAME  "com.nokia.phone.SSC"
@@ -149,6 +150,8 @@
 static DBusConnection *connection = NULL;
 
 static GSList *calls = NULL;
+static GSList *watches = NULL;
+static GSList *pending = NULL;
 
 /* Reference count for determining the call indicator status */
 static GSList *active_calls = NULL;
@@ -168,14 +171,6 @@
 /* Supported set of call hold operations */
 static const char *chld_str = "0,1,1x,2,2x,3,4";
 
-/* Response and hold state
- * -1 = none
- *  0 = incoming call is put on hold in the AG
- *  1 = held incoming call is accepted in the AG
- *  2 = held incoming call is rejected in the AG
- */
-static int response_and_hold = -1;
-
 static char *last_dialed_number = NULL;
 
 /* Timer for tracking call creation requests */
@@ -476,11 +471,8 @@
 
 void telephony_response_and_hold_req(void *telephony_device, int rh)
 {
-	response_and_hold = rh;
-
-	telephony_response_and_hold_ind(response_and_hold);
-
-	telephony_response_and_hold_rsp(telephony_device, CME_ERROR_NONE);
+	telephony_response_and_hold_rsp(telephony_device,
+						CME_ERROR_NOT_SUPPORTED);
 }
 
 void telephony_last_dialed_number_req(void *telephony_device)
@@ -498,6 +490,7 @@
 void telephony_terminate_call_req(void *telephony_device)
 {
 	struct csd_call *call;
+	struct csd_call *alerting;
 	int err;
 
 	call = find_call_with_status(CSD_CALL_STATUS_ACTIVE);
@@ -511,7 +504,10 @@
 		return;
 	}
 
-	if (call->conference)
+	alerting = find_call_with_status(CSD_CALL_STATUS_MO_ALERTING);
+	if (call->on_hold && alerting)
+		err = release_call(alerting);
+	else if (call->conference)
 		err = release_conference();
 	else
 		err = release_call(call);
@@ -587,7 +583,7 @@
 	}
 
 	dbus_pending_call_set_notify(call, cb, user_data, NULL);
-	dbus_pending_call_unref(call);
+	pending = g_slist_prepend(pending, call);
 	dbus_message_unref(msg);
 
 	return 0;
@@ -780,8 +776,11 @@
 
 	switch (cmd[0]) {
 	case '0':
-		foreach_call_with_status(CSD_CALL_STATUS_HOLD, release_call);
-		foreach_call_with_status(CSD_CALL_STATUS_WAITING,
+		if (find_call_with_status(CSD_CALL_STATUS_WAITING))
+			foreach_call_with_status(CSD_CALL_STATUS_WAITING,
+								release_call);
+		else
+			foreach_call_with_status(CSD_CALL_STATUS_HOLD,
 								release_call);
 		break;
 	case '1':
@@ -907,15 +906,16 @@
 	g_free(call->number);
 	call->number = g_strdup(number);
 
-	telephony_update_indicator(maemo_indicators, "callsetup",
-					EV_CALLSETUP_INCOMING);
-
-	if (find_call_with_status(CSD_CALL_STATUS_ACTIVE))
+	if (find_call_with_status(CSD_CALL_STATUS_ACTIVE) ||
+			find_call_with_status(CSD_CALL_STATUS_HOLD))
 		telephony_call_waiting_ind(call->number,
 						number_type(call->number));
 	else
 		telephony_incoming_call_ind(call->number,
 						number_type(call->number));
+
+	telephony_update_indicator(maemo_indicators, "callsetup",
+					EV_CALLSETUP_INCOMING);
 }
 
 static void handle_outgoing_call(DBusMessage *msg)
@@ -973,37 +973,16 @@
 					EV_CALLSETUP_OUTGOING);
 }
 
-static void handle_call_status(DBusMessage *msg, const char *call_path)
+static void call_set_status(struct csd_call *call, dbus_uint32_t status)
 {
-	struct csd_call *call;
-	dbus_uint32_t status, cause_type, cause;
+	dbus_uint32_t prev_status;
 	int callheld = telephony_get_indicator(maemo_indicators, "callheld");
 
-	if (!dbus_message_get_args(msg, NULL,
-					DBUS_TYPE_UINT32, &status,
-					DBUS_TYPE_UINT32, &cause_type,
-					DBUS_TYPE_UINT32, &cause,
-					DBUS_TYPE_INVALID)) {
-		error("Unexpected paramters in Instance.CallStatus() signal");
-		return;
-	}
+	prev_status = call->status;
+	DBG("Call %s changed from %s to %s", call->object_path,
+		call_status_str[prev_status], call_status_str[status]);
 
-	call = find_call(call_path);
-	if (!call) {
-		error("Didn't find any matching call object for %s",
-				call_path);
-		return;
-	}
-
-	if (status > 16) {
-		error("Invalid call status %u", status);
-		return;
-	}
-
-	DBG("Call %s changed from %s to %s", call_path,
-		call_status_str[call->status], call_status_str[status]);
-
-	if (call->status == (int) status) {
+	if (prev_status == status) {
 		DBG("Ignoring CSD Call state change to existing state");
 		return;
 	}
@@ -1043,6 +1022,14 @@
 						EV_CALLSETUP_ALERTING);
 		break;
 	case CSD_CALL_STATUS_MT_ALERTING:
+		/* Some headsets expect incoming call notification before they
+		 * can send ATA command. When call changed status from waiting
+		 * to alerting we need to send missing notification. Otherwise
+		 * headsets like Nokia BH-108 or BackBeat 903 are unable to
+		 * answer incoming call that was previously waiting. */
+		if (prev_status == CSD_CALL_STATUS_WAITING)
+			telephony_incoming_call_ind(call->number,
+						number_type(call->number));
 		break;
 	case CSD_CALL_STATUS_WAITING:
 		break;
@@ -1123,6 +1110,35 @@
 	}
 }
 
+static void handle_call_status(DBusMessage *msg, const char *call_path)
+{
+	struct csd_call *call;
+	dbus_uint32_t status, cause_type, cause;
+
+	if (!dbus_message_get_args(msg, NULL,
+					DBUS_TYPE_UINT32, &status,
+					DBUS_TYPE_UINT32, &cause_type,
+					DBUS_TYPE_UINT32, &cause,
+					DBUS_TYPE_INVALID)) {
+		error("Unexpected paramters in Instance.CallStatus() signal");
+		return;
+	}
+
+	call = find_call(call_path);
+	if (!call) {
+		error("Didn't find any matching call object for %s",
+				call_path);
+		return;
+	}
+
+	if (status > 16) {
+		error("Invalid call status %u", status);
+		return;
+	}
+
+	call_set_status(call, status);
+}
+
 static void handle_conference(DBusMessage *msg, gboolean joined)
 {
 	const char *path;
@@ -1294,6 +1310,12 @@
 	return type == DBUS_TYPE_INVALID ? TRUE : FALSE;
 }
 
+static void remove_pending(DBusPendingCall *call)
+{
+	pending = g_slist_remove(pending, call);
+	dbus_pending_call_unref(call);
+}
+
 static void hal_battery_level_reply(DBusPendingCall *call, void *user_data)
 {
 	DBusError err;
@@ -1342,8 +1364,10 @@
 
 		telephony_update_indicator(maemo_indicators, "battchg", new);
 	}
+
 done:
 	dbus_message_unref(reply);
+	remove_pending(call);
 }
 
 static void hal_get_integer(const char *path, const char *key, void *user_data)
@@ -1453,13 +1477,12 @@
 		if (!call) {
 			call = g_new0(struct csd_call, 1);
 			call->object_path = g_strdup(object_path);
-			call->status = (int) status;
 			calls = g_slist_append(calls, call);
 			DBG("telephony-maemo6: new csd call instance at %s",
 								object_path);
 		}
 
-		if (call->status == CSD_CALL_STATUS_IDLE)
+		if (status == CSD_CALL_STATUS_IDLE)
 			continue;
 
 		/* CSD gives incorrect call_hold property sometimes */
@@ -1476,6 +1499,9 @@
 		g_free(call->number);
 		call->number = g_strdup(number);
 
+		/* Update indicators */
+		call_set_status(call, status);
+
 	} while (dbus_message_iter_next(iter));
 }
 
@@ -1485,8 +1511,7 @@
 		return;
 
 	g_free(net.operator_name);
-	net.operator_name = g_strdup(name);
-
+	net.operator_name = g_strndup(name, 16);
 	DBG("telephony-maemo6: operator name updated: %s", name);
 }
 
@@ -1539,6 +1564,7 @@
 done:
 	g_free(prop);
 	dbus_message_unref(reply);
+	remove_pending(call);
 }
 
 static int get_property(const char *iface, const char *prop)
@@ -1598,61 +1624,9 @@
 
 done:
 	dbus_message_unref(reply);
+	remove_pending(call);
 }
 
-static void hal_find_device_reply(DBusPendingCall *call, void *user_data)
-{
-	DBusError err;
-	DBusMessage *reply;
-	DBusMessageIter iter, sub;
-	const char *path;
-	char match_string[256];
-	int type;
-
-	reply = dbus_pending_call_steal_reply(call);
-
-	dbus_error_init(&err);
-	if (dbus_set_error_from_message(&err, reply)) {
-		error("hald replied with an error: %s, %s",
-				err.name, err.message);
-		dbus_error_free(&err);
-		goto done;
-	}
-
-	dbus_message_iter_init(reply, &iter);
-
-	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
-		error("Unexpected signature in FindDeviceByCapability return");
-		goto done;
-	}
-
-	dbus_message_iter_recurse(&iter, &sub);
-
-	type = dbus_message_iter_get_arg_type(&sub);
-
-	if (type != DBUS_TYPE_OBJECT_PATH && type != DBUS_TYPE_STRING) {
-		error("No hal device with battery capability found");
-		goto done;
-	}
-
-	dbus_message_iter_get_basic(&sub, &path);
-
-	DBG("telephony-maemo6: found battery device at %s", path);
-
-	snprintf(match_string, sizeof(match_string),
-			"type='signal',"
-			"path='%s',"
-			"interface='org.freedesktop.Hal.Device',"
-			"member='PropertyModified'", path);
-	dbus_bus_add_match(connection, match_string, NULL);
-
-	hal_get_integer(path, "battery.charge_level.last_full", &battchg_last);
-	hal_get_integer(path, "battery.charge_level.current", &battchg_cur);
-	hal_get_integer(path, "battery.charge_level.design", &battchg_design);
-
-done:
-	dbus_message_unref(reply);
-}
 
 static void phonebook_read_reply(DBusPendingCall *call, void *user_data)
 {
@@ -1701,6 +1675,7 @@
 
 done:
 	dbus_message_unref(reply);
+	remove_pending(call);
 }
 
 static void csd_init(void)
@@ -1744,12 +1719,6 @@
 	}
 }
 
-static inline DBusMessage *invalid_args(DBusMessage *msg)
-{
-	return g_dbus_create_error(msg,"org.bluez.Error.InvalidArguments",
-					"Invalid arguments in method call");
-}
-
 static uint32_t get_callflag(const char *callerid_setting)
 {
 	if (callerid_setting != NULL) {
@@ -1814,7 +1783,7 @@
 	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING,
 						&callerid_setting,
 						DBUS_TYPE_INVALID) == FALSE)
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 
 	if (g_str_equal(callerid_setting, "allowed") ||
 			g_str_equal(callerid_setting, "restricted") ||
@@ -1828,7 +1797,7 @@
 
 	error("telephony-maemo6: invalid argument %s for method call"
 					" SetCallerId", callerid_setting);
-		return invalid_args(msg);
+		return btd_error_invalid_args(msg);
 }
 
 static DBusMessage *clear_lastnumber(DBusConnection *conn, DBusMessage *msg,
@@ -1879,16 +1848,14 @@
 		handle_modem_state(reply);
 
 	dbus_message_unref(reply);
+	remove_pending(call);
 }
 
-static DBusHandlerResult signal_filter(DBusConnection *conn,
-						DBusMessage *msg, void *data)
+static gboolean signal_filter(DBusConnection *conn, DBusMessage *msg,
+								void *data)
 {
 	const char *path = dbus_message_get_path(msg);
 
-	if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL)
-		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-
 	if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Coming"))
 		handle_incoming_call(msg);
 	else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Created"))
@@ -1918,7 +1885,68 @@
 						"modem_state_changed_ind"))
 		handle_modem_state(msg);
 
-	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+	return TRUE;
+}
+
+static void add_watch(const char *sender, const char *path,
+				const char *interface, const char *member)
+{
+	guint watch;
+
+	watch = g_dbus_add_signal_watch(connection, sender, path, interface,
+					member, signal_filter, NULL, NULL);
+
+	watches = g_slist_prepend(watches, GUINT_TO_POINTER(watch));
+}
+
+static void hal_find_device_reply(DBusPendingCall *call, void *user_data)
+{
+	DBusError err;
+	DBusMessage *reply;
+	DBusMessageIter iter, sub;
+	const char *path;
+	int type;
+
+	reply = dbus_pending_call_steal_reply(call);
+
+	dbus_error_init(&err);
+	if (dbus_set_error_from_message(&err, reply)) {
+		error("hald replied with an error: %s, %s",
+				err.name, err.message);
+		dbus_error_free(&err);
+		goto done;
+	}
+
+	dbus_message_iter_init(reply, &iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+		error("Unexpected signature in FindDeviceByCapability return");
+		goto done;
+	}
+
+	dbus_message_iter_recurse(&iter, &sub);
+
+	type = dbus_message_iter_get_arg_type(&sub);
+
+	if (type != DBUS_TYPE_OBJECT_PATH && type != DBUS_TYPE_STRING) {
+		error("No hal device with battery capability found");
+		goto done;
+	}
+
+	dbus_message_iter_get_basic(&sub, &path);
+
+	DBG("telephony-maemo6: found battery device at %s", path);
+
+	add_watch(NULL, path, "org.freedesktop.Hal.Device",
+							"PropertyModified");
+
+	hal_get_integer(path, "battery.charge_level.last_full", &battchg_last);
+	hal_get_integer(path, "battery.charge_level.current", &battchg_cur);
+	hal_get_integer(path, "battery.charge_level.design", &battchg_design);
+
+done:
+	dbus_message_unref(reply);
+	remove_pending(call);
 }
 
 int telephony_init(void)
@@ -1931,31 +1959,19 @@
 				AG_FEATURE_ENHANCED_CALL_CONTROL |
 				AG_FEATURE_EXTENDED_ERROR_RESULT_CODES |
 				AG_FEATURE_THREE_WAY_CALLING;
+	int i;
+
+	DBG("");
 
 	connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
 
-	if (!dbus_connection_add_filter(connection, signal_filter,
-						NULL, NULL))
-		error("Can't add signal filter");
-
-	dbus_bus_add_match(connection,
-			"type=signal,interface=" CSD_CALL_INTERFACE, NULL);
-	dbus_bus_add_match(connection,
-			"type=signal,interface=" CSD_CALL_INSTANCE, NULL);
-	dbus_bus_add_match(connection,
-			"type=signal,interface=" CSD_CALL_CONFERENCE, NULL);
-	dbus_bus_add_match(connection,
-			"type=signal,interface=" CSD_CSNET_REGISTRATION,
-			NULL);
-	dbus_bus_add_match(connection,
-			"type=signal,interface=" CSD_CSNET_OPERATOR,
-			NULL);
-	dbus_bus_add_match(connection,
-			"type=signal,interface=" CSD_CSNET_SIGNAL,
-			NULL);
-	dbus_bus_add_match(connection,
-				"type=signal,interface=" SSC_DBUS_IFACE
-				",member=modem_state_changed_ind", NULL);
+	add_watch(NULL, NULL, CSD_CALL_INTERFACE, NULL);
+	add_watch(NULL, NULL, CSD_CALL_INSTANCE, NULL);
+	add_watch(NULL, NULL, CSD_CALL_CONFERENCE, NULL);
+	add_watch(NULL, NULL, CSD_CSNET_REGISTRATION, "RegistrationChanged");
+	add_watch(NULL, NULL, CSD_CSNET_OPERATOR, "OperatorNameChanged");
+	add_watch(NULL, NULL, CSD_CSNET_SIGNAL, "SignalBarsChanged");
+	add_watch(NULL, NULL, SSC_DBUS_IFACE, "modem_state_changed_ind");
 
 	if (send_method_call(SSC_DBUS_NAME, SSC_DBUS_PATH, SSC_DBUS_IFACE,
 					"get_modem_state", modem_state_reply,
@@ -1975,7 +1991,15 @@
 	DBG("telephony-maemo6 registering %s interface on path %s",
 			TELEPHONY_MAEMO_INTERFACE, TELEPHONY_MAEMO_PATH);
 
-	telephony_ready_ind(features, maemo_indicators, response_and_hold,
+	/* Reset indicators */
+	for (i = 0; maemo_indicators[i].desc != NULL; i++) {
+		if (g_str_equal(maemo_indicators[i].desc, "battchg"))
+			maemo_indicators[i].val = 5;
+		else
+			maemo_indicators[i].val = 0;
+	}
+
+	telephony_ready_ind(features, maemo_indicators, BTRH_NOT_SUPPORTED,
 								chld_str);
 	if (send_method_call("org.freedesktop.Hal",
 				"/org/freedesktop/Hal/Manager",
@@ -1989,20 +2013,45 @@
 	return 0;
 }
 
+static void remove_watch(gpointer data)
+{
+	g_dbus_remove_watch(connection, GPOINTER_TO_UINT(data));
+}
+
 void telephony_exit(void)
 {
+	DBG("");
+
 	g_free(net.operator_name);
 	net.operator_name = NULL;
 
+	net.status = NETWORK_REG_STATUS_UNKOWN;
+	net.signal_bars = 0;
+
 	g_free(last_dialed_number);
 	last_dialed_number = NULL;
 
+	g_slist_free(active_calls);
+	active_calls = NULL;
+
 	g_slist_foreach(calls, (GFunc) csd_call_free, NULL);
 	g_slist_free(calls);
 	calls = NULL;
 
-	dbus_connection_remove_filter(connection, signal_filter, NULL);
+	g_slist_foreach(pending, (GFunc) dbus_pending_call_cancel, NULL);
+	g_slist_foreach(pending, (GFunc) dbus_pending_call_unref, NULL);
+	g_slist_free(pending);
+	pending = NULL;
+
+	g_slist_foreach(watches, (GFunc) remove_watch, NULL);
+	g_slist_free(watches);
+	watches = NULL;
+
+	g_dbus_unregister_interface(connection, TELEPHONY_MAEMO_PATH,
+						TELEPHONY_MAEMO_INTERFACE);
 
 	dbus_connection_unref(connection);
 	connection = NULL;
+
+	telephony_deinit();
 }
diff --git a/audio/telephony-ofono.c b/audio/telephony-ofono.c
index 9c20112..0a7f0bd 100644
--- a/audio/telephony-ofono.c
+++ b/audio/telephony-ofono.c
@@ -48,6 +48,7 @@
 	char *obj_path;
 	int status;
 	gboolean originating;
+	gboolean conference;
 	char *number;
 	guint watch;
 };
@@ -56,18 +57,17 @@
 static char *modem_obj_path = NULL;
 static char *last_dialed_number = NULL;
 static GSList *calls = NULL;
+static GSList *watches = NULL;
+static GSList *pending = NULL;
 
 #define OFONO_BUS_NAME "org.ofono"
 #define OFONO_PATH "/"
+#define OFONO_MODEM_INTERFACE "org.ofono.Modem"
 #define OFONO_MANAGER_INTERFACE "org.ofono.Manager"
 #define OFONO_NETWORKREG_INTERFACE "org.ofono.NetworkRegistration"
 #define OFONO_VCMANAGER_INTERFACE "org.ofono.VoiceCallManager"
 #define OFONO_VC_INTERFACE "org.ofono.VoiceCall"
 
-static guint registration_watch = 0;
-static guint voice_watch = 0;
-static guint device_watch = 0;
-
 /* HAL battery namespace key values */
 static int battchg_cur = -1;    /* "battery.charge_level.current" */
 static int battchg_last = -1;   /* "battery.charge_level.last_full" */
@@ -88,14 +88,6 @@
 
 static gboolean events_enabled = FALSE;
 
-/* Response and hold state
- * -1 = none
- *  0 = incoming call is put on hold in the AG
- *  1 = held incoming call is accepted in the AG
- *  2 = held incoming call is rejected in the AG
- */
-static int response_and_hold = -1;
-
 static struct indicator ofono_indicators[] =
 {
 	{ "battchg",	"0-5",	5,	TRUE },
@@ -136,9 +128,46 @@
 	return NULL;
 }
 
+static struct voice_call *find_vc_without_status(int status)
+{
+	GSList *l;
+
+	for (l = calls; l != NULL; l = l->next) {
+		struct voice_call *call = l->data;
+
+		if (call->status != status)
+			return call;
+	}
+
+	return NULL;
+}
+
+static int number_type(const char *number)
+{
+	if (number == NULL)
+		return NUMBER_TYPE_TELEPHONY;
+
+	if (number[0] == '+' || strncmp(number, "00", 2) == 0)
+		return NUMBER_TYPE_INTERNATIONAL;
+
+	return NUMBER_TYPE_TELEPHONY;
+}
+
 void telephony_device_connected(void *telephony_device)
 {
+	struct voice_call *coming;
+
 	DBG("telephony-ofono: device %p connected", telephony_device);
+
+	coming = find_vc_with_status(CALL_STATUS_ALERTING);
+	if (coming) {
+		if (find_vc_with_status(CALL_STATUS_ACTIVE))
+			telephony_call_waiting_ind(coming->number,
+						number_type(coming->number));
+		else
+			telephony_incoming_call_ind(coming->number,
+						number_type(coming->number));
+	}
 }
 
 void telephony_device_disconnected(void *telephony_device)
@@ -156,11 +185,8 @@
 
 void telephony_response_and_hold_req(void *telephony_device, int rh)
 {
-	response_and_hold = rh;
-
-	telephony_response_and_hold_ind(response_and_hold);
-
-	telephony_response_and_hold_rsp(telephony_device, CME_ERROR_NONE);
+	telephony_response_and_hold_rsp(telephony_device,
+						CME_ERROR_NOT_SUPPORTED);
 }
 
 void telephony_last_dialed_number_req(void *telephony_device)
@@ -211,60 +237,137 @@
 	}
 
 	dbus_pending_call_set_notify(call, cb, user_data, NULL);
-	dbus_pending_call_unref(call);
+	pending = g_slist_prepend(pending, call);
 	dbus_message_unref(msg);
 
 	return 0;
 }
 
+static int answer_call(struct voice_call *vc)
+{
+	DBG("%s", vc->number);
+	return send_method_call(OFONO_BUS_NAME, vc->obj_path,
+						OFONO_VC_INTERFACE, "Answer",
+						NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int release_call(struct voice_call *vc)
+{
+	DBG("%s", vc->number);
+	return send_method_call(OFONO_BUS_NAME, vc->obj_path,
+						OFONO_VC_INTERFACE, "Hangup",
+						NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int release_answer_calls()
+{
+	DBG("");
+	return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+						OFONO_VCMANAGER_INTERFACE,
+						"ReleaseAndAnswer",
+						NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int split_call(struct voice_call *call)
+{
+	DBG("%s", call->number);
+	return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+						OFONO_VCMANAGER_INTERFACE,
+						"PrivateChat",
+						NULL, NULL,
+						DBUS_TYPE_OBJECT_PATH,
+						call->obj_path,
+						DBUS_TYPE_INVALID);
+	return -1;
+}
+
+static int swap_calls(void)
+{
+	DBG("");
+	return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+						OFONO_VCMANAGER_INTERFACE,
+						"SwapCalls",
+						NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int create_conference(void)
+{
+	DBG("");
+	return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+						OFONO_VCMANAGER_INTERFACE,
+						"CreateMultiparty",
+						NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int release_conference(void)
+{
+	DBG("");
+	return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+						OFONO_VCMANAGER_INTERFACE,
+						"HangupMultiparty",
+						NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int call_transfer(void)
+{
+	DBG("");
+	return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+						OFONO_VCMANAGER_INTERFACE,
+						"Transfer",
+						NULL, NULL, DBUS_TYPE_INVALID);
+}
+
 void telephony_terminate_call_req(void *telephony_device)
 {
-	struct voice_call *vc;
-	int ret;
+	struct voice_call *call;
+	struct voice_call *alerting;
+	int err;
 
-	if ((vc = find_vc_with_status(CALL_STATUS_ACTIVE))) {
-	} else if ((vc = find_vc_with_status(CALL_STATUS_DIALING))) {
-	} else if ((vc = find_vc_with_status(CALL_STATUS_ALERTING))) {
-	} else if ((vc = find_vc_with_status(CALL_STATUS_INCOMING))) {
-	}
+	call = find_vc_with_status(CALL_STATUS_ACTIVE);
+	if (!call)
+		call = calls->data;
 
-	if (!vc) {
-		error("in telephony_terminate_call_req, no active call");
+	if (!call) {
+		error("No active call");
 		telephony_terminate_call_rsp(telephony_device,
-					CME_ERROR_NOT_ALLOWED);
+						CME_ERROR_NOT_ALLOWED);
 		return;
 	}
 
-	ret = send_method_call(OFONO_BUS_NAME, vc->obj_path,
-					OFONO_VC_INTERFACE,
-					"Hangup", NULL,
-					NULL, DBUS_TYPE_INVALID);
+	alerting = find_vc_with_status(CALL_STATUS_ALERTING);
+	if (call->status == CALL_STATUS_HELD && alerting)
+		err = release_call(alerting);
+	else if (call->conference)
+		err = release_conference();
+	else
+		err = release_call(call);
 
-	if (ret < 0) {
-		telephony_answer_call_rsp(telephony_device,
-					CME_ERROR_AG_FAILURE);
-		return;
-	}
-
-	telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE);
+	if (err < 0)
+		telephony_terminate_call_rsp(telephony_device,
+						CME_ERROR_AG_FAILURE);
+	else
+		telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE);
 }
 
 void telephony_answer_call_req(void *telephony_device)
 {
-	struct voice_call *vc = find_vc_with_status(CALL_STATUS_INCOMING);
+	struct voice_call *vc;
 	int ret;
 
+	vc = find_vc_with_status(CALL_STATUS_INCOMING);
+	if (!vc)
+		vc = find_vc_with_status(CALL_STATUS_ALERTING);
+
+	if (!vc)
+		vc = find_vc_with_status(CALL_STATUS_WAITING);
+
 	if (!vc) {
 		telephony_answer_call_rsp(telephony_device,
 					CME_ERROR_NOT_ALLOWED);
 		return;
 	}
 
-	ret = send_method_call(OFONO_BUS_NAME, vc->obj_path,
-			OFONO_VC_INTERFACE,
-			"Answer", NULL,
-			NULL, DBUS_TYPE_INVALID);
-
+	ret = answer_call(vc);
 	if (ret < 0) {
 		telephony_answer_call_rsp(telephony_device,
 					CME_ERROR_AG_FAILURE);
@@ -358,15 +461,22 @@
 
 	for (l = calls, i = 1; l != NULL; l = l->next, i++) {
 		struct voice_call *vc = l->data;
-		int direction;
+		int direction, multiparty;
 
 		direction = vc->originating ?
 				CALL_DIR_OUTGOING : CALL_DIR_INCOMING;
 
+		multiparty = vc->conference ?
+				CALL_MULTIPARTY_YES : CALL_MULTIPARTY_NO;
+
+		DBG("call %s direction %d multiparty %d", vc->number,
+							direction, multiparty);
+
 		telephony_list_current_call_ind(i, direction, vc->status,
-					CALL_MODE_VOICE, CALL_MULTIPARTY_NO,
-					vc->number, NUMBER_TYPE_TELEPHONY);
+					CALL_MODE_VOICE, multiparty,
+					vc->number, number_type(vc->number));
 	}
+
 	telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE);
 }
 
@@ -379,10 +489,84 @@
 	telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE);
 }
 
+static void foreach_vc_with_status(int status,
+					int (*func)(struct voice_call *vc))
+{
+	GSList *l;
+
+	for (l = calls; l != NULL; l = l->next) {
+		struct voice_call *call = l->data;
+
+		if (call->status == status)
+			func(call);
+	}
+}
+
 void telephony_call_hold_req(void *telephony_device, const char *cmd)
 {
+	const char *idx;
+	struct voice_call *call;
+	int err = 0;
+
 	DBG("telephony-ofono: got call hold request %s", cmd);
-	telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE);
+
+	if (strlen(cmd) > 1)
+		idx = &cmd[1];
+	else
+		idx = NULL;
+
+	if (idx)
+		call = g_slist_nth_data(calls, strtol(idx, NULL, 0) - 1);
+	else
+		call = NULL;
+
+	switch (cmd[0]) {
+	case '0':
+		if (find_vc_with_status(CALL_STATUS_WAITING))
+			foreach_vc_with_status(CALL_STATUS_WAITING,
+								release_call);
+		else
+			foreach_vc_with_status(CALL_STATUS_HELD, release_call);
+		break;
+	case '1':
+		if (idx) {
+			if (call)
+				err = release_call(call);
+			break;
+		}
+		err = release_answer_calls();
+		break;
+	case '2':
+		if (idx) {
+			if (call)
+				err = split_call(call);
+		} else {
+			call = find_vc_with_status(CALL_STATUS_WAITING);
+
+			if (call)
+				err = answer_call(call);
+			else
+				err = swap_calls();
+		}
+		break;
+	case '3':
+		if (find_vc_with_status(CALL_STATUS_HELD) ||
+				find_vc_with_status(CALL_STATUS_WAITING))
+			err = create_conference();
+		break;
+	case '4':
+		err = call_transfer();
+		break;
+	default:
+		DBG("Unknown call hold request");
+		break;
+	}
+
+	if (err)
+		telephony_call_hold_rsp(telephony_device,
+					CME_ERROR_AG_FAILURE);
+	else
+		telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE);
 }
 
 void telephony_nr_and_ec_req(void *telephony_device, gboolean enable)
@@ -395,8 +579,29 @@
 
 void telephony_key_press_req(void *telephony_device, const char *keys)
 {
+	struct voice_call *active, *waiting;
+	int err;
+
 	DBG("telephony-ofono: got key press request for %s", keys);
-	telephony_key_press_rsp(telephony_device, CME_ERROR_NONE);
+
+	waiting = find_vc_with_status(CALL_STATUS_INCOMING);
+	if (!waiting)
+		waiting = find_vc_with_status(CALL_STATUS_DIALING);
+
+	active = find_vc_with_status(CALL_STATUS_ACTIVE);
+
+	if (waiting)
+		err = answer_call(waiting);
+	else if (active)
+		err = release_call(active);
+	else
+		err = 0;
+
+	if (err < 0)
+		telephony_key_press_rsp(telephony_device,
+							CME_ERROR_AG_FAILURE);
+	else
+		telephony_key_press_rsp(telephony_device, CME_ERROR_NONE);
 }
 
 void telephony_voice_dial_req(void *telephony_device, gboolean enable)
@@ -435,13 +640,258 @@
 	return type == DBUS_TYPE_INVALID ? TRUE : FALSE;
 }
 
-static void handle_registration_property(const char *property, DBusMessageIter sub)
+static void call_free(struct voice_call *vc)
+{
+	DBG("%s", vc->obj_path);
+
+	if (vc->status == CALL_STATUS_ACTIVE)
+		telephony_update_indicator(ofono_indicators, "call",
+							EV_CALL_INACTIVE);
+	else
+		telephony_update_indicator(ofono_indicators, "callsetup",
+							EV_CALLSETUP_INACTIVE);
+
+	if (vc->status == CALL_STATUS_INCOMING)
+		telephony_calling_stopped_ind();
+
+	g_dbus_remove_watch(connection, vc->watch);
+	g_free(vc->obj_path);
+	g_free(vc->number);
+	g_free(vc);
+}
+
+static gboolean handle_vc_property_changed(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct voice_call *vc = data;
+	const char *obj_path = dbus_message_get_path(msg);
+	DBusMessageIter iter, sub;
+	const char *property, *state;
+
+	DBG("path %s", obj_path);
+
+	dbus_message_iter_init(msg, &iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+		error("Unexpected signature in vc PropertyChanged signal");
+		return TRUE;
+	}
+
+	dbus_message_iter_get_basic(&iter, &property);
+	DBG("property %s", property);
+
+	dbus_message_iter_next(&iter);
+	dbus_message_iter_recurse(&iter, &sub);
+	if (g_str_equal(property, "State")) {
+		dbus_message_iter_get_basic(&sub, &state);
+		DBG("State %s", state);
+		if (g_str_equal(state, "disconnected")) {
+			calls = g_slist_remove(calls, vc);
+			call_free(vc);
+		} else if (g_str_equal(state, "active")) {
+			telephony_update_indicator(ofono_indicators,
+							"call", EV_CALL_ACTIVE);
+			telephony_update_indicator(ofono_indicators,
+							"callsetup",
+							EV_CALLSETUP_INACTIVE);
+			if (vc->status == CALL_STATUS_INCOMING)
+				telephony_calling_stopped_ind();
+			vc->status = CALL_STATUS_ACTIVE;
+		} else if (g_str_equal(state, "alerting")) {
+			telephony_update_indicator(ofono_indicators,
+					"callsetup", EV_CALLSETUP_ALERTING);
+			vc->status = CALL_STATUS_ALERTING;
+			vc->originating = TRUE;
+		} else if (g_str_equal(state, "incoming")) {
+			/* state change from waiting to incoming */
+			telephony_update_indicator(ofono_indicators,
+					"callsetup", EV_CALLSETUP_INCOMING);
+			telephony_incoming_call_ind(vc->number,
+						NUMBER_TYPE_TELEPHONY);
+			vc->status = CALL_STATUS_INCOMING;
+			vc->originating = FALSE;
+		} else if (g_str_equal(state, "held")) {
+			vc->status = CALL_STATUS_HELD;
+			if (find_vc_without_status(CALL_STATUS_HELD))
+				telephony_update_indicator(ofono_indicators,
+							"callheld",
+							EV_CALLHELD_MULTIPLE);
+			else
+				telephony_update_indicator(ofono_indicators,
+							"callheld",
+							EV_CALLHELD_ON_HOLD);
+		}
+	} else if (g_str_equal(property, "Multiparty")) {
+		dbus_bool_t multiparty;
+
+		dbus_message_iter_get_basic(&sub, &multiparty);
+		DBG("Multiparty %s", multiparty ? "True" : "False");
+		vc->conference = multiparty;
+	}
+
+	return TRUE;
+}
+
+static struct voice_call *call_new(const char *path, DBusMessageIter *properties)
+{
+	struct voice_call *vc;
+
+	DBG("%s", path);
+
+	vc = g_new0(struct voice_call, 1);
+	vc->obj_path = g_strdup(path);
+	vc->watch = g_dbus_add_signal_watch(connection, NULL, path,
+					OFONO_VC_INTERFACE, "PropertyChanged",
+					handle_vc_property_changed, vc, NULL);
+
+	while (dbus_message_iter_get_arg_type(properties)
+						== DBUS_TYPE_DICT_ENTRY) {
+		DBusMessageIter entry, value;
+		const char *property, *cli, *state;
+		dbus_bool_t multiparty;
+
+		dbus_message_iter_recurse(properties, &entry);
+		dbus_message_iter_get_basic(&entry, &property);
+
+		dbus_message_iter_next(&entry);
+		dbus_message_iter_recurse(&entry, &value);
+
+		if (g_str_equal(property, "LineIdentification")) {
+			dbus_message_iter_get_basic(&value, &cli);
+			DBG("cli %s", cli);
+			vc->number = g_strdup(cli);
+		} else if (g_str_equal(property, "State")) {
+			dbus_message_iter_get_basic(&value, &state);
+			DBG("state %s", state);
+			if (g_str_equal(state, "incoming"))
+				vc->status = CALL_STATUS_INCOMING;
+			else if (g_str_equal(state, "dialing"))
+				vc->status = CALL_STATUS_DIALING;
+			else if (g_str_equal(state, "alerting"))
+				vc->status = CALL_STATUS_ALERTING;
+			else if (g_str_equal(state, "waiting"))
+				vc->status = CALL_STATUS_WAITING;
+			else if (g_str_equal(state, "held"))
+				vc->status = CALL_STATUS_HELD;
+		} else if (g_str_equal(property, "Multiparty")) {
+			dbus_message_iter_get_basic(&value, &multiparty);
+			DBG("Multipary %s", multiparty ? "True" : "False");
+			vc->conference = multiparty;
+		}
+
+		dbus_message_iter_next(properties);
+	}
+
+	switch (vc->status) {
+	case CALL_STATUS_INCOMING:
+		DBG("CALL_STATUS_INCOMING");
+		vc->originating = FALSE;
+		telephony_update_indicator(ofono_indicators, "callsetup",
+					EV_CALLSETUP_INCOMING);
+		telephony_incoming_call_ind(vc->number, NUMBER_TYPE_TELEPHONY);
+		break;
+	case CALL_STATUS_DIALING:
+		DBG("CALL_STATUS_DIALING");
+		vc->originating = TRUE;
+		g_free(last_dialed_number);
+		last_dialed_number = g_strdup(vc->number);
+		telephony_update_indicator(ofono_indicators, "callsetup",
+					EV_CALLSETUP_OUTGOING);
+		break;
+	case CALL_STATUS_ALERTING:
+		DBG("CALL_STATUS_ALERTING");
+		vc->originating = TRUE;
+		g_free(last_dialed_number);
+		last_dialed_number = g_strdup(vc->number);
+		telephony_update_indicator(ofono_indicators, "callsetup",
+					EV_CALLSETUP_ALERTING);
+		break;
+	case CALL_STATUS_WAITING:
+		DBG("CALL_STATUS_WAITING");
+		vc->originating = FALSE;
+		telephony_update_indicator(ofono_indicators, "callsetup",
+					EV_CALLSETUP_INCOMING);
+		telephony_call_waiting_ind(vc->number, NUMBER_TYPE_TELEPHONY);
+		break;
+	}
+
+	return vc;
+}
+
+static void remove_pending(DBusPendingCall *call)
+{
+	pending = g_slist_remove(pending, call);
+	dbus_pending_call_unref(call);
+}
+
+static void call_added(const char *path, DBusMessageIter *properties)
+{
+	struct voice_call *vc;
+
+	DBG("%s", path);
+
+	vc = find_vc(path);
+	if (vc)
+		return;
+
+	vc = call_new(path, properties);
+	calls = g_slist_prepend(calls, vc);
+}
+
+static void get_calls_reply(DBusPendingCall *call, void *user_data)
+{
+	DBusError err;
+	DBusMessage *reply;
+	DBusMessageIter iter, entry;
+
+	DBG("");
+	reply = dbus_pending_call_steal_reply(call);
+
+	dbus_error_init(&err);
+	if (dbus_set_error_from_message(&err, reply)) {
+		error("ofono replied with an error: %s, %s",
+				err.name, err.message);
+		dbus_error_free(&err);
+		goto done;
+	}
+
+	dbus_message_iter_init(reply, &iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+		error("Unexpected signature");
+		goto done;
+	}
+
+	dbus_message_iter_recurse(&iter, &entry);
+
+	while (dbus_message_iter_get_arg_type(&entry)
+						== DBUS_TYPE_STRUCT) {
+		const char *path;
+		DBusMessageIter value, properties;
+
+		dbus_message_iter_recurse(&entry, &value);
+		dbus_message_iter_get_basic(&value, &path);
+
+		dbus_message_iter_next(&value);
+		dbus_message_iter_recurse(&value, &properties);
+
+		call_added(path, &properties);
+
+		dbus_message_iter_next(&entry);
+	}
+
+done:
+	dbus_message_unref(reply);
+	remove_pending(call);
+}
+
+static void handle_network_property(const char *property, DBusMessageIter *variant)
 {
 	const char *status, *operator;
 	unsigned int signals_bar;
 
 	if (g_str_equal(property, "Status")) {
-		dbus_message_iter_get_basic(&sub, &status);
+		dbus_message_iter_get_basic(variant, &status);
 		DBG("Status is %s", status);
 		if (g_str_equal(status, "registered")) {
 			net.status = NETWORK_REG_STATUS_HOME;
@@ -462,13 +912,13 @@
 			telephony_update_indicator(ofono_indicators,
 						"service", EV_SERVICE_NONE);
 		}
-	} else if (g_str_equal(property, "Operator")) {
-		dbus_message_iter_get_basic(&sub, &operator);
+	} else if (g_str_equal(property, "Name")) {
+		dbus_message_iter_get_basic(variant, &operator);
 		DBG("Operator is %s", operator);
 		g_free(net.operator_name);
 		net.operator_name = g_strdup(operator);
 	} else if (g_str_equal(property, "SignalStrength")) {
-		dbus_message_iter_get_basic(&sub, &signals_bar);
+		dbus_message_iter_get_basic(variant, &signals_bar);
 		DBG("SignalStrength is %d", signals_bar);
 		net.signals_bar = signals_bar;
 		telephony_update_indicator(ofono_indicators, "signal",
@@ -476,16 +926,55 @@
 	}
 }
 
-static void get_registration_reply(DBusPendingCall *call, void *user_data)
+static int parse_network_properties(DBusMessageIter *properties)
+{
+	uint32_t features = AG_FEATURE_EC_ANDOR_NR |
+				AG_FEATURE_INBAND_RINGTONE |
+				AG_FEATURE_REJECT_A_CALL |
+				AG_FEATURE_ENHANCED_CALL_STATUS |
+				AG_FEATURE_ENHANCED_CALL_CONTROL |
+				AG_FEATURE_EXTENDED_ERROR_RESULT_CODES |
+				AG_FEATURE_THREE_WAY_CALLING;
+	int i;
+
+	/* Reset indicators */
+	for (i = 0; ofono_indicators[i].desc != NULL; i++) {
+		if (g_str_equal(ofono_indicators[i].desc, "battchg"))
+			ofono_indicators[i].val = 5;
+		else
+			ofono_indicators[i].val = 0;
+	}
+
+	while (dbus_message_iter_get_arg_type(properties)
+						== DBUS_TYPE_DICT_ENTRY) {
+		const char *key;
+		DBusMessageIter value, entry;
+
+		dbus_message_iter_recurse(properties, &entry);
+		dbus_message_iter_get_basic(&entry, &key);
+
+		dbus_message_iter_next(&entry);
+		dbus_message_iter_recurse(&entry, &value);
+
+		handle_network_property(key, &value);
+
+		dbus_message_iter_next(properties);
+	}
+
+	telephony_ready_ind(features, ofono_indicators, BTRH_NOT_SUPPORTED,
+								chld_str);
+
+	return 0;
+}
+
+static void get_properties_reply(DBusPendingCall *call, void *user_data)
 {
 	DBusError err;
 	DBusMessage *reply;
-	DBusMessageIter iter, iter_entry;
-	uint32_t features = AG_FEATURE_EC_ANDOR_NR |
-				AG_FEATURE_REJECT_A_CALL |
-				AG_FEATURE_ENHANCED_CALL_STATUS |
-				AG_FEATURE_EXTENDED_ERROR_RESULT_CODES;
+	DBusMessageIter iter, properties;
+	int ret = 0;
 
+	DBG("");
 	reply = dbus_pending_call_steal_reply(call);
 
 	dbus_error_init(&err);
@@ -498,67 +987,134 @@
 
 	dbus_message_iter_init(reply, &iter);
 
-	/* ARRAY -> ENTRY -> VARIANT */
 	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
-		error("Unexpected signature in GetProperties return");
+		error("Unexpected signature");
 		goto done;
 	}
 
-	dbus_message_iter_recurse(&iter, &iter_entry);
+	dbus_message_iter_recurse(&iter, &properties);
 
-	if (dbus_message_iter_get_arg_type(&iter_entry)
-					!= DBUS_TYPE_DICT_ENTRY) {
-		error("Unexpected signature in GetProperties return");
+	ret = parse_network_properties(&properties);
+	if (ret < 0) {
+		error("Unable to parse %s.GetProperty reply",
+						OFONO_NETWORKREG_INTERFACE);
 		goto done;
 	}
 
-	while (dbus_message_iter_get_arg_type(&iter_entry)
-					!= DBUS_TYPE_INVALID) {
-		DBusMessageIter iter_property, sub;
-		char *property;
-
-		dbus_message_iter_recurse(&iter_entry, &iter_property);
-		if (dbus_message_iter_get_arg_type(&iter_property)
-					!= DBUS_TYPE_STRING) {
-			error("Unexpected signature in GetProperties return");
-			goto done;
-		}
-
-		dbus_