BluetoothPbapVcardManager.java revision d896162ef022a5607b13bae5177637ff3f18b737
1/* 2 * Copyright (c) 2008-2009, Motorola, Inc. 3 * 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * - Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 12 * - Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the documentation 14 * and/or other materials provided with the distribution. 15 * 16 * - Neither the name of the Motorola, Inc. nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33package com.android.bluetooth.pbap; 34 35import com.android.bluetooth.R; 36 37import android.content.ContentResolver; 38import android.content.Context; 39import android.database.Cursor; 40import android.net.Uri; 41import android.provider.Contacts; 42import android.provider.CallLog.Calls; 43import android.provider.Contacts.Organizations; 44import android.provider.CallLog; 45import android.provider.Contacts.People; 46import android.provider.Contacts.Phones; 47import android.syncml.pim.vcard.ContactStruct; 48import android.syncml.pim.vcard.VCardComposer; 49import android.syncml.pim.vcard.VCardException; 50import android.syncml.pim.vcard.VCardParser; 51import android.util.Log; 52 53import java.util.ArrayList; 54 55public class BluetoothPbapVcardManager { 56 static private final String TAG = "BluetoothPbapVcardManager"; 57 58 private ContentResolver mResolver; 59 60 private Context mContext; 61 62 private String mDefaultName = null; 63 64 private String mDefaultNumber = null; 65 66 /** The projection to use when querying the call log table */ 67 public static final String[] CALL_LOG_PROJECTION = new String[] { 68 Calls._ID, Calls.NUMBER, Calls.DATE, Calls.DURATION, Calls.TYPE, Calls.CACHED_NAME, 69 Calls.CACHED_NUMBER_TYPE, Calls.CACHED_NUMBER_LABEL 70 }; 71 72 public static final int ID_COLUMN_INDEX = 0; 73 74 public static final int NUMBER_COLUMN_INDEX = 1; 75 76 public static final int DATE_COLUMN_INDEX = 2; 77 78 public static final int DURATION_COLUMN_INDEX = 3; 79 80 public static final int CALL_TYPE_COLUMN_INDEX = 4; 81 82 public static final int CALLER_NAME_COLUMN_INDEX = 5; 83 84 public static final int CALLER_NUMBERTYPE_COLUMN_INDEX = 6; 85 86 public static final int CALLER_NUMBERLABEL_COLUMN_INDEX = 7; 87 88 /** The projection to use when querying the phone book table */ 89 public static final String[] CONTACT_PROJECTION = new String[] { 90 People._ID, // 0 91 People.NAME, // 1 92 People.NOTES, // 2 93 People.PRIMARY_PHONE_ID, // 3 94 People.PRESENCE_STATUS, // 4 95 People.STARRED, // 5 96 People.CUSTOM_RINGTONE, // 6 97 People.SEND_TO_VOICEMAIL, // 7 98 People.PHONETIC_NAME, // 8 99 }; 100 101 public static final int CONTACT_ID_COLUMN = 0; 102 103 public static final int CONTACT_NAME_COLUMN = 1; 104 105 public static final int CONTACT_NOTES_COLUMN = 2; 106 107 public static final int CONTACT_PREFERRED_PHONE_COLUMN = 3; 108 109 public static final int CONTACT_SERVER_STATUS_COLUMN = 4; 110 111 public static final int CONTACT_STARRED_COLUMN = 5; 112 113 public static final int CONTACT_CUSTOM_RINGTONE_COLUMN = 6; 114 115 public static final int CONTACT_SEND_TO_VOICEMAIL_COLUMN = 7; 116 117 public static final int CONTACT_PHONETIC_NAME_COLUMN = 8; 118 119 public static final String[] PHONES_PROJECTION = new String[] { 120 People.Phones._ID, // 0 121 People.Phones.NUMBER, // 1 122 People.Phones.TYPE, // 2 123 People.Phones.LABEL, // 3 124 People.Phones.ISPRIMARY, // 4 125 }; 126 127 public static final int PHONES_ID_COLUMN = 0; 128 129 public static final int PHONES_NUMBER_COLUMN = 1; 130 131 public static final int PHONES_TYPE_COLUMN = 2; 132 133 public static final int PHONES_LABEL_COLUMN = 3; 134 135 public static final int PHONES_ISPRIMARY_COLUMN = 4; 136 137 public static final String[] METHODS_PROJECTION = new String[] { 138 People.ContactMethods._ID, // 0 139 People.ContactMethods.KIND, // 1 140 People.ContactMethods.DATA, // 2 141 People.ContactMethods.TYPE, // 3 142 People.ContactMethods.LABEL, // 4 143 People.ContactMethods.ISPRIMARY, // 5 144 People.ContactMethods.AUX_DATA, // 6 145 }; 146 147 public static final int METHODS_ID_COLUMN = 0; 148 149 public static final int METHODS_KIND_COLUMN = 1; 150 151 public static final int METHODS_DATA_COLUMN = 2; 152 153 public static final int METHODS_TYPE_COLUMN = 3; 154 155 public static final int METHODS_LABEL_COLUMN = 4; 156 157 public static final int METHODS_ISPRIMARY_COLUMN = 5; 158 159 public static final int METHODS_AUX_DATA_COLUMN = 6; 160 161 public static final int METHODS_STATUS_COLUMN = 7; 162 163 public static final String[] ORGANIZATIONS_PROJECTION = new String[] { 164 Organizations._ID, // 0 165 Organizations.TYPE, // 1 166 Organizations.LABEL, // 2 167 Organizations.COMPANY, // 3 168 Organizations.TITLE, // 4 169 Organizations.ISPRIMARY, // 5 170 }; 171 172 public static final int ORGANIZATIONS_ID_COLUMN = 0; 173 174 public static final int ORGANIZATIONS_TYPE_COLUMN = 1; 175 176 public static final int ORGANIZATIONS_LABEL_COLUMN = 2; 177 178 public static final int ORGANIZATIONS_COMPANY_COLUMN = 3; 179 180 public static final int ORGANIZATIONS_TITLE_COLUMN = 4; 181 182 public static final int ORGANIZATIONS_ISPRIMARY_COLUMN = 5; 183 184 public BluetoothPbapVcardManager(final Context context) { 185 mContext = context; 186 mResolver = mContext.getContentResolver(); 187 mDefaultName = context.getString(android.R.string.unknownName); 188 mDefaultNumber = context.getString(R.string.defaultnumber); 189 } 190 191 private String getThisPhoneName() { 192 String name; 193 name = BluetoothPbapService.getLocalPhoneName(); 194 if (name == null || name.trim().length() == 0) { 195 name = mDefaultName; 196 } 197 return name; 198 } 199 200 private String getThisPhoneNumber() { 201 String number; 202 number = BluetoothPbapService.getLocalPhoneNum(); 203 if (number == null || number.trim().length() == 0) { 204 number = mDefaultNumber; 205 } 206 return number; 207 } 208 209 public final int getPhonebookSize() { 210 Uri myUri = Contacts.People.CONTENT_URI; 211 Cursor contactC = mResolver.query(myUri, null, null, null, null); 212 213 int mPhonebookSize = 0; 214 if (contactC != null) { 215 mPhonebookSize = contactC.getCount() + 1; // always has the 0.vcf 216 } 217 return mPhonebookSize; 218 } 219 220 public final String getPhonebook(final int pos, final boolean vCard21) { 221 try { 222 if (pos >= 0 && pos < getPhonebookSize()) { 223 long id = 0; 224 Uri myUri = Contacts.People.CONTENT_URI; 225 // Individual Contact may be deleted, which lead to incontinuous 226 // ID for Uri; So we need to calculate the actual Uri. 227 if (pos > 0) { 228 Cursor personCursor = mResolver.query(myUri, CONTACT_PROJECTION, null, null, 229 null); 230 if (personCursor != null) { 231 personCursor.moveToPosition(pos - 1); 232 id = personCursor.getLong(CONTACT_ID_COLUMN); 233 personCursor.close(); 234 } 235 String sPos = String.valueOf(id); 236 myUri = Uri.withAppendedPath(myUri, sPos); 237 } 238 return loadPhonebook(myUri, pos, vCard21); 239 } else { 240 Log.w(TAG, "pos invalid"); 241 } 242 } catch (Exception e) { 243 Log.e(TAG, "catch exception" + e.toString()); 244 } 245 return null; 246 } 247 248 public final int getCallHistorySize(final int type) { 249 int size = 0; 250 Uri myUri = CallLog.Calls.CONTENT_URI; 251 String selection = null; 252 switch (type) { 253 case BluetoothPbapObexServer.ContentType.INCOMING_CALL_HISTORY: 254 selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE; 255 break; 256 case BluetoothPbapObexServer.ContentType.OUTGOING_CALL_HISTORY: 257 selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE; 258 break; 259 case BluetoothPbapObexServer.ContentType.MISSED_CALL_HISTORY: 260 selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE; 261 break; 262 default: 263 break; 264 } 265 Cursor callCursor = mResolver.query(myUri, null, selection, null, 266 CallLog.Calls.DEFAULT_SORT_ORDER); 267 if (callCursor != null) { 268 size = callCursor.getCount(); 269 callCursor.close(); 270 } 271 return size; 272 } 273 274 public final String getCallHistory(final int pos, final int type, final boolean vCard21) { 275 int size = 0; 276 String selection = null; 277 switch (type) { 278 case BluetoothPbapObexServer.ContentType.INCOMING_CALL_HISTORY: 279 selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE; 280 break; 281 case BluetoothPbapObexServer.ContentType.OUTGOING_CALL_HISTORY: 282 selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE; 283 break; 284 case BluetoothPbapObexServer.ContentType.MISSED_CALL_HISTORY: 285 selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE; 286 break; 287 default: 288 break; 289 } 290 size = getCallHistorySize(type); 291 try { 292 if (pos >= 0 && pos < size) { 293 Uri myUri = CallLog.Calls.CONTENT_URI; 294 return loadCallHistory(myUri, pos, selection, vCard21); 295 } else { 296 Log.w(TAG, "pos invalid"); 297 } 298 } catch (Exception e) { 299 Log.e(TAG, "catch exception e" + e.toString()); 300 } 301 return null; 302 } 303 304 public final ArrayList<String> loadNameList() { 305 ArrayList<String> nameList = new ArrayList<String>(); 306 int size = getPhonebookSize(); 307 Uri myUri = Contacts.People.CONTENT_URI; 308 Cursor contactC = mResolver.query(myUri, null, null, null, null); 309 for (int pos = 0; pos < size; pos++) { 310 if (pos == 0) { 311 nameList.add(getThisPhoneName()); 312 } else { 313 if (contactC != null) { 314 contactC.moveToPosition(pos - 1); 315 String name = contactC.getString(contactC 316 .getColumnIndexOrThrow(Contacts.People.NAME)); 317 if (name == null || name.trim().length() == 0) { 318 name = mDefaultName; 319 } 320 nameList.add(name); 321 } 322 } 323 } 324 if (contactC != null) { 325 contactC.close(); 326 } 327 return nameList; 328 } 329 330 public final ArrayList<String> loadNumberList() { 331 ArrayList<String> numberList = new ArrayList<String>(); 332 if (numberList.size() > 0) { 333 numberList.clear(); 334 } 335 int size = getPhonebookSize(); 336 Uri myUri = Contacts.People.CONTENT_URI; 337 Cursor contactC = mResolver.query(myUri, null, null, null, null); 338 for (int pos = 0; pos < size; pos++) { 339 if (pos == 0) { 340 numberList.add(getThisPhoneNumber()); 341 } else { 342 contactC.moveToPosition(pos - 1); 343 String number = contactC.getString(contactC 344 .getColumnIndexOrThrow(Contacts.People.PRIMARY_PHONE_ID)); 345 if (number == null || number.trim().length() == 0) { 346 number = mDefaultNumber; 347 } 348 numberList.add(number); 349 } 350 } 351 return numberList; 352 } 353 354 public final ArrayList<String> loadCallHistoryList(final int type) { 355 int size = 0; 356 String selection = null; 357 Uri myUri = CallLog.Calls.CONTENT_URI; 358 ArrayList<String> list = new ArrayList<String>(); 359 switch (type) { 360 case BluetoothPbapObexServer.ContentType.INCOMING_CALL_HISTORY: 361 selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE; 362 break; 363 case BluetoothPbapObexServer.ContentType.OUTGOING_CALL_HISTORY: 364 selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE; 365 break; 366 case BluetoothPbapObexServer.ContentType.MISSED_CALL_HISTORY: 367 selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE; 368 break; 369 default: 370 break; 371 } 372 size = getCallHistorySize(type); 373 Cursor callCursor = mResolver.query(myUri, CALL_LOG_PROJECTION, selection, null, 374 CallLog.Calls.DEFAULT_SORT_ORDER); 375 if (callCursor != null) { 376 for (int pos = 0; pos < size; pos++) { 377 callCursor.moveToPosition(pos); 378 String name = callCursor.getString(CALLER_NAME_COLUMN_INDEX); 379 if (name == null || name.trim().length() == 0) { 380 // name not found,use number instead 381 name = callCursor.getString(NUMBER_COLUMN_INDEX); 382 } 383 list.add(name); 384 } 385 callCursor.close(); 386 } 387 return list; 388 } 389 390 private final String loadCallHistory(final Uri mUri, final int pos, final String selection, 391 final boolean vCard21) { 392 ContactStruct contactStruct = new ContactStruct(); 393 Cursor callCursor = mResolver.query(mUri, CALL_LOG_PROJECTION, selection, null, 394 CallLog.Calls.DEFAULT_SORT_ORDER); 395 if (callCursor != null) { 396 if (callCursor.moveToPosition(pos)) { 397 contactStruct.name = callCursor.getString(CALLER_NAME_COLUMN_INDEX); 398 if (contactStruct.name == null || contactStruct.name.trim().length() == 0) { 399 contactStruct.name = callCursor.getString(NUMBER_COLUMN_INDEX); 400 } 401 String number = callCursor.getString(NUMBER_COLUMN_INDEX); 402 int type = callCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX); 403 String label = callCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX); 404 if (label == null || label.trim().length() == 0) { 405 label = Integer.toString(type); 406 } 407 contactStruct.addPhone(type, number, label, true); 408 } 409 callCursor.close(); 410 } 411 try { 412 VCardComposer composer = new VCardComposer(); 413 if (vCard21) { 414 return composer.createVCard(contactStruct, VCardParser.VERSION_VCARD21_INT); 415 } else { 416 return composer.createVCard(contactStruct, VCardParser.VERSION_VCARD30_INT); 417 } 418 } catch (VCardException e) { 419 Log.e(TAG, "catch exception" + e.toString()); 420 return null; 421 } 422 } 423 424 private final String loadPhonebook(final Uri mUri, final int pos, final boolean vCard21) { 425 // Build up the phone entries 426 ContactStruct contactStruct = new ContactStruct(); 427 Cursor personCursor = mResolver.query(mUri, CONTACT_PROJECTION, null, null, null); 428 if (personCursor == null) { 429 return null; 430 } 431 if (pos == 0) { 432 contactStruct.name = getThisPhoneName(); 433 contactStruct.addPhone(Contacts.PhonesColumns.TYPE_MOBILE, getThisPhoneNumber(), "", 434 true); 435 } else if (personCursor.moveToNext()) { 436 contactStruct.name = personCursor.getString(CONTACT_NAME_COLUMN); 437 if (contactStruct.name == null || contactStruct.name.trim().length() == 0) { 438 contactStruct.name = mDefaultName; 439 } 440 contactStruct.notes.add(checkStrEnd(personCursor.getString(CONTACT_NOTES_COLUMN), 441 vCard21)); 442 final Uri phonesUri = Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY); 443 final Cursor phonesCursor = mResolver.query(phonesUri, PHONES_PROJECTION, null, null, 444 Phones.ISPRIMARY + " DESC"); 445 if (phonesCursor != null) { 446 for (phonesCursor.moveToFirst(); !phonesCursor.isAfterLast(); 447 phonesCursor.moveToNext()) { 448 int type = phonesCursor.getInt(PHONES_TYPE_COLUMN); 449 String number = phonesCursor.getString(PHONES_NUMBER_COLUMN); 450 String label = phonesCursor.getString(PHONES_LABEL_COLUMN); 451 contactStruct.addPhone(type, number, label, true); 452 } 453 phonesCursor.close(); 454 } 455 // Build the contact method entries 456 final Uri methodsUri = Uri.withAppendedPath(mUri, 457 People.ContactMethods.CONTENT_DIRECTORY); 458 Cursor methodsCursor = mResolver 459 .query(methodsUri, METHODS_PROJECTION, null, null, null); 460 if (methodsCursor != null) { 461 for (methodsCursor.moveToFirst(); !methodsCursor.isAfterLast(); 462 methodsCursor.moveToNext()) { 463 int kind = methodsCursor.getInt(METHODS_KIND_COLUMN); 464 String label = methodsCursor.getString(METHODS_LABEL_COLUMN); 465 String data = methodsCursor.getString(METHODS_DATA_COLUMN); 466 int type = methodsCursor.getInt(METHODS_TYPE_COLUMN); 467 boolean isPrimary = methodsCursor.getInt(METHODS_ISPRIMARY_COLUMN) == 1 ? true 468 : false; 469 // TODO 470 // Below code is totally depend on the implementation of 471 // package android.syncml.pim.vcard 472 // VcardComposer shall throw null pointer exception when 473 // label is not set 474 // function appendContactMethodStr is weak and shall be 475 // improved in the future 476 if (kind == Contacts.KIND_EMAIL) { 477 if (type == Contacts.ContactMethodsColumns.TYPE_OTHER) { 478 if (label == null || label.trim().length() == 0) { 479 label = Integer.toString(type); 480 } 481 } 482 } 483 if (kind == Contacts.KIND_POSTAL) { 484 data = checkStrEnd(data, vCard21); 485 } 486 contactStruct.addContactmethod(kind, type, data, label, isPrimary); 487 } 488 methodsCursor.close(); 489 } 490 491 // Build the organization entries 492 final Uri organizationsUri = Uri 493 .withAppendedPath(mUri, Organizations.CONTENT_DIRECTORY); 494 Cursor organizationsCursor = mResolver.query(organizationsUri, 495 ORGANIZATIONS_PROJECTION, "isprimary", null, null); 496 497 if (organizationsCursor != null) { 498 for (organizationsCursor.moveToFirst(); !organizationsCursor.isAfterLast(); 499 organizationsCursor.moveToNext()) { 500 int type = organizationsCursor.getInt(ORGANIZATIONS_TYPE_COLUMN); 501 String company = organizationsCursor.getString(ORGANIZATIONS_COMPANY_COLUMN); 502 String title = checkStrEnd(organizationsCursor 503 .getString(ORGANIZATIONS_TITLE_COLUMN), vCard21); 504 boolean isPrimary = organizationsCursor. 505 getInt(ORGANIZATIONS_ISPRIMARY_COLUMN) == 1 ? true 506 : false; 507 contactStruct.addOrganization(type, company, title, isPrimary); 508 } 509 organizationsCursor.close(); 510 } 511 } 512 personCursor.close(); 513 // Generate vCard data. 514 try { 515 VCardComposer composer = new VCardComposer(); 516 if (vCard21) { 517 return composer.createVCard(contactStruct, VCardParser.VERSION_VCARD21_INT); 518 } else { 519 return composer.createVCard(contactStruct, VCardParser.VERSION_VCARD30_INT); 520 } 521 } catch (VCardException e) { 522 Log.e(TAG, "catch exception in loadPhonebook" + e.toString()); 523 return null; 524 } 525 } 526 527 /** 528 * This function is to check the string end to avoid function 529 * foldingString's returning null in package android.syncml.pim.vcard 530 */ 531 private final String checkStrEnd(String str, final boolean vCard21) { 532 if (str == null) { 533 return str; 534 } 535 if (str.charAt(str.length() - 1) != '\n') { 536 if (vCard21) { 537 str += "\r\n"; 538 } else { 539 str += "\n"; 540 } 541 } 542 return str; 543 } 544 545} 546