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