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 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID, 990 ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum); 991 if (D) { 992 Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true, num= " 993 + nmnum); 994 } 995 } 996 997 if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) { 998 setDbCounters(ap); 999 } 1000 if (needSendPhonebookVersionCounters) { 1001 setFolderVersionCounters(ap); 1002 } 1003 if (needSendCallHistoryVersionCounters) { 1004 setCallversionCounters(ap, appParamValue); 1005 } 1006 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 1007 1008 if (D) { 1009 Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size); 1010 } 1011 1012 return pushHeader(op, reply); 1013 } 1014 1015 // Only apply to "mch" download/listing. 1016 // NewMissedCalls is used only in the response, together with Body 1017 // header. 1018 if (mNeedNewMissedCallsNum) { 1019 if (D) { 1020 Log.d(TAG, "Need new missed call num in response header."); 1021 } 1022 mNeedNewMissedCallsNum = false; 1023 int nmnum = 0; 1024 ContentResolver contentResolver; 1025 contentResolver = mContext.getContentResolver(); 1026 1027 Cursor c = contentResolver.query(Calls.CONTENT_URI, null, 1028 Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND " 1029 + android.provider.CallLog.Calls.NEW + " = 1", null, 1030 Calls.DEFAULT_SORT_ORDER); 1031 1032 if (c != null) { 1033 nmnum = c.getCount(); 1034 c.close(); 1035 } 1036 1037 nmnum = nmnum > 0 ? nmnum : 0; 1038 misnum[0] = (byte) nmnum; 1039 if (D) { 1040 Log.d(TAG, 1041 "handleAppParaForResponse(): mNeedNewMissedCallsNum=true, num= " + nmnum); 1042 } 1043 1044 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID, 1045 ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum); 1046 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 1047 if (D) { 1048 Log.d(TAG, 1049 "handleAppParaForResponse(): mNeedNewMissedCallsNum=true, num= " + nmnum); 1050 } 1051 1052 // Only Specifies the headers, not write for now, will write to PCE 1053 // together with Body 1054 try { 1055 op.sendHeaders(reply); 1056 } catch (IOException e) { 1057 Log.e(TAG, e.toString()); 1058 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 1059 } 1060 } 1061 1062 if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) { 1063 setDbCounters(ap); 1064 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 1065 try { 1066 op.sendHeaders(reply); 1067 } catch (IOException e) { 1068 Log.e(TAG, e.toString()); 1069 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 1070 } 1071 } 1072 1073 if (needSendPhonebookVersionCounters) { 1074 setFolderVersionCounters(ap); 1075 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 1076 try { 1077 op.sendHeaders(reply); 1078 } catch (IOException e) { 1079 Log.e(TAG, e.toString()); 1080 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 1081 } 1082 } 1083 1084 if (needSendCallHistoryVersionCounters) { 1085 setCallversionCounters(ap, appParamValue); 1086 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 1087 try { 1088 op.sendHeaders(reply); 1089 } catch (IOException e) { 1090 Log.e(TAG, e.toString()); 1091 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 1092 } 1093 } 1094 1095 return NEED_SEND_BODY; 1096 } 1097 pullVcardListing(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, Operation op, String name)1098 private int pullVcardListing(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, 1099 Operation op, String name) { 1100 String searchAttr = appParamValue.searchAttr.trim(); 1101 1102 if (searchAttr == null || searchAttr.length() == 0) { 1103 // If searchAttr is not set by PCE, set default value per spec. 1104 appParamValue.searchAttr = "0"; 1105 if (D) { 1106 Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default"); 1107 } 1108 } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) { 1109 Log.w(TAG, "search attr not supported"); 1110 if (searchAttr.equals("2")) { 1111 // search by sound is not supported currently 1112 Log.w(TAG, "do not support search by sound"); 1113 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 1114 } 1115 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 1116 } else { 1117 Log.i(TAG, "searchAttr is valid: " + searchAttr); 1118 } 1119 1120 int size = mVcardManager.getPhonebookSize(appParamValue.needTag); 1121 int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name); 1122 if (needSendBody != NEED_SEND_BODY) { 1123 op.noBodyHeader(); 1124 return needSendBody; 1125 } 1126 1127 if (size == 0) { 1128 if (D) { 1129 Log.d(TAG, "PhonebookSize is 0, return."); 1130 } 1131 return ResponseCodes.OBEX_HTTP_OK; 1132 } 1133 1134 String orderPara = appParamValue.order.trim(); 1135 if (TextUtils.isEmpty(orderPara)) { 1136 // If order parameter is not set by PCE, set default value per spec. 1137 orderPara = "0"; 1138 if (D) { 1139 Log.d(TAG, "Order parameter is not set by PCE. " 1140 + "Assume order by 'Indexed' by default"); 1141 } 1142 } else if (!orderPara.equals("0") && !orderPara.equals("1")) { 1143 if (D) { 1144 Log.d(TAG, "Order parameter is not supported: " + appParamValue.order); 1145 } 1146 if (orderPara.equals("2")) { 1147 // Order by sound is not supported currently 1148 Log.w(TAG, "Do not support order by sound"); 1149 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 1150 } 1151 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 1152 } else { 1153 Log.i(TAG, "Order parameter is valid: " + orderPara); 1154 } 1155 1156 if (orderPara.equals("0")) { 1157 mOrderBy = ORDER_BY_INDEXED; 1158 } else if (orderPara.equals("1")) { 1159 mOrderBy = ORDER_BY_ALPHABETICAL; 1160 } 1161 1162 return sendVcardListingXml(appParamValue, op, needSendBody, size); 1163 } 1164 pullVcardEntry(byte[] appParam, AppParamValue appParamValue, Operation op, HeaderSet reply, final String name, final String currentPath)1165 private int pullVcardEntry(byte[] appParam, AppParamValue appParamValue, Operation op, 1166 HeaderSet reply, final String name, final String currentPath) { 1167 if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) { 1168 if (D) { 1169 Log.d(TAG, "Name is Null, or the length of name < 5 !"); 1170 } 1171 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 1172 } 1173 String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1); 1174 int intIndex = 0; 1175 if (strIndex.trim().length() != 0) { 1176 try { 1177 intIndex = Integer.parseInt(strIndex); 1178 } catch (NumberFormatException e) { 1179 Log.e(TAG, "catch number format exception " + e.toString()); 1180 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 1181 } 1182 } 1183 1184 int size = mVcardManager.getPhonebookSize(appParamValue.needTag); 1185 int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name); 1186 if (size == 0) { 1187 if (D) { 1188 Log.d(TAG, "PhonebookSize is 0, return."); 1189 } 1190 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 1191 } 1192 1193 boolean vcard21 = appParamValue.vcard21; 1194 if (appParamValue.needTag == 0) { 1195 Log.w(TAG, "wrong path!"); 1196 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 1197 } else if (appParamValue.needTag == ContentType.PHONEBOOK) { 1198 if (intIndex < 0 || intIndex >= size) { 1199 Log.w(TAG, "The requested vcard is not acceptable! name= " + name); 1200 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 1201 } else if (intIndex == 0) { 1202 // For PB_PATH, 0.vcf is the phone number of this phone. 1203 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, 1204 appParamValue.ignorefilter ? null : appParamValue.propertySelector); 1205 return pushBytes(op, ownerVcard); 1206 } else { 1207 return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null, 1208 mOrderBy, appParamValue.ignorefilter, appParamValue.propertySelector); 1209 } 1210 } else { 1211 if (intIndex <= 0 || intIndex > size) { 1212 Log.w(TAG, "The requested vcard is not acceptable! name= " + name); 1213 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 1214 } 1215 // For others (ich/och/cch/mch), 0.vcf is meaningless, and must 1216 // begin from 1.vcf 1217 if (intIndex >= 1) { 1218 return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op, 1219 intIndex, intIndex, vcard21, needSendBody, size, appParamValue.ignorefilter, 1220 appParamValue.propertySelector, appParamValue.vCardSelector, 1221 appParamValue.vCardSelectorOperator, mVcardSelector); 1222 } 1223 } 1224 return ResponseCodes.OBEX_HTTP_OK; 1225 } 1226 pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, Operation op, final String name)1227 private int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, 1228 Operation op, final String name) { 1229 // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C 1230 if (name != null) { 1231 int dotIndex = name.indexOf("."); 1232 String vcf = "vcf"; 1233 if (dotIndex >= 0 && dotIndex <= name.length()) { 1234 if (!name.regionMatches(dotIndex + 1, vcf, 0, vcf.length())) { 1235 Log.w(TAG, "name is not .vcf"); 1236 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 1237 } 1238 } 1239 } // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C 1240 1241 int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag); 1242 int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op, name); 1243 if (needSendBody != NEED_SEND_BODY) { 1244 op.noBodyHeader(); 1245 return needSendBody; 1246 } 1247 1248 if (pbSize == 0) { 1249 if (D) { 1250 Log.d(TAG, "PhonebookSize is 0, return."); 1251 } 1252 return ResponseCodes.OBEX_HTTP_OK; 1253 } 1254 1255 int requestSize = 1256 pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize; 1257 int startPoint = appParamValue.listStartOffset; 1258 if (startPoint < 0 || startPoint >= pbSize) { 1259 Log.w(TAG, "listStartOffset is not correct! " + startPoint); 1260 return ResponseCodes.OBEX_HTTP_OK; 1261 } 1262 1263 // Limit the number of call log to CALLLOG_NUM_LIMIT 1264 if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) { 1265 if (requestSize > CALLLOG_NUM_LIMIT) { 1266 requestSize = CALLLOG_NUM_LIMIT; 1267 } 1268 } 1269 1270 int endPoint = startPoint + requestSize - 1; 1271 if (endPoint > pbSize - 1) { 1272 endPoint = pbSize - 1; 1273 } 1274 if (D) { 1275 Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" + startPoint 1276 + " endPoint=" + endPoint); 1277 } 1278 1279 boolean vcard21 = appParamValue.vcard21; 1280 if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) { 1281 if (startPoint == 0) { 1282 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, 1283 appParamValue.ignorefilter ? null : appParamValue.propertySelector); 1284 if (endPoint == 0) { 1285 return pushBytes(op, ownerVcard); 1286 } else { 1287 return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21, 1288 ownerVcard, needSendBody, pbSize, appParamValue.ignorefilter, 1289 appParamValue.propertySelector, appParamValue.vCardSelector, 1290 appParamValue.vCardSelectorOperator, mVcardSelector); 1291 } 1292 } else { 1293 return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint, 1294 vcard21, null, needSendBody, pbSize, appParamValue.ignorefilter, 1295 appParamValue.propertySelector, appParamValue.vCardSelector, 1296 appParamValue.vCardSelectorOperator, mVcardSelector); 1297 } 1298 } else { 1299 return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op, 1300 startPoint, endPoint, vcard21, needSendBody, pbSize, 1301 appParamValue.ignorefilter, appParamValue.propertySelector, 1302 appParamValue.vCardSelector, appParamValue.vCardSelectorOperator, 1303 mVcardSelector); 1304 } 1305 } 1306 closeStream(final OutputStream out, final Operation op)1307 public static boolean closeStream(final OutputStream out, final Operation op) { 1308 boolean returnvalue = true; 1309 try { 1310 if (out != null) { 1311 out.close(); 1312 } 1313 } catch (IOException e) { 1314 Log.e(TAG, "outputStream close failed" + e.toString()); 1315 returnvalue = false; 1316 } 1317 try { 1318 if (op != null) { 1319 op.close(); 1320 } 1321 } catch (IOException e) { 1322 Log.e(TAG, "operation close failed" + e.toString()); 1323 returnvalue = false; 1324 } 1325 return returnvalue; 1326 } 1327 1328 // Reserved for future use. In case PSE challenge PCE and PCE input wrong 1329 // session key. 1330 @Override onAuthenticationFailure(final byte[] userName)1331 public final void onAuthenticationFailure(final byte[] userName) { 1332 } 1333 createSelectionPara(final int type)1334 public static final String createSelectionPara(final int type) { 1335 String selection = null; 1336 switch (type) { 1337 case ContentType.INCOMING_CALL_HISTORY: 1338 selection = 1339 "(" + Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE + " OR " + Calls.TYPE 1340 + "=" + CallLog.Calls.REJECTED_TYPE + ")"; 1341 break; 1342 case ContentType.OUTGOING_CALL_HISTORY: 1343 selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE; 1344 break; 1345 case ContentType.MISSED_CALL_HISTORY: 1346 selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE; 1347 break; 1348 default: 1349 break; 1350 } 1351 if (V) { 1352 Log.v(TAG, "Call log selection: " + selection); 1353 } 1354 return selection; 1355 } 1356 1357 /** 1358 * XML encode special characters in the name field 1359 */ xmlEncode(String name, StringBuilder result)1360 private void xmlEncode(String name, StringBuilder result) { 1361 if (name == null) { 1362 return; 1363 } 1364 1365 final StringCharacterIterator iterator = new StringCharacterIterator(name); 1366 char character = iterator.current(); 1367 while (character != CharacterIterator.DONE) { 1368 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 if (character == '&') { 1377 result.append("&"); 1378 } else { 1379 // The char is not a special one, add it to the result as is 1380 result.append(character); 1381 } 1382 character = iterator.next(); 1383 } 1384 } 1385 writeVCardEntry(int vcfIndex, String name, StringBuilder result)1386 private void writeVCardEntry(int vcfIndex, String name, StringBuilder result) { 1387 result.append("<card handle=\""); 1388 result.append(vcfIndex); 1389 result.append(".vcf\" name=\""); 1390 xmlEncode(name, result); 1391 result.append("\"/>"); 1392 } 1393 notifyUpdateWakeLock()1394 private void notifyUpdateWakeLock() { 1395 Message msg = Message.obtain(mCallback); 1396 msg.what = BluetoothPbapService.MSG_ACQUIRE_WAKE_LOCK; 1397 msg.sendToTarget(); 1398 } 1399 logHeader(HeaderSet hs)1400 public static final void logHeader(HeaderSet hs) { 1401 Log.v(TAG, "Dumping HeaderSet " + hs.toString()); 1402 try { 1403 1404 Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT)); 1405 Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME)); 1406 Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE)); 1407 Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH)); 1408 Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601)); 1409 Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE)); 1410 Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION)); 1411 Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET)); 1412 Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP)); 1413 Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO)); 1414 Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS)); 1415 Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER)); 1416 } catch (IOException e) { 1417 Log.e(TAG, "dump HeaderSet error " + e); 1418 } 1419 } 1420 setDbCounters(ApplicationParameter ap)1421 private void setDbCounters(ApplicationParameter ap) { 1422 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.DATABASEIDENTIFIER_TAGID, 1423 ApplicationParameter.TRIPLET_LENGTH.DATABASEIDENTIFIER_LENGTH, 1424 getDatabaseIdentifier()); 1425 } 1426 setFolderVersionCounters(ApplicationParameter ap)1427 private void setFolderVersionCounters(ApplicationParameter ap) { 1428 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID, 1429 ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH, 1430 getPBPrimaryFolderVersion()); 1431 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID, 1432 ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH, 1433 getPBSecondaryFolderVersion()); 1434 } 1435 setCallversionCounters(ApplicationParameter ap, AppParamValue appParamValue)1436 private void setCallversionCounters(ApplicationParameter ap, AppParamValue appParamValue) { 1437 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID, 1438 ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH, 1439 appParamValue.callHistoryVersionCounter); 1440 1441 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID, 1442 ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH, 1443 appParamValue.callHistoryVersionCounter); 1444 } 1445 getDatabaseIdentifier()1446 private byte[] getDatabaseIdentifier() { 1447 mDatabaseIdentifierHigh = 0; 1448 mDatabaseIdentifierLow = BluetoothPbapUtils.sDbIdentifier.get(); 1449 if (mDatabaseIdentifierLow != INVALID_VALUE_PARAMETER 1450 && mDatabaseIdentifierHigh != INVALID_VALUE_PARAMETER) { 1451 ByteBuffer ret = ByteBuffer.allocate(16); 1452 ret.putLong(mDatabaseIdentifierHigh); 1453 ret.putLong(mDatabaseIdentifierLow); 1454 return ret.array(); 1455 } else { 1456 return null; 1457 } 1458 } 1459 getPBPrimaryFolderVersion()1460 private byte[] getPBPrimaryFolderVersion() { 1461 long primaryVcMsb = 0; 1462 ByteBuffer pvc = ByteBuffer.allocate(16); 1463 pvc.putLong(primaryVcMsb); 1464 1465 Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.sPrimaryVersionCounter); 1466 pvc.putLong(BluetoothPbapUtils.sPrimaryVersionCounter); 1467 return pvc.array(); 1468 } 1469 getPBSecondaryFolderVersion()1470 private byte[] getPBSecondaryFolderVersion() { 1471 long secondaryVcMsb = 0; 1472 ByteBuffer svc = ByteBuffer.allocate(16); 1473 svc.putLong(secondaryVcMsb); 1474 1475 Log.d(TAG, "secondaryVersionCounter is " + BluetoothPbapUtils.sSecondaryVersionCounter); 1476 svc.putLong(BluetoothPbapUtils.sSecondaryVersionCounter); 1477 return svc.array(); 1478 } 1479 checkPbapFeatureSupport(long featureBitMask)1480 private boolean checkPbapFeatureSupport(long featureBitMask) { 1481 Log.d(TAG, "checkPbapFeatureSupport featureBitMask is " + featureBitMask); 1482 return ((ByteBuffer.wrap(mConnAppParamValue.supportedFeature).getInt() & featureBitMask) 1483 != 0); 1484 } 1485 } 1486