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