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