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