Added AT+CPBR support

Change-Id: Ice860d8f97350d805a5478e94a27ae19cf42dafd
diff --git a/jni/com_android_bluetooth_hfp.cpp b/jni/com_android_bluetooth_hfp.cpp
index e618152..f730382 100644
--- a/jni/com_android_bluetooth_hfp.cpp
+++ b/jni/com_android_bluetooth_hfp.cpp
@@ -474,11 +474,11 @@
     return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
-static jboolean atResponseCodeNative(JNIEnv *env, jobject object, jint response_code) {
+static jboolean atResponseCodeNative(JNIEnv *env, jobject object, jint response_code, jint cmee_code) {
     bt_status_t status;
     if (!sBluetoothHfpInterface) return JNI_FALSE;
 
-    if ( (status = sBluetoothHfpInterface->at_response((bthf_at_response_t) response_code)) !=
+    if ( (status = sBluetoothHfpInterface->at_response((bthf_at_response_t) response_code, cmee_code)) !=
          BT_STATUS_SUCCESS) {
         LOGE("Failed AT response, status: %d", status);
     }
@@ -538,7 +538,7 @@
     {"copsResponseNative", "(Ljava/lang/String;)Z", (void *) copsResponseNative},
     {"cindResponseNative", "(IIIIIII)Z", (void *) cindResponseNative},
     {"atResponseStringNative", "(Ljava/lang/String;)Z", (void *) atResponseStringNative},
-    {"atResponseCodeNative", "(I)Z", (void *)atResponseCodeNative},
+    {"atResponseCodeNative", "(II)Z", (void *)atResponseCodeNative},
     {"clccResponseNative", "(IIIIZLjava/lang/String;I)Z", (void *) clccResponseNative},
     {"phoneStateChangeNative", "(IIILjava/lang/String;I)Z", (void *) phoneStateChangeNative},
 };
diff --git a/src/com/android/bluetooth/hfp/AtPhonebook.java b/src/com/android/bluetooth/hfp/AtPhonebook.java
index 555465c..ef76dd7 100755
--- a/src/com/android/bluetooth/hfp/AtPhonebook.java
+++ b/src/com/android/bluetooth/hfp/AtPhonebook.java
@@ -16,6 +16,8 @@
 
 package com.android.bluetooth.hfp;
 
+import com.android.bluetooth.R;
+
 import com.android.internal.telephony.GsmAlphabet;
 
 import android.bluetooth.BluetoothDevice;
@@ -70,6 +72,8 @@
         public int     nameColumn;
     };
 
+    private HeadsetStateMachine mStateMachine;
+
     private final Context mContext;
 
     private String mCurrentPhonebook;
@@ -87,8 +91,14 @@
     private final HashMap<String, PhonebookResult> mPhonebooks =
             new HashMap<String, PhonebookResult>(4);
 
-    public AtPhonebook(Context context) {
+    final int TYPE_UNKNOWN = -1;
+    final int TYPE_READ = 0;
+    final int TYPE_SET = 1;
+    final int TYPE_TEST = 2;
+
+    public AtPhonebook(Context context, HeadsetStateMachine headsetState) {
         mContext = context;
+        mStateMachine = headsetState;
         mPhonebooks.put("DC", new PhonebookResult());  // dialled calls
         mPhonebooks.put("RC", new PhonebookResult());  // received calls
         mPhonebooks.put("MC", new PhonebookResult());  // missed calls
@@ -119,36 +129,217 @@
         return number;
     }
 
-    /* package */ void handleAccessPermissionResult(Intent intent) {
-        if (!mCheckingAccessPermission) {
-            return;
-        }
+    public boolean getCheckingAccessPermission() {
+        return mCheckingAccessPermission;
+    }
 
-        //HeadsetBase headset = mHandsfree.getHeadset();
-        // ASSERT: (headset != null) && headSet.isConnected()
-        // REASON: mCheckingAccessPermission is true, otherwise resetAtState
-        //         has set mCheckingAccessPermission to false
+    public void setCheckingAccessPermission(boolean checkAccessPermission) {
+        mCheckingAccessPermission = checkAccessPermission;
+    }
 
-        if (intent.getAction().equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
+    public void setCpbrIndex(int cpbrIndex) {
+        mCpbrIndex1 = mCpbrIndex2 = cpbrIndex;
+    }
 
-            if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
-                                   BluetoothDevice.CONNECTION_ACCESS_NO) ==
-                BluetoothDevice.CONNECTION_ACCESS_YES) {
-                // BluetoothDevice remoteDevice = headset.getRemoteDevice();
-                // TODO(BT) when we do CPBR, fix this NullPointerException
-                BluetoothDevice remoteDevice = null;
-                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
-                    remoteDevice.setTrust(true);
+    public void handleCscsCommand(String atString, int type)
+    {
+        log("handleCscsCommand - atString = " +atString);
+        // Select Character Set
+        int atCommandResult = HeadsetHalConstants.AT_RESPONSE_ERROR;
+        int atCommandErrorCode = -1;
+        String atCommandResponse = null;
+        switch (type) {
+            case TYPE_READ: // Read
+                log("handleCscsCommand - Read Command");
+                atCommandResponse = "+CSCS: \"" + mCharacterSet + "\"";
+                atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
+                break;
+            case TYPE_TEST: // Test
+                log("handleCscsCommand - Test Command");
+                atCommandResponse = ( "+CSCS: (\"UTF-8\",\"IRA\",\"GSM\")");
+                atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
+                break;
+            case TYPE_SET: // Set
+                log("handleCscsCommand - Set Command");
+                String[] args = atString.split("=");
+                if (args.length < 2 || !(args[1] instanceof String)) {
+                    mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+                    break;
                 }
-
-                // AtCommandResult cpbrResult = processCpbrCommand();
-                // headset.sendURC(cpbrResult.toString());
-            } else {
-                // headset.sendURC("ERROR");
-            }
+                String characterSet = ((atString.split("="))[1]);
+                characterSet = characterSet.replace("\"", "");
+                if (characterSet.equals("GSM") || characterSet.equals("IRA") ||
+                    characterSet.equals("UTF-8") || characterSet.equals("UTF8")) {
+                    mCharacterSet = characterSet;
+                    atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
+                } else {
+                    atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_SUPPORTED;
+                }
+                break;
+            case TYPE_UNKNOWN:
+            default:
+                log("handleCscsCommand - Invalid chars");
+                atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
         }
-        mCpbrIndex1 = mCpbrIndex2 = -1;
-        mCheckingAccessPermission = false;
+        if (atCommandResponse != null)
+            mStateMachine.atResponseStringNative(atCommandResponse);
+        mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+    }
+
+    public void handleCpbsCommand(String atString, int type) {
+        // Select PhoneBook memory Storage
+        log("handleCpbsCommand - atString = " +atString);
+        int atCommandResult = HeadsetHalConstants.AT_RESPONSE_ERROR;
+        int atCommandErrorCode = -1;
+        String atCommandResponse = null;
+        switch (type) {
+            case TYPE_READ: // Read
+                log("handleCpbsCommand - read command");
+                // Return current size and max size
+                if ("SM".equals(mCurrentPhonebook)) {
+                    atCommandResponse = "+CPBS: \"SM\",0," + getMaxPhoneBookSize(0);
+                    atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
+                    if (atCommandResponse != null)
+                        mStateMachine.atResponseStringNative(atCommandResponse);
+                    mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+                    break;
+                }
+                PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true);
+                if (pbr == null) {
+                    atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_SUPPORTED;
+                    mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+                    break;
+                }
+                int size = pbr.cursor.getCount();
+                atCommandResponse = "+CPBS: \"" + mCurrentPhonebook + "\"," + size + "," + getMaxPhoneBookSize(size);
+                atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
+                break;
+            case TYPE_TEST: // Test
+                log("handleCpbsCommand - test command");
+                atCommandResponse = ("+CPBS: (\"ME\",\"SM\",\"DC\",\"RC\",\"MC\")");
+                atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
+                break;
+            case TYPE_SET: // Set
+                log("handleCpbsCommand - set command");
+                String[] args = atString.split("=");
+                // Select phonebook memory
+                if (args.length < 2 || !(args[1] instanceof String)) {
+                    mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+                    break;
+                }
+                String pb = ((String)args[1]).trim();
+                while (pb.endsWith("\"")) pb = pb.substring(0, pb.length() - 1);
+                while (pb.startsWith("\"")) pb = pb.substring(1, pb.length());
+                if (getPhonebookResult(pb, false) == null && !"SM".equals(pb)) {
+                   if (DBG) log("Dont know phonebook: '" + pb + "'");
+                   atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
+                   mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+                   break;
+                }
+                mCurrentPhonebook = pb;
+                atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
+                break;
+            case TYPE_UNKNOWN:
+            default:
+                log("handleCpbsCommand - invalid chars");
+                atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
+        }
+        if (atCommandResponse != null)
+            mStateMachine.atResponseStringNative(atCommandResponse);
+        mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+    }
+
+    public void handleCpbrCommand(String atString, int type, BluetoothDevice remoteDevice) {
+        log("handleCpbrCommand - atString = " +atString);
+        int atCommandResult = HeadsetHalConstants.AT_RESPONSE_ERROR;
+        int atCommandErrorCode = -1;
+        String atCommandResponse = null;
+        switch (type) {
+            case TYPE_TEST: // Test
+                /* Ideally we should return the maximum range of valid index's
+                 * for the selected phone book, but this causes problems for the
+                 * Parrot CK3300. So instead send just the range of currently
+                 * valid index's.
+                 */
+                log("handleCpbrCommand - test command");
+                int size;
+                if ("SM".equals(mCurrentPhonebook)) {
+                    size = 0;
+                } else {
+                    PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true); //false);
+                    if (pbr == null) {
+                        atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
+                        mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+                        break;
+                    }
+                    size = pbr.cursor.getCount();
+                    log("handleCpbrCommand - size = "+size);
+                }
+                if (size == 0) {
+                    /* Sending "+CPBR: (1-0)" can confused some carkits, send "1-1" * instead */
+                    size = 1;
+                }
+                atCommandResponse = "+CPBR: (1-" + size + "),30,30";
+                atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
+                if (atCommandResponse != null)
+                    mStateMachine.atResponseStringNative(atCommandResponse);
+                mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+                break;
+            // Read PhoneBook Entries
+            case TYPE_READ:
+            case TYPE_SET: // Set & read
+                // Phone Book Read Request
+                // AT+CPBR=<index1>[,<index2>]
+                log("handleCpbrCommand - set/read command");
+                if (mCpbrIndex1 != -1) {
+                   /* handling a CPBR at the moment, reject this CPBR command */
+                   atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
+                   mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+                   break;
+                }
+                // Parse indexes
+                int index1;
+                int index2;
+                if ((atString.split("=")).length < 2) {
+                    mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+                    break;
+                }
+                String atCommand = (atString.split("="))[1];
+                String[] indices = atCommand.split(",");
+                for(int i = 0; i < indices.length; i++)
+                    indices[i] = indices[i].trim();
+                try {
+                    index1 = Integer.parseInt(indices[0]);
+                    if (indices.length == 1)
+                        index2 = index1;
+                    else
+                        index2 = Integer.parseInt(indices[1]);
+                }
+                catch (Exception e) {
+                    log("handleCpbrCommand - exception - invalid chars");
+                    atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
+                    mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+                    break;
+                }
+                mCpbrIndex1 = index1;
+                mCpbrIndex2 = index2;
+                mCheckingAccessPermission = true;
+
+                if (checkAccessPermission(remoteDevice)) {
+                    mCheckingAccessPermission = false;
+                    atCommandResult = processCpbrCommand();
+                    mCpbrIndex1 = mCpbrIndex2 = -1;
+                    mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+                    break;
+                }
+                // no reponse here, will continue the process in handleAccessPermissionResult
+                break;
+                case TYPE_UNKNOWN:
+                default:
+                    log("handleCpbrCommand - invalid chars");
+                    atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
+                    mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
+        }
     }
 
     /** Get the most recent result for the given phone book,
@@ -246,18 +437,26 @@
     }
 
     // process CPBR command after permission check
-    private String processCpbrCommand()
+    /*package*/ int processCpbrCommand()
     {
+        log("processCpbrCommand");
+        int atCommandResult = HeadsetHalConstants.AT_RESPONSE_ERROR;
+        int atCommandErrorCode = -1;
+        String atCommandResponse = null;
+        StringBuilder response = new StringBuilder();
+        String record;
+
         // Shortcut SM phonebook
         if ("SM".equals(mCurrentPhonebook)) {
-            // return new AtCommandResult(AtCommandResult.OK);
+            atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
+            return atCommandResult;
         }
 
         // Check phonebook
-        PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, false);
+        PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true); //false);
         if (pbr == null) {
-            // return mHandsfree.reportCmeError(BluetoothCmeError.OPERATION_NOT_ALLOWED);
-            return null;
+            atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
+            return atCommandResult;
         }
 
         // More sanity checks
@@ -266,14 +465,15 @@
         // Handsfree connection.
         if (pbr.cursor.getCount() == 0 || mCpbrIndex1 <= 0 || mCpbrIndex2 < mCpbrIndex1  ||
             mCpbrIndex2 > pbr.cursor.getCount() || mCpbrIndex1 > pbr.cursor.getCount()) {
-            // return new AtCommandResult(AtCommandResult.OK);
-            return null;
+            atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
+            return atCommandResult;
         }
 
         // Process
-        //AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
+        atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
         int errorDetected = -1; // no error
         pbr.cursor.moveToPosition(mCpbrIndex1 - 1);
+        log("mCpbrIndex1 = "+mCpbrIndex1+ " and mCpbrIndex2 = "+mCpbrIndex2);
         for (int index = mCpbrIndex1; index <= mCpbrIndex2; index++) {
             String number = pbr.cursor.getString(pbr.numberColumn);
             String name = null;
@@ -316,7 +516,7 @@
             if (number.equals("-1")) {
                 // unknown numbers are stored as -1 in our database
                 number = "";
-                // name = mContext.getString(R.string.unknown);
+                name = mContext.getString(R.string.unknownNumber);
             }
 
             // TODO(): Handle IRA commands. It's basically
@@ -324,20 +524,45 @@
             if (!name.equals("") && mCharacterSet.equals("GSM")) {
                 byte[] nameByte = GsmAlphabet.stringToGsm8BitPacked(name);
                 if (nameByte == null) {
-                    // name = mContext.getString(R.string.unknown);
+                    name = mContext.getString(R.string.unknownNumber);
                 } else {
                     name = new String(nameByte);
                 }
             }
 
-            // result.addResponse("+CPBR: " + index + ",\"" + number + "\"," +
-            //                   regionType + ",\"" + name + "\"");
+            record = "+CPBR: " + index + ",\"" + number + "\"," + regionType + ",\"" + name + "\"";
+            record = record + "\r\n\r\n";
+            atCommandResponse = record;
+            log("processCpbrCommand - atCommandResponse = "+atCommandResponse);
+            mStateMachine.atResponseStringNative(atCommandResponse);
             if (!pbr.cursor.moveToNext()) {
                 break;
             }
         }
-        // return result;
-        return null;
+        return atCommandResult;
+    }
+
+    // Check if the remote device has premission to read our phone book
+    // Return true if it has the permission
+    // false if not known and we have sent our Intent to check
+    private boolean checkAccessPermission(BluetoothDevice remoteDevice) {
+        log("checkAccessPermission");
+        boolean trust = remoteDevice.getTrustState();
+
+        if (trust) {
+            return true;
+        }
+
+        log("checkAccessPermission - ACTION_CONNECTION_ACCESS_REQUEST");
+        Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
+        intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
+        intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
+        BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, remoteDevice);
+        // Leave EXTRA_PACKAGE_NAME and EXTRA_CLASS_NAME field empty
+        // BluetoothHandsfree's broadcast receiver is anonymous, cannot be targeted
+        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+        return false;
     }
 
     private static String getPhoneType(int type) {
diff --git a/src/com/android/bluetooth/hfp/BluetoothCmeError.java b/src/com/android/bluetooth/hfp/BluetoothCmeError.java
new file mode 100644
index 0000000..1f2a45d
--- /dev/null
+++ b/src/com/android/bluetooth/hfp/BluetoothCmeError.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2008 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.hfp;
+
+/* Constants for extended AT error codes specified by the Handsfree profile. */
+public class BluetoothCmeError {
+    public static final int AG_FAILURE = 0;
+    public static final int NO_CONNECTION_TO_PHONE = 1;
+    public static final int OPERATION_NOT_ALLOWED = 3;
+    public static final int OPERATION_NOT_SUPPORTED = 4;
+    public static final int PIN_REQUIRED = 5;
+    public static final int SIM_MISSING = 10;
+    public static final int SIM_PIN_REQUIRED = 11;
+    public static final int SIM_PUK_REQUIRED = 12;
+    public static final int SIM_FAILURE = 13;
+    public static final int SIM_BUSY = 14;
+    public static final int WRONG_PASSWORD = 16;
+    public static final int SIM_PIN2_REQUIRED = 17;
+    public static final int SIM_PUK2_REQUIRED = 18;
+    public static final int MEMORY_FULL = 20;
+    public static final int INVALID_INDEX = 21;
+    public static final int MEMORY_FAILURE = 23;
+    public static final int TEXT_TOO_LONG = 24;
+    public static final int TEXT_HAS_INVALID_CHARS = 25;
+    public static final int DIAL_STRING_TOO_LONG = 26;
+    public static final int DIAL_STRING_HAS_INVALID_CHARS = 27;
+    public static final int NO_SERVICE = 30;
+    public static final int ONLY_911_ALLOWED = 32;
+}
+
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index 698c1af..817f053 100755
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -54,6 +54,8 @@
         mStateMachine.start();
         IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
         filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
+        filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
+
         registerReceiver(mHeadsetReceiver, filter);
         mReceiverRegistered=true;
         return true;
@@ -92,6 +94,10 @@
                                               intent);
                 }
             }
+            else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
+                Log.v(TAG, "HeadsetService -  Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY");
+                mStateMachine.handleAccessPermissionResult(intent);
+            }
         }
     };
 
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index c16b676..5eb9753 100755
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -96,7 +96,6 @@
     private AudioOn mAudioOn;
 
     private Context mContext;
-
     private PowerManager mPowerManager;
 
     private boolean mVoiceRecognitionStarted = false;
@@ -158,7 +157,7 @@
 
         mDialingOut = false;
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        mPhonebook = new AtPhonebook(mContext);
+        mPhonebook = new AtPhonebook(mContext, this);
         mPhoneState = new HeadsetPhoneState(context, this);
         mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -208,6 +207,7 @@
         @Override
         public void enter() {
             log("Enter Disconnected: " + getCurrentMessage().what);
+            mPhonebook.resetAtState();
             mPhoneState.listenForPhoneState(false);
         }
 
@@ -597,14 +597,14 @@
                 case DIALING_OUT_TIMEOUT:
                     if (mDialingOut) {
                         mDialingOut= false;
-                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
+                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                     }
                     break;
                 case START_VR_TIMEOUT:
                     if (mWaitingForVoiceRecognition) {
                         mWaitingForVoiceRecognition = false;
                         Log.e(TAG, "Timeout waiting for voice recognition to start");
-                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
+                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                     }
                     break;
                 case STACK_EVENT:
@@ -798,14 +798,14 @@
                 case DIALING_OUT_TIMEOUT:
                     if (mDialingOut) {
                         mDialingOut= false;
-                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
+                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                     }
                     break;
                 case START_VR_TIMEOUT:
                     if (mWaitingForVoiceRecognition) {
                         mWaitingForVoiceRecognition = false;
                         Log.e(TAG, "Timeout waiting for voice recognition to start");
-                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
+                        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                     }
                     break;
                 case STACK_EVENT:
@@ -1020,7 +1020,7 @@
                 try {
                     mContext.startActivity(sVoiceCommandIntent);
                 } catch (ActivityNotFoundException e) {
-                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
+                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                     return;
                 }
                 expectVoiceRecognition();
@@ -1028,7 +1028,7 @@
         } else if (state == HeadsetHalConstants.VR_STATE_STOPPED) {
             if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition)
             {
-                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK);
+                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
                 mVoiceRecognitionStarted = false;
                 mWaitingForVoiceRecognition = false;
                 if (!isInCall())
@@ -1036,7 +1036,7 @@
             }
             else
             {
-                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
+                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
             }
         } else {
             Log.e(TAG, "Bad Voice Recognition state: " + state);
@@ -1060,7 +1060,7 @@
             {
                 Log.d(TAG, "Voice recognition started successfully");
                 mWaitingForVoiceRecognition = false;
-                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK);
+                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
                 removeMessages(START_VR_TIMEOUT);
             }
             else
@@ -1153,6 +1153,51 @@
                                     HEADSET_NREC + "=on");
     }
 
+    private String parseUnknownAt(String atString)
+    {
+        StringBuilder atCommand = new StringBuilder(atString.length());
+        String result = null;
+
+        for (int i = 0; i < atString.length(); i++) {
+            char c = atString.charAt(i);
+            if (c == '"') {
+                int j = atString.indexOf('"', i + 1 );  // search for closing "
+                if (j == -1) {  // unmatched ", insert one.
+                    atCommand.append(atString.substring(i, atString.length()));
+                    atCommand.append('"');
+                    break;
+                }
+                atCommand.append(atString.substring(i, j + 1));
+                i = j;
+            } else if (c != ' ') {
+                atCommand.append(Character.toUpperCase(c));
+            }
+        }
+        result = atCommand.toString();
+        return result;
+    }
+
+    private int getAtCommandType(String atCommand)
+    {
+        int commandType = mPhonebook.TYPE_UNKNOWN;
+        String atString = null;
+        atCommand = atCommand.trim();
+        if (atCommand.length() > 5)
+        {
+            atString = atCommand.substring(5);
+            if (atString.startsWith("?"))     // Read
+                commandType = mPhonebook.TYPE_READ;
+            else if (atString.startsWith("=?"))   // Test
+                commandType = mPhonebook.TYPE_TEST;
+            else if (atString.startsWith("="))   // Set
+                commandType = mPhonebook.TYPE_SET;
+            else
+                commandType = mPhonebook.TYPE_UNKNOWN;
+        }
+        return commandType;
+    }
+
+
     private void processAnswerCall() {
         if (mPhoneProxy != null) {
             try {
@@ -1183,21 +1228,21 @@
             dialNumber = mPhonebook.getLastDialledNumber();
             if (dialNumber == null) {
                 if (DBG) log("processDialCall, last dial number null");
-                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
+                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                 return;
             }
         } else if (number.charAt(0) == '>') {
             // Yuck - memory dialling requested.
             // Just dial last number for now
             if (number.startsWith(">9999")) {   // for PTS test
-                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
+                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                 return;
             }
             if (DBG) log("processDialCall, memory dial do last dial for now");
             dialNumber = mPhonebook.getLastDialledNumber();
             if (dialNumber == null) {
                 if (DBG) log("processDialCall, last dial number null");
-                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
+                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                 return;
             }
         } else {
@@ -1250,7 +1295,7 @@
         mPhoneState.setNumHeldCall(callState.mNumHeld);
         mPhoneState.setCallState(callState.mCallState);
         if (mDialingOut && callState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING) {
-                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK);
+                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
                 removeMessages(DIALING_OUT_TIMEOUT);
                 mDialingOut = false;
         }
@@ -1277,17 +1322,17 @@
         if (mPhoneProxy != null) {
             try {
                 if (mPhoneProxy.processChld(chld)) {
-                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK);
+                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
                 } else {
-                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
+                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                 }
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
+                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
             }
         } else {
             Log.e(TAG, "Handsfree phone proxy null for At+Chld");
-            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
+            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
         }
     }
 
@@ -1298,11 +1343,11 @@
                 if (number != null) {
                     atResponseStringNative("+CNUM: ,\"" + number + "\"," +
                                            PhoneNumberUtils.toaFromString(number) + ",,4");
-                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK);
+                    atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
                 }
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
+                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
             }
         } else {
             Log.e(TAG, "Handsfree phone proxy null for At+CNUM");
@@ -1350,9 +1395,52 @@
         }
     }
 
+    private void processAtCscs(String atString, int type) {
+        log("processAtCscs - atString = "+ atString);
+        if(mPhonebook != null) {
+            mPhonebook.handleCscsCommand(atString, type);
+        }
+        else {
+            Log.e(TAG, "Phonebook handle null for At+CSCS");
+            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+        }
+    }
+
+    private void processAtCpbs(String atString, int type) {
+        log("processAtCpbs - atString = "+ atString);
+        if(mPhonebook != null) {
+            mPhonebook.handleCpbsCommand(atString, type);
+        }
+        else {
+            Log.e(TAG, "Phonebook handle null for At+CPBS");
+            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+        }
+    }
+
+    private void processAtCpbr(String atString, int type, BluetoothDevice mCurrentDevice) {
+        log("processAtCpbr - atString = "+ atString);
+        if(mPhonebook != null) {
+            mPhonebook.handleCpbrCommand(atString, type, mCurrentDevice);
+        }
+        else {
+            Log.e(TAG, "Phonebook handle null for At+CPBR");
+            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+        }
+    }
+
     private void processUnknownAt(String atString) {
         // TODO (BT)
-        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR);
+        log("processUnknownAt - atString = "+ atString);
+        String atCommand = parseUnknownAt(atString);
+        int commandType = getAtCommandType(atCommand);
+        if (atCommand.startsWith("+CSCS"))
+            processAtCscs(atCommand.substring(5), commandType);
+        else if (atCommand.startsWith("+CPBS"))
+            processAtCpbs(atCommand.substring(5), commandType);
+        else if (atCommand.startsWith("+CPBR"))
+            processAtCpbr(atCommand.substring(5), commandType, mCurrentDevice);
+        else
+            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
     }
 
     private void processKeyPressed() {
@@ -1550,6 +1638,43 @@
         }
     }
 
+    public void handleAccessPermissionResult(Intent intent) {
+        log("handleAccessPermissionResult");
+        if(mPhonebook != null) {
+            if (!mPhonebook.getCheckingAccessPermission()) {
+                return;
+            }
+            int atCommandResult = 0;
+            int atCommandErrorCode = 0;
+            //HeadsetBase headset = mHandsfree.getHeadset();
+            // ASSERT: (headset != null) && headSet.isConnected()
+            // REASON: mCheckingAccessPermission is true, otherwise resetAtState
+            // has set mCheckingAccessPermission to false
+            if (intent.getAction().equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
+                if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
+                    BluetoothDevice.CONNECTION_ACCESS_NO) ==
+                    BluetoothDevice.CONNECTION_ACCESS_YES) {
+                    if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
+                        mCurrentDevice.setTrust(true);
+                    }
+                    atCommandResult = mPhonebook.processCpbrCommand();
+                }
+            }
+            mPhonebook.setCpbrIndex(-1);
+            mPhonebook.setCheckingAccessPermission(false);
+
+            if (atCommandResult >= 0) {
+                atResponseCodeNative(atCommandResult, atCommandErrorCode);
+            }
+            else
+                log("handleAccessPermissionResult - RESULT_NONE");
+        }
+        else {
+            Log.e(TAG, "Phonebook handle null");
+            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+        }
+    }
+
     private static final String SCHEME_TEL = "tel";
 
     // Event types for STACK_EVENT message
@@ -1583,6 +1708,9 @@
         }
     }
 
+    /*package*/native boolean atResponseCodeNative(int responseCode, int errorCode);
+    /*package*/ native boolean atResponseStringNative(String responseString);
+
     private native static void classInitNative();
     private native void initializeNative();
     private native void cleanupNative();
@@ -1598,11 +1726,11 @@
                                               int batteryCharge);
     private native boolean notifyDeviceStatusNative(int networkState, int serviceType, int signal,
                                                     int batteryCharge);
-    private native boolean atResponseCodeNative(int responseCode);
+
     private native boolean clccResponseNative(int index, int dir, int status, int mode,
                                               boolean mpty, String number, int type);
     private native boolean copsResponseNative(String operatorName);
-    private native boolean atResponseStringNative(String responseString);
+
     private native boolean phoneStateChangeNative(int numActive, int numHeld, int callState,
                                                   String number, int type);
 }