HFP 1.7 profile update (4/4)

-> Added HF Indicator support.
-> Sent a Broadcast intent upon receiving AT + BIND and
   AT + BIEV Events with the required parameters
-> Support for sending +BIND response on indicator
   status change

Bug: 19983867
Change-Id: I2580fc771080f1bc92fc4ddd5ca7c7cb57773183
diff --git a/jni/com_android_bluetooth_hfp.cpp b/jni/com_android_bluetooth_hfp.cpp
index 1666990..3b6c2ce 100644
--- a/jni/com_android_bluetooth_hfp.cpp
+++ b/jni/com_android_bluetooth_hfp.cpp
@@ -50,6 +50,8 @@
 static jmethodID method_onAtClcc;
 static jmethodID method_onUnknownAt;
 static jmethodID method_onKeyPressed;
+static jmethodID method_onAtBind;
+static jmethodID method_onAtBiev;
 
 static const bthf_interface_t *sBluetoothHfpInterface = NULL;
 static jobject mCallbacksObj = NULL;
@@ -377,6 +379,34 @@
     sCallbackEnv->DeleteLocalRef(addr);
 }
 
+static void at_bind_callback(char *at_string, bt_bdaddr_t *bd_addr) {
+    CHECK_CALLBACK_ENV
+
+    jbyteArray addr = marshall_bda(bd_addr);
+    if (addr == NULL)
+        return;
+
+    jstring js_at_string = sCallbackEnv->NewStringUTF(at_string);
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtBind, js_at_string, addr);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+
+    sCallbackEnv->DeleteLocalRef(js_at_string);
+    sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void at_biev_callback(bthf_hf_ind_type_t ind_id, int ind_value, bt_bdaddr_t *bd_addr) {
+    CHECK_CALLBACK_ENV
+
+    jbyteArray addr = marshall_bda(bd_addr);
+    if (addr == NULL)
+        return;
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtBiev, ind_id, (jint)ind_value, addr);
+    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
+    sCallbackEnv->DeleteLocalRef(addr);
+}
+
 static bthf_callbacks_t sBluetoothHfpCallbacks = {
     sizeof(sBluetoothHfpCallbacks),
     connection_state_callback,
@@ -395,6 +425,8 @@
     at_cops_callback,
     at_clcc_callback,
     unknown_at_callback,
+    at_bind_callback,
+    at_biev_callback,
     key_pressed_callback
 };
 
@@ -417,6 +449,8 @@
     method_onAtClcc = env->GetMethodID(clazz, "onAtClcc", "([B)V");
     method_onUnknownAt = env->GetMethodID(clazz, "onUnknownAt", "(Ljava/lang/String;[B)V");
     method_onKeyPressed = env->GetMethodID(clazz, "onKeyPressed", "([B)V");
+    method_onAtBind = env->GetMethodID(clazz, "onATBind", "(Ljava/lang/String;[B)V");
+    method_onAtBiev = env->GetMethodID(clazz, "onATBiev", "(II[B)V");
 
     ALOGI("%s: succeeds", __FUNCTION__);
 }
@@ -678,6 +712,30 @@
     return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
+static jboolean bindResponseNative(JNIEnv *env,jobject object,
+                                jint ind_id, jboolean ind_status,
+                                jbyteArray address) {
+    ALOGI("%s: sBluetoothHfpInterface: %p", __FUNCTION__, sBluetoothHfpInterface);
+
+    if (!sBluetoothHfpInterface)
+        return JNI_FALSE;
+
+    jbyte *addr = env->GetByteArrayElements(address, NULL);
+    if (!addr) {
+        jniThrowIOException(env, EINVAL);
+        return JNI_FALSE;
+    }
+
+    bt_status_t status = sBluetoothHfpInterface->bind_response((bthf_hf_ind_type_t) ind_id,
+                   ind_status ? BTHF_HF_IND_ENABLED : BTHF_HF_IND_DISABLED,
+                   (bt_bdaddr_t *)addr);
+
+    if (status != BT_STATUS_SUCCESS)
+        ALOGE("%s: Failed bind_response, status: %d", __FUNCTION__, status);
+
+    env->ReleaseByteArrayElements(address, addr, 0);
+    return (status == BT_STATUS_SUCCESS ? JNI_TRUE : JNI_FALSE);
+}
 
 static jboolean atResponseStringNative(JNIEnv *env, jobject object, jstring response_str,
                                                  jbyteArray address) {
@@ -806,6 +864,7 @@
     {"notifyDeviceStatusNative", "(IIII)Z", (void *) notifyDeviceStatusNative},
     {"copsResponseNative", "(Ljava/lang/String;[B)Z", (void *) copsResponseNative},
     {"cindResponseNative", "(IIIIIII[B)Z", (void *) cindResponseNative},
+    {"bindResponseNative", "(IZ[B)Z", (void *)bindResponseNative},
     {"atResponseStringNative", "(Ljava/lang/String;[B)Z", (void *) atResponseStringNative},
     {"atResponseCodeNative", "(II[B)Z", (void *)atResponseCodeNative},
     {"clccResponseNative", "(IIIIZLjava/lang/String;I[B)Z", (void *) clccResponseNative},
diff --git a/src/com/android/bluetooth/hfp/HeadsetHalConstants.java b/src/com/android/bluetooth/hfp/HeadsetHalConstants.java
index 0742917..dee633f 100644
--- a/src/com/android/bluetooth/hfp/HeadsetHalConstants.java
+++ b/src/com/android/bluetooth/hfp/HeadsetHalConstants.java
@@ -64,4 +64,8 @@
     final static int CALL_STATE_INCOMING = 4;
     final static int CALL_STATE_WAITING = 5;
     final static int CALL_STATE_IDLE = 6;
+
+    // Match up with bthf_hf_ind_type_t of bt_hf.h
+    final static int HF_INDICATOR_ENHANCED_DRIVER_SAFETY = 1;
+    final static int HF_INDICATOR_BATTERY_LEVEL_STATUS = 2;
 }
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index 10bf23a..3f662d8 100755
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -307,6 +307,12 @@
             if (service == null) return false;
             return service.disableWBS();
         }
+
+        public void bindResponse(int ind_id, boolean ind_status) {
+            HeadsetService service = getService();
+            if (service == null) return;
+            service.bindResponse(ind_id, ind_status);
+        }
     };
 
     //API methods
@@ -579,6 +585,24 @@
         return true;
     }
 
+    private boolean bindResponse(int ind_id, boolean ind_status) {
+        for (BluetoothDevice device: getConnectedDevices()) {
+            int connectionState = mStateMachine.getConnectionState(device);
+            if (connectionState != BluetoothProfile.STATE_CONNECTED &&
+                connectionState != BluetoothProfile.STATE_CONNECTING) {
+                continue;
+            }
+            if (DBG) Log.d("Bind Response sent for", device.getAddress());
+            Message msg = mStateMachine.obtainMessage(HeadsetStateMachine.BIND_RESPONSE);
+            msg.obj = device;
+            msg.arg1 = ind_id;
+            msg.arg2 = (ind_status == true) ? 1 : 0;
+            mStateMachine.sendMessage(msg);
+            return true;
+        }
+        return false;
+    }
+
     @Override
     public void dump(StringBuilder sb) {
         super.dump(sb);
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index 66d75d3..ae53ee9 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -104,6 +104,7 @@
     static final int ENABLE_WBS = 16;
     static final int DISABLE_WBS = 17;
 
+    static final int BIND_RESPONSE = 18;
 
     private static final int STACK_EVENT = 101;
     private static final int DIALING_OUT_TIMEOUT = 102;
@@ -522,6 +523,15 @@
                             }
                             processConnectionEvent(event.valueInt, event.device);
                             break;
+
+                        case EVENT_TYPE_BIND:
+                            processAtBind(event.valueString, event.device);
+                            break;
+
+                        case EVENT_TYPE_BIEV:
+                            processAtBiev(event.valueInt, event.valueInt2, event.device);
+                            break;
+
                         default:
                             Log.e(TAG, "Unexpected event: " + event.type);
                             break;
@@ -924,6 +934,13 @@
                     configureWBSNative(getByteAddress(device),NBS_CODEC);
                 }
                     break;
+                case BIND_RESPONSE:
+                {
+                    BluetoothDevice device = (BluetoothDevice) message.obj;
+                    bindResponseNative((int)message.arg1, ((message.arg2 == 1) ? true : false),
+                                        getByteAddress(device));
+                }
+                    break;
                 case START_VR_TIMEOUT:
                 {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
@@ -998,6 +1015,12 @@
                         case EVENT_TYPE_KEY_PRESSED:
                             processKeyPressed(event.device);
                             break;
+                        case EVENT_TYPE_BIND:
+                            processAtBind(event.valueString, event.device);
+                            break;
+                        case EVENT_TYPE_BIEV:
+                            processAtBiev(event.valueInt, event.valueInt2, event.device);
+                            break;
                         default:
                             Log.e(TAG, "Unknown stack event: " + event.type);
                             break;
@@ -1428,6 +1451,12 @@
                         case EVENT_TYPE_KEY_PRESSED:
                             processKeyPressed(event.device);
                             break;
+                        case EVENT_TYPE_BIND:
+                            processAtBind(event.valueString, event.device);
+                            break;
+                        case EVENT_TYPE_BIEV:
+                            processAtBiev(event.valueInt, event.valueInt2, event.device);
+                            break;
                         default:
                             Log.e(TAG, "Unknown stack event: " + event.type);
                             break;
@@ -1770,6 +1799,12 @@
                         case EVENT_TYPE_KEY_PRESSED:
                             processKeyPressed(event.device);
                             break;
+                        case EVENT_TYPE_BIND:
+                            processAtBind(event.valueString, event.device);
+                            break;
+                        case EVENT_TYPE_BIEV:
+                            processAtBiev(event.valueInt, event.valueInt2, event.device);
+                            break;
                         default:
                             Log.e(TAG, "Unexpected event: " + event.type);
                             break;
@@ -3127,6 +3162,65 @@
         }
     }
 
+    private void sendIndicatorIntent(BluetoothDevice device, int ind_id, String ind_value)
+    {
+        Intent intent = new Intent(BluetoothHeadset.ACTION_HF_INDICATORS_VALUE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, ind_id);
+        if (ind_value != null)
+            intent.putExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, ind_value);
+
+        mService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
+    }
+
+    private void processAtBind( String at_string, BluetoothDevice device) {
+        log("processAtBind processAtBind: " + at_string);
+
+        // Parse the AT String to find the Indicator Ids that are supported
+        int ind_id = 0;
+        int iter = 0;
+        int iter1 = 0;
+
+        while (iter < at_string.length()) {
+            iter1 = findChar(',', at_string, iter);
+            String id = at_string.substring(iter, iter1);
+
+            try {
+                ind_id = new Integer(id);
+            } catch (NumberFormatException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+
+            switch (ind_id) {
+                case HeadsetHalConstants.HF_INDICATOR_ENHANCED_DRIVER_SAFETY :
+                    log("Send Broadcast intent for the" +
+                        "Enhanced Driver Safety indicator.");
+                    sendIndicatorIntent(device, ind_id, null);
+                    break;
+                case HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS :
+                    log("Send Broadcast intent for the" +
+                        "Battery Level indicator.");
+                    sendIndicatorIntent(device, ind_id, null);
+                    break;
+                default:
+                    log("Invalid HF Indicator Received");
+                    break;
+            }
+
+            iter = iter1 + 1; // move past comma
+        }
+    }
+
+    private void processAtBiev( int ind_id, int ind_value, BluetoothDevice device) {
+        log(" Process AT + BIEV Command : " + ind_id + ", " + ind_value);
+
+        String ind_value_str = Integer.toString(ind_value);
+
+        Intent intent = new Intent(BluetoothHeadset.ACTION_HF_INDICATORS_VALUE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        sendIndicatorIntent(device, ind_id, ind_value_str);
+    }
+
     private void onConnectionStateChanged(int state, byte[] address) {
         StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
         event.valueInt = state;
@@ -3240,6 +3334,21 @@
         sendMessage(STACK_EVENT, event);
     }
 
+    private void onATBind(String atString, byte[] address) {
+        StackEvent event = new StackEvent(EVENT_TYPE_BIND);
+        event.valueString = atString;
+        event.device = getDevice(address);
+        sendMessage(STACK_EVENT, event);
+    }
+
+    private void onATBiev(int ind_id, int ind_value, byte[] address) {
+        StackEvent event = new StackEvent(EVENT_TYPE_BIEV);
+        event.valueInt = ind_id;
+        event.valueInt2 = ind_value;
+        event.device = getDevice(address);
+        sendMessage(STACK_EVENT, event);
+    }
+
     private void processIntentBatteryChanged(Intent intent) {
         int batteryLevel = intent.getIntExtra("level", -1);
         int scale = intent.getIntExtra("scale", -1);
@@ -3408,6 +3517,8 @@
     final private static int EVENT_TYPE_UNKNOWN_AT = 15;
     final private static int EVENT_TYPE_KEY_PRESSED = 16;
     final private static int EVENT_TYPE_WBS = 17;
+    final private static int EVENT_TYPE_BIND = 18;
+    final private static int EVENT_TYPE_BIEV = 19;
 
     private class StackEvent {
         int type = EVENT_TYPE_NONE;
@@ -3438,6 +3549,7 @@
     private native boolean cindResponseNative(int service, int numActive, int numHeld,
                                               int callState, int signal, int roam,
                                               int batteryCharge, byte[] address);
+    private native boolean bindResponseNative(int ind_id, boolean ind_status, byte[] address);
     private native boolean notifyDeviceStatusNative(int networkState, int serviceType, int signal,
                                                     int batteryCharge);