1 /* 2 * Copyright (c) 2008-2009, Motorola, Inc. 3 * 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * - Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 12 * - Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the documentation 14 * and/or other materials provided with the distribution. 15 * 16 * - Neither the name of the Motorola, Inc. nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.android.bluetooth.pbap; 34 35 import android.content.Context; 36 import android.os.Message; 37 import android.os.Handler; 38 import android.text.TextUtils; 39 import android.util.Log; 40 import android.provider.CallLog.Calls; 41 import android.provider.CallLog; 42 43 import java.io.IOException; 44 import java.io.OutputStream; 45 import java.text.CharacterIterator; 46 import java.text.StringCharacterIterator; 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 50 import javax.obex.ServerRequestHandler; 51 import javax.obex.ResponseCodes; 52 import javax.obex.ApplicationParameter; 53 import javax.obex.ServerOperation; 54 import javax.obex.Operation; 55 import javax.obex.HeaderSet; 56 57 public class BluetoothPbapObexServer extends ServerRequestHandler { 58 59 private static final String TAG = "BluetoothPbapObexServer"; 60 61 private static final boolean D = BluetoothPbapService.DEBUG; 62 63 private static final boolean V = BluetoothPbapService.VERBOSE; 64 65 private static final int UUID_LENGTH = 16; 66 67 // The length of suffix of vcard name - ".vcf" is 5 68 private static final int VCARD_NAME_SUFFIX_LENGTH = 5; 69 70 // 128 bit UUID for PBAP 71 private static final byte[] PBAP_TARGET = new byte[] { 72 0x79, 0x61, 0x35, (byte)0xf0, (byte)0xf0, (byte)0xc5, 0x11, (byte)0xd8, 0x09, 0x66, 73 0x08, 0x00, 0x20, 0x0c, (byte)0x9a, 0x66 74 }; 75 76 // Currently not support SIM card 77 private static final String[] LEGAL_PATH = { 78 "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch", 79 "/telecom/cch" 80 }; 81 82 @SuppressWarnings("unused") 83 private static final String[] LEGAL_PATH_WITH_SIM = { 84 "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch", 85 "/telecom/cch", "/SIM1", "/SIM1/telecom", "/SIM1/telecom/ich", "/SIM1/telecom/och", 86 "/SIM1/telecom/mch", "/SIM1/telecom/cch", "/SIM1/telecom/pb" 87 88 }; 89 90 // SIM card 91 private static final String SIM1 = "SIM1"; 92 93 // missed call history 94 private static final String MCH = "mch"; 95 96 // incoming call history 97 private static final String ICH = "ich"; 98 99 // outgoing call history 100 private static final String OCH = "och"; 101 102 // combined call history 103 private static final String CCH = "cch"; 104 105 // phone book 106 private static final String PB = "pb"; 107 108 private static final String ICH_PATH = "/telecom/ich"; 109 110 private static final String OCH_PATH = "/telecom/och"; 111 112 private static final String MCH_PATH = "/telecom/mch"; 113 114 private static final String CCH_PATH = "/telecom/cch"; 115 116 private static final String PB_PATH = "/telecom/pb"; 117 118 // type for list vcard objects 119 private static final String TYPE_LISTING = "x-bt/vcard-listing"; 120 121 // type for get single vcard object 122 private static final String TYPE_VCARD = "x-bt/vcard"; 123 124 // to indicate if need send body besides headers 125 private static final int NEED_SEND_BODY = -1; 126 127 // type for download all vcard objects 128 private static final String TYPE_PB = "x-bt/phonebook"; 129 130 // The number of indexes in the phone book. 131 private boolean mNeedPhonebookSize = false; 132 133 // The number of missed calls that have not been checked on the PSE at the 134 // point of the request. Only apply to "mch" case. 135 private boolean mNeedNewMissedCallsNum = false; 136 137 private int mMissedCallSize = 0; 138 139 // record current path the client are browsing 140 private String mCurrentPath = ""; 141 142 private Handler mCallback = null; 143 144 private Context mContext; 145 146 private BluetoothPbapVcardManager mVcardManager; 147 148 private int mOrderBy = ORDER_BY_INDEXED; 149 150 private static int CALLLOG_NUM_LIMIT = 50; 151 152 public static int ORDER_BY_INDEXED = 0; 153 154 public static int ORDER_BY_ALPHABETICAL = 1; 155 156 public static boolean sIsAborted = false; 157 158 public static class ContentType { 159 public static final int PHONEBOOK = 1; 160 161 public static final int INCOMING_CALL_HISTORY = 2; 162 163 public static final int OUTGOING_CALL_HISTORY = 3; 164 165 public static final int MISSED_CALL_HISTORY = 4; 166 167 public static final int COMBINED_CALL_HISTORY = 5; 168 } 169 170 public BluetoothPbapObexServer(Handler callback, Context context) { 171 super(); 172 mCallback = callback; 173 mContext = context; 174 mVcardManager = new BluetoothPbapVcardManager(mContext); 175 } 176 177 @Override 178 public int onConnect(final HeaderSet request, HeaderSet reply) { 179 if (V) logHeader(request); 180 notifyUpdateWakeLock(); 181 try { 182 byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET); 183 if (uuid == null) { 184 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 185 } 186 if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid)); 187 188 if (uuid.length != UUID_LENGTH) { 189 Log.w(TAG, "Wrong UUID length"); 190 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 191 } 192 for (int i = 0; i < UUID_LENGTH; i++) { 193 if (uuid[i] != PBAP_TARGET[i]) { 194 Log.w(TAG, "Wrong UUID"); 195 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 196 } 197 } 198 reply.setHeader(HeaderSet.WHO, uuid); 199 } catch (IOException e) { 200 Log.e(TAG, e.toString()); 201 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 202 } 203 204 try { 205 byte[] remote = (byte[])request.getHeader(HeaderSet.WHO); 206 if (remote != null) { 207 if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote)); 208 reply.setHeader(HeaderSet.TARGET, remote); 209 } 210 } catch (IOException e) { 211 Log.e(TAG, e.toString()); 212 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 213 } 214 215 if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " + 216 "MSG_SESSION_ESTABLISHED msg."); 217 218 Message msg = Message.obtain(mCallback); 219 msg.what = BluetoothPbapService.MSG_SESSION_ESTABLISHED; 220 msg.sendToTarget(); 221 222 return ResponseCodes.OBEX_HTTP_OK; 223 } 224 225 @Override 226 public void onDisconnect(final HeaderSet req, final HeaderSet resp) { 227 if (D) Log.d(TAG, "onDisconnect(): enter"); 228 if (V) logHeader(req); 229 notifyUpdateWakeLock(); 230 resp.responseCode = ResponseCodes.OBEX_HTTP_OK; 231 if (mCallback != null) { 232 Message msg = Message.obtain(mCallback); 233 msg.what = BluetoothPbapService.MSG_SESSION_DISCONNECTED; 234 msg.sendToTarget(); 235 if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out."); 236 } 237 } 238 239 @Override 240 public int onAbort(HeaderSet request, HeaderSet reply) { 241 if (D) Log.d(TAG, "onAbort(): enter."); 242 notifyUpdateWakeLock(); 243 sIsAborted = true; 244 return ResponseCodes.OBEX_HTTP_OK; 245 } 246 247 @Override 248 public int onPut(final Operation op) { 249 if (D) Log.d(TAG, "onPut(): not support PUT request."); 250 notifyUpdateWakeLock(); 251 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 252 } 253 254 @Override 255 public int onDelete(final HeaderSet request, final HeaderSet reply) { 256 if (D) Log.d(TAG, "onDelete(): not support PUT request."); 257 notifyUpdateWakeLock(); 258 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 259 } 260 261 @Override 262 public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup, 263 final boolean create) { 264 if (V) logHeader(request); 265 if (D) Log.d(TAG, "before setPath, mCurrentPath == " + mCurrentPath); 266 notifyUpdateWakeLock(); 267 String current_path_tmp = mCurrentPath; 268 String tmp_path = null; 269 try { 270 tmp_path = (String)request.getHeader(HeaderSet.NAME); 271 } catch (IOException e) { 272 Log.e(TAG, "Get name header fail"); 273 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 274 } 275 if (D) Log.d(TAG, "backup=" + backup + " create=" + create + " name=" + tmp_path); 276 277 if (backup) { 278 if (current_path_tmp.length() != 0) { 279 current_path_tmp = current_path_tmp.substring(0, 280 current_path_tmp.lastIndexOf("/")); 281 } 282 } else { 283 if (tmp_path == null) { 284 current_path_tmp = ""; 285 } else { 286 current_path_tmp = current_path_tmp + "/" + tmp_path; 287 } 288 } 289 290 if ((current_path_tmp.length() != 0) && (!isLegalPath(current_path_tmp))) { 291 if (create) { 292 Log.w(TAG, "path create is forbidden!"); 293 return ResponseCodes.OBEX_HTTP_FORBIDDEN; 294 } else { 295 Log.w(TAG, "path is not legal"); 296 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 297 } 298 } 299 mCurrentPath = current_path_tmp; 300 if (V) Log.v(TAG, "after setPath, mCurrentPath == " + mCurrentPath); 301 302 return ResponseCodes.OBEX_HTTP_OK; 303 } 304 305 @Override 306 public void onClose() { 307 if (mCallback != null) { 308 Message msg = Message.obtain(mCallback); 309 msg.what = BluetoothPbapService.MSG_SERVERSESSION_CLOSE; 310 msg.sendToTarget(); 311 if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out."); 312 } 313 } 314 315 @Override 316 public int onGet(Operation op) { 317 notifyUpdateWakeLock(); 318 sIsAborted = false; 319 HeaderSet request = null; 320 HeaderSet reply = new HeaderSet(); 321 String type = ""; 322 String name = ""; 323 byte[] appParam = null; 324 AppParamValue appParamValue = new AppParamValue(); 325 try { 326 request = op.getReceivedHeader(); 327 type = (String)request.getHeader(HeaderSet.TYPE); 328 name = (String)request.getHeader(HeaderSet.NAME); 329 appParam = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER); 330 } catch (IOException e) { 331 Log.e(TAG, "request headers error"); 332 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 333 } 334 335 if (V) logHeader(request); 336 if (D) Log.d(TAG, "OnGet type is " + type + "; name is " + name); 337 338 if (type == null) { 339 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 340 } 341 // Accroding to specification,the name header could be omitted such as 342 // sony erriccsonHBH-DS980 343 344 // For "x-bt/phonebook" and "x-bt/vcard-listing": 345 // if name == null, guess what carkit actually want from current path 346 // For "x-bt/vcard": 347 // We decide which kind of content client would like per current path 348 349 boolean validName = true; 350 if (TextUtils.isEmpty(name)) { 351 validName = false; 352 } 353 354 if (!validName || (validName && type.equals(TYPE_VCARD))) { 355 if (D) Log.d(TAG, "Guess what carkit actually want from current path (" + 356 mCurrentPath + ")"); 357 358 if (mCurrentPath.equals(PB_PATH)) { 359 appParamValue.needTag = ContentType.PHONEBOOK; 360 } else if (mCurrentPath.equals(ICH_PATH)) { 361 appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY; 362 } else if (mCurrentPath.equals(OCH_PATH)) { 363 appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY; 364 } else if (mCurrentPath.equals(MCH_PATH)) { 365 appParamValue.needTag = ContentType.MISSED_CALL_HISTORY; 366 mNeedNewMissedCallsNum = true; 367 } else if (mCurrentPath.equals(CCH_PATH)) { 368 appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY; 369 } else { 370 Log.w(TAG, "mCurrentpath is not valid path!!!"); 371 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 372 } 373 if (D) Log.v(TAG, "onGet(): appParamValue.needTag=" + appParamValue.needTag); 374 } else { 375 // Not support SIM card currently 376 if (name.contains(SIM1.subSequence(0, SIM1.length()))) { 377 Log.w(TAG, "Not support access SIM card info!"); 378 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 379 } 380 381 // we have weak name checking here to provide better 382 // compatibility with other devices,although unique name such as 383 // "pb.vcf" is required by SIG spec. 384 if (isNameMatchTarget(name, PB)) { 385 appParamValue.needTag = ContentType.PHONEBOOK; 386 if (D) Log.v(TAG, "download phonebook request"); 387 } else if (isNameMatchTarget(name, ICH)) { 388 appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY; 389 if (D) Log.v(TAG, "download incoming calls request"); 390 } else if (isNameMatchTarget(name, OCH)) { 391 appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY; 392 if (D) Log.v(TAG, "download outgoing calls request"); 393 } else if (isNameMatchTarget(name, MCH)) { 394 appParamValue.needTag = ContentType.MISSED_CALL_HISTORY; 395 mNeedNewMissedCallsNum = true; 396 if (D) Log.v(TAG, "download missed calls request"); 397 } else if (isNameMatchTarget(name, CCH)) { 398 appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY; 399 if (D) Log.v(TAG, "download combined calls request"); 400 } else { 401 Log.w(TAG, "Input name doesn't contain valid info!!!"); 402 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 403 } 404 } 405 406 if ((appParam != null) && !parseApplicationParameter(appParam, appParamValue)) { 407 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 408 } 409 410 // listing request 411 if (type.equals(TYPE_LISTING)) { 412 return pullVcardListing(appParam, appParamValue, reply, op); 413 } 414 // pull vcard entry request 415 else if (type.equals(TYPE_VCARD)) { 416 return pullVcardEntry(appParam, appParamValue, op, name, mCurrentPath); 417 } 418 // down load phone book request 419 else if (type.equals(TYPE_PB)) { 420 return pullPhonebook(appParam, appParamValue, reply, op, name); 421 } else { 422 Log.w(TAG, "unknown type request!!!"); 423 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 424 } 425 } 426 427 private boolean isNameMatchTarget(String name, String target) { 428 String contentTypeName = name; 429 if (contentTypeName.endsWith(".vcf")) { 430 contentTypeName = contentTypeName 431 .substring(0, contentTypeName.length() - ".vcf".length()); 432 } 433 // There is a test case: Client will send a wrong name "/telecom/pbpb". 434 // So we must use the String between '/' and '/' as a indivisible part 435 // for comparing. 436 String[] nameList = contentTypeName.split("/"); 437 for (String subName : nameList) { 438 if (subName.equals(target)) { 439 return true; 440 } 441 } 442 return false; 443 } 444 445 /** check whether path is legal */ 446 private final boolean isLegalPath(final String str) { 447 if (str.length() == 0) { 448 return true; 449 } 450 for (int i = 0; i < LEGAL_PATH.length; i++) { 451 if (str.equals(LEGAL_PATH[i])) { 452 return true; 453 } 454 } 455 return false; 456 } 457 458 private class AppParamValue { 459 public int maxListCount; 460 461 public int listStartOffset; 462 463 public String searchValue; 464 465 // Indicate which vCard parameter the search operation shall be carried 466 // out on. Can be "Name | Number | Sound", default value is "Name". 467 public String searchAttr; 468 469 // Indicate which sorting order shall be used for the 470 // <x-bt/vcard-listing> listing object. 471 // Can be "Alphabetical | Indexed | Phonetical", default value is 472 // "Indexed". 473 public String order; 474 475 public int needTag; 476 477 public boolean vcard21; 478 479 public byte[] filter; 480 481 public boolean ignorefilter; 482 483 public AppParamValue() { 484 maxListCount = 0xFFFF; 485 listStartOffset = 0; 486 searchValue = ""; 487 searchAttr = ""; 488 order = ""; 489 needTag = 0x00; 490 vcard21 = true; 491 //Filter is not set by default 492 ignorefilter = true; 493 filter = new byte[] {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} ; 494 } 495 496 public void dump() { 497 Log.i(TAG, "maxListCount=" + maxListCount + " listStartOffset=" + listStartOffset 498 + " searchValue=" + searchValue + " searchAttr=" + searchAttr + " needTag=" 499 + needTag + " vcard21=" + vcard21 + " order=" + order); 500 } 501 } 502 503 /** To parse obex application parameter */ 504 private final boolean parseApplicationParameter(final byte[] appParam, 505 AppParamValue appParamValue) { 506 int i = 0; 507 boolean parseOk = true; 508 while ((i < appParam.length) && (parseOk == true)) { 509 switch (appParam[i]) { 510 case ApplicationParameter.TRIPLET_TAGID.FILTER_TAGID: 511 i += 2; // length and tag field in triplet 512 for (int index=0; index < ApplicationParameter.TRIPLET_LENGTH.FILTER_LENGTH; 513 index++) { 514 if (appParam[i+index] != 0){ 515 appParamValue.ignorefilter = false; 516 appParamValue.filter[index] = appParam[i+index]; 517 } 518 } 519 i += ApplicationParameter.TRIPLET_LENGTH.FILTER_LENGTH; 520 break; 521 case ApplicationParameter.TRIPLET_TAGID.ORDER_TAGID: 522 i += 2; // length and tag field in triplet 523 appParamValue.order = Byte.toString(appParam[i]); 524 i += ApplicationParameter.TRIPLET_LENGTH.ORDER_LENGTH; 525 break; 526 case ApplicationParameter.TRIPLET_TAGID.SEARCH_VALUE_TAGID: 527 i += 1; // length field in triplet 528 // length of search value is variable 529 int length = appParam[i]; 530 if (length == 0) { 531 parseOk = false; 532 break; 533 } 534 if (appParam[i+length] == 0x0) { 535 appParamValue.searchValue = new String(appParam, i + 1, length-1); 536 } else { 537 appParamValue.searchValue = new String(appParam, i + 1, length); 538 } 539 i += length; 540 i += 1; 541 break; 542 case ApplicationParameter.TRIPLET_TAGID.SEARCH_ATTRIBUTE_TAGID: 543 i += 2; 544 appParamValue.searchAttr = Byte.toString(appParam[i]); 545 i += ApplicationParameter.TRIPLET_LENGTH.SEARCH_ATTRIBUTE_LENGTH; 546 break; 547 case ApplicationParameter.TRIPLET_TAGID.MAXLISTCOUNT_TAGID: 548 i += 2; 549 if (appParam[i] == 0 && appParam[i + 1] == 0) { 550 mNeedPhonebookSize = true; 551 } else { 552 int highValue = appParam[i] & 0xff; 553 int lowValue = appParam[i + 1] & 0xff; 554 appParamValue.maxListCount = highValue * 256 + lowValue; 555 } 556 i += ApplicationParameter.TRIPLET_LENGTH.MAXLISTCOUNT_LENGTH; 557 break; 558 case ApplicationParameter.TRIPLET_TAGID.LISTSTARTOFFSET_TAGID: 559 i += 2; 560 int highValue = appParam[i] & 0xff; 561 int lowValue = appParam[i + 1] & 0xff; 562 appParamValue.listStartOffset = highValue * 256 + lowValue; 563 i += ApplicationParameter.TRIPLET_LENGTH.LISTSTARTOFFSET_LENGTH; 564 break; 565 case ApplicationParameter.TRIPLET_TAGID.FORMAT_TAGID: 566 i += 2;// length field in triplet 567 if (appParam[i] != 0) { 568 appParamValue.vcard21 = false; 569 } 570 i += ApplicationParameter.TRIPLET_LENGTH.FORMAT_LENGTH; 571 break; 572 default: 573 parseOk = false; 574 Log.e(TAG, "Parse Application Parameter error"); 575 break; 576 } 577 } 578 579 if (D) appParamValue.dump(); 580 581 return parseOk; 582 } 583 584 /** Form and Send an XML format String to client for Phone book listing */ 585 private final int sendVcardListingXml(final int type, Operation op, 586 final int maxListCount, final int listStartOffset, final String searchValue, 587 String searchAttr) { 588 StringBuilder result = new StringBuilder(); 589 int itemsFound = 0; 590 result.append("<?xml version=\"1.0\"?>"); 591 result.append("<!DOCTYPE vcard-listing SYSTEM \"vcard-listing.dtd\">"); 592 result.append("<vCard-listing version=\"1.0\">"); 593 594 // Phonebook listing request 595 if (type == ContentType.PHONEBOOK) { 596 if (searchAttr.equals("0")) { // search by name 597 itemsFound = createList(maxListCount, listStartOffset, searchValue, result, 598 "name"); 599 } else if (searchAttr.equals("1")) { // search by number 600 itemsFound = createList(maxListCount, listStartOffset, searchValue, result, 601 "number"); 602 }// end of search by number 603 else { 604 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 605 } 606 } 607 // Call history listing request 608 else { 609 ArrayList<String> nameList = mVcardManager.loadCallHistoryList(type); 610 int requestSize = nameList.size() >= maxListCount ? maxListCount : nameList.size(); 611 int startPoint = listStartOffset; 612 int endPoint = startPoint + requestSize; 613 if (endPoint > nameList.size()) { 614 endPoint = nameList.size(); 615 } 616 if (D) Log.d(TAG, "call log list, size=" + requestSize + " offset=" + listStartOffset); 617 618 for (int j = startPoint; j < endPoint; j++) { 619 writeVCardEntry(j+1, nameList.get(j),result); 620 } 621 } 622 result.append("</vCard-listing>"); 623 624 if (V) Log.v(TAG, "itemsFound =" + itemsFound); 625 626 return pushBytes(op, result.toString()); 627 } 628 629 private int createList(final int maxListCount, final int listStartOffset, 630 final String searchValue, StringBuilder result, String type) { 631 int itemsFound = 0; 632 ArrayList<String> nameList = mVcardManager.getPhonebookNameList(mOrderBy); 633 final int requestSize = nameList.size() >= maxListCount ? maxListCount : nameList.size(); 634 final int listSize = nameList.size(); 635 String compareValue = "", currentValue; 636 637 if (D) Log.d(TAG, "search by " + type + ", requestSize=" + requestSize + " offset=" 638 + listStartOffset + " searchValue=" + searchValue); 639 640 if (type.equals("number")) { 641 // query the number, to get the names 642 ArrayList<String> names = mVcardManager.getContactNamesByNumber(searchValue); 643 for (int i = 0; i < names.size(); i++) { 644 compareValue = names.get(i).trim(); 645 if (D) Log.d(TAG, "compareValue=" + compareValue); 646 for (int pos = listStartOffset; pos < listSize && 647 itemsFound < requestSize; pos++) { 648 currentValue = nameList.get(pos); 649 if (D) Log.d(TAG, "currentValue=" + currentValue); 650 if (currentValue.equals(compareValue)) { 651 itemsFound++; 652 if (currentValue.contains(",")) 653 currentValue = currentValue.substring(0, currentValue.lastIndexOf(',')); 654 writeVCardEntry(pos, currentValue,result); 655 } 656 } 657 if (itemsFound >= requestSize) { 658 break; 659 } 660 } 661 } else { 662 if (searchValue != null) { 663 compareValue = searchValue.trim(); 664 } 665 for (int pos = listStartOffset; pos < listSize && 666 itemsFound < requestSize; pos++) { 667 currentValue = nameList.get(pos); 668 if (currentValue.contains(",")) 669 currentValue = currentValue.substring(0, currentValue.lastIndexOf(',')); 670 671 if (searchValue.isEmpty() || ((currentValue.toLowerCase()).equals(compareValue.toLowerCase()))) { 672 itemsFound++; 673 writeVCardEntry(pos, currentValue,result); 674 } 675 } 676 } 677 return itemsFound; 678 } 679 680 /** 681 * Function to send obex header back to client such as get phonebook size 682 * request 683 */ 684 private final int pushHeader(final Operation op, final HeaderSet reply) { 685 OutputStream outputStream = null; 686 687 if (D) Log.d(TAG, "Push Header"); 688 if (D) Log.d(TAG, reply.toString()); 689 690 int pushResult = ResponseCodes.OBEX_HTTP_OK; 691 try { 692 op.sendHeaders(reply); 693 outputStream = op.openOutputStream(); 694 outputStream.flush(); 695 } catch (IOException e) { 696 Log.e(TAG, e.toString()); 697 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 698 } finally { 699 if (!closeStream(outputStream, op)) { 700 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 701 } 702 } 703 return pushResult; 704 } 705 706 /** Function to send vcard data to client */ 707 private final int pushBytes(Operation op, final String vcardString) { 708 if (vcardString == null) { 709 Log.w(TAG, "vcardString is null!"); 710 return ResponseCodes.OBEX_HTTP_OK; 711 } 712 713 OutputStream outputStream = null; 714 int pushResult = ResponseCodes.OBEX_HTTP_OK; 715 try { 716 outputStream = op.openOutputStream(); 717 outputStream.write(vcardString.getBytes()); 718 if (V) Log.v(TAG, "Send Data complete!"); 719 } catch (IOException e) { 720 Log.e(TAG, "open/write outputstrem failed" + e.toString()); 721 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 722 } 723 724 if (!closeStream(outputStream, op)) { 725 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 726 } 727 728 return pushResult; 729 } 730 731 private final int handleAppParaForResponse(AppParamValue appParamValue, int size, 732 HeaderSet reply, Operation op) { 733 byte[] misnum = new byte[1]; 734 ApplicationParameter ap = new ApplicationParameter(); 735 736 // In such case, PCE only want the number of index. 737 // So response not contain any Body header. 738 if (mNeedPhonebookSize) { 739 if (V) Log.v(TAG, "Need Phonebook size in response header."); 740 mNeedPhonebookSize = false; 741 742 byte[] pbsize = new byte[2]; 743 744 pbsize[0] = (byte)((size / 256) & 0xff);// HIGH VALUE 745 pbsize[1] = (byte)((size % 256) & 0xff);// LOW VALUE 746 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID, 747 ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize); 748 749 if (mNeedNewMissedCallsNum) { 750 mNeedNewMissedCallsNum = false; 751 int nmnum = size - mMissedCallSize; 752 mMissedCallSize = size; 753 754 nmnum = nmnum > 0 ? nmnum : 0; 755 misnum[0] = (byte)nmnum; 756 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID, 757 ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum); 758 if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true, num= " 759 + nmnum); 760 } 761 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 762 763 if (D) Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size); 764 765 return pushHeader(op, reply); 766 } 767 768 // Only apply to "mch" download/listing. 769 // NewMissedCalls is used only in the response, together with Body 770 // header. 771 if (mNeedNewMissedCallsNum) { 772 if (V) Log.v(TAG, "Need new missed call num in response header."); 773 mNeedNewMissedCallsNum = false; 774 775 int nmnum = size - mMissedCallSize; 776 mMissedCallSize = size; 777 778 nmnum = nmnum > 0 ? nmnum : 0; 779 misnum[0] = (byte)nmnum; 780 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID, 781 ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum); 782 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 783 if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true, num= " 784 + nmnum); 785 786 // Only Specifies the headers, not write for now, will write to PCE 787 // together with Body 788 try { 789 op.sendHeaders(reply); 790 } catch (IOException e) { 791 Log.e(TAG, e.toString()); 792 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 793 } 794 } 795 return NEED_SEND_BODY; 796 } 797 798 private final int pullVcardListing(byte[] appParam, AppParamValue appParamValue, 799 HeaderSet reply, Operation op) { 800 String searchAttr = appParamValue.searchAttr.trim(); 801 802 if (searchAttr == null || searchAttr.length() == 0) { 803 // If searchAttr is not set by PCE, set default value per spec. 804 appParamValue.searchAttr = "0"; 805 if (D) Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default"); 806 } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) { 807 Log.w(TAG, "search attr not supported"); 808 if (searchAttr.equals("2")) { 809 // search by sound is not supported currently 810 Log.w(TAG, "do not support search by sound"); 811 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 812 } 813 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 814 } else { 815 Log.i(TAG, "searchAttr is valid: " + searchAttr); 816 } 817 818 int size = mVcardManager.getPhonebookSize(appParamValue.needTag); 819 int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op); 820 if (needSendBody != NEED_SEND_BODY) { 821 return needSendBody; 822 } 823 824 if (size == 0) { 825 if (V) Log.v(TAG, "PhonebookSize is 0, return."); 826 return ResponseCodes.OBEX_HTTP_OK; 827 } 828 829 String orderPara = appParamValue.order.trim(); 830 if (TextUtils.isEmpty(orderPara)) { 831 // If order parameter is not set by PCE, set default value per spec. 832 orderPara = "0"; 833 if (D) Log.d(TAG, "Order parameter is not set by PCE. " + 834 "Assume order by 'Indexed' by default"); 835 } else if (!orderPara.equals("0") && !orderPara.equals("1")) { 836 if (V) Log.v(TAG, "Order parameter is not supported: " + appParamValue.order); 837 if (orderPara.equals("2")) { 838 // Order by sound is not supported currently 839 Log.w(TAG, "Do not support order by sound"); 840 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 841 } 842 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 843 } else { 844 Log.i(TAG, "Order parameter is valid: " + orderPara); 845 } 846 847 if (orderPara.equals("0")) { 848 mOrderBy = ORDER_BY_INDEXED; 849 } else if (orderPara.equals("1")) { 850 mOrderBy = ORDER_BY_ALPHABETICAL; 851 } 852 853 int sendResult = sendVcardListingXml(appParamValue.needTag, op, appParamValue.maxListCount, 854 appParamValue.listStartOffset, appParamValue.searchValue, 855 appParamValue.searchAttr); 856 return sendResult; 857 } 858 859 private final int pullVcardEntry(byte[] appParam, AppParamValue appParamValue, 860 Operation op, final String name, final String current_path) { 861 if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) { 862 if (D) Log.d(TAG, "Name is Null, or the length of name < 5 !"); 863 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 864 } 865 String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1); 866 int intIndex = 0; 867 if (strIndex.trim().length() != 0) { 868 try { 869 intIndex = Integer.parseInt(strIndex); 870 } catch (NumberFormatException e) { 871 Log.e(TAG, "catch number format exception " + e.toString()); 872 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 873 } 874 } 875 876 int size = mVcardManager.getPhonebookSize(appParamValue.needTag); 877 if (size == 0) { 878 if (V) Log.v(TAG, "PhonebookSize is 0, return."); 879 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 880 } 881 882 boolean vcard21 = appParamValue.vcard21; 883 if (appParamValue.needTag == 0) { 884 Log.w(TAG, "wrong path!"); 885 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 886 } else if (appParamValue.needTag == ContentType.PHONEBOOK) { 887 if (intIndex < 0 || intIndex >= size) { 888 Log.w(TAG, "The requested vcard is not acceptable! name= " + name); 889 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 890 } else if (intIndex == 0) { 891 // For PB_PATH, 0.vcf is the phone number of this phone. 892 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,null); 893 return pushBytes(op, ownerVcard); 894 } else { 895 return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null, 896 mOrderBy, appParamValue.ignorefilter, appParamValue.filter); 897 } 898 } else { 899 if (intIndex <= 0 || intIndex > size) { 900 Log.w(TAG, "The requested vcard is not acceptable! name= " + name); 901 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 902 } 903 // For others (ich/och/cch/mch), 0.vcf is meaningless, and must 904 // begin from 1.vcf 905 if (intIndex >= 1) { 906 return mVcardManager.composeAndSendCallLogVcards(appParamValue.needTag, op, 907 intIndex, intIndex, vcard21, appParamValue.ignorefilter, 908 appParamValue.filter); 909 } 910 } 911 return ResponseCodes.OBEX_HTTP_OK; 912 } 913 914 private final int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, 915 Operation op, final String name) { 916 // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C 917 if (name != null) { 918 int dotIndex = name.indexOf("."); 919 String vcf = "vcf"; 920 if (dotIndex >= 0 && dotIndex <= name.length()) { 921 if (name.regionMatches(dotIndex + 1, vcf, 0, vcf.length()) == false) { 922 Log.w(TAG, "name is not .vcf"); 923 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 924 } 925 } 926 } // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C 927 928 int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag); 929 int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op); 930 if (needSendBody != NEED_SEND_BODY) { 931 return needSendBody; 932 } 933 934 if (pbSize == 0) { 935 if (V) Log.v(TAG, "PhonebookSize is 0, return."); 936 return ResponseCodes.OBEX_HTTP_OK; 937 } 938 939 int requestSize = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount 940 : pbSize; 941 int startPoint = appParamValue.listStartOffset; 942 if (startPoint < 0 || startPoint >= pbSize) { 943 Log.w(TAG, "listStartOffset is not correct! " + startPoint); 944 return ResponseCodes.OBEX_HTTP_OK; 945 } 946 947 // Limit the number of call log to CALLLOG_NUM_LIMIT 948 if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) { 949 if (requestSize > CALLLOG_NUM_LIMIT) { 950 requestSize = CALLLOG_NUM_LIMIT; 951 } 952 } 953 954 int endPoint = startPoint + requestSize - 1; 955 if (endPoint > pbSize - 1) { 956 endPoint = pbSize - 1; 957 } 958 if (D) Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" + 959 startPoint + " endPoint=" + endPoint); 960 961 boolean vcard21 = appParamValue.vcard21; 962 if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) { 963 if (startPoint == 0) { 964 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,null); 965 if (endPoint == 0) { 966 return pushBytes(op, ownerVcard); 967 } else { 968 return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21, 969 ownerVcard, appParamValue.ignorefilter, appParamValue.filter); 970 } 971 } else { 972 return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint, 973 vcard21, null, appParamValue.ignorefilter, appParamValue.filter); 974 } 975 } else { 976 return mVcardManager.composeAndSendCallLogVcards(appParamValue.needTag, op, 977 startPoint + 1, endPoint + 1, vcard21, appParamValue.ignorefilter, 978 appParamValue.filter); 979 } 980 } 981 982 public static boolean closeStream(final OutputStream out, final Operation op) { 983 boolean returnvalue = true; 984 try { 985 if (out != null) { 986 out.close(); 987 } 988 } catch (IOException e) { 989 Log.e(TAG, "outputStream close failed" + e.toString()); 990 returnvalue = false; 991 } 992 try { 993 if (op != null) { 994 op.close(); 995 } 996 } catch (IOException e) { 997 Log.e(TAG, "operation close failed" + e.toString()); 998 returnvalue = false; 999 } 1000 return returnvalue; 1001 } 1002 1003 // Reserved for future use. In case PSE challenge PCE and PCE input wrong 1004 // session key. 1005 public final void onAuthenticationFailure(final byte[] userName) { 1006 } 1007 1008 public static final String createSelectionPara(final int type) { 1009 String selection = null; 1010 switch (type) { 1011 case ContentType.INCOMING_CALL_HISTORY: 1012 selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE; 1013 break; 1014 case ContentType.OUTGOING_CALL_HISTORY: 1015 selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE; 1016 break; 1017 case ContentType.MISSED_CALL_HISTORY: 1018 selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE; 1019 break; 1020 default: 1021 break; 1022 } 1023 if (V) Log.v(TAG, "Call log selection: " + selection); 1024 return selection; 1025 } 1026 1027 /** 1028 * XML encode special characters in the name field 1029 */ 1030 private void xmlEncode(String name, StringBuilder result) { 1031 if (name == null) { 1032 return; 1033 } 1034 1035 final StringCharacterIterator iterator = new StringCharacterIterator(name); 1036 char character = iterator.current(); 1037 while (character != CharacterIterator.DONE ){ 1038 if (character == '<') { 1039 result.append("<"); 1040 } 1041 else if (character == '>') { 1042 result.append(">"); 1043 } 1044 else if (character == '\"') { 1045 result.append("""); 1046 } 1047 else if (character == '\'') { 1048 result.append("'"); 1049 } 1050 else if (character == '&') { 1051 result.append("&"); 1052 } 1053 else { 1054 // The char is not a special one, add it to the result as is 1055 result.append(character); 1056 } 1057 character = iterator.next(); 1058 } 1059 } 1060 1061 private void writeVCardEntry(int vcfIndex, String name, StringBuilder result) { 1062 result.append("<card handle=\""); 1063 result.append(vcfIndex); 1064 result.append(".vcf\" name=\""); 1065 xmlEncode(name, result); 1066 result.append("\"/>"); 1067 } 1068 1069 private void notifyUpdateWakeLock() { 1070 Message msg = Message.obtain(mCallback); 1071 msg.what = BluetoothPbapService.MSG_ACQUIRE_WAKE_LOCK; 1072 msg.sendToTarget(); 1073 } 1074 1075 public static final void logHeader(HeaderSet hs) { 1076 Log.v(TAG, "Dumping HeaderSet " + hs.toString()); 1077 try { 1078 1079 Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT)); 1080 Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME)); 1081 Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE)); 1082 Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH)); 1083 Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601)); 1084 Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE)); 1085 Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION)); 1086 Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET)); 1087 Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP)); 1088 Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO)); 1089 Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS)); 1090 Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER)); 1091 } catch (IOException e) { 1092 Log.e(TAG, "dump HeaderSet error " + e); 1093 } 1094 } 1095 } 1096