Merge "Enables AT+ANDROID command in Bluetooth HFP." into klp-dev
diff --git a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
index c408691..05034e2 100755
--- a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
+++ b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
@@ -323,3 +323,13 @@
         mType = type;
     }
 }
+
+class HeadsetVendorSpecificResultCode {
+    String mCommand;
+    String mArg;
+
+    public HeadsetVendorSpecificResultCode(String command, String arg) {
+        mCommand = command;
+        mArg = arg;
+    }
+}
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index baf30f4..6ea0721 100755
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -263,6 +263,16 @@
             if (service == null) return;
             service.clccResponse(index, direction, status, mode, mpty, number, type);
         }
+
+        public boolean sendVendorSpecificResultCode(BluetoothDevice device,
+                                                    String command,
+                                                    String arg) {
+            HeadsetService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.sendVendorSpecificResultCode(device, command, arg);
+        }
     };
 
     //API methods
@@ -479,4 +489,22 @@
             new HeadsetClccResponse(index, direction, status, mode, mpty, number, type));
     }
 
+    private boolean sendVendorSpecificResultCode(BluetoothDevice device,
+                                                 String command,
+                                                 String arg) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        int connectionState = mStateMachine.getConnectionState(device);
+        if (connectionState != BluetoothProfile.STATE_CONNECTED) {
+            return false;
+        }
+        // Currently we support only "+ANDROID".
+        if (!command.equals(BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID)) {
+            Log.w(TAG, "Disallowed unsolicited result code command: " + command);
+            return false;
+        }
+        mStateMachine.sendMessage(HeadsetStateMachine.SEND_VENDOR_SPECIFIC_RESULT_CODE,
+                new HeadsetVendorSpecificResultCode(command, arg));
+        return true;
+    }
+
 }
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index 562a83b..d30d17c 100755
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -62,7 +62,9 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 final class HeadsetStateMachine extends StateMachine {
@@ -89,9 +91,10 @@
     static final int INTENT_BATTERY_CHANGED = 10;
     static final int DEVICE_STATE_CHANGED = 11;
     static final int SEND_CCLC_RESPONSE = 12;
+    static final int SEND_VENDOR_SPECIFIC_RESULT_CODE = 13;
 
-    static final int VIRTUAL_CALL_START = 13;
-    static final int VIRTUAL_CALL_STOP = 14;
+    static final int VIRTUAL_CALL_START = 14;
+    static final int VIRTUAL_CALL_STOP = 15;
 
     private static final int STACK_EVENT = 101;
     private static final int DIALING_OUT_TIMEOUT = 102;
@@ -102,6 +105,9 @@
     private static final int DIALING_OUT_TIMEOUT_VALUE = 10000;
     private static final int START_VR_TIMEOUT_VALUE = 5000;
 
+    // Keys are AT commands, and values are the company IDs.
+    private static final Map<String, Integer> VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID;
+
     private static final ParcelUuid[] HEADSET_UUIDS = {
         BluetoothUuid.HSP,
         BluetoothUuid.Handsfree,
@@ -159,6 +165,10 @@
 
     static {
         classInitNative();
+
+        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID = new HashMap<String, Integer>();
+        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put("+XEVENT", BluetoothAssignedNumbers.PLANTRONICS);
+        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put("+ANDROID", BluetoothAssignedNumbers.GOOGLE);
     }
 
     private HeadsetStateMachine(HeadsetService context) {
@@ -649,6 +659,10 @@
                 case SEND_CCLC_RESPONSE:
                     processSendClccResponse((HeadsetClccResponse) message.obj);
                     break;
+                case SEND_VENDOR_SPECIFIC_RESULT_CODE:
+                    processSendVendorSpecificResultCode(
+                            (HeadsetVendorSpecificResultCode) message.obj);
+                    break;
                 case DIALING_OUT_TIMEOUT:
                     if (mDialingOut) {
                         mDialingOut= false;
@@ -863,6 +877,10 @@
                 case SEND_CCLC_RESPONSE:
                     processSendClccResponse((HeadsetClccResponse) message.obj);
                     break;
+                case SEND_VENDOR_SPECIFIC_RESULT_CODE:
+                    processSendVendorSpecificResultCode(
+                            (HeadsetVendorSpecificResultCode) message.obj);
+                    break;
 
                 case VIRTUAL_CALL_START:
                     initiateScoUsingVirtualVoiceCall();
@@ -1701,21 +1719,40 @@
         return out.toArray();
     }
 
-    private void processAtXevent(String atString) {
-        log("processAtXevent - atString = "+ atString);
-        if (atString.startsWith("=") && !atString.startsWith("=?")) {
-            Object[] args = generateArgs(atString.substring(1));
-            broadcastVendorSpecificEventIntent("+XEVENT",
-                                               BluetoothAssignedNumbers.PLANTRONICS,
-                                               BluetoothHeadset.AT_CMD_TYPE_SET,
-                                               args,
-                                               mCurrentDevice);
-            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
+    /**
+     * @return {@code true} if the given string is a valid vendor-specific AT command.
+     */
+    private boolean processVendorSpecificAt(String atString) {
+        log("processVendorSpecificAt - atString = " + atString);
+
+        // Currently we accept only SET type commands.
+        int indexOfEqual = atString.indexOf("=");
+        if (indexOfEqual == -1) {
+            Log.e(TAG, "processVendorSpecificAt: command type error in " + atString);
+            return false;
         }
-        else {
-            Log.e(TAG, "processAtXevent: command type error");
-            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+
+        String command = atString.substring(0, indexOfEqual);
+        Integer companyId = VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.get(command);
+        if (companyId == null) {
+            Log.e(TAG, "processVendorSpecificAt: unsupported command: " + atString);
+            return false;
         }
+
+        String arg = atString.substring(indexOfEqual + 1);
+        if (arg.startsWith("?")) {
+            Log.e(TAG, "processVendorSpecificAt: command type error in " + atString);
+            return false;
+        }
+
+        Object[] args = generateArgs(arg);
+        broadcastVendorSpecificEventIntent(command,
+                                           companyId,
+                                           BluetoothHeadset.AT_CMD_TYPE_SET,
+                                           args,
+                                           mCurrentDevice);
+        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
+        return true;
     }
 
     private void processUnknownAt(String atString) {
@@ -1729,9 +1766,7 @@
             processAtCpbs(atCommand.substring(5), commandType);
         else if (atCommand.startsWith("+CPBR"))
             processAtCpbr(atCommand.substring(5), commandType, mCurrentDevice);
-        else if (atCommand.startsWith("+XEVENT"))
-            processAtXevent(atCommand.substring(7));
-        else
+        else if (!processVendorSpecificAt(atCommand))
             atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
     }
 
@@ -1889,6 +1924,14 @@
                            clcc.mNumber, clcc.mType);
     }
 
+    private void processSendVendorSpecificResultCode(HeadsetVendorSpecificResultCode resultCode) {
+        String stringToSend = resultCode.mCommand + ": ";
+        if (resultCode.mArg != null) {
+            stringToSend += resultCode.mArg;
+        }
+        atResponseStringNative(stringToSend);
+    }
+
     private String getCurrentDeviceName() {
         String defaultName = "<unknown>";
         if (mCurrentDevice == null) {