Merge BlueZ 5.43 to master

Release of BlueZ 5.43
29th October 2016, 09:44 am by Johan Hedberg

This is almost purely a bug-fix release with fixes to HoG, ATT and PAN.
There’s also a fix for a regression in 5.42 that caused connection failures
for external profiles like OBEX. Feature-wise there’s one notable addition:
LE privacy. By enabling this in main.conf it’s now possible to make BlueZ
generate a local Identity Resolving Key (IRK) and use Resolvable Private
Addresses (RPAs) for itself.

Release of BlueZ 5.42
26th September 2016, 06:33 pm by Johan Hedberg

The major API change with this release is that the GATT D-Bus API is no longer
marked as experimental. This should hopefully encourage the creation of more
applications using it. Feature-wise, btmon received support for decoding full
mgmt message traces – a feature that is available in the bluetooth-next kernel
tree, i.e. on its way to the 4.9 kernel release. In addition to these changes,
this release contains also fixes in areas such as PBAP, transport selection
(BR/EDR vs LE), ATT and A2DP.

NOTE:
Adjustments were necessary to audioOverBle, battery, and oad profiles due to:

commit 82666b345e14b0a723e3d7cf8e56003edef22fed
Author: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Date:   Mon Sep 26 16:46:09 2016 +0300
core: Remove attio callbacks

Change-Id: I8d25c7c866f76c23fcca284e3b20b237b4b1b7eb
diff --git a/Makefile.plugins b/Makefile.plugins
index ecf6f08..192a2cc 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -5,6 +5,9 @@
 builtin_modules += wiimote
 builtin_sources += plugins/wiimote.c
 
+builtin_modules += gfrm
+builtin_sources += plugins/gfrm.c
+
 builtin_modules += autopair
 builtin_sources += plugins/autopair.c
 
@@ -68,6 +71,12 @@
 
 EXTRA_DIST += profiles/input/suspend-dummy.c
 
+builtin_modules += oad
+builtin_sources += profiles/oad/oad.c
+
+builtin_modules += battery
+builtin_sources += profiles/battery/battery.c
+
 if EXPERIMENTAL
 builtin_modules += health
 builtin_sources += profiles/health/mcap.h profiles/health/mcap.c \
@@ -94,3 +103,9 @@
 						-no-undefined @UDEV_LIBS@
 plugins_sixaxis_la_CFLAGS = $(AM_CFLAGS) -fvisibility=hidden @UDEV_CFLAGS@
 endif
+
+builtin_modules += audioOverBle
+builtin_sources += profiles/audioOverBle/main.c profiles/audioOverBle/manager.h \
+			profiles/audioOverBle/manager.c \
+			profiles/audioOverBle/btvoice.h \
+			profiles/audioOverBle/btvoice.c
diff --git a/Makefile.tools b/Makefile.tools
index 7706dc7..7ed442c 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -343,8 +343,8 @@
 endif
 
 if READLINE
-noinst_PROGRAMS += attrib/gatttool tools/btmgmt \
-			tools/obex-client-tool tools/obex-server-tool \
+bin_PROGRAMS += attrib/gatttool tools/btmgmt
+noinst_PROGRAMS += tools/obex-client-tool tools/obex-server-tool \
 			tools/bluetooth-player tools/obexctl
 
 attrib_gatttool_SOURCES = attrib/gatttool.c attrib/att.c attrib/gatt.c \
@@ -422,3 +422,5 @@
 		test/pbap-client test/map-client test/example-advertisement \
 		test/example-gatt-server test/example-gatt-client \
 		test/test-gatt-profile
+
+test_scripts += test/gfiber-agent test/unplug-GFRM100 test/gfrm-inventory
diff --git a/attrib/gattrib.c b/attrib/gattrib.c
index 2e1e39a..6bb6e6d 100644
--- a/attrib/gattrib.c
+++ b/attrib/gattrib.c
@@ -151,7 +151,9 @@
 
 	__sync_fetch_and_add(&attrib->ref_count, 1);
 
-	DBG("%p: g_attrib_ref=%d ", attrib, attrib->ref_count);
+	// suppress very common debug message
+	if (attrib->ref_count == 1 || attrib->ref_count > 20)
+		DBG("%p: g_attrib_ref=%d ", attrib, attrib->ref_count);
 
 	return attrib;
 }
@@ -184,7 +186,9 @@
 	if (!attrib)
 		return;
 
-	DBG("%p: g_attrib_unref=%d ", attrib, attrib->ref_count - 1);
+	// suppress very common debug message
+	if (attrib->ref_count - 1 == 0 || attrib->ref_count - 1 > 20)
+		DBG("%p: g_attrib_unref=%d ", attrib, attrib->ref_count - 1);
 
 	if (__sync_sub_and_fetch(&attrib->ref_count, 1))
 		return;
@@ -451,6 +455,14 @@
 	return attrib->buf;
 }
 
+int g_attrib_get_mtu(GAttrib *attrib)
+{
+	if (!attrib)
+		return -1;
+
+	return bt_att_get_mtu(attrib->att);
+}
+
 gboolean g_attrib_set_mtu(GAttrib *attrib, int mtu)
 {
 	if (!attrib)
diff --git a/attrib/gattrib.h b/attrib/gattrib.h
index 611f952..6cd3bf9 100644
--- a/attrib/gattrib.h
+++ b/attrib/gattrib.h
@@ -65,6 +65,7 @@
 				GDestroyNotify notify);
 
 uint8_t *g_attrib_get_buffer(GAttrib *attrib, size_t *len);
+int g_attrib_get_mtu(GAttrib *attrib);
 gboolean g_attrib_set_mtu(GAttrib *attrib, int mtu);
 
 gboolean g_attrib_unregister(GAttrib *attrib, guint id);
diff --git a/lib/hci.c b/lib/hci.c
index a3f5d96..5ce39bd 100644
--- a/lib/hci.c
+++ b/lib/hci.c
@@ -2345,6 +2345,57 @@
 	return 0;
 }
 
+int hci_read_page_scan_type(int dd, uint8_t *type, int to)
+{
+	read_page_scan_type_rp rp;
+	struct hci_request rq;
+
+	memset(&rq, 0, sizeof(rq));
+	rq.ogf    = OGF_HOST_CTL;
+	rq.ocf    = OCF_READ_PAGE_SCAN_TYPE;
+	rq.rparam = &rp;
+	rq.rlen   = READ_PAGE_SCAN_TYPE_RP_SIZE;
+
+	if (hci_send_req(dd, &rq, to) < 0)
+		return -1;
+
+	if (rp.status) {
+		errno = EIO;
+		return -1;
+	}
+
+	*type = rp.type;
+	return 0;
+}
+
+int hci_write_page_scan_type(int dd, uint8_t type, int to)
+{
+	write_page_scan_type_cp cp;
+	write_page_scan_type_rp rp;
+	struct hci_request rq;
+
+	memset(&cp, 0, sizeof(cp));
+	cp.type = type;
+
+	memset(&rq, 0, sizeof(rq));
+	rq.ogf    = OGF_HOST_CTL;
+	rq.ocf    = OCF_WRITE_PAGE_SCAN_TYPE;
+	rq.cparam = &cp;
+	rq.clen   = WRITE_PAGE_SCAN_TYPE_CP_SIZE;
+	rq.rparam = &rp;
+	rq.rlen   = WRITE_PAGE_SCAN_TYPE_RP_SIZE;
+
+	if (hci_send_req(dd, &rq, to) < 0)
+		return -1;
+
+	if (rp.status) {
+		errno = EIO;
+		return -1;
+	}
+
+	return 0;
+}
+
 int hci_read_inquiry_mode(int dd, uint8_t *mode, int to)
 {
 	read_inquiry_mode_rp rp;
diff --git a/lib/hci.h b/lib/hci.h
index 794333b..3ef4311 100644
--- a/lib/hci.h
+++ b/lib/hci.h
@@ -1074,10 +1074,23 @@
 #define WRITE_INQUIRY_MODE_RP_SIZE 1
 
 #define OCF_READ_PAGE_SCAN_TYPE		0x0046
+typedef struct {
+	uint8_t		status;
+	uint8_t		type;
+} __attribute__ ((packed)) read_page_scan_type_rp;
+#define READ_PAGE_SCAN_TYPE_RP_SIZE 2
 
 #define OCF_WRITE_PAGE_SCAN_TYPE	0x0047
-	#define PAGE_SCAN_TYPE_STANDARD		0x00
-	#define PAGE_SCAN_TYPE_INTERLACED	0x01
+typedef struct {
+	uint8_t		type;
+} __attribute__ ((packed)) write_page_scan_type_cp;
+#define WRITE_PAGE_SCAN_TYPE_CP_SIZE 1
+typedef struct {
+	uint8_t		status;
+} __attribute__ ((packed)) write_page_scan_type_rp;
+#define WRITE_PAGE_SCAN_TYPE_RP_SIZE 1
+#define PAGE_SCAN_TYPE_STANDARD		0x00
+#define PAGE_SCAN_TYPE_INTERLACED	0x01
 
 #define OCF_READ_AFH_MODE		0x0048
 typedef struct {
diff --git a/lib/hci_lib.h b/lib/hci_lib.h
index 55aeb17..602a4cb 100644
--- a/lib/hci_lib.h
+++ b/lib/hci_lib.h
@@ -92,6 +92,8 @@
 int hci_exit_park_mode(int dd, uint16_t handle, int to);
 int hci_read_inquiry_scan_type(int dd, uint8_t *type, int to);
 int hci_write_inquiry_scan_type(int dd, uint8_t type, int to);
+int hci_read_page_scan_type(int dd, uint8_t *type, int to);
+int hci_write_page_scan_type(int dd, uint8_t type, int to);
 int hci_read_inquiry_mode(int dd, uint8_t *mode, int to);
 int hci_write_inquiry_mode(int dd, uint8_t mode, int to);
 int hci_read_afh_mode(int dd, uint8_t *mode, int to);
diff --git a/monitor/l2cap.c b/monitor/l2cap.c
index f4b54af..ebc90e0 100644
--- a/monitor/l2cap.c
+++ b/monitor/l2cap.c
@@ -3105,6 +3105,8 @@
 			avdtp_packet(&frame);
 			break;
 		default:
+			/* limit amount of data to log */
+			if (size > 32) size = 32;
 			packet_hexdump(data, size);
 			break;
 		}
diff --git a/monitor/main.c b/monitor/main.c
index f9bca22..c04a94a 100644
--- a/monitor/main.c
+++ b/monitor/main.c
@@ -70,6 +70,7 @@
 		"\t-T, --date             Show time and date information\n"
 		"\t-S, --sco              Dump SCO traffic\n"
 		"\t-E, --ellisys [ip]     Send Ellisys HCI Injection\n"
+		"\t-A, --no_leadv         Suppress LE_ADV messages\n"
 		"\t-h, --help             Show help options\n");
 }
 
@@ -86,6 +87,7 @@
 	{ "date",    no_argument,       NULL, 'T' },
 	{ "sco",     no_argument,	NULL, 'S' },
 	{ "ellisys", required_argument, NULL, 'E' },
+	{ "no_leadv", no_argument,      NULL, 'A' },
 	{ "todo",    no_argument,       NULL, '#' },
 	{ "version", no_argument,       NULL, 'v' },
 	{ "help",    no_argument,       NULL, 'h' },
@@ -106,6 +108,7 @@
 	int exit_status;
 	sigset_t mask;
 
+	setvbuf(stdout, NULL, _IOLBF, 0);
 	mainloop_init();
 
 	filter_mask |= PACKET_FILTER_SHOW_TIME_OFFSET;
@@ -113,7 +116,7 @@
 	for (;;) {
 		int opt;
 
-		opt = getopt_long(argc, argv, "d:r:w:a:s:p:i:tTSE:vh",
+		opt = getopt_long(argc, argv, "d:r:w:a:s:p:i:tTSAE:vh",
 						main_options, NULL);
 		if (opt < 0)
 			break;
@@ -167,6 +170,9 @@
 		case 'S':
 			filter_mask |= PACKET_FILTER_SHOW_SCO_DATA;
 			break;
+		case 'A':
+			filter_mask |= PACKET_FILTER_SUPPRESS_LE_ADV;
+			break;
 		case 'E':
 			ellisys_server = optarg;
 			ellisys_port = 24352;
diff --git a/monitor/packet.c b/monitor/packet.c
index 6272562..45c9e8c 100644
--- a/monitor/packet.c
+++ b/monitor/packet.c
@@ -3262,12 +3262,12 @@
 
 			uuid = data;
 			print_field("  UUID: %8.8x-%4.4x-%4.4x-%4.4x-%8.8x%4.4x",
-				get_le32(&uuid[12]), get_le16(&uuid[10]),
-				get_le16(&uuid[8]), get_le16(&uuid[6]),
-				get_le32(&uuid[2]), get_le16(&uuid[0]));
+				get_be32(&uuid[0]), get_be16(&uuid[4]),
+				get_be16(&uuid[6]), get_be16(&uuid[8]),
+				get_be32(&uuid[10]), get_be16(&uuid[14]));
 
-			major = get_le16(data + 16);
-			minor = get_le16(data + 18);
+			major = get_be16(data + 16);
+			minor = get_be16(data + 18);
 			print_field("  Version: %u.%u", major, minor);
 
 			tx_power = *(int8_t *) (data + 20);
@@ -9038,6 +9038,14 @@
 		event_str = "Unknown";
 	}
 
+	if (filter_mask & PACKET_FILTER_SUPPRESS_LE_ADV && hdr->evt == 0x3e) {
+		uint8_t subevent = *((const uint8_t *) data);
+		if (subevent == 0x02) {
+			/* LE Advertising Report. */
+			return;
+		}
+	}
+
 	sprintf(extra_str, "(0x%2.2x) plen %d", hdr->evt, hdr->plen);
 
 	print_packet(tv, cred, '>', index, NULL, event_color, "HCI Event",
diff --git a/monitor/packet.h b/monitor/packet.h
index 354f4fe..a02ea50 100644
--- a/monitor/packet.h
+++ b/monitor/packet.h
@@ -33,6 +33,7 @@
 #define PACKET_FILTER_SHOW_TIME_OFFSET	(1 << 3)
 #define PACKET_FILTER_SHOW_ACL_DATA	(1 << 4)
 #define PACKET_FILTER_SHOW_SCO_DATA	(1 << 5)
+#define PACKET_FILTER_SUPPRESS_LE_ADV	(1 << 31)
 
 void packet_set_filter(unsigned long filter);
 void packet_add_filter(unsigned long filter);
diff --git a/plugins/gfrm.c b/plugins/gfrm.c
new file mode 100644
index 0000000..6643022
--- /dev/null
+++ b/plugins/gfrm.c
@@ -0,0 +1,318 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2014 Google 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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "lib/sdp.h"
+#include "lib/uuid.h"
+
+#include "src/adapter.h"
+#include "src/device.h"
+#include "src/log.h"
+#include "src/plugin.h"
+
+#define BD_ADDR_FIFO "/tmp/bd-addr-fifo"
+
+struct gfrm_device {
+	char *name;
+	uint8_t bdaddr_type;
+	uint16_t vid;
+	uint16_t pid;
+};
+
+static struct gfrm_device gfrm_devs[] = {
+	{ "GFRM100", BDADDR_BREDR, 0x58, 0x2000 },
+	{ "GFRM200", BDADDR_LE_PUBLIC, 0x471, 0x2210 },
+	{ "HID AdvRemote", BDADDR_LE_PUBLIC, 0xD, 0x0 },
+};
+
+static struct gfrm_device *gfrm_find_dev_by_bdaddr_type(uint8_t bdaddr_type)
+{
+	uint16_t i;
+
+	for (i = 0; i < G_N_ELEMENTS(gfrm_devs); ++i) {
+		if (bdaddr_type == gfrm_devs[i].bdaddr_type) {
+			return &gfrm_devs[i];
+		}
+	}
+
+	return NULL;
+}
+
+static struct gfrm_device *gfrm_find_dev_by_vid_pid(uint16_t vid, uint16_t pid)
+{
+	uint16_t i;
+
+	for (i = 0; i < G_N_ELEMENTS(gfrm_devs); ++i) {
+		if (vid == gfrm_devs[i].vid && pid == gfrm_devs[i].pid) {
+			return &gfrm_devs[i];
+		}
+	}
+
+	return NULL;
+}
+
+static ssize_t gfrm_pincb(struct btd_adapter *adapter, struct btd_device *device,
+			  char *pinbuf, bool *display, unsigned int attempt)
+{
+	uint16_t vid, pid;
+	char addr[18], name[25];
+
+	/*
+	 * Only try the pin code once per device.
+	 * If it's not correct then it's an unknown device.
+	 */
+	if (attempt > 1)
+		return 0;
+
+	ba2str(device_get_address(device), addr);
+
+	vid = btd_device_get_vendor(device);
+	pid = btd_device_get_product(device);
+	DBG("vendor 0x%x product 0x%x", vid, pid);
+
+	device_get_name(device, name, sizeof(name));
+	name[sizeof(name) - 1] = 0;
+
+	if (gfrm_find_dev_by_vid_pid(vid, pid)) {
+		DBG("Forcing PIN 0000 on %s at %s", name, addr);
+		memcpy(pinbuf, "0000", 4);
+		return 4;
+	}
+
+	return 0;
+}
+
+static void gfrm_disconnect_cb(struct btd_device *device, uint8_t reason)
+{
+	bdaddr_t bdaddr;
+	uint8_t bdaddr_type;
+	uint16_t vid, pid;
+	char addr[18], name[25];
+
+	memcpy(&bdaddr, device_get_address(device), sizeof(bdaddr));
+	ba2str(&bdaddr, addr);
+	bdaddr_type = btd_device_get_bdaddr_type(device);
+
+	vid = btd_device_get_vendor(device);
+	pid = btd_device_get_product(device);
+
+	device_get_name(device, name, sizeof(name));
+	name[sizeof(name) - 1] = 0;
+
+	if (gfrm_find_dev_by_vid_pid(vid, pid)) {
+		DBG("%s [%s] disconnected: reason %u", name, addr, reason);
+	}
+}
+
+static void gfrm_conn_fail_cb(struct btd_device *device, uint8_t reason)
+{
+	bdaddr_t bdaddr;
+	uint8_t bdaddr_type;
+	uint16_t vid, pid;
+	char addr[18], name[25];
+
+	memcpy(&bdaddr, device_get_address(device), sizeof(bdaddr));
+	ba2str(&bdaddr, addr);
+	bdaddr_type = btd_device_get_bdaddr_type(device);
+
+	vid = btd_device_get_vendor(device);
+	pid = btd_device_get_product(device);
+
+	device_get_name(device, name, sizeof(name));
+	name[sizeof(name) - 1] = 0;
+
+	if (gfrm_find_dev_by_vid_pid(vid, pid)) {
+		DBG("%s [%s] connection failed: reason %u", name, addr, reason);
+	}
+}
+
+static void gfrm_start_bd_addr_fifo_watch(
+		gboolean (*callback)(GIOChannel *, GIOCondition, gpointer))
+{
+	int fd;
+	GIOChannel *io;
+
+	if ((fd = open(BD_ADDR_FIFO, O_RDONLY | O_NONBLOCK)) < 0) {
+		DBG("open failed");
+		return;
+	}
+
+	if ((io = g_io_channel_unix_new(fd)) == NULL) {
+		DBG("g_io_channel_unix_new failed");
+		close(fd);
+		return;
+	}
+
+	if (!g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+			    callback, NULL)) {
+		DBG("g_io_add_watch failed");
+	}
+
+	g_io_channel_set_close_on_unref(io, TRUE);
+	g_io_channel_unref(io);
+}
+
+static gboolean gfrm_add_device(GIOChannel *io, GIOCondition condition,
+				gpointer data)
+{
+	int fd;
+	uint8_t bdaddr_and_type[7] = { 0 };
+	uint8_t bdaddr_type;
+	bdaddr_t bdaddr;
+	char bdaddr_str[18];
+	struct gfrm_device *gfrm_dev;
+	struct btd_adapter *adapter;
+	struct btd_device *device;
+
+	if (condition & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
+		DBG("io error: condition %u", condition);
+	}
+
+	if (!(condition & (G_IO_IN))) {
+		DBG("pipe has no data to read");
+		goto reopen;
+	}
+
+	/* Read BD address from FIFO */
+	fd = g_io_channel_unix_get_fd(io);
+
+	if (read(fd, &bdaddr_and_type, sizeof(bdaddr_and_type)) != 7) {
+		DBG("read failed");
+		goto reopen;
+	}
+
+	bdaddr_type = bdaddr_and_type[6];
+	baswap(&bdaddr, (bdaddr_t*) bdaddr_and_type);
+	ba2str(&bdaddr, bdaddr_str);
+
+	gfrm_dev = gfrm_find_dev_by_bdaddr_type(bdaddr_type);
+	if (!gfrm_dev)
+		goto reopen;
+
+	DBG("Discovered %s at %s", gfrm_dev->name, bdaddr_str);
+
+	/* Add device to BlueZ stack */
+	adapter = btd_adapter_get_default();
+	if (!adapter) {
+		DBG("btd_adapter_get_default failed");
+		goto reopen;
+	}
+
+	device = btd_adapter_find_device(adapter, &bdaddr, bdaddr_type);
+	if (device) {
+		DBG("device %p paired %d bonded %d bonding %d", device,
+		    device_is_paired(device, bdaddr_type),
+		    device_is_bonded(device, bdaddr_type),
+		    device_is_bonding(device, NULL));
+
+		if (device_is_bonding(device, NULL))
+			goto reopen;
+
+		DBG("Removing %s @ %s", gfrm_dev->name, bdaddr_str);
+		btd_device_set_temporary(device, TRUE);
+		btd_adapter_remove_device(adapter, device);
+	}
+
+	DBG("Adding %s @ %s", gfrm_dev->name, bdaddr_str);
+	device = btd_adapter_get_device(adapter, &bdaddr, bdaddr_type);
+	if (device) {
+		btd_device_device_set_name(device, gfrm_dev->name);
+		btd_device_set_pnpid(device, 0x1, gfrm_dev->vid,
+			gfrm_dev->pid, 0x0);
+		btd_device_set_temporary(device, FALSE);
+	}
+
+	/*
+	 * Pairing the remote is handled in Python script:
+	 * test/gfiber-agent
+	 */
+
+reopen:
+	/* Reopen the FIFO (new fd, io channel, and watch) */
+	gfrm_start_bd_addr_fifo_watch(gfrm_add_device);
+
+	/*
+	 * Return FALSE, so that the watch on the old io channel is removed.
+	 * That, in turn, triggers closing of io channel and file descriptor.
+	 */
+	return FALSE;
+}
+
+static int gfrm_probe(struct btd_adapter *adapter)
+{
+	btd_adapter_register_pin_cb(adapter, gfrm_pincb);
+}
+
+static void gfrm_remove(struct btd_adapter *adapter)
+{
+	btd_adapter_unregister_pin_cb(adapter, gfrm_pincb);
+}
+
+static struct btd_adapter_driver gfrm_driver = {
+	.name	= "gfrm",
+	.probe	= gfrm_probe,
+	.remove	= gfrm_remove,
+};
+
+static int gfrm_init(void)
+{
+	btd_add_disconnect_cb(gfrm_disconnect_cb);
+	btd_add_conn_fail_cb(gfrm_conn_fail_cb);
+	btd_register_adapter_driver(&gfrm_driver);
+
+	/* Create FIFO for IR-assisted pairing */
+	if (mkfifo(BD_ADDR_FIFO, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
+		   S_IROTH | S_IWOTH) < 0 && errno != EEXIST) {
+		DBG("mkfifo failed");
+		return 0;
+	}
+	/* Set file mode bits on the fifo since umask clears group/other */
+	chmod(BD_ADDR_FIFO, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
+				S_IROTH | S_IWOTH);
+
+	/* Start watching the FIFO */
+	gfrm_start_bd_addr_fifo_watch(gfrm_add_device);
+
+	return 0;
+}
+
+static void gfrm_exit(void)
+{
+	btd_unregister_adapter_driver(&gfrm_driver);
+	btd_remove_conn_fail_cb(gfrm_conn_fail_cb);
+	btd_remove_disconnect_cb(gfrm_disconnect_cb);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(gfrm, VERSION,
+			BLUETOOTH_PLUGIN_PRIORITY_HIGH, gfrm_init, gfrm_exit)
diff --git a/profiles/audioOverBle/audioOverBle.conf b/profiles/audioOverBle/audioOverBle.conf
new file mode 100644
index 0000000..c96b684
--- /dev/null
+++ b/profiles/audioOverBle/audioOverBle.conf
@@ -0,0 +1,12 @@
+# Configuration file for the audioOverBle service
+
+# This section contains options which are not specific to any
+# particular interface
+[General]
+#Mode can be PROP_MODE, L2CAP_MODE or ATT_MODE
+mode=ATT_MODE
+l2capchannel=0
+
+# Configuration to allow disabling Proximity services
+# Allowed values: LinkLoss,PathLoss,FindMe
+#Disable=audioOverBle
diff --git a/profiles/audioOverBle/btvoice.c b/profiles/audioOverBle/btvoice.c
new file mode 100644
index 0000000..d6f192b
--- /dev/null
+++ b/profiles/audioOverBle/btvoice.c
@@ -0,0 +1,560 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2011  Nokia Corporation
+ *  Copyright (C) 2011  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2016  Google Fiber
+ *
+ *  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 <pthread.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "lib/sdp.h"
+#include "lib/sdp_lib.h"
+#include "lib/uuid.h"
+
+#include <gdbus/gdbus.h>
+
+#include "attrib/gattrib.h"
+#include "attrib/att.h"
+#include "attrib/gatt.h"
+#include "src/adapter.h"
+#include "src/dbus-common.h"
+#include "src/device.h"
+#include "src/error.h"
+#include "src/log.h"
+#include "src/service.h"
+#include "src/shared/util.h"
+#include "src/textfile.h"
+
+#include "btvoice.h"
+
+#define AUD_START_UUID 0xFFF1
+#define AUD_CONFIG_UUID 0xFFF2
+#define AUD_STREAM_UUID 0xFFF4
+
+struct btvoice_device {
+  uint16_t id;
+  bdaddr_t bdaddr;
+  struct btd_device *device;
+  GAttrib *attrib;
+  struct gatt_primary *audioOverBle;
+  struct btvoiceenable voiceenable;
+  GSList *reports;
+  uint16_t aud_start_handle;
+  uint16_t aud_config_handle;
+  uint16_t aud_stream_handle;
+  uint16_t aud_cust1_handle;
+  uint16_t aud_cust2_handle;
+  uint16_t aud_cust4_handle;
+  uint16_t aud_cust7_handle;
+  uint16_t aud_cust8_handle;
+};
+
+struct audiostream {
+  uint8_t id;
+  uint8_t type;
+  uint16_t ccc_handle;
+  guint notifyid;
+  struct gatt_char *decl;
+  struct btvoice_device *btvoicedev;
+};
+
+static GSList *btvoicedevices = NULL;
+
+static struct btvoice_device *find_btvoicedev(struct btd_device *device) {
+  GSList *l;
+
+  for (l = btvoicedevices; l; l = l->next) {
+    struct btvoice_device *btvoicedev = l->data;
+
+    if (btvoicedev->device == device) return btvoicedev;
+  }
+
+  return NULL;
+}
+
+static void aud_cfg_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                               gpointer user_data) {
+  if (status != 0) {
+    error("Write output report failed: %s", att_ecode2str(status));
+    return;
+  }
+}
+
+static void aud_cust1a_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                               gpointer user_data) {
+  if (status != 0) {
+    error("cust1a failed: %s", att_ecode2str(status));
+    return;
+  }
+}
+
+static void aud_cust1_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                               gpointer user_data) {
+  struct btvoice_device *btvoicedev = user_data;
+  uint8_t data[] = { 0x0a, 0x01 };
+
+  if (status != 0) {
+    error("cust1 failed: %s", att_ecode2str(status));
+    return;
+  }
+
+  gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust1_handle, data,
+    sizeof(data), aud_cust1a_written_cb, btvoicedev);
+}
+
+static void aud_cust2b_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                               gpointer user_data) {
+  if (status != 0) {
+    error("cust2b failed: %s", att_ecode2str(status));
+    return;
+  }
+}
+
+static void aud_cust2a_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                               gpointer user_data) {
+  struct btvoice_device *btvoicedev = user_data;
+  uint8_t data[] = { 0x05, 0x00, 0x01, 0x05 };
+
+  if (status != 0) {
+    error("cust2a failed: %s", att_ecode2str(status));
+    return;
+  }
+
+  gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust2_handle, data,
+    sizeof(data), aud_cust2b_written_cb, btvoicedev);
+}
+
+static void aud_cust2_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                               gpointer user_data) {
+  struct btvoice_device *btvoicedev = user_data;
+  uint8_t data[] = { 0x05, 0x00, 0x01, 0x01 };
+
+  if (status != 0) {
+    error("cust2 failed: %s", att_ecode2str(status));
+    return;
+  }
+
+  gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust2_handle, data,
+    sizeof(data), aud_cust2a_written_cb, btvoicedev);
+}
+
+static void aud_cust4_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                               gpointer user_data) {
+  if (status != 0) {
+    error("Write output report failed: %s", att_ecode2str(status));
+    return;
+  }
+}
+
+static void aud_cust7_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                               gpointer user_data) {
+  if (status != 0) {
+    error("Write output report failed: %s", att_ecode2str(status));
+    return;
+  }
+}
+
+static void aud_cust8_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                               gpointer user_data) {
+  if (status != 0) {
+    error("Write output report failed: %s", att_ecode2str(status));
+    return;
+  }
+}
+
+#define RAS_END_CMD      0x00
+#define TI_AUDIO_PATH "\0rc_audio_ti"
+
+static void audio_value_cb(const uint8_t *pdu, uint16_t len,
+                           gpointer user_data)
+{
+  struct audiostream *audiostream = user_data;
+  struct btvoice_device *btvoicedev = audiostream->btvoicedev;
+  static int audio_fd = -1;
+  static uint16_t old_mtu;
+  static int sent = 0, dropped = 0;
+
+  if (audio_fd == -1) {
+    struct sockaddr_un sun;
+
+    memset(&sun, 0, sizeof(sun));
+    sun.sun_family = AF_UNIX;
+    snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", TI_AUDIO_PATH);
+
+    audio_fd = socket(AF_UNIX, SOCK_CLOEXEC | SOCK_NONBLOCK | SOCK_DGRAM, 0);
+    if (connect(audio_fd, (const struct sockaddr *) &sun, sizeof(sun)) < 0) {
+      perror("connect(AF_UNIX, TI_AUDIO_PATH)");
+      close(audio_fd);
+      audio_fd = -1;
+      dropped++;
+      return;
+    }
+
+    /* Remote sends 103 byte notifications, plus metadata. */
+    old_mtu = g_attrib_get_mtu(btvoicedev->attrib);
+    g_attrib_set_mtu(btvoicedev->attrib, 105);
+  }
+
+  if (audio_fd != -1) {
+    if (len == 4 && pdu[3] == RAS_END_CMD) {
+      close(audio_fd);
+      audio_fd = -1;
+      g_attrib_set_mtu(btvoicedev->attrib, old_mtu);
+      DBG("Finished audio stream; sent = %d dropped = %d\n", sent, dropped);
+      sent = dropped = 0;
+    } else {
+      struct iovec iov[3];
+      struct msghdr mh;
+      uint8_t remote_type = 0;  /* GFRM210 */
+
+      memset(&iov, 0, sizeof(iov));
+      iov[0].iov_base = &btvoicedev->bdaddr;
+      iov[0].iov_len = sizeof(btvoicedev->bdaddr);
+      iov[1].iov_base = (void *)&remote_type;
+      iov[1].iov_len = 1;
+      iov[2].iov_base = (void *)(pdu + 3);  /* Skip 3 bytes of BLE header */
+      iov[2].iov_len = len - 3;
+
+      memset(&mh, 0, sizeof(mh));
+      mh.msg_iov = iov;
+      mh.msg_iovlen = 3;
+
+      if (sendmsg(audio_fd, &mh, MSG_DONTWAIT) >= 0) {
+        sent++;
+      } else {
+        dropped++;
+      }
+    }
+  }
+}
+
+static void audio_ccc_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                                 gpointer user_data) {
+  struct audiostream *audiostream = user_data;
+
+  if (status != 0) {
+    error("Write audio characteristic descriptor failed: %s",
+          att_ecode2str(status));
+    return;
+  }
+}
+
+static void audio_write_ccc(uint16_t handle, gpointer user_data) {
+  struct audiostream *audiostream = user_data;
+  struct btvoice_device *btvoicedev = audiostream->btvoicedev;
+  uint8_t value[] = {0x01, 0x00};
+
+  audiostream->notifyid = g_attrib_register(
+      btvoicedev->attrib, ATT_OP_HANDLE_NOTIFY, audiostream->decl->value_handle,
+      audio_value_cb, audiostream, NULL);
+
+  gatt_write_char(btvoicedev->attrib, handle, value, sizeof(value),
+                  audio_ccc_written_cb, audiostream);
+}
+
+static void report_reference_cb(guint8 status, const guint8 *pdu, guint16 plen,
+                                gpointer user_data) {
+  struct audiostream *report = user_data;
+
+  if (status != 0) {
+    error("Read Report Reference descriptor failed: %s", att_ecode2str(status));
+    return;
+  }
+
+  if (plen != 3) {
+    error("Malformed ATT read response");
+    return;
+  }
+
+  report->id = pdu[1];
+  report->type = pdu[2];
+}
+
+static void discover_descriptor_cb(uint8_t status, GSList *descs,
+                                   void *user_data) {
+  struct audiostream *report;
+  GAttrib *attrib = NULL;
+
+  if (status != 0) {
+    error("Discover all characteristic descriptors failed: %s",
+          att_ecode2str(status));
+    return;
+  }
+
+  for (; descs; descs = descs->next) {
+    struct gatt_desc *desc = descs->data;
+
+    switch (desc->uuid16) {
+      case GATT_CLIENT_CHARAC_CFG_UUID:
+        report = user_data;
+        report->ccc_handle = desc->handle;
+        attrib = report->btvoicedev->attrib;
+        audio_write_ccc(desc->handle, report);
+        DBG("Audio Notifications Enabled\n");
+        break;
+      case GATT_REPORT_REFERENCE:
+        report = user_data;
+        attrib = report->btvoicedev->attrib;
+        gatt_read_char(attrib, desc->handle, report_reference_cb, report);
+        DBG("Audio Stream Report Reference Characterstic Read\n");
+        break;
+    }
+  }
+}
+
+static void discover_descriptor(GAttrib *attrib, uint16_t start, uint16_t end,
+                                gpointer user_data) {
+  if (start > end) {
+    DBG("Start before End\n");
+    return;
+  }
+
+  gatt_discover_desc(attrib, start, end, NULL, discover_descriptor_cb,
+                     user_data);
+}
+
+static void char_discovered_cb(guint8 status, GSList *chars, void *user_data) {
+  struct btvoice_device *btvoicedev = user_data;
+  bt_uuid_t audstart_uuid, audconfig_uuid, audstream_uuid;
+  bt_uuid_t audcust1_uuid, audcust2_uuid, audcust4_uuid,
+    audcust7_uuid, audcust8_uuid;
+
+  uint128_t aud_custom1_uuid = {
+      .data = {  0xf0, 0x00, 0xfe, 0xf1, 0x04, 0x51, 0x40, 0x00,
+      0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
+  };
+
+  uint128_t aud_custom2_uuid = {
+      .data = {  0xf0, 0x00, 0xfe, 0xf2, 0x04, 0x51, 0x40, 0x00,
+      0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
+  };
+
+  uint128_t aud_custom4_uuid = {
+      .data = {  0xf0, 0x00, 0xfe, 0xf4, 0x04, 0x51, 0x40, 0x00,
+      0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
+  };
+
+  uint128_t aud_custom7_uuid = {
+      .data = {  0xf0, 0x00, 0xfe, 0xf7, 0x04, 0x51, 0x40, 0x00,
+      0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
+  };
+
+  uint128_t aud_custom8_uuid = {
+      .data = {  0xf0, 0x00, 0xfe, 0xf8, 0x04, 0x51, 0x40, 0x00,
+      0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
+  };
+
+  struct audiostream *audiostream;
+  GSList *l;
+
+  if (status != 0) {
+    const char *str = att_ecode2str(status);
+    return;
+  }
+
+  bt_uuid16_create(&audstart_uuid, AUD_START_UUID);
+  bt_uuid16_create(&audconfig_uuid, AUD_CONFIG_UUID);
+  bt_uuid16_create(&audstream_uuid, AUD_STREAM_UUID);
+  bt_uuid128_create(&audcust1_uuid, aud_custom1_uuid);
+  bt_uuid128_create(&audcust2_uuid, aud_custom2_uuid);
+  bt_uuid128_create(&audcust4_uuid, aud_custom4_uuid);
+  bt_uuid128_create(&audcust7_uuid, aud_custom7_uuid);
+  bt_uuid128_create(&audcust8_uuid, aud_custom8_uuid);
+
+  for (l = chars; l; l = g_slist_next(l)) {
+    struct gatt_char *chr;
+    bt_uuid_t uuid;
+
+    chr = l->data;
+    bt_string_to_uuid(&uuid, chr->uuid);
+
+    if (bt_uuid_cmp(&uuid, &audstart_uuid) == 0) {
+      btvoicedev->aud_start_handle = chr->value_handle;
+    } else if (bt_uuid_cmp(&uuid, &audconfig_uuid) == 0) {
+      btvoicedev->aud_config_handle = chr->value_handle;
+    } else if (bt_uuid_cmp(&uuid, &audstream_uuid) == 0) {
+      audiostream = g_new0(struct audiostream, 1);
+      audiostream->btvoicedev = btvoicedev;
+      audiostream->decl = g_memdup(chr, sizeof(*chr));
+      btvoicedev->reports = g_slist_append(btvoicedev->reports, audiostream);
+
+      btvoicedev->aud_stream_handle = chr->value_handle;
+      discover_descriptor(btvoicedev->attrib, chr->value_handle,
+                          btvoicedev->audioOverBle->range.end, audiostream);
+    } else if (bt_uuid_cmp(&uuid, &audcust1_uuid) == 0) {
+      uint8_t data[] = { 0x0a, 0x00 };
+      btvoicedev->aud_cust1_handle = chr->value_handle;
+      gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust1_handle, data,
+          sizeof(data), aud_cust1_written_cb, btvoicedev);
+    } else if (bt_uuid_cmp(&uuid, &audcust2_uuid) == 0) {
+      uint8_t data[] = { 0x00, 0x05, 0x00, 0x00, 0x01 };
+      btvoicedev->aud_cust2_handle = chr->value_handle;
+      gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust2_handle, data,
+          sizeof(data), aud_cust2_written_cb, btvoicedev);
+    } else if (bt_uuid_cmp(&uuid, &audcust4_uuid) == 0) {
+      uint8_t data[] = { 0x0f };
+      btvoicedev->aud_cust4_handle = chr->value_handle;
+      gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust4_handle, data,
+          sizeof(data), aud_cust4_written_cb, btvoicedev);
+    } else if (bt_uuid_cmp(&uuid, &audcust7_uuid) == 0) {
+      uint8_t data[] = { 0x05 };
+      btvoicedev->aud_cust7_handle = chr->value_handle;
+      gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust7_handle, data,
+          sizeof(data), aud_cust7_written_cb, btvoicedev);
+    } else if (bt_uuid_cmp(&uuid, &audcust8_uuid) == 0) {
+      uint8_t data[] = { 0x02 };
+      btvoicedev->aud_cust8_handle = chr->value_handle;
+      gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust8_handle, data,
+          sizeof(data), aud_cust8_written_cb, btvoicedev);
+    }
+  }
+
+  if (btvoicedev->aud_config_handle) {
+    uint8_t data[] = {0x03, 0x00, 0x00};
+    gatt_write_char(btvoicedev->attrib, btvoicedev->aud_config_handle, data,
+                    sizeof(data), aud_cfg_written_cb, btvoicedev);
+  }
+}
+
+
+int btvoice_accept(struct btd_service *service) {
+  struct btvoice_device *btvoicedev = btd_service_get_user_data(service);
+  struct gatt_primary *prim = btvoicedev->audioOverBle;
+  struct btd_device *device = btd_service_get_device(service);
+  GAttrib *attrib = btd_device_get_attrib(device);
+
+  btvoicedev->attrib = g_attrib_ref(attrib);
+
+  btd_service_connecting_complete(service, 0);
+
+  if (btvoicedev->voiceenable.audioOverBle) {
+    gatt_discover_char(btvoicedev->attrib, prim->range.start, prim->range.end,
+                       NULL, char_discovered_cb, btvoicedev);
+  }
+
+  return 0;
+}
+
+int btvoice_disconnect(struct btd_service *service) {
+  struct btvoice_device *btvoicedev = btd_service_get_user_data(service);
+  GSList *l;
+
+  for (l = btvoicedev->reports; l; l = l->next) {
+    struct audiostream *r = l->data;
+
+    g_attrib_unregister(btvoicedev->attrib, r->notifyid);
+  }
+
+  g_attrib_unref(btvoicedev->attrib);
+  btvoicedev->attrib = NULL;
+
+  btd_service_disconnecting_complete(service, 0);
+
+  return 0;
+}
+
+static struct btvoice_device *register_btvoicedevice(
+    struct btd_service *service, struct btd_device *device)
+{
+  struct btvoice_device *btvoicedev;
+  const bdaddr_t *bdaddr = device_get_address(device);
+
+  btvoicedev = find_btvoicedev(device);
+  if (btvoicedev != NULL) return btvoicedev;
+
+  btvoicedev = g_try_new0(struct btvoice_device, 1);
+  if (!btvoicedev) return NULL;
+
+  btvoicedev->device = btd_device_ref(device);
+  btvoicedev->bdaddr = *bdaddr;
+
+  btd_service_set_user_data(service, btvoicedev);
+  btvoicedevices = g_slist_append(btvoicedevices, btvoicedev);
+
+  return btvoicedev;
+}
+
+int btvoice_register_audioOverBle(struct btd_service *service,
+                                  struct btd_device *device,
+                                  struct btvoiceenable *voiceenable,
+                                  struct gatt_primary *prim) {
+  struct btvoice_device *btvoicedev;
+
+  if (!voiceenable->audioOverBle) return 0;
+
+  btvoicedev = register_btvoicedevice(service, device);
+  if (btvoicedev == NULL) return -1;
+
+  btvoicedev->id = prim->range.start;
+  btvoicedev->audioOverBle = g_memdup(prim, sizeof(*prim));
+  btvoicedev->voiceenable.audioOverBle = TRUE;
+
+  return 0;
+}
+
+static void cleanup_btdevice(struct btvoice_device *btvoicedev)
+{
+  struct btd_device *device = btvoicedev->device;
+
+  if (btvoicedev->audioOverBle != NULL) return;
+
+  if (btvoicedev->attrib != NULL) {
+    g_attrib_unref(btvoicedev->attrib);
+    btvoicedev->attrib = NULL;
+  }
+
+  if (btvoicedev->device != NULL) {
+    btd_device_unref(btvoicedev->device);
+    btvoicedev->device = NULL;
+  }
+}
+
+void btvoice_unregister_audioOverBle(struct btd_device *device)
+{
+  struct btvoice_device *btvoicedev;
+
+  btvoicedev = find_btvoicedev(device);
+  if (btvoicedev == NULL) return;
+
+  g_free(btvoicedev->audioOverBle);
+  btvoicedev->audioOverBle = NULL;
+  btvoicedev->voiceenable.audioOverBle = FALSE;
+
+  cleanup_btdevice(btvoicedev);
+
+  g_free(btvoicedev);
+}
diff --git a/profiles/audioOverBle/btvoice.h b/profiles/audioOverBle/btvoice.h
new file mode 100644
index 0000000..6bf57f5
--- /dev/null
+++ b/profiles/audioOverBle/btvoice.h
@@ -0,0 +1,45 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2011  Nokia Corporation
+ *  Copyright (C) 2011  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 BTVOICE_H_
+#define BTVOICE_H_
+
+struct btvoiceenable {
+	gboolean audioOverBle;
+	int	 mode;
+	int	 l2capChannel;
+};
+
+int btvoice_register_audioOverBle(struct btd_service *service,
+						struct btd_device *device,
+						struct btvoiceenable *voiceenable,
+						struct gatt_primary *audioOverBle);
+
+
+void btvoice_unregister_audioOverBle(struct btd_device *device);
+
+int btvoice_accept(struct btd_service *service);
+int btvoice_disconnect(struct btd_service *service);
+
+#endif  // BTVOICE_H_
diff --git a/profiles/audioOverBle/main.c b/profiles/audioOverBle/main.c
new file mode 100644
index 0000000..4b44cbd
--- /dev/null
+++ b/profiles/audioOverBle/main.c
@@ -0,0 +1,79 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2011  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 <stdint.h>
+#include <glib.h>
+#include <gdbus/gdbus.h>
+
+#include "src/log.h"
+#include "src/plugin.h"
+#include "manager.h"
+#include "src/hcid.h"
+
+static GKeyFile *config = NULL;
+
+static GKeyFile *open_config_file(const char *file)
+{
+	GError *gerr = NULL;
+	GKeyFile *keyfile;
+
+	keyfile = g_key_file_new();
+
+	g_key_file_set_list_separator(keyfile, ',');
+
+	if (!g_key_file_load_from_file(keyfile, file, 0, &gerr)) {
+		if (!g_error_matches(gerr, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+			error("Parsing %s failed: %s", file, gerr->message);
+		g_error_free(gerr);
+		g_key_file_free(keyfile);
+		return NULL;
+	}
+
+	return keyfile;
+}
+
+static int audioOverBle_init(void)
+{
+	config = open_config_file(CONFIGDIR "/audioOverBle.conf");
+	if (audioOverBle_manager_init(config) < 0)
+		return -EIO;
+
+	return 0;
+}
+
+static void audioOverBle_exit(void)
+{
+	if (config)
+		g_key_file_free(config);
+
+	audioOverBle_manager_exit();
+}
+
+BLUETOOTH_PLUGIN_DEFINE(audioOverBle, VERSION,
+			BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+			audioOverBle_init, audioOverBle_exit)
diff --git a/profiles/audioOverBle/manager.c b/profiles/audioOverBle/manager.c
new file mode 100644
index 0000000..b38ed29
--- /dev/null
+++ b/profiles/audioOverBle/manager.c
@@ -0,0 +1,153 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2011  Nokia Corporation
+ *  Copyright (C) 2011  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 <stdbool.h>
+#include <errno.h>
+
+#include <gdbus/gdbus.h>
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "lib/sdp.h"
+#include "lib/sdp_lib.h"
+#include "lib/uuid.h"
+
+#include "src/log.h"
+#include "src/adapter.h"
+#include "src/device.h"
+#include "src/profile.h"
+#include "src/service.h"
+#include "attrib/att.h"
+#include "attrib/gattrib.h"
+#include "attrib/gatt.h"
+
+#include "btvoice.h"
+#include "manager.h"
+
+#define AUD_UUID		"0000fff0-0000-1000-8000-00805f9b34fb"
+
+static struct btvoiceenable voiceenable = {
+	.audioOverBle = TRUE,
+};
+
+static int btvoice_audioOverBle_probe(struct btd_service *service)
+{
+	struct btd_device *device = btd_service_get_device(service);
+	struct gatt_primary *audioOverBlePrim;
+	const char *path = device_get_path(device);
+
+	DBG("path %s", path);
+
+	audioOverBlePrim = btd_device_get_primary(device, AUD_UUID);
+	if (audioOverBlePrim == NULL)
+		return -1;
+
+	return btvoice_register_audioOverBle(service, device, &voiceenable, audioOverBlePrim);
+}
+
+
+static void btvoice_audioOverBle_remove(struct btd_service *service)
+{
+	struct btd_device *device = btd_service_get_device(service);
+
+	btvoice_unregister_audioOverBle(device);
+}
+
+
+static struct btd_profile pxp_btvoice_audioOverBle_profile = {
+	.name		= "audioOverBle",
+	.remote_uuid	= AUD_UUID,
+	.device_probe	= btvoice_audioOverBle_probe,
+	.device_remove	= btvoice_audioOverBle_remove,
+	.accept		= btvoice_accept,
+	.disconnect	= btvoice_disconnect,
+	.auto_connect	= true,
+};
+
+static void load_config_file(GKeyFile *config)
+{
+	char **list;
+	char *str;
+
+	int i;
+
+	if (config == NULL)
+	{
+		DBG("No Config found, assume default value");
+		voiceenable.audioOverBle = 1;
+		voiceenable.mode = 3;
+		voiceenable.l2capChannel = 0;
+		return;
+	}
+
+	list = g_key_file_get_string_list(config, "General", "Disable",
+								NULL, NULL);
+	for (i = 0; list && list[i] != NULL; i++) {
+		if (g_str_equal(list[i], "audioOverBle"))
+			voiceenable.audioOverBle = FALSE;
+	}
+	DBG("audio OverBLE Enable? %d", voiceenable.audioOverBle);
+	g_strfreev(list);
+
+	str = g_key_file_get_string(config, "General", "Mode", NULL);
+	if (str) {
+		DBG("Mode: %s", str);
+		if (g_str_equal(str, "PROP_MODE"))
+			voiceenable.mode = 1;
+		else if (g_str_equal(str, "L2CAP_MODE"))
+			voiceenable.mode = 2;
+		else if (g_str_equal(str, "ATT_MODE"))
+			voiceenable.mode = 3;
+		g_free(str);
+	}
+	DBG("audio OverBLE Mode? %d", voiceenable.mode);
+
+	voiceenable.l2capChannel = g_key_file_get_integer(config, "General", "l2capChannel", NULL);
+	DBG("audio OverBLE L2cap Channel? %d", voiceenable.l2capChannel);
+}
+
+void audioOverBle_manager_exit(void)
+{
+	btd_profile_unregister(&pxp_btvoice_audioOverBle_profile);
+}
+
+int audioOverBle_manager_init(GKeyFile *config)
+{
+	load_config_file(config);
+
+	if (btd_profile_register(&pxp_btvoice_audioOverBle_profile) < 0)
+	{
+		DBG("Fail to register...");
+		audioOverBle_manager_exit();		
+		return -1;
+	}
+	else
+	{
+		return 0;
+	}
+}
diff --git a/profiles/audioOverBle/manager.h b/profiles/audioOverBle/manager.h
new file mode 100644
index 0000000..f138dbe
--- /dev/null
+++ b/profiles/audioOverBle/manager.h
@@ -0,0 +1,29 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2011  Nokia Corporation
+ *  Copyright (C) 2011  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 AUDIO_OVER_BLE_MANAGER_H
+#define AUDIO_OVER_BLE_MANAGER_H
+int audioOverBle_manager_init(GKeyFile *conf);
+void audioOverBle_manager_exit(void);
+#endif  /* AUDIO_OVER_BLE_MANAGER_H */
diff --git a/profiles/battery/battery.c b/profiles/battery/battery.c
new file mode 100644
index 0000000..4ec35fc
--- /dev/null
+++ b/profiles/battery/battery.c
@@ -0,0 +1,368 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2014	Google, Inc.  (edjames@google.com)
+ *  Copyright (C) 2012	Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2012	Nordic Semiconductor Inc.
+ *  Copyright (C) 2012	Instituto Nokia de Tecnologia - INdT
+ *
+ *  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
+ *
+ */
+
+/* much lifted from input/hog.c */
+
+/* battery - read battery level */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "lib/sdp.h"
+#include "lib/uuid.h"
+
+#include "src/adapter.h"
+#include "src/device.h"
+#include "src/log.h"
+#include "src/plugin.h"
+#include "src/profile.h"
+#include "src/service.h"
+#include "attrib/att.h"
+#include "attrib/gattrib.h"
+#include "attrib/gatt.h"
+#include "src/shared/util.h"
+
+#define BATT_UUID		"0000180f-0000-1000-8000-00805f9b34fb"
+#define BATT_LEVEL_UUID		"00002a19-0000-1000-8000-00805f9b34fb"
+
+#define BATTERY_DIR		"/tmp/batteries"
+
+struct service {
+	uint16_t		value_handle;
+	int			value;
+};
+
+struct batt_device {
+	uint16_t		id;
+	struct btd_device	*device;
+	GAttrib			*attrib;
+	struct gatt_primary	*batt_primary;
+
+	struct service		level;
+
+	int			batt_level;
+};
+
+static GSList *devices = NULL;
+
+static struct batt_device *batt_new_device(struct btd_device *device,
+								uint16_t id)
+{
+	struct batt_device *battdev;
+
+	battdev = g_try_new0(struct batt_device, 1);
+	if (!battdev)
+		return NULL;
+
+	battdev->id = id;
+	battdev->device = btd_device_ref(device);
+
+	return battdev;
+}
+
+static void batt_free_device(struct batt_device *battdev)
+{
+	btd_device_unref(battdev->device);
+	g_attrib_unref(battdev->attrib);
+	g_free(battdev->batt_primary);
+	g_free(battdev);
+}
+
+static time_t wallclock(void)
+{
+	struct timespec ts;
+
+	if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
+		return 0;
+	}
+	return ts.tv_sec;
+}
+
+/* should really be done in the dbus agent, I think */
+static void batt_update_level_file(struct batt_device* battdev)
+{
+	char path[MAXPATHLEN], pathnew[MAXPATHLEN];
+
+	const char* addr = NULL;
+	const char* id = device_get_path(battdev->device);
+
+	/* id == /org/bluez/hci0/dev_D0_5F_B8_29_10_8D */
+
+	if (id != NULL) {
+		addr = rindex(id, '/');
+		if (addr == NULL) {
+			addr = id;
+		} else {
+			addr++;
+		}
+	}
+	if (addr == NULL || *addr == '\0') {
+		addr = "UNKNOWN";
+	}
+
+	snprintf(path, sizeof path, "%s/%s", BATTERY_DIR, addr);
+	snprintf(pathnew, sizeof pathnew, "%s.new", path);
+
+	if (mkdir(BATTERY_DIR, 0777) < 0 && errno != EEXIST) {
+		error("BATT mkdir: %s: %s", BATTERY_DIR, strerror(errno));
+		return;
+	}
+	if (chmod(BATTERY_DIR, 0755) < 0) {
+		error("BATT chmod: %s: %s", BATTERY_DIR, strerror(errno));
+	}
+
+	FILE* fp = fopen(pathnew, "w");
+	if (fp == NULL) {
+		error("BATT fopen: %s: %s", pathnew, strerror(errno));
+		return;
+	}
+	if (fchmod(fileno(fp), 0644) < 0) {
+		error("BATT chmod: %s: %s", pathnew, strerror(errno));
+	}
+	fprintf(fp, "%d\n", battdev->batt_level);
+	fclose(fp);
+
+	if (rename(pathnew, path) != 0) {
+		error("BATT rename: %s -> %s: %s", pathnew, path, strerror(errno));
+		return;
+	}
+}
+
+static void level_read_char_cb(guint8 status, const guint8 *pdu, guint16 len,
+							gpointer user_data)
+{
+	struct batt_device *battdev = user_data;
+
+	if (status != 0) {
+		error("BATT %s failed: %s", __func__, att_ecode2str(status));
+		return;
+	}
+	battdev->batt_level = pdu[1];
+	DBG("BATT level=%d", battdev->batt_level);
+	batt_update_level_file(battdev);
+}
+
+static void check_level(struct batt_device *battdev)
+{
+	if (battdev->level.value_handle == 0)
+		return;
+
+	gatt_read_char(battdev->attrib, battdev->level.value_handle,
+						level_read_char_cb, battdev);
+}
+
+static void char_discovered_cb(uint8_t status, GSList *chars, void* user_data)
+{
+	struct batt_device *battdev = user_data;
+	struct gatt_primary *prim = battdev->batt_primary;
+	bt_uuid_t img_identity_uuid, img_block_uuid;
+	GSList *l;
+	uint16_t info_handle = 0, proto_mode_handle = 0;
+
+	DBG("BATT inspecting characteristics");
+	if (status != 0) {
+		error("BATT %s failed: %s", __func__, att_ecode2str(status));
+		return;
+	}
+
+	for (l = chars; l; l = g_slist_next(l)) {
+		struct gatt_char *chr, *next;
+		bt_uuid_t uuid;
+		uint16_t start, end;
+
+		chr = l->data;
+		next = l->next ? l->next->data : NULL;
+
+		DBG("BATT 0x%04x UUID: %s properties: %02x",
+				chr->handle, chr->uuid, chr->properties);
+
+
+		start = chr->value_handle + 1;
+		end = (next ? next->handle - 1 : prim->range.end);
+
+		if (g_strcmp0(chr->uuid, BATT_LEVEL_UUID) == 0) {
+			battdev->level.value_handle = chr->value_handle;
+			DBG("BATT found BATT_LEVEL_UUID value_handle=0x%04x", chr->value_handle);
+			check_level(battdev);
+		}
+	}
+}
+
+static int batt_accept(struct btd_service *service)
+{
+	struct batt_device *battdev = btd_service_get_user_data(service);
+	struct gatt_primary *prim = battdev->batt_primary;
+	struct btd_device *device = btd_service_get_device(service);
+	GAttrib *attrib = btd_device_get_attrib(device);
+
+	DBG("BATT connected");
+
+	battdev->attrib = g_attrib_ref(attrib);
+	battdev->level.value = -1;
+
+	btd_service_connecting_complete(service, 0);
+
+	if (battdev->level.value_handle == 0) {
+		DBG("BATT discovering characteristics");
+
+		gatt_discover_char(battdev->attrib, prim->range.start,
+						prim->range.end, NULL,
+						char_discovered_cb, battdev);
+	} else {
+		check_level(battdev);
+	}
+
+	return 0;
+}
+
+static int batt_disconnect(struct btd_service *service)
+{
+	struct batt_device *battdev = btd_service_get_user_data(service);
+
+	DBG("BATT disconnected");
+
+	if (battdev->attrib) {
+		g_attrib_unref(battdev->attrib);
+		battdev->attrib = NULL;
+	}
+
+	btd_service_disconnecting_complete(service, 0);
+
+	return 0;
+}
+
+static struct batt_device *batt_register_device(struct btd_device *device,
+						struct gatt_primary *prim)
+{
+	struct batt_device *battdev;
+	GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_NVAL;
+	GIOChannel *io;
+
+	battdev = batt_new_device(device, prim->range.start);
+	if (!battdev)
+		return NULL;
+
+	battdev->batt_primary = g_memdup(prim, sizeof(*prim));
+
+	return battdev;
+}
+
+static int batt_unregister_device(struct batt_device *battdev)
+{
+	batt_free_device(battdev);
+
+	return 0;
+}
+
+static int batt_probe(struct btd_service *service)
+{
+	struct btd_device *device = btd_service_get_device(service);
+	const char *path = device_get_path(device);
+	GSList *primaries, *l;
+
+	DBG("BATT path %s", path);
+
+	primaries = btd_device_get_primaries(device);
+	if (primaries == NULL)
+		return -EINVAL;
+
+	for (l = primaries; l; l = g_slist_next(l)) {
+		struct gatt_primary *prim = l->data;
+		struct batt_device *battdev;
+
+		DBG("BATT uuid=%s", prim->uuid);
+		if (strcmp(prim->uuid, BATT_UUID) != 0)
+			continue;
+
+		DBG("BATT matched BATT uuid %s", prim->uuid);
+		battdev = batt_register_device(device, prim);
+		if (battdev == NULL)
+			continue;
+
+		btd_service_set_user_data(service, battdev);
+		devices = g_slist_append(devices, battdev);
+	}
+
+	return 0;
+}
+
+static void remove_device(gpointer a, gpointer b)
+{
+	struct batt_device *battdev = a;
+	struct btd_device *device = b;
+
+	if (battdev->device != device)
+		return;
+
+	devices = g_slist_remove(devices, battdev);
+	batt_unregister_device(battdev);
+}
+
+static void batt_remove(struct btd_service *service)
+{
+	struct btd_device *device = btd_service_get_device(service);
+	const char *path = device_get_path(device);
+
+	DBG("BATT path %s", path);
+
+	g_slist_foreach(devices, remove_device, device);
+}
+
+static struct btd_profile batt_profile = {
+	.name		= "Battery",
+	.remote_uuid	= BATT_UUID,
+	.device_probe	= batt_probe,
+	.device_remove	= batt_remove,
+	.accept		= batt_accept,
+	.disconnect	= batt_disconnect,
+	.auto_connect	= true,
+};
+
+static int batt_init(void)
+{
+	int err;
+
+	return btd_profile_register(&batt_profile);
+}
+
+static void batt_exit(void)
+{
+	btd_profile_unregister(&batt_profile);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(battery, VERSION, BLUETOOTH_PLUGIN_PRIORITY_LOW,
+							batt_init, batt_exit)
diff --git a/profiles/input/device.c b/profiles/input/device.c
index a494ea2..5a590e3 100644
--- a/profiles/input/device.c
+++ b/profiles/input/device.c
@@ -91,6 +91,7 @@
 
 static int idle_timeout = 0;
 static bool uhid_enabled = false;
+static bool encryption_enabled = true;
 
 void input_set_idle_timeout(int timeout)
 {
@@ -102,6 +103,11 @@
 	uhid_enabled = state;
 }
 
+void input_enable_encryption(bool state)
+{
+	encryption_enabled = state;
+}
+
 static void input_device_enter_reconnect_mode(struct input_device *idev);
 static int connection_disconnect(struct input_device *idev, uint32_t flags);
 
@@ -971,7 +977,7 @@
 		device_get_name(idev->device, req->name, sizeof(req->name));
 
 	/* Encryption is mandatory for keyboards */
-	if (req->subclass & 0x40) {
+	if (encryption_enabled && (req->subclass & 0x40)) {
 		if (!bt_io_set(idev->intr_io, &gerr,
 					BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
 					BT_IO_OPT_INVALID)) {
@@ -1495,3 +1501,23 @@
 
 	return 0;
 }
+
+bool input_device_must_unplug(const bdaddr_t *src, const bdaddr_t *dst)
+{
+	struct input_device *idev = find_device(src, dst);
+	struct btd_device *device;
+
+	if (!idev)
+		return false;
+
+	device = idev->device;
+	if (!device)
+		return false;
+
+	if (btd_device_get_unplug(device)) {
+		btd_device_set_unplug(device, false);
+		return true;
+	}
+
+	return false;
+}
diff --git a/profiles/input/device.h b/profiles/input/device.h
index 51a9aee..6935663 100644
--- a/profiles/input/device.h
+++ b/profiles/input/device.h
@@ -29,6 +29,7 @@
 
 void input_set_idle_timeout(int timeout);
 void input_enable_userspace_hid(bool state);
+void input_enable_encryption(bool state);
 
 int input_device_register(struct btd_service *service);
 void input_device_unregister(struct btd_service *service);
@@ -40,3 +41,5 @@
 
 int input_device_connect(struct btd_service *service);
 int input_device_disconnect(struct btd_service *service);
+
+bool input_device_must_unplug(const bdaddr_t *src, const bdaddr_t *dst);
diff --git a/profiles/input/hog.c b/profiles/input/hog.c
index 9836a6c..233929d 100644
--- a/profiles/input/hog.c
+++ b/profiles/input/hog.c
@@ -243,5 +243,5 @@
 	btd_profile_unregister(&hog_profile);
 }
 
-BLUETOOTH_PLUGIN_DEFINE(hog, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+BLUETOOTH_PLUGIN_DEFINE(hog, VERSION, BLUETOOTH_PLUGIN_PRIORITY_HIGH,
 							hog_init, hog_exit)
diff --git a/profiles/input/manager.c b/profiles/input/manager.c
index 1d31b06..96596b5 100644
--- a/profiles/input/manager.c
+++ b/profiles/input/manager.c
@@ -97,6 +97,7 @@
 	if (config) {
 		int idle_timeout;
 		gboolean uhid_enabled;
+		gboolean encryption;
 
 		idle_timeout = g_key_file_get_integer(config, "General",
 							"IdleTimeout", &err);
@@ -114,6 +115,15 @@
 			input_enable_userspace_hid(uhid_enabled);
 		} else
 			g_clear_error(&err);
+
+		encryption = g_key_file_get_boolean(config, "General",
+							"Encryption", &err);
+		if (!err) {
+			DBG("input.conf: Encryption=%s", encryption ?
+							"true" : "false");
+			input_enable_encryption(encryption);
+		} else
+			g_clear_error(&err);
 	}
 
 	btd_profile_register(&input_profile);
@@ -129,5 +139,5 @@
 	btd_profile_unregister(&input_profile);
 }
 
-BLUETOOTH_PLUGIN_DEFINE(input, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+BLUETOOTH_PLUGIN_DEFINE(input, VERSION, BLUETOOTH_PLUGIN_PRIORITY_HIGH,
 							input_init, input_exit)
diff --git a/profiles/input/server.c b/profiles/input/server.c
index eb3fcf8..3cabbe2 100644
--- a/profiles/input/server.c
+++ b/profiles/input/server.c
@@ -153,6 +153,7 @@
 	char address[18];
 	GError *gerr = NULL;
 	int ret;
+	bool must_unplug = false;
 
 	if (err) {
 		error("%s", err->message);
@@ -174,6 +175,13 @@
 	ba2str(&dst, address);
 	DBG("Incoming connection from %s on PSM %d", address, psm);
 
+	if (input_device_must_unplug(&src, &dst)) {
+		info("connect_event_cb: deliberately sending unplug to %s",
+			address);
+		must_unplug = true;
+		goto do_unplug;
+	}
+
 	ret = input_device_set_channel(&src, &dst, psm, chan);
 	if (ret == 0)
 		return;
@@ -185,8 +193,9 @@
 
 	error("Refusing input device connect: %s (%d)", strerror(-ret), -ret);
 
+do_unplug:
 	/* Send unplug virtual cable to unknown devices */
-	if (ret == -ENOENT && psm == L2CAP_PSM_HIDP_CTRL) {
+	if (must_unplug || (ret == -ENOENT && psm == L2CAP_PSM_HIDP_CTRL)) {
 		unsigned char unplug = 0x15;
 		int sk = g_io_channel_unix_get_fd(chan);
 		if (write(sk, &unplug, sizeof(unplug)) < 0)
diff --git a/profiles/oad/oad.c b/profiles/oad/oad.c
new file mode 100644
index 0000000..ad09863
--- /dev/null
+++ b/profiles/oad/oad.c
@@ -0,0 +1,915 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2014	Google, Inc.  (edjames@google.com)
+ *  Copyright (C) 2012	Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2012	Nordic Semiconductor Inc.
+ *  Copyright (C) 2012	Instituto Nokia de Tecnologia - INdT
+ *
+ *  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
+ *
+ */
+
+/* much lifted from input/hog.c */
+
+/* OAD - over air download of firmware to devices */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "lib/sdp.h"
+#include "lib/uuid.h"
+
+#include "src/adapter.h"
+#include "src/device.h"
+#include "src/log.h"
+#include "src/plugin.h"
+#include "src/profile.h"
+#include "src/service.h"
+#include "attrib/att.h"
+#include "attrib/gattrib.h"
+#include "attrib/gatt.h"
+#include "src/shared/util.h"
+
+#define OAD_UUID		"f000ffc0-0451-4000-b000-000000000000"
+#define OAD_IMG_IDENTITY_UUID	"f000ffc1-0451-4000-b000-000000000000"
+#define OAD_IMG_BLOCK_UUID	"f000ffc2-0451-4000-b000-000000000000"
+
+#define OAD_MAJOR_VERSION(x)	((x) >> 8)
+#define OAD_MINOR_VERSION(x)	((x) & 0xff)
+
+/* these codes may be returned instead of GP versions */
+#define OAD_VERSION_BAD		0xfefe
+#define OAD_VERSION_BUSY	0xffff
+
+/* remote has 2 processors, 2 firmwares. */
+#define OAD_GP_FIRMWARE_GFRM200		"gfrm200.gp.bin"
+#define OAD_TI_FIRMWARE_GFRM200		"gfrm200.ti.bin"
+#define OAD_GP_FIRMWARE_GFRM210		"gfrm210.gp.bin"
+#define OAD_TI_FIRMWARE_GFRM210		"gfrm210.ti.bin"
+
+/* remote checks in every 15 minutes */
+#define OAD_UPGRADE_DELAY_SECS		1		// delay between wake and upgrade check
+#define OAD_DELAY_NO_UPGRADE		(8*60*60)	// check for new ACS files (not likely)
+#define OAD_DELAY_NO_UPGRADE_DEBUG	150		// check faster when /tmp firmware exists
+#define OAD_DELAY_UPGRADING		60		// recheck in 1 minute (in case of 2 firmware update)
+
+#define OAD_HEADER_LEN			8		// on-disk header.  See OAD_Formatv7.xlsx
+
+enum OAD_Status {
+	OADSTATUS_None            = 0,
+	OADSTATUS_DataTransfer    = 1,
+	OADSTATUS_TransferSuccess = 2,
+	OADSTATUS_TIActive        = 0x81,	// maybe flashing
+	OADSTATUS_GPActive        = 0x82,	// maybe rebooting
+	OADSTATUS_CRCError        = 0xE0,
+	OADSTATUS_LVD             = 0xE1,	// low voltage detection
+	OADSTATUS_KeyPress        = 0xE2,	// user interrupted (not idle)
+	OADSTATUS_GPWrite         = 0xE3,	// GP write failed?
+	OADSTATUS_Bootloader      = 0xE5,	// Bootloader rejected?
+	OADSTATUS_ImageError      = 0xE6,	// Image format error?
+};
+
+enum OAD_Action {
+	OADACTION_GetRemoteInfo   = 0,
+	OADACTION_TransferOADData = 1,
+	OADACTION_StartUpdate     = 2,
+};
+
+/* firmware is upgraded in this order */
+enum OAD_FirmwareType {
+	OADFW_Unknown = -1,
+	OADFW_TI = 0,
+	OADFW_GP = 1,
+	OADFW_Total = 2,
+};
+
+struct oad_fwinfo {
+	uint16_t		version;
+	uint32_t		size;
+	uint8_t			header[OAD_HEADER_LEN];
+	char			file[MAXPATHLEN];
+};
+
+struct oad_service {
+	guint			attio_id;
+	uint16_t		ccc_handle;
+	uint16_t		value_handle;
+	int			notify_enabled;
+};
+
+struct oad_device {
+	uint16_t		id;
+	struct btd_device	*device;
+	GAttrib			*attrib;
+	struct gatt_primary	*oad_primary;
+
+	struct oad_service	identity;
+	struct oad_service	block;
+
+	time_t			nextCheck;
+	struct oad_fwinfo	disk[OADFW_Total];
+	struct oad_fwinfo	remote[OADFW_Total];
+
+	int			force_upgrade;
+	int			retries;
+	int			num_blocks;
+	int			progress;
+
+	uint8_t			header[OAD_HEADER_LEN];
+	int			fd;
+	guint			tid;			// Timer id to delay starting upgrade
+};
+
+struct oad_characteristic {
+	struct oad_device	*oaddev;
+	char			uuid[MAX_LEN_UUID_STR + 1];
+};
+
+static GSList *oad_devices = NULL;
+
+static struct oad_device *oad_new_device(struct btd_device *device, uint16_t id)
+{
+	struct oad_device *oaddev;
+
+	oaddev = g_try_new0(struct oad_device, 1);
+	if (!oaddev)
+		return NULL;
+
+	oaddev->id = id;
+	oaddev->device = btd_device_ref(device);
+	oaddev->fd = -1;
+
+	return oaddev;
+}
+
+static void oad_free_device(struct oad_device *oaddev)
+{
+	if (oaddev->fd != -1) {
+		close(oaddev->fd);
+		oaddev->fd = -1;
+	}
+	btd_device_unref(oaddev->device);
+	g_attrib_unref(oaddev->attrib);
+	g_free(oaddev->oad_primary);
+	g_free(oaddev);
+}
+
+static void oad_block_char_write_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
+{
+	struct oad_device *oaddev = user_data;
+
+	if (status != 0) {
+		error("OAD %s failed: %s", __func__, att_ecode2str(status));
+		return;
+	}
+	oaddev->retries = 0;	// successful write
+}
+
+static void oad_identity_char_write_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
+{
+	struct oad_device *oaddev = user_data;
+
+	if (status != 0) {
+		error("OAD %s failed: %s", __func__, att_ecode2str(status));
+		return;
+	}
+}
+
+static void oad_start_transfer(struct oad_device* oaddev)
+{
+	uint8_t attr_val[OAD_HEADER_LEN+1];		// + 1 byte action
+
+	memcpy(attr_val, oaddev->header, OAD_HEADER_LEN);
+	attr_val[OAD_HEADER_LEN] = OADACTION_TransferOADData;	/* start OAD image transfer */
+
+	gatt_write_char(oaddev->attrib, oaddev->identity.value_handle, attr_val,
+			sizeof(attr_val), oad_identity_char_write_cb, oaddev);
+}
+
+static void oad_block_notify_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
+{
+	struct oad_device *oaddev = user_data;
+	int retry = 0;
+
+
+	/* should be at least opcode (1b) + handle (7a00) */
+	if (len < 3) {
+		error("OAD Invalid PDU received");
+		return;
+	}
+
+	// 2 bytes block_num + status byte
+	if (len != 6) {
+		error("OAD block notify format error: expected 6 bytes, got %d", len);
+		return;
+	}
+
+	/* request for block */
+	uint16_t block_num = get_le16(pdu + 3);
+	enum OAD_Status status = (enum OAD_Status) pdu[5];
+
+	switch (status) {
+	case OADSTATUS_DataTransfer:		// send a block
+		{
+			uint8_t block[18];	// 2 bytes block_num + 16 data bytes
+
+			memset(block, 0, sizeof block);
+			put_le16(block_num, block);
+
+			if (lseek(oaddev->fd, 16 * block_num, SEEK_SET) < 0) {
+				perror("OAD firmware seek");
+				goto error;
+			}
+			if (read(oaddev->fd, block+2, sizeof block - 2) < 0) {
+				perror("OAD firmware read");
+				goto error;
+			}
+
+			int progress = 0;
+			if (oaddev->num_blocks > 0) progress = 100 * block_num / oaddev->num_blocks;
+			if (oaddev->progress != progress) {
+				oaddev->progress = progress;
+				DBG("OAD sending block %d (%d%%)", block_num, progress);
+			}
+			gatt_write_char(oaddev->attrib, oaddev->block.value_handle, block,
+				sizeof(block), oad_block_char_write_cb, oaddev);
+		}
+		break;
+	case OADSTATUS_TransferSuccess:		// OK, successful transfer
+		{
+			DBG("OAD firmware transfer successful");
+			close(oaddev->fd);
+			oaddev->fd = -1;
+
+			uint8_t attr_val[OAD_HEADER_LEN+1];		// + 1 byte action
+			memcpy(attr_val, oaddev->header, OAD_HEADER_LEN);
+			attr_val[OAD_HEADER_LEN] = OADACTION_StartUpdate;		/* commit fw image */
+			DBG("OAD committing firmware");
+			gatt_write_char(oaddev->attrib, oaddev->identity.value_handle, attr_val,
+				sizeof(attr_val), oad_identity_char_write_cb, oaddev);
+		}
+		break;
+	case OADSTATUS_GPActive:
+		DBG("OAD GP is active (good)");
+		break;
+	case OADSTATUS_TIActive:
+		DBG("OAD TI is active (good)");
+		break;
+
+	case OADSTATUS_CRCError:
+		DBG("OAD firmware transfer checksum error");
+		goto error;
+	case OADSTATUS_LVD:
+		DBG("OAD firmware upgrade low battery error");
+		goto error;
+	case OADSTATUS_KeyPress:
+		DBG("OAD firmware upgrade user interrupted");
+		goto error;
+	case OADSTATUS_GPWrite:
+		DBG("OAD firmware upgrade GP write error");
+		goto error;
+	case OADSTATUS_Bootloader:
+		DBG("OAD firmware upgrade Bootloader error");
+		goto error;
+	case OADSTATUS_ImageError:
+		DBG("OAD firmware upgrade Image error");
+		goto error;
+
+	default:
+		/* 0xe0 or greater appear to be unrecoverable errors */
+		if (status >= OADSTATUS_CRCError) {
+			DBG("OAD unexpected block notify error 0x%02x", status);
+			goto error;
+		}
+		DBG("OAD unexpected block notify status 0x%02x (ignoring)", status);
+		break;
+	}
+	/* success so far*/
+	return;
+
+error:
+	DBG("OAD upgrade stopping due to error %d", status);
+	close(oaddev->fd);
+	oaddev->fd = -1;
+}
+
+static time_t oad_wallclock(void)
+{
+	struct timespec ts;
+
+	if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
+		return 0;
+	}
+	return ts.tv_sec;
+}
+
+static int oad_is_time_for_upgrade_check(struct oad_device* oaddev)
+{
+	time_t now = oad_wallclock();
+
+	if (now < oaddev->nextCheck) {
+		int next = oaddev->nextCheck - now;
+		DBG("OAD not time for upgrade check yet (%d seconds left)", next);
+		return 0;
+	}
+	DBG("OAD time for upgrade check");
+	return 1;
+}
+
+static void oad_get_firmware_info(struct oad_device *oaddev, const char* file)
+{
+	uint8_t	header[OAD_HEADER_LEN];
+
+	int fd = open(file, O_RDONLY);
+	if (fd < 0) {
+		error("OAD open: %s: %s", file, strerror(errno));
+		return;
+	}
+	int len = read(fd, header, sizeof header);
+	if (len < 0) {
+		error("OAD read: %s: %s", file, strerror(errno));
+		close(fd);
+		return;
+	}
+	if (len < sizeof header) {
+		error("OAD short read: %s: wanted %lu, got %d", file, sizeof header, len);
+		close(fd);
+		return;
+	}
+	close(fd);
+	fd = -1;
+
+	/* See OAD_Formatv7.xlsx, 'Image Info' tab.  8 bytes are:
+	 * 2 bytes checksum
+	 * 2 bytes version
+	 * 3 bytes size
+	 * 1 byte id: "T" TI image, "G" GP image
+	 */
+	uint16_t version = get_le16(header + 2);
+	uint32_t size = get_le32(header+4) & 0x00ffffff;
+	int id = header[7];
+	enum OAD_FirmwareType type;
+
+	switch(id) {
+	case 'T':
+	case 'U':
+		type = OADFW_TI;
+		break;
+	case 'G':
+	case 'H':
+		type = OADFW_GP;
+		break;
+	default:
+		type = OADFW_Unknown;
+		break;
+	}
+	if (type == OADFW_Unknown) {
+		error("OAD unknown firmware id '%c': file=%s version=%d.%d",
+			id, file, OAD_MAJOR_VERSION(version), OAD_MINOR_VERSION(version));
+		return;
+	}
+	int index = (int)type;
+	if (version <= oaddev->disk[index].version) {
+		error("OAD ignoring older firmware: file=%s type=%d version=%d.%d",
+			file, type, OAD_MAJOR_VERSION(version), OAD_MINOR_VERSION(version));
+		return;
+	}
+	if (strlen(file) >= sizeof oaddev->disk[index].file) {
+		error("OAD firmware file name too long: %s", file);
+		return;
+	}
+
+	oaddev->disk[index].version = version;
+	oaddev->disk[index].size = size;
+	memcpy(oaddev->disk[index].header, header, sizeof oaddev->disk[index].header);
+	strncpy(oaddev->disk[index].file, file, sizeof oaddev->disk[index].file);
+	oaddev->disk[index].file[sizeof oaddev->disk[index].file - 1] = '\0';
+}
+
+static const char* oad_firmware_type_string(enum OAD_FirmwareType type)
+{
+	switch (type) {
+	case OADFW_GP:	return "GP";
+	case OADFW_TI:	return "TI";
+	}
+	return "Unknown";
+}
+
+static void oad_find_firmware(struct oad_device *oaddev)
+{
+	char name[MAX_NAME_LENGTH + 1] = "";
+	const char *tmp_gp;
+	const char *tmp_ti;
+	const char *etc_gp;
+	const char *etc_ti;
+
+	device_get_name(oaddev->device, name, sizeof(name));
+	if (g_strcmp0(name, "GFRM200") == 0) {
+		tmp_gp = "/tmp/" OAD_GP_FIRMWARE_GFRM200;
+		tmp_ti = "/tmp/" OAD_TI_FIRMWARE_GFRM200;
+		etc_gp = "/etc/remote/" OAD_GP_FIRMWARE_GFRM200;
+		etc_ti = "/etc/remote/" OAD_TI_FIRMWARE_GFRM200;
+	} else if (g_strcmp0(name, "GFRM201") == 0 ||
+			g_strcmp0(name, "GFRM210") == 0) {
+		tmp_gp = "/tmp/" OAD_GP_FIRMWARE_GFRM210;
+		tmp_ti = "/tmp/" OAD_TI_FIRMWARE_GFRM210;
+		etc_gp = "/etc/remote/" OAD_GP_FIRMWARE_GFRM210;
+		etc_ti = "/etc/remote/" OAD_TI_FIRMWARE_GFRM210;
+	} else {
+		error("OAD unknown model %s, skipping", name);
+	}
+
+	memset(oaddev->disk, 0, sizeof oaddev->disk);
+
+	/* check in tmp first, to make debugging easier */
+	if (access(tmp_gp, R_OK) == 0 || access(tmp_ti, R_OK) == 0) {
+		DBG("OAD found firmware in /tmp, ignoring /etc/remote, enabling downgrade.");
+		oaddev->force_upgrade = 1;	// allow downgrade
+		oad_get_firmware_info(oaddev, tmp_gp);
+		oad_get_firmware_info(oaddev, tmp_ti);
+	} else {
+		oaddev->force_upgrade = 0;
+		oad_get_firmware_info(oaddev, etc_gp);
+		oad_get_firmware_info(oaddev, etc_ti);
+	}
+
+	int i;
+	for (i = 0; i < OADFW_Total; i++) {
+		enum OAD_FirmwareType type = (enum OAD_FirmwareType) i;
+		const char* typeStr = oad_firmware_type_string(type);
+		if (strlen(oaddev->disk[type].file) == 0) {
+			error("OAD no %s firmware found on disk", typeStr);
+		} else {
+			DBG("OAD found %s firmware: file=%s version=%d.%d",
+				typeStr, oaddev->disk[i].file,
+				OAD_MAJOR_VERSION(oaddev->disk[i].version),
+				OAD_MINOR_VERSION(oaddev->disk[i].version));
+		}
+	}
+}
+
+static void oad_check_for_upgrade(struct oad_device *oaddev)
+{
+	if (!oad_is_time_for_upgrade_check(oaddev)) {
+		return;
+	}
+
+	oad_find_firmware(oaddev);
+
+	struct oad_fwinfo* fp = NULL;
+	int i;
+	for (i = 0; i < OADFW_Total; i++) {
+		enum OAD_FirmwareType type = (enum OAD_FirmwareType) i;
+		const char* typeStr = oad_firmware_type_string(type);
+		if (strlen(oaddev->disk[i].file) == 0) {
+			continue;
+		}
+		if (oaddev->disk[i].version == oaddev->remote[i].version ||
+		    (!oaddev->force_upgrade &&
+		     oaddev->disk[i].version < oaddev->remote[i].version &&
+		     oaddev->remote[i].version != OAD_VERSION_BAD)) {
+			DBG("OAD %s firmware is up to date", typeStr);
+		} else {
+			fp = &oaddev->disk[i];
+			break;
+		}
+	}
+
+	if (fp == NULL) {
+		int next = oaddev->force_upgrade
+				?  OAD_DELAY_NO_UPGRADE_DEBUG
+				: OAD_DELAY_NO_UPGRADE;
+		DBG("OAD next check is in %d seconds", next);
+		oaddev->nextCheck = oad_wallclock() + next;
+		return;
+	}
+	oaddev->nextCheck = oad_wallclock() + OAD_DELAY_UPGRADING;
+
+	DBG("OAD starting upgrade with %s", fp->file);
+
+	if (oaddev->fd != -1) {
+		close(oaddev->fd);
+	}
+	oaddev->fd = open(fp->file, O_RDONLY);
+	if (oaddev->fd < 0) {
+		error("OAD open: %s: %s", fp->file, strerror(errno));
+		return;
+	}
+	if (oaddev->tid) {
+		g_source_remove(oaddev->tid);
+		oaddev->tid = 0;
+	}
+
+	oaddev->progress = 0;
+	oaddev->retries = 0;
+	oaddev->num_blocks = (fp->size + 15) / 16;
+	memcpy(oaddev->header, fp->header, sizeof oaddev->header);
+	oad_start_transfer(oaddev);
+}
+
+static void oad_identity_notify_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
+{
+	struct oad_device *oaddev = user_data;
+
+
+	/* should be at least opcode (1b) + handle (76 00) */
+	if (len < 3) {
+		error("OAD Invalid PDU received");
+		return;
+	}
+
+	/* See OAD_Formatv7.xlsx, 'Image Info' tab.  10 bytes are:
+	 *	char gp_version[2];	// 09 00 is 0.9
+	 * 	char gp_size[3];	// 00 00 01 is 0x010000 == 64k
+	 *	char ti_version[2];	// 2a 00 is 0.42
+	 * 	char ti_size[3];	// 00 00 01 is 0x010000 == 64k
+	 */
+
+	if (len != 13) {
+		error("OAD identity data format error: expected 13 bytes, got %d", len);
+		return;
+	}
+
+	oaddev->remote[OADFW_GP].version = get_le16(pdu+3);
+	oaddev->remote[OADFW_GP].size = get_le32(pdu+5) & 0x00ffffff;
+
+	oaddev->remote[OADFW_TI].version = get_le16(pdu+8);
+	unsigned char size[4];
+	memcpy(size, pdu+10, 3);
+	size[3] = 0;				// need 4 bytes for get_le32
+	oaddev->remote[OADFW_TI].size = get_le32(size) & 0x00ffffff;
+
+	int i;
+	for (i = 0; i < OADFW_Total; i++) {
+		enum OAD_FirmwareType type = (enum OAD_FirmwareType) i;
+		const char* typeStr = oad_firmware_type_string(type);
+		DBG("OAD %s firmware on remote: version=%d.%d",
+			typeStr, OAD_MAJOR_VERSION(oaddev->remote[i].version),
+			OAD_MINOR_VERSION(oaddev->remote[i].version));
+	}
+	oad_check_for_upgrade(oaddev);
+}
+
+static void oad_check_fwversion(struct oad_device *oaddev)
+{
+	uint8_t attr_val[9];	// 8 byte header + 1 command byte (all zeros)
+
+	if (!oaddev->identity.notify_enabled || !oaddev->block.notify_enabled) {
+		return;
+	}
+
+	DBG("OAD sending fw version request");
+	memset(attr_val, 0, sizeof attr_val);
+	attr_val[8] = OADACTION_GetRemoteInfo;		/* trigger fw version notify */
+	gatt_write_char(oaddev->attrib, oaddev->identity.value_handle, attr_val,
+		sizeof(attr_val), oad_identity_char_write_cb, oaddev);
+}
+
+static void oad_ccc_char_write_identity_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
+{
+	struct oad_device *oaddev = user_data;
+
+	if (status != 0) {
+		error("OAD %s failed: %s", __func__, att_ecode2str(status));
+		return;
+	}
+	oaddev->identity.notify_enabled = 1;
+
+	oad_check_fwversion(oaddev);
+}
+
+static void oad_ccc_char_write_block_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
+{
+	struct oad_device *oaddev = user_data;
+
+	if (status != 0) {
+		error("OAD %s failed: %s", __func__, att_ecode2str(status));
+		return;
+	}
+	oaddev->block.notify_enabled = 1;
+
+	oad_check_fwversion(oaddev);
+}
+
+static void oad_enable_notify(struct oad_device* oaddev)
+{
+	if (oaddev->identity.ccc_handle == 0 || oaddev->block.ccc_handle == 0 ||
+	    oaddev->identity.value_handle == 0 || oaddev->block.value_handle == 0) {
+	    	error("OAD discovery not complete, cannot enable notifiers");
+		return;
+	}
+
+	oaddev->identity.attio_id =
+		g_attrib_register(oaddev->attrib,
+			ATT_OP_HANDLE_NOTIFY, oaddev->identity.value_handle,
+			oad_identity_notify_handler, oaddev, NULL);
+	oaddev->block.attio_id =
+		g_attrib_register(oaddev->attrib,
+			ATT_OP_HANDLE_NOTIFY, oaddev->block.value_handle,
+			oad_block_notify_handler, oaddev, NULL);
+
+	uint8_t attr_val[2];
+	put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, attr_val);
+	DBG("OAD enabling notify for OAD_IMG_IDENTITY_UUID");
+	gatt_write_char(oaddev->attrib, oaddev->identity.ccc_handle, attr_val,
+		sizeof(attr_val), oad_ccc_char_write_identity_cb, oaddev);
+	DBG("OAD enabling notify for OAD_IMG_BLOCK_UUID");
+	gatt_write_char(oaddev->attrib, oaddev->block.ccc_handle, attr_val,
+		sizeof(attr_val), oad_ccc_char_write_block_cb, oaddev);
+}
+
+static gboolean oad_upgrade_timer(gpointer data)
+{
+	struct oad_device *oaddev = data;
+
+	g_source_remove(oaddev->tid);
+	oaddev->tid = 0;
+
+	DBG("OAD timer fired");
+	oad_enable_notify(oaddev);
+}
+
+static void oad_desc_discovered_cb(uint8_t status, GSList *descs, void* user_data)
+{
+	struct oad_characteristic *ch = user_data;
+	struct oad_device *oaddev = ch->oaddev;
+	struct GSList *list = NULL;
+
+	if (status != 0) {
+		error("OAD %s failed: %s", __func__, att_ecode2str(status));
+		goto done;
+	}
+
+	for (; descs; descs = descs->next) {
+		struct gatt_desc *desc = descs->data;
+
+		if (desc->uuid16 != GATT_CLIENT_CHARAC_CFG_UUID)
+			continue;
+
+		if (g_strcmp0(ch->uuid, OAD_IMG_IDENTITY_UUID) == 0) {
+			DBG("OAD found OAD_IMG_IDENTITY_UUID ccc_handle=0x%04x", desc->handle);
+			oaddev->identity.ccc_handle = desc->handle;
+
+		} else if (g_strcmp0(ch->uuid, OAD_IMG_BLOCK_UUID) == 0) {
+			DBG("OAD found OAD_IMG_BLOCK_UUID ccc_handle=0x%04x", desc->handle);
+			oaddev->block.ccc_handle = desc->handle;
+		}
+	}
+	if (oaddev->identity.ccc_handle && oaddev->block.ccc_handle)
+		oaddev->tid = g_timeout_add_seconds(OAD_UPGRADE_DELAY_SECS, oad_upgrade_timer, oaddev);
+
+done:
+	g_free(ch);
+}
+
+static void oad_discover_desc(struct oad_device *oaddev, struct gatt_char *c, struct gatt_char *c_next)
+{
+	struct gatt_primary *prim = oaddev->oad_primary;
+	struct oad_characteristic *ch;
+	uint16_t start, end;
+
+	start = c->value_handle + 1;
+
+	if (c_next != NULL) {
+		if (start == c_next->handle)
+			return;
+		end = c_next->handle - 1;
+	} else if (c->value_handle != prim->range.end) {
+		end = prim->range.end;
+	} else {
+		return;
+	}
+
+	ch = g_new0(struct oad_characteristic, 1);
+	ch->oaddev = oaddev;
+	memcpy(ch->uuid, c->uuid, sizeof(c->uuid));
+
+	DBG("OAD discovering descriptors start=0x%04x end=0x%04x", start, end);
+
+	gatt_discover_desc(oaddev->attrib, start, end, NULL, oad_desc_discovered_cb, ch);
+}
+
+static void oad_char_discovered_cb(uint8_t status, GSList *chars, void* user_data)
+{
+	struct oad_device *oaddev = user_data;
+	struct gatt_primary *prim = oaddev->oad_primary;
+	bt_uuid_t img_identity_uuid, img_block_uuid;
+	GSList *l;
+	uint16_t info_handle = 0, proto_mode_handle = 0;
+
+	DBG("OAD inspecting characteristics");
+	if (status != 0) {
+		error("OAD %s failed: %s", __func__, att_ecode2str(status));
+		return;
+	}
+
+	for (l = chars; l; l = g_slist_next(l)) {
+		struct gatt_char *chr, *next;
+		bt_uuid_t uuid;
+		uint16_t start, end;
+
+		chr = l->data;
+		next = l->next ? l->next->data : NULL;
+
+		DBG("OAD 0x%04x UUID: %s properties: %02x", chr->handle, chr->uuid, chr->properties);
+
+
+		start = chr->value_handle + 1;
+		end = (next ? next->handle - 1 : prim->range.end);
+
+		if (g_strcmp0(chr->uuid, OAD_IMG_IDENTITY_UUID) == 0) {
+			oaddev->identity.value_handle = chr->value_handle;
+			DBG("OAD found OAD_IMG_IDENTITY_UUID value_handle=0x%04x", chr->value_handle);
+			oad_discover_desc(oaddev, chr, next);
+		} else if (g_strcmp0(chr->uuid, OAD_IMG_BLOCK_UUID) == 0) {
+			oaddev->block.value_handle = chr->value_handle;
+			DBG("OAD found OAD_IMG_BLOCK_UUID value_handle=0x%04x", chr->value_handle);
+			oad_discover_desc(oaddev, chr, next);
+		}
+
+	}
+}
+
+static int oad_accept(struct btd_service *service)
+{
+	struct oad_device *oaddev = btd_service_get_user_data(service);
+	struct gatt_primary *prim = oaddev->oad_primary;
+	struct btd_device *device = btd_service_get_device(service);
+	GAttrib *attrib = btd_device_get_attrib(device);
+
+	DBG("OAD connected");
+
+	oaddev->attrib = g_attrib_ref(attrib);
+	oaddev->identity.notify_enabled = 0;
+	oaddev->block.notify_enabled = 0;
+
+	btd_service_connecting_complete(service, 0);
+
+	if (oaddev->identity.ccc_handle == 0 || oaddev->block.ccc_handle == 0 ||
+	    oaddev->identity.value_handle == 0 || oaddev->block.value_handle == 0) {
+		DBG("OAD discovering characteristics");
+
+		gatt_discover_char(oaddev->attrib, prim->range.start, prim->range.end, NULL,
+			oad_char_discovered_cb, oaddev);
+	} else {
+		if (oad_is_time_for_upgrade_check(oaddev)) {
+			oad_enable_notify(oaddev);
+		}
+	}
+
+	return 0;
+}
+
+static int oad_disconnect(struct btd_service *service)
+{
+	struct oad_device *oaddev = btd_service_get_user_data(service);
+
+	DBG("OAD disconnected");
+
+	if (oaddev->identity.attio_id > 0) {
+		g_attrib_unregister(oaddev->attrib, oaddev->identity.attio_id);
+		oaddev->identity.attio_id = 0;
+	}
+	if (oaddev->block.attio_id > 0) {
+		g_attrib_unregister(oaddev->attrib, oaddev->block.attio_id);
+		oaddev->block.attio_id = 0;
+	}
+	if (oaddev->attrib) {
+		g_attrib_unref(oaddev->attrib);
+		oaddev->attrib = NULL;
+	}
+
+	btd_service_disconnecting_complete(service, 0);
+
+	return 0;
+}
+
+static struct oad_device *oad_register_device(struct btd_device *device, struct gatt_primary *prim)
+{
+	struct oad_device *oaddev;
+	GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_NVAL;
+	GIOChannel *io;
+
+	oaddev = oad_new_device(device, prim->range.start);
+	if (!oaddev)
+		return NULL;
+
+	oaddev->oad_primary = g_memdup(prim, sizeof(*prim));
+
+	return oaddev;
+}
+
+static int oad_unregister_device(struct oad_device *oaddev)
+{
+	if (oaddev->tid) {
+		g_source_remove(oaddev->tid);
+		oaddev->tid = 0;
+	}
+	oad_free_device(oaddev);
+
+	return 0;
+}
+
+static int oad_probe(struct btd_service *service)
+{
+	struct btd_device *device = btd_service_get_device(service);
+	const char *path = device_get_path(device);
+	GSList *primaries, *l;
+
+	DBG("OAD path %s", path);
+
+	primaries = btd_device_get_primaries(device);
+	if (primaries == NULL)
+		return -EINVAL;
+
+	for (l = primaries; l; l = g_slist_next(l)) {
+		struct gatt_primary *prim = l->data;
+		struct oad_device *oaddev;
+
+		DBG("OAD uuid=%s", prim->uuid);
+		if (strcmp(prim->uuid, OAD_UUID) != 0)
+			continue;
+
+		DBG("OAD matched OAD uuid");
+		oaddev = oad_register_device(device, prim);
+		if (oaddev == NULL)
+			continue;
+
+		btd_service_set_user_data(service, oaddev);
+		oad_devices = g_slist_append(oad_devices, oaddev);
+	}
+
+	return 0;
+}
+
+static void oad_remove_device(gpointer a, gpointer b)
+{
+	struct oad_device *oaddev = a;
+	struct btd_device *device = b;
+
+	if (oaddev->device != device)
+		return;
+
+	oad_devices = g_slist_remove(oad_devices, oaddev);
+	oad_unregister_device(oaddev);
+}
+
+static void oad_remove(struct btd_service *service)
+{
+	struct btd_device *device = btd_service_get_device(service);
+	const char *path = device_get_path(device);
+
+	DBG("OAD path %s", path);
+
+	g_slist_foreach(oad_devices, oad_remove_device, device);
+}
+
+static struct btd_profile oad_profile = {
+	.name		= "OAD",
+	.remote_uuid	= OAD_UUID,
+	.device_probe	= oad_probe,
+	.device_remove	= oad_remove,
+	.accept		= oad_accept,
+	.disconnect	= oad_disconnect,
+	.auto_connect	= true,
+};
+
+static int oad_init(void)
+{
+	int err;
+
+	return btd_profile_register(&oad_profile);
+}
+
+static void oad_exit(void)
+{
+	btd_profile_unregister(&oad_profile);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(oad, VERSION, BLUETOOTH_PLUGIN_PRIORITY_LOW, oad_init, oad_exit)
diff --git a/src/adapter.c b/src/adapter.c
index 5d7a9a9..e7cb86b 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -6649,6 +6649,7 @@
 							adapter->dev_id);
 
 	adapter->pair_device_timeout = 0;
+	adapter->pair_device_id = 0;
 
 	adapter_cancel_bonding(adapter, &data->bdaddr, data->addr_type);
 
@@ -6782,6 +6783,11 @@
 
 	device = btd_adapter_find_device(adapter, &addr->bdaddr, addr->type);
 	if (device) {
+		DBG("device %p paired %d bonded %d bonding %d", device,
+		    device_is_paired(device, addr->type),
+		    device_is_bonded(device, addr->type),
+		    device_is_bonding(device, NULL));
+
 		adapter_remove_connection(adapter, device, addr->type);
 		disconnect_notify(device, reason);
 	}
@@ -7697,6 +7703,11 @@
 		return;
 	}
 
+	DBG("device %p paired %d bonded %d bonding %d", device,
+	    device_is_paired(device, ev->addr.type),
+	    device_is_bonded(device, ev->addr.type),
+	    device_is_bonding(device, NULL));
+
 	memset(&eir_data, 0, sizeof(eir_data));
 	if (eir_len > 0)
 		eir_parse(&eir_data, ev->eir, eir_len);
@@ -8157,6 +8168,11 @@
 
 	if (adapter->current_settings & MGMT_SETTING_POWERED)
 		adapter_start(adapter);
+	else
+		set_mode(adapter, MGMT_OP_SET_POWERED, 0x01);
+
+	set_mode(adapter, MGMT_OP_SET_CONNECTABLE, 0x01);
+	set_mode(adapter, MGMT_OP_SET_FAST_CONNECTABLE, 0x01);
 
 	return;
 
@@ -8189,6 +8205,16 @@
 		return;
 	}
 
+	/*
+	 * allow an interface to be ignored.
+	 */
+	char ignore[64] = { 0 };
+	snprintf(ignore, sizeof ignore, "IGNORE_hci%d", index);
+	if (getenv(ignore) != NULL) {
+		warn("Ignoring index due to environment variable: %s", ignore);
+		return;
+	}
+
 	adapter = btd_adapter_new(index);
 	if (!adapter) {
 		btd_error(index,
diff --git a/src/attrib-server.c b/src/attrib-server.c
index 4439c27..658db55 100644
--- a/src/attrib-server.c
+++ b/src/attrib-server.c
@@ -997,7 +997,8 @@
 	size_t vlen;
 	uint8_t *value = g_attrib_get_buffer(channel->attrib, &vlen);
 
-	DBG("op 0x%02x", ipdu[0]);
+	// suppress very common debug message
+	// DBG("op 0x%02x", ipdu[0]);
 
 	if (len > vlen) {
 		error("Too much data on ATT socket");
@@ -1306,9 +1307,11 @@
 								atval, 5);
 
 	/* GAP service: device name attribute */
+	const char* name = btd_adapter_get_name(server->adapter);
 	bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME);
 	attrib_db_add_new(server, server->name_handle, &uuid, ATT_NONE,
-						ATT_NOT_PERMITTED, NULL, 0);
+						ATT_NOT_PERMITTED, name,
+						strlen(name));
 
 	/* GAP service: device appearance characteristic */
 	server->appearance_handle = 0x0008;
diff --git a/src/device.c b/src/device.c
index 99454a7..6ed9509 100644
--- a/src/device.c
+++ b/src/device.c
@@ -243,6 +243,7 @@
 	gboolean	auto_connect;
 	gboolean	disable_auto_connect;
 	gboolean	general_connect;
+	gboolean	unplug;
 
 	bool		legacy;
 	int8_t		rssi;
@@ -405,6 +406,9 @@
 	g_key_file_set_boolean(key_file, "General", "Blocked",
 							device->blocked);
 
+	g_key_file_set_boolean(key_file, "General", "Unplug",
+							device->unplug);
+
 	if (device->uuids) {
 		GSList *l;
 		int i;
@@ -1060,6 +1064,46 @@
 	set_blocked(id, b, data);
 }
 
+static gboolean dev_property_get_unplug(const GDBusPropertyTable *property,
+					DBusMessageIter *iter, void *data)
+{
+	struct btd_device *device = data;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN,
+							&device->unplug);
+
+	return TRUE;
+}
+
+static void set_unplug(GDBusPendingPropertySet id, gboolean value,
+					void *data)
+{
+	struct btd_device *device = data;
+
+	btd_device_set_unplug(device, value);
+
+	g_dbus_pending_property_success(id);
+}
+
+static void dev_property_set_unplug(const GDBusPropertyTable *property,
+					DBusMessageIter *value,
+					GDBusPendingPropertySet id, void *data)
+{
+	dbus_bool_t b;
+
+	if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) {
+		g_dbus_pending_property_error(id,
+					ERROR_INTERFACE ".InvalidArguments",
+					"Invalid arguments in method call");
+		return;
+	}
+
+	dbus_message_iter_get_basic(value, &b);
+
+	set_unplug(id, b, data);
+}
+
+
 static gboolean dev_property_get_connected(const GDBusPropertyTable *property,
 					DBusMessageIter *iter, void *data)
 {
@@ -2548,6 +2592,7 @@
 	{ "Paired", "b", dev_property_get_paired },
 	{ "Trusted", "b", dev_property_get_trusted, dev_property_set_trusted },
 	{ "Blocked", "b", dev_property_get_blocked, dev_property_set_blocked },
+	{ "Unplug", "b", dev_property_get_unplug, dev_property_set_unplug },
 	{ "LegacyPairing", "b", dev_property_get_legacy },
 	{ "RSSI", "n", dev_property_get_rssi, NULL, dev_property_exists_rssi },
 	{ "Connected", "b", dev_property_get_connected },
@@ -2891,6 +2936,9 @@
 	if (blocked)
 		device_block(device, FALSE);
 
+	device->unplug = g_key_file_get_boolean(key_file, "General",
+						"Unplug", NULL);
+
 	/* Load device profile list */
 	uuids = g_key_file_get_string_list(key_file, "General", "Services",
 						NULL, NULL);
@@ -5168,6 +5216,29 @@
 					DEVICE_INTERFACE, "Trusted");
 }
 
+gboolean btd_device_get_unplug(struct btd_device *device)
+{
+	return device->unplug;
+}
+
+void btd_device_set_unplug(struct btd_device *device, gboolean unplug)
+{
+	if (!device)
+		return;
+
+	if (device->unplug == unplug)
+		return;
+
+	DBG("unplug %d", unplug);
+
+	device->unplug = unplug;
+
+	store_device_info(device);
+
+	g_dbus_emit_property_changed(dbus_conn, device->path,
+					DEVICE_INTERFACE, "Unplug");
+}
+
 void device_set_bonded(struct btd_device *device, uint8_t bdaddr_type)
 {
 	if (!device)
diff --git a/src/device.h b/src/device.h
index 3cab366..bc5535c 100644
--- a/src/device.h
+++ b/src/device.h
@@ -163,3 +163,6 @@
 
 void btd_device_init(void);
 void btd_device_cleanup(void);
+
+gboolean btd_device_get_unplug(struct btd_device *device);
+void btd_device_set_unplug(struct btd_device *device, gboolean value);
diff --git a/src/main.c b/src/main.c
index bcc1e6f..21ca830 100644
--- a/src/main.c
+++ b/src/main.c
@@ -599,6 +599,7 @@
 	guint signal, watchdog;
 	const char *watchdog_usec;
 
+	setvbuf(stdout, NULL, _IOLBF, 0);
 	init_defaults();
 
 	context = g_option_context_new(NULL);
diff --git a/test/gfiber-agent b/test/gfiber-agent
new file mode 100755
index 0000000..ca9929f
--- /dev/null
+++ b/test/gfiber-agent
@@ -0,0 +1,229 @@
+#!/usr/bin/python -u
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import dbus
+import dbus.exceptions
+import dbus.service
+import dbus.mainloop.glib
+import os
+import subprocess
+import sys
+try:
+  from gi.repository import GObject
+except ImportError:
+  import gobject as GObject
+
+BLUEZ_BUS = 'org.bluez'
+BLUEZ_ROOT_OBJ = '/org/bluez'
+AGENT_INTF = 'org.bluez.Agent1'
+AGENT_MGR_INTF = 'org.bluez.AgentManager1'
+DEVICE_INTF = 'org.bluez.Device1'
+OBJECT_MGR_INTF = 'org.freedesktop.DBus.ObjectManager'
+PROPERTY_INTF = 'org.freedesktop.DBus.Properties'
+AGENT_PATH = '/com/google/gfiber/agent'
+AGENT_CAPABILITY = 'NoInputNoOutput'
+DEV_NAME_GFRM100 = 'GFRM100'
+DEV_NAME_GFRM200 = 'GFRM200'
+DEV_NAME_GFRM210 = 'GFRM210'
+# Early GFRM210s identified themselves as GFRM201
+DEV_NAME_GFRM201 = 'GFRM201'
+DEV_NAME_TIARC = 'HID AdvRemote'
+DEV_OUI_TIARC = '90:59:AF'
+
+
+g = {'bus': None, 'agent': None, 'adaptdir': None}
+
+
+def dev_trusted(path):
+	bus = g['bus']
+	obj = bus.get_object(BLUEZ_BUS, path)
+	props = dbus.Interface(obj, PROPERTY_INTF)
+	props.Set(DEVICE_INTF, "Trusted", True)
+
+class Rejected(dbus.DBusException):
+	_dbus_error_name = "org.bluez.Error.Rejected"
+
+class Agent(dbus.service.Object):
+	exit_on_release = True
+
+	def set_exit_on_release(self, exit_on_release):
+		self.exit_on_release = exit_on_release
+
+	@dbus.service.method(AGENT_INTF, in_signature="", out_signature="")
+	def Release(self):
+		print("Release")
+		if self.exit_on_release:
+			mainloop.quit()
+
+	@dbus.service.method(AGENT_INTF, in_signature="os", out_signature="")
+	def AuthorizeService(self, device, uuid):
+		print("AuthorizeService (%s, %s)" % (device, uuid))
+		raise Rejected("Not implemented")
+
+	@dbus.service.method(AGENT_INTF, in_signature="o", out_signature="s")
+	def RequestPinCode(self, device):
+		print("RequestPinCode (%s)" % (device))
+		dev_trusted(device)
+		return '0000'
+
+	@dbus.service.method(AGENT_INTF, in_signature="o", out_signature="u")
+	def RequestPasskey(self, device):
+		print("RequestPasskey (%s)" % (device))
+		dev_trusted(device)
+		return dbus.UInt32('0000')
+
+	@dbus.service.method(AGENT_INTF, in_signature="ouq", out_signature="")
+	def DisplayPasskey(self, device, passkey, entered):
+		print("DisplayPasskey (%s, %06u entered %u)" %
+						(device, passkey, entered))
+
+	@dbus.service.method(AGENT_INTF, in_signature="os", out_signature="")
+	def DisplayPinCode(self, device, pincode):
+		print("DisplayPinCode (%s, %s)" % (device, pincode))
+
+	@dbus.service.method(AGENT_INTF, in_signature="ou", out_signature="")
+	def RequestConfirmation(self, device, passkey):
+		print("RequestConfirmation (%s, %06d)" % (device, passkey))
+		raise Rejected("Not implemented")
+
+	@dbus.service.method(AGENT_INTF, in_signature="o", out_signature="")
+	def RequestAuthorization(self, device):
+		print("RequestAuthorization (%s)" % (device))
+		raise Rejected("Not implemented")
+
+	@dbus.service.method(AGENT_INTF, in_signature="", out_signature="")
+	def Cancel(self):
+		print("Cancel")
+
+def register_agent():
+	bus = g['bus']
+	obj = bus.get_object(BLUEZ_BUS, BLUEZ_ROOT_OBJ)
+	mgr = dbus.Interface(obj, AGENT_MGR_INTF)
+	mgr.RegisterAgent(AGENT_PATH, AGENT_CAPABILITY)
+	mgr.RequestDefaultAgent(AGENT_PATH)
+	print("Agent registered")
+
+def unregister_agent():
+	bus = g['bus']
+	obj = bus.get_object(BLUEZ_BUS, BLUEZ_ROOT_OBJ)
+	mgr = dbus.Interface(obj, AGENT_MGR_INTF)
+	mgr.UnregisterAgent(AGENT_PATH)
+	print("Agent unregistered")
+
+def set_le_adv_ind_filter(adap_path, dev_addr):
+	name = adap_path.split('/')[-1]
+	path = "/sys/kernel/debug/bluetooth/%s/le_adv_ind_filter" % name
+	try:
+		f = open(path, 'w')
+		f.write(dev_addr)
+		f.close()
+	except IOError as e:
+		print("%s" % e)
+
+def dev_pair_and_connect(path):
+	bus = g['bus']
+	obj = bus.get_object(BLUEZ_BUS, path)
+	dev = dbus.Interface(obj, DEVICE_INTF)
+	props = dbus.Interface(obj, PROPERTY_INTF)
+	adap_path = props.Get(DEVICE_INTF, "Adapter")
+	paired = props.Get(DEVICE_INTF, "Paired")
+
+	if paired == True:
+		print("%s is already paired" % (path))
+		return
+
+	def dev_pair_reply():
+		print("Device pairing completed for %s" % path)
+		set_le_adv_ind_filter(adap_path, '00:00:00:00:00:00')
+		props.Set(DEVICE_INTF, "Trusted", True)
+		dev.Connect()
+
+	def dev_pair_error(error):
+		print("Device pairing failed for %s" % path)
+		set_le_adv_ind_filter(adap_path, '00:00:00:00:00:00')
+		err_name = error.get_dbus_name()
+		print("Device pairing error: %s" % err_name)
+		dev.CancelPairing()
+
+	dev_addr = props.Get(DEVICE_INTF, "Address")
+	set_le_adv_ind_filter(adap_path, dev_addr)
+	dev.Pair(reply_handler=dev_pair_reply, error_handler=dev_pair_error, timeout=15.0)
+
+def get_name_and_addr(path):
+	bus = g['bus']
+	obj = bus.get_object(BLUEZ_BUS, path)
+	props = dbus.Interface(obj, PROPERTY_INTF)
+	addr = props.Get(DEVICE_INTF, "Address")
+	try:
+		name = props.Get(DEVICE_INTF, "Name")
+	except dbus.exceptions.DBusException:
+		name = ""
+	return (name, addr)
+
+def is_gfiber_remote(name):
+	return (name == DEV_NAME_GFRM100 or
+	        name == DEV_NAME_GFRM200 or
+	        name == DEV_NAME_GFRM210 or
+	        name == DEV_NAME_GFRM201)
+
+def interfaces_added(path, interfaces):
+	if not DEVICE_INTF in interfaces:
+		return
+	(name, addr) = get_name_and_addr(path)
+	print("Discovered %s [%s] [%s]" % (path, addr, name))
+	if (is_gfiber_remote(name) or
+	    name == DEV_NAME_TIARC or
+	    addr.startswith(DEV_OUI_TIARC)):
+		print("Pair with  %s [%s] [%s]" % (path, addr, name))
+		dev_pair_and_connect(path)
+
+def properties_changed(interface, changed, invalidated, path):
+	if interface != DEVICE_INTF:
+		return
+	connected = changed.get('Connected', False)
+	if not connected:
+		return
+	(name, addr) = get_name_and_addr(path)
+	if is_gfiber_remote(name):
+		jsonfile = os.path.join(g['adaptdir'], addr, 'gfiber-inventory')
+		subprocess.call(["gfrm-inventory", addr, jsonfile])
+
+
+def main():
+	dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+	bus = dbus.SystemBus()
+	g['bus'] = bus
+	g['agent'] = Agent(bus, AGENT_PATH)
+
+	if len(sys.argv) == 1:
+		print("usage: %s adaptpath" % sys.argv[0])
+		print("  where adaptpath is the adaptor directory like "
+		      "/user/bluez/lib/bluetooth/F4:F5:E8:80:XX:XX")
+		sys.exit(1)
+	g['adaptdir'] = sys.argv[1]
+
+	register_agent()
+	bus.add_signal_receiver(interfaces_added, bus_name=BLUEZ_BUS,
+				dbus_interface=OBJECT_MGR_INTF,
+				signal_name="InterfacesAdded")
+	bus.add_signal_receiver(properties_changed,
+				dbus_interface=PROPERTY_INTF,
+				signal_name="PropertiesChanged",
+				arg0=DEVICE_INTF, path_keyword="path")
+
+	mainloop = GObject.MainLoop()
+	mainloop.run()
+
+	bus.remove_signal_receiver(interfaces_added, bus_name=BLUEZ_BUS,
+				   dbus_interface=OBJECT_MGR_INTF,
+				   signal_name="InterfacesAdded")
+	bus.remove_signal_receiver(properties_changed,
+				   dbus_interface=PROPERTY_INTF,
+				   signal_name="PropertiesChanged",
+				   arg0=DEVICE_INTF, path_keyword="path")
+	unregister_agent()
+
+if __name__ == '__main__':
+	main()
diff --git a/test/gfrm-inventory b/test/gfrm-inventory
new file mode 100755
index 0000000..93053f4
--- /dev/null
+++ b/test/gfrm-inventory
@@ -0,0 +1,201 @@
+#!/usr/bin/python
+
+import copy
+import dbus
+import dbus.mainloop.glib
+import errno
+import gobject
+import json
+import os
+import sys
+
+
+FIRMWARE_VERSION_UUID = u'00002a26-0000-1000-8000-00805f9b34fb'
+HARDWARE_REV_UUID = u'00002a27-0000-1000-8000-00805f9b34fb'
+SERIAL_NUMBER_UUID = u'00002a25-0000-1000-8000-00805f9b34fb'
+
+
+# Inventory information for the remote control in question.
+rcu = {}
+orig = {}
+status = {'nqueries': 0}
+
+
+def TimeoutInventory():
+  mainloop.quit()
+
+
+def CheckIfCallbacksDone():
+  """Decrement count of outstanding queries."""
+  status['nqueries'] -= 1
+  if status['nqueries'] <= 0:
+    mainloop.quit()
+
+
+def FirmwareVersionCallback(value):
+  """Render the firmware version string like 'T0055.12 G001E.04'."""
+  rcu['firmware'] = ''.join([chr(byte) for byte in value])
+  CheckIfCallbacksDone()
+
+
+def HardwareVersionCallback(value):
+  """Render the Google Part Number string like 'GPN#07081865-01'.
+
+     The GPN string contains a trailing NUL, which is annoying so we suppress it.
+  """
+  rcu['hardware'] = ''.join([chr(byte) for byte in value if byte != 0])
+  CheckIfCallbacksDone()
+
+
+def SerialNumberCallback(value):
+  """Render the serial number.
+
+     The Serial number is a series of integers.
+     The first is always '2', not sure what it means.
+     Then (all numbers are decimal):
+     YY == last two digits of the year, like 15 for 2015.
+     WW == week number, where 51 would be the last week of the year.
+     DD == day of the week, 0-6.
+     LL == "line number" in the factory.
+     XX XX XX XX XX == an incrementing digit.
+
+     The sticker on the remote inserts a space as the third character, so this:
+     2, 14, 51, 2, 0, 0, 0, 20, 0, 0
+     is printed as '21451 20002000'. We match this formatting.
+  """
+
+  array = [int(byte) for byte in value]
+  array.insert(3, ' ')
+  rcu['serial'] = ''.join([str(byte) for byte in array])
+  CheckIfCallbacksDone()
+
+
+def DBusErrorHandler(error):
+  sys.stderr.write('D-Bus call failed: ' + str(error))
+  mainloop.quit()
+
+
+def GetDeviceByAddress(remote):
+  """Look up a device by its BDADDR, like 5C:31:3E:08:25:44."""
+  bus = dbus.SystemBus()
+  om = dbus.Interface(bus.get_object('org.bluez', '/'),
+                      'org.freedesktop.DBus.ObjectManager')
+  objects = om.GetManagedObjects()
+  for path, interfaces in objects.iteritems():
+    if 'org.bluez.Device1' in interfaces:
+      device = interfaces['org.bluez.Device1']
+      if device.get('Address', 'noaddress').lower() == remote.lower():
+        return device
+  return {}
+
+
+def GetGattService(device):
+  """Return the General GATT service for device."""
+  bus = dbus.SystemBus()
+  service_path = ''
+  for s in device.get('GattServices', []):
+    if 'service0010' in s:
+      service_path = s
+  if not service_path:
+    return None
+  service = bus.get_object('org.bluez', service_path)
+  p = 'org.freedesktop.DBus.Properties'
+  return service.GetAll('org.bluez.GattService1', dbus_interface=p)
+
+
+def CallbackIfInteresting(uuid):
+  """If we are interested in this UUID, return the callback for it."""
+  if uuid == FIRMWARE_VERSION_UUID:
+    return FirmwareVersionCallback
+  elif uuid == HARDWARE_REV_UUID:
+    return HardwareVersionCallback
+  elif uuid == SERIAL_NUMBER_UUID:
+    return SerialNumberCallback
+
+
+def GetRcuGattCharacteristics(service):
+  """Send queries for the characteristics we are interested in."""
+  bus = dbus.SystemBus()
+  characteristics = service.get('Characteristics', [])
+  for path in characteristics:
+    c = bus.get_object('org.bluez', path)
+    props = c.GetAll('org.bluez.GattCharacteristic1',
+                     dbus_interface='org.freedesktop.DBus.Properties')
+    uuid = props.get('UUID', None)
+    callback = CallbackIfInteresting(uuid)
+    if callback:
+      status['nqueries'] += 1
+      c.ReadValue(reply_handler=callback,
+                  error_handler=DBusErrorHandler,
+                  dbus_interface='org.bluez.GattCharacteristic1')
+
+
+def ReadFromJson(filename):
+  try:
+    with open(filename) as f:
+      return json.load(f)
+  except IOError as e:
+    if e.errno != errno.ENOENT:
+      raise
+  return {}
+
+
+def MakeDirectoryIfNotExist(directory):
+  try:
+    os.makedirs(directory)
+  except OSError as e:
+    if e.errno != errno.EEXIST:
+      raise
+
+
+def WriteToJsonIfChanged(filename):
+  """Write the contents of 'rcu' to filename if they have changed."""
+  if rcu == orig:
+    return
+  with open(filename + '.tmp', 'w') as f:
+    json.dump(rcu, f, sort_keys=True)
+  os.rename(filename + '.tmp', filename)
+
+
+def usage():
+  sys.stderr.write('usage: %s addr jsonfile' % sys.argv[0])
+  sys.stderr.write('  where addr is a Bluetooth device BDADDR')
+  sys.stderr.write('  and jsonfile is the path to a JSON file to update')
+  sys.exit(1)
+
+
+if __name__ == '__main__':
+  if len(sys.argv) != 3:
+    usage()
+
+  remote = sys.argv[1]
+  jsonfile = sys.argv[2]
+
+  directory = os.path.dirname(jsonfile)
+  MakeDirectoryIfNotExist(directory)
+
+  # Bluetooth is kindof flakey. We want to always retain information we've
+  # retrieved from a remote, and supplement it with any additional information
+  # we find this time (or when things change, like a firmware update).
+  #
+  # We don't want to start afresh each time the remote appears and expect to
+  # always reliably retrieve its inventory information.
+  orig.update(ReadFromJson(jsonfile))
+  rcu = copy.copy(orig)
+
+  dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+  gobject.timeout_add(20000, TimeoutInventory)
+  device = GetDeviceByAddress(remote)
+  if 'Name' in device:
+    rcu['model'] = device['Name']
+  if 'Address' in device:
+    rcu['bdaddr'] = device['Address']
+
+  # Get more information from BLE remotes like GFRM200/GFRM210.
+  service = GetGattService(device)
+  if service:
+    GetRcuGattCharacteristics(service)
+    mainloop = gobject.MainLoop()
+    mainloop.run()
+
+  WriteToJsonIfChanged(jsonfile)
diff --git a/test/test-device b/test/test-device
index b490d53..310f9cd 100755
--- a/test/test-device
+++ b/test/test-device
@@ -38,6 +38,7 @@
 	print("  alias <address> [alias]")
 	print("  trusted <address> [yes/no]")
 	print("  blocked <address> [yes/no]")
+	print("  unplug <address> [yes/no]")
 	sys.exit(1)
 
 if (args[0] == "list"):
@@ -198,5 +199,26 @@
 			props.Set("org.bluez.Device1", "Blocked", value)
 	sys.exit(0)
 
+if (args[0] == "unplug"):
+	if (len(args) < 2):
+		print("Need address parameter")
+	else:
+		device = bluezutils.find_device(args[1], options.dev_id)
+		path = device.object_path
+		props = dbus.Interface(bus.get_object("org.bluez", path),
+					"org.freedesktop.DBus.Properties")
+		if (len(args) < 3):
+			unplug = props.Get("org.bluez.Device1", "Unplug")
+			print(unplug)
+		else:
+			if (args[2] == "yes"):
+				value = dbus.Boolean(1)
+			elif (args[2] == "no"):
+				value = dbus.Boolean(0)
+			else:
+				value = dbus.Boolean(args[2])
+			props.Set("org.bluez.Device1", "Unplug", value)
+	sys.exit(0)
+
 print("Unknown command")
 sys.exit(1)
diff --git a/test/unplug-GFRM100 b/test/unplug-GFRM100
new file mode 100755
index 0000000..f89eb25
--- /dev/null
+++ b/test/unplug-GFRM100
@@ -0,0 +1,70 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+import dbus.mainloop.glib
+
+
+def SetUnplug(path):
+  bus = dbus.SystemBus()
+  props = dbus.Interface(bus.get_object('org.bluez', path),
+                         'org.freedesktop.DBus.Properties')
+  value = dbus.Boolean(1)
+  props.Set('org.bluez.Device1', 'Unplug', value)
+
+
+def getAllDevices():
+  bus = dbus.SystemBus()
+  om = dbus.Interface(bus.get_object('org.bluez', '/'),
+                      'org.freedesktop.DBus.ObjectManager')
+  objects = om.GetManagedObjects()
+  devices = {}
+  for path, interfaces in objects.iteritems():
+    if 'org.bluez.Device1' in interfaces:
+      devices[path] = interfaces['org.bluez.Device1']
+  return devices
+
+
+def GetAllRemotes():
+  devices = getAllDevices()
+  remotes = []
+  for (key, value) in devices.iteritems():
+    name = value.get('Name', '')
+    if name == 'GFRM100':
+      remotes.append(key)
+  return remotes
+
+
+def GetDevicesByAddresses(addresses):
+  devices = getAllDevices()
+  compare = [a.lower() for a in addresses]
+  remotes = []
+  for (key, value) in devices.iteritems():
+    a = value.get('Address', '').lower()
+    if a in compare:
+      remotes.append(key)
+  return remotes
+
+
+def usage():
+  print 'usage: %s [addr1 addr2 | all]' % sys.argv[0]
+  print '  where addr is a Bluetooth device BDADDR'
+  print '  "all" will unplug all paired GFRM100 devices.'
+  sys.exit(1)
+
+
+if __name__ == '__main__':
+  dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+  if len(sys.argv) == 1:
+    usage()
+  if len(sys.argv) > 2 and sys.argv[1] == 'all':
+    usage()
+
+  if sys.argv[1] == 'all':
+    remotes = GetAllRemotes()
+  else:
+    remotes = GetDevicesByAddresses(sys.argv[1:])
+
+  for remote in remotes:
+    SetUnplug(remote)
diff --git a/tools/hciconfig.c b/tools/hciconfig.c
index 8a97cc4..a609008 100644
--- a/tools/hciconfig.c
+++ b/tools/hciconfig.c
@@ -1470,6 +1470,43 @@
 	hci_close_dev(s);
 }
 
+static void cmd_page_type(int ctl, int hdev, char *opt)
+{
+	int dd;
+
+	dd = hci_open_dev(hdev);
+	if (dd < 0) {
+		fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+						hdev, strerror(errno), errno);
+		exit(1);
+	}
+
+	if (opt) {
+		uint8_t type = atoi(opt);
+
+		if (hci_write_page_scan_type(dd, type, 2000) < 0) {
+			fprintf(stderr, "Can't set page scan type on hci%d: %s (%d)\n",
+						hdev, strerror(errno), errno);
+			exit(1);
+		}
+	} else {
+		uint8_t type;
+
+		if (hci_read_page_scan_type(dd, &type, 1000) < 0) {
+			fprintf(stderr, "Can't read page scan type on hci%d: %s (%d)\n",
+						hdev, strerror(errno), errno);
+			exit(1);
+		}
+
+		print_dev_hdr(&di);
+		printf("\tPage scan type: %s\n",
+			type == PAGE_SCAN_TYPE_INTERLACED ?
+			"Interlaced Page Scan" : "Standard Page Scan");
+	}
+
+	hci_close_dev(dd);
+}
+
 static void cmd_page_parms(int ctl, int hdev, char *opt)
 {
 	struct hci_request rq;
@@ -1950,6 +1987,7 @@
 	{ "inqdata",	cmd_inq_data,	"[data]",	"Get/Set inquiry data" },
 	{ "inqtype",	cmd_inq_type,	"[type]",	"Get/Set inquiry scan type" },
 	{ "inqparms",	cmd_inq_parms,	"[win:int]",	"Get/Set inquiry scan window and interval" },
+	{ "pagetype",	cmd_page_type,	"[type]",	"Get/Set page scan type" },
 	{ "pageparms",	cmd_page_parms,	"[win:int]",	"Get/Set page scan window and interval" },
 	{ "pageto",	cmd_page_to,	"[to]",		"Get/Set page timeout" },
 	{ "afhmode",	cmd_afh_mode,	"[mode]",	"Get/Set AFH mode" },