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 // query the number, to get the names 821 ArrayList<String> names = 822 mVcardManager.getContactNamesByNumber(appParamValue.searchValue); 823 if (mOrderBy == ORDER_BY_ALPHABETICAL) Collections.sort(names); 824 for (int i = 0; i < names.size(); i++) { 825 compareValue = names.get(i).trim(); 826 if (D) { 827 Log.d(TAG, "compareValue=" + compareValue); 828 } 829 for (int pos = appParamValue.listStartOffset; 830 pos < listSize && itemsFound < requestSize; pos++) { 831 currentValue = nameList.get(pos); 832 if (V) { 833 Log.d(TAG, "currentValue=" + currentValue); 834 } 835 if (currentValue.equals(compareValue)) { 836 itemsFound++; 837 if (currentValue.contains(",")) { 838 currentValue = currentValue.substring(0, currentValue.lastIndexOf(',')); 839 } 840 writeVCardEntry(pos, currentValue, result); 841 } 842 } 843 if (itemsFound >= requestSize) { 844 break; 845 } 846 } 847 } else { 848 if (appParamValue.searchValue != null) { 849 compareValue = appParamValue.searchValue.trim().toLowerCase(); 850 } 851 for (int pos = appParamValue.listStartOffset; 852 pos < listSize && itemsFound < requestSize; pos++) { 853 currentValue = nameList.get(pos); 854 if (currentValue.contains(",")) { 855 currentValue = currentValue.substring(0, currentValue.lastIndexOf(',')); 856 } 857 858 if (appParamValue.searchValue.isEmpty() || ((currentValue.toLowerCase()).startsWith( 859 compareValue))) { 860 itemsFound++; 861 writeVCardEntry(pos, currentValue, result); 862 } 863 } 864 } 865 return itemsFound; 866 } 867 868 /** 869 * Function to send obex header back to client such as get phonebook size 870 * request 871 */ pushHeader(final Operation op, final HeaderSet reply)872 private int pushHeader(final Operation op, final HeaderSet reply) { 873 OutputStream outputStream = null; 874 875 if (D) { 876 Log.d(TAG, "Push Header"); 877 } 878 if (D) { 879 Log.d(TAG, reply.toString()); 880 } 881 882 int pushResult = ResponseCodes.OBEX_HTTP_OK; 883 try { 884 op.sendHeaders(reply); 885 outputStream = op.openOutputStream(); 886 outputStream.flush(); 887 } catch (IOException e) { 888 Log.e(TAG, e.toString()); 889 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 890 } finally { 891 if (!closeStream(outputStream, op)) { 892 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 893 } 894 } 895 return pushResult; 896 } 897 898 /** Function to send vcard data to client */ pushBytes(Operation op, final String vcardString)899 private int pushBytes(Operation op, final String vcardString) { 900 if (vcardString == null) { 901 Log.w(TAG, "vcardString is null!"); 902 return ResponseCodes.OBEX_HTTP_OK; 903 } 904 905 OutputStream outputStream = null; 906 int pushResult = ResponseCodes.OBEX_HTTP_OK; 907 try { 908 outputStream = op.openOutputStream(); 909 outputStream.write(vcardString.getBytes()); 910 if (V) { 911 Log.v(TAG, "Send Data complete!"); 912 } 913 } catch (IOException e) { 914 Log.e(TAG, "open/write outputstrem failed" + e.toString()); 915 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 916 } 917 918 if (!closeStream(outputStream, op)) { 919 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 920 } 921 922 return pushResult; 923 } 924 handleAppParaForResponse(AppParamValue appParamValue, int size, HeaderSet reply, Operation op, String name)925 private int handleAppParaForResponse(AppParamValue appParamValue, int size, HeaderSet reply, 926 Operation op, String name) { 927 byte[] misnum = new byte[1]; 928 ApplicationParameter ap = new ApplicationParameter(); 929 boolean needSendCallHistoryVersionCounters = false; 930 if (isNameMatchTarget(name, MCH) || isNameMatchTarget(name, ICH) || isNameMatchTarget(name, 931 OCH) || isNameMatchTarget(name, CCH)) { 932 needSendCallHistoryVersionCounters = 933 checkPbapFeatureSupport(mFolderVersionCounterbitMask); 934 } 935 boolean needSendPhonebookVersionCounters = false; 936 if (isNameMatchTarget(name, PB)) { 937 needSendPhonebookVersionCounters = 938 checkPbapFeatureSupport(mFolderVersionCounterbitMask); 939 } 940 941 // In such case, PCE only want the number of index. 942 // So response not contain any Body header. 943 if (mNeedPhonebookSize) { 944 if (D) { 945 Log.d(TAG, "Need Phonebook size in response header."); 946 } 947 mNeedPhonebookSize = false; 948 949 byte[] pbsize = new byte[2]; 950 951 pbsize[0] = (byte) ((size / 256) & 0xff); // HIGH VALUE 952 pbsize[1] = (byte) ((size % 256) & 0xff); // LOW VALUE 953 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID, 954 ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize); 955 956 if (mNeedNewMissedCallsNum) { 957 mNeedNewMissedCallsNum = false; 958 int nmnum = 0; 959 ContentResolver contentResolver; 960 contentResolver = mContext.getContentResolver(); 961 962 Cursor c = contentResolver.query(Calls.CONTENT_URI, null, 963 Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND " 964 + android.provider.CallLog.Calls.NEW + " = 1", null, 965 Calls.DEFAULT_SORT_ORDER); 966 967 if (c != null) { 968 nmnum = c.getCount(); 969 c.close(); 970 } 971 972 nmnum = nmnum > 0 ? nmnum : 0; 973 misnum[0] = (byte) nmnum; 974 if (D) { 975 Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true, num= " 976 + nmnum); 977 } 978 } 979 980 if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) { 981 setDbCounters(ap); 982 } 983 if (needSendPhonebookVersionCounters) { 984 setFolderVersionCounters(ap); 985 } 986 if (needSendCallHistoryVersionCounters) { 987 setCallversionCounters(ap, appParamValue); 988 } 989 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 990 991 if (D) { 992 Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size); 993 } 994 995 return pushHeader(op, reply); 996 } 997 998 // Only apply to "mch" download/listing. 999 // NewMissedCalls is used only in the response, together with Body 1000 // header. 1001 if (mNeedNewMissedCallsNum) { 1002 if (D) { 1003 Log.d(TAG, "Need new missed call num in response header."); 1004 } 1005 mNeedNewMissedCallsNum = false; 1006 int nmnum = 0; 1007 ContentResolver contentResolver; 1008 contentResolver = mContext.getContentResolver(); 1009 1010 Cursor c = contentResolver.query(Calls.CONTENT_URI, null, 1011 Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND " 1012 + android.provider.CallLog.Calls.NEW + " = 1", null, 1013 Calls.DEFAULT_SORT_ORDER); 1014 1015 if (c != null) { 1016 nmnum = c.getCount(); 1017 c.close(); 1018 } 1019 1020 nmnum = nmnum > 0 ? nmnum : 0; 1021 misnum[0] = (byte) nmnum; 1022 if (D) { 1023 Log.d(TAG, 1024 "handleAppParaForResponse(): mNeedNewMissedCallsNum=true, num= " + nmnum); 1025 } 1026 1027 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID, 1028 ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum); 1029 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 1030 if (D) { 1031 Log.d(TAG, 1032 "handleAppParaForResponse(): mNeedNewMissedCallsNum=true, num= " + nmnum); 1033 } 1034 1035 // Only Specifies the headers, not write for now, will write to PCE 1036 // together with Body 1037 try { 1038 op.sendHeaders(reply); 1039 } catch (IOException e) { 1040 Log.e(TAG, e.toString()); 1041 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 1042 } 1043 } 1044 1045 if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) { 1046 setDbCounters(ap); 1047 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 1048 try { 1049 op.sendHeaders(reply); 1050 } catch (IOException e) { 1051 Log.e(TAG, e.toString()); 1052 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 1053 } 1054 } 1055 1056 if (needSendPhonebookVersionCounters) { 1057 setFolderVersionCounters(ap); 1058 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 1059 try { 1060 op.sendHeaders(reply); 1061 } catch (IOException e) { 1062 Log.e(TAG, e.toString()); 1063 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 1064 } 1065 } 1066 1067 if (needSendCallHistoryVersionCounters) { 1068 setCallversionCounters(ap, appParamValue); 1069 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 1070 try { 1071 op.sendHeaders(reply); 1072 } catch (IOException e) { 1073 Log.e(TAG, e.toString()); 1074 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 1075 } 1076 } 1077 1078 return NEED_SEND_BODY; 1079 } 1080 pullVcardListing(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, Operation op, String name)1081 private int pullVcardListing(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, 1082 Operation op, String name) { 1083 String searchAttr = appParamValue.searchAttr.trim(); 1084 1085 if (searchAttr == null || searchAttr.length() == 0) { 1086 // If searchAttr is not set by PCE, set default value per spec. 1087 appParamValue.searchAttr = "0"; 1088 if (D) { 1089 Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default"); 1090 } 1091 } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) { 1092 Log.w(TAG, "search attr not supported"); 1093 if (searchAttr.equals("2")) { 1094 // search by sound is not supported currently 1095 Log.w(TAG, "do not support search by sound"); 1096 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 1097 } 1098 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 1099 } else { 1100 Log.i(TAG, "searchAttr is valid: " + searchAttr); 1101 } 1102 1103 int size = mVcardManager.getPhonebookSize(appParamValue.needTag); 1104 int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name); 1105 if (needSendBody != NEED_SEND_BODY) { 1106 op.noBodyHeader(); 1107 return needSendBody; 1108 } 1109 1110 if (size == 0) { 1111 if (D) { 1112 Log.d(TAG, "PhonebookSize is 0, return."); 1113 } 1114 return ResponseCodes.OBEX_HTTP_OK; 1115 } 1116 1117 String orderPara = appParamValue.order.trim(); 1118 if (TextUtils.isEmpty(orderPara)) { 1119 // If order parameter is not set by PCE, set default value per spec. 1120 orderPara = "0"; 1121 if (D) { 1122 Log.d(TAG, "Order parameter is not set by PCE. " 1123 + "Assume order by 'Indexed' by default"); 1124 } 1125 } else if (!orderPara.equals("0") && !orderPara.equals("1")) { 1126 if (D) { 1127 Log.d(TAG, "Order parameter is not supported: " + appParamValue.order); 1128 } 1129 if (orderPara.equals("2")) { 1130 // Order by sound is not supported currently 1131 Log.w(TAG, "Do not support order by sound"); 1132 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 1133 } 1134 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 1135 } else { 1136 Log.i(TAG, "Order parameter is valid: " + orderPara); 1137 } 1138 1139 if (orderPara.equals("0")) { 1140 mOrderBy = ORDER_BY_INDEXED; 1141 } else if (orderPara.equals("1")) { 1142 mOrderBy = ORDER_BY_ALPHABETICAL; 1143 } 1144 1145 return sendVcardListingXml(appParamValue, op, needSendBody, size); 1146 } 1147 pullVcardEntry(byte[] appParam, AppParamValue appParamValue, Operation op, HeaderSet reply, final String name, final String currentPath)1148 private int pullVcardEntry(byte[] appParam, AppParamValue appParamValue, Operation op, 1149 HeaderSet reply, final String name, final String currentPath) { 1150 if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) { 1151 if (D) { 1152 Log.d(TAG, "Name is Null, or the length of name < 5 !"); 1153 } 1154 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 1155 } 1156 String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1); 1157 int intIndex = 0; 1158 if (strIndex.trim().length() != 0) { 1159 try { 1160 intIndex = Integer.parseInt(strIndex); 1161 } catch (NumberFormatException e) { 1162 Log.e(TAG, "catch number format exception " + e.toString()); 1163 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 1164 } 1165 } 1166 1167 int size = mVcardManager.getPhonebookSize(appParamValue.needTag); 1168 int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name); 1169 if (size == 0) { 1170 if (D) { 1171 Log.d(TAG, "PhonebookSize is 0, return."); 1172 } 1173 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 1174 } 1175 1176 boolean vcard21 = appParamValue.vcard21; 1177 if (appParamValue.needTag == 0) { 1178 Log.w(TAG, "wrong path!"); 1179 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 1180 } else if (appParamValue.needTag == ContentType.PHONEBOOK) { 1181 if (intIndex < 0 || intIndex >= size) { 1182 Log.w(TAG, "The requested vcard is not acceptable! name= " + name); 1183 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 1184 } else if (intIndex == 0) { 1185 // For PB_PATH, 0.vcf is the phone number of this phone. 1186 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, 1187 appParamValue.ignorefilter ? null : appParamValue.propertySelector); 1188 return pushBytes(op, ownerVcard); 1189 } else { 1190 return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null, 1191 mOrderBy, appParamValue.ignorefilter, appParamValue.propertySelector); 1192 } 1193 } else { 1194 if (intIndex <= 0 || intIndex > size) { 1195 Log.w(TAG, "The requested vcard is not acceptable! name= " + name); 1196 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 1197 } 1198 // For others (ich/och/cch/mch), 0.vcf is meaningless, and must 1199 // begin from 1.vcf 1200 if (intIndex >= 1) { 1201 return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op, 1202 intIndex, intIndex, vcard21, needSendBody, size, appParamValue.ignorefilter, 1203 appParamValue.propertySelector, appParamValue.vCardSelector, 1204 appParamValue.vCardSelectorOperator, mVcardSelector); 1205 } 1206 } 1207 return ResponseCodes.OBEX_HTTP_OK; 1208 } 1209 pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, Operation op, final String name)1210 private int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, 1211 Operation op, final String name) { 1212 // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C 1213 if (name != null) { 1214 int dotIndex = name.indexOf("."); 1215 String vcf = "vcf"; 1216 if (dotIndex >= 0 && dotIndex <= name.length()) { 1217 if (!name.regionMatches(dotIndex + 1, vcf, 0, vcf.length())) { 1218 Log.w(TAG, "name is not .vcf"); 1219 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 1220 } 1221 } 1222 } // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C 1223 1224 int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag); 1225 int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op, name); 1226 if (needSendBody != NEED_SEND_BODY) { 1227 op.noBodyHeader(); 1228 return needSendBody; 1229 } 1230 1231 if (pbSize == 0) { 1232 if (D) { 1233 Log.d(TAG, "PhonebookSize is 0, return."); 1234 } 1235 return ResponseCodes.OBEX_HTTP_OK; 1236 } 1237 1238 int requestSize = 1239 pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize; 1240 int startPoint = appParamValue.listStartOffset; 1241 if (startPoint < 0 || startPoint >= pbSize) { 1242 Log.w(TAG, "listStartOffset is not correct! " + startPoint); 1243 return ResponseCodes.OBEX_HTTP_OK; 1244 } 1245 1246 // Limit the number of call log to CALLLOG_NUM_LIMIT 1247 if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) { 1248 if (requestSize > CALLLOG_NUM_LIMIT) { 1249 requestSize = CALLLOG_NUM_LIMIT; 1250 } 1251 } 1252 1253 int endPoint = startPoint + requestSize - 1; 1254 if (endPoint > pbSize - 1) { 1255 endPoint = pbSize - 1; 1256 } 1257 if (D) { 1258 Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" + startPoint 1259 + " endPoint=" + endPoint); 1260 } 1261 1262 boolean vcard21 = appParamValue.vcard21; 1263 if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) { 1264 if (startPoint == 0) { 1265 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, 1266 appParamValue.ignorefilter ? null : appParamValue.propertySelector); 1267 if (endPoint == 0) { 1268 return pushBytes(op, ownerVcard); 1269 } else { 1270 return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21, 1271 ownerVcard, needSendBody, pbSize, appParamValue.ignorefilter, 1272 appParamValue.propertySelector, appParamValue.vCardSelector, 1273 appParamValue.vCardSelectorOperator, mVcardSelector); 1274 } 1275 } else { 1276 return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint, 1277 vcard21, null, needSendBody, pbSize, appParamValue.ignorefilter, 1278 appParamValue.propertySelector, appParamValue.vCardSelector, 1279 appParamValue.vCardSelectorOperator, mVcardSelector); 1280 } 1281 } else { 1282 return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op, 1283 startPoint + 1, endPoint + 1, vcard21, needSendBody, pbSize, 1284 appParamValue.ignorefilter, appParamValue.propertySelector, 1285 appParamValue.vCardSelector, appParamValue.vCardSelectorOperator, 1286 mVcardSelector); 1287 } 1288 } 1289 closeStream(final OutputStream out, final Operation op)1290 public static boolean closeStream(final OutputStream out, final Operation op) { 1291 boolean returnvalue = true; 1292 try { 1293 if (out != null) { 1294 out.close(); 1295 } 1296 } catch (IOException e) { 1297 Log.e(TAG, "outputStream close failed" + e.toString()); 1298 returnvalue = false; 1299 } 1300 try { 1301 if (op != null) { 1302 op.close(); 1303 } 1304 } catch (IOException e) { 1305 Log.e(TAG, "operation close failed" + e.toString()); 1306 returnvalue = false; 1307 } 1308 return returnvalue; 1309 } 1310 1311 // Reserved for future use. In case PSE challenge PCE and PCE input wrong 1312 // session key. 1313 @Override onAuthenticationFailure(final byte[] userName)1314 public final void onAuthenticationFailure(final byte[] userName) { 1315 } 1316 createSelectionPara(final int type)1317 public static final String createSelectionPara(final int type) { 1318 String selection = null; 1319 switch (type) { 1320 case ContentType.INCOMING_CALL_HISTORY: 1321 selection = 1322 "(" + Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE + " OR " + Calls.TYPE 1323 + "=" + CallLog.Calls.REJECTED_TYPE + ")"; 1324 break; 1325 case ContentType.OUTGOING_CALL_HISTORY: 1326 selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE; 1327 break; 1328 case ContentType.MISSED_CALL_HISTORY: 1329 selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE; 1330 break; 1331 default: 1332 break; 1333 } 1334 if (V) { 1335 Log.v(TAG, "Call log selection: " + selection); 1336 } 1337 return selection; 1338 } 1339 1340 /** 1341 * XML encode special characters in the name field 1342 */ xmlEncode(String name, StringBuilder result)1343 private void xmlEncode(String name, StringBuilder result) { 1344 if (name == null) { 1345 return; 1346 } 1347 1348 final StringCharacterIterator iterator = new StringCharacterIterator(name); 1349 char character = iterator.current(); 1350 while (character != CharacterIterator.DONE) { 1351 if (character == '<') { 1352 result.append("<"); 1353 } else if (character == '>') { 1354 result.append(">"); 1355 } else if (character == '\"') { 1356 result.append("""); 1357 } else if (character == '\'') { 1358 result.append("'"); 1359 } else if (character == '&') { 1360 result.append("&"); 1361 } else { 1362 // The char is not a special one, add it to the result as is 1363 result.append(character); 1364 } 1365 character = iterator.next(); 1366 } 1367 } 1368 writeVCardEntry(int vcfIndex, String name, StringBuilder result)1369 private void writeVCardEntry(int vcfIndex, String name, StringBuilder result) { 1370 result.append("<card handle=\""); 1371 result.append(vcfIndex); 1372 result.append(".vcf\" name=\""); 1373 xmlEncode(name, result); 1374 result.append("\"/>"); 1375 } 1376 notifyUpdateWakeLock()1377 private void notifyUpdateWakeLock() { 1378 Message msg = Message.obtain(mCallback); 1379 msg.what = BluetoothPbapService.MSG_ACQUIRE_WAKE_LOCK; 1380 msg.sendToTarget(); 1381 } 1382 logHeader(HeaderSet hs)1383 public static final void logHeader(HeaderSet hs) { 1384 Log.v(TAG, "Dumping HeaderSet " + hs.toString()); 1385 try { 1386 1387 Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT)); 1388 Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME)); 1389 Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE)); 1390 Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH)); 1391 Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601)); 1392 Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE)); 1393 Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION)); 1394 Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET)); 1395 Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP)); 1396 Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO)); 1397 Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS)); 1398 Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER)); 1399 } catch (IOException e) { 1400 Log.e(TAG, "dump HeaderSet error " + e); 1401 } 1402 } 1403 setDbCounters(ApplicationParameter ap)1404 private void setDbCounters(ApplicationParameter ap) { 1405 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.DATABASEIDENTIFIER_TAGID, 1406 ApplicationParameter.TRIPLET_LENGTH.DATABASEIDENTIFIER_LENGTH, 1407 getDatabaseIdentifier()); 1408 } 1409 setFolderVersionCounters(ApplicationParameter ap)1410 private void setFolderVersionCounters(ApplicationParameter ap) { 1411 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID, 1412 ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH, 1413 getPBPrimaryFolderVersion()); 1414 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID, 1415 ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH, 1416 getPBSecondaryFolderVersion()); 1417 } 1418 setCallversionCounters(ApplicationParameter ap, AppParamValue appParamValue)1419 private void setCallversionCounters(ApplicationParameter ap, AppParamValue appParamValue) { 1420 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID, 1421 ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH, 1422 appParamValue.callHistoryVersionCounter); 1423 1424 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID, 1425 ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH, 1426 appParamValue.callHistoryVersionCounter); 1427 } 1428 getDatabaseIdentifier()1429 private byte[] getDatabaseIdentifier() { 1430 mDatabaseIdentifierHigh = 0; 1431 mDatabaseIdentifierLow = BluetoothPbapUtils.sDbIdentifier.get(); 1432 if (mDatabaseIdentifierLow != INVALID_VALUE_PARAMETER 1433 && mDatabaseIdentifierHigh != INVALID_VALUE_PARAMETER) { 1434 ByteBuffer ret = ByteBuffer.allocate(16); 1435 ret.putLong(mDatabaseIdentifierHigh); 1436 ret.putLong(mDatabaseIdentifierLow); 1437 return ret.array(); 1438 } else { 1439 return null; 1440 } 1441 } 1442 getPBPrimaryFolderVersion()1443 private byte[] getPBPrimaryFolderVersion() { 1444 long primaryVcMsb = 0; 1445 ByteBuffer pvc = ByteBuffer.allocate(16); 1446 pvc.putLong(primaryVcMsb); 1447 1448 Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.sPrimaryVersionCounter); 1449 pvc.putLong(BluetoothPbapUtils.sPrimaryVersionCounter); 1450 return pvc.array(); 1451 } 1452 getPBSecondaryFolderVersion()1453 private byte[] getPBSecondaryFolderVersion() { 1454 long secondaryVcMsb = 0; 1455 ByteBuffer svc = ByteBuffer.allocate(16); 1456 svc.putLong(secondaryVcMsb); 1457 1458 Log.d(TAG, "secondaryVersionCounter is " + BluetoothPbapUtils.sSecondaryVersionCounter); 1459 svc.putLong(BluetoothPbapUtils.sSecondaryVersionCounter); 1460 return svc.array(); 1461 } 1462 checkPbapFeatureSupport(long featureBitMask)1463 private boolean checkPbapFeatureSupport(long featureBitMask) { 1464 Log.d(TAG, "checkPbapFeatureSupport featureBitMask is " + featureBitMask); 1465 return ((ByteBuffer.wrap(mConnAppParamValue.supportedFeature).getInt() & featureBitMask) 1466 != 0); 1467 } 1468 } 1469