Home | History | Annotate | Download | only in hfp
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.bluetooth.hfp;
     18 
     19 import com.android.bluetooth.R;
     20 
     21 import com.android.internal.telephony.GsmAlphabet;
     22 
     23 import android.bluetooth.BluetoothDevice;
     24 import android.content.ContentResolver;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.database.Cursor;
     28 import android.net.Uri;
     29 import android.provider.CallLog.Calls;
     30 import android.provider.ContactsContract.CommonDataKinds.Phone;
     31 import android.provider.ContactsContract.PhoneLookup;
     32 import android.telephony.PhoneNumberUtils;
     33 import android.util.Log;
     34 
     35 import java.util.HashMap;
     36 
     37 /**
     38  * Helper for managing phonebook presentation over AT commands
     39  * @hide
     40  */
     41 public class AtPhonebook {
     42     private static final String TAG = "BluetoothAtPhonebook";
     43     private static final boolean DBG = false;
     44 
     45     /** The projection to use when querying the call log database in response
     46      *  to AT+CPBR for the MC, RC, and DC phone books (missed, received, and
     47      *   dialed calls respectively)
     48      */
     49     private static final String[] CALLS_PROJECTION = new String[] {
     50         Calls._ID, Calls.NUMBER, Calls.NUMBER_PRESENTATION
     51     };
     52 
     53     /** The projection to use when querying the contacts database in response
     54      *   to AT+CPBR for the ME phonebook (saved phone numbers).
     55      */
     56     private static final String[] PHONES_PROJECTION = new String[] {
     57         Phone._ID, Phone.DISPLAY_NAME, Phone.NUMBER, Phone.TYPE
     58     };
     59 
     60     /** Android supports as many phonebook entries as the flash can hold, but
     61      *  BT periphals don't. Limit the number we'll report. */
     62     private static final int MAX_PHONEBOOK_SIZE = 16384;
     63 
     64     private static final String OUTGOING_CALL_WHERE = Calls.TYPE + "=" + Calls.OUTGOING_TYPE;
     65     private static final String INCOMING_CALL_WHERE = Calls.TYPE + "=" + Calls.INCOMING_TYPE;
     66     private static final String MISSED_CALL_WHERE = Calls.TYPE + "=" + Calls.MISSED_TYPE;
     67     private static final String VISIBLE_PHONEBOOK_WHERE = Phone.IN_VISIBLE_GROUP + "=1";
     68 
     69     private class PhonebookResult {
     70         public Cursor  cursor; // result set of last query
     71         public int     numberColumn;
     72         public int     numberPresentationColumn;
     73         public int     typeColumn;
     74         public int     nameColumn;
     75     };
     76 
     77     private Context mContext;
     78     private ContentResolver mContentResolver;
     79     private HeadsetStateMachine mStateMachine;
     80     private String mCurrentPhonebook;
     81     private String mCharacterSet = "UTF-8";
     82 
     83     private int mCpbrIndex1, mCpbrIndex2;
     84     private boolean mCheckingAccessPermission;
     85 
     86     // package and class name to which we send intent to check phone book access permission
     87     private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
     88     private static final String ACCESS_AUTHORITY_CLASS =
     89         "com.android.settings.bluetooth.BluetoothPermissionRequest";
     90     private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
     91 
     92     private final HashMap<String, PhonebookResult> mPhonebooks =
     93             new HashMap<String, PhonebookResult>(4);
     94 
     95     final int TYPE_UNKNOWN = -1;
     96     final int TYPE_READ = 0;
     97     final int TYPE_SET = 1;
     98     final int TYPE_TEST = 2;
     99 
    100     public AtPhonebook(Context context, HeadsetStateMachine headsetState) {
    101         mContext = context;
    102         mContentResolver = context.getContentResolver();
    103         mStateMachine = headsetState;
    104         mPhonebooks.put("DC", new PhonebookResult());  // dialled calls
    105         mPhonebooks.put("RC", new PhonebookResult());  // received calls
    106         mPhonebooks.put("MC", new PhonebookResult());  // missed calls
    107         mPhonebooks.put("ME", new PhonebookResult());  // mobile phonebook
    108 
    109         mCurrentPhonebook = "ME";  // default to mobile phonebook
    110 
    111         mCpbrIndex1 = mCpbrIndex2 = -1;
    112         mCheckingAccessPermission = false;
    113     }
    114 
    115     public void cleanup() {
    116         mPhonebooks.clear();
    117     }
    118 
    119     /** Returns the last dialled number, or null if no numbers have been called */
    120     public String getLastDialledNumber() {
    121         String[] projection = {Calls.NUMBER};
    122         Cursor cursor = mContentResolver.query(Calls.CONTENT_URI, projection,
    123                 Calls.TYPE + "=" + Calls.OUTGOING_TYPE, null, Calls.DEFAULT_SORT_ORDER +
    124                 " LIMIT 1");
    125         if (cursor == null) return null;
    126 
    127         if (cursor.getCount() < 1) {
    128             cursor.close();
    129             return null;
    130         }
    131         cursor.moveToNext();
    132         int column = cursor.getColumnIndexOrThrow(Calls.NUMBER);
    133         String number = cursor.getString(column);
    134         cursor.close();
    135         return number;
    136     }
    137 
    138     public boolean getCheckingAccessPermission() {
    139         return mCheckingAccessPermission;
    140     }
    141 
    142     public void setCheckingAccessPermission(boolean checkAccessPermission) {
    143         mCheckingAccessPermission = checkAccessPermission;
    144     }
    145 
    146     public void setCpbrIndex(int cpbrIndex) {
    147         mCpbrIndex1 = mCpbrIndex2 = cpbrIndex;
    148     }
    149 
    150     public void handleCscsCommand(String atString, int type)
    151     {
    152         log("handleCscsCommand - atString = " +atString);
    153         // Select Character Set
    154         int atCommandResult = HeadsetHalConstants.AT_RESPONSE_ERROR;
    155         int atCommandErrorCode = -1;
    156         String atCommandResponse = null;
    157         switch (type) {
    158             case TYPE_READ: // Read
    159                 log("handleCscsCommand - Read Command");
    160                 atCommandResponse = "+CSCS: \"" + mCharacterSet + "\"";
    161                 atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
    162                 break;
    163             case TYPE_TEST: // Test
    164                 log("handleCscsCommand - Test Command");
    165                 atCommandResponse = ( "+CSCS: (\"UTF-8\",\"IRA\",\"GSM\")");
    166                 atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
    167                 break;
    168             case TYPE_SET: // Set
    169                 log("handleCscsCommand - Set Command");
    170                 String[] args = atString.split("=");
    171                 if (args.length < 2 || !(args[1] instanceof String)) {
    172                     mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
    173                     break;
    174                 }
    175                 String characterSet = ((atString.split("="))[1]);
    176                 characterSet = characterSet.replace("\"", "");
    177                 if (characterSet.equals("GSM") || characterSet.equals("IRA") ||
    178                     characterSet.equals("UTF-8") || characterSet.equals("UTF8")) {
    179                     mCharacterSet = characterSet;
    180                     atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
    181                 } else {
    182                     atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_SUPPORTED;
    183                 }
    184                 break;
    185             case TYPE_UNKNOWN:
    186             default:
    187                 log("handleCscsCommand - Invalid chars");
    188                 atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
    189         }
    190         if (atCommandResponse != null)
    191             mStateMachine.atResponseStringNative(atCommandResponse);
    192         mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
    193     }
    194 
    195     public void handleCpbsCommand(String atString, int type) {
    196         // Select PhoneBook memory Storage
    197         log("handleCpbsCommand - atString = " +atString);
    198         int atCommandResult = HeadsetHalConstants.AT_RESPONSE_ERROR;
    199         int atCommandErrorCode = -1;
    200         String atCommandResponse = null;
    201         switch (type) {
    202             case TYPE_READ: // Read
    203                 log("handleCpbsCommand - read command");
    204                 // Return current size and max size
    205                 if ("SM".equals(mCurrentPhonebook)) {
    206                     atCommandResponse = "+CPBS: \"SM\",0," + getMaxPhoneBookSize(0);
    207                     atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
    208                     if (atCommandResponse != null)
    209                         mStateMachine.atResponseStringNative(atCommandResponse);
    210                     mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
    211                     break;
    212                 }
    213                 PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true);
    214                 if (pbr == null) {
    215                     atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_SUPPORTED;
    216                     mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
    217                     break;
    218                 }
    219                 int size = pbr.cursor.getCount();
    220                 atCommandResponse = "+CPBS: \"" + mCurrentPhonebook + "\"," + size + "," + getMaxPhoneBookSize(size);
    221                 pbr.cursor.close();
    222                 pbr.cursor = null;
    223                 atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
    224                 break;
    225             case TYPE_TEST: // Test
    226                 log("handleCpbsCommand - test command");
    227                 atCommandResponse = ("+CPBS: (\"ME\",\"SM\",\"DC\",\"RC\",\"MC\")");
    228                 atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
    229                 break;
    230             case TYPE_SET: // Set
    231                 log("handleCpbsCommand - set command");
    232                 String[] args = atString.split("=");
    233                 // Select phonebook memory
    234                 if (args.length < 2 || !(args[1] instanceof String)) {
    235                     mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
    236                     break;
    237                 }
    238                 String pb = ((String)args[1]).trim();
    239                 while (pb.endsWith("\"")) pb = pb.substring(0, pb.length() - 1);
    240                 while (pb.startsWith("\"")) pb = pb.substring(1, pb.length());
    241                 if (getPhonebookResult(pb, false) == null && !"SM".equals(pb)) {
    242                    if (DBG) log("Dont know phonebook: '" + pb + "'");
    243                    atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
    244                    mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
    245                    break;
    246                 }
    247                 mCurrentPhonebook = pb;
    248                 atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
    249                 break;
    250             case TYPE_UNKNOWN:
    251             default:
    252                 log("handleCpbsCommand - invalid chars");
    253                 atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
    254         }
    255         if (atCommandResponse != null)
    256             mStateMachine.atResponseStringNative(atCommandResponse);
    257         mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
    258     }
    259 
    260     public void handleCpbrCommand(String atString, int type, BluetoothDevice remoteDevice) {
    261         log("handleCpbrCommand - atString = " +atString);
    262         int atCommandResult = HeadsetHalConstants.AT_RESPONSE_ERROR;
    263         int atCommandErrorCode = -1;
    264         String atCommandResponse = null;
    265         switch (type) {
    266             case TYPE_TEST: // Test
    267                 /* Ideally we should return the maximum range of valid index's
    268                  * for the selected phone book, but this causes problems for the
    269                  * Parrot CK3300. So instead send just the range of currently
    270                  * valid index's.
    271                  */
    272                 log("handleCpbrCommand - test command");
    273                 int size;
    274                 if ("SM".equals(mCurrentPhonebook)) {
    275                     size = 0;
    276                 } else {
    277                     PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true); //false);
    278                     if (pbr == null) {
    279                         atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
    280                         mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
    281                         break;
    282                     }
    283                     size = pbr.cursor.getCount();
    284                     log("handleCpbrCommand - size = "+size);
    285                     pbr.cursor.close();
    286                     pbr.cursor = null;
    287                 }
    288                 if (size == 0) {
    289                     /* Sending "+CPBR: (1-0)" can confused some carkits, send "1-1" * instead */
    290                     size = 1;
    291                 }
    292                 atCommandResponse = "+CPBR: (1-" + size + "),30,30";
    293                 atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
    294                 if (atCommandResponse != null)
    295                     mStateMachine.atResponseStringNative(atCommandResponse);
    296                 mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
    297                 break;
    298             // Read PhoneBook Entries
    299             case TYPE_READ:
    300             case TYPE_SET: // Set & read
    301                 // Phone Book Read Request
    302                 // AT+CPBR=<index1>[,<index2>]
    303                 log("handleCpbrCommand - set/read command");
    304                 if (mCpbrIndex1 != -1) {
    305                    /* handling a CPBR at the moment, reject this CPBR command */
    306                    atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
    307                    mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
    308                    break;
    309                 }
    310                 // Parse indexes
    311                 int index1;
    312                 int index2;
    313                 if ((atString.split("=")).length < 2) {
    314                     mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
    315                     break;
    316                 }
    317                 String atCommand = (atString.split("="))[1];
    318                 String[] indices = atCommand.split(",");
    319                 for(int i = 0; i < indices.length; i++)
    320                     //replace AT command separator ';' from the index if any
    321                     indices[i] = indices[i].replace(';', ' ').trim();
    322                 try {
    323                     index1 = Integer.parseInt(indices[0]);
    324                     if (indices.length == 1)
    325                         index2 = index1;
    326                     else
    327                         index2 = Integer.parseInt(indices[1]);
    328                 }
    329                 catch (Exception e) {
    330                     log("handleCpbrCommand - exception - invalid chars: " + e.toString());
    331                     atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
    332                     mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
    333                     break;
    334                 }
    335                 mCpbrIndex1 = index1;
    336                 mCpbrIndex2 = index2;
    337                 mCheckingAccessPermission = true;
    338 
    339                 if (checkAccessPermission(remoteDevice)) {
    340                     mCheckingAccessPermission = false;
    341                     atCommandResult = processCpbrCommand();
    342                     mCpbrIndex1 = mCpbrIndex2 = -1;
    343                     mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
    344                     break;
    345                 }
    346                 // no reponse here, will continue the process in handleAccessPermissionResult
    347                 break;
    348                 case TYPE_UNKNOWN:
    349                 default:
    350                     log("handleCpbrCommand - invalid chars");
    351                     atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
    352                     mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode);
    353         }
    354     }
    355 
    356     /** Get the most recent result for the given phone book,
    357      *  with the cursor ready to go.
    358      *  If force then re-query that phonebook
    359      *  Returns null if the cursor is not ready
    360      */
    361     private synchronized PhonebookResult getPhonebookResult(String pb, boolean force) {
    362         if (pb == null) {
    363             return null;
    364         }
    365         PhonebookResult pbr = mPhonebooks.get(pb);
    366         if (pbr == null) {
    367             pbr = new PhonebookResult();
    368         }
    369         if (force || pbr.cursor == null) {
    370             if (!queryPhonebook(pb, pbr)) {
    371                 return null;
    372             }
    373         }
    374 
    375         return pbr;
    376     }
    377 
    378     private synchronized boolean queryPhonebook(String pb, PhonebookResult pbr) {
    379         String where;
    380         boolean ancillaryPhonebook = true;
    381 
    382         if (pb.equals("ME")) {
    383             ancillaryPhonebook = false;
    384             where = VISIBLE_PHONEBOOK_WHERE;
    385         } else if (pb.equals("DC")) {
    386             where = OUTGOING_CALL_WHERE;
    387         } else if (pb.equals("RC")) {
    388             where = INCOMING_CALL_WHERE;
    389         } else if (pb.equals("MC")) {
    390             where = MISSED_CALL_WHERE;
    391         } else {
    392             return false;
    393         }
    394 
    395         if (pbr.cursor != null) {
    396             pbr.cursor.close();
    397             pbr.cursor = null;
    398         }
    399 
    400         if (ancillaryPhonebook) {
    401             pbr.cursor = mContentResolver.query(
    402                     Calls.CONTENT_URI, CALLS_PROJECTION, where, null,
    403                     Calls.DEFAULT_SORT_ORDER + " LIMIT " + MAX_PHONEBOOK_SIZE);
    404             if (pbr.cursor == null) return false;
    405 
    406             pbr.numberColumn = pbr.cursor.getColumnIndexOrThrow(Calls.NUMBER);
    407             pbr.numberPresentationColumn =
    408                     pbr.cursor.getColumnIndexOrThrow(Calls.NUMBER_PRESENTATION);
    409             pbr.typeColumn = -1;
    410             pbr.nameColumn = -1;
    411         } else {
    412             pbr.cursor = mContentResolver.query(Phone.CONTENT_URI, PHONES_PROJECTION,
    413                     where, null, Phone.NUMBER + " LIMIT " + MAX_PHONEBOOK_SIZE);
    414             if (pbr.cursor == null) return false;
    415 
    416             pbr.numberColumn = pbr.cursor.getColumnIndex(Phone.NUMBER);
    417             pbr.numberPresentationColumn = -1;
    418             pbr.typeColumn = pbr.cursor.getColumnIndex(Phone.TYPE);
    419             pbr.nameColumn = pbr.cursor.getColumnIndex(Phone.DISPLAY_NAME);
    420         }
    421         Log.i(TAG, "Refreshed phonebook " + pb + " with " + pbr.cursor.getCount() + " results");
    422         return true;
    423     }
    424 
    425     synchronized void resetAtState() {
    426         mCharacterSet = "UTF-8";
    427         mCpbrIndex1 = mCpbrIndex2 = -1;
    428         mCheckingAccessPermission = false;
    429     }
    430 
    431     private synchronized int getMaxPhoneBookSize(int currSize) {
    432         // some car kits ignore the current size and request max phone book
    433         // size entries. Thus, it takes a long time to transfer all the
    434         // entries. Use a heuristic to calculate the max phone book size
    435         // considering future expansion.
    436         // maxSize = currSize + currSize / 2 rounded up to nearest power of 2
    437         // If currSize < 100, use 100 as the currSize
    438 
    439         int maxSize = (currSize < 100) ? 100 : currSize;
    440         maxSize += maxSize / 2;
    441         return roundUpToPowerOfTwo(maxSize);
    442     }
    443 
    444     private int roundUpToPowerOfTwo(int x) {
    445         x |= x >> 1;
    446         x |= x >> 2;
    447         x |= x >> 4;
    448         x |= x >> 8;
    449         x |= x >> 16;
    450         return x + 1;
    451     }
    452 
    453     // process CPBR command after permission check
    454     /*package*/ int processCpbrCommand()
    455     {
    456         log("processCpbrCommand");
    457         int atCommandResult = HeadsetHalConstants.AT_RESPONSE_ERROR;
    458         int atCommandErrorCode = -1;
    459         String atCommandResponse = null;
    460         StringBuilder response = new StringBuilder();
    461         String record;
    462 
    463         // Shortcut SM phonebook
    464         if ("SM".equals(mCurrentPhonebook)) {
    465             atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
    466             return atCommandResult;
    467         }
    468 
    469         // Check phonebook
    470         PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true); //false);
    471         if (pbr == null) {
    472             atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
    473             return atCommandResult;
    474         }
    475 
    476         // More sanity checks
    477         // Send OK instead of ERROR if these checks fail.
    478         // When we send error, certain kits like BMW disconnect the
    479         // Handsfree connection.
    480         if (pbr.cursor.getCount() == 0 || mCpbrIndex1 <= 0 || mCpbrIndex2 < mCpbrIndex1  ||
    481             mCpbrIndex2 > pbr.cursor.getCount() || mCpbrIndex1 > pbr.cursor.getCount()) {
    482             atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
    483             return atCommandResult;
    484         }
    485 
    486         // Process
    487         atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
    488         int errorDetected = -1; // no error
    489         pbr.cursor.moveToPosition(mCpbrIndex1 - 1);
    490         log("mCpbrIndex1 = "+mCpbrIndex1+ " and mCpbrIndex2 = "+mCpbrIndex2);
    491         for (int index = mCpbrIndex1; index <= mCpbrIndex2; index++) {
    492             String number = pbr.cursor.getString(pbr.numberColumn);
    493             String name = null;
    494             int type = -1;
    495             if (pbr.nameColumn == -1 && number != null && number.length() > 0) {
    496                 // try caller id lookup
    497                 // TODO: This code is horribly inefficient. I saw it
    498                 // take 7 seconds to process 100 missed calls.
    499                 Cursor c = mContentResolver.
    500                     query(Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, number),
    501                           new String[] {PhoneLookup.DISPLAY_NAME, PhoneLookup.TYPE},
    502                           null, null, null);
    503                 if (c != null) {
    504                     if (c.moveToFirst()) {
    505                         name = c.getString(0);
    506                         type = c.getInt(1);
    507                     }
    508                     c.close();
    509                 }
    510                 if (DBG && name == null) log("Caller ID lookup failed for " + number);
    511 
    512             } else if (pbr.nameColumn != -1) {
    513                 name = pbr.cursor.getString(pbr.nameColumn);
    514             } else {
    515                 log("processCpbrCommand: empty name and number");
    516             }
    517             if (name == null) name = "";
    518             name = name.trim();
    519             if (name.length() > 28) name = name.substring(0, 28);
    520 
    521             if (pbr.typeColumn != -1) {
    522                 type = pbr.cursor.getInt(pbr.typeColumn);
    523                 name = name + "/" + getPhoneType(type);
    524             }
    525 
    526             if (number == null) number = "";
    527             int regionType = PhoneNumberUtils.toaFromString(number);
    528 
    529             number = number.trim();
    530             number = PhoneNumberUtils.stripSeparators(number);
    531             if (number.length() > 30) number = number.substring(0, 30);
    532             int numberPresentation = Calls.PRESENTATION_ALLOWED;
    533             if (pbr.numberPresentationColumn != -1) {
    534                 numberPresentation = pbr.cursor.getInt(pbr.numberPresentationColumn);
    535             }
    536             if (numberPresentation != Calls.PRESENTATION_ALLOWED) {
    537                 number = "";
    538                 // TODO: there are 3 types of numbers should have resource
    539                 // strings for: unknown, private, and payphone
    540                 name = mContext.getString(R.string.unknownNumber);
    541             }
    542 
    543             // TODO(): Handle IRA commands. It's basically
    544             // a 7 bit ASCII character set.
    545             if (!name.equals("") && mCharacterSet.equals("GSM")) {
    546                 byte[] nameByte = GsmAlphabet.stringToGsm8BitPacked(name);
    547                 if (nameByte == null) {
    548                     name = mContext.getString(R.string.unknownNumber);
    549                 } else {
    550                     name = new String(nameByte);
    551                 }
    552             }
    553 
    554             record = "+CPBR: " + index + ",\"" + number + "\"," + regionType + ",\"" + name + "\"";
    555             record = record + "\r\n\r\n";
    556             atCommandResponse = record;
    557             log("processCpbrCommand - atCommandResponse = "+atCommandResponse);
    558             mStateMachine.atResponseStringNative(atCommandResponse);
    559             if (!pbr.cursor.moveToNext()) {
    560                 break;
    561             }
    562         }
    563         if(pbr != null && pbr.cursor != null) {
    564             pbr.cursor.close();
    565             pbr.cursor = null;
    566         }
    567         return atCommandResult;
    568     }
    569 
    570     // Check if the remote device has premission to read our phone book
    571     // Return true if it has the permission
    572     // false if not known and we have sent our Intent to check
    573     private boolean checkAccessPermission(BluetoothDevice remoteDevice) {
    574         log("checkAccessPermission");
    575         boolean trust = remoteDevice.getTrustState();
    576 
    577         if (trust) {
    578             return true;
    579         }
    580 
    581         log("checkAccessPermission - ACTION_CONNECTION_ACCESS_REQUEST");
    582         Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
    583         intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
    584         intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
    585         BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
    586         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, remoteDevice);
    587         // Leave EXTRA_PACKAGE_NAME and EXTRA_CLASS_NAME field empty
    588         // BluetoothHandsfree's broadcast receiver is anonymous, cannot be targeted
    589         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    590         return false;
    591     }
    592 
    593     private static String getPhoneType(int type) {
    594         switch (type) {
    595             case Phone.TYPE_HOME:
    596                 return "H";
    597             case Phone.TYPE_MOBILE:
    598                 return "M";
    599             case Phone.TYPE_WORK:
    600                 return "W";
    601             case Phone.TYPE_FAX_HOME:
    602             case Phone.TYPE_FAX_WORK:
    603                 return "F";
    604             case Phone.TYPE_OTHER:
    605             case Phone.TYPE_CUSTOM:
    606             default:
    607                 return "O";
    608         }
    609     }
    610 
    611     private static void log(String msg) {
    612         Log.d(TAG, msg);
    613     }
    614 }
    615