resolved conflicts for merge of c508483d to jb-mr2-dev

Change-Id: I15a1f2b2391a973645ef14c36bb3ddf71c815bc9
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f4c0d3b..4a363dd 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -128,6 +128,9 @@
                 <data android:mimeType="application/msword" />
                 <data android:mimeType="application/vnd.ms-powerpoint" />
                 <data android:mimeType="application/pdf" />
+                <data android:mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />
+                <data android:mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
+                <data android:mimeType="application/vnd.openxmlformats-officedocument.presentationml.presentation" />
             </intent-filter>
             <intent-filter>
                 <action android:name="android.intent.action.SEND_MULTIPLE" />
@@ -219,6 +222,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..056c712 100644
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -6,9 +6,11 @@
     com_android_bluetooth_btservice_AdapterService.cpp \
     com_android_bluetooth_hfp.cpp \
     com_android_bluetooth_a2dp.cpp \
+    com_android_bluetooth_avrcp.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..506cddd 100644
--- a/jni/com_android_bluetooth.h
+++ b/jni/com_android_bluetooth.h
@@ -35,12 +35,16 @@
 
 int register_com_android_bluetooth_a2dp(JNIEnv* env);
 
+int register_com_android_bluetooth_avrcp(JNIEnv* env);
+
 int register_com_android_bluetooth_hid(JNIEnv* env);
 
 int register_com_android_bluetooth_hdp(JNIEnv* env);
 
 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_avrcp.cpp b/jni/com_android_bluetooth_avrcp.cpp
new file mode 100644
index 0000000..125ac4b
--- /dev/null
+++ b/jni/com_android_bluetooth_avrcp.cpp
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2012 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 LOG_TAG "BluetoothAvrcpServiceJni"
+
+#define LOG_NDEBUG 0
+
+#include "com_android_bluetooth.h"
+#include "hardware/bt_rc.h"
+#include "utils/Log.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <string.h>
+
+namespace android {
+static jmethodID method_getPlayStatus;
+static jmethodID method_getElementAttr;
+static jmethodID method_registerNotification;
+
+static const btrc_interface_t *sBluetoothAvrcpInterface = NULL;
+static jobject mCallbacksObj = NULL;
+static JNIEnv *sCallbackEnv = NULL;
+
+static bool checkCallbackThread() {
+    // Always fetch the latest callbackEnv from AdapterService.
+    // Caching this could cause this sCallbackEnv to go out-of-sync
+    // with the AdapterService's ENV if an ASSOCIATE/DISASSOCIATE event
+    // is received
+    sCallbackEnv = getCallbackEnv();
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    if (sCallbackEnv != env || sCallbackEnv == NULL) return false;
+    return true;
+}
+
+static void btavrcp_get_play_status_callback() {
+    ALOGI("%s", __FUNCTION__);
+
+    if (!checkCallbackThread()) {
+        ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+        return;
+    }
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getPlayStatus);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static void btavrcp_get_element_attr_callback(uint8_t num_attr, btrc_media_attr_t *p_attrs) {
+    jintArray attrs;
+
+    ALOGI("%s", __FUNCTION__);
+
+    if (!checkCallbackThread()) {
+        ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+        return;
+    }
+    attrs = (jintArray)sCallbackEnv->NewIntArray(num_attr);
+    if (!attrs) {
+        ALOGE("Fail to new jintArray for attrs");
+        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+        return;
+    }
+    sCallbackEnv->SetIntArrayRegion(attrs, 0, num_attr, (jint *)p_attrs);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_getElementAttr, (jbyte)num_attr, attrs);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+    sCallbackEnv->DeleteLocalRef(attrs);
+}
+
+static void btavrcp_register_notification_callback(btrc_event_id_t event_id, uint32_t param) {
+    ALOGI("%s", __FUNCTION__);
+
+    if (!checkCallbackThread()) {
+        ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
+        return;
+    }
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_registerNotification,
+                                 (jint)event_id, (jint)param);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+}
+
+static btrc_callbacks_t sBluetoothAvrcpCallbacks = {
+    sizeof(sBluetoothAvrcpCallbacks),
+    btavrcp_get_play_status_callback,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    btavrcp_get_element_attr_callback,
+    btavrcp_register_notification_callback
+};
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+    method_getPlayStatus =
+        env->GetMethodID(clazz, "getPlayStatus", "()V");
+
+    method_getElementAttr =
+        env->GetMethodID(clazz, "getElementAttr", "(B[I)V");
+
+    method_registerNotification =
+        env->GetMethodID(clazz, "registerNotification", "(II)V");
+
+    ALOGI("%s: succeeds", __FUNCTION__);
+}
+
+static void initNative(JNIEnv *env, jobject object) {
+    const bt_interface_t* btInf;
+    bt_status_t status;
+
+    if ( (btInf = getBluetoothInterface()) == NULL) {
+        ALOGE("Bluetooth module is not loaded");
+        return;
+    }
+
+    if (sBluetoothAvrcpInterface !=NULL) {
+         ALOGW("Cleaning up Avrcp Interface before initializing...");
+         sBluetoothAvrcpInterface->cleanup();
+         sBluetoothAvrcpInterface = NULL;
+    }
+
+    if (mCallbacksObj != NULL) {
+         ALOGW("Cleaning up Avrcp callback object");
+         env->DeleteGlobalRef(mCallbacksObj);
+         mCallbacksObj = NULL;
+    }
+
+    if ( (sBluetoothAvrcpInterface = (btrc_interface_t *)
+          btInf->get_profile_interface(BT_PROFILE_AV_RC_ID)) == NULL) {
+        ALOGE("Failed to get Bluetooth Avrcp Interface");
+        return;
+    }
+
+    if ( (status = sBluetoothAvrcpInterface->init(&sBluetoothAvrcpCallbacks)) !=
+         BT_STATUS_SUCCESS) {
+        ALOGE("Failed to initialize Bluetooth Avrcp, status: %d", status);
+        sBluetoothAvrcpInterface = NULL;
+        return;
+    }
+
+    mCallbacksObj = env->NewGlobalRef(object);
+}
+
+static void cleanupNative(JNIEnv *env, jobject object) {
+    const bt_interface_t* btInf;
+
+    if ( (btInf = getBluetoothInterface()) == NULL) {
+        ALOGE("Bluetooth module is not loaded");
+        return;
+    }
+
+    if (sBluetoothAvrcpInterface !=NULL) {
+        sBluetoothAvrcpInterface->cleanup();
+        sBluetoothAvrcpInterface = NULL;
+    }
+
+    if (mCallbacksObj != NULL) {
+        env->DeleteGlobalRef(mCallbacksObj);
+        mCallbacksObj = NULL;
+    }
+}
+
+static jboolean getPlayStatusRspNative(JNIEnv *env, jobject object, jint playStatus,
+                                       jint songLen, jint songPos) {
+    bt_status_t status;
+
+    ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+    if (!sBluetoothAvrcpInterface) return JNI_FALSE;
+
+    if ((status = sBluetoothAvrcpInterface->get_play_status_rsp((btrc_play_status_t)playStatus,
+                                            songLen, songPos)) != BT_STATUS_SUCCESS) {
+        ALOGE("Failed get_play_status_rsp, status: %d", status);
+    }
+
+    return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+  static jboolean getElementAttrRspNative(JNIEnv *env, jobject object, jbyte numAttr,
+                                          jintArray attrIds, jobjectArray textArray) {
+    jint *attr;
+    bt_status_t status;
+    jstring text;
+    int i;
+    btrc_element_attr_val_t *pAttrs = NULL;
+    const char* textStr;
+
+    if (!sBluetoothAvrcpInterface) return JNI_FALSE;
+
+    if (numAttr > BTRC_MAX_ELEM_ATTR_SIZE) {
+        ALOGE("get_element_attr_rsp: number of attributes exceed maximum");
+        return JNI_FALSE;
+    }
+
+    pAttrs = new btrc_element_attr_val_t[numAttr];
+    if (!pAttrs) {
+        ALOGE("get_element_attr_rsp: not have enough memeory");
+        return JNI_FALSE;
+    }
+
+    attr = env->GetIntArrayElements(attrIds, NULL);
+    if (!attr) {
+        delete[] pAttrs;
+        jniThrowIOException(env, EINVAL);
+        return JNI_FALSE;
+    }
+
+    for (i = 0; i < numAttr; ++i) {
+        text = (jstring) env->GetObjectArrayElement(textArray, i);
+        textStr = env->GetStringUTFChars(text, NULL);
+        if (!textStr) {
+            ALOGE("get_element_attr_rsp: GetStringUTFChars return NULL");
+            env->DeleteLocalRef(text);
+            break;
+        }
+
+        pAttrs[i].attr_id = attr[i];
+        if (strlen(textStr) >= BTRC_MAX_ATTR_STR_LEN) {
+            ALOGE("get_element_attr_rsp: string length exceed maximum");
+            strncpy((char *)pAttrs[i].text, textStr, BTRC_MAX_ATTR_STR_LEN-1);
+            pAttrs[i].text[BTRC_MAX_ATTR_STR_LEN-1] = 0;
+        } else {
+            strcpy((char *)pAttrs[i].text, textStr);
+        }
+        env->ReleaseStringUTFChars(text, textStr);
+        env->DeleteLocalRef(text);
+    }
+
+    if (i < numAttr) {
+        delete[] pAttrs;
+        env->ReleaseIntArrayElements(attrIds, attr, 0);
+        return JNI_FALSE;
+    }
+
+    if ((status = sBluetoothAvrcpInterface->get_element_attr_rsp(numAttr, pAttrs)) !=
+        BT_STATUS_SUCCESS) {
+        ALOGE("Failed get_element_attr_rsp, status: %d", status);
+    }
+
+    delete[] pAttrs;
+    env->ReleaseIntArrayElements(attrIds, attr, 0);
+    return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean registerNotificationRspPlayStatusNative(JNIEnv *env, jobject object,
+                                                        jint type, jint playStatus) {
+    bt_status_t status;
+    btrc_register_notification_t param;
+
+    ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+    if (!sBluetoothAvrcpInterface) return JNI_FALSE;
+
+    param.play_status = (btrc_play_status_t)playStatus;
+    if ((status = sBluetoothAvrcpInterface->register_notification_rsp(BTRC_EVT_PLAY_STATUS_CHANGED,
+                  (btrc_notification_type_t)type, &param)) != BT_STATUS_SUCCESS) {
+        ALOGE("Failed register_notification_rsp play status, status: %d", status);
+    }
+
+    return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean registerNotificationRspTrackChangeNative(JNIEnv *env, jobject object,
+                                                         jint type, jbyteArray track) {
+    bt_status_t status;
+    btrc_register_notification_t param;
+    jbyte *trk;
+    int i;
+
+    ALOGI("%s: sBluetoothAvrcpInterface: %p", __FUNCTION__, sBluetoothAvrcpInterface);
+    if (!sBluetoothAvrcpInterface) return JNI_FALSE;
+
+    trk = env->GetByteArrayElements(track, NULL);
+    if (!trk) {
+        jniThrowIOException(env, EINVAL);
+        return JNI_FALSE;
+    }
+
+    for (i = 0; i < BTRC_UID_SIZE; ++i) {
+      param.track[i] = trk[i];
+    }
+
+    if ((status = sBluetoothAvrcpInterface->register_notification_rsp(BTRC_EVT_TRACK_CHANGE,
+                  (btrc_notification_type_t)type, &param)) != BT_STATUS_SUCCESS) {
+        ALOGE("Failed register_notification_rsp track change, status: %d", status);
+    }
+
+    env->ReleaseByteArrayElements(track, trk, 0);
+    return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static JNINativeMethod sMethods[] = {
+    {"classInitNative", "()V", (void *) classInitNative},
+    {"initNative", "()V", (void *) initNative},
+    {"cleanupNative", "()V", (void *) cleanupNative},
+    {"getPlayStatusRspNative", "(III)Z", (void *) getPlayStatusRspNative},
+    {"getElementAttrRspNative", "(B[I[Ljava/lang/String;)Z", (void *) getElementAttrRspNative},
+    {"registerNotificationRspPlayStatusNative", "(II)Z",
+     (void *) registerNotificationRspPlayStatusNative},
+    {"registerNotificationRspTrackChangeNative", "(I[B)Z",
+     (void *) registerNotificationRspTrackChangeNative},
+};
+
+int register_com_android_bluetooth_avrcp(JNIEnv* env)
+{
+    return jniRegisterNativeMethods(env, "com/android/bluetooth/a2dp/Avrcp",
+                                    sMethods, NELEM(sMethods));
+}
+
+}
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 a861217..310dcea
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -442,6 +442,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,
@@ -454,6 +461,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) {
@@ -923,7 +933,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)
@@ -966,6 +976,11 @@
         return JNI_ERR;
     }
 
+    if ((status = android::register_com_android_bluetooth_avrcp(e)) < 0) {
+        ALOGE("jni avrcp registration failure: %d", status);
+        return JNI_ERR;
+    }
+
     if ((status = android::register_com_android_bluetooth_hid(e)) < 0) {
         ALOGE("jni hid registration failure: %d", status);
         return JNI_ERR;
@@ -981,5 +996,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..0c3dbb3
--- /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 LOG_TAG "BtGatt.JNI"
+
+#define LOG_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-am/strings.xml b/res/values-am/strings.xml
index 53c566a..914854b 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -18,7 +18,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"አውርድ አደራጅን ድረስ።"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"የብሉቱዝ አጋራ አስተዳዳሪውን ለመድረስ እና ፋይሎች እንዲያስተላልፉ ለመጠቀም ለመተግበሪያው ይፈቅዳል።"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"የብሉቱዝ መሳሪያ መዳረሻን በተወዳጆች ዝርዝር አስገባ።"</string>
+    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"የብሉቱዝ መሳሪያ መዳረሻን በተወዳጆች ዝርዝር ያስገቡ።"</string>
     <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"መተግበሪያው አንድን የብሉቱዝ መሳሪያ በጊዜያዊነት በተወዳጆች ዝርዝር እንዲያስገባ ይፈቅድለታል፣ ይህም መሳሪያው ወደዚህኛው መሳሪያ ፋይሎችን ያለተጠቃሚው ማረጋገጫ ለመላክ ያስችለዋል።"</string>
     <string name="permlab_handoverStatus" msgid="7316032998801933554">"የBT ርክክብ ስርጭቶችን ተቀበል።"</string>
     <string name="permdesc_handoverStatus" msgid="4752738070064786310">"ከብሉቱዝ የርክክብ ሁኔታ መረጃ ለመቀበል ያስችላል።"</string>
@@ -39,8 +39,8 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"እሺ"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"ከ \"<xliff:g id="SENDER">%1$s</xliff:g>\" ገቢ መልዕክት ፋይል እየተቀበለ ሳለ ጊዜ አልቆ ነበር።"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="2958227698135117210">"የብሉቱዝ መጋሪያ፡ ገቢ ፋይል"</string>
-    <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"ይህን ፋይል መቀበል ትፈልጋለህ?"</string>
-    <string name="incoming_file_toast_msg" msgid="1733710749992901811">"ከሌላ መሣሪያ የሚገባ ፋይል አለ፣ ይህን ፋይል ለመቀበል መፈለግህን አረጋግጥ።"</string>
+    <string name="incoming_file_confirm_Notification_caption" msgid="6671081128475981157">"ይህን ፋይል መቀበል ይፈልጋሉ?"</string>
+    <string name="incoming_file_toast_msg" msgid="1733710749992901811">"ከሌላ መሣሪያ የሚገባ ፋይል አለ፣ ይህን ፋይል ለመቀበል መፈለግዎን ያረጋግጡ።"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"ብሉቱዝ ማጋሪያ፡ <xliff:g id="FILE">%1$s</xliff:g> እየተቀበለ"</string>
     <string name="notification_received" msgid="3324588019186687985">"ብሉቱዝ ማጋሪያ፡ <xliff:g id="FILE">%1$s</xliff:g> ደርሷል"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"ብሉቱዝ ማጋሪያ፡ ፋይል<xliff:g id="FILE">%1$s</xliff:g> አልደረሰም"</string>
@@ -78,7 +78,7 @@
     <string name="not_exist_file_desc" msgid="4059531573790529229">"ፋይል የለም:: "\n</string>
     <string name="enabling_progress_title" msgid="436157952334723406">"እባክዎ ይጠብቁ…"</string>
     <string name="enabling_progress_content" msgid="4601542238119927904">"ብሉቱዝ በማብራት ላይ..."</string>
-    <string name="bt_toast_1" msgid="972182708034353383">"ፋይሉ ይደርሳል።በማሳወቂያ ውስን ቦታ ውስጥ ሂደቱን ተመልከት።"</string>
+    <string name="bt_toast_1" msgid="972182708034353383">"ፋይሉ ይደርሳል።በማሳወቂያ ውስን ቦታ ውስጥ ሂደቱን ይመልከቱ።"</string>
     <string name="bt_toast_2" msgid="8602553334099066582">"ፋይሉን መቀበል አይቻልም::"</string>
     <string name="bt_toast_3" msgid="6707884165086862518">"ከ\"<xliff:g id="SENDER">%1$s</xliff:g>\" ፋይል መቀበል አቁሟል"</string>
     <string name="bt_toast_4" msgid="4678812947604395649">"ፋይል ወደ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" በመላክ ላይ"</string>
@@ -91,7 +91,7 @@
     <string name="status_pending" msgid="2503691772030877944">"የፋይል ዝውውር ገና አልተጀመረም::"</string>
     <string name="status_running" msgid="6562808920311008696">"የፋይል ዝውውር በመካሄድ ላይ ነው::"</string>
     <string name="status_success" msgid="239573225847565868">"የፋይል ዝውውሩ በተሳካ ሁኔታ ተጠናቋል::"</string>
-    <string name="status_not_accept" msgid="1695082417193780738">"ይዘት አይደገፍም::"</string>
+    <string name="status_not_accept" msgid="1695082417193780738">"ይዘቱ አይደገፍም።"</string>
     <string name="status_forbidden" msgid="613956401054050725">"ይህ ዝውውር በታለመው መሣሪያ የተከለከለ ነው።"</string>
     <string name="status_canceled" msgid="6664490318773098285">"ዝውውር በተጠቃሚ ተትቷል::"</string>
     <string name="status_file_error" msgid="3671917770630165299">"የማከማቻ ጉዳይ"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index bda160d..c44c55e 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -25,7 +25,7 @@
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Dispositivo desconocido"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Desconocido"</string>
-    <string name="airplane_error_title" msgid="2683839635115739939">"Modo de avión"</string>
+    <string name="airplane_error_title" msgid="2683839635115739939">"Modo avión"</string>
     <string name="airplane_error_msg" msgid="8698965595254137230">"No puedes usar Bluetooth en Modo avión."</string>
     <string name="bt_enable_title" msgid="8657832550503456572"></string>
     <string name="bt_enable_line1" msgid="7203551583048149">"Para acceder a nuestros servicios de Bluetooth, primero debes activar el Bluetooth."</string>
@@ -100,12 +100,12 @@
     <string name="status_connection_error" msgid="947681831523219891">"Conexión incorrecta"</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"No se puede procesar la solicitud correctamente."</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"Error desconocido"</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth recibido"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Recibidos por Bluetooth"</string>
     <string name="download_success" msgid="7036160438766730871">"Se recibieron <xliff:g id="FILE_SIZE">%1$s</xliff:g> completos."</string>
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> enviados por completo."</string>
     <string name="inbound_history_title" msgid="6940914942271327563">"Transferencias de entrada"</string>
     <string name="outbound_history_title" msgid="4279418703178140526">"Transferencias de salida"</string>
-    <string name="no_transfers" msgid="3482965619151865672">"El historial de transferencia está vacío."</string>
+    <string name="no_transfers" msgid="3482965619151865672">"El historial de transferencias está vacío."</string>
     <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"Se borrarán todos los elementos de la lista."</string>
     <string name="outbound_noti_title" msgid="8051906709452260849">"Compartir con Bluetooth: Archivos enviados"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"Compartir con Bluetooth: Archivos recibidos"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index d695f89..f67d9ba 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -62,7 +62,7 @@
     <string name="download_fail_ok" msgid="1521733664438320300">"Oke"</string>
     <string name="download_succ_line5" msgid="4509944688281573595">"File diterima"</string>
     <string name="download_succ_ok" msgid="7053688246357050216">"Buka"</string>
-    <string name="upload_line1" msgid="2055952074059709052">"Ke: \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
+    <string name="upload_line1" msgid="2055952074059709052">"Kepada: \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="upload_line3" msgid="4920689672457037437">"Jenis file: <xliff:g id="TYPE">%1$s</xliff:g> (<xliff:g id="SIZE">%2$s</xliff:g>)"</string>
     <string name="upload_line5" msgid="7759322537674229752">"Mengirim file…"</string>
     <string name="upload_succ_line5" msgid="5687317197463383601">"File terkirim"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 7a06b9e..a1593f6 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -31,7 +31,7 @@
     <string name="bt_enable_line1" msgid="7203551583048149">"Para utilizar os serviços Bluetooth, tem de activar primeiro o Bluetooth."</string>
     <string name="bt_enable_line2" msgid="4341936569415937994">"Activar o Bluetooth agora?"</string>
     <string name="bt_enable_cancel" msgid="1988832367505151727">"Cancelar"</string>
-    <string name="bt_enable_ok" msgid="3432462749994538265">"Activar"</string>
+    <string name="bt_enable_ok" msgid="3432462749994538265">"Ativar"</string>
     <string name="incoming_file_confirm_title" msgid="8139874248612182627">"Transferência do ficheiro"</string>
     <string name="incoming_file_confirm_content" msgid="6673812334377911289">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" quer enviar-lhe <xliff:g id="FILE">%2$s</xliff:g> (<xliff:g id="SIZE">%3$s</xliff:g>). "\n\n" Aceita o ficheiro?"</string>
     <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"Recusar"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index b779100..384dcd9 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Fikia kidhibiti cha vipakuzi"</string>
+    <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Fikia kidhibiti cha vipakuliwa."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Huruhusu programu kufikia kidhibiti cha BluetoothShare na kukitumia kuhamisha faili."</string>
     <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Ruhusu ufikiaji kifaa cha bluetooth."</string>
     <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Huruhusu programu kuorodhesha kwa muda kifaa cha Bluetooth kwenye orodha ya vifaa maalum, ikiruhusu kifaa hicho kutuma faili kwa kifaa hiki bila uthibitishaji wa mtumiaji."</string>
@@ -73,7 +73,7 @@
     <string name="upload_fail_cancel" msgid="9118496285835687125">"Funga"</string>
     <string name="bt_error_btn_ok" msgid="5965151173011534240">"Sawa"</string>
     <string name="unknown_file" msgid="6092727753965095366">"Faili isiyojulikana"</string>
-    <string name="unknown_file_desc" msgid="480434281415453287">"Hakuna prog ya kushughulikia aina hii ya faili. "\n</string>
+    <string name="unknown_file_desc" msgid="480434281415453287">"Hakuna programu ya kushughulikia aina hii ya faili. "\n</string>
     <string name="not_exist_file" msgid="3489434189599716133">"Hakuna faili."</string>
     <string name="not_exist_file_desc" msgid="4059531573790529229">"Faili haipo. "\n</string>
     <string name="enabling_progress_title" msgid="436157952334723406">"Tafadhali subiri…"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 13f9776..400d5a5 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -77,7 +77,7 @@
     <string name="not_exist_file" msgid="3489434189599716133">"沒有檔案"</string>
     <string name="not_exist_file_desc" msgid="4059531573790529229">"檔案不存在。"\n</string>
     <string name="enabling_progress_title" msgid="436157952334723406">"請稍候…"</string>
-    <string name="enabling_progress_content" msgid="4601542238119927904">"正在開啟藍牙..."</string>
+    <string name="enabling_progress_content" msgid="4601542238119927904">"正在開啟藍牙…"</string>
     <string name="bt_toast_1" msgid="972182708034353383">"即將接收檔案,請在通知面板中查看進度。"</string>
     <string name="bt_toast_2" msgid="8602553334099066582">"無法接收檔案。"</string>
     <string name="bt_toast_3" msgid="6707884165086862518">"已停止接收來自「<xliff:g id="SENDER">%1$s</xliff:g>」的檔案"</string>
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/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index 0e7515a..2ff6487 100755
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -39,6 +39,7 @@
     private static final String TAG="A2dpService";
 
     private A2dpStateMachine mStateMachine;
+    private Avrcp mAvrcp;
     private static A2dpService sAd2dpService;
 
     protected String getName() {
@@ -51,12 +52,14 @@
 
     protected boolean start() {
         mStateMachine = A2dpStateMachine.make(this, this);
+        mAvrcp = Avrcp.make(this);
         setA2dpService(this);
         return true;
     }
 
     protected boolean stop() {
         mStateMachine.doQuit();
+        mAvrcp.doQuit();
         return true;
     }
 
@@ -64,6 +67,10 @@
         if (mStateMachine!= null) {
             mStateMachine.cleanup();
         }
+        if (mAvrcp != null) {
+            mAvrcp.cleanup();
+            mAvrcp = null;
+        }
         clearA2dpService();
         return true;
     }
diff --git a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
index 4e1a9c8..f7cf37f 100755
--- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -58,7 +58,6 @@
 import java.util.Set;
 
 final class A2dpStateMachine extends StateMachine {
-    private static final String TAG = "A2dpStateMachine";
     private static final boolean DBG = false;
 
     static final int CONNECT = 1;
@@ -116,7 +115,7 @@
     }
 
     private A2dpStateMachine(A2dpService svc, Context context) {
-        super(TAG);
+        super("A2dpStateMachine");
         mService = svc;
         mContext = context;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -143,7 +142,7 @@
     }
 
     static A2dpStateMachine make(A2dpService svc, Context context) {
-        Log.d(TAG, "make");
+        Log.d("A2dpStateMachine", "make");
         A2dpStateMachine a2dpSm = new A2dpStateMachine(svc, context);
         a2dpSm.start();
         return a2dpSm;
@@ -167,7 +166,7 @@
         public boolean processMessage(Message message) {
             log("Disconnected process message: " + message.what);
             if (mCurrentDevice != null || mTargetDevice != null  || mIncomingDevice != null) {
-                Log.e(TAG, "ERROR: current, target, or mIncomingDevice not null in Disconnected");
+                loge("ERROR: current, target, or mIncomingDevice not null in Disconnected");
                 return NOT_HANDLED;
             }
 
@@ -202,7 +201,7 @@
                             processConnectionEvent(event.valueInt, event.device);
                             break;
                         default:
-                            Log.e(TAG, "Unexpected stack event: " + event.type);
+                            loge("Unexpected stack event: " + event.type);
                             break;
                     }
                     break;
@@ -221,11 +220,11 @@
         private void processConnectionEvent(int state, BluetoothDevice device) {
             switch (state) {
             case CONNECTION_STATE_DISCONNECTED:
-                Log.w(TAG, "Ignore HF DISCONNECTED event, device: " + device);
+                logw("Ignore HF DISCONNECTED event, device: " + device);
                 break;
             case CONNECTION_STATE_CONNECTING:
                 if (okToConnect(device)){
-                    Log.i(TAG,"Incoming A2DP accepted");
+                    logi("Incoming A2DP accepted");
                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
                                              BluetoothProfile.STATE_DISCONNECTED);
                     synchronized (A2dpStateMachine.this) {
@@ -234,7 +233,7 @@
                     }
                 } else {
                     //reject the connection and stay in Disconnected state itself
-                    Log.i(TAG,"Incoming A2DP rejected");
+                    logi("Incoming A2DP rejected");
                     disconnectA2dpNative(getByteAddress(device));
                     // the other profile connection should be initiated
                     AdapterService adapterService = AdapterService.getAdapterService();
@@ -245,9 +244,9 @@
                 }
                 break;
             case CONNECTION_STATE_CONNECTED:
-                Log.w(TAG, "A2DP Connected from Disconnected state");
+                logw("A2DP Connected from Disconnected state");
                 if (okToConnect(device)){
-                    Log.i(TAG,"Incoming A2DP accepted");
+                    logi("Incoming A2DP accepted");
                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
                                              BluetoothProfile.STATE_DISCONNECTED);
                     synchronized (A2dpStateMachine.this) {
@@ -256,7 +255,7 @@
                     }
                 } else {
                     //reject the connection and stay in Disconnected state itself
-                    Log.i(TAG,"Incoming A2DP rejected");
+                    logi("Incoming A2DP rejected");
                     disconnectA2dpNative(getByteAddress(device));
                     // the other profile connection should be initiated
                     AdapterService adapterService = AdapterService.getAdapterService();
@@ -267,10 +266,10 @@
                 }
                 break;
             case CONNECTION_STATE_DISCONNECTING:
-                Log.w(TAG, "Ignore HF DISCONNECTING event, device: " + device);
+                logw("Ignore HF DISCONNECTING event, device: " + device);
                 break;
             default:
-                Log.e(TAG, "Incorrect state: " + state);
+                loge("Incorrect state: " + state);
                 break;
             }
         }
@@ -317,7 +316,7 @@
                             processConnectionEvent(event.valueInt, event.device);
                             break;
                         default:
-                            Log.e(TAG, "Unexpected stack event: " + event.type);
+                            loge("Unexpected stack event: " + event.type);
                             break;
                     }
                     break;
@@ -372,7 +371,7 @@
                             transitionTo(mDisconnected);
                         }
                     } else {
-                        Log.e(TAG, "Unknown device Disconnected: " + device);
+                        loge("Unknown device Disconnected: " + device);
                     }
                     break;
             case CONNECTION_STATE_CONNECTED:
@@ -405,7 +404,7 @@
                         transitionTo(mConnected);
                     }
                 } else {
-                    Log.e(TAG, "Unknown device Connected: " + device);
+                    loge("Unknown device Connected: " + device);
                     // something is wrong here, but sync our state with stack
                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
                                              BluetoothProfile.STATE_DISCONNECTED);
@@ -428,7 +427,7 @@
                     log("Stack and target device are connecting");
                 }
                 else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
-                    Log.e(TAG, "Another connecting event on the incoming device");
+                    loge("Another connecting event on the incoming device");
                 } else {
                     // We get an incoming connecting request while Pending
                     // TODO(BT) is stack handing this case? let's ignore it for now
@@ -442,15 +441,15 @@
                         log("stack is disconnecting mCurrentDevice");
                     }
                 } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
-                    Log.e(TAG, "TargetDevice is getting disconnected");
+                    loge("TargetDevice is getting disconnected");
                 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
-                    Log.e(TAG, "IncomingDevice is getting disconnected");
+                    loge("IncomingDevice is getting disconnected");
                 } else {
-                    Log.e(TAG, "Disconnecting unknow device: " + device);
+                    loge("Disconnecting unknow device: " + device);
                 }
                 break;
             default:
-                Log.e(TAG, "Incorrect state: " + state);
+                loge("Incorrect state: " + state);
                 break;
             }
         }
@@ -470,7 +469,7 @@
         public boolean processMessage(Message message) {
             log("Connected process message: " + message.what);
             if (mCurrentDevice == null) {
-                Log.e(TAG, "ERROR: mCurrentDevice is null in Connected");
+                loge("ERROR: mCurrentDevice is null in Connected");
                 return NOT_HANDLED;
             }
 
@@ -523,7 +522,7 @@
                             processAudioStateEvent(event.valueInt, event.device);
                             break;
                         default:
-                            Log.e(TAG, "Unexpected stack event: " + event.type);
+                            loge("Unexpected stack event: " + event.type);
                             break;
                     }
                     break;
@@ -545,17 +544,17 @@
                             transitionTo(mDisconnected);
                         }
                     } else {
-                        Log.e(TAG, "Disconnected from unknown device: " + device);
+                        loge("Disconnected from unknown device: " + device);
                     }
                     break;
               default:
-                  Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
+                  loge("Connection State Device: " + device + " bad state: " + state);
                   break;
             }
         }
         private void processAudioStateEvent(int state, BluetoothDevice device) {
             if (!mCurrentDevice.equals(device)) {
-                Log.e(TAG, "Audio State Device:" + device + "is different from ConnectedDevice:" +
+                loge("Audio State Device:" + device + "is different from ConnectedDevice:" +
                                                            mCurrentDevice);
                 return;
             }
@@ -575,7 +574,7 @@
                     }
                     break;
                 default:
-                  Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
+                  loge("Audio State Device: " + device + " bad state: " + state);
                   break;
             }
         }
@@ -607,7 +606,7 @@
                 }
                 return BluetoothProfile.STATE_DISCONNECTED;
             } else {
-                Log.e(TAG, "Bad currentState: " + currentState);
+                loge("Bad currentState: " + currentState);
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
         }
@@ -720,10 +719,6 @@
         return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
     }
 
-    private void log(String msg) {
-        Log.d(TAG, msg);
-    }
-
     private class StackEvent {
         int type = EVENT_TYPE_NONE;
         int valueInt = 0;
diff --git a/src/com/android/bluetooth/a2dp/Avrcp.java b/src/com/android/bluetooth/a2dp/Avrcp.java
new file mode 100755
index 0000000..0f1e950
--- /dev/null
+++ b/src/com/android/bluetooth/a2dp/Avrcp.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2012 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.a2dp;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+import android.media.IRemoteControlDisplay;
+import android.media.MediaMetadataRetriever;
+import android.media.RemoteControlClient;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.content.Intent;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ParcelUuid;
+import android.util.Log;
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.util.IState;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * support Bluetooth AVRCP profile.
+ * support metadata, play status and event notification
+ */
+final class Avrcp {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "Avrcp";
+
+    private Context mContext;
+    private final AudioManager mAudioManager;
+    private AvrcpMessageHandler mHandler;
+    private IRemoteControlDisplayWeak mRemoteControlDisplay;
+    private int mClientGeneration;
+    private Metadata mMetadata;
+    private int mTransportControlFlags;
+    private int mCurrentPlayState;
+    private int mPlayStatusChangedNT;
+    private int mTrackChangedNT;
+    private long mTrackNumber;
+    private static final int MESSAGE_GET_PLAY_STATUS = 1;
+    private static final int MESSAGE_GET_ELEM_ATTRS = 2;
+    private static final int MESSAGE_REGISTER_NOTIFICATION = 3;
+    private static final int MSG_UPDATE_STATE = 100;
+    private static final int MSG_SET_METADATA = 101;
+    private static final int MSG_SET_TRANSPORT_CONTROLS = 102;
+    private static final int MSG_SET_ARTWORK = 103;
+    private static final int MSG_SET_GENERATION_ID = 104;
+
+    static {
+        classInitNative();
+    }
+
+    private Avrcp(Context context) {
+        mMetadata = new Metadata();
+        mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback
+        mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED;
+        mTrackChangedNT = NOTIFICATION_TYPE_CHANGED;
+        mTrackNumber = 0L;
+
+        mContext = context;
+
+        initNative();
+
+        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+    }
+
+    private void start() {
+        HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
+        thread.start();
+        Looper looper = thread.getLooper();
+        mHandler = new AvrcpMessageHandler(looper);
+        mRemoteControlDisplay = new IRemoteControlDisplayWeak(mHandler);
+        mAudioManager.registerRemoteControlDisplay(mRemoteControlDisplay);
+    }
+
+    static Avrcp make(Context context) {
+        if (DEBUG) Log.v(TAG, "make");
+        Avrcp ar = new Avrcp(context);
+        ar.start();
+        return ar;
+    }
+
+    public void doQuit() {
+        mHandler.removeCallbacksAndMessages(null);
+        Looper looper = mHandler.getLooper();
+        if (looper != null) {
+            looper.quit();
+        }
+        mAudioManager.unregisterRemoteControlDisplay(mRemoteControlDisplay);
+    }
+
+    public void cleanup() {
+        cleanupNative();
+    }
+
+    private static class IRemoteControlDisplayWeak extends IRemoteControlDisplay.Stub {
+        private WeakReference<Handler> mLocalHandler;
+        IRemoteControlDisplayWeak(Handler handler) {
+            mLocalHandler = new WeakReference<Handler>(handler);
+        }
+
+        @Override
+        public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
+                long currentPosMs, float speed) {
+            Handler handler = mLocalHandler.get();
+            if (handler != null) {
+                // TODO handle current playback position and playback speed
+                handler.obtainMessage(MSG_UPDATE_STATE, generationId, state).sendToTarget();
+            }
+        }
+
+        @Override
+        public void setMetadata(int generationId, Bundle metadata) {
+            Handler handler = mLocalHandler.get();
+            if (handler != null) {
+                handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget();
+            }
+        }
+
+        @Override
+        public void setTransportControlFlags(int generationId, int flags) {
+            Handler handler = mLocalHandler.get();
+            if (handler != null) {
+                handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, generationId, flags)
+                        .sendToTarget();
+            }
+        }
+
+        @Override
+        public void setArtwork(int generationId, Bitmap bitmap) {
+        }
+
+        @Override
+        public void setAllMetadata(int generationId, Bundle metadata, Bitmap bitmap) {
+            Handler handler = mLocalHandler.get();
+            if (handler != null) {
+                handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget();
+                handler.obtainMessage(MSG_SET_ARTWORK, generationId, 0, bitmap).sendToTarget();
+            }
+        }
+
+        @Override
+        public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent,
+                boolean clearing) throws RemoteException {
+            Handler handler = mLocalHandler.get();
+            if (handler != null) {
+                handler.obtainMessage(MSG_SET_GENERATION_ID,
+                    clientGeneration, (clearing ? 1 : 0), mediaIntent).sendToTarget();
+            }
+        }
+    }
+
+    /** Handles Avrcp messages. */
+    private final class AvrcpMessageHandler extends Handler {
+        private AvrcpMessageHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case MSG_UPDATE_STATE:
+                if (mClientGeneration == msg.arg1) updatePlayPauseState(msg.arg2);
+                break;
+
+            case MSG_SET_METADATA:
+                if (mClientGeneration == msg.arg1) updateMetadata((Bundle) msg.obj);
+                break;
+
+            case MSG_SET_TRANSPORT_CONTROLS:
+                if (mClientGeneration == msg.arg1) updateTransportControls(msg.arg2);
+                break;
+
+            case MSG_SET_ARTWORK:
+                if (mClientGeneration == msg.arg1) {
+                }
+                break;
+
+            case MSG_SET_GENERATION_ID:
+                if (DEBUG) Log.v(TAG, "New genId = " + msg.arg1 + ", clearing = " + msg.arg2);
+                mClientGeneration = msg.arg1;
+                break;
+
+            case MESSAGE_GET_PLAY_STATUS:
+                if (DEBUG) Log.v(TAG, "MESSAGE_GET_PLAY_STATUS");
+                getPlayStatusRspNative(convertPlayStateToPlayStatus(mCurrentPlayState), -1, -1);
+                break;
+
+            case MESSAGE_GET_ELEM_ATTRS:
+            {
+                String[] textArray;
+                int[] attrIds;
+                byte numAttr = (byte) msg.arg1;
+                ArrayList<Integer> attrList = (ArrayList<Integer>) msg.obj;
+                if (DEBUG) Log.v(TAG, "MESSAGE_GET_ELEM_ATTRS:numAttr=" + numAttr);
+                attrIds = new int[numAttr];
+                textArray = new String[numAttr];
+                for (int i = 0; i < numAttr; ++i) {
+                    attrIds[i] = attrList.get(i).intValue();
+                    textArray[i] = getAttributeString(attrIds[i]);
+                }
+                getElementAttrRspNative(numAttr, attrIds, textArray);
+                break;
+            }
+            case MESSAGE_REGISTER_NOTIFICATION:
+                if (DEBUG) Log.v(TAG, "MESSAGE_REGISTER_NOTIFICATION:event=" + msg.arg1 +
+                                      " param=" + msg.arg2);
+                processRegisterNotification(msg.arg1, msg.arg2);
+                break;
+
+            }
+        }
+    }
+
+    private void updatePlayPauseState(int state) {
+        if (DEBUG) Log.v(TAG,
+                "updatePlayPauseState(), old=" + mCurrentPlayState + ", state=" + state);
+        if (state == mCurrentPlayState) {
+            return;
+        }
+
+        int oldPlayStatus = convertPlayStateToPlayStatus(mCurrentPlayState);
+        int newPlayStatus = convertPlayStateToPlayStatus(state);
+        mCurrentPlayState = state;
+        if ((mPlayStatusChangedNT == NOTIFICATION_TYPE_INTERIM) && (oldPlayStatus != newPlayStatus)) {
+            mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED;
+            registerNotificationRspPlayStatusNative(mPlayStatusChangedNT, newPlayStatus);
+        }
+    }
+
+    private void updateTransportControls(int transportControlFlags) {
+        mTransportControlFlags = transportControlFlags;
+    }
+
+    class Metadata {
+        private String artist;
+        private String trackTitle;
+        private String albumTitle;
+
+        public Metadata() {
+            artist = null;
+            trackTitle = null;
+            albumTitle = null;
+        }
+
+        public String toString() {
+            return "Metadata[artist=" + artist + " trackTitle=" + trackTitle + " albumTitle=" +
+                   albumTitle + "]";
+        }
+    }
+
+    private String getMdString(Bundle data, int id) {
+        return data.getString(Integer.toString(id));
+    }
+
+    private void updateMetadata(Bundle data) {
+        String oldMetadata = mMetadata.toString();
+        mMetadata.artist = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST);
+        mMetadata.trackTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_TITLE);
+        mMetadata.albumTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUM);
+        if (!oldMetadata.equals(mMetadata.toString())) {
+            mTrackNumber++;
+            if (mTrackChangedNT == NOTIFICATION_TYPE_INTERIM) {
+                mTrackChangedNT = NOTIFICATION_TYPE_CHANGED;
+                sendTrackChangedRsp();
+            }
+        }
+        if (DEBUG) Log.v(TAG, "mMetadata=" + mMetadata.toString());
+    }
+
+    private void getPlayStatus() {
+        Message msg = mHandler.obtainMessage(MESSAGE_GET_PLAY_STATUS);
+        mHandler.sendMessage(msg);
+    }
+
+    private void getElementAttr(byte numAttr, int[] attrs) {
+        int i;
+        ArrayList<Integer> attrList = new ArrayList<Integer>();
+        for (i = 0; i < numAttr; ++i) {
+            attrList.add(attrs[i]);
+        }
+        Message msg = mHandler.obtainMessage(MESSAGE_GET_ELEM_ATTRS, (int)numAttr, 0, attrList);
+        mHandler.sendMessage(msg);
+    }
+
+    private void registerNotification(int eventId, int param) {
+        Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_NOTIFICATION, eventId, param);
+        mHandler.sendMessage(msg);
+    }
+
+    private void processRegisterNotification(int eventId, int param) {
+        switch (eventId) {
+            case EVT_PLAY_STATUS_CHANGED:
+                mPlayStatusChangedNT = NOTIFICATION_TYPE_INTERIM;
+                registerNotificationRspPlayStatusNative(mPlayStatusChangedNT,
+                                       convertPlayStateToPlayStatus(mCurrentPlayState));
+                break;
+
+            case EVT_TRACK_CHANGED:
+                mTrackChangedNT = NOTIFICATION_TYPE_INTERIM;
+                sendTrackChangedRsp();
+                break;
+
+        }
+    }
+
+    private void sendTrackChangedRsp() {
+        byte[] track = new byte[TRACK_ID_SIZE];
+        for (int i = 0; i < TRACK_ID_SIZE; ++i) {
+            track[i] = (byte) (mTrackNumber >> (8 * i));
+        }
+        registerNotificationRspTrackChangeNative(mTrackChangedNT, track);
+    }
+
+    private String getAttributeString(int attrId) {
+        String attrStr = null;
+        switch (attrId) {
+            case MEDIA_ATTR_TITLE:
+                attrStr = mMetadata.trackTitle;
+                break;
+
+            case MEDIA_ATTR_ARTIST:
+                attrStr = mMetadata.artist;
+                break;
+
+            case MEDIA_ATTR_ALBUM:
+                attrStr = mMetadata.albumTitle;
+                break;
+
+        }
+        if (attrStr == null) {
+            attrStr = new String();
+        }
+        if (DEBUG) Log.v(TAG, "getAttributeString:attrId=" + attrId + " str=" + attrStr);
+        return attrStr;
+    }
+
+    private int convertPlayStateToPlayStatus(int playState) {
+        int playStatus = PLAYSTATUS_ERROR;
+        switch (playState) {
+            case RemoteControlClient.PLAYSTATE_PLAYING:
+            case RemoteControlClient.PLAYSTATE_BUFFERING:
+                playStatus = PLAYSTATUS_PLAYING;
+                break;
+
+            case RemoteControlClient.PLAYSTATE_STOPPED:
+                playStatus = PLAYSTATUS_STOPPED;
+                break;
+
+            case RemoteControlClient.PLAYSTATE_PAUSED:
+                playStatus = PLAYSTATUS_PAUSED;
+                break;
+
+            case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
+            case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
+                playStatus = PLAYSTATUS_FWD_SEEK;
+                break;
+
+            case RemoteControlClient.PLAYSTATE_REWINDING:
+            case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
+                playStatus = PLAYSTATUS_REV_SEEK;
+                break;
+
+            case RemoteControlClient.PLAYSTATE_ERROR:
+            case RemoteControlClient.PLAYSTATE_NONE:
+                playStatus = PLAYSTATUS_ERROR;
+                break;
+
+        }
+        return playStatus;
+    }
+
+    // Do not modify without updating the HAL bt_rc.h files.
+
+    // match up with btrc_play_status_t enum of bt_rc.h
+    final static int PLAYSTATUS_STOPPED = 0;
+    final static int PLAYSTATUS_PLAYING = 1;
+    final static int PLAYSTATUS_PAUSED = 2;
+    final static int PLAYSTATUS_FWD_SEEK = 3;
+    final static int PLAYSTATUS_REV_SEEK = 4;
+    final static int PLAYSTATUS_ERROR = 255;
+
+    // match up with btrc_media_attr_t enum of bt_rc.h
+    final static int MEDIA_ATTR_TITLE = 1;
+    final static int MEDIA_ATTR_ARTIST = 2;
+    final static int MEDIA_ATTR_ALBUM = 3;
+    final static int MEDIA_ATTR_TRACK_NUM = 4;
+    final static int MEDIA_ATTR_NUM_TRACKS = 5;
+    final static int MEDIA_ATTR_GENRE = 6;
+    final static int MEDIA_ATTR_PLAYING_TIME = 7;
+
+    // match up with btrc_event_id_t enum of bt_rc.h
+    final static int EVT_PLAY_STATUS_CHANGED = 1;
+    final static int EVT_TRACK_CHANGED = 2;
+    final static int EVT_TRACK_REACHED_END = 3;
+    final static int EVT_TRACK_REACHED_START = 4;
+    final static int EVT_PLAY_POS_CHANGED = 5;
+    final static int EVT_BATT_STATUS_CHANGED = 6;
+    final static int EVT_SYSTEM_STATUS_CHANGED = 7;
+    final static int EVT_APP_SETTINGS_CHANGED = 8;
+
+    // match up with btrc_notification_type_t enum of bt_rc.h
+    final static int NOTIFICATION_TYPE_INTERIM = 0;
+    final static int NOTIFICATION_TYPE_CHANGED = 1;
+
+    // match up with BTRC_UID_SIZE of bt_rc.h
+    final static int TRACK_ID_SIZE = 8;
+
+    private native static void classInitNative();
+    private native void initNative();
+    private native void cleanupNative();
+    private native boolean getPlayStatusRspNative(int playStatus, int songLen, int songPos);
+    private native boolean getElementAttrRspNative(byte numAttr, int[] attrIds, String[] textArray);
+    private native boolean registerNotificationRspPlayStatusNative(int type, int playStatus);
+    private native boolean registerNotificationRspTrackChangeNative(int type, byte[] track);
+}
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/AdapterProperties.java b/src/com/android/bluetooth/btservice/AdapterProperties.java
index 9b3c20a..b05becd 100755
--- a/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -33,7 +33,8 @@
 import java.util.ArrayList;
 
 class AdapterProperties {
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
     private static final String TAG = "BluetoothAdapterProperties";
 
     private static final int BD_ADDR_LEN = 6; // 6 bytes
@@ -203,7 +204,7 @@
      */
     int getState() {
         synchronized (mObject) {
-            debugLog("State = " + mState);
+            if (VDBG) debugLog("State = " + mState);
             return mState;
         }
     }
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index 2fd6532..bc5851a 100755
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -889,7 +889,7 @@
             return  BluetoothAdapter.STATE_OFF;
         }
         else {
-            debugLog("getState(): mAdapterProperties: " + mAdapterProperties);
+            if (DBG) debugLog("getState(): mAdapterProperties: " + mAdapterProperties);
             return mAdapterProperties.getState();
         }
     }
diff --git a/src/com/android/bluetooth/btservice/AdapterState.java b/src/com/android/bluetooth/btservice/AdapterState.java
index a50a463..fbc32a5 100755
--- a/src/com/android/bluetooth/btservice/AdapterState.java
+++ b/src/com/android/bluetooth/btservice/AdapterState.java
@@ -36,7 +36,8 @@
  */
 
 final class AdapterState extends StateMachine {
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
     private static final String TAG = "BluetoothAdapterState";
 
     static final int USER_TURN_ON = 1;
@@ -72,13 +73,13 @@
 
     public boolean isTurningOn() {
         boolean isTurningOn=  mPendingCommandState.isTurningOn();
-        if (DBG) Log.d(TAG,"isTurningOn()=" + isTurningOn);
+        if (VDBG) Log.d(TAG,"isTurningOn()=" + isTurningOn);
         return isTurningOn;
     }
 
     public boolean isTurningOff() {
         boolean isTurningOff= mPendingCommandState.isTurningOff();
-        if (DBG) Log.d(TAG,"isTurningOff()=" + isTurningOff);
+        if (VDBG) Log.d(TAG,"isTurningOff()=" + isTurningOff);
         return isTurningOff;
     }
 
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;
+    }
+}
diff --git a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
index f5fbacc..f6d282c 100755
--- a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
+++ b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
@@ -238,9 +238,12 @@
 
             cdmaIconLevel = (levelDbm < levelEcio) ? levelDbm : levelEcio;
 
+            // STOPSHIP: Change back to getRilVoiceRadioTechnology
             if (mServiceState != null &&
-                  (mServiceState.getRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0 ||
-                   mServiceState.getRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A)) {
+                  (mServiceState.getRadioTechnology() ==
+                      ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0 ||
+                   mServiceState.getRadioTechnology() ==
+                       ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A)) {
                   int evdoEcio = signalStrength.getEvdoEcio();
                   int evdoSnr = signalStrength.getEvdoSnr();
                   int levelEvdoEcio = 0;
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index d9b2b7d..059f326 100755
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -1957,10 +1957,10 @@
         return ret;
     }
 
-
-    private void log(String msg) {
+    @Override
+    protected void log(String msg) {
         if (DBG) {
-            Log.d(TAG, msg);
+            super.log(msg);
         }
     }
 
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBatch.java b/src/com/android/bluetooth/opp/BluetoothOppBatch.java
index 7f51fe2..08701c4 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBatch.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBatch.java
@@ -187,6 +187,10 @@
         return (mShares.size() == 0);
     }
 
+    public int getNumShares() {
+        return mShares.size();
+    }
+
     /**
      * Get the running status of the batch
      * @return
diff --git a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
index bbe7899..e63d67c 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
@@ -169,7 +169,7 @@
             Intent intent1 = new Intent();
             intent1.setAction(action);
             intent1.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
-            intent1.setData(uri);
+            intent1.setDataAndNormalize(uri);
             this.sendBroadcast(intent1);
             finish();
         } else {
diff --git a/src/com/android/bluetooth/opp/BluetoothOppLiveFolder.java b/src/com/android/bluetooth/opp/BluetoothOppLiveFolder.java
index b1266a6..0cc0a21 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppLiveFolder.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppLiveFolder.java
@@ -68,7 +68,7 @@
     private static Intent createLiveFolder(Context context, Uri uri, String name, int icon) {
         final Intent intent = new Intent();
 
-        intent.setData(uri);
+        intent.setDataAndNormalize(uri);
         intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT, new Intent(
                 Constants.ACTION_OPEN, BluetoothShare.CONTENT_URI));
         intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME, name);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppNotification.java b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
index 7589850..5bc687b 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppNotification.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
@@ -330,7 +330,7 @@
 
             Intent intent = new Intent(Constants.ACTION_LIST);
             intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
-            intent.setData(Uri.parse(BluetoothShare.CONTENT_URI + "/" + item.id));
+            intent.setDataAndNormalize(Uri.parse(BluetoothShare.CONTENT_URI + "/" + item.id));
 
             b.setContentIntent(PendingIntent.getBroadcast(mContext, 0, intent, 0));
             mNotificationMgr.notify(item.id, b.getNotification());
@@ -491,7 +491,7 @@
 
             Intent intent = new Intent(Constants.ACTION_INCOMING_FILE_CONFIRM);
             intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
-            intent.setData(contentUri);
+            intent.setDataAndNormalize(contentUri);
 
             n.when = timeStamp;
             n.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(mContext, 0,
@@ -499,7 +499,7 @@
 
             intent = new Intent(Constants.ACTION_HIDE);
             intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
-            intent.setData(contentUri);
+            intent.setDataAndNormalize(contentUri);
             n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
 
             mNotificationMgr.notify(id, n);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java b/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
index dce7fa3..d3eb881 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
@@ -83,10 +83,10 @@
         mTransport = transport;
     }
 
-    public void start(Handler handler) {
+    public void start(Handler handler, int numShares) {
         if (D) Log.d(TAG, "Start!");
         mCallback = handler;
-        mThread = new ClientThread(mContext, mTransport);
+        mThread = new ClientThread(mContext, mTransport, numShares);
         mThread.start();
     }
 
@@ -140,13 +140,15 @@
 
         private boolean mConnected = false;
 
-        public ClientThread(Context context, ObexTransport transport) {
+        private int mNumShares;
+
+        public ClientThread(Context context, ObexTransport transport, int initialNumShares) {
             super("BtOpp ClientThread");
             mContext1 = context;
             mTransport1 = transport;
             waitingForShare = true;
             mWaitingForRemote = false;
-
+            mNumShares = initialNumShares;
             PowerManager pm = (PowerManager)mContext1.getSystemService(Context.POWER_SERVICE);
             wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         }
@@ -171,7 +173,7 @@
                 mInterrupted = true;
             }
             if (!mInterrupted) {
-                connect();
+                connect(mNumShares);
             }
 
             while (!mInterrupted) {
@@ -229,7 +231,7 @@
             }
         }
 
-        private void connect() {
+        private void connect(int numShares) {
             if (D) Log.d(TAG, "Create ClientSession with transport " + mTransport1.toString());
             try {
                 mCs = new ClientSession(mTransport1);
@@ -240,6 +242,7 @@
             if (mConnected) {
                 mConnected = false;
                 HeaderSet hs = new HeaderSet();
+                hs.setHeader(HeaderSet.COUNT, (long) numShares);
                 synchronized (this) {
                     mWaitingForRemote = true;
                 }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java b/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
index 3512fad..5bd54af 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
@@ -128,7 +128,7 @@
     /**
      * Called from BluetoothOppTransfer to start the "Transfer"
      */
-    public void start(Handler handler) {
+    public void start(Handler handler, int numShares) {
         if (D) Log.d(TAG, "Start!");
         mCallback = handler;
 
@@ -286,6 +286,7 @@
             values.put(BluetoothShare.USER_CONFIRMATION,
                     BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
             needConfirm = false;
+
         }
 
         Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
@@ -539,16 +540,39 @@
 
         if (D) Log.d(TAG, "onConnect");
         if (V) Constants.logHeader(request);
+        Long objectCount = null;
         try {
             byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
             if (V) Log.v(TAG, "onConnect(): uuid =" + Arrays.toString(uuid));
             if(uuid != null) {
                  return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
             }
+
+            objectCount = (Long) request.getHeader(HeaderSet.COUNT);
         } catch (IOException e) {
             Log.e(TAG, e.toString());
             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
         }
+        String destination;
+        if (mTransport instanceof BluetoothOppRfcommTransport) {
+            destination = ((BluetoothOppRfcommTransport)mTransport).getRemoteAddress();
+        } else {
+            destination = "FF:FF:FF:00:00:00";
+        }
+        boolean isHandover = BluetoothOppManager.getInstance(mContext).
+                isWhitelisted(destination);
+        if (isHandover) {
+            // Notify the handover requester file transfer has started
+            Intent intent = new Intent(Constants.ACTION_HANDOVER_STARTED);
+            if (objectCount != null) {
+                intent.putExtra(Constants.EXTRA_BT_OPP_OBJECT_COUNT, objectCount.intValue());
+            } else {
+                intent.putExtra(Constants.EXTRA_BT_OPP_OBJECT_COUNT,
+                        Constants.COUNT_HEADER_UNAVAILABLE);
+            }
+            intent.putExtra(Constants.EXTRA_BT_OPP_ADDRESS, destination);
+            mContext.sendBroadcast(intent, Constants.HANDOVER_STATUS_PERMISSION);
+        }
         mTimestamp = System.currentTimeMillis();
         return ResponseCodes.OBEX_HTTP_OK;
     }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppObexSession.java b/src/com/android/bluetooth/opp/BluetoothOppObexSession.java
index ce16875..19c98d2 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppObexSession.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppObexSession.java
@@ -66,7 +66,7 @@
 
     int SESSION_TIMEOUT = 50000;
 
-    void start(Handler sessionHandler);
+    void start(Handler sessionHandler, int numShares);
 
     void stop();
 
diff --git a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
index a061fa8..f4c8d9c 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
@@ -116,7 +116,7 @@
             Uri uri = intent.getData();
             Intent in = new Intent(context, BluetoothOppIncomingFileConfirmActivity.class);
             in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            in.setData(uri);
+            in.setDataAndNormalize(uri);
             context.startActivity(in);
 
             NotificationManager notMgr = (NotificationManager)context
@@ -157,7 +157,7 @@
             } else {
                 Intent in = new Intent(context, BluetoothOppTransferActivity.class);
                 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                in.setData(uri);
+                in.setDataAndNormalize(uri);
                 context.startActivity(in);
             }
 
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
index 2be679c..2dbed49 100755
--- a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
@@ -453,7 +453,7 @@
             if (V) Log.v(TAG, "Transfer has Server session" + mSession.toString());
         }
 
-        mSession.start(mSessionHandler);
+        mSession.start(mSessionHandler, mBatch.getNumShares());
         processCurrentShare();
     }
 
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java b/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java
index 697e3cb..abe343f 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java
@@ -289,7 +289,7 @@
         } else {
             Intent in = new Intent(this, BluetoothOppTransferActivity.class);
             in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            in.setData(contentUri);
+            in.setDataAndNormalize(contentUri);
             this.startActivity(in);
         }
     }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
index 7e03281..a3befb9 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppUtility.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -194,7 +194,7 @@
 
         if (isRecognizedFileType(context, path, mimetype)) {
             Intent activityIntent = new Intent(Intent.ACTION_VIEW);
-            activityIntent.setDataAndType(path, mimetype);
+            activityIntent.setDataAndTypeAndNormalize(path, mimetype);
 
             activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             try {
@@ -222,7 +222,7 @@
         if (D) Log.d(TAG, "RecognizedFileType() fileUri: " + fileUri + " mimetype: " + mimetype);
 
         Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW);
-        mimetypeIntent.setDataAndType(fileUri, mimetype);
+        mimetypeIntent.setDataAndTypeAndNormalize(fileUri, mimetype);
         List<ResolveInfo> list = context.getPackageManager().queryIntentActivities(mimetypeIntent,
                 PackageManager.MATCH_DEFAULT_ONLY);
 
diff --git a/src/com/android/bluetooth/opp/Constants.java b/src/com/android/bluetooth/opp/Constants.java
index b17c0e4..29df875 100644
--- a/src/com/android/bluetooth/opp/Constants.java
+++ b/src/com/android/bluetooth/opp/Constants.java
@@ -88,6 +88,10 @@
     public static final String ACTION_HANDOVER_SEND_MULTIPLE =
             "android.btopp.intent.action.HANDOVER_SEND_MULTIPLE";
 
+    /** the intent that is used for indicating an incoming transfer*/
+    public static final String ACTION_HANDOVER_STARTED =
+            "android.btopp.intent.action.BT_OPP_HANDOVER_STARTED";
+
     /** intent action used to indicate the progress of a handover transfer */
     public static final String ACTION_BT_OPP_TRANSFER_PROGRESS =
             "android.btopp.intent.action.BT_OPP_TRANSFER_PROGRESS";
@@ -104,6 +108,10 @@
     public static final String EXTRA_BT_OPP_ADDRESS =
             "android.btopp.intent.extra.BT_OPP_ADDRESS";
 
+    public static final String EXTRA_BT_OPP_OBJECT_COUNT =
+            "android.btopp.intent.extra.BT_OPP_OBJECT_COUNT";
+
+    public static final int COUNT_HEADER_UNAVAILABLE = -1;
     public static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0;
 
     public static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1;
@@ -210,6 +218,9 @@
         "application/msword",
         "application/vnd.ms-powerpoint",
         "application/pdf",
+        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+        "application/vnd.openxmlformats-officedocument.presentationml.presentation",
     };
 
     /**
@@ -277,7 +288,7 @@
         if (BluetoothShare.isStatusCompleted(status)) {
             Intent intent = new Intent(BluetoothShare.TRANSFER_COMPLETED_ACTION);
             intent.setClassName(THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
-            intent.setData(contentUri);
+            intent.setDataAndNormalize(contentUri);
             context.sendBroadcast(intent);
         }
     }
diff --git a/src/com/android/bluetooth/pan/PanService.java b/src/com/android/bluetooth/pan/PanService.java
index c0ada7c..22f4d83 100755
--- a/src/com/android/bluetooth/pan/PanService.java
+++ b/src/com/android/bluetooth/pan/PanService.java
@@ -30,17 +30,21 @@
 import android.net.ConnectivityManager;
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkStateTracker;
 import android.net.NetworkUtils;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
 import android.os.Message;
+import android.os.Messenger;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.util.Log;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.Utils;
+import com.android.internal.util.AsyncChannel;
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -73,6 +77,8 @@
     private static final int MESSAGE_CONNECT_STATE_CHANGED = 11;
     private boolean mTetherOn = false;
 
+    AsyncChannel mTetherAc;
+
 
     static {
         classInitNative();
@@ -97,11 +103,20 @@
         }
         initializeNative();
         mNativeAvailable=true;
+
+        ConnectivityManager cm = (ConnectivityManager) getSystemService(
+                Context.CONNECTIVITY_SERVICE);
+        cm.supplyMessenger(ConnectivityManager.TYPE_BLUETOOTH, new Messenger(mHandler));
+
         return true;
     }
 
     protected boolean stop() {
         mHandler.removeCallbacksAndMessages(null);
+        if (mTetherAc != null) {
+            mTetherAc.disconnect();
+            mTetherAc = null;
+        }
         return true;
     }
 
@@ -113,8 +128,8 @@
         if(mPanDevices != null) {
             List<BluetoothDevice> DevList = getConnectedDevices();
             for(BluetoothDevice dev : DevList) {
-               handlePanDeviceStateChange(dev, mPanIfName, BluetoothProfile.STATE_DISCONNECTED,
-                                                   BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE);
+                handlePanDeviceStateChange(dev, mPanIfName, BluetoothProfile.STATE_DISCONNECTED,
+                        BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE);
             }
             mPanDevices.clear();
         }
@@ -131,11 +146,13 @@
                 case MESSAGE_CONNECT:
                 {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
-                    if (!connectPanNative(Utils.getByteAddress(device), BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE)) {
+                    if (!connectPanNative(Utils.getByteAddress(device),
+                            BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE)) {
                         handlePanDeviceStateChange(device, null, BluetoothProfile.STATE_CONNECTING,
-                                                   BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE);
-                        handlePanDeviceStateChange(device, null, BluetoothProfile.STATE_DISCONNECTED,
-                                                   BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE);
+                                BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE);
+                        handlePanDeviceStateChange(device, null,
+                                BluetoothProfile.STATE_DISCONNECTED, BluetoothPan.LOCAL_PANU_ROLE,
+                                BluetoothPan.REMOTE_NAP_ROLE);
                         break;
                     }
                 }
@@ -144,10 +161,12 @@
                 {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
                     if (!disconnectPanNative(Utils.getByteAddress(device)) ) {
-                        handlePanDeviceStateChange(device, mPanIfName, BluetoothProfile.STATE_DISCONNECTING,
-                                                   BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE);
-                        handlePanDeviceStateChange(device, mPanIfName, BluetoothProfile.STATE_DISCONNECTED,
-                                                   BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE);
+                        handlePanDeviceStateChange(device, mPanIfName,
+                                BluetoothProfile.STATE_DISCONNECTING, BluetoothPan.LOCAL_PANU_ROLE,
+                                BluetoothPan.REMOTE_NAP_ROLE);
+                        handlePanDeviceStateChange(device, mPanIfName,
+                                BluetoothProfile.STATE_DISCONNECTED, BluetoothPan.LOCAL_PANU_ROLE,
+                                BluetoothPan.REMOTE_NAP_ROLE);
                         break;
                     }
                 }
@@ -157,9 +176,33 @@
                     ConnectState cs = (ConnectState)msg.obj;
                     BluetoothDevice device = getDevice(cs.addr);
                     // TBD get iface from the msg
-                    if (DBG) log("MESSAGE_CONNECT_STATE_CHANGED: " + device + " state: " + cs.state);
-                    handlePanDeviceStateChange(device, mPanIfName /* iface */, convertHalState(cs.state),
-                                               cs.local_role,  cs.remote_role);
+                    if (DBG) {
+                        log("MESSAGE_CONNECT_STATE_CHANGED: " + device + " state: " + cs.state);
+                    }
+                    handlePanDeviceStateChange(device, mPanIfName /* iface */,
+                            convertHalState(cs.state), cs.local_role,  cs.remote_role);
+                }
+                break;
+                case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
+                {
+                    if (mTetherAc != null) {
+                        mTetherAc.replyToMessage(msg,
+                                AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+                                AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
+                    } else {
+                        mTetherAc = new AsyncChannel();
+                        mTetherAc.connected(null, this, msg.replyTo);
+                        mTetherAc.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+                                AsyncChannel.STATUS_SUCCESSFUL);
+                    }
+                }
+                break;
+                case AsyncChannel.CMD_CHANNEL_DISCONNECT:
+                {
+                    if (mTetherAc != null) {
+                        mTetherAc.disconnect();
+                        mTetherAc = null;
+                    }
                 }
                 break;
             }
@@ -169,7 +212,8 @@
     /**
      * Handlers for incoming service calls
      */
-    private static class BluetoothPanBinder extends IBluetoothPan.Stub implements IProfileServiceBinder {
+    private static class BluetoothPanBinder extends IBluetoothPan.Stub
+            implements IProfileServiceBinder {
         private PanService mService;
         public BluetoothPanBinder(PanService svc) {
             mService = svc;
@@ -328,8 +372,12 @@
         int local_role;
         int remote_role;
     };
-    private void onConnectStateChanged(byte[] address, int state, int error, int local_role, int remote_role) {
-        if (DBG) log("onConnectStateChanged: " + state + ", local role:" + local_role + ", remote_role: " + remote_role);
+    private void onConnectStateChanged(byte[] address, int state, int error, int local_role,
+            int remote_role) {
+        if (DBG) {
+            log("onConnectStateChanged: " + state + ", local role:" + local_role +
+                    ", remote_role: " + remote_role);
+        }
         Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_STATE_CHANGED);
         msg.obj = new ConnectState(address, state, error, local_role, remote_role);
         mHandler.sendMessage(msg);
@@ -359,8 +407,11 @@
 
     void handlePanDeviceStateChange(BluetoothDevice device,
                                     String iface, int state, int local_role, int remote_role) {
-        if(DBG) Log.d(TAG, "handlePanDeviceStateChange: device: " + device + ", iface: " + iface +
-                    ", state: " + state + ", local_role:" + local_role + ", remote_role:" + remote_role);
+        if(DBG) {
+            Log.d(TAG, "handlePanDeviceStateChange: device: " + device + ", iface: " + iface +
+                    ", state: " + state + ", local_role:" + local_role + ", remote_role:" +
+                    remote_role);
+        }
         int prevState;
         String ifaceAddr = null;
         BluetoothPanDevice panDevice = mPanDevices.get(device);
@@ -392,31 +443,18 @@
             }
         } else {
             // PANU Role = reverse Tether
-            Log.d(TAG, "handlePanDeviceStateChange LOCAL_PANU_ROLE:REMOTE_NAP_ROLE");
+            Log.d(TAG, "handlePanDeviceStateChange LOCAL_PANU_ROLE:REMOTE_NAP_ROLE state = " +
+                    state + ", prevState = " + prevState);
             if (state == BluetoothProfile.STATE_CONNECTED) {
-                if(DBG) Log.d(TAG, "handlePanDeviceStateChange: panu STATE_CONNECTED, startReverseTether");
-                IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
-                INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
-                Log.d(TAG, "call INetworkManagementService.startReverseTethering()");
-                try {
-                    service.startReverseTethering(iface);
-                } catch (Exception e) {
-                    Log.e(TAG, "Cannot start reverse tethering: " + e);
-                    return;
-                }
-            } else if (state == BluetoothProfile.STATE_DISCONNECTED &&
-                  (prevState == BluetoothProfile.STATE_CONNECTED ||
-                  prevState == BluetoothProfile.STATE_DISCONNECTING)) {
-                if(DBG) Log.d(TAG, "handlePanDeviceStateChange: stopReverseTether, panDevice.mIface: "
-                                    + panDevice.mIface);
-                IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
-                INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
-                try {
-                    service.stopReverseTethering();
-                } catch(Exception e) {
-                    Log.e(TAG, "Cannot stop reverse tethering: " + e);
-                    return;
-                }
+                LinkProperties lp = new LinkProperties();
+                lp.setInterfaceName(iface);
+                mTetherAc.sendMessage(NetworkStateTracker.EVENT_NETWORK_CONNECTED, lp);
+           } else if (state == BluetoothProfile.STATE_DISCONNECTED &&
+                   (prevState == BluetoothProfile.STATE_CONNECTED ||
+                   prevState == BluetoothProfile.STATE_DISCONNECTING)) {
+                LinkProperties lp = new LinkProperties();
+                lp.setInterfaceName(iface);
+                mTetherAc.sendMessage(NetworkStateTracker.EVENT_NETWORK_DISCONNECTED, lp);
             }
         }