BluetoothPbapObexServer.java revision 91e595b5bef1b092975992e529b2c581eb1be08b
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 android.content.Context; 36import android.content.ContentResolver; 37import android.database.Cursor; 38import android.os.Message; 39import android.os.Handler; 40import android.provider.CallLog; 41import android.provider.CallLog.Calls; 42import android.text.TextUtils; 43import android.util.Log; 44import android.os.SystemProperties; 45 46import java.io.IOException; 47import java.io.OutputStream; 48import java.text.CharacterIterator; 49import java.text.StringCharacterIterator; 50import java.util.ArrayList; 51import java.util.Arrays; 52import java.nio.ByteBuffer; 53 54import javax.obex.ServerRequestHandler; 55import javax.obex.ResponseCodes; 56import javax.obex.ApplicationParameter; 57import javax.obex.Operation; 58import javax.obex.HeaderSet; 59 60public class BluetoothPbapObexServer extends ServerRequestHandler { 61 62 private static final String TAG = "BluetoothPbapObexServer"; 63 64 private static final boolean D = BluetoothPbapService.DEBUG; 65 66 private static final boolean V = BluetoothPbapService.VERBOSE; 67 68 private static final int UUID_LENGTH = 16; 69 70 public static final long INVALID_VALUE_PARAMETER = -1; 71 72 // The length of suffix of vcard name - ".vcf" is 5 73 private static final int VCARD_NAME_SUFFIX_LENGTH = 5; 74 75 // 128 bit UUID for PBAP 76 private static final byte[] PBAP_TARGET = new byte[] { 77 0x79, 0x61, 0x35, (byte)0xf0, (byte)0xf0, (byte)0xc5, 0x11, (byte)0xd8, 0x09, 0x66, 78 0x08, 0x00, 0x20, 0x0c, (byte)0x9a, 0x66 79 }; 80 81 // Currently not support SIM card 82 private static final String[] LEGAL_PATH = { 83 "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch", 84 "/telecom/cch" 85 }; 86 87 @SuppressWarnings("unused") 88 private static final String[] LEGAL_PATH_WITH_SIM = { 89 "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch", 90 "/telecom/cch", "/SIM1", "/SIM1/telecom", "/SIM1/telecom/ich", "/SIM1/telecom/och", 91 "/SIM1/telecom/mch", "/SIM1/telecom/cch", "/SIM1/telecom/pb" 92 93 }; 94 95 // SIM card 96 private static final String SIM1 = "SIM1"; 97 98 // missed call history 99 private static final String MCH = "mch"; 100 101 // incoming call history 102 private static final String ICH = "ich"; 103 104 // outgoing call history 105 private static final String OCH = "och"; 106 107 // combined call history 108 private static final String CCH = "cch"; 109 110 // phone book 111 private static final String PB = "pb"; 112 113 private static final String TELECOM_PATH = "/telecom"; 114 115 private static final String ICH_PATH = "/telecom/ich"; 116 117 private static final String OCH_PATH = "/telecom/och"; 118 119 private static final String MCH_PATH = "/telecom/mch"; 120 121 private static final String CCH_PATH = "/telecom/cch"; 122 123 private static final String PB_PATH = "/telecom/pb"; 124 125 // type for list vcard objects 126 private static final String TYPE_LISTING = "x-bt/vcard-listing"; 127 128 // type for get single vcard object 129 private static final String TYPE_VCARD = "x-bt/vcard"; 130 131 // to indicate if need send body besides headers 132 private static final int NEED_SEND_BODY = -1; 133 134 // type for download all vcard objects 135 private static final String TYPE_PB = "x-bt/phonebook"; 136 137 // The number of indexes in the phone book. 138 private boolean mNeedPhonebookSize = false; 139 140 // The number of missed calls that have not been checked on the PSE at the 141 // point of the request. Only apply to "mch" case. 142 private boolean mNeedNewMissedCallsNum = false; 143 144 private boolean mVcardSelector = false; 145 146 private int mMissedCallSize = 0; 147 148 // record current path the client are browsing 149 private String mCurrentPath = ""; 150 151 private Handler mCallback = null; 152 153 private Context mContext; 154 155 private BluetoothPbapService mService; 156 157 private BluetoothPbapVcardManager mVcardManager; 158 159 private int mOrderBy = ORDER_BY_INDEXED; 160 161 private static int CALLLOG_NUM_LIMIT = 50; 162 163 public static int ORDER_BY_INDEXED = 0; 164 165 public static int ORDER_BY_ALPHABETICAL = 1; 166 167 public static boolean sIsAborted = false; 168 169 private long mDatabaseIdentifierLow = INVALID_VALUE_PARAMETER; 170 171 private long mDatabaseIdentifierHigh = INVALID_VALUE_PARAMETER; 172 173 private long folderVersionCounterbitMask = 0x0008; 174 175 private long databaseIdentifierBitMask = 0x0004; 176 177 private AppParamValue connAppParamValue; 178 179 public static class ContentType { 180 public static final int PHONEBOOK = 1; 181 182 public static final int INCOMING_CALL_HISTORY = 2; 183 184 public static final int OUTGOING_CALL_HISTORY = 3; 185 186 public static final int MISSED_CALL_HISTORY = 4; 187 188 public static final int COMBINED_CALL_HISTORY = 5; 189 } 190 191 public BluetoothPbapObexServer(Handler callback, BluetoothPbapService service) { 192 super(); 193 mCallback = callback; 194 mService = service; 195 mContext = service.getApplicationContext(); 196 mVcardManager = new BluetoothPbapVcardManager(mContext); 197 } 198 199 @Override 200 public int onConnect(final HeaderSet request, HeaderSet reply) { 201 if (V) logHeader(request); 202 notifyUpdateWakeLock(); 203 try { 204 byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET); 205 if (uuid == null) { 206 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 207 } 208 if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid)); 209 210 if (uuid.length != UUID_LENGTH) { 211 Log.w(TAG, "Wrong UUID length"); 212 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 213 } 214 for (int i = 0; i < UUID_LENGTH; i++) { 215 if (uuid[i] != PBAP_TARGET[i]) { 216 Log.w(TAG, "Wrong UUID"); 217 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 218 } 219 } 220 reply.setHeader(HeaderSet.WHO, uuid); 221 } catch (IOException e) { 222 Log.e(TAG, e.toString()); 223 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 224 } 225 226 try { 227 byte[] remote = (byte[])request.getHeader(HeaderSet.WHO); 228 if (remote != null) { 229 if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote)); 230 reply.setHeader(HeaderSet.TARGET, remote); 231 } 232 } catch (IOException e) { 233 Log.e(TAG, e.toString()); 234 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 235 } 236 237 try { 238 byte[] appParam = null; 239 connAppParamValue = new AppParamValue(); 240 appParam = (byte[]) request.getHeader(HeaderSet.APPLICATION_PARAMETER); 241 if ((appParam != null) && !parseApplicationParameter(appParam, connAppParamValue)) { 242 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 243 } 244 } catch (IOException e) { 245 Log.e(TAG, e.toString()); 246 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 247 } 248 249 if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " + 250 "MSG_SESSION_ESTABLISHED msg."); 251 252 Message msg = Message.obtain(mCallback); 253 msg.what = BluetoothPbapService.MSG_SESSION_ESTABLISHED; 254 msg.sendToTarget(); 255 return ResponseCodes.OBEX_HTTP_OK; 256 } 257 258 @Override 259 public void onDisconnect(final HeaderSet req, final HeaderSet resp) { 260 if (D) Log.d(TAG, "onDisconnect(): enter"); 261 if (V) logHeader(req); 262 notifyUpdateWakeLock(); 263 resp.responseCode = ResponseCodes.OBEX_HTTP_OK; 264 if (mCallback != null) { 265 Message msg = Message.obtain(mCallback); 266 msg.what = BluetoothPbapService.MSG_SESSION_DISCONNECTED; 267 msg.sendToTarget(); 268 if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out."); 269 } 270 } 271 272 @Override 273 public int onAbort(HeaderSet request, HeaderSet reply) { 274 if (D) Log.d(TAG, "onAbort(): enter."); 275 notifyUpdateWakeLock(); 276 sIsAborted = true; 277 return ResponseCodes.OBEX_HTTP_OK; 278 } 279 280 @Override 281 public int onPut(final Operation op) { 282 if (D) Log.d(TAG, "onPut(): not support PUT request."); 283 notifyUpdateWakeLock(); 284 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 285 } 286 287 @Override 288 public int onDelete(final HeaderSet request, final HeaderSet reply) { 289 if (D) Log.d(TAG, "onDelete(): not support PUT request."); 290 notifyUpdateWakeLock(); 291 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 292 } 293 294 @Override 295 public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup, 296 final boolean create) { 297 if (V) logHeader(request); 298 if (D) Log.d(TAG, "before setPath, mCurrentPath == " + mCurrentPath); 299 notifyUpdateWakeLock(); 300 String current_path_tmp = mCurrentPath; 301 String tmp_path = null; 302 try { 303 tmp_path = (String)request.getHeader(HeaderSet.NAME); 304 } catch (IOException e) { 305 Log.e(TAG, "Get name header fail"); 306 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 307 } 308 if (D) Log.d(TAG, "backup=" + backup + " create=" + create + " name=" + tmp_path); 309 310 if (backup) { 311 if (current_path_tmp.length() != 0) { 312 current_path_tmp = current_path_tmp.substring(0, 313 current_path_tmp.lastIndexOf("/")); 314 } 315 } else { 316 if (tmp_path == null) { 317 current_path_tmp = ""; 318 } else { 319 current_path_tmp = current_path_tmp + "/" + tmp_path; 320 } 321 } 322 323 if ((current_path_tmp.length() != 0) && (!isLegalPath(current_path_tmp))) { 324 if (create) { 325 Log.w(TAG, "path create is forbidden!"); 326 return ResponseCodes.OBEX_HTTP_FORBIDDEN; 327 } else { 328 Log.w(TAG, "path is not legal"); 329 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 330 } 331 } 332 mCurrentPath = current_path_tmp; 333 if (V) Log.v(TAG, "after setPath, mCurrentPath == " + mCurrentPath); 334 335 return ResponseCodes.OBEX_HTTP_OK; 336 } 337 338 @Override 339 public void onClose() { 340 if (mCallback != null) { 341 Message msg = Message.obtain(mCallback); 342 msg.what = BluetoothPbapService.MSG_SERVERSESSION_CLOSE; 343 msg.sendToTarget(); 344 if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out."); 345 } 346 } 347 348 @Override 349 public int onGet(Operation op) { 350 notifyUpdateWakeLock(); 351 sIsAborted = false; 352 HeaderSet request = null; 353 HeaderSet reply = new HeaderSet(); 354 String type = ""; 355 String name = ""; 356 byte[] appParam = null; 357 AppParamValue appParamValue = new AppParamValue(); 358 try { 359 request = op.getReceivedHeader(); 360 type = (String)request.getHeader(HeaderSet.TYPE); 361 name = (String)request.getHeader(HeaderSet.NAME); 362 appParam = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER); 363 } catch (IOException e) { 364 Log.e(TAG, "request headers error"); 365 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 366 } 367 368 /* TODO: block Get request if contacts are not completely loaded locally */ 369 370 if (V) logHeader(request); 371 if (D) Log.d(TAG, "OnGet type is " + type + "; name is " + name); 372 373 if (type == null) { 374 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 375 } 376 // Accroding to specification,the name header could be omitted such as 377 // sony erriccsonHBH-DS980 378 379 // For "x-bt/phonebook" and "x-bt/vcard-listing": 380 // if name == null, guess what carkit actually want from current path 381 // For "x-bt/vcard": 382 // We decide which kind of content client would like per current path 383 384 boolean validName = true; 385 if (TextUtils.isEmpty(name)) { 386 validName = false; 387 } 388 389 if (!validName || (validName && type.equals(TYPE_VCARD))) { 390 if (D) Log.d(TAG, "Guess what carkit actually want from current path (" + 391 mCurrentPath + ")"); 392 393 if (mCurrentPath.equals(PB_PATH)) { 394 appParamValue.needTag = ContentType.PHONEBOOK; 395 } else if (mCurrentPath.equals(ICH_PATH)) { 396 appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY; 397 } else if (mCurrentPath.equals(OCH_PATH)) { 398 appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY; 399 } else if (mCurrentPath.equals(MCH_PATH)) { 400 appParamValue.needTag = ContentType.MISSED_CALL_HISTORY; 401 mNeedNewMissedCallsNum = true; 402 } else if (mCurrentPath.equals(CCH_PATH)) { 403 appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY; 404 } else if (mCurrentPath.equals(TELECOM_PATH)) { 405 /* PBAP 1.1.1 change */ 406 if (!validName && type.equals(TYPE_LISTING)) { 407 Log.e(TAG, "invalid vcard listing request in default folder"); 408 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 409 } 410 } else { 411 Log.w(TAG, "mCurrentpath is not valid path!!!"); 412 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 413 } 414 if (D) Log.v(TAG, "onGet(): appParamValue.needTag=" + appParamValue.needTag); 415 } else { 416 // Not support SIM card currently 417 if (name.contains(SIM1.subSequence(0, SIM1.length()))) { 418 Log.w(TAG, "Not support access SIM card info!"); 419 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 420 } 421 422 // we have weak name checking here to provide better 423 // compatibility with other devices,although unique name such as 424 // "pb.vcf" is required by SIG spec. 425 if (isNameMatchTarget(name, PB)) { 426 appParamValue.needTag = ContentType.PHONEBOOK; 427 if (D) Log.v(TAG, "download phonebook request"); 428 } else if (isNameMatchTarget(name, ICH)) { 429 appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY; 430 appParamValue.callHistoryVersionCounter = 431 mVcardManager.getCallHistoryPrimaryFolderVersion( 432 ContentType.INCOMING_CALL_HISTORY); 433 if (D) Log.v(TAG, "download incoming calls request"); 434 } else if (isNameMatchTarget(name, OCH)) { 435 appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY; 436 appParamValue.callHistoryVersionCounter = 437 mVcardManager.getCallHistoryPrimaryFolderVersion( 438 ContentType.OUTGOING_CALL_HISTORY); 439 if (D) Log.v(TAG, "download outgoing calls request"); 440 } else if (isNameMatchTarget(name, MCH)) { 441 appParamValue.needTag = ContentType.MISSED_CALL_HISTORY; 442 appParamValue.callHistoryVersionCounter = 443 mVcardManager.getCallHistoryPrimaryFolderVersion( 444 ContentType.MISSED_CALL_HISTORY); 445 mNeedNewMissedCallsNum = true; 446 if (D) Log.v(TAG, "download missed calls request"); 447 } else if (isNameMatchTarget(name, CCH)) { 448 appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY; 449 appParamValue.callHistoryVersionCounter = 450 mVcardManager.getCallHistoryPrimaryFolderVersion( 451 ContentType.COMBINED_CALL_HISTORY); 452 if (D) Log.v(TAG, "download combined calls request"); 453 } else { 454 Log.w(TAG, "Input name doesn't contain valid info!!!"); 455 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 456 } 457 } 458 459 if ((appParam != null) && !parseApplicationParameter(appParam, appParamValue)) { 460 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 461 } 462 463 // listing request 464 if (type.equals(TYPE_LISTING)) { 465 return pullVcardListing(appParam, appParamValue, reply, op, name); 466 } 467 // pull vcard entry request 468 else if (type.equals(TYPE_VCARD)) { 469 return pullVcardEntry(appParam, appParamValue, op, reply, name, mCurrentPath); 470 } 471 // down load phone book request 472 else if (type.equals(TYPE_PB)) { 473 return pullPhonebook(appParam, appParamValue, reply, op, name); 474 } else { 475 Log.w(TAG, "unknown type request!!!"); 476 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 477 } 478 } 479 480 private boolean isNameMatchTarget(String name, String target) { 481 String contentTypeName = name; 482 if (contentTypeName.endsWith(".vcf")) { 483 contentTypeName = contentTypeName 484 .substring(0, contentTypeName.length() - ".vcf".length()); 485 } 486 // There is a test case: Client will send a wrong name "/telecom/pbpb". 487 // So we must use the String between '/' and '/' as a indivisible part 488 // for comparing. 489 String[] nameList = contentTypeName.split("/"); 490 for (String subName : nameList) { 491 if (subName.equals(target)) { 492 return true; 493 } 494 } 495 return false; 496 } 497 498 /** check whether path is legal */ 499 private final boolean isLegalPath(final String str) { 500 if (str.length() == 0) { 501 return true; 502 } 503 for (int i = 0; i < LEGAL_PATH.length; i++) { 504 if (str.equals(LEGAL_PATH[i])) { 505 return true; 506 } 507 } 508 return false; 509 } 510 511 private class AppParamValue { 512 public int maxListCount; 513 514 public int listStartOffset; 515 516 public String searchValue; 517 518 // Indicate which vCard parameter the search operation shall be carried 519 // out on. Can be "Name | Number | Sound", default value is "Name". 520 public String searchAttr; 521 522 // Indicate which sorting order shall be used for the 523 // <x-bt/vcard-listing> listing object. 524 // Can be "Alphabetical | Indexed | Phonetical", default value is 525 // "Indexed". 526 public String order; 527 528 public int needTag; 529 530 public boolean vcard21; 531 532 public byte[] propertySelector; 533 534 public byte[] supportedFeature; 535 536 public boolean ignorefilter; 537 538 public byte[] vCardSelector; 539 540 public String vCardSelectorOperator; 541 542 public byte[] callHistoryVersionCounter; 543 544 public AppParamValue() { 545 maxListCount = 0xFFFF; 546 listStartOffset = 0; 547 searchValue = ""; 548 searchAttr = ""; 549 order = ""; 550 needTag = 0x00; 551 vcard21 = true; 552 //Filter is not set by default 553 ignorefilter = true; 554 vCardSelectorOperator = "0"; 555 propertySelector = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 556 vCardSelector = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 557 supportedFeature = new byte[] {0x00, 0x00, 0x00, 0x00}; 558 } 559 560 public void dump() { 561 Log.i(TAG, "maxListCount=" + maxListCount + " listStartOffset=" + listStartOffset 562 + " searchValue=" + searchValue + " searchAttr=" + searchAttr 563 + " needTag=" + needTag + " vcard21=" + vcard21 + " order=" + order 564 + "vcardselector=" + vCardSelector + "vcardselop=" 565 + vCardSelectorOperator); 566 } 567 } 568 569 /** To parse obex application parameter */ 570 private final boolean parseApplicationParameter(final byte[] appParam, 571 AppParamValue appParamValue) { 572 int i = 0; 573 boolean parseOk = true; 574 while ((i < appParam.length) && (parseOk == true)) { 575 switch (appParam[i]) { 576 case ApplicationParameter.TRIPLET_TAGID.PROPERTY_SELECTOR_TAGID: 577 i += 2; // length and tag field in triplet 578 for (int index = 0; 579 index < ApplicationParameter.TRIPLET_LENGTH.PROPERTY_SELECTOR_LENGTH; 580 index++) { 581 if (appParam[i+index] != 0){ 582 appParamValue.ignorefilter = false; 583 appParamValue.propertySelector[index] = appParam[i + index]; 584 } 585 } 586 i += ApplicationParameter.TRIPLET_LENGTH.PROPERTY_SELECTOR_LENGTH; 587 break; 588 case ApplicationParameter.TRIPLET_TAGID.SUPPORTEDFEATURE_TAGID: 589 i += 2; // length and tag field in triplet 590 for (int index = 0; 591 index < ApplicationParameter.TRIPLET_LENGTH.SUPPORTEDFEATURE_LENGTH; 592 index++) { 593 if (appParam[i + index] != 0) { 594 appParamValue.supportedFeature[index] = appParam[i + index]; 595 } 596 } 597 598 i += ApplicationParameter.TRIPLET_LENGTH.SUPPORTEDFEATURE_LENGTH; 599 break; 600 601 case ApplicationParameter.TRIPLET_TAGID.ORDER_TAGID: 602 i += 2; // length and tag field in triplet 603 appParamValue.order = Byte.toString(appParam[i]); 604 i += ApplicationParameter.TRIPLET_LENGTH.ORDER_LENGTH; 605 break; 606 case ApplicationParameter.TRIPLET_TAGID.SEARCH_VALUE_TAGID: 607 i += 1; // length field in triplet 608 // length of search value is variable 609 int length = appParam[i]; 610 if (length == 0) { 611 parseOk = false; 612 break; 613 } 614 if (appParam[i+length] == 0x0) { 615 appParamValue.searchValue = new String(appParam, i + 1, length-1); 616 } else { 617 appParamValue.searchValue = new String(appParam, i + 1, length); 618 } 619 i += length; 620 i += 1; 621 break; 622 case ApplicationParameter.TRIPLET_TAGID.SEARCH_ATTRIBUTE_TAGID: 623 i += 2; 624 appParamValue.searchAttr = Byte.toString(appParam[i]); 625 i += ApplicationParameter.TRIPLET_LENGTH.SEARCH_ATTRIBUTE_LENGTH; 626 break; 627 case ApplicationParameter.TRIPLET_TAGID.MAXLISTCOUNT_TAGID: 628 i += 2; 629 if (appParam[i] == 0 && appParam[i + 1] == 0) { 630 mNeedPhonebookSize = true; 631 } else { 632 int highValue = appParam[i] & 0xff; 633 int lowValue = appParam[i + 1] & 0xff; 634 appParamValue.maxListCount = highValue * 256 + lowValue; 635 } 636 i += ApplicationParameter.TRIPLET_LENGTH.MAXLISTCOUNT_LENGTH; 637 break; 638 case ApplicationParameter.TRIPLET_TAGID.LISTSTARTOFFSET_TAGID: 639 i += 2; 640 int highValue = appParam[i] & 0xff; 641 int lowValue = appParam[i + 1] & 0xff; 642 appParamValue.listStartOffset = highValue * 256 + lowValue; 643 i += ApplicationParameter.TRIPLET_LENGTH.LISTSTARTOFFSET_LENGTH; 644 break; 645 case ApplicationParameter.TRIPLET_TAGID.FORMAT_TAGID: 646 i += 2;// length field in triplet 647 if (appParam[i] != 0) { 648 appParamValue.vcard21 = false; 649 } 650 i += ApplicationParameter.TRIPLET_LENGTH.FORMAT_LENGTH; 651 break; 652 653 case ApplicationParameter.TRIPLET_TAGID.VCARDSELECTOR_TAGID: 654 i += 2; 655 for (int index = 0; 656 index < ApplicationParameter.TRIPLET_LENGTH.VCARDSELECTOR_LENGTH; 657 index++) { 658 if (appParam[i + index] != 0) { 659 mVcardSelector = true; 660 appParamValue.vCardSelector[index] = appParam[i + index]; 661 } 662 } 663 i += ApplicationParameter.TRIPLET_LENGTH.VCARDSELECTOR_LENGTH; 664 break; 665 case ApplicationParameter.TRIPLET_TAGID.VCARDSELECTOROPERATOR_TAGID: 666 i += 2; 667 appParamValue.vCardSelectorOperator = Byte.toString(appParam[i]); 668 i += ApplicationParameter.TRIPLET_LENGTH.VCARDSELECTOROPERATOR_LENGTH; 669 break; 670 default: 671 parseOk = false; 672 Log.e(TAG, "Parse Application Parameter error"); 673 break; 674 } 675 } 676 677 if (D) appParamValue.dump(); 678 679 return parseOk; 680 } 681 682 /** Form and Send an XML format String to client for Phone book listing */ 683 private final int sendVcardListingXml( 684 AppParamValue appParamValue, Operation op, int needSendBody, int size) { 685 StringBuilder result = new StringBuilder(); 686 int itemsFound = 0; 687 result.append("<?xml version=\"1.0\"?>"); 688 result.append("<!DOCTYPE vcard-listing SYSTEM \"vcard-listing.dtd\">"); 689 result.append("<vCard-listing version=\"1.0\">"); 690 691 // Phonebook listing request 692 if (appParamValue.needTag == ContentType.PHONEBOOK) { 693 String type = ""; 694 if (appParamValue.searchAttr.equals("0")) { 695 type = "name"; 696 } else if (appParamValue.searchAttr.equals("1")) { 697 type = "number"; 698 } 699 if (type.length() > 0) { 700 itemsFound = createList(appParamValue, needSendBody, size, result, type); 701 } else { 702 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 703 } 704 } 705 // Call history listing request 706 else { 707 ArrayList<String> nameList = mVcardManager.loadCallHistoryList(appParamValue.needTag); 708 int requestSize = nameList.size() >= appParamValue.maxListCount 709 ? appParamValue.maxListCount 710 : nameList.size(); 711 int startPoint = appParamValue.listStartOffset; 712 int endPoint = startPoint + requestSize; 713 if (endPoint > nameList.size()) { 714 endPoint = nameList.size(); 715 } 716 if (D) 717 Log.d(TAG, "call log list, size=" + requestSize + " offset=" 718 + appParamValue.listStartOffset); 719 720 for (int j = startPoint; j < endPoint; j++) { 721 writeVCardEntry(j+1, nameList.get(j),result); 722 } 723 } 724 result.append("</vCard-listing>"); 725 726 if (D) Log.d(TAG, "itemsFound =" + itemsFound); 727 728 return pushBytes(op, result.toString()); 729 } 730 731 private int createList(AppParamValue appParamValue, int needSendBody, int size, 732 StringBuilder result, String type) { 733 int itemsFound = 0; 734 735 ArrayList<String> nameList = null; 736 if (mVcardSelector) { 737 nameList = mVcardManager.getSelectedPhonebookNameList(mOrderBy, appParamValue.vcard21, 738 needSendBody, size, appParamValue.vCardSelector, 739 appParamValue.vCardSelectorOperator); 740 } else { 741 nameList = mVcardManager.getPhonebookNameList(mOrderBy); 742 } 743 744 final int requestSize = nameList.size() >= appParamValue.maxListCount 745 ? appParamValue.maxListCount 746 : nameList.size(); 747 final int listSize = nameList.size(); 748 String compareValue = "", currentValue; 749 750 if (D) 751 Log.d(TAG, "search by " + type + ", requestSize=" + requestSize + " offset=" 752 + appParamValue.listStartOffset + " searchValue=" 753 + appParamValue.searchValue); 754 755 if (type.equals("number")) { 756 // query the number, to get the names 757 ArrayList<String> names = 758 mVcardManager.getContactNamesByNumber(appParamValue.searchValue); 759 for (int i = 0; i < names.size(); i++) { 760 compareValue = names.get(i).trim(); 761 if (D) Log.d(TAG, "compareValue=" + compareValue); 762 for (int pos = appParamValue.listStartOffset; 763 pos < listSize && itemsFound < requestSize; pos++) { 764 currentValue = nameList.get(pos); 765 if (D) Log.d(TAG, "currentValue=" + currentValue); 766 if (currentValue.equals(compareValue)) { 767 itemsFound++; 768 if (currentValue.contains(",")) 769 currentValue = currentValue.substring(0, currentValue.lastIndexOf(',')); 770 writeVCardEntry(pos, currentValue,result); 771 } 772 } 773 if (itemsFound >= requestSize) { 774 break; 775 } 776 } 777 } else { 778 if (appParamValue.searchValue != null) { 779 compareValue = appParamValue.searchValue.trim().toLowerCase(); 780 } 781 for (int pos = appParamValue.listStartOffset; 782 pos < listSize && itemsFound < requestSize; pos++) { 783 currentValue = nameList.get(pos); 784 if (currentValue.contains(",")) 785 currentValue = currentValue.substring(0, currentValue.lastIndexOf(',')); 786 787 if (appParamValue.searchValue.isEmpty() 788 || ((currentValue.toLowerCase()).startsWith(compareValue))) { 789 itemsFound++; 790 writeVCardEntry(pos, currentValue,result); 791 } 792 } 793 } 794 return itemsFound; 795 } 796 797 /** 798 * Function to send obex header back to client such as get phonebook size 799 * request 800 */ 801 private final int pushHeader(final Operation op, final HeaderSet reply) { 802 OutputStream outputStream = null; 803 804 if (D) Log.d(TAG, "Push Header"); 805 if (D) Log.d(TAG, reply.toString()); 806 807 int pushResult = ResponseCodes.OBEX_HTTP_OK; 808 try { 809 op.sendHeaders(reply); 810 outputStream = op.openOutputStream(); 811 outputStream.flush(); 812 } catch (IOException e) { 813 Log.e(TAG, e.toString()); 814 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 815 } finally { 816 if (!closeStream(outputStream, op)) { 817 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 818 } 819 } 820 return pushResult; 821 } 822 823 /** Function to send vcard data to client */ 824 private final int pushBytes(Operation op, final String vcardString) { 825 if (vcardString == null) { 826 Log.w(TAG, "vcardString is null!"); 827 return ResponseCodes.OBEX_HTTP_OK; 828 } 829 830 OutputStream outputStream = null; 831 int pushResult = ResponseCodes.OBEX_HTTP_OK; 832 try { 833 outputStream = op.openOutputStream(); 834 outputStream.write(vcardString.getBytes()); 835 if (V) Log.v(TAG, "Send Data complete!"); 836 } catch (IOException e) { 837 Log.e(TAG, "open/write outputstrem failed" + e.toString()); 838 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 839 } 840 841 if (!closeStream(outputStream, op)) { 842 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 843 } 844 845 return pushResult; 846 } 847 848 private final int handleAppParaForResponse( 849 AppParamValue appParamValue, int size, HeaderSet reply, Operation op, String name) { 850 byte[] misnum = new byte[1]; 851 ApplicationParameter ap = new ApplicationParameter(); 852 boolean needSendCallHistoryVersionCounters = false; 853 if (isNameMatchTarget(name, MCH) || isNameMatchTarget(name, ICH) 854 || isNameMatchTarget(name, OCH) || isNameMatchTarget(name, CCH)) 855 needSendCallHistoryVersionCounters = 856 checkPbapFeatureSupport(folderVersionCounterbitMask); 857 boolean needSendPhonebookVersionCounters = false; 858 if (isNameMatchTarget(name, PB)) 859 needSendPhonebookVersionCounters = checkPbapFeatureSupport(folderVersionCounterbitMask); 860 861 // In such case, PCE only want the number of index. 862 // So response not contain any Body header. 863 if (mNeedPhonebookSize) { 864 if (D) Log.d(TAG, "Need Phonebook size in response header."); 865 mNeedPhonebookSize = false; 866 867 byte[] pbsize = new byte[2]; 868 869 pbsize[0] = (byte)((size / 256) & 0xff);// HIGH VALUE 870 pbsize[1] = (byte)((size % 256) & 0xff);// LOW VALUE 871 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID, 872 ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize); 873 874 if (mNeedNewMissedCallsNum) { 875 mNeedNewMissedCallsNum = false; 876 int nmnum = 0; 877 ContentResolver contentResolver; 878 contentResolver = mContext.getContentResolver(); 879 880 Cursor c = contentResolver.query( 881 Calls.CONTENT_URI, 882 null, 883 Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND " + android.provider.CallLog.Calls.NEW + " = 1", 884 null, 885 Calls.DEFAULT_SORT_ORDER); 886 887 if (c != null) { 888 nmnum = c.getCount(); 889 c.close(); 890 } 891 892 nmnum = nmnum > 0 ? nmnum : 0; 893 misnum[0] = (byte)nmnum; 894 if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true, num= " + nmnum); 895 } 896 897 if (checkPbapFeatureSupport(databaseIdentifierBitMask)) { 898 setDbCounters(ap); 899 } 900 if (needSendPhonebookVersionCounters) { 901 setFolderVersionCounters(ap); 902 } 903 if (needSendCallHistoryVersionCounters) { 904 setCallversionCounters(ap, appParamValue); 905 } 906 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 907 908 if (D) Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size); 909 910 return pushHeader(op, reply); 911 } 912 913 // Only apply to "mch" download/listing. 914 // NewMissedCalls is used only in the response, together with Body 915 // header. 916 if (mNeedNewMissedCallsNum) { 917 if (D) Log.d(TAG, "Need new missed call num in response header."); 918 mNeedNewMissedCallsNum = false; 919 int nmnum = 0; 920 ContentResolver contentResolver; 921 contentResolver = mContext.getContentResolver(); 922 923 Cursor c = contentResolver.query( 924 Calls.CONTENT_URI, 925 null, 926 Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND " + android.provider.CallLog.Calls.NEW + " = 1", 927 null, 928 Calls.DEFAULT_SORT_ORDER); 929 930 if (c != null) { 931 nmnum = c.getCount(); 932 c.close(); 933 } 934 935 nmnum = nmnum > 0 ? nmnum : 0; 936 misnum[0] = (byte)nmnum; 937 if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true, num= " + nmnum); 938 939 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID, 940 ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum); 941 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 942 if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true, num= " 943 + nmnum); 944 945 // Only Specifies the headers, not write for now, will write to PCE 946 // together with Body 947 try { 948 op.sendHeaders(reply); 949 } catch (IOException e) { 950 Log.e(TAG, e.toString()); 951 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 952 } 953 } 954 955 if (checkPbapFeatureSupport(databaseIdentifierBitMask)) { 956 setDbCounters(ap); 957 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 958 try { 959 op.sendHeaders(reply); 960 } catch (IOException e) { 961 Log.e(TAG, e.toString()); 962 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 963 } 964 } 965 966 if (needSendPhonebookVersionCounters) { 967 setFolderVersionCounters(ap); 968 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 969 try { 970 op.sendHeaders(reply); 971 } catch (IOException e) { 972 Log.e(TAG, e.toString()); 973 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 974 } 975 } 976 977 if (needSendCallHistoryVersionCounters) { 978 setCallversionCounters(ap, appParamValue); 979 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 980 try { 981 op.sendHeaders(reply); 982 } catch (IOException e) { 983 Log.e(TAG, e.toString()); 984 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 985 } 986 } 987 988 return NEED_SEND_BODY; 989 } 990 991 private final int pullVcardListing(byte[] appParam, AppParamValue appParamValue, 992 HeaderSet reply, Operation op, String name) { 993 String searchAttr = appParamValue.searchAttr.trim(); 994 995 if (searchAttr == null || searchAttr.length() == 0) { 996 // If searchAttr is not set by PCE, set default value per spec. 997 appParamValue.searchAttr = "0"; 998 if (D) Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default"); 999 } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) { 1000 Log.w(TAG, "search attr not supported"); 1001 if (searchAttr.equals("2")) { 1002 // search by sound is not supported currently 1003 Log.w(TAG, "do not support search by sound"); 1004 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 1005 } 1006 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 1007 } else { 1008 Log.i(TAG, "searchAttr is valid: " + searchAttr); 1009 } 1010 1011 int size = mVcardManager.getPhonebookSize(appParamValue.needTag); 1012 int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name); 1013 if (needSendBody != NEED_SEND_BODY) { 1014 op.noBodyHeader(); 1015 return needSendBody; 1016 } 1017 1018 if (size == 0) { 1019 if (D) Log.d(TAG, "PhonebookSize is 0, return."); 1020 return ResponseCodes.OBEX_HTTP_OK; 1021 } 1022 1023 String orderPara = appParamValue.order.trim(); 1024 if (TextUtils.isEmpty(orderPara)) { 1025 // If order parameter is not set by PCE, set default value per spec. 1026 orderPara = "0"; 1027 if (D) Log.d(TAG, "Order parameter is not set by PCE. " + 1028 "Assume order by 'Indexed' by default"); 1029 } else if (!orderPara.equals("0") && !orderPara.equals("1")) { 1030 if (D) Log.d(TAG, "Order parameter is not supported: " + appParamValue.order); 1031 if (orderPara.equals("2")) { 1032 // Order by sound is not supported currently 1033 Log.w(TAG, "Do not support order by sound"); 1034 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 1035 } 1036 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 1037 } else { 1038 Log.i(TAG, "Order parameter is valid: " + orderPara); 1039 } 1040 1041 if (orderPara.equals("0")) { 1042 mOrderBy = ORDER_BY_INDEXED; 1043 } else if (orderPara.equals("1")) { 1044 mOrderBy = ORDER_BY_ALPHABETICAL; 1045 } 1046 1047 return sendVcardListingXml(appParamValue, op, needSendBody, size); 1048 } 1049 1050 private final int pullVcardEntry(byte[] appParam, AppParamValue appParamValue, Operation op, 1051 HeaderSet reply, final String name, final String current_path) { 1052 if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) { 1053 if (D) Log.d(TAG, "Name is Null, or the length of name < 5 !"); 1054 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 1055 } 1056 String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1); 1057 int intIndex = 0; 1058 if (strIndex.trim().length() != 0) { 1059 try { 1060 intIndex = Integer.parseInt(strIndex); 1061 } catch (NumberFormatException e) { 1062 Log.e(TAG, "catch number format exception " + e.toString()); 1063 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 1064 } 1065 } 1066 1067 int size = mVcardManager.getPhonebookSize(appParamValue.needTag); 1068 int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name); 1069 if (size == 0) { 1070 if (D) Log.d(TAG, "PhonebookSize is 0, return."); 1071 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 1072 } 1073 1074 boolean vcard21 = appParamValue.vcard21; 1075 if (appParamValue.needTag == 0) { 1076 Log.w(TAG, "wrong path!"); 1077 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 1078 } else if (appParamValue.needTag == ContentType.PHONEBOOK) { 1079 if (intIndex < 0 || intIndex >= size) { 1080 Log.w(TAG, "The requested vcard is not acceptable! name= " + name); 1081 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 1082 } else if (intIndex == 0) { 1083 // For PB_PATH, 0.vcf is the phone number of this phone. 1084 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, null); 1085 return pushBytes(op, ownerVcard); 1086 } else { 1087 return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null, 1088 mOrderBy, appParamValue.ignorefilter, appParamValue.propertySelector); 1089 } 1090 } else { 1091 if (intIndex <= 0 || intIndex > size) { 1092 Log.w(TAG, "The requested vcard is not acceptable! name= " + name); 1093 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 1094 } 1095 // For others (ich/och/cch/mch), 0.vcf is meaningless, and must 1096 // begin from 1.vcf 1097 if (intIndex >= 1) { 1098 return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op, 1099 intIndex, intIndex, vcard21, needSendBody, size, appParamValue.ignorefilter, 1100 appParamValue.propertySelector, appParamValue.vCardSelector, 1101 appParamValue.vCardSelectorOperator, mVcardSelector); 1102 } 1103 } 1104 return ResponseCodes.OBEX_HTTP_OK; 1105 } 1106 1107 private final int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, 1108 Operation op, final String name) { 1109 // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C 1110 if (name != null) { 1111 int dotIndex = name.indexOf("."); 1112 String vcf = "vcf"; 1113 if (dotIndex >= 0 && dotIndex <= name.length()) { 1114 if (name.regionMatches(dotIndex + 1, vcf, 0, vcf.length()) == false) { 1115 Log.w(TAG, "name is not .vcf"); 1116 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 1117 } 1118 } 1119 } // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C 1120 1121 int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag); 1122 int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op, name); 1123 if (needSendBody != NEED_SEND_BODY) { 1124 op.noBodyHeader(); 1125 return needSendBody; 1126 } 1127 1128 if (pbSize == 0) { 1129 if (D) Log.d(TAG, "PhonebookSize is 0, return."); 1130 return ResponseCodes.OBEX_HTTP_OK; 1131 } 1132 1133 int requestSize = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount 1134 : pbSize; 1135 int startPoint = appParamValue.listStartOffset; 1136 if (startPoint < 0 || startPoint >= pbSize) { 1137 Log.w(TAG, "listStartOffset is not correct! " + startPoint); 1138 return ResponseCodes.OBEX_HTTP_OK; 1139 } 1140 1141 // Limit the number of call log to CALLLOG_NUM_LIMIT 1142 if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) { 1143 if (requestSize > CALLLOG_NUM_LIMIT) { 1144 requestSize = CALLLOG_NUM_LIMIT; 1145 } 1146 } 1147 1148 int endPoint = startPoint + requestSize - 1; 1149 if (endPoint > pbSize - 1) { 1150 endPoint = pbSize - 1; 1151 } 1152 if (D) Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" + 1153 startPoint + " endPoint=" + endPoint); 1154 1155 boolean vcard21 = appParamValue.vcard21; 1156 if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) { 1157 if (startPoint == 0) { 1158 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, null); 1159 if (endPoint == 0) { 1160 return pushBytes(op, ownerVcard); 1161 } else { 1162 return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21, 1163 ownerVcard, needSendBody, pbSize, appParamValue.ignorefilter, 1164 appParamValue.propertySelector, appParamValue.vCardSelector, 1165 appParamValue.vCardSelectorOperator, mVcardSelector); 1166 } 1167 } else { 1168 return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint, 1169 vcard21, null, needSendBody, pbSize, appParamValue.ignorefilter, 1170 appParamValue.propertySelector, appParamValue.vCardSelector, 1171 appParamValue.vCardSelectorOperator, mVcardSelector); 1172 } 1173 } else { 1174 return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op, 1175 startPoint + 1, endPoint + 1, vcard21, needSendBody, pbSize, 1176 appParamValue.ignorefilter, appParamValue.propertySelector, 1177 appParamValue.vCardSelector, appParamValue.vCardSelectorOperator, 1178 mVcardSelector); 1179 } 1180 } 1181 1182 public static boolean closeStream(final OutputStream out, final Operation op) { 1183 boolean returnvalue = true; 1184 try { 1185 if (out != null) { 1186 out.close(); 1187 } 1188 } catch (IOException e) { 1189 Log.e(TAG, "outputStream close failed" + e.toString()); 1190 returnvalue = false; 1191 } 1192 try { 1193 if (op != null) { 1194 op.close(); 1195 } 1196 } catch (IOException e) { 1197 Log.e(TAG, "operation close failed" + e.toString()); 1198 returnvalue = false; 1199 } 1200 return returnvalue; 1201 } 1202 1203 // Reserved for future use. In case PSE challenge PCE and PCE input wrong 1204 // session key. 1205 public final void onAuthenticationFailure(final byte[] userName) { 1206 } 1207 1208 public static final String createSelectionPara(final int type) { 1209 String selection = null; 1210 switch (type) { 1211 case ContentType.INCOMING_CALL_HISTORY: 1212 selection = "(" + Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE + " OR " 1213 + Calls.TYPE + "=" + CallLog.Calls.REJECTED_TYPE + ")"; 1214 break; 1215 case ContentType.OUTGOING_CALL_HISTORY: 1216 selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE; 1217 break; 1218 case ContentType.MISSED_CALL_HISTORY: 1219 selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE; 1220 break; 1221 default: 1222 break; 1223 } 1224 if (V) Log.v(TAG, "Call log selection: " + selection); 1225 return selection; 1226 } 1227 1228 /** 1229 * XML encode special characters in the name field 1230 */ 1231 private void xmlEncode(String name, StringBuilder result) { 1232 if (name == null) { 1233 return; 1234 } 1235 1236 final StringCharacterIterator iterator = new StringCharacterIterator(name); 1237 char character = iterator.current(); 1238 while (character != CharacterIterator.DONE ){ 1239 if (character == '<') { 1240 result.append("<"); 1241 } 1242 else if (character == '>') { 1243 result.append(">"); 1244 } 1245 else if (character == '\"') { 1246 result.append("""); 1247 } 1248 else if (character == '\'') { 1249 result.append("'"); 1250 } 1251 else if (character == '&') { 1252 result.append("&"); 1253 } 1254 else { 1255 // The char is not a special one, add it to the result as is 1256 result.append(character); 1257 } 1258 character = iterator.next(); 1259 } 1260 } 1261 1262 private void writeVCardEntry(int vcfIndex, String name, StringBuilder result) { 1263 result.append("<card handle=\""); 1264 result.append(vcfIndex); 1265 result.append(".vcf\" name=\""); 1266 xmlEncode(name, result); 1267 result.append("\"/>"); 1268 } 1269 1270 private void notifyUpdateWakeLock() { 1271 Message msg = Message.obtain(mCallback); 1272 msg.what = BluetoothPbapService.MSG_ACQUIRE_WAKE_LOCK; 1273 msg.sendToTarget(); 1274 } 1275 1276 public static final void logHeader(HeaderSet hs) { 1277 Log.v(TAG, "Dumping HeaderSet " + hs.toString()); 1278 try { 1279 1280 Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT)); 1281 Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME)); 1282 Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE)); 1283 Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH)); 1284 Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601)); 1285 Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE)); 1286 Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION)); 1287 Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET)); 1288 Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP)); 1289 Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO)); 1290 Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS)); 1291 Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER)); 1292 } catch (IOException e) { 1293 Log.e(TAG, "dump HeaderSet error " + e); 1294 } 1295 } 1296 1297 private void setDbCounters(ApplicationParameter ap) { 1298 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.DATABASEIDENTIFIER_TAGID, 1299 ApplicationParameter.TRIPLET_LENGTH.DATABASEIDENTIFIER_LENGTH, 1300 getDatabaseIdentifier()); 1301 } 1302 1303 private void setFolderVersionCounters(ApplicationParameter ap) { 1304 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID, 1305 ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH, 1306 getPBPrimaryFolderVersion()); 1307 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID, 1308 ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH, 1309 getPBSecondaryFolderVersion()); 1310 } 1311 1312 private void setCallversionCounters(ApplicationParameter ap, AppParamValue appParamValue) { 1313 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID, 1314 ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH, 1315 appParamValue.callHistoryVersionCounter); 1316 1317 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID, 1318 ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH, 1319 appParamValue.callHistoryVersionCounter); 1320 } 1321 1322 private byte[] getDatabaseIdentifier() { 1323 mDatabaseIdentifierHigh = 0; 1324 mDatabaseIdentifierLow = mService.getDbIdentifier(); 1325 if (mDatabaseIdentifierLow != INVALID_VALUE_PARAMETER 1326 && mDatabaseIdentifierHigh != INVALID_VALUE_PARAMETER) { 1327 ByteBuffer ret = ByteBuffer.allocate(16); 1328 ret.putLong(mDatabaseIdentifierHigh); 1329 ret.putLong(mDatabaseIdentifierLow); 1330 return ret.array(); 1331 } else { 1332 return null; 1333 } 1334 } 1335 1336 private byte[] getPBPrimaryFolderVersion() { 1337 long primaryVcMsb = 0; 1338 ByteBuffer pvc = ByteBuffer.allocate(16); 1339 pvc.putLong(primaryVcMsb); 1340 1341 Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.primaryVersionCounter); 1342 pvc.putLong(BluetoothPbapUtils.primaryVersionCounter); 1343 return pvc.array(); 1344 } 1345 1346 private byte[] getPBSecondaryFolderVersion() { 1347 long secondaryVcMsb = 0; 1348 ByteBuffer svc = ByteBuffer.allocate(16); 1349 svc.putLong(secondaryVcMsb); 1350 1351 Log.d(TAG, "secondaryVersionCounter is " + BluetoothPbapUtils.secondaryVersionCounter); 1352 svc.putLong(BluetoothPbapUtils.secondaryVersionCounter); 1353 return svc.array(); 1354 } 1355 1356 private boolean checkPbapFeatureSupport(long featureBitMask) { 1357 Log.d(TAG, "checkPbapFeatureSupport featureBitMask is " + featureBitMask); 1358 return ((ByteBuffer.wrap(connAppParamValue.supportedFeature).getInt() & featureBitMask) 1359 != 0); 1360 } 1361} 1362