BluetoothPbapVcardManager.java revision 18bde766b20c899310ebdd5ca823e30ff27d407f
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.net.Uri; 38import android.os.Handler; 39import android.syncml.pim.vcard.ContactStruct; 40import android.syncml.pim.vcard.VCardException; 41import android.text.TextUtils; 42import android.util.Log; 43import android.database.Cursor; 44import android.content.ContentResolver; 45import android.content.Context; 46import android.provider.CallLog; 47import android.provider.CallLog.Calls; 48import android.provider.ContactsContract.RawContacts; 49import android.provider.ContactsContract.PhoneLookup; 50import android.pim.vcard.VCardComposer; 51import android.pim.vcard.VCardConfig; 52import android.pim.vcard.VCardComposer.OneEntryHandler; 53import android.provider.ContactsContract; 54import android.provider.ContactsContract.CommonDataKinds; 55import android.provider.ContactsContract.Contacts; 56import android.provider.ContactsContract.Data; 57import android.provider.ContactsContract.CommonDataKinds.Phone; 58 59import javax.obex.ResponseCodes; 60import javax.obex.Operation; 61import java.io.IOException; 62import java.io.OutputStream; 63import java.io.Writer; 64import java.util.ArrayList; 65 66public class BluetoothPbapVcardManager { 67 private static final String TAG = "BluetoothPbapVcardManager"; 68 69 private static final boolean V = BluetoothPbapService.VERBOSE; 70 71 private ContentResolver mResolver; 72 73 private Context mContext; 74 75 private StringBuilder mVcardResults = null; 76 77 static final String[] PHONES_PROJECTION = new String[] { 78 Data._ID, // 0 79 CommonDataKinds.Phone.TYPE, // 1 80 CommonDataKinds.Phone.LABEL, // 2 81 CommonDataKinds.Phone.NUMBER, // 3 82 Contacts.DISPLAY_NAME, // 4 83 }; 84 85 private static final int ID_COLUMN_INDEX = 0; 86 87 private static final int PHONE_TYPE_COLUMN_INDEX = 1; 88 89 private static final int PHONE_LABEL_COLUMN_INDEX = 2; 90 91 private static final int PHOEN_NUMBER_COLUMN_INDEX = 3; 92 93 private static final int CONTACTS_DISPLAY_NAME_COLUMN_INDEX = 4; 94 95 static final String SORT_ORDER_NAME = Contacts.DISPLAY_NAME + " ASC"; 96 97 static final String SORT_ORDER_PHONE_NUMBER = CommonDataKinds.Phone.NUMBER + " ASC"; 98 99 public BluetoothPbapVcardManager(final Context context) { 100 mContext = context; 101 mResolver = mContext.getContentResolver(); 102 } 103 104 public final String getOwnerPhoneNumberVcard(final boolean vcardType21) { 105 VCardComposer composer = new VCardComposer(mContext); 106 String name = BluetoothPbapService.getLocalPhoneName(); 107 String number = BluetoothPbapService.getLocalPhoneNum(); 108 String vcard = composer.composeVCardForPhoneOwnNumber(Phone.TYPE_MOBILE, name, number, 109 vcardType21); 110 return vcard; 111 } 112 113 public final int getPhonebookSize(final int type) { 114 int size; 115 switch (type) { 116 case BluetoothPbapObexServer.ContentType.PHONEBOOK: 117 size = getContactsSize(); 118 break; 119 default: 120 size = getCallHistorySize(type); 121 break; 122 } 123 if (V) Log.v(TAG, "getPhonebookSzie size = " + size + " type = " + type); 124 return size; 125 } 126 127 public final int getContactsSize() { 128 Uri myUri = RawContacts.CONTENT_URI; 129 int size = 0; 130 Cursor contactCursor = null; 131 try { 132 contactCursor = mResolver.query(myUri, null, null, null, null); 133 if (contactCursor != null) { 134 size = contactCursor.getCount() + 1; // always has the 0.vcf 135 } 136 } finally { 137 if (contactCursor != null) { 138 contactCursor.close(); 139 } 140 } 141 return size; 142 } 143 144 public final int getCallHistorySize(final int type) { 145 Uri myUri = CallLog.Calls.CONTENT_URI; 146 String selection = BluetoothPbapObexServer.createSelectionPara(type); 147 int size = 0; 148 Cursor callCursor = null; 149 try { 150 callCursor = mResolver.query(myUri, null, selection, null, 151 CallLog.Calls.DEFAULT_SORT_ORDER); 152 if (callCursor != null) { 153 size = callCursor.getCount(); 154 } 155 } finally { 156 if (callCursor != null) { 157 callCursor.close(); 158 } 159 } 160 return size; 161 } 162 163 public final ArrayList<String> loadCallHistoryList(final int type) { 164 Uri myUri = CallLog.Calls.CONTENT_URI; 165 String selection = BluetoothPbapObexServer.createSelectionPara(type); 166 String[] projection = new String[] { 167 Calls.NUMBER, Calls.CACHED_NAME 168 }; 169 final int CALLS_NUMBER_COLUMN_INDEX = 0; 170 final int CALLS_NAME_COLUMN_INDEX = 1; 171 172 Cursor callCursor = null; 173 ArrayList<String> list = new ArrayList<String>(); 174 try { 175 callCursor = mResolver.query(myUri, projection, selection, null, 176 CallLog.Calls.DEFAULT_SORT_ORDER); 177 if (callCursor != null) { 178 for (callCursor.moveToFirst(); !callCursor.isAfterLast(); 179 callCursor.moveToNext()) { 180 String name = callCursor.getString(CALLS_NAME_COLUMN_INDEX); 181 if (TextUtils.isEmpty(name)) { 182 // name not found,use number instead 183 name = callCursor.getString(CALLS_NUMBER_COLUMN_INDEX); 184 } 185 list.add(name); 186 } 187 } 188 } finally { 189 if (callCursor != null) { 190 callCursor.close(); 191 } 192 } 193 return list; 194 } 195 196 public final ArrayList<String> getPhonebookNameList() { 197 ArrayList<String> nameList = new ArrayList<String>(); 198 nameList.add(BluetoothPbapService.getLocalPhoneName()); 199 200 Uri myUri = Phone.CONTENT_URI; 201 Cursor phoneCursor = null; 202 try { 203 phoneCursor = mResolver.query(myUri, PHONES_PROJECTION, null, null, SORT_ORDER_NAME); 204 if (phoneCursor != null) { 205 for (phoneCursor.moveToFirst(); !phoneCursor.isAfterLast(); phoneCursor 206 .moveToNext()) { 207 String name = phoneCursor.getString(CONTACTS_DISPLAY_NAME_COLUMN_INDEX); 208 if (TextUtils.isEmpty(name)) { 209 name = mContext.getString(android.R.string.unknownName); 210 } 211 nameList.add(name); 212 } 213 } 214 } finally { 215 if (phoneCursor != null) { 216 phoneCursor.close(); 217 } 218 } 219 return nameList; 220 } 221 222 public final ArrayList<String> getPhonebookNumberList() { 223 ArrayList<String> numberList = new ArrayList<String>(); 224 numberList.add(BluetoothPbapService.getLocalPhoneNum()); 225 226 Uri myUri = Phone.CONTENT_URI; 227 Cursor phoneCursor = null; 228 try { 229 phoneCursor = mResolver.query(myUri, PHONES_PROJECTION, null, null, 230 SORT_ORDER_PHONE_NUMBER); 231 if (phoneCursor != null) { 232 for (phoneCursor.moveToFirst(); !phoneCursor.isAfterLast(); phoneCursor 233 .moveToNext()) { 234 String number = phoneCursor.getString(PHOEN_NUMBER_COLUMN_INDEX); 235 if (TextUtils.isEmpty(number)) { 236 number = mContext.getString(R.string.defaultnumber); 237 } 238 numberList.add(number); 239 } 240 } 241 } finally { 242 if (phoneCursor != null) { 243 phoneCursor.close(); 244 } 245 } 246 return numberList; 247 } 248 249 public final int composeAndSendCallLogVcards(final int type, final Operation op, 250 final int startPoint, final int endPoint, final boolean vcardType21) { 251 String typeSelection = BluetoothPbapObexServer.createSelectionPara(type); 252 String recordSelection; 253 if (startPoint == endPoint) { 254 recordSelection = Calls._ID + "=" + startPoint; 255 } else { 256 recordSelection = Calls._ID + ">=" + startPoint + " AND " 257 + Calls._ID + "<=" + endPoint; 258 } 259 260 String selection; 261 if (typeSelection == null) { 262 selection = recordSelection; 263 } else { 264 selection = "(" + typeSelection + ") AND (" + recordSelection + ")"; 265 } 266 267 if (V) Log.v(TAG, "Query selection is: " + selection); 268 269 return composeAndSendVCards(op, selection, vcardType21, null, false); 270 } 271 272 public final int composeAndSendPhonebookVcards(final Operation op, final int startPoint, 273 final int endPoint, final boolean vcardType21, String ownerVCard) { 274 String selection; 275 if (startPoint == endPoint) { 276 selection = RawContacts._ID + "=" + startPoint; 277 } else { 278 selection = RawContacts._ID + ">=" + startPoint + " AND " + RawContacts._ID + "<=" 279 + endPoint; 280 } 281 282 if (V) Log.v(TAG, "Query selection is: " + selection); 283 284 return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true); 285 } 286 287 public final int composeAndSendVCards(final Operation op, final String selection, 288 final boolean vcardType21, String ownerVCard, boolean isContacts) { 289 long timestamp = 0; 290 if (V) timestamp = System.currentTimeMillis(); 291 292 VCardComposer composer = null; 293 try { 294 // Currently only support Generic Vcard 2.1 and 3.0 295 int vcardType; 296 if (vcardType21) { 297 vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC; 298 } else { 299 vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC; 300 } 301 302 if (isContacts) { 303 // create VCardComposer for contacts 304 composer = new VCardComposer(mContext, vcardType, true); 305 } else { 306 // create VCardComposer for call logs 307 composer = new VCardComposer(mContext, vcardType, true, true); 308 } 309 310 composer.addHandler(new HandlerForStringBuffer(op, ownerVCard)); 311 312 if (!composer.init(selection, null)) { 313 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 314 } 315 316 while (!composer.isAfterLast()) { 317 if (!composer.createOneEntry()) { 318 Log.e(TAG, "Failed to read a contact. Error reason: " 319 + composer.getErrorReason()); 320 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 321 } 322 } 323 } finally { 324 if (composer != null) { 325 composer.terminate(); 326 } 327 } 328 329 if (V) Log.v(TAG, "Total vcard composing and sending out takes " 330 + (System.currentTimeMillis() - timestamp) + " ms"); 331 332 return ResponseCodes.OBEX_HTTP_OK; 333 } 334 335 /** 336 * Handler to emit VCard String to PCE once size grow to maxPacketSize. 337 */ 338 public class HandlerForStringBuffer implements OneEntryHandler { 339 @SuppressWarnings("hiding") 340 private Operation operation; 341 342 private OutputStream outputStream; 343 344 private int maxPacketSize; 345 346 private String phoneOwnVCard = null; 347 348 public HandlerForStringBuffer(Operation op, String ownerVCard) { 349 operation = op; 350 maxPacketSize = operation.getMaxPacketSize(); 351 if (V) Log.v(TAG, "getMaxPacketSize() = " + maxPacketSize); 352 if (ownerVCard != null) { 353 phoneOwnVCard = ownerVCard; 354 if (V) Log.v(TAG, "phone own number vcard:"); 355 if (V) Log.v(TAG, phoneOwnVCard); 356 } 357 } 358 359 public boolean onInit(Context context) { 360 try { 361 outputStream = operation.openOutputStream(); 362 mVcardResults = new StringBuilder(); 363 if (phoneOwnVCard != null) { 364 mVcardResults.append(phoneOwnVCard); 365 } 366 } catch (IOException e) { 367 Log.e(TAG, "open outputstrem failed" + e.toString()); 368 return false; 369 } 370 if (V) Log.v(TAG, "openOutputStream() ok."); 371 return true; 372 } 373 374 public boolean onEntryCreated(String vcard) { 375 int vcardLen = vcard.length(); 376 if (V) Log.v(TAG, "The length of this vcard is: " + vcardLen); 377 378 mVcardResults.append(vcard); 379 int vcardStringLen = mVcardResults.toString().length(); 380 if (V) Log.v(TAG, "The length of this vcardResults is: " + vcardStringLen); 381 382 if (vcardStringLen >= maxPacketSize) { 383 long timestamp = 0; 384 int position = 0; 385 386 // Need while loop to handle the big vcard case 387 while (position < (vcardStringLen - maxPacketSize)) { 388 if (V) timestamp = System.currentTimeMillis(); 389 390 String subStr = mVcardResults.toString().substring(position, 391 position + maxPacketSize); 392 try { 393 outputStream.write(subStr.getBytes(), 0, maxPacketSize); 394 } catch (IOException e) { 395 Log.e(TAG, "write outputstrem failed" + e.toString()); 396 return false; 397 } 398 if (V) Log.v(TAG, "Sending vcard String " + maxPacketSize + " bytes took " 399 + (System.currentTimeMillis() - timestamp) + " ms"); 400 401 position += maxPacketSize; 402 } 403 mVcardResults.delete(0, position); 404 } 405 return true; 406 } 407 408 public void onTerminate() { 409 // Send out last packet 410 String lastStr = mVcardResults.toString(); 411 try { 412 outputStream.write(lastStr.getBytes(), 0, lastStr.length()); 413 } catch (IOException e) { 414 Log.e(TAG, "write outputstrem failed" + e.toString()); 415 } 416 if (V) Log.v(TAG, "Last packet sent out, sending process complete!"); 417 418 if (!BluetoothPbapObexServer.closeStream(outputStream, operation)) { 419 if (V) Log.v(TAG, "CloseStream failed!"); 420 } else { 421 if (V) Log.v(TAG, "CloseStream ok!"); 422 } 423 } 424 } 425} 426