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