Initial version of BLE support for Bluedroid

Change-Id: I9579b3074bc4bc59dd45f71c0937e8879196555e
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f4c0d3b..389a21c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -219,6 +219,14 @@
         </receiver>
         <service
             android:process="@string/process"
+            android:name = ".gatt.GattService"
+            android:enabled="@bool/profile_supported_gatt">
+            <intent-filter>
+                <action android:name="android.bluetooth.IBluetoothGatt" />
+            </intent-filter>
+        </service>
+        <service
+            android:process="@string/process"
             android:name = ".hfp.HeadsetService"
             android:enabled="@bool/profile_supported_hs_hfp">
             <intent-filter>
diff --git a/jni/Android.mk b/jni/Android.mk
index 4098907..ee822d9 100644
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -8,7 +8,8 @@
     com_android_bluetooth_a2dp.cpp \
     com_android_bluetooth_hid.cpp \
     com_android_bluetooth_hdp.cpp \
-    com_android_bluetooth_pan.cpp
+    com_android_bluetooth_pan.cpp \
+    com_android_bluetooth_gatt.cpp
 
 LOCAL_C_INCLUDES += \
     $(JNI_H_INCLUDE) \
diff --git a/jni/com_android_bluetooth.h b/jni/com_android_bluetooth.h
index 2ffe7de..8689247 100644
--- a/jni/com_android_bluetooth.h
+++ b/jni/com_android_bluetooth.h
@@ -41,6 +41,8 @@
 
 int register_com_android_bluetooth_pan(JNIEnv* env);
 
+int register_com_android_bluetooth_gatt (JNIEnv* env);
+
 }
 
 #endif /* COM_ANDROID_BLUETOOTH_H */
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
old mode 100755
new mode 100644
index 5989c79..9722b5e
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -443,6 +443,13 @@
     }
 }
 
+static void dut_mode_recv_callback (uint16_t opcode, uint8_t *buf, uint8_t len) {
+
+}
+static void le_test_mode_recv_callback (bt_status_t status, uint16_t packet_count) {
+
+    ALOGV("%s: status:%d packet_count:%d ", __FUNCTION__, status, packet_count);
+}
 bt_callbacks_t sBluetoothCallbacks = {
     sizeof(sBluetoothCallbacks),
     adapter_state_change_callback,
@@ -455,6 +462,9 @@
     bond_state_changed_callback,
     acl_state_changed_callback,
     callback_thread_event,
+    dut_mode_recv_callback,
+
+    le_test_mode_recv_callback
 };
 
 static void classInitNative(JNIEnv* env, jclass clazz) {
@@ -924,7 +934,7 @@
     {"getRemoteServicesNative", "([B)Z", (void*) getRemoteServicesNative},
     {"connectSocketNative", "([BI[BII)I", (void*) connectSocketNative},
     {"createSocketChannelNative", "(ILjava/lang/String;[BII)I",
-     (void*) createSocketChannelNative},
+     (void*) createSocketChannelNative}
 };
 
 int register_com_android_bluetooth_btservice_AdapterService(JNIEnv* env)
@@ -982,5 +992,9 @@
       return JNI_ERR;
    }
 
+   if ((status = android::register_com_android_bluetooth_gatt(e)) < 0) {
+       ALOGE("jni gatt registration failure: %d", status);
+      return JNI_ERR;
+   }
    return JNI_VERSION_1_6;
 }
diff --git a/jni/com_android_bluetooth_gatt.cpp b/jni/com_android_bluetooth_gatt.cpp
new file mode 100644
index 0000000..a77be9c
--- /dev/null
+++ b/jni/com_android_bluetooth_gatt.cpp
@@ -0,0 +1,1276 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#define ALOG_TAG "BtGatt.JNI"
+
+#define ALOG_NDEBUG 0
+
+#define CHECK_CALLBACK_ENV                                                      \
+   if (!checkCallbackThread()) {                                                \
+       error("Callback: '%s' is not called on the correct thread", __FUNCTION__);\
+       return;                                                                  \
+   }
+
+#include "com_android_bluetooth.h"
+#include "hardware/bt_gatt.h"
+#include "utils/Log.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <string.h>
+
+#include <cutils/log.h>
+#define info(fmt, ...)  ALOGI ("%s(L%d): " fmt,__FUNCTION__, __LINE__,  ## __VA_ARGS__)
+#define debug(fmt, ...) ALOGD ("%s(L%d): " fmt,__FUNCTION__, __LINE__,  ## __VA_ARGS__)
+#define warn(fmt, ...) ALOGW ("WARNING: %s(L%d): " fmt "##",__FUNCTION__, __LINE__, ## __VA_ARGS__)
+#define error(fmt, ...) ALOGE ("ERROR: %s(L%d): " fmt "##",__FUNCTION__, __LINE__, ## __VA_ARGS__)
+#define asrt(s) if(!(s)) ALOGE ("%s(L%d): ASSERT %s failed! ##",__FUNCTION__, __LINE__, #s)
+
+#define BD_ADDR_LEN 6
+
+#define UUID_PARAMS(uuid_ptr) \
+    uuid_lsb(uuid_ptr),  uuid_msb(uuid_ptr)
+
+#define CHAR_ID_PARAMS(char_ptr) \
+    char_ptr->inst_id, \
+    UUID_PARAMS((&char_ptr->uuid))
+
+#define SRVC_ID_PARAMS(srvc_ptr) \
+    (srvc_ptr->is_primary ? \
+    BTGATT_SERVICE_TYPE_PRIMARY : BTGATT_SERVICE_TYPE_SECONDARY), \
+    CHAR_ID_PARAMS((&srvc_ptr->id))
+
+
+static void set_uuid(uint8_t* uuid, jlong uuid_msb, jlong uuid_lsb)
+{
+    for (int i = 0; i != 8; ++i)
+    {
+        uuid[i]     = (uuid_lsb >> (8 * i)) & 0xFF;
+        uuid[i + 8] = (uuid_msb >> (8 * i)) & 0xFF;
+    }
+}
+
+static uint64_t uuid_lsb(bt_uuid_t* uuid)
+{
+    uint64_t  lsb = 0;
+    int i;
+
+    for (i = 7; i >= 0; i--)
+    {
+        lsb <<= 8;
+        lsb |= uuid->uu[i];
+    }
+
+    return lsb;
+}
+
+static uint64_t uuid_msb(bt_uuid_t* uuid)
+{
+    uint64_t msb = 0;
+    int i;
+
+    for (i = 15; i >= 8; i--)
+    {
+        msb <<= 8;
+        msb |= uuid->uu[i];
+    }
+
+    return msb;
+}
+
+static void bd_addr_str_to_addr(const char* str, uint8_t *bd_addr)
+{
+    int    i;
+    char   c;
+
+    c = *str++;
+    for (i = 0; i < BD_ADDR_LEN; i++)
+    {
+        if (c >= '0' && c <= '9')
+            bd_addr[i] = c - '0';
+        else if (c >= 'a' && c <= 'z')
+            bd_addr[i] = c - 'a' + 10;
+        else   // (c >= 'A' && c <= 'Z')
+            bd_addr[i] = c - 'A' + 10;
+
+        c = *str++;
+        if (c != ':')
+        {
+            bd_addr[i] <<= 4;
+            if (c >= '0' && c <= '9')
+                bd_addr[i] |= c - '0';
+            else if (c >= 'a' && c <= 'z')
+                bd_addr[i] |= c - 'a' + 10;
+            else   // (c >= 'A' && c <= 'Z')
+                bd_addr[i] |= c - 'A' + 10;
+
+            c = *str++;
+        }
+
+        c = *str++;
+    }
+}
+
+static void jstr2bdaddr(JNIEnv* env, bt_bdaddr_t *bda, jstring address)
+{
+    const char* c_bda = env->GetStringUTFChars(address, NULL);
+    if (c_bda != NULL && bda != NULL && strlen(c_bda) == 17)
+    {
+        bd_addr_str_to_addr(c_bda, bda->address);
+        env->ReleaseStringUTFChars(address, c_bda);
+    }
+}
+
+namespace android {
+
+/**
+ * Client callback methods
+ */
+
+static jmethodID method_onClientRegistered;
+static jmethodID method_onScanResult;
+static jmethodID method_onConnected;
+static jmethodID method_onDisconnected;
+static jmethodID method_onReadCharacteristic;
+static jmethodID method_onWriteCharacteristic;
+static jmethodID method_onExecuteCompleted;
+static jmethodID method_onSearchCompleted;
+static jmethodID method_onSearchResult;
+static jmethodID method_onReadDescrExtProp;
+static jmethodID method_onReadDescriptor;
+static jmethodID method_onWriteDescriptor;
+static jmethodID method_onNotify;
+static jmethodID method_onGetCharacteristic;
+static jmethodID method_onGetDescriptor;
+static jmethodID method_onGetIncludedService;
+static jmethodID method_onRegisterForNotifications;
+static jmethodID method_onReadRemoteRssi;
+
+/**
+ * Server callback methods
+ */
+static jmethodID method_onServerRegistered;
+static jmethodID method_onClientConnected;
+static jmethodID method_onServiceAdded;
+static jmethodID method_onIncludedServiceAdded;
+static jmethodID method_onCharacteristicAdded;
+static jmethodID method_onDescriptorAdded;
+static jmethodID method_onServiceStarted;
+static jmethodID method_onServiceStopped;
+static jmethodID method_onServiceDeleted;
+static jmethodID method_onResponseSendCompleted;
+static jmethodID method_onAttributeRead;
+static jmethodID method_onAttributeWrite;
+static jmethodID method_onExecuteWrite;
+
+/**
+ * Static variables
+ */
+
+static const btgatt_interface_t *sGattIf = NULL;
+static jobject mCallbacksObj = NULL;
+static JNIEnv *sCallbackEnv = NULL;
+
+static bool checkCallbackThread() {
+    sCallbackEnv = getCallbackEnv();
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    if (sCallbackEnv != env || sCallbackEnv == NULL) return false;
+    return true;
+}
+
+/**
+ * BTA client callbacks
+ */
+
+void btgattc_register_app_cb(int status, int clientIf, bt_uuid_t *app_uuid)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientRegistered, status,
+        clientIf, UUID_PARAMS(app_uuid));
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_scan_result_cb(bt_bdaddr_t* bda, int rssi, uint8_t* adv_data)
+{
+    CHECK_CALLBACK_ENV
+
+    char c_address[32];
+    snprintf(c_address, sizeof(c_address),"%02X:%02X:%02X:%02X:%02X:%02X",
+        bda->address[0], bda->address[1], bda->address[2],
+        bda->address[3], bda->address[4], bda->address[5]);
+
+    jstring address = sCallbackEnv->NewStringUTF(c_address);
+    jbyteArray jb = sCallbackEnv->NewByteArray(62);
+    sCallbackEnv->SetByteArrayRegion(jb, 0, 62, (jbyte *) adv_data);
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanResult
+        , address, rssi, jb);
+
+    sCallbackEnv->DeleteLocalRef(address);
+    sCallbackEnv->DeleteLocalRef(jb);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_open_cb(int conn_id, int status, int clientIf, bt_bdaddr_t* bda)
+{
+    CHECK_CALLBACK_ENV
+
+    char c_address[32];
+    snprintf(c_address, sizeof(c_address),"%02X:%02X:%02X:%02X:%02X:%02X",
+        bda->address[0], bda->address[1], bda->address[2],
+        bda->address[3], bda->address[4], bda->address[5]);
+
+    jstring address = sCallbackEnv->NewStringUTF(c_address);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnected,
+        clientIf, conn_id, status, address);
+    sCallbackEnv->DeleteLocalRef(address);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_close_cb(int conn_id, int status, int clientIf, bt_bdaddr_t* bda)
+{
+    CHECK_CALLBACK_ENV
+    char c_address[32];
+    snprintf(c_address, sizeof(c_address),"%02X:%02X:%02X:%02X:%02X:%02X",
+        bda->address[0], bda->address[1], bda->address[2],
+        bda->address[3], bda->address[4], bda->address[5]);
+
+    jstring address = sCallbackEnv->NewStringUTF(c_address);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onDisconnected,
+        clientIf, conn_id, status, address);
+    sCallbackEnv->DeleteLocalRef(address);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_search_complete_cb(int conn_id, int status)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSearchCompleted,
+                                 conn_id, status);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_search_result_cb(int conn_id, btgatt_srvc_id_t *srvc_id)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSearchResult, conn_id,
+        SRVC_ID_PARAMS(srvc_id));
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_get_characteristic_cb(int conn_id, int status,
+                btgatt_srvc_id_t *srvc_id, btgatt_char_id_t *char_id,
+                int char_prop)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGetCharacteristic
+        , conn_id, status, SRVC_ID_PARAMS(srvc_id), CHAR_ID_PARAMS(char_id)
+        , char_prop);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_get_descriptor_cb(int conn_id, int status,
+                btgatt_srvc_id_t *srvc_id, btgatt_char_id_t *char_id,
+                bt_uuid_t *descr_id)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGetDescriptor
+        , conn_id, status, SRVC_ID_PARAMS(srvc_id), CHAR_ID_PARAMS(char_id)
+        , UUID_PARAMS(descr_id));
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_get_included_service_cb(int conn_id, int status,
+                btgatt_srvc_id_t *srvc_id, btgatt_srvc_id_t *incl_srvc_id)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGetIncludedService
+        , conn_id, status, SRVC_ID_PARAMS(srvc_id), SRVC_ID_PARAMS(incl_srvc_id));
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_register_for_notification_cb(int conn_id, int registered, int status,
+                                          btgatt_srvc_id_t *srvc_id, btgatt_char_id_t *char_id)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onRegisterForNotifications
+        , conn_id, status, registered, SRVC_ID_PARAMS(srvc_id), CHAR_ID_PARAMS(char_id));
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_notify_cb(int conn_id, btgatt_notify_params_t *p_data)
+{
+    CHECK_CALLBACK_ENV
+
+    char c_address[32];
+    snprintf(c_address, sizeof(c_address), "%02X:%02X:%02X:%02X:%02X:%02X",
+        p_data->bda.address[0], p_data->bda.address[1], p_data->bda.address[2],
+        p_data->bda.address[3], p_data->bda.address[4], p_data->bda.address[5]);
+
+    jstring address = sCallbackEnv->NewStringUTF(c_address);
+    jbyteArray jb = sCallbackEnv->NewByteArray(p_data->len);
+    sCallbackEnv->SetByteArrayRegion(jb, 0, p_data->len, (jbyte *) p_data->value);
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNotify
+        , conn_id, address, SRVC_ID_PARAMS((&p_data->srvc_id))
+        , CHAR_ID_PARAMS((&p_data->char_id)), p_data->is_notify, jb);
+
+    sCallbackEnv->DeleteLocalRef(address);
+    sCallbackEnv->DeleteLocalRef(jb);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_read_characteristic_cb(int conn_id, int status, btgatt_read_params_t *p_data)
+{
+    CHECK_CALLBACK_ENV
+
+    jbyteArray jb;
+    if ( status == 0 )      //successful
+    {
+        jb = sCallbackEnv->NewByteArray(p_data->value.len);
+        sCallbackEnv->SetByteArrayRegion(jb, 0, p_data->value.len,
+            (jbyte *) p_data->value.value);
+    } else {
+        uint8_t value = 0;
+        jb = sCallbackEnv->NewByteArray(1);
+        sCallbackEnv->SetByteArrayRegion(jb, 0, 1, (jbyte *) &value);
+    }
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onReadCharacteristic
+        , conn_id, status, SRVC_ID_PARAMS((&p_data->srvc_id))
+        , CHAR_ID_PARAMS((&p_data->char_id)), p_data->value_type, jb);
+    sCallbackEnv->DeleteLocalRef(jb);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_write_characteristic_cb(int conn_id, int status, btgatt_write_params_t *p_data)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onWriteCharacteristic
+        , conn_id, status, SRVC_ID_PARAMS((&p_data->srvc_id))
+        , CHAR_ID_PARAMS((&p_data->char_id)));
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_execute_write_cb(int conn_id, int status)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onExecuteCompleted
+        , conn_id, status);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_read_descriptor_cb(int conn_id, int status, btgatt_read_params_t *p_data)
+{
+    CHECK_CALLBACK_ENV
+
+    jbyteArray jb;
+    if ( p_data->value.len != 0 )
+    {
+        jb = sCallbackEnv->NewByteArray(p_data->value.len);
+        sCallbackEnv->SetByteArrayRegion(jb, 0, p_data->value.len,
+                                (jbyte *) p_data->value.value);
+    } else {
+        jb = sCallbackEnv->NewByteArray(1);
+    }
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onReadDescriptor
+        , conn_id, status, SRVC_ID_PARAMS((&p_data->srvc_id))
+        , CHAR_ID_PARAMS((&p_data->char_id)), UUID_PARAMS((&p_data->descr_id))
+        , p_data->value_type, jb);
+
+    sCallbackEnv->DeleteLocalRef(jb);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_write_descriptor_cb(int conn_id, int status, btgatt_write_params_t *p_data)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onWriteDescriptor
+        , conn_id, status, SRVC_ID_PARAMS((&p_data->srvc_id))
+        , CHAR_ID_PARAMS((&p_data->char_id)), UUID_PARAMS((&p_data->descr_id)));
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgattc_remote_rssi_cb(int client_if,bt_bdaddr_t* bda, int rssi, int status)
+{
+    CHECK_CALLBACK_ENV
+
+    char c_address[32];
+    snprintf(c_address, sizeof(c_address),"%02X:%02X:%02X:%02X:%02X:%02X",
+        bda->address[0], bda->address[1], bda->address[2],
+        bda->address[3], bda->address[4], bda->address[5]);
+    jstring address = sCallbackEnv->NewStringUTF(c_address);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onReadRemoteRssi,
+       client_if, address, rssi, status);
+    sCallbackEnv->DeleteLocalRef(address);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static const btgatt_client_callbacks_t sGattClientCallbacks = {
+    btgattc_register_app_cb,
+    btgattc_scan_result_cb,
+    btgattc_open_cb,
+    btgattc_close_cb,
+    btgattc_search_complete_cb,
+    btgattc_search_result_cb,
+    btgattc_get_characteristic_cb,
+    btgattc_get_descriptor_cb,
+    btgattc_get_included_service_cb,
+    btgattc_register_for_notification_cb,
+    btgattc_notify_cb,
+    btgattc_read_characteristic_cb,
+    btgattc_write_characteristic_cb,
+    btgattc_read_descriptor_cb,
+    btgattc_write_descriptor_cb,
+    btgattc_execute_write_cb,
+    btgattc_remote_rssi_cb
+};
+
+
+/**
+ * BTA server callbacks
+ */
+
+void btgatts_register_app_cb(int status, int server_if, bt_uuid_t *uuid)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServerRegistered
+        , status, server_if, UUID_PARAMS(uuid));
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgatts_connection_cb(int conn_id, int connected, bt_bdaddr_t *bda)
+{
+    CHECK_CALLBACK_ENV
+
+    char c_address[32];
+    sprintf(c_address, "%02X:%02X:%02X:%02X:%02X:%02X",
+            bda->address[0], bda->address[1], bda->address[2],
+            bda->address[3], bda->address[4], bda->address[5]);
+
+    jstring address = sCallbackEnv->NewStringUTF(c_address);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientConnected,
+                                 address, connected, conn_id);
+    sCallbackEnv->DeleteLocalRef(address);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgatts_service_added_cb(int status, int server_if,
+                              btgatt_srvc_id_t *srvc_id, int srvc_handle)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServiceAdded, status,
+                                 server_if, SRVC_ID_PARAMS(srvc_id),
+                                 srvc_handle);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgatts_included_service_added_cb(int status, int server_if,
+                                   int srvc_handle,
+                                   int incl_srvc_handle)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onIncludedServiceAdded,
+                                 status, server_if, srvc_handle, incl_srvc_handle);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgatts_characteristic_added_cb(int status, int server_if, bt_uuid_t *char_id,
+                                     int srvc_handle, int char_handle)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCharacteristicAdded,
+                                 status, server_if, UUID_PARAMS(char_id),
+                                 srvc_handle, char_handle);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgatts_descriptor_added_cb(int status, int server_if,
+                                 bt_uuid_t *descr_id, int srvc_handle,
+                                 int descr_handle)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onDescriptorAdded,
+                                 status, server_if, UUID_PARAMS(descr_id),
+                                 srvc_handle, descr_handle);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgatts_service_started_cb(int status, int server_if, int srvc_handle)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServiceStarted, status,
+                                 server_if, srvc_handle);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgatts_service_stopped_cb(int status, int server_if, int srvc_handle)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServiceStopped, status,
+                                 server_if, srvc_handle);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgatts_service_deleted_cb(int status, int server_if, int srvc_handle)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServiceDeleted, status,
+                                 server_if, srvc_handle);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgatts_request_read_cb(int conn_id, int trans_id, bt_bdaddr_t *bda,
+                             int attr_handle, int offset, bool is_long)
+{
+    CHECK_CALLBACK_ENV
+
+    char c_address[32];
+    sprintf(c_address, "%02X:%02X:%02X:%02X:%02X:%02X",
+            bda->address[0], bda->address[1], bda->address[2],
+            bda->address[3], bda->address[4], bda->address[5]);
+
+    jstring address = sCallbackEnv->NewStringUTF(c_address);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAttributeRead,
+                                 address, conn_id, trans_id, attr_handle,
+                                 offset, is_long);
+    sCallbackEnv->DeleteLocalRef(address);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgatts_request_write_cb(int conn_id, int trans_id,
+                              bt_bdaddr_t *bda, int attr_handle,
+                              int offset, int length,
+                              bool need_rsp, bool is_prep, uint8_t* value)
+{
+    CHECK_CALLBACK_ENV
+
+    char c_address[32];
+    sprintf(c_address, "%02X:%02X:%02X:%02X:%02X:%02X",
+            bda->address[0], bda->address[1], bda->address[2],
+            bda->address[3], bda->address[4], bda->address[5]);
+
+    jstring address = sCallbackEnv->NewStringUTF(c_address);
+
+    jbyteArray val = sCallbackEnv->NewByteArray(length);
+    if (val) sCallbackEnv->SetByteArrayRegion(val, 0, length, (jbyte*)value);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAttributeWrite,
+                                 address, conn_id, trans_id, attr_handle,
+                                 offset, length, need_rsp, is_prep, val);
+    sCallbackEnv->DeleteLocalRef(address);
+    sCallbackEnv->DeleteLocalRef(val);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgatts_request_exec_write_cb(int conn_id, int trans_id,
+                                   bt_bdaddr_t *bda, int exec_write)
+{
+    CHECK_CALLBACK_ENV
+
+    char c_address[32];
+    sprintf(c_address, "%02X:%02X:%02X:%02X:%02X:%02X",
+            bda->address[0], bda->address[1], bda->address[2],
+            bda->address[3], bda->address[4], bda->address[5]);
+
+    jstring address = sCallbackEnv->NewStringUTF(c_address);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onExecuteWrite,
+                                 address, conn_id, trans_id, exec_write);
+    sCallbackEnv->DeleteLocalRef(address);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+void btgatts_response_confirmation_cb(int status, int handle)
+{
+    CHECK_CALLBACK_ENV
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onResponseSendCompleted,
+                                 status, handle);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static const btgatt_server_callbacks_t sGattServerCallbacks = {
+    btgatts_register_app_cb,
+    btgatts_connection_cb,
+    btgatts_service_added_cb,
+    btgatts_included_service_added_cb,
+    btgatts_characteristic_added_cb,
+    btgatts_descriptor_added_cb,
+    btgatts_service_started_cb,
+    btgatts_service_stopped_cb,
+    btgatts_service_deleted_cb,
+    btgatts_request_read_cb,
+    btgatts_request_write_cb,
+    btgatts_request_exec_write_cb,
+    btgatts_response_confirmation_cb
+};
+
+/**
+ * GATT callbacks
+ */
+
+static const btgatt_callbacks_t sGattCallbacks = {
+    sizeof(btgatt_callbacks_t),
+    &sGattClientCallbacks,
+    &sGattServerCallbacks
+};
+
+/**
+ * Native function definitions
+ */
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+
+    // Client callbacks
+
+    method_onClientRegistered = env->GetMethodID(clazz, "onClientRegistered", "(IIJJ)V");
+    method_onScanResult = env->GetMethodID(clazz, "onScanResult", "(Ljava/lang/String;I[B)V");
+    method_onConnected   = env->GetMethodID(clazz, "onConnected", "(IIILjava/lang/String;)V");
+    method_onDisconnected = env->GetMethodID(clazz, "onDisconnected", "(IIILjava/lang/String;)V");
+    method_onReadCharacteristic = env->GetMethodID(clazz, "onReadCharacteristic", "(IIIIJJIJJI[B)V");
+    method_onWriteCharacteristic = env->GetMethodID(clazz, "onWriteCharacteristic", "(IIIIJJIJJ)V");
+    method_onExecuteCompleted = env->GetMethodID(clazz, "onExecuteCompleted",  "(II)V");
+    method_onSearchCompleted = env->GetMethodID(clazz, "onSearchCompleted",  "(II)V");
+    method_onSearchResult = env->GetMethodID(clazz, "onSearchResult", "(IIIJJ)V");
+    method_onReadDescriptor = env->GetMethodID(clazz, "onReadDescriptor", "(IIIIJJIJJJJI[B)V");
+    method_onWriteDescriptor = env->GetMethodID(clazz, "onWriteDescriptor", "(IIIIJJIJJJJ)V");
+    method_onNotify = env->GetMethodID(clazz, "onNotify", "(ILjava/lang/String;IIJJIJJZ[B)V");
+    method_onGetCharacteristic = env->GetMethodID(clazz, "onGetCharacteristic", "(IIIIJJIJJI)V");
+    method_onGetDescriptor = env->GetMethodID(clazz, "onGetDescriptor", "(IIIIJJIJJJJ)V");
+    method_onGetIncludedService = env->GetMethodID(clazz, "onGetIncludedService", "(IIIIJJIIJJ)V");
+    method_onRegisterForNotifications = env->GetMethodID(clazz, "onRegisterForNotifications", "(IIIIIJJIJJ)V");
+    method_onReadRemoteRssi = env->GetMethodID(clazz, "onReadRemoteRssi", "(ILjava/lang/String;II)V");
+
+     // Server callbacks
+
+    method_onServerRegistered = env->GetMethodID(clazz, "onServerRegistered", "(IIJJ)V");
+    method_onClientConnected = env->GetMethodID(clazz, "onClientConnected", "(Ljava/lang/String;ZI)V");
+    method_onServiceAdded = env->GetMethodID(clazz, "onServiceAdded", "(IIIIJJI)V");
+    method_onIncludedServiceAdded = env->GetMethodID(clazz, "onIncludedServiceAdded", "(IIII)V");
+    method_onCharacteristicAdded  = env->GetMethodID(clazz, "onCharacteristicAdded", "(IIJJII)V");
+    method_onDescriptorAdded = env->GetMethodID(clazz, "onDescriptorAdded", "(IIJJII)V");
+    method_onServiceStarted = env->GetMethodID(clazz, "onServiceStarted", "(III)V");
+    method_onServiceStopped = env->GetMethodID(clazz, "onServiceStopped", "(III)V");
+    method_onServiceDeleted = env->GetMethodID(clazz, "onServiceDeleted", "(III)V");
+    method_onResponseSendCompleted = env->GetMethodID(clazz, "onResponseSendCompleted", "(II)V");
+    method_onAttributeRead= env->GetMethodID(clazz, "onAttributeRead", "(Ljava/lang/String;IIIIZ)V");
+    method_onAttributeWrite= env->GetMethodID(clazz, "onAttributeWrite", "(Ljava/lang/String;IIIIIZZ[B)V");
+    method_onExecuteWrite= env->GetMethodID(clazz, "onExecuteWrite", "(Ljava/lang/String;III)V");
+
+    info("classInitNative: Success!");
+}
+
+static const bt_interface_t* btIf;
+
+static void initializeNative(JNIEnv *env, jobject object) {
+    if(btIf)
+        return;
+
+    if ( (btIf = getBluetoothInterface()) == NULL) {
+        error("Bluetooth module is not loaded");
+        return;
+    }
+
+    if (sGattIf != NULL) {
+         ALOGW("Cleaning up Bluetooth GATT Interface before initializing...");
+         sGattIf->cleanup();
+         sGattIf = NULL;
+    }
+
+    if (mCallbacksObj != NULL) {
+         ALOGW("Cleaning up Bluetooth GATT callback object");
+         env->DeleteGlobalRef(mCallbacksObj);
+         mCallbacksObj = NULL;
+    }
+
+    if ( (sGattIf = (btgatt_interface_t *)
+          btIf->get_profile_interface(BT_PROFILE_GATT_ID)) == NULL) {
+        error("Failed to get Bluetooth GATT Interface");
+        return;
+    }
+
+    bt_status_t status;
+    if ( (status = sGattIf->init(&sGattCallbacks)) != BT_STATUS_SUCCESS) {
+        error("Failed to initialize Bluetooth GATT, status: %d", status);
+        sGattIf = NULL;
+        return;
+    }
+
+    mCallbacksObj = env->NewGlobalRef(object);
+}
+
+static void cleanupNative(JNIEnv *env, jobject object) {
+    bt_status_t status;
+    if (!btIf) return;
+
+    if (sGattIf != NULL) {
+        sGattIf->cleanup();
+        sGattIf = NULL;
+    }
+
+    if (mCallbacksObj != NULL) {
+        env->DeleteGlobalRef(mCallbacksObj);
+        mCallbacksObj = NULL;
+    }
+    btIf = NULL;
+}
+
+/**
+ * Native Client functions
+ */
+
+static int gattClientGetDeviceTypeNative(JNIEnv* env, jobject object, jstring address)
+{
+    if (!sGattIf) return 0;
+    bt_bdaddr_t bda;
+    jstr2bdaddr(env, &bda, address);
+    return sGattIf->client->get_device_type(&bda);
+}
+
+static void gattClientRegisterAppNative(JNIEnv* env, jobject object,
+                                        jlong app_uuid_lsb, jlong app_uuid_msb )
+{
+    bt_uuid_t uuid;
+
+    if (!sGattIf) return;
+    set_uuid(uuid.uu, app_uuid_msb, app_uuid_lsb);
+    sGattIf->client->register_client(&uuid);
+}
+
+static void gattClientUnregisterAppNative(JNIEnv* env, jobject object, jint clientIf)
+{
+    if (!sGattIf) return;
+    sGattIf->client->unregister_client(clientIf);
+}
+
+static void gattClientScanNative(JNIEnv* env, jobject object, jint clientIf, jboolean start)
+{
+    if (!sGattIf) return;
+    sGattIf->client->scan(clientIf, start);
+}
+
+static void gattClientConnectNative(JNIEnv* env, jobject object, jint clientif,
+                                 jstring address, jboolean isDirect)
+{
+    if (!sGattIf) return;
+
+    bt_bdaddr_t bda;
+    jstr2bdaddr(env, &bda, address);
+    sGattIf->client->connect(clientif, &bda, isDirect);
+}
+
+static void gattClientDisconnectNative(JNIEnv* env, jobject object, jint clientIf,
+                                  jstring address, jint conn_id)
+{
+    if (!sGattIf) return;
+    bt_bdaddr_t bda;
+    jstr2bdaddr(env, &bda, address);
+    sGattIf->client->disconnect(clientIf, &bda, conn_id);
+}
+
+static void gattClientRefreshNative(JNIEnv* env, jobject object, jint clientIf,
+                                    jstring address)
+{
+    if (!sGattIf) return;
+
+    bt_bdaddr_t bda;
+    jstr2bdaddr(env, &bda, address);
+    sGattIf->client->refresh(clientIf, &bda);
+}
+
+static void gattClientSearchServiceNative(JNIEnv* env, jobject object, jint conn_id,
+            jboolean search_all, jlong service_uuid_lsb, jlong service_uuid_msb)
+{
+    if (!sGattIf) return;
+
+    bt_uuid_t uuid;
+    set_uuid(uuid.uu, service_uuid_msb, service_uuid_lsb);
+    sGattIf->client->search_service(conn_id, search_all ? 0 : &uuid);
+}
+
+static void gattClientGetCharacteristicNative(JNIEnv* env, jobject object,
+    jint conn_id,
+    jint  service_type, jint  service_id_inst_id,
+    jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
+    jint  char_id_inst_id,
+    jlong char_id_uuid_lsb, jlong char_id_uuid_msb)
+{
+    if (!sGattIf) return;
+
+    btgatt_srvc_id_t srvc_id;
+    srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
+    srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
+
+    set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
+
+    btgatt_char_id_t char_id;
+    char_id.inst_id = (uint8_t) char_id_inst_id;
+    set_uuid(char_id.uuid.uu, char_id_uuid_msb, char_id_uuid_lsb);
+
+    if (char_id_uuid_lsb == 0)
+    {
+        sGattIf->client->get_characteristic(conn_id, &srvc_id, 0);
+    } else {
+        sGattIf->client->get_characteristic(conn_id, &srvc_id, &char_id);
+    }
+}
+
+static void gattClientGetDescriptorNative(JNIEnv* env, jobject object,
+    jint conn_id,
+    jint  service_type, jint  service_id_inst_id,
+    jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
+    jint  char_id_inst_id,
+    jlong char_id_uuid_lsb, jlong char_id_uuid_msb,
+    jlong descr_id_uuid_lsb, jlong descr_id_uuid_msb)
+{
+    if (!sGattIf) return;
+
+    btgatt_srvc_id_t srvc_id;
+    srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
+    srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
+    set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
+
+    btgatt_char_id_t char_id;
+    char_id.inst_id = (uint8_t) char_id_inst_id;
+    set_uuid(char_id.uuid.uu, char_id_uuid_msb, char_id_uuid_lsb);
+
+    bt_uuid_t descr_id;
+    set_uuid(descr_id.uu, descr_id_uuid_msb, descr_id_uuid_lsb);
+
+    if (descr_id_uuid_lsb == 0)
+    {
+        sGattIf->client->get_descriptor(conn_id, &srvc_id, &char_id, 0);
+    } else {
+        sGattIf->client->get_descriptor(conn_id, &srvc_id, &char_id, &descr_id);
+    }
+}
+
+static void gattClientGetIncludedServiceNative(JNIEnv* env, jobject object,
+    jint conn_id, jint service_type, jint service_id_inst_id,
+    jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
+    jint incl_service_id_inst_id, jint incl_service_type,
+    jlong incl_service_id_uuid_lsb, jlong incl_service_id_uuid_msb)
+{
+    if (!sGattIf) return;
+
+    btgatt_srvc_id_t srvc_id;
+    srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
+    srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
+    set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
+
+    btgatt_srvc_id_t  incl_srvc_id;
+    incl_srvc_id.id.inst_id = (uint8_t) incl_service_id_inst_id;
+    incl_srvc_id.is_primary = (incl_service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
+    set_uuid(incl_srvc_id.id.uuid.uu, incl_service_id_uuid_msb, incl_service_id_uuid_lsb);
+
+    if (incl_service_id_uuid_lsb == 0)
+    {
+        sGattIf->client->get_included_service(conn_id, &srvc_id, 0);
+    } else {
+        sGattIf->client->get_included_service(conn_id, &srvc_id, &incl_srvc_id);
+    }
+}
+
+static void gattClientReadCharacteristicNative(JNIEnv* env, jobject object,
+    jint conn_id, jint  service_type, jint  service_id_inst_id,
+    jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
+    jint  char_id_inst_id,
+    jlong char_id_uuid_lsb, jlong char_id_uuid_msb,
+    jint authReq)
+{
+    if (!sGattIf) return;
+
+    btgatt_srvc_id_t srvc_id;
+    srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
+    srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
+    set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
+
+    btgatt_char_id_t char_id;
+    char_id.inst_id = (uint8_t) char_id_inst_id;
+    set_uuid(char_id.uuid.uu, char_id_uuid_msb, char_id_uuid_lsb);
+
+    sGattIf->client->read_characteristic(conn_id, &srvc_id, &char_id, authReq);
+}
+
+static void gattClientReadDescriptorNative(JNIEnv* env, jobject object,
+    jint conn_id, jint  service_type, jint  service_id_inst_id,
+    jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
+    jint  char_id_inst_id,
+    jlong char_id_uuid_lsb, jlong char_id_uuid_msb,
+    jlong descr_id_uuid_lsb, jlong descr_id_uuid_msb,
+    jint authReq)
+{
+    if (!sGattIf) return;
+
+    btgatt_srvc_id_t srvc_id;
+    srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
+    srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
+    set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
+
+    btgatt_char_id_t char_id;
+    char_id.inst_id = (uint8_t) char_id_inst_id;
+    set_uuid(char_id.uuid.uu, char_id_uuid_msb, char_id_uuid_lsb);
+
+    bt_uuid_t descr_id;
+    set_uuid(descr_id.uu, descr_id_uuid_msb, descr_id_uuid_lsb);
+
+    sGattIf->client->read_descriptor(conn_id, &srvc_id, &char_id, &descr_id, authReq);
+}
+
+static void gattClientWriteCharacteristicNative(JNIEnv* env, jobject object,
+    jint conn_id, jint  service_type, jint  service_id_inst_id,
+    jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
+    jint  char_id_inst_id,
+    jlong char_id_uuid_lsb, jlong char_id_uuid_msb,
+    jint write_type, jint auth_req, jbyteArray value)
+{
+    if (!sGattIf) return;
+
+    btgatt_srvc_id_t srvc_id;
+    srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
+    srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
+    set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
+
+    btgatt_char_id_t char_id;
+    char_id.inst_id = (uint8_t) char_id_inst_id;
+    set_uuid(char_id.uuid.uu, char_id_uuid_msb, char_id_uuid_lsb);
+
+    uint16_t len = (uint16_t) env->GetArrayLength(value);
+    jbyte *p_value = env->GetByteArrayElements(value, NULL);
+    if (p_value == NULL) return;
+
+    sGattIf->client->write_characteristic(conn_id, &srvc_id, &char_id,
+                                    write_type, len, auth_req, (char*)p_value);
+    env->ReleaseByteArrayElements(value, p_value, 0);
+}
+
+static void gattClientExecuteWriteNative(JNIEnv* env, jobject object,
+    jint conn_id, jboolean execute)
+{
+    if (!sGattIf) return;
+    sGattIf->client->execute_write(conn_id, execute ? 1 : 0);
+}
+
+static void gattClientWriteDescriptorNative(JNIEnv* env, jobject object,
+    jint conn_id, jint  service_type, jint service_id_inst_id,
+    jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
+    jint char_id_inst_id,
+    jlong char_id_uuid_lsb, jlong char_id_uuid_msb,
+    jlong descr_id_uuid_lsb, jlong descr_id_uuid_msb,
+    jint write_type, jint auth_req, jbyteArray value)
+{
+    if (!sGattIf) return;
+
+    btgatt_srvc_id_t srvc_id;
+    srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
+    srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
+    set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
+
+    btgatt_char_id_t char_id;
+    char_id.inst_id = (uint8_t) char_id_inst_id;
+    set_uuid(char_id.uuid.uu, char_id_uuid_msb, char_id_uuid_lsb);
+
+    bt_uuid_t descr_id;
+    set_uuid(descr_id.uu, descr_id_uuid_msb, descr_id_uuid_lsb);
+
+    uint16_t len = (uint16_t) env->GetArrayLength(value);
+    jbyte *p_value = env->GetByteArrayElements(value, NULL);
+    if (p_value == NULL) return;
+
+    sGattIf->client->write_descriptor(conn_id, &srvc_id, &char_id, &descr_id,
+                                    write_type, len, auth_req, (char*)p_value);
+    env->ReleaseByteArrayElements(value, p_value, 0);
+}
+
+static void gattClientRegisterForNotificationsNative(JNIEnv* env, jobject object,
+    jint clientIf, jstring address,
+    jint  service_type, jint service_id_inst_id,
+    jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
+    jint char_id_inst_id,
+    jlong char_id_uuid_lsb, jlong char_id_uuid_msb,
+    jboolean enable)
+{
+    if (!sGattIf) return;
+
+    btgatt_srvc_id_t srvc_id;
+    srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
+    srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
+    set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
+
+    btgatt_char_id_t char_id;
+    char_id.inst_id = (uint8_t) char_id_inst_id;
+    set_uuid(char_id.uuid.uu, char_id_uuid_msb, char_id_uuid_lsb);
+
+    bt_bdaddr_t bd_addr;
+    const char *c_address = env->GetStringUTFChars(address, NULL);
+    bd_addr_str_to_addr(c_address, bd_addr.address);
+
+    if (enable)
+        sGattIf->client->register_for_notification(clientIf, &bd_addr, &srvc_id, &char_id);
+    else
+        sGattIf->client->deregister_for_notification(clientIf, &bd_addr, &srvc_id, &char_id);
+}
+
+static void gattClientReadRemoteRssiNative(JNIEnv* env, jobject object, jint clientif,
+                                 jstring address)
+{
+    if (!sGattIf) return;
+
+    bt_bdaddr_t bda;
+    jstr2bdaddr(env, &bda, address);
+
+    sGattIf->client->read_remote_rssi(clientif, &bda);
+}
+
+
+/**
+ * Native server functions
+ */
+static void gattServerRegisterAppNative(JNIEnv* env, jobject object,
+                                        jlong app_uuid_lsb, jlong app_uuid_msb )
+{
+    bt_uuid_t uuid;
+    if (!sGattIf) return;
+    set_uuid(uuid.uu, app_uuid_msb, app_uuid_lsb);
+    sGattIf->server->register_server(&uuid);
+}
+
+static void gattServerUnregisterAppNative(JNIEnv* env, jobject object, jint serverIf)
+{
+    if (!sGattIf) return;
+    sGattIf->server->unregister_server(serverIf);
+}
+
+static void gattServerConnectNative(JNIEnv *env, jobject object,
+                                 jint server_if, jstring address, jboolean is_direct)
+{
+    if (!sGattIf) return;
+
+    bt_bdaddr_t bd_addr;
+    const char *c_address = env->GetStringUTFChars(address, NULL);
+    bd_addr_str_to_addr(c_address, bd_addr.address);
+
+    sGattIf->server->connect(server_if, &bd_addr, is_direct);
+}
+
+static void gattServerDisconnectNative(JNIEnv* env, jobject object, jint serverIf,
+                                  jstring address, jint conn_id)
+{
+    if (!sGattIf) return;
+    bt_bdaddr_t bda;
+    jstr2bdaddr(env, &bda, address);
+    sGattIf->server->disconnect(serverIf, &bda, conn_id);
+}
+
+static void gattServerAddServiceNative (JNIEnv *env, jobject object,
+        jint server_if, jint service_type, jint service_id_inst_id,
+        jlong service_id_uuid_lsb, jlong service_id_uuid_msb,
+        jint num_handles)
+{
+    if (!sGattIf) return;
+
+    btgatt_srvc_id_t srvc_id;
+    srvc_id.id.inst_id = (uint8_t) service_id_inst_id;
+    srvc_id.is_primary = (service_type == BTGATT_SERVICE_TYPE_PRIMARY ? 1 : 0);
+    set_uuid(srvc_id.id.uuid.uu, service_id_uuid_msb, service_id_uuid_lsb);
+
+    sGattIf->server->add_service(server_if, &srvc_id, num_handles);
+}
+
+static void gattServerAddIncludedServiceNative (JNIEnv *env, jobject object,
+        jint server_if, jint svc_handle, jint included_svc_handle)
+{
+    if (!sGattIf) return;
+    sGattIf->server->add_included_service(server_if, svc_handle,
+                                          included_svc_handle);
+}
+
+static void gattServerAddCharacteristicNative (JNIEnv *env, jobject object,
+        jint server_if, jint svc_handle,
+        jlong char_uuid_lsb, jlong char_uuid_msb,
+        jint properties, jint permissions)
+{
+    if (!sGattIf) return;
+
+    bt_uuid_t uuid;
+    set_uuid(uuid.uu, char_uuid_msb, char_uuid_lsb);
+
+    sGattIf->server->add_characteristic(server_if, svc_handle,
+                                        &uuid, properties, permissions);
+}
+
+static void gattServerAddDescriptorNative (JNIEnv *env, jobject object,
+        jint server_if, jint svc_handle,
+        jlong desc_uuid_lsb, jlong desc_uuid_msb,
+        jint permissions)
+{
+    if (!sGattIf) return;
+
+    bt_uuid_t uuid;
+    set_uuid(uuid.uu, desc_uuid_msb, desc_uuid_lsb);
+
+    sGattIf->server->add_descriptor(server_if, svc_handle, &uuid, permissions);
+}
+
+static void gattServerStartServiceNative (JNIEnv *env, jobject object,
+        jint server_if, jint svc_handle, jint transport )
+{
+    if (!sGattIf) return;
+    sGattIf->server->start_service(server_if, svc_handle, transport);
+}
+
+static void gattServerStopServiceNative (JNIEnv *env, jobject object,
+                                         jint server_if, jint svc_handle)
+{
+    if (!sGattIf) return;
+    sGattIf->server->stop_service(server_if, svc_handle);
+}
+
+static void gattServerDeleteServiceNative (JNIEnv *env, jobject object,
+                                           jint server_if, jint svc_handle)
+{
+    if (!sGattIf) return;
+    sGattIf->server->delete_service(server_if, svc_handle);
+}
+
+static void gattServerSendIndicationNative (JNIEnv *env, jobject object,
+        jint server_if, jint attr_handle, jint conn_id, jbyteArray val)
+{
+    if (!sGattIf) return;
+
+    jbyte* array = env->GetByteArrayElements(val, 0);
+    int val_len = env->GetArrayLength(val);
+
+    sGattIf->server->send_indication(server_if, attr_handle, conn_id, val_len,
+                                     /*confirm*/ 1, (char*)array);
+    env->ReleaseByteArrayElements(val, array, JNI_ABORT);
+}
+
+static void gattServerSendNotificationNative (JNIEnv *env, jobject object,
+        jint server_if, jint attr_handle, jint conn_id, jbyteArray val)
+{
+    if (!sGattIf) return;
+
+    jbyte* array = env->GetByteArrayElements(val, 0);
+    int val_len = env->GetArrayLength(val);
+
+    sGattIf->server->send_indication(server_if, attr_handle, conn_id, val_len,
+                                     /*confirm*/ 0, (char*)array);
+    env->ReleaseByteArrayElements(val, array, JNI_ABORT);
+}
+
+static void gattServerSendResponseNative (JNIEnv *env, jobject object,
+        jint server_if, jint conn_id, jint trans_id, jint status,
+        jint handle, jint offset, jbyteArray val, jint auth_req)
+{
+    if (!sGattIf) return;
+
+    btgatt_response_t response;
+
+    response.attr_value.handle = handle;
+    response.attr_value.auth_req = auth_req;
+    response.attr_value.offset = offset;
+    response.attr_value.len = 0;
+
+    if (val != NULL)
+    {
+        response.attr_value.len = (uint16_t) env->GetArrayLength(val);
+        jbyte* array = env->GetByteArrayElements(val, 0);
+
+        for (int i = 0; i != response.attr_value.len; ++i)
+            response.attr_value.value[i] = (uint8_t) array[i];
+        env->ReleaseByteArrayElements(val, array, JNI_ABORT);
+    }
+
+    sGattIf->server->send_response(conn_id, trans_id, status, &response);
+}
+
+static void gattTestNative(JNIEnv *env, jobject object, jint command,
+                           jlong uuid1_lsb, jlong uuid1_msb, jstring bda1,
+                           jint p1, jint p2, jint p3, jint p4, jint p5 )
+{
+    if (!sGattIf) return;
+
+    bt_bdaddr_t bt_bda1;
+    jstr2bdaddr(env, &bt_bda1, bda1);
+
+    bt_uuid_t uuid1;
+    set_uuid(uuid1.uu, uuid1_msb, uuid1_lsb);
+
+    btgatt_test_params_t params;
+    params.bda1 = &bt_bda1;
+    params.uuid1 = &uuid1;
+    params.u1 = p1;
+    params.u2 = p2;
+    params.u3 = p3;
+    params.u4 = p4;
+    params.u5 = p5;
+    sGattIf->client->test_command(command, &params);
+}
+
+/**
+ * JNI function definitinos
+ */
+
+static JNINativeMethod sMethods[] = {
+    {"classInitNative", "()V", (void *) classInitNative},
+    {"initializeNative", "()V", (void *) initializeNative},
+    {"cleanupNative", "()V", (void *) cleanupNative},
+
+    {"gattClientGetDeviceTypeNative", "(Ljava/lang/String;)I", (void *) gattClientGetDeviceTypeNative},
+    {"gattClientRegisterAppNative", "(JJ)V", (void *) gattClientRegisterAppNative},
+    {"gattClientUnregisterAppNative", "(I)V", (void *) gattClientUnregisterAppNative},
+    {"gattClientScanNative", "(IZ)V", (void *) gattClientScanNative},
+    {"gattClientConnectNative", "(ILjava/lang/String;Z)V", (void *) gattClientConnectNative},
+    {"gattClientDisconnectNative", "(ILjava/lang/String;I)V", (void *) gattClientDisconnectNative},
+    {"gattClientRefreshNative", "(ILjava/lang/String;)V", (void *) gattClientRefreshNative},
+    {"gattClientSearchServiceNative", "(IZJJ)V", (void *) gattClientSearchServiceNative},
+    {"gattClientGetCharacteristicNative", "(IIIJJIJJ)V", (void *) gattClientGetCharacteristicNative},
+    {"gattClientGetDescriptorNative", "(IIIJJIJJJJ)V", (void *) gattClientGetDescriptorNative},
+    {"gattClientGetIncludedServiceNative", "(IIIJJIIJJ)V", (void *) gattClientGetIncludedServiceNative},
+    {"gattClientReadCharacteristicNative", "(IIIJJIJJI)V", (void *) gattClientReadCharacteristicNative},
+    {"gattClientReadDescriptorNative", "(IIIJJIJJJJI)V", (void *) gattClientReadDescriptorNative},
+    {"gattClientWriteCharacteristicNative", "(IIIJJIJJII[B)V", (void *) gattClientWriteCharacteristicNative},
+    {"gattClientWriteDescriptorNative", "(IIIJJIJJJJII[B)V", (void *) gattClientWriteDescriptorNative},
+    {"gattClientExecuteWriteNative", "(IZ)V", (void *) gattClientExecuteWriteNative},
+    {"gattClientRegisterForNotificationsNative", "(ILjava/lang/String;IIJJIJJZ)V", (void *) gattClientRegisterForNotificationsNative},
+    {"gattClientReadRemoteRssiNative", "(ILjava/lang/String;)V", (void *) gattClientReadRemoteRssiNative},
+
+    {"gattServerRegisterAppNative", "(JJ)V", (void *) gattServerRegisterAppNative},
+    {"gattServerUnregisterAppNative", "(I)V", (void *) gattServerUnregisterAppNative},
+    {"gattServerConnectNative", "(ILjava/lang/String;Z)V", (void *) gattServerConnectNative},
+    {"gattServerDisconnectNative", "(ILjava/lang/String;I)V", (void *) gattServerDisconnectNative},
+    {"gattServerAddServiceNative", "(IIIJJI)V", (void *) gattServerAddServiceNative},
+    {"gattServerAddIncludedServiceNative", "(III)V", (void *) gattServerAddIncludedServiceNative},
+    {"gattServerAddCharacteristicNative", "(IIJJII)V", (void *) gattServerAddCharacteristicNative},
+    {"gattServerAddDescriptorNative", "(IIJJI)V", (void *) gattServerAddDescriptorNative},
+    {"gattServerStartServiceNative", "(III)V", (void *) gattServerStartServiceNative},
+    {"gattServerStopServiceNative", "(II)V", (void *) gattServerStopServiceNative},
+    {"gattServerDeleteServiceNative", "(II)V", (void *) gattServerDeleteServiceNative},
+    {"gattServerSendIndicationNative", "(III[B)V", (void *) gattServerSendIndicationNative},
+    {"gattServerSendNotificationNative", "(III[B)V", (void *) gattServerSendNotificationNative},
+    {"gattServerSendResponseNative", "(IIIIII[BI)V", (void *) gattServerSendResponseNative},
+
+    {"gattTestNative", "(IJJLjava/lang/String;IIIII)V", (void *) gattTestNative},
+};
+
+int register_com_android_bluetooth_gatt(JNIEnv* env)
+{
+    return jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/GattService",
+                                    sMethods, NELEM(sMethods));
+}
+
+}
diff --git a/res/values/config.xml b/res/values/config.xml
index 7616fb6..af90926 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -20,6 +20,7 @@
     <bool name="profile_supported_opp">true</bool>
     <bool name="profile_supported_pan">true</bool>
     <bool name="profile_supported_pbap">true</bool>
+    <bool name="profile_supported_gatt">true</bool>
     <bool name="pbap_include_photos_in_vcard">false</bool>
     <bool name="pbap_use_profile_for_owner_vcard">true</bool>
 </resources>
diff --git a/src/com/android/bluetooth/btservice/AbstractionLayer.java b/src/com/android/bluetooth/btservice/AbstractionLayer.java
old mode 100755
new mode 100644
index 24bcb5d..4e0ebd0
--- a/src/com/android/bluetooth/btservice/AbstractionLayer.java
+++ b/src/com/android/bluetooth/btservice/AbstractionLayer.java
@@ -76,4 +76,6 @@
     public static final int BT_STATUS_UNHANDLED = 8;
     public static final int BT_STATUS_AUTH_FAILURE = 9;
     public static final int BT_STATUS_RMT_DEV_DOWN = 10;
+    public static final int BT_STATUS_AUTH_REJECTED =11;
+    public static final int BT_STATUS_AUTH_TIMEOUT = 12;
 }
diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java
old mode 100755
new mode 100644
index d80ad7c..0b6478b
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -347,6 +347,10 @@
             return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN;
         else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE)
             return BluetoothDevice.UNBOND_REASON_AUTH_FAILED;
+        else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED)
+            return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED;
+        else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT)
+            return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT;
 
         /* default */
         return BluetoothDevice.UNBOND_REASON_REMOVED;
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index d8d1194..a2b2ff3 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -28,6 +28,7 @@
 import com.android.bluetooth.hfp.HeadsetService;
 import com.android.bluetooth.hid.HidService;
 import com.android.bluetooth.pan.PanService;
+import com.android.bluetooth.gatt.GattService;
 
 public class Config {
     private static final String TAG = "AdapterServiceConfig";
@@ -42,7 +43,8 @@
         A2dpService.class,
         HidService.class,
         HealthService.class,
-        PanService.class
+        PanService.class,
+        GattService.class
     };
     /**
      * Resource flag to indicate whether profile is supported or not.
@@ -52,7 +54,8 @@
         R.bool.profile_supported_a2dp,
         R.bool.profile_supported_hid,
         R.bool.profile_supported_hdp,
-        R.bool.profile_supported_pan
+        R.bool.profile_supported_pan,
+        R.bool.profile_supported_gatt
     };
 
     private static Class[] SUPPORTED_PROFILES = new Class[0];
diff --git a/src/com/android/bluetooth/gatt/ContextMap.java b/src/com/android/bluetooth/gatt/ContextMap.java
new file mode 100644
index 0000000..ca265b1
--- /dev/null
+++ b/src/com/android/bluetooth/gatt/ContextMap.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+     package com.android.bluetooth.gatt;
+
+
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Helper class that keeps track of registered GATT applications.
+ * This class manages application callbacks and keeps track of GATT connections.
+ * @hide
+ */
+/*package*/ class ContextMap<T> {
+    private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap";
+
+    /**
+     * Connection class helps map connection IDs to device addresses.
+     */
+    class Connection {
+        int connId;
+        String address;
+        int appId;
+
+        Connection(int connId, String address,int appId) {
+            this.connId = connId;
+            this.address = address;
+            this.appId = appId;
+        }
+    }
+
+    /**
+     * Application entry mapping UUIDs to appIDs and callbacks.
+     */
+    class App {
+        /** The UUID of the application */
+        UUID uuid;
+
+        /** The id of the application */
+        int id;
+
+        /** Application callbacks */
+        T callback;
+
+        /**
+         * Creates a new app context.
+         */
+        App(UUID uuid, T callback) {
+            this.uuid = uuid;
+            this.callback = callback;
+        }
+    }
+
+    /** Our internal application list */
+    List<App> mApps = new ArrayList<App>();
+
+    /** Internal list of connected devices **/
+    Set<Connection> mConnections = new HashSet<Connection>();
+
+    /**
+     * Add an entry to the application context list.
+     */
+    void add(UUID uuid, T callback) {
+        mApps.add(new App(uuid, callback));
+    }
+
+    /**
+     * Remove the context for a given application ID.
+     */
+    void remove(int id) {
+        Iterator<App> i = mApps.iterator();
+        while(i.hasNext()) {
+            App entry = i.next();
+            if (entry.id == id) {
+                i.remove();
+                break;
+            }
+        }
+    }
+
+    /**
+     * Add a new connection for a given application ID.
+     */
+    void addConnection(int id, int connId, String address) {
+        App entry = getById(id);
+        if (entry != null){
+            mConnections.add(new Connection(connId, address, id));
+        }
+    }
+
+    /**
+     * Remove a connection with the given ID.
+     */
+    void removeConnection(int id, int connId) {
+        Iterator<Connection> i = mConnections.iterator();
+        while(i.hasNext()) {
+            Connection connection = i.next();
+            if (connection.connId == connId) {
+                i.remove();
+                break;
+            }
+        }
+    }
+
+    /**
+     * Get an application context by ID.
+     */
+    App getById(int id) {
+        Iterator<App> i = mApps.iterator();
+        while(i.hasNext()) {
+            App entry = i.next();
+            if (entry.id == id) return entry;
+        }
+        Log.e(TAG, "Context not found for ID " + id);
+        return null;
+    }
+
+    /**
+     * Get an application context by UUID.
+     */
+    App getByUuid(UUID uuid) {
+        Iterator<App> i = mApps.iterator();
+        while(i.hasNext()) {
+            App entry = i.next();
+            if (entry.uuid.equals(uuid)) return entry;
+        }
+        Log.e(TAG, "Context not found for UUID " + uuid);
+        return null;
+    }
+
+    /**
+     * Get the device addresses for all connected devices
+     */
+    Set<String> getConnectedDevices() {
+        Set<String> addresses = new HashSet<String>();
+        Iterator<Connection> i = mConnections.iterator();
+        while(i.hasNext()) {
+            Connection connection = i.next();
+            addresses.add(connection.address);
+        }
+        return addresses;
+    }
+
+    /**
+     * Get an application context by a connection ID.
+     */
+    App getByConnId(int connId) {
+        Iterator<Connection> ii = mConnections.iterator();
+        while(ii.hasNext()) {
+            Connection connection = ii.next();
+            if (connection.connId == connId){
+                return getById(connection.appId);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a connection ID for a given device address.
+     */
+    Integer connIdByAddress(int id, String address) {
+        App entry = getById(id);
+        if (entry == null) return null;
+
+        Iterator<Connection> i = mConnections.iterator();
+        while(i.hasNext()) {
+            Connection connection = i.next();
+            if (connection.address.equals(address) && connection.appId == id)
+                return connection.connId;
+        }
+        return null;
+    }
+
+    /**
+     * Returns the device address for a given connection ID.
+     */
+    String addressByConnId(int connId) {
+        Iterator<Connection> i = mConnections.iterator();
+        while(i.hasNext()) {
+            Connection connection = i.next();
+            if (connection.connId == connId) return connection.address;
+        }
+        return null;
+    }
+
+    List<Connection> getConnectionByApp(int appId) {
+        List<Connection> currentConnections = new ArrayList<Connection>();
+        Iterator<Connection> i = mConnections.iterator();
+        while(i.hasNext()) {
+            Connection connection = i.next();
+            if (connection.appId == appId)
+                currentConnections.add(connection);
+        }
+        return currentConnections;
+    }
+
+    /**
+     * Erases all application context entries.
+     */
+    void clear() {
+        mApps.clear();
+        mConnections.clear();
+    }
+
+    /**
+     * Logs debug information.
+     */
+    void dump() {
+        StringBuilder b = new StringBuilder();
+        b.append(  "-------------- GATT Context Map ----------------");
+        b.append("\nEntries: " + mApps.size());
+
+        Iterator<App> i = mApps.iterator();
+        while(i.hasNext()) {
+            App entry = i.next();
+            List<Connection> connections = getConnectionByApp(entry.id);
+
+            b.append("\n\nApplication Id: " + entry.id);
+            b.append("\nUUID: " + entry.uuid);
+            b.append("\nConnections: " + connections.size());
+
+            Iterator<Connection> ii = connections.iterator();
+            while(ii.hasNext()) {
+                Connection connection = ii.next();
+                b.append("\n  " + connection.connId + ": " + connection.address);
+            }
+        }
+
+        b.append("\n------------------------------------------------");
+        Log.d(TAG, b.toString());
+    }
+}
diff --git a/src/com/android/bluetooth/gatt/GattDebugUtils.java b/src/com/android/bluetooth/gatt/GattDebugUtils.java
new file mode 100644
index 0000000..3911e6c
--- /dev/null
+++ b/src/com/android/bluetooth/gatt/GattDebugUtils.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.gatt;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import java.util.UUID;
+
+/**
+ * Helper class containing useful tools for GATT service debugging.
+ */
+/*package*/ class GattDebugUtils {
+    private static final String TAG = GattServiceConfig.TAG_PREFIX + "DebugUtils";
+    private static final boolean DEBUG_ADMIN = GattServiceConfig.DEBUG_ADMIN;
+
+    private static final String ACTION_DEBUG_DUMP_CLIENTMAP =
+                                "android.bluetooth.action.DEBUG_DUMP_CLIENTMAP";
+    private static final String ACTION_DEBUG_DUMP_SERVERMAP =
+                                "android.bluetooth.action.DEBUG_DUMP_SERVERMAP";
+    private static final String ACTION_DEBUG_DUMP_HANDLEMAP =
+                                "android.bluetooth.action.DEBUG_DUMP_HANDLEMAP";
+
+    private static final String ACTION_GATT_PAIRING_CONFIG =
+                                "android.bluetooth.action.GATT_PAIRING_CONFIG";
+
+    private static final String ACTION_GATT_TEST_USAGE =
+                                "android.bluetooth.action.GATT_TEST_USAGE";
+    private static final String ACTION_GATT_TEST_ENABLE =
+                                "android.bluetooth.action.GATT_TEST_ENABLE";
+    private static final String ACTION_GATT_TEST_CONNECT =
+                                "android.bluetooth.action.GATT_TEST_CONNECT";
+    private static final String ACTION_GATT_TEST_DISCONNECT =
+                                "android.bluetooth.action.GATT_TEST_DISCONNECT";
+    private static final String ACTION_GATT_TEST_DISCOVER =
+                                "android.bluetooth.action.GATT_TEST_DISCOVER";
+
+    private static final String EXTRA_ENABLE = "enable";
+    private static final String EXTRA_ADDRESS = "address";
+    private static final String EXTRA_UUID = "uuid";
+    private static final String EXTRA_TYPE = "type";
+    private static final String EXTRA_SHANDLE = "start";
+    private static final String EXTRA_EHANDLE = "end";
+    private static final String EXTRA_AUTH_REQ = "auth_req";
+    private static final String EXTRA_IO_CAP = "io_cap";
+    private static final String EXTRA_INIT_KEY = "init_key";
+    private static final String EXTRA_RESP_KEY = "resp_key";
+    private static final String EXTRA_MAX_KEY = "max_key";
+
+    /**
+     * Handles intents passed in via GattService.onStartCommand().
+     * This allows sending debug actions to the running service.
+     * To trigger a debug action, invoke the following shell command:
+     *
+     *   adb shell am startservice -a <action> <component>
+     *
+     * Where <action> represents one of the ACTION_* constants defines above
+     * and  <component> identifies the GATT service.
+     *
+     * For example:
+     *   import com.android.bluetooth.gatt.GattService;
+     */
+    static boolean handleDebugAction(GattService svc, Intent intent) {
+        if (!DEBUG_ADMIN) return false;
+
+        String action = intent.getAction();
+        Log.d(TAG, "handleDebugAction() action=" + action);
+
+        /*
+         * Debug log functinos
+         */
+
+        if (ACTION_DEBUG_DUMP_CLIENTMAP.equals(action)) {
+            svc.mClientMap.dump();
+
+        } else if (ACTION_DEBUG_DUMP_SERVERMAP.equals(action)) {
+            svc.mServerMap.dump();
+
+        } else if (ACTION_DEBUG_DUMP_HANDLEMAP.equals(action)) {
+            svc.mHandleMap.dump();
+
+        /*
+         * PTS test commands
+         */
+
+        } else if (ACTION_GATT_TEST_USAGE.equals(action)) {
+            logUsageInfo();
+
+        } else if (ACTION_GATT_TEST_ENABLE.equals(action)) {
+            boolean bEnable = intent.getBooleanExtra(EXTRA_ENABLE, true);
+            svc.gattTestCommand( 0x01, null,null, bEnable ? 1 : 0, 0,0,0,0);
+
+        } else if (ACTION_GATT_TEST_CONNECT.equals(action)) {
+            String address = intent.getStringExtra(EXTRA_ADDRESS);
+            int type = intent.getIntExtra(EXTRA_TYPE, 2 /* LE device */);
+            svc.gattTestCommand( 0x02, null, address, type, 0,0,0,0);
+
+        } else if (ACTION_GATT_TEST_DISCONNECT.equals(action)) {
+            svc.gattTestCommand( 0x03, null, null, 0,0,0,0,0);
+
+        } else if (ACTION_GATT_TEST_DISCOVER.equals(action)) {
+            UUID uuid = getUuidExtra(intent);
+            int type = intent.getIntExtra(EXTRA_TYPE, 1 /* All services */);
+            int shdl = getHandleExtra(intent, EXTRA_SHANDLE, 1);
+            int ehdl = getHandleExtra(intent, EXTRA_EHANDLE, 0xFFFF);
+            svc.gattTestCommand( 0x04, uuid, null, type, shdl, ehdl, 0,0);
+
+        } else if (ACTION_GATT_PAIRING_CONFIG.equals(action)) {
+            int auth_req = intent.getIntExtra(EXTRA_AUTH_REQ, 5);
+            int io_cap = intent.getIntExtra(EXTRA_IO_CAP, 4);
+            int init_key = intent.getIntExtra(EXTRA_INIT_KEY, 7);
+            int resp_key = intent.getIntExtra(EXTRA_RESP_KEY, 7);
+            int max_key = intent.getIntExtra(EXTRA_MAX_KEY, 16);
+            svc.gattTestCommand( 0xF0, null, null, auth_req, io_cap, init_key,
+                                 resp_key, max_key);
+
+        } else {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Attempts to retrieve an extra from an intent first as hex value,
+     * then as an ineger.
+     * @hide
+     */
+    static private int getHandleExtra(Intent intent, String extra, int default_value) {
+        Bundle extras = intent.getExtras();
+        Object uuid = extras != null ? extras.get(extra) : null;
+        if (uuid != null && uuid.getClass().getName().equals("java.lang.String")) {
+            try
+            {
+                return Integer.parseInt(extras.getString(extra), 16);
+            } catch (NumberFormatException e) {
+                return default_value;
+            }
+        }
+
+        return intent.getIntExtra(extra, default_value);
+    }
+
+    /**
+     * Retrieves the EXTRA_UUID parameter.
+     * If a string of length 4 is detected, a 16-bit hex UUID is assumed and
+     * the default Bluetooth UUID is appended.
+     * @hide
+     */
+    static private UUID getUuidExtra(Intent intent) {
+        String uuidStr = intent.getStringExtra(EXTRA_UUID);
+        if (uuidStr != null && uuidStr.length() == 4) {
+            uuidStr = String.format("0000%s-0000-1000-8000-00805f9b34fb", uuidStr);
+        }
+        return (uuidStr != null) ? UUID.fromString(uuidStr) : null;
+    }
+
+    /**
+     * Log usage information.
+     * @hide
+     */
+    static private void logUsageInfo() {
+        StringBuilder b = new StringBuilder();
+        b.append(  "------------ GATT TEST ACTIONS  ----------------");
+        b.append("\nGATT_TEST_ENABLE");
+        b.append("\n  [--ez enable <bool>] Enable or disable,");
+        b.append("\n                       defaults to true (enable).\n");
+        b.append("\nGATT_TEST_CONNECT");
+        b.append("\n   --es address <bda>");
+        b.append("\n  [--ei type <type>]   Default is 2 (LE Only)\n");
+        b.append("\nGATT_TEST_DISCONNECT\n");
+        b.append("\nGATT_TEST_DISCOVER");
+        b.append("\n  [--ei type <type>]   Possible values:");
+        b.append("\n                         1 = Discover all services (default)");
+        b.append("\n                         2 = Discover services by UUID");
+        b.append("\n                         3 = Discover included services");
+        b.append("\n                         4 = Discover characteristics");
+        b.append("\n                         5 = Discover descriptors\n");
+        b.append("\n  [--es uuid <uuid>]   Optional; Can be either full 128-bit");
+        b.append("\n                       UUID hex string, or 4 hex characters");
+        b.append("\n                       for 16-bit UUIDs.\n");
+        b.append("\n  [--ei start <hdl>]   Start of handle range (default 1)");
+        b.append("\n  [--ei end <hdl>]     End of handle range (default 65355)");
+        b.append("\n    or");
+        b.append("\n  [--es start <hdl>]   Start of handle range (hex format)");
+        b.append("\n  [--es end <hdl>]     End of handle range (hex format)\n");
+        b.append("\nGATT_PAIRING_CONFIG");
+        b.append("\n  [--ei auth_req]      Authentication flag (default 5)");
+        b.append("\n  [--ei io_cap]        IO capabilities (default 4)");
+        b.append("\n  [--ei init_key]      Initial key size (default 7)");
+        b.append("\n  [--ei resp_key]      Response key size (default 7)");
+        b.append("\n  [--ei max_key]       Maximum key size (default 16)");
+        b.append("\n------------------------------------------------");
+        Log.i(TAG, b.toString());
+    }
+}
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
new file mode 100644
index 0000000..56db06d
--- /dev/null
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -0,0 +1,1676 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.gatt;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Intent;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder;
+import android.bluetooth.IBluetoothGatt;
+import android.bluetooth.IBluetoothGattCallback;
+import android.bluetooth.IBluetoothGattServerCallback;
+
+/**
+ * Provides Bluetooth Gatt profile, as a service in
+ * the Bluetooth application.
+ * @hide
+ */
+public class GattService extends ProfileService {
+    private static final boolean DBG = GattServiceConfig.DBG;
+    private static final String TAG = GattServiceConfig.TAG_PREFIX + "GattService";
+    BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+    /**
+     * Search queue to serialize remote onbject inspection.
+     */
+    SearchQueue mSearchQueue = new SearchQueue();
+
+    /**
+     * List of our registered clients.
+     */
+
+    class ClientMap extends ContextMap<IBluetoothGattCallback> {}
+    ClientMap mClientMap = new ClientMap();
+
+    /**
+     * List of our registered server apps.
+     */
+    class ServerMap extends ContextMap<IBluetoothGattServerCallback> {}
+    ServerMap mServerMap = new ServerMap();
+
+    /**
+     * Server handle map.
+     */
+    HandleMap mHandleMap = new HandleMap();
+
+    /**
+     * Pending service declaration queue
+     */
+    private List<ServiceDeclaration> mServiceDeclarations = new ArrayList<ServiceDeclaration>();
+
+    private ServiceDeclaration addDeclaration() {
+        synchronized (mServiceDeclarations) {
+            mServiceDeclarations.add(new ServiceDeclaration());
+        }
+        return getActiveDeclaration();
+    }
+
+    private ServiceDeclaration getActiveDeclaration() {
+        synchronized (mServiceDeclarations) {
+            if (mServiceDeclarations.size() > 0)
+                return mServiceDeclarations.get(mServiceDeclarations.size() - 1);
+        }
+        return null;
+    }
+
+    private ServiceDeclaration getPendingDeclaration() {
+        synchronized (mServiceDeclarations) {
+            if (mServiceDeclarations.size() > 0)
+                return mServiceDeclarations.get(0);
+        }
+        return null;
+    }
+
+    private void removePendingDeclaration() {
+        synchronized (mServiceDeclarations) {
+            if (mServiceDeclarations.size() > 0)
+                mServiceDeclarations.remove(0);
+        }
+    }
+
+    /**
+     * List of clients intereste in scan results.
+     */
+    private List<ScanClient> mScanQueue = new ArrayList<ScanClient>();
+
+    private ScanClient getScanClient(int appIf, boolean isServer) {
+        for(ScanClient client : mScanQueue) {
+            if (client.appIf == appIf && client.isServer == isServer) {
+                return client;
+            }
+        }
+        return null;
+    }
+
+    private void removeScanClient(int appIf, boolean isServer) {
+        for(ScanClient client : mScanQueue) {
+            if (client.appIf == appIf && client.isServer == isServer) {
+                mScanQueue.remove(client);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Reliable write queue
+     */
+    private Set<String> mReliableQueue = new HashSet<String>();
+
+    static {
+        classInitNative();
+    }
+
+    protected String getName() {
+        return TAG;
+    }
+
+    protected IProfileServiceBinder initBinder() {
+        return new BluetoothGattBinder(this);
+    }
+
+    protected boolean start() {
+        if (DBG) Log.d(TAG, "start()");
+        initializeNative();
+        return true;
+    }
+
+    protected boolean stop() {
+        if (DBG) Log.d(TAG, "stop()");
+        mClientMap.clear();
+        mServerMap.clear();
+        mSearchQueue.clear();
+        mScanQueue.clear();
+        mHandleMap.clear();
+        mServiceDeclarations.clear();
+        mReliableQueue.clear();
+        return true;
+    }
+
+    protected boolean cleanup() {
+        if (DBG) Log.d(TAG, "cleanup()");
+        cleanupNative();
+        return true;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (GattDebugUtils.handleDebugAction(this, intent)) {
+            return Service.START_NOT_STICKY;
+        }
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    /**
+     * Handlers for incoming service calls
+     */
+    private static class BluetoothGattBinder extends IBluetoothGatt.Stub implements IProfileServiceBinder {
+        private GattService mService;
+
+        public BluetoothGattBinder(GattService svc) {
+            mService = svc;
+        }
+
+        public boolean cleanup()  {
+            mService = null;
+            return true;
+        }
+
+        private GattService getService() {
+            if (mService  != null && mService.isAvailable()) return mService;
+            Log.e(TAG, "getService() - Service requested, but not available!");
+            return null;
+        }
+
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+            GattService service = getService();
+            if (service == null) return new ArrayList<BluetoothDevice>();
+            return service.getDevicesMatchingConnectionStates(states);
+        }
+
+        public void registerClient(ParcelUuid uuid, IBluetoothGattCallback callback) {
+            GattService service = getService();
+            if (service == null) return;
+            service.registerClient(uuid.getUuid(), callback);
+        }
+
+        public void unregisterClient(int clientIf) {
+            GattService service = getService();
+            if (service == null) return;
+            service.unregisterClient(clientIf);
+        }
+
+        public void startScan(int appIf, boolean isServer) {
+            GattService service = getService();
+            if (service == null) return;
+            service.startScan(appIf, isServer);
+        }
+
+        public void startScanWithUuids(int appIf, boolean isServer, ParcelUuid[] ids) {
+            GattService service = getService();
+            if (service == null) return;
+            UUID[] uuids = new UUID[ids.length];
+            for(int i = 0; i != ids.length; ++i) {
+                uuids[i] = ids[i].getUuid();
+            }
+            service.startScanWithUuids(appIf, isServer, uuids);
+        }
+
+        public void stopScan(int appIf, boolean isServer) {
+            GattService service = getService();
+            if (service == null) return;
+            service.stopScan(appIf, isServer);
+        }
+
+        public void clientConnect(int clientIf, String address, boolean isDirect) {
+            GattService service = getService();
+            if (service == null) return;
+            service.clientConnect(clientIf, address, isDirect);
+        }
+
+        public void clientDisconnect(int clientIf, String address) {
+            GattService service = getService();
+            if (service == null) return;
+            service.clientDisconnect(clientIf, address);
+        }
+
+        public void refreshDevice(int clientIf, String address) {
+            GattService service = getService();
+            if (service == null) return;
+            service.refreshDevice(clientIf, address);
+        }
+
+        public void discoverServices(int clientIf, String address) {
+            GattService service = getService();
+            if (service == null) return;
+            service.discoverServices(clientIf, address);
+        }
+
+        public void readCharacteristic(int clientIf, String address, int srvcType,
+                                       int srvcInstanceId, ParcelUuid srvcId,
+                                       int charInstanceId, ParcelUuid charId,
+                                       int authReq) {
+            GattService service = getService();
+            if (service == null) return;
+            service.readCharacteristic(clientIf, address, srvcType, srvcInstanceId,
+                                       srvcId.getUuid(), charInstanceId,
+                                       charId.getUuid(), authReq);
+        }
+
+        public void writeCharacteristic(int clientIf, String address, int srvcType,
+                             int srvcInstanceId, ParcelUuid srvcId,
+                             int charInstanceId, ParcelUuid charId,
+                             int writeType, int authReq, byte[] value) {
+            GattService service = getService();
+            if (service == null) return;
+            service.writeCharacteristic(clientIf, address, srvcType, srvcInstanceId,
+                                       srvcId.getUuid(), charInstanceId,
+                                       charId.getUuid(), writeType, authReq,
+                                       value);
+        }
+
+        public void readDescriptor(int clientIf, String address, int srvcType,
+                            int srvcInstanceId, ParcelUuid srvcId,
+                            int charInstanceId, ParcelUuid charId,
+                            ParcelUuid descrId, int authReq) {
+            GattService service = getService();
+            if (service == null) return;
+            service.readDescriptor(clientIf, address, srvcType, srvcInstanceId,
+                                       srvcId.getUuid(), charInstanceId,
+                                       charId.getUuid(), descrId.getUuid(),
+                                       authReq);
+        }
+
+        public void writeDescriptor(int clientIf, String address, int srvcType,
+                            int srvcInstanceId, ParcelUuid srvcId,
+                            int charInstanceId, ParcelUuid charId,
+                            ParcelUuid descrId, int writeType,
+                            int authReq, byte[] value) {
+            GattService service = getService();
+            if (service == null) return;
+            service.writeDescriptor(clientIf, address, srvcType, srvcInstanceId,
+                                       srvcId.getUuid(), charInstanceId,
+                                       charId.getUuid(), descrId.getUuid(),
+                                       writeType, authReq, value);
+        }
+
+        public void beginReliableWrite(int clientIf, String address) {
+            GattService service = getService();
+            if (service == null) return;
+            service.beginReliableWrite(clientIf, address);
+        }
+
+        public void endReliableWrite(int clientIf, String address, boolean execute) {
+            GattService service = getService();
+            if (service == null) return;
+            service.endReliableWrite(clientIf, address, execute);
+        }
+
+        public void registerForNotification(int clientIf, String address, int srvcType,
+                            int srvcInstanceId, ParcelUuid srvcId,
+                            int charInstanceId, ParcelUuid charId,
+                            boolean enable) {
+            GattService service = getService();
+            if (service == null) return;
+            service.registerForNotification(clientIf, address, srvcType, srvcInstanceId,
+                                       srvcId.getUuid(), charInstanceId,
+                                       charId.getUuid(), enable);
+        }
+
+        public void readRemoteRssi(int clientIf, String address) {
+            GattService service = getService();
+            if (service == null) return;
+            service.readRemoteRssi(clientIf, address);
+        }
+
+        public void registerServer(ParcelUuid uuid, IBluetoothGattServerCallback callback) {
+            GattService service = getService();
+            if (service == null) return;
+            service.registerServer(uuid.getUuid(), callback);
+        }
+
+        public void unregisterServer(int serverIf) {
+            GattService service = getService();
+            if (service == null) return;
+            service.unregisterServer(serverIf);
+        }
+
+        public void serverConnect(int serverIf, String address, boolean isDirect) {
+            GattService service = getService();
+            if (service == null) return;
+            service.serverConnect(serverIf, address, isDirect);
+        }
+
+        public void serverDisconnect(int serverIf, String address) {
+            GattService service = getService();
+            if (service == null) return;
+            service.serverDisconnect(serverIf, address);
+        }
+
+        public void beginServiceDeclaration(int serverIf, int srvcType,
+                                            int srvcInstanceId, int minHandles,
+                                            ParcelUuid srvcId) {
+            GattService service = getService();
+            if (service == null) return;
+            service.beginServiceDeclaration(serverIf, srvcType, srvcInstanceId,
+                               minHandles, srvcId.getUuid());
+        }
+
+        public void addIncludedService(int serverIf, int srvcType,
+                            int srvcInstanceId, ParcelUuid srvcId) {
+            GattService service = getService();
+            if (service == null) return;
+            service.addIncludedService(serverIf, srvcType, srvcInstanceId,
+                                            srvcId.getUuid());
+        }
+
+        public void addCharacteristic(int serverIf, ParcelUuid charId,
+                            int properties, int permissions) {
+            GattService service = getService();
+            if (service == null) return;
+            service.addCharacteristic(serverIf, charId.getUuid(), properties,
+                                      permissions);
+        }
+
+        public void addDescriptor(int serverIf, ParcelUuid descId,
+                           int permissions) {
+            GattService service = getService();
+            if (service == null) return;
+            service.addDescriptor(serverIf, descId.getUuid(), permissions);
+        }
+
+        public void endServiceDeclaration(int serverIf) {
+            GattService service = getService();
+            if (service == null) return;
+            service.endServiceDeclaration(serverIf);
+        }
+
+        public void removeService(int serverIf, int srvcType,
+                           int srvcInstanceId, ParcelUuid srvcId) {
+            GattService service = getService();
+            if (service == null) return;
+            service.removeService(serverIf, srvcType, srvcInstanceId,
+                                  srvcId.getUuid());
+        }
+
+        public void clearServices(int serverIf) {
+            GattService service = getService();
+            if (service == null) return;
+            service.clearServices(serverIf);
+        }
+
+        public void sendResponse(int serverIf, String address, int requestId,
+                                 int status, int offset, byte[] value) {
+            GattService service = getService();
+            if (service == null) return;
+            service.sendResponse(serverIf, address, requestId, status, offset, value);
+        }
+
+        public void sendNotification(int serverIf, String address, int srvcType,
+                                              int srvcInstanceId, ParcelUuid srvcId,
+                                              int charInstanceId, ParcelUuid charId,
+                                              boolean confirm, byte[] value) {
+            GattService service = getService();
+            if (service == null) return;
+            service.sendNotification(serverIf, address, srvcType, srvcInstanceId,
+                srvcId.getUuid(), charInstanceId, charId.getUuid(), confirm, value);
+        }
+
+    };
+
+    /**************************************************************************
+     * Callback functions - CLIENT
+     *************************************************************************/
+
+    void onScanResult(String address, int rssi, byte[] adv_data) {
+        if (DBG) Log.d(TAG, "onScanResult() - address=" + address
+                    + ", rssi=" + rssi);
+
+        List<UUID> remoteUuids = parseUuids(adv_data);
+        for (ScanClient client : mScanQueue) {
+            if (client.uuids.length > 0) {
+                int matches = 0;
+                for (UUID search : client.uuids) {
+                    for (UUID remote: remoteUuids) {
+                        if (remote.equals(search)) {
+                            ++matches;
+                            break; // Only count 1st match in case of duplicates
+                        }
+                    }
+                }
+
+                if (matches < client.uuids.length) continue;
+            }
+
+            if (!client.isServer) {
+                ClientMap.App app = mClientMap.getById(client.appIf);
+                if (app != null) {
+                    try {
+                        app.callback.onScanResult(address, rssi, adv_data);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Exception: " + e);
+                        mClientMap.remove(client.appIf);
+                        mScanQueue.remove(client);
+                    }
+                }
+            } else {
+                ServerMap.App app = mServerMap.getById(client.appIf);
+                if (app != null) {
+                    try {
+                        app.callback.onScanResult(address, rssi, adv_data);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Exception: " + e);
+                        mServerMap.remove(client.appIf);
+                        mScanQueue.remove(client);
+                    }
+                }
+            }
+        }
+    }
+
+    void onClientRegistered(int status, int clientIf, long uuidLsb, long uuidMsb)
+            throws RemoteException {
+        UUID uuid = new UUID(uuidMsb, uuidLsb);
+        if (DBG) Log.d(TAG, "onClientRegistered() - UUID=" + uuid + ", clientIf=" + clientIf);
+        ClientMap.App app = mClientMap.getByUuid(uuid);
+        if (app != null) {
+            app.id = clientIf;
+            app.callback.onClientRegistered(status, clientIf);
+        }
+    }
+
+    void onConnected(int clientIf, int connId, int status, String address)
+            throws RemoteException  {
+        if (DBG) Log.d(TAG, "onConnected() - clientIf=" + clientIf
+            + ", connId=" + connId + ", address=" + address);
+
+        if (status == 0) mClientMap.addConnection(clientIf, connId, address);
+        ClientMap.App app = mClientMap.getById(clientIf);
+        if (app != null) {
+            app.callback.onClientConnectionState(status, clientIf, true, address);
+        }
+    }
+
+    void onDisconnected(int clientIf, int connId, int status, String address)
+            throws RemoteException {
+        if (DBG) Log.d(TAG, "onDisconnected() - clientIf=" + clientIf
+            + ", connId=" + connId + ", address=" + address);
+
+        mClientMap.removeConnection(clientIf, connId);
+        mSearchQueue.removeConnId(connId);
+        ClientMap.App app = mClientMap.getById(clientIf);
+        if (app != null) {
+            app.callback.onClientConnectionState(status, clientIf, false, address);
+        }
+    }
+
+    void onSearchCompleted(int connId, int status) throws RemoteException {
+        if (DBG) Log.d(TAG, "onSearchCompleted() - connId=" + connId+ ", status=" + status);
+        // We got all services, now let's explore characteristics...
+        continueSearch(connId, status);
+    }
+
+    void onSearchResult(int connId, int srvcType,
+            int srvcInstId, long srvcUuidLsb, long srvcUuidMsb)
+            throws RemoteException {
+        UUID uuid = new UUID(srvcUuidMsb, srvcUuidLsb);
+        String address = mClientMap.addressByConnId(connId);
+
+        if (DBG) Log.d(TAG, "onSearchResult() - address=" + address + ", uuid=" + uuid);
+
+        mSearchQueue.add(connId, srvcType, srvcInstId, srvcUuidLsb, srvcUuidMsb);
+
+        ClientMap.App app = mClientMap.getByConnId(connId);
+        if (app != null) {
+            app.callback.onGetService(address, srvcType, srvcInstId,
+                                        new ParcelUuid(uuid));
+        }
+    }
+
+    void onGetCharacteristic(int connId, int status, int srvcType,
+            int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
+            int charInstId, long charUuidLsb, long charUuidMsb,
+            int charProp) throws RemoteException {
+
+        UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
+        UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
+        String address = mClientMap.addressByConnId(connId);
+
+        if (DBG) Log.d(TAG, "onGetCharacteristic() - address=" + address
+            + ", status=" + status + ", charUuid=" + charUuid + ", prop=" + charProp);
+
+        if (status == 0) {
+            mSearchQueue.add(connId, srvcType,
+                            srvcInstId, srvcUuidLsb, srvcUuidMsb,
+                            charInstId, charUuidLsb, charUuidMsb);
+
+            ClientMap.App app = mClientMap.getByConnId(connId);
+            if (app != null) {
+                app.callback.onGetCharacteristic(address, srvcType,
+                            srvcInstId, new ParcelUuid(srvcUuid),
+                            charInstId, new ParcelUuid(charUuid), charProp);
+            }
+
+            // Get next characteristic in the current service
+            gattClientGetCharacteristicNative(connId, srvcType,
+                                        srvcInstId, srvcUuidLsb, srvcUuidMsb,
+                                        charInstId, charUuidLsb, charUuidMsb);
+        } else {
+            // Check for included services next
+            gattClientGetIncludedServiceNative(connId,
+                srvcType, srvcInstId, srvcUuidLsb, srvcUuidMsb,
+                0,0,0,0);
+        }
+    }
+
+    void onGetDescriptor(int connId, int status, int srvcType,
+            int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
+            int charInstId, long charUuidLsb, long charUuidMsb,
+            long descrUuidLsb, long descrUuidMsb) throws RemoteException {
+
+        UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
+        UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
+        UUID descUuid = new UUID(descrUuidMsb, descrUuidLsb);
+        String address = mClientMap.addressByConnId(connId);
+
+        if (DBG) Log.d(TAG, "onGetDescriptor() - address=" + address
+            + ", status=" + status + ", descUuid=" + descUuid);
+
+        if (status == 0) {
+            ClientMap.App app = mClientMap.getByConnId(connId);
+            if (app != null) {
+                app.callback.onGetDescriptor(address, srvcType,
+                            srvcInstId, new ParcelUuid(srvcUuid),
+                            charInstId, new ParcelUuid(charUuid),
+                            new ParcelUuid(descUuid));
+            }
+
+            // Get next descriptor for the current characteristic
+            gattClientGetDescriptorNative(connId, srvcType,
+                                    srvcInstId, srvcUuidLsb, srvcUuidMsb,
+                                    charInstId, charUuidLsb, charUuidMsb,
+                                    descrUuidLsb, descrUuidMsb);
+        } else {
+            // Explore the next service
+            continueSearch(connId, 0);
+        }
+    }
+
+    void onGetIncludedService(int connId, int status, int srvcType,
+            int srvcInstId, long srvcUuidLsb, long srvcUuidMsb, int inclSrvcType,
+            int inclSrvcInstId, long inclSrvcUuidLsb, long inclSrvcUuidMsb)
+            throws RemoteException {
+        UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
+        UUID inclSrvcUuid = new UUID(inclSrvcUuidMsb, inclSrvcUuidLsb);
+        String address = mClientMap.addressByConnId(connId);
+
+        if (DBG) Log.d(TAG, "onGetIncludedService() - address=" + address
+            + ", status=" + status + ", uuid=" + srvcUuid
+            + ", inclUuid=" + inclSrvcUuid);
+
+        if (status == 0) {
+            ClientMap.App app = mClientMap.getByConnId(connId);
+            if (app != null) {
+                app.callback.onGetIncludedService(address,
+                    srvcType, srvcInstId, new ParcelUuid(srvcUuid),
+                    inclSrvcType, inclSrvcInstId, new ParcelUuid(inclSrvcUuid));
+            }
+
+            // Find additional included services
+            gattClientGetIncludedServiceNative(connId,
+                srvcType, srvcInstId, srvcUuidLsb, srvcUuidMsb,
+                inclSrvcType, inclSrvcInstId, inclSrvcUuidLsb, inclSrvcUuidMsb);
+        } else {
+            // Discover descriptors now
+            continueSearch(connId, 0);
+        }
+    }
+
+    void onRegisterForNotifications(int connId, int status, int registered, int srvcType,
+            int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
+            int charInstId, long charUuidLsb, long charUuidMsb) {
+        UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
+        UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
+        String address = mClientMap.addressByConnId(connId);
+
+        if (DBG) Log.d(TAG, "onRegisterForNotifications() - address=" + address
+            + ", status=" + status + ", registered=" + registered
+            + ", charUuid=" + charUuid);
+    }
+
+    void onNotify(int connId, String address, int srvcType,
+            int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
+            int charInstId, long charUuidLsb, long charUuidMsb,
+            boolean isNotify, byte[] data) throws RemoteException {
+        UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
+        UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
+
+        if (DBG) Log.d(TAG, "onNotify() - address=" + address
+            + ", charUuid=" + charUuid + ", length=" + data.length);
+
+        ClientMap.App app = mClientMap.getByConnId(connId);
+        if (app != null) {
+            app.callback.onNotify(address, srvcType,
+                        srvcInstId, new ParcelUuid(srvcUuid),
+                        charInstId, new ParcelUuid(charUuid),
+                        data);
+        }
+    }
+
+    void onReadCharacteristic(int connId, int status, int srvcType,
+            int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
+            int charInstId, long charUuidLsb, long charUuidMsb,
+            int charType, byte[] data) throws RemoteException {
+
+        UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
+        UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
+        String address = mClientMap.addressByConnId(connId);
+
+        if (DBG) Log.d(TAG, "onReadCharacteristic() - address=" + address
+            + ", status=" + status + ", length=" + data.length);
+
+        ClientMap.App app = mClientMap.getByConnId(connId);
+        if (app != null) {
+            app.callback.onCharacteristicRead(address, status, srvcType,
+                        srvcInstId, new ParcelUuid(srvcUuid),
+                        charInstId, new ParcelUuid(charUuid), data);
+        }
+    }
+
+    void onWriteCharacteristic(int connId, int status, int srvcType,
+            int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
+            int charInstId, long charUuidLsb, long charUuidMsb)
+            throws RemoteException {
+
+        UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
+        UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
+        String address = mClientMap.addressByConnId(connId);
+
+        if (DBG) Log.d(TAG, "onWriteCharacteristic() - address=" + address
+            + ", status=" + status);
+
+        ClientMap.App app = mClientMap.getByConnId(connId);
+        if (app != null) {
+            app.callback.onCharacteristicWrite(address, status, srvcType,
+                        srvcInstId, new ParcelUuid(srvcUuid),
+                        charInstId, new ParcelUuid(charUuid));
+        }
+    }
+
+    void onExecuteCompleted(int connId, int status) throws RemoteException {
+        String address = mClientMap.addressByConnId(connId);
+        if (DBG) Log.d(TAG, "onExecuteCompleted() - address=" + address
+            + ", status=" + status);
+
+        ClientMap.App app = mClientMap.getByConnId(connId);
+        if (app != null) {
+            app.callback.onExecuteWrite(address, status);
+        }
+    }
+
+    void onReadDescriptor(int connId, int status, int srvcType,
+            int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
+            int charInstId, long charUuidLsb, long charUuidMsb,
+            long descrUuidLsb, long descrUuidMsb,
+            int charType, byte[] data) throws RemoteException {
+
+        UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
+        UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
+        UUID descrUuid = new UUID(descrUuidMsb, descrUuidLsb);
+        String address = mClientMap.addressByConnId(connId);
+
+        if (DBG) Log.d(TAG, "onReadDescriptor() - address=" + address
+            + ", status=" + status + ", length=" + data.length);
+
+        ClientMap.App app = mClientMap.getByConnId(connId);
+        if (app != null) {
+            app.callback.onDescriptorRead(address, status, srvcType,
+                        srvcInstId, new ParcelUuid(srvcUuid),
+                        charInstId, new ParcelUuid(charUuid),
+                        new ParcelUuid(descrUuid), data);
+        }
+    }
+
+    void onWriteDescriptor(int connId, int status, int srvcType,
+            int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
+            int charInstId, long charUuidLsb, long charUuidMsb,
+            long descrUuidLsb, long descrUuidMsb) throws RemoteException {
+
+        UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb);
+        UUID charUuid = new UUID(charUuidMsb, charUuidLsb);
+        UUID descrUuid = new UUID(descrUuidMsb, descrUuidLsb);
+        String address = mClientMap.addressByConnId(connId);
+
+        if (DBG) Log.d(TAG, "onWriteDescriptor() - address=" + address
+            + ", status=" + status);
+
+        ClientMap.App app = mClientMap.getByConnId(connId);
+        if (app != null) {
+            app.callback.onDescriptorWrite(address, status, srvcType,
+                        srvcInstId, new ParcelUuid(srvcUuid),
+                        charInstId, new ParcelUuid(charUuid),
+                        new ParcelUuid(descrUuid));
+        }
+    }
+
+    void onReadRemoteRssi(int clientIf, String address,
+                    int rssi, int status) throws RemoteException{
+        if (DBG) Log.d(TAG, "onReadRemoteRssi() - clientIf=" + clientIf + " address=" +
+                     address + ", rssi=" + rssi + ", status=" + status);
+
+        ClientMap.App app = mClientMap.getById(clientIf);
+        if (app != null) {
+            app.callback.onReadRemoteRssi(address, rssi, status);
+        }
+    }
+
+    /**************************************************************************
+     * GATT Service functions - Shared CLIENT/SERVER
+     *************************************************************************/
+
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        final int DEVICE_TYPE_BREDR = 0x1;
+
+        Map<BluetoothDevice, Integer> deviceStates = new HashMap<BluetoothDevice,
+                                                                 Integer>();
+
+        // Add paired LE devices
+
+        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+        for (BluetoothDevice device : bondedDevices) {
+            if (getDeviceType(device) != DEVICE_TYPE_BREDR) {
+                deviceStates.put(device, BluetoothProfile.STATE_DISCONNECTED);
+            }
+        }
+
+        // Add connected deviceStates
+
+        Set<String> connectedDevices = new HashSet<String>();
+        connectedDevices.addAll(mClientMap.getConnectedDevices());
+        connectedDevices.addAll(mServerMap.getConnectedDevices());
+
+        for (String address : connectedDevices ) {
+            BluetoothDevice device = mAdapter.getRemoteDevice(address);
+            if (device != null) {
+                deviceStates.put(device, BluetoothProfile.STATE_CONNECTED);
+            }
+        }
+
+        // Create matching device sub-set
+
+        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
+
+        for (Map.Entry<BluetoothDevice, Integer> entry : deviceStates.entrySet()) {
+            for(int state : states) {
+                if (entry.getValue() == state) {
+                    deviceList.add(entry.getKey());
+                }
+            }
+        }
+
+        return deviceList;
+    }
+
+    void startScan(int appIf, boolean isServer) {
+        if (DBG) Log.d(TAG, "startScan() - queue=" + mScanQueue.size());
+
+        if (getScanClient(appIf, isServer) == null) {
+            if (DBG) Log.d(TAG, "startScan() - adding client=" + appIf);
+            mScanQueue.add(new ScanClient(appIf, isServer));
+        }
+
+        gattClientScanNative(appIf, true);
+    }
+
+    void startScanWithUuids(int appIf, boolean isServer, UUID[] uuids) {
+        if (DBG) Log.d(TAG, "startScanWithUuids() - queue=" + mScanQueue.size());
+
+        if (getScanClient(appIf, isServer) == null) {
+            if (DBG) Log.d(TAG, "startScanWithUuids() - adding client=" + appIf);
+            mScanQueue.add(new ScanClient(appIf, isServer, uuids));
+        }
+
+        gattClientScanNative(appIf, true);
+    }
+
+    void stopScan(int appIf, boolean isServer) {
+        if (DBG) Log.d(TAG, "stopScan() - queue=" + mScanQueue.size());
+
+        removeScanClient(appIf, isServer);
+
+        if (mScanQueue.isEmpty()) {
+            if (DBG) Log.d(TAG, "stopScan() - queue empty; stopping scan");
+            gattClientScanNative(appIf, false);
+        }
+    }
+
+    /**************************************************************************
+     * GATT Service functions - CLIENT
+     *************************************************************************/
+
+    void registerClient(UUID uuid, IBluetoothGattCallback callback) {
+        if (DBG) Log.d(TAG, "registerClient() - UUID=" + uuid);
+        mClientMap.add(uuid, callback);
+        gattClientRegisterAppNative(uuid.getLeastSignificantBits(),
+                                    uuid.getMostSignificantBits());
+    }
+
+    void unregisterClient(int clientIf) {
+        if (DBG) Log.d(TAG, "unregisterClient() - clientIf=" + clientIf);
+        removeScanClient(clientIf, false);
+        mClientMap.remove(clientIf);
+        gattClientUnregisterAppNative(clientIf);
+    }
+
+    void clientConnect(int clientIf, String address, boolean isDirect) {
+        if (DBG) Log.d(TAG, "clientConnect() - address=" + address);
+        gattClientConnectNative(clientIf, address, isDirect);
+    }
+
+    void clientDisconnect(int clientIf, String address) {
+        Integer connId = mClientMap.connIdByAddress(clientIf, address);
+        if (DBG) Log.d(TAG, "clientDisconnect() - address=" + address + ", connId=" + connId);
+
+        gattClientDisconnectNative(clientIf, address, connId != null ? connId : 0);
+    }
+
+    List<String> getConnectedDevices() {
+        Set<String> connectedDevAddress = new HashSet<String>();
+        connectedDevAddress.addAll(mClientMap.getConnectedDevices());
+        connectedDevAddress.addAll(mServerMap.getConnectedDevices());
+        List<String> connectedDeviceList = new ArrayList<String>(connectedDevAddress);
+        return connectedDeviceList;
+    }
+
+    void refreshDevice(int clientIf, String address) {
+        if (DBG) Log.d(TAG, "refreshDevice() - address=" + address);
+        gattClientRefreshNative(clientIf, address);
+    }
+
+    void discoverServices(int clientIf, String address) {
+        Integer connId = mClientMap.connIdByAddress(clientIf, address);
+        if (DBG) Log.d(TAG, "discoverServices() - address=" + address + ", connId=" + connId);
+
+        if (connId != null)
+            gattClientSearchServiceNative(connId, true, 0, 0);
+        else
+            Log.e(TAG, "discoverServices() - No connection for " + address + "...");
+    }
+
+    void readCharacteristic(int clientIf, String address, int srvcType,
+                            int srvcInstanceId, UUID srvcUuid,
+                            int charInstanceId, UUID charUuid, int authReq) {
+        if (DBG) Log.d(TAG, "readCharacteristic() - address=" + address);
+
+        Integer connId = mClientMap.connIdByAddress(clientIf, address);
+        if (connId != null)
+            gattClientReadCharacteristicNative(connId, srvcType,
+                srvcInstanceId, srvcUuid.getLeastSignificantBits(),
+                srvcUuid.getMostSignificantBits(), charInstanceId,
+                charUuid.getLeastSignificantBits(), charUuid.getMostSignificantBits(),
+                authReq);
+        else
+            Log.e(TAG, "readCharacteristic() - No connection for " + address + "...");
+    }
+
+    void writeCharacteristic(int clientIf, String address, int srvcType,
+                             int srvcInstanceId, UUID srvcUuid,
+                             int charInstanceId, UUID charUuid, int writeType,
+                             int authReq, byte[] value) {
+        if (DBG) Log.d(TAG, "writeCharacteristic() - address=" + address);
+
+        if (mReliableQueue.contains(address)) writeType = 3; // Prepared write
+
+        Integer connId = mClientMap.connIdByAddress(clientIf, address);
+        if (connId != null)
+            gattClientWriteCharacteristicNative(connId, srvcType,
+                srvcInstanceId, srvcUuid.getLeastSignificantBits(),
+                srvcUuid.getMostSignificantBits(), charInstanceId,
+                charUuid.getLeastSignificantBits(), charUuid.getMostSignificantBits(),
+                writeType, authReq, value);
+        else
+            Log.e(TAG, "writeCharacteristic() - No connection for " + address + "...");
+    }
+
+    void readDescriptor(int clientIf, String address, int srvcType,
+                            int srvcInstanceId, UUID srvcUuid,
+                            int charInstanceId, UUID charUuid,
+                            UUID descrUuid, int authReq) {
+        if (DBG) Log.d(TAG, "readDescriptor() - address=" + address);
+
+        Integer connId = mClientMap.connIdByAddress(clientIf, address);
+        if (connId != null)
+            gattClientReadDescriptorNative(connId, srvcType,
+                srvcInstanceId, srvcUuid.getLeastSignificantBits(),
+                srvcUuid.getMostSignificantBits(), charInstanceId,
+                charUuid.getLeastSignificantBits(), charUuid.getMostSignificantBits(),
+                descrUuid.getLeastSignificantBits(), descrUuid.getMostSignificantBits(),
+                authReq);
+        else
+            Log.e(TAG, "readDescriptor() - No connection for " + address + "...");
+    };
+
+    void writeDescriptor(int clientIf, String address, int srvcType,
+                            int srvcInstanceId, UUID srvcUuid,
+                            int charInstanceId, UUID charUuid,
+                            UUID descrUuid, int writeType,
+                            int authReq, byte[] value) {
+        if (DBG) Log.d(TAG, "writeDescriptor() - address=" + address);
+
+        Integer connId = mClientMap.connIdByAddress(clientIf, address);
+        if (connId != null)
+            gattClientWriteDescriptorNative(connId, srvcType,
+                srvcInstanceId, srvcUuid.getLeastSignificantBits(),
+                srvcUuid.getMostSignificantBits(), charInstanceId,
+                charUuid.getLeastSignificantBits(), charUuid.getMostSignificantBits(),
+                descrUuid.getLeastSignificantBits(), descrUuid.getMostSignificantBits(),
+                writeType, authReq, value);
+        else
+            Log.e(TAG, "writeDescriptor() - No connection for " + address + "...");
+    }
+
+    void beginReliableWrite(int clientIf, String address) {
+        if (DBG) Log.d(TAG, "beginReliableWrite() - address=" + address);
+        mReliableQueue.add(address);
+    }
+
+    void endReliableWrite(int clientIf, String address, boolean execute) {
+        if (DBG) Log.d(TAG, "endReliableWrite() - address=" + address
+                                + " execute: " + execute);
+        mReliableQueue.remove(address);
+
+        Integer connId = mClientMap.connIdByAddress(clientIf, address);
+        if (connId != null) gattClientExecuteWriteNative(connId, execute);
+    }
+
+    void registerForNotification(int clientIf, String address, int srvcType,
+                int srvcInstanceId, UUID srvcUuid,
+                int charInstanceId, UUID charUuid,
+                boolean enable) {
+        if (DBG) Log.d(TAG, "registerForNotification() - address=" + address + " enable: " + enable);
+
+        Integer connId = mClientMap.connIdByAddress(clientIf, address);
+        if (connId != null) {
+            gattClientRegisterForNotificationsNative(clientIf, address,
+                srvcType, srvcInstanceId, srvcUuid.getLeastSignificantBits(),
+                srvcUuid.getMostSignificantBits(), charInstanceId,
+                charUuid.getLeastSignificantBits(), charUuid.getMostSignificantBits(),
+                enable);
+        } else {
+            Log.e(TAG, "registerForNotification() - No connection for " + address + "...");
+        }
+    }
+
+    void readRemoteRssi(int clientIf, String address) {
+        if (DBG) Log.d(TAG, "readRemoteRssi() - address=" + address);
+        gattClientReadRemoteRssiNative(clientIf, address);
+    }
+
+    /**************************************************************************
+     * Callback functions - SERVER
+     *************************************************************************/
+
+    void onServerRegistered(int status, int serverIf, long uuidLsb, long uuidMsb)
+            throws RemoteException {
+
+        UUID uuid = new UUID(uuidMsb, uuidLsb);
+        if (DBG) Log.d(TAG, "onServerRegistered() - UUID=" + uuid + ", serverIf=" + serverIf);
+        ServerMap.App app = mServerMap.getByUuid(uuid);
+        if (app != null) {
+            app.id = serverIf;
+            app.callback.onServerRegistered(status, serverIf);
+        }
+    }
+
+    void onServiceAdded(int status, int serverIf, int srvcType, int srvcInstId,
+                        long srvcUuidLsb, long srvcUuidMsb, int srvcHandle)
+                        throws RemoteException {
+        UUID uuid = new UUID(srvcUuidMsb, srvcUuidLsb);
+        if (DBG) Log.d(TAG, "onServiceAdded() UUID=" + uuid + ", status=" + status
+            + ", handle=" + srvcHandle);
+        if (status == 0)
+            mHandleMap.addService(serverIf, srvcHandle, uuid, srvcType, srvcInstId);
+        continueServiceDeclaration(serverIf, status, srvcHandle);
+    }
+
+    void onIncludedServiceAdded(int status, int serverIf, int srvcHandle,
+                                int includedSrvcHandle) throws RemoteException {
+        if (DBG) Log.d(TAG, "onIncludedServiceAdded() status=" + status
+            + ", service=" + srvcHandle + ", included=" + includedSrvcHandle);
+        continueServiceDeclaration(serverIf, status, srvcHandle);
+    }
+
+    void onCharacteristicAdded(int status, int serverIf,
+                               long charUuidLsb, long charUuidMsb,
+                               int srvcHandle, int charHandle)
+                               throws RemoteException {
+            UUID uuid = new UUID(charUuidMsb, charUuidLsb);
+        if (DBG) Log.d(TAG, "onCharacteristicAdded() UUID=" + uuid + ", status=" + status
+            + ", srvcHandle=" + srvcHandle + ", charHandle=" + charHandle);
+        if (status == 0)
+            mHandleMap.addCharacteristic(serverIf, charHandle, uuid, srvcHandle);
+        continueServiceDeclaration(serverIf, status, srvcHandle);
+    }
+
+    void onDescriptorAdded(int status, int serverIf,
+                           long descrUuidLsb, long descrUuidMsb,
+                           int srvcHandle, int descrHandle)
+                           throws RemoteException {
+            UUID uuid = new UUID(descrUuidMsb, descrUuidLsb);
+        if (DBG) Log.d(TAG, "onDescriptorAdded() UUID=" + uuid + ", status=" + status
+            + ", srvcHandle=" + srvcHandle + ", descrHandle=" + descrHandle);
+        if (status == 0)
+            mHandleMap.addDescriptor(serverIf, descrHandle, uuid, srvcHandle);
+        continueServiceDeclaration(serverIf, status, srvcHandle);
+    }
+
+    void onServiceStarted(int status, int serverIf, int srvcHandle)
+            throws RemoteException {
+        if (DBG) Log.d(TAG, "onServiceStarted() srvcHandle=" + srvcHandle
+            + ", status=" + status);
+        if (status == 0)
+            mHandleMap.setStarted(serverIf, srvcHandle, true);
+    }
+
+    void onServiceStopped(int status, int serverIf, int srvcHandle)
+            throws RemoteException {
+        if (DBG) Log.d(TAG, "onServiceStopped() srvcHandle=" + srvcHandle
+            + ", status=" + status);
+        if (status == 0)
+            mHandleMap.setStarted(serverIf, srvcHandle, false);
+        stopNextService(serverIf, status);
+    }
+
+    void onServiceDeleted(int status, int serverIf, int srvcHandle) {
+        if (DBG) Log.d(TAG, "onServiceDeleted() srvcHandle=" + srvcHandle
+            + ", status=" + status);
+        mHandleMap.deleteService(serverIf, srvcHandle);
+    }
+
+    void onClientConnected(String address, boolean connected, int connId)
+            throws RemoteException {
+
+        if (DBG) Log.d(TAG, "onConnected() connId=" + connId
+            + ", address=" + address + ", connected=" + connected);
+
+        Iterator<ServerMap.App> i = mServerMap.mApps.iterator();
+        while(i.hasNext()) {
+            ServerMap.App entry = i.next();
+            if (connected) {
+                mServerMap.addConnection(entry.id, connId, address);
+            } else {
+                mServerMap.removeConnection(entry.id, connId);
+            }
+            entry.callback.onServerConnectionState((byte)0, entry.id, connected, address);
+        }
+    }
+
+    void onAttributeRead(String address, int connId, int transId,
+                            int attrHandle, int offset, boolean isLong)
+                            throws RemoteException {
+        if (DBG) Log.d(TAG, "onAttributeRead() connId=" + connId
+            + ", address=" + address + ", handle=" + attrHandle
+            + ", requestId=" + transId + ", offset=" + offset);
+
+        HandleMap.Entry entry = mHandleMap.getByHandle(attrHandle);
+        if (entry == null) return;
+
+        if (DBG) Log.d(TAG, "onAttributeRead() UUID=" + entry.uuid
+            + ", serverIf=" + entry.serverIf + ", type=" + entry.type);
+
+        mHandleMap.addRequest(transId, attrHandle);
+
+        ServerMap.App app = mServerMap.getById(entry.serverIf);
+        if (app == null) return;
+
+        switch(entry.type) {
+            case HandleMap.TYPE_CHARACTERISTIC:
+            {
+                HandleMap.Entry serviceEntry = mHandleMap.getByHandle(entry.serviceHandle);
+                app.callback.onCharacteristicReadRequest(address, transId, offset, isLong,
+                    serviceEntry.serviceType, serviceEntry.instance,
+                    new ParcelUuid(serviceEntry.uuid), entry.instance,
+                    new ParcelUuid(entry.uuid));
+                break;
+            }
+
+            case HandleMap.TYPE_DESCRIPTOR:
+            {
+                HandleMap.Entry serviceEntry = mHandleMap.getByHandle(entry.serviceHandle);
+                HandleMap.Entry charEntry = mHandleMap.getByHandle(entry.charHandle);
+                app.callback.onDescriptorReadRequest(address, transId, offset, isLong,
+                    serviceEntry.serviceType, serviceEntry.instance,
+                    new ParcelUuid(serviceEntry.uuid), charEntry.instance,
+                    new ParcelUuid(charEntry.uuid),
+                    new ParcelUuid(entry.uuid));
+                break;
+            }
+
+            default:
+                Log.e(TAG, "onAttributeRead() - Requested unknown attribute type.");
+                break;
+        }
+    }
+
+    void onAttributeWrite(String address, int connId, int transId,
+                            int attrHandle, int offset, int length,
+                            boolean needRsp, boolean isPrep,
+                            byte[] data)
+                            throws RemoteException {
+        if (DBG) Log.d(TAG, "onAttributeWrite() connId=" + connId
+            + ", address=" + address + ", handle=" + attrHandle
+            + ", requestId=" + transId + ", isPrep=" + isPrep
+            + ", offset=" + offset);
+
+        HandleMap.Entry entry = mHandleMap.getByHandle(attrHandle);
+        if (entry == null) return;
+
+        if (DBG) Log.d(TAG, "onAttributeWrite() UUID=" + entry.uuid
+            + ", serverIf=" + entry.serverIf + ", type=" + entry.type);
+
+        mHandleMap.addRequest(transId, attrHandle);
+
+        ServerMap.App app = mServerMap.getById(entry.serverIf);
+        if (app == null) return;
+
+        switch(entry.type) {
+            case HandleMap.TYPE_CHARACTERISTIC:
+            {
+                HandleMap.Entry serviceEntry = mHandleMap.getByHandle(entry.serviceHandle);
+                app.callback.onCharacteristicWriteRequest(address, transId,
+                            offset, length, isPrep, needRsp,
+                            serviceEntry.serviceType, serviceEntry.instance,
+                            new ParcelUuid(serviceEntry.uuid), entry.instance,
+                            new ParcelUuid(entry.uuid), data);
+                break;
+            }
+
+            case HandleMap.TYPE_DESCRIPTOR:
+            {
+                HandleMap.Entry serviceEntry = mHandleMap.getByHandle(entry.serviceHandle);
+                HandleMap.Entry charEntry = mHandleMap.getByHandle(entry.charHandle);
+                app.callback.onDescriptorWriteRequest(address, transId,
+                            offset, length, isPrep, needRsp,
+                            serviceEntry.serviceType, serviceEntry.instance,
+                            new ParcelUuid(serviceEntry.uuid), charEntry.instance,
+                            new ParcelUuid(charEntry.uuid),
+                            new ParcelUuid(entry.uuid), data);
+                break;
+            }
+
+            default:
+                Log.e(TAG, "onAttributeWrite() - Requested unknown attribute type.");
+                break;
+        }
+    }
+
+    void onExecuteWrite(String address, int connId, int transId, int execWrite)
+            throws RemoteException {
+        if (DBG) Log.d(TAG, "onExecuteWrite() connId=" + connId
+            + ", address=" + address + ", transId=" + transId);
+
+        ServerMap.App app = mServerMap.getByConnId(connId);
+        if (app == null) return;
+
+        app.callback.onExecuteWrite(address, transId, execWrite == 1);
+    }
+
+    void onResponseSendCompleted(int status, int attrHandle) {
+        if (DBG) Log.d(TAG, "onResponseSendCompleted() handle=" + attrHandle);
+    }
+
+    /**************************************************************************
+     * GATT Service functions - SERVER
+     *************************************************************************/
+
+    void registerServer(UUID uuid, IBluetoothGattServerCallback callback) {
+        if (DBG) Log.d(TAG, "registerServer() - UUID=" + uuid);
+        mServerMap.add(uuid, callback);
+        gattServerRegisterAppNative(uuid.getLeastSignificantBits(),
+                                    uuid.getMostSignificantBits());
+    }
+
+    void unregisterServer(int serverIf) {
+        if (DBG) Log.d(TAG, "unregisterServer() - serverIf=" + serverIf);
+
+        deleteServices(serverIf);
+
+        mServerMap.remove(serverIf);
+        gattServerUnregisterAppNative(serverIf);
+    }
+
+    void serverConnect(int serverIf, String address, boolean isDirect) {
+        if (DBG) Log.d(TAG, "serverConnect() - address=" + address);
+        gattServerConnectNative(serverIf, address, isDirect);
+    }
+
+    void serverDisconnect(int serverIf, String address) {
+        Integer connId = mServerMap.connIdByAddress(serverIf, address);
+        if (DBG) Log.d(TAG, "serverDisconnect() - address=" + address + ", connId=" + connId);
+
+        gattServerDisconnectNative(serverIf, address, connId != null ? connId : 0);
+    }
+
+    void beginServiceDeclaration(int serverIf, int srvcType, int srvcInstanceId,
+                                 int minHandles, UUID srvcUuid) {
+        if (DBG) Log.d(TAG, "beginServiceDeclaration() - uuid=" + srvcUuid);
+        ServiceDeclaration serviceDeclaration = addDeclaration();
+        serviceDeclaration.addService(srvcUuid, srvcType, srvcInstanceId, minHandles);
+    }
+
+    void addIncludedService(int serverIf, int srvcType, int srvcInstanceId,
+                            UUID srvcUuid) {
+        if (DBG) Log.d(TAG, "addIncludedService() - uuid=" + srvcUuid);
+        getActiveDeclaration().addIncludedService(srvcUuid, srvcType, srvcInstanceId);
+    }
+
+    void addCharacteristic(int serverIf, UUID charUuid, int properties,
+                           int permissions) {
+        if (DBG) Log.d(TAG, "addCharacteristic() - uuid=" + charUuid);
+        getActiveDeclaration().addCharacteristic(charUuid, properties, permissions);
+    }
+
+    void addDescriptor(int serverIf, UUID descUuid, int permissions) {
+        if (DBG) Log.d(TAG, "addDescriptor() - uuid=" + descUuid);
+        getActiveDeclaration().addDescriptor(descUuid, permissions);
+    }
+
+    void endServiceDeclaration(int serverIf) {
+        if (DBG) Log.d(TAG, "endServiceDeclaration()");
+
+        if (getActiveDeclaration() == getPendingDeclaration()) {
+            try {
+                continueServiceDeclaration(serverIf, (byte)0, 0);
+            } catch (RemoteException e) {
+                Log.e(TAG,""+e);
+            }
+        }
+    }
+
+    void removeService(int serverIf, int srvcType,
+                  int srvcInstanceId, UUID srvcUuid) {
+        if (DBG) Log.d(TAG, "removeService() - uuid=" + srvcUuid);
+
+        int srvcHandle = mHandleMap.getServiceHandle(srvcUuid, srvcType, srvcInstanceId);
+        if (srvcHandle == 0) return;
+        gattServerDeleteServiceNative(serverIf, srvcHandle);
+    }
+
+    void clearServices(int serverIf) {
+        if (DBG) Log.d(TAG, "clearServices()");
+        deleteServices(serverIf);
+    }
+
+    void sendResponse(int serverIf, String address, int requestId,
+                      int status, int offset, byte[] value) {
+        if (DBG) Log.d(TAG, "sendResponse() - address=" + address);
+
+        int handle = 0;
+        HandleMap.Entry entry = mHandleMap.getByRequestId(requestId);
+        if (entry != null) handle = entry.handle;
+
+        int connId = mServerMap.connIdByAddress(serverIf, address);
+        gattServerSendResponseNative(serverIf, connId, requestId, (byte)status,
+                                     handle, offset, value, (byte)0);
+        mHandleMap.deleteRequest(requestId);
+    }
+
+    void sendNotification(int serverIf, String address, int srvcType,
+                                 int srvcInstanceId, UUID srvcUuid,
+                                 int charInstanceId, UUID charUuid,
+                                 boolean confirm, byte[] value) {
+        if (DBG) Log.d(TAG, "sendNotification() - address=" + address);
+
+        int srvcHandle = mHandleMap.getServiceHandle(srvcUuid, srvcType, srvcInstanceId);
+        if (srvcHandle == 0) return;
+
+        int charHandle = mHandleMap.getCharacteristicHandle(srvcHandle, charUuid, charInstanceId);
+        if (charHandle == 0) return;
+
+        int connId = mServerMap.connIdByAddress(serverIf, address);
+        if (connId == 0) return;
+
+        if (confirm) {
+            gattServerSendIndicationNative(serverIf, charHandle, connId, value);
+        } else {
+            gattServerSendNotificationNative(serverIf, charHandle, connId, value);
+        }
+    }
+
+    /**************************************************************************
+     * Private functions
+     *************************************************************************/
+
+    private int getDeviceType(BluetoothDevice device) {
+        int type = gattClientGetDeviceTypeNative(device.getAddress());
+        if (DBG) Log.d(TAG, "getDeviceType() - device=" + device
+            + ", type=" + type);
+        return type;
+    }
+
+    private void continueSearch(int connId, int status) throws RemoteException {
+        if (status == 0 && !mSearchQueue.isEmpty()) {
+            SearchQueue.Entry svc = mSearchQueue.pop();
+
+            if (svc.charUuidLsb == 0) {
+                // Characteristic is up next
+                gattClientGetCharacteristicNative(svc.connId, svc.srvcType,
+                    svc.srvcInstId, svc.srvcUuidLsb, svc.srvcUuidMsb, 0, 0, 0);
+            } else {
+                // Descriptor is up next
+                gattClientGetDescriptorNative(svc.connId, svc.srvcType,
+                    svc.srvcInstId, svc.srvcUuidLsb, svc.srvcUuidMsb,
+                    svc.charInstId, svc.charUuidLsb, svc.charUuidMsb, 0,0);
+            }
+        } else {
+            ClientMap.App app = mClientMap.getByConnId(connId);
+            if (app != null) {
+                app.callback.onSearchComplete(mClientMap.addressByConnId(connId), status);
+            }
+        }
+    }
+
+    private void continueServiceDeclaration(int serverIf, int status, int srvcHandle) throws RemoteException {
+        if (mServiceDeclarations.size() == 0) return;
+        if (DBG) Log.d(TAG, "continueServiceDeclaration() - srvcHandle=" + srvcHandle);
+
+        boolean finished = false;
+
+        ServiceDeclaration.Entry entry = null;
+        if (status == 0)
+            entry = getPendingDeclaration().getNext();
+
+        if (entry != null) {
+            if (DBG) Log.d(TAG, "continueServiceDeclaration() - next entry type="
+                + entry.type);
+            switch(entry.type) {
+                case ServiceDeclaration.TYPE_SERVICE:
+                    gattServerAddServiceNative(serverIf, entry.serviceType,
+                        entry.instance,
+                        entry.uuid.getLeastSignificantBits(),
+                        entry.uuid.getMostSignificantBits(),
+                        getPendingDeclaration().getNumHandles());
+                    break;
+
+                case ServiceDeclaration.TYPE_CHARACTERISTIC:
+                    gattServerAddCharacteristicNative(serverIf, srvcHandle,
+                        entry.uuid.getLeastSignificantBits(),
+                        entry.uuid.getMostSignificantBits(),
+                        entry.properties, entry.permissions);
+                    break;
+
+                case ServiceDeclaration.TYPE_DESCRIPTOR:
+                    gattServerAddDescriptorNative(serverIf, srvcHandle,
+                        entry.uuid.getLeastSignificantBits(),
+                        entry.uuid.getMostSignificantBits(),
+                        entry.permissions);
+                    break;
+
+                case ServiceDeclaration.TYPE_INCLUDED_SERVICE:
+                {
+                    int inclSrvc = mHandleMap.getServiceHandle(entry.uuid,
+                                            entry.serviceType, entry.instance);
+                    if (inclSrvc != 0) {
+                        gattServerAddIncludedServiceNative(serverIf, srvcHandle,
+                                                           inclSrvc);
+                    } else {
+                        finished = true;
+                    }
+                    break;
+                }
+            }
+        } else {
+            gattServerStartServiceNative(serverIf, srvcHandle, (byte)2 /*BREDR/LE*/);
+            finished = true;
+        }
+
+        if (finished) {
+            if (DBG) Log.d(TAG, "continueServiceDeclaration() - completed.");
+            ServerMap.App app = mServerMap.getById(serverIf);
+            if (app != null) {
+                HandleMap.Entry serviceEntry = mHandleMap.getByHandle(srvcHandle);
+                if (serviceEntry != null) {
+                    app.callback.onServiceAdded(status, serviceEntry.serviceType,
+                        serviceEntry.instance, new ParcelUuid(serviceEntry.uuid));
+                } else {
+                    app.callback.onServiceAdded(status, 0, 0, null);
+                }
+            }
+            removePendingDeclaration();
+
+            if (getPendingDeclaration() != null) {
+                continueServiceDeclaration(serverIf, (byte)0, 0);
+            }
+        }
+    }
+
+    private void stopNextService(int serverIf, int status) throws RemoteException {
+        if (DBG) Log.d(TAG, "stopNextService() - serverIf=" + serverIf
+            + ", status=" + status);
+
+        if (status == 0) {
+            List<HandleMap.Entry> entries = mHandleMap.getEntries();
+            for(HandleMap.Entry entry : entries) {
+                if (entry.type != HandleMap.TYPE_SERVICE ||
+                    entry.serverIf != serverIf ||
+                    entry.started == false)
+                        continue;
+
+                gattServerStopServiceNative(serverIf, entry.handle);
+                return;
+            }
+        }
+    }
+
+    private void deleteServices(int serverIf) {
+        if (DBG) Log.d(TAG, "deleteServices() - serverIf=" + serverIf);
+
+        /*
+         * Figure out which handles to delete.
+         * The handles are copied into a new list to avoid race conditions.
+         */
+        List<Integer> handleList = new ArrayList<Integer>();
+        List<HandleMap.Entry> entries = mHandleMap.getEntries();
+        for(HandleMap.Entry entry : entries) {
+            if (entry.type != HandleMap.TYPE_SERVICE ||
+                entry.serverIf != serverIf)
+                    continue;
+            handleList.add(entry.handle);
+        }
+
+        /* Now actually delete the services.... */
+        for(Integer handle : handleList) {
+            gattServerDeleteServiceNative(serverIf, handle);
+        }
+    }
+
+    private List<UUID> parseUuids(byte[] adv_data) {
+        List<UUID> uuids = new ArrayList<UUID>();
+
+        int offset = 0;
+        while(offset < (adv_data.length-2)) {
+            int len = adv_data[offset++];
+            if (len == 0) break;
+
+            int type = adv_data[offset++];
+            switch (type) {
+                case 0x02: // Partial list of 16-bit UUIDs
+                case 0x03: // Complete list of 16-bit UUIDs
+                    while (len > 1) {
+                        int uuid16 = adv_data[offset++];
+                        uuid16 += (adv_data[offset++] << 8);
+                        len -= 2;
+                        uuids.add(UUID.fromString(String.format(
+                            "%08x-0000-1000-8000-00805f9b34fb", uuid16)));
+                    }
+                    break;
+
+                default:
+                    offset += (len - 1);
+                    break;
+            }
+        }
+
+        return uuids;
+    }
+
+    /**************************************************************************
+     * GATT Test functions
+     *************************************************************************/
+
+    void gattTestCommand(int command, UUID uuid1, String bda1,
+                         int p1, int p2, int p3, int p4, int p5) {
+        if (bda1 == null) bda1 = "00:00:00:00:00:00";
+        if (uuid1 != null)
+            gattTestNative(command, uuid1.getLeastSignificantBits(),
+                       uuid1.getMostSignificantBits(), bda1, p1, p2, p3, p4, p5);
+        else
+            gattTestNative(command, 0,0, bda1, p1, p2, p3, p4, p5);
+    }
+
+    private native void gattTestNative(int command,
+                                    long uuid1_lsb, long uuid1_msb, String bda1,
+                                    int p1, int p2, int p3, int p4, int p5);
+
+    /**************************************************************************
+     * Native functions prototypes
+     *************************************************************************/
+
+    private native static void classInitNative();
+    private native void initializeNative();
+    private native void cleanupNative();
+
+    private native int gattClientGetDeviceTypeNative(String address);
+
+    private native void gattClientRegisterAppNative(long app_uuid_lsb,
+                                                    long app_uuid_msb);
+
+    private native void gattClientUnregisterAppNative(int clientIf);
+
+    private native void gattClientScanNative(int clientIf, boolean start);
+
+    private native void gattClientConnectNative(int clientIf, String address,
+            boolean isDirect);
+
+    private native void gattClientDisconnectNative(int clientIf, String address,
+            int conn_id);
+
+    private native void gattClientRefreshNative(int clientIf, String address);
+
+    private native void gattClientSearchServiceNative(int conn_id,
+            boolean search_all, long service_uuid_lsb, long service_uuid_msb);
+
+    private native void gattClientGetCharacteristicNative(int conn_id,
+            int service_type, int service_id_inst_id, long service_id_uuid_lsb,
+            long service_id_uuid_msb, int char_id_inst_id, long char_id_uuid_lsb,
+            long char_id_uuid_msb);
+
+    private native void gattClientGetDescriptorNative(int conn_id,
+            int service_type, int service_id_inst_id, long service_id_uuid_lsb,
+            long service_id_uuid_msb, int char_id_inst_id, long char_id_uuid_lsb,
+            long char_id_uuid_msb, long descr_id_uuid_lsb, long descr_id_uuid_msb);
+
+    private native void gattClientGetIncludedServiceNative(int conn_id,
+            int service_type, int service_id_inst_id,
+            long service_id_uuid_lsb, long service_id_uuid_msb,
+            int incl_service_id_inst_id, int incl_service_type,
+            long incl_service_id_uuid_lsb, long incl_service_id_uuid_msb);
+
+    private native void gattClientReadCharacteristicNative(int conn_id,
+            int service_type, int service_id_inst_id, long service_id_uuid_lsb,
+            long service_id_uuid_msb, int char_id_inst_id, long char_id_uuid_lsb,
+            long char_id_uuid_msb, int authReq);
+
+    private native void gattClientReadDescriptorNative(int conn_id,
+            int service_type, int service_id_inst_id, long service_id_uuid_lsb,
+            long service_id_uuid_msb, int char_id_inst_id, long char_id_uuid_lsb,
+            long char_id_uuid_msb, long descr_id_uuid_lsb, long descr_id_uuid_msb,
+            int authReq);
+
+    private native void gattClientWriteCharacteristicNative(int conn_id,
+            int service_type, int service_id_inst_id, long service_id_uuid_lsb,
+            long service_id_uuid_msb, int char_id_inst_id, long char_id_uuid_lsb,
+            long char_id_uuid_msb, int write_type, int auth_req, byte[] value);
+
+    private native void gattClientWriteDescriptorNative(int conn_id,
+            int service_type, int service_id_inst_id, long service_id_uuid_lsb,
+            long service_id_uuid_msb, int char_id_inst_id, long char_id_uuid_lsb,
+            long char_id_uuid_msb, long descr_id_uuid_lsb, long descr_id_uuid_msb,
+            int write_type, int auth_req, byte[] value);
+
+    private native void gattClientExecuteWriteNative(int conn_id, boolean execute);
+
+    private native void gattClientRegisterForNotificationsNative(int clientIf,
+            String address, int service_type, int service_id_inst_id,
+            long service_id_uuid_lsb, long service_id_uuid_msb,
+            int char_id_inst_id, long char_id_uuid_lsb, long char_id_uuid_msb,
+            boolean enable);
+
+    private native void gattClientReadRemoteRssiNative(int clientIf,
+            String address);
+
+    private native void gattServerRegisterAppNative(long app_uuid_lsb,
+                                                    long app_uuid_msb);
+
+    private native void gattServerUnregisterAppNative(int serverIf);
+
+    private native void gattServerConnectNative(int server_if, String address,
+                                             boolean is_direct);
+
+    private native void gattServerDisconnectNative(int serverIf, String address,
+                                              int conn_id);
+
+    private native void gattServerAddServiceNative (int server_if,
+            int service_type, int service_id_inst_id,
+            long service_id_uuid_lsb, long service_id_uuid_msb,
+            int num_handles);
+
+    private native void gattServerAddIncludedServiceNative (int server_if,
+            int svc_handle, int included_svc_handle);
+
+    private native void gattServerAddCharacteristicNative (int server_if,
+            int svc_handle, long char_uuid_lsb, long char_uuid_msb,
+            int properties, int permissions);
+
+    private native void gattServerAddDescriptorNative (int server_if,
+            int svc_handle, long desc_uuid_lsb, long desc_uuid_msb,
+            int permissions);
+
+    private native void gattServerStartServiceNative (int server_if,
+            int svc_handle, int transport );
+
+    private native void gattServerStopServiceNative (int server_if,
+                                                     int svc_handle);
+
+    private native void gattServerDeleteServiceNative (int server_if,
+                                                       int svc_handle);
+
+    private native void gattServerSendIndicationNative (int server_if,
+            int attr_handle, int conn_id, byte[] val);
+
+    private native void gattServerSendNotificationNative (int server_if,
+            int attr_handle, int conn_id, byte[] val);
+
+    private native void gattServerSendResponseNative (int server_if,
+            int conn_id, int trans_id, int status, int handle, int offset,
+            byte[] val, int auth_req);
+}
diff --git a/src/com/android/bluetooth/gatt/GattServiceConfig.java b/src/com/android/bluetooth/gatt/GattServiceConfig.java
new file mode 100644
index 0000000..9b5a8ab
--- /dev/null
+++ b/src/com/android/bluetooth/gatt/GattServiceConfig.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.gatt;
+
+/**
+ * GattService configuration.
+ */
+/*package*/ class GattServiceConfig {
+    public static final boolean DBG = true;
+    public static final String TAG_PREFIX = "BtGatt.";
+    public static final boolean DEBUG_ADMIN = true;
+}
diff --git a/src/com/android/bluetooth/gatt/HandleMap.java b/src/com/android/bluetooth/gatt/HandleMap.java
new file mode 100644
index 0000000..5d45654
--- /dev/null
+++ b/src/com/android/bluetooth/gatt/HandleMap.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.gatt;
+
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+class HandleMap {
+    private static final boolean DBG = GattServiceConfig.DBG;
+    private static final String TAG = GattServiceConfig.TAG_PREFIX + "HandleMap";
+
+    public static final int TYPE_UNDEFINED = 0;
+    public static final int TYPE_SERVICE = 1;
+    public static final int TYPE_CHARACTERISTIC = 2;
+    public static final int TYPE_DESCRIPTOR = 3;
+
+    class Entry {
+        int serverIf = 0;
+        int type = TYPE_UNDEFINED;
+        int handle = 0;
+        UUID uuid = null;
+        int instance = 0;
+        int serviceType = 0;
+        int serviceHandle = 0;
+        int charHandle = 0;
+        boolean started = false;
+
+        Entry(int serverIf, int handle, UUID uuid, int serviceType, int instance) {
+            this.serverIf = serverIf;
+            this.type = TYPE_SERVICE;
+            this.handle = handle;
+            this.uuid = uuid;
+            this.instance = instance;
+            this.serviceType = serviceType;
+        }
+
+        Entry(int serverIf, int type, int handle, UUID uuid, int serviceHandle) {
+            this.serverIf = serverIf;
+            this.type = type;
+            this.handle = handle;
+            this.uuid = uuid;
+            this.instance = instance;
+            this.serviceHandle = serviceHandle;
+        }
+
+        Entry(int serverIf, int type, int handle, UUID uuid, int serviceHandle, int charHandle) {
+            this.serverIf = serverIf;
+            this.type = type;
+            this.handle = handle;
+            this.uuid = uuid;
+            this.instance = instance;
+            this.serviceHandle = serviceHandle;
+            this.charHandle = charHandle;
+        }
+    }
+
+    List<Entry> mEntries = null;
+    Map<Integer, Integer> mRequestMap = null;
+    int mLastCharacteristic = 0;
+
+    HandleMap() {
+        mEntries = new ArrayList<Entry>();
+        mRequestMap = new HashMap<Integer, Integer>();
+    }
+
+    void clear() {
+        mEntries.clear();
+        mRequestMap.clear();
+    }
+
+    void addService(int serverIf, int handle, UUID uuid, int serviceType, int instance) {
+        mEntries.add(new Entry(serverIf, handle, uuid, serviceType, instance));
+    }
+
+    void addCharacteristic(int serverIf, int handle, UUID uuid, int serviceHandle) {
+        mLastCharacteristic = handle;
+        mEntries.add(new Entry(serverIf, TYPE_CHARACTERISTIC, handle, uuid, serviceHandle));
+    }
+
+    void addDescriptor(int serverIf, int handle, UUID uuid, int serviceHandle) {
+        mEntries.add(new Entry(serverIf, TYPE_DESCRIPTOR, handle, uuid, serviceHandle, mLastCharacteristic));
+    }
+
+    void setStarted(int serverIf, int handle, boolean started) {
+        for(Entry entry : mEntries) {
+            if (entry.type != TYPE_SERVICE ||
+                entry.serverIf != serverIf ||
+                entry.handle != handle)
+                continue;
+
+            entry.started = started;
+            return;
+        }
+    }
+
+    Entry getByHandle(int handle) {
+        for(Entry entry : mEntries) {
+            if (entry.handle == handle)
+                return entry;
+        }
+        Log.e(TAG, "getByHandle() - Handle " + handle + " not found!");
+        return null;
+    }
+
+    int getServiceHandle(UUID uuid, int serviceType, int instance) {
+        for(Entry entry : mEntries) {
+            if (entry.type == TYPE_SERVICE &&
+                entry.serviceType == serviceType &&
+                entry.instance == instance &&
+                entry.uuid.equals(uuid)) {
+                return entry.handle;
+            }
+        }
+        Log.e(TAG, "getServiceHandle() - UUID " + uuid + " not found!");
+        return 0;
+    }
+
+    int getCharacteristicHandle(int serviceHandle, UUID uuid, int instance) {
+        for(Entry entry : mEntries) {
+            if (entry.type == TYPE_CHARACTERISTIC &&
+                entry.serviceHandle == serviceHandle &&
+                entry.instance == instance &&
+                entry.uuid.equals(uuid)) {
+                return entry.handle;
+            }
+        }
+        Log.e(TAG, "getCharacteristicHandle() - Service " + serviceHandle
+                    + ", UUID " + uuid + " not found!");
+        return 0;
+    }
+
+    void deleteService(int serverIf, int serviceHandle) {
+        for(Iterator <Entry> it = mEntries.iterator(); it.hasNext();) {
+            Entry entry = it.next();
+            if (entry.serverIf != serverIf) continue;
+
+            if (entry.handle == serviceHandle ||
+                entry.serviceHandle == serviceHandle)
+                it.remove();
+        }
+    }
+
+    List<Entry> getEntries() {
+        return mEntries;
+    }
+
+    void addRequest(int requestId, int handle) {
+        mRequestMap.put(requestId, handle);
+    }
+
+    void deleteRequest(int requestId) {
+        mRequestMap.remove(requestId);
+    }
+
+    Entry getByRequestId(int requestId) {
+        Integer handle = mRequestMap.get(requestId);
+        if (handle == null) {
+            Log.e(TAG, "getByRequestId() - Request ID " + requestId + " not found!");
+            return null;
+        }
+        return getByHandle(handle);
+    }
+
+
+    /**
+     * Logs debug information.
+     */
+    void dump() {
+        StringBuilder b = new StringBuilder();
+        b.append(  "-------------- GATT Handle Map -----------------");
+        b.append("\nEntries: " + mEntries.size());
+        b.append("\nRequests: " + mRequestMap.size());
+
+        for (Entry entry : mEntries) {
+            b.append("\n" + entry.serverIf + ": [" + entry.handle + "] ");
+            switch(entry.type) {
+                case TYPE_SERVICE:
+                    b.append("Service " + entry.uuid);
+                    b.append(", started " + entry.started);
+                    break;
+
+                case TYPE_CHARACTERISTIC:
+                    b.append("  Characteristic " + entry.uuid);
+                    break;
+
+                case TYPE_DESCRIPTOR:
+                    b.append("    Descriptor " + entry.uuid);
+                    break;
+            }
+        }
+
+        b.append("\n------------------------------------------------");
+        Log.d(TAG, b.toString());
+    }
+}
diff --git a/src/com/android/bluetooth/gatt/ScanClient.java b/src/com/android/bluetooth/gatt/ScanClient.java
new file mode 100644
index 0000000..3b1e421
--- /dev/null
+++ b/src/com/android/bluetooth/gatt/ScanClient.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.gatt;
+
+import java.util.UUID;
+
+/**
+ * Helper class identifying a client that has requested LE scan results.
+ * @hide
+ */
+/*package*/ class ScanClient {
+    int appIf;
+    boolean isServer;
+    UUID[] uuids;
+
+    ScanClient(int appIf, boolean isServer) {
+        this.appIf = appIf;
+        this.isServer = isServer;
+        this.uuids = new UUID[0];
+    }
+
+    ScanClient(int appIf, boolean isServer, UUID[] uuids) {
+        this.appIf = appIf;
+        this.isServer = isServer;
+        this.uuids = uuids;
+    }
+}
diff --git a/src/com/android/bluetooth/gatt/SearchQueue.java b/src/com/android/bluetooth/gatt/SearchQueue.java
new file mode 100644
index 0000000..3064a51
--- /dev/null
+++ b/src/com/android/bluetooth/gatt/SearchQueue.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.gatt;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Helper class to store characteristics and descriptors that will be
+ * queued up for future exploration.
+ * @hide
+ */
+/*package*/ class SearchQueue {
+    class Entry {
+        public int connId;
+        public int srvcType;
+        public int srvcInstId;
+        public long srvcUuidLsb;
+        public long srvcUuidMsb;
+        public int charInstId;
+        public long charUuidLsb;
+        public long charUuidMsb;
+    }
+
+    private List<Entry> mEntries = new ArrayList<Entry>();
+
+    void add(int connId, int srvcType,
+            int srvcInstId, long srvcUuidLsb, long srvcUuidMsb) {
+        Entry entry = new Entry();
+        entry.connId = connId;
+        entry.srvcType = srvcType;
+        entry.srvcInstId = srvcInstId;
+        entry.srvcUuidLsb = srvcUuidLsb;
+        entry.srvcUuidMsb = srvcUuidMsb;
+        entry.charUuidLsb = 0;
+        mEntries.add(entry);
+    }
+
+    void add(int connId, int srvcType,
+        int srvcInstId, long srvcUuidLsb, long srvcUuidMsb,
+        int charInstId, long charUuidLsb, long charUuidMsb)
+    {
+        Entry entry = new Entry();
+        entry.connId = connId;
+        entry.srvcType = srvcType;
+        entry.srvcInstId = srvcInstId;
+        entry.srvcUuidLsb = srvcUuidLsb;
+        entry.srvcUuidMsb = srvcUuidMsb;
+        entry.charInstId = charInstId;
+        entry.charUuidLsb = charUuidLsb;
+        entry.charUuidMsb = charUuidMsb;
+        mEntries.add(entry);
+    }
+
+    Entry pop() {
+        Entry entry = mEntries.get(0);
+        mEntries.remove(0);
+        return entry;
+    }
+
+    void removeConnId(int connId) {
+        for (Iterator<Entry> it = mEntries.iterator(); it.hasNext();) {
+            Entry entry = it.next();
+            if (entry.connId == connId) {
+                it.remove();
+            }
+        }
+    }
+
+    boolean isEmpty() {
+        return mEntries.isEmpty();
+    }
+
+    void clear() {
+        mEntries.clear();
+    }
+}
diff --git a/src/com/android/bluetooth/gatt/ServiceDeclaration.java b/src/com/android/bluetooth/gatt/ServiceDeclaration.java
new file mode 100644
index 0000000..0c9a51b
--- /dev/null
+++ b/src/com/android/bluetooth/gatt/ServiceDeclaration.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.gatt;
+
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.UUID;
+
+class ServiceDeclaration {
+    private static final boolean DBG = GattServiceConfig.DBG;
+    private static final String TAG = GattServiceConfig.TAG_PREFIX + "ServiceDeclaration";
+
+    public static final byte TYPE_UNDEFINED = 0;
+    public static final byte TYPE_SERVICE = 1;
+    public static final byte TYPE_CHARACTERISTIC = 2;
+    public static final byte TYPE_DESCRIPTOR = 3;
+    public static final byte TYPE_INCLUDED_SERVICE = 4;
+
+    class Entry {
+        byte type = TYPE_UNDEFINED;
+        UUID uuid = null;
+        int instance = 0;
+        int permissions = 0;
+        int properties = 0;
+        int serviceType = 0;
+        int serviceHandle = 0;
+
+        Entry(UUID uuid, int serviceType, int instance) {
+            this.type = TYPE_SERVICE;
+            this.uuid = uuid;
+            this.instance = instance;
+            this.serviceType = serviceType;
+        }
+
+        Entry(UUID uuid, int properties, int permissions, int instance) {
+            this.type = TYPE_CHARACTERISTIC;
+            this.uuid = uuid;
+            this.instance = instance;
+            this.permissions = permissions;
+            this.properties = properties;
+        }
+
+        Entry(UUID uuid, int permissions) {
+            this.type = TYPE_DESCRIPTOR;
+            this.uuid = uuid;
+            this.permissions = permissions;
+        }
+    }
+
+    List<Entry> mEntries = null;
+    int mNumHandles = 0;
+
+    ServiceDeclaration() {
+        mEntries = new ArrayList<Entry>();
+    }
+
+    void addService(UUID uuid, int serviceType, int instance, int minHandles) {
+        mEntries.add(new Entry(uuid, serviceType, instance));
+        if (minHandles == 0) {
+            ++mNumHandles;
+        } else {
+            mNumHandles = minHandles;
+        }
+    }
+
+    void addIncludedService(UUID uuid, int serviceType, int instance) {
+        Entry entry = new Entry(uuid, serviceType, instance);
+        entry.type = TYPE_INCLUDED_SERVICE;
+        mEntries.add(entry);
+        ++mNumHandles;
+    }
+
+    void addCharacteristic(UUID uuid, int properties, int permissions) {
+        mEntries.add(new Entry(uuid, properties, permissions, 0 /*instance*/));
+        mNumHandles += 2;
+    }
+
+    void addDescriptor(UUID uuid, int permissions) {
+        mEntries.add(new Entry(uuid, permissions));
+        ++mNumHandles;
+    }
+
+    Entry getNext() {
+        if (mEntries.isEmpty()) return null;
+        Entry entry = mEntries.get(0);
+        mEntries.remove(0);
+        return entry;
+    }
+
+    int getNumHandles() {
+        return mNumHandles;
+    }
+}