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