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 // set initial value when ObexServer created 177 mMissedCallSize = mVcardManager.getPhonebookSize(ContentType.MISSED_CALL_HISTORY); 178 if (D) Log.d(TAG, "Initialize mMissedCallSize=" + mMissedCallSize); 179 } 180 181 @Override 182 public int onConnect(final HeaderSet request, HeaderSet reply) { 183 if (V) logHeader(request); 184 notifyUpdateWakeLock(); 185 try { 186 byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET); 187 if (uuid == null) { 188 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 189 } 190 if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid)); 191 192 if (uuid.length != UUID_LENGTH) { 193 Log.w(TAG, "Wrong UUID length"); 194 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 195 } 196 for (int i = 0; i < UUID_LENGTH; i++) { 197 if (uuid[i] != PBAP_TARGET[i]) { 198 Log.w(TAG, "Wrong UUID"); 199 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 200 } 201 } 202 reply.setHeader(HeaderSet.WHO, uuid); 203 } catch (IOException e) { 204 Log.e(TAG, e.toString()); 205 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 206 } 207 208 try { 209 byte[] remote = (byte[])request.getHeader(HeaderSet.WHO); 210 if (remote != null) { 211 if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote)); 212 reply.setHeader(HeaderSet.TARGET, remote); 213 } 214 } catch (IOException e) { 215 Log.e(TAG, e.toString()); 216 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 217 } 218 219 if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " + 220 "MSG_SESSION_ESTABLISHED msg."); 221 222 Message msg = Message.obtain(mCallback); 223 msg.what = BluetoothPbapService.MSG_SESSION_ESTABLISHED; 224 msg.sendToTarget(); 225 226 return ResponseCodes.OBEX_HTTP_OK; 227 } 228 229 @Override 230 public void onDisconnect(final HeaderSet req, final HeaderSet resp) { 231 if (D) Log.d(TAG, "onDisconnect(): enter"); 232 if (V) logHeader(req); 233 notifyUpdateWakeLock(); 234 resp.responseCode = ResponseCodes.OBEX_HTTP_OK; 235 if (mCallback != null) { 236 Message msg = Message.obtain(mCallback); 237 msg.what = BluetoothPbapService.MSG_SESSION_DISCONNECTED; 238 msg.sendToTarget(); 239 if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out."); 240 } 241 } 242 243 @Override 244 public int onAbort(HeaderSet request, HeaderSet reply) { 245 if (D) Log.d(TAG, "onAbort(): enter."); 246 notifyUpdateWakeLock(); 247 sIsAborted = true; 248 return ResponseCodes.OBEX_HTTP_OK; 249 } 250 251 @Override 252 public int onPut(final Operation op) { 253 if (D) Log.d(TAG, "onPut(): not support PUT request."); 254 notifyUpdateWakeLock(); 255 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 256 } 257 258 @Override 259 public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup, 260 final boolean create) { 261 if (V) logHeader(request); 262 if (D) Log.d(TAG, "before setPath, mCurrentPath == " + mCurrentPath); 263 notifyUpdateWakeLock(); 264 String current_path_tmp = mCurrentPath; 265 String tmp_path = null; 266 try { 267 tmp_path = (String)request.getHeader(HeaderSet.NAME); 268 } catch (IOException e) { 269 Log.e(TAG, "Get name header fail"); 270 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 271 } 272 if (D) Log.d(TAG, "backup=" + backup + " create=" + create + " name=" + tmp_path); 273 274 if (backup) { 275 if (current_path_tmp.length() != 0) { 276 current_path_tmp = current_path_tmp.substring(0, 277 current_path_tmp.lastIndexOf("/")); 278 } 279 } else { 280 if (tmp_path == null) { 281 current_path_tmp = ""; 282 } else { 283 current_path_tmp = current_path_tmp + "/" + tmp_path; 284 } 285 } 286 287 if ((current_path_tmp.length() != 0) && (!isLegalPath(current_path_tmp))) { 288 if (create) { 289 Log.w(TAG, "path create is forbidden!"); 290 return ResponseCodes.OBEX_HTTP_FORBIDDEN; 291 } else { 292 Log.w(TAG, "path is not legal"); 293 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 294 } 295 } 296 mCurrentPath = current_path_tmp; 297 if (V) Log.v(TAG, "after setPath, mCurrentPath == " + mCurrentPath); 298 299 return ResponseCodes.OBEX_HTTP_OK; 300 } 301 302 @Override 303 public void onClose() { 304 if (mCallback != null) { 305 Message msg = Message.obtain(mCallback); 306 msg.what = BluetoothPbapService.MSG_SERVERSESSION_CLOSE; 307 msg.sendToTarget(); 308 if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out."); 309 } 310 } 311 312 @Override 313 public int onGet(Operation op) { 314 notifyUpdateWakeLock(); 315 sIsAborted = false; 316 HeaderSet request = null; 317 HeaderSet reply = new HeaderSet(); 318 String type = ""; 319 String name = ""; 320 byte[] appParam = null; 321 AppParamValue appParamValue = new AppParamValue(); 322 try { 323 request = op.getReceivedHeader(); 324 type = (String)request.getHeader(HeaderSet.TYPE); 325 name = (String)request.getHeader(HeaderSet.NAME); 326 appParam = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER); 327 } catch (IOException e) { 328 Log.e(TAG, "request headers error"); 329 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 330 } 331 332 if (V) logHeader(request); 333 if (D) Log.d(TAG, "OnGet type is " + type + "; name is " + name); 334 335 if (type == null) { 336 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 337 } 338 // Accroding to specification,the name header could be omitted such as 339 // sony erriccsonHBH-DS980 340 341 // For "x-bt/phonebook" and "x-bt/vcard-listing": 342 // if name == null, guess what carkit actually want from current path 343 // For "x-bt/vcard": 344 // We decide which kind of content client would like per current path 345 346 boolean validName = true; 347 if (TextUtils.isEmpty(name)) { 348 validName = false; 349 } 350 351 if (!validName || (validName && type.equals(TYPE_VCARD))) { 352 if (D) Log.d(TAG, "Guess what carkit actually want from current path (" + 353 mCurrentPath + ")"); 354 355 if (mCurrentPath.equals(PB_PATH)) { 356 appParamValue.needTag = ContentType.PHONEBOOK; 357 } else if (mCurrentPath.equals(ICH_PATH)) { 358 appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY; 359 } else if (mCurrentPath.equals(OCH_PATH)) { 360 appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY; 361 } else if (mCurrentPath.equals(MCH_PATH)) { 362 appParamValue.needTag = ContentType.MISSED_CALL_HISTORY; 363 mNeedNewMissedCallsNum = true; 364 } else if (mCurrentPath.equals(CCH_PATH)) { 365 appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY; 366 } else { 367 Log.w(TAG, "mCurrentpath is not valid path!!!"); 368 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 369 } 370 if (D) Log.v(TAG, "onGet(): appParamValue.needTag=" + appParamValue.needTag); 371 } else { 372 // Not support SIM card currently 373 if (name.contains(SIM1.subSequence(0, SIM1.length()))) { 374 Log.w(TAG, "Not support access SIM card info!"); 375 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 376 } 377 378 // we have weak name checking here to provide better 379 // compatibility with other devices,although unique name such as 380 // "pb.vcf" is required by SIG spec. 381 if (name.contains(PB.subSequence(0, PB.length()))) { 382 appParamValue.needTag = ContentType.PHONEBOOK; 383 if (D) Log.v(TAG, "download phonebook request"); 384 } else if (name.contains(ICH.subSequence(0, ICH.length()))) { 385 appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY; 386 if (D) Log.v(TAG, "download incoming calls request"); 387 } else if (name.contains(OCH.subSequence(0, OCH.length()))) { 388 appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY; 389 if (D) Log.v(TAG, "download outgoing calls request"); 390 } else if (name.contains(MCH.subSequence(0, MCH.length()))) { 391 appParamValue.needTag = ContentType.MISSED_CALL_HISTORY; 392 mNeedNewMissedCallsNum = true; 393 if (D) Log.v(TAG, "download missed calls request"); 394 } else if (name.contains(CCH.subSequence(0, CCH.length()))) { 395 appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY; 396 if (D) Log.v(TAG, "download combined calls request"); 397 } else { 398 Log.w(TAG, "Input name doesn't contain valid info!!!"); 399 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 400 } 401 } 402 403 if ((appParam != null) && !parseApplicationParameter(appParam, appParamValue)) { 404 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 405 } 406 407 // listing request 408 if (type.equals(TYPE_LISTING)) { 409 return pullVcardListing(appParam, appParamValue, reply, op); 410 } 411 // pull vcard entry request 412 else if (type.equals(TYPE_VCARD)) { 413 return pullVcardEntry(appParam, appParamValue, op, name, mCurrentPath); 414 } 415 // down load phone book request 416 else if (type.equals(TYPE_PB)) { 417 return pullPhonebook(appParam, appParamValue, reply, op, name); 418 } else { 419 Log.w(TAG, "unknown type request!!!"); 420 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 421 } 422 } 423 424 /** check whether path is legal */ 425 private final boolean isLegalPath(final String str) { 426 if (str.length() == 0) { 427 return true; 428 } 429 for (int i = 0; i < LEGAL_PATH.length; i++) { 430 if (str.equals(LEGAL_PATH[i])) { 431 return true; 432 } 433 } 434 return false; 435 } 436 437 private class AppParamValue { 438 public int maxListCount; 439 440 public int listStartOffset; 441 442 public String searchValue; 443 444 // Indicate which vCard parameter the search operation shall be carried 445 // out on. Can be "Name | Number | Sound", default value is "Name". 446 public String searchAttr; 447 448 // Indicate which sorting order shall be used for the 449 // <x-bt/vcard-listing> listing object. 450 // Can be "Alphabetical | Indexed | Phonetical", default value is 451 // "Indexed". 452 public String order; 453 454 public int needTag; 455 456 public boolean vcard21; 457 458 public AppParamValue() { 459 maxListCount = 0xFFFF; 460 listStartOffset = 0; 461 searchValue = ""; 462 searchAttr = ""; 463 order = ""; 464 needTag = 0x00; 465 vcard21 = true; 466 } 467 468 public void dump() { 469 Log.i(TAG, "maxListCount=" + maxListCount + " listStartOffset=" + listStartOffset 470 + " searchValue=" + searchValue + " searchAttr=" + searchAttr + " needTag=" 471 + needTag + " vcard21=" + vcard21 + " order=" + order); 472 } 473 } 474 475 /** To parse obex application parameter */ 476 private final boolean parseApplicationParameter(final byte[] appParam, 477 AppParamValue appParamValue) { 478 int i = 0; 479 boolean parseOk = true; 480 while (i < appParam.length) { 481 switch (appParam[i]) { 482 case ApplicationParameter.TRIPLET_TAGID.FILTER_TAGID: 483 i += 2; // length and tag field in triplet 484 i += ApplicationParameter.TRIPLET_LENGTH.FILTER_LENGTH; 485 break; 486 case ApplicationParameter.TRIPLET_TAGID.ORDER_TAGID: 487 i += 2; // length and tag field in triplet 488 appParamValue.order = Byte.toString(appParam[i]); 489 i += ApplicationParameter.TRIPLET_LENGTH.ORDER_LENGTH; 490 break; 491 case ApplicationParameter.TRIPLET_TAGID.SEARCH_VALUE_TAGID: 492 i += 1; // length field in triplet 493 // length of search value is variable 494 int length = appParam[i]; 495 if (length == 0) { 496 parseOk = false; 497 break; 498 } 499 if (appParam[i+length] == 0x0) { 500 appParamValue.searchValue = new String(appParam, i + 1, length-1); 501 } else { 502 appParamValue.searchValue = new String(appParam, i + 1, length); 503 } 504 i += length; 505 i += 1; 506 break; 507 case ApplicationParameter.TRIPLET_TAGID.SEARCH_ATTRIBUTE_TAGID: 508 i += 2; 509 appParamValue.searchAttr = Byte.toString(appParam[i]); 510 i += ApplicationParameter.TRIPLET_LENGTH.SEARCH_ATTRIBUTE_LENGTH; 511 break; 512 case ApplicationParameter.TRIPLET_TAGID.MAXLISTCOUNT_TAGID: 513 i += 2; 514 if (appParam[i] == 0 && appParam[i + 1] == 0) { 515 mNeedPhonebookSize = true; 516 } else { 517 int highValue = appParam[i] & 0xff; 518 int lowValue = appParam[i + 1] & 0xff; 519 appParamValue.maxListCount = highValue * 256 + lowValue; 520 } 521 i += ApplicationParameter.TRIPLET_LENGTH.MAXLISTCOUNT_LENGTH; 522 break; 523 case ApplicationParameter.TRIPLET_TAGID.LISTSTARTOFFSET_TAGID: 524 i += 2; 525 int highValue = appParam[i] & 0xff; 526 int lowValue = appParam[i + 1] & 0xff; 527 appParamValue.listStartOffset = highValue * 256 + lowValue; 528 i += ApplicationParameter.TRIPLET_LENGTH.LISTSTARTOFFSET_LENGTH; 529 break; 530 case ApplicationParameter.TRIPLET_TAGID.FORMAT_TAGID: 531 i += 2;// length field in triplet 532 if (appParam[i] != 0) { 533 appParamValue.vcard21 = false; 534 } 535 i += ApplicationParameter.TRIPLET_LENGTH.FORMAT_LENGTH; 536 break; 537 default: 538 parseOk = false; 539 Log.e(TAG, "Parse Application Parameter error"); 540 break; 541 } 542 } 543 544 if (D) appParamValue.dump(); 545 546 return parseOk; 547 } 548 549 /** Form and Send an XML format String to client for Phone book listing */ 550 private final int sendVcardListingXml(final int type, Operation op, 551 final int maxListCount, final int listStartOffset, final String searchValue, 552 String searchAttr) { 553 StringBuilder result = new StringBuilder(); 554 int itemsFound = 0; 555 result.append("<?xml version=\"1.0\"?>"); 556 result.append("<!DOCTYPE vcard-listing SYSTEM \"vcard-listing.dtd\">"); 557 result.append("<vCard-listing version=\"1.0\">"); 558 559 // Phonebook listing request 560 if (type == ContentType.PHONEBOOK) { 561 if (searchAttr.equals("0")) { // search by name 562 itemsFound = createList(maxListCount, listStartOffset, searchValue, result, 563 "name"); 564 } else if (searchAttr.equals("1")) { // search by number 565 itemsFound = createList(maxListCount, listStartOffset, searchValue, result, 566 "number"); 567 }// end of search by number 568 else { 569 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 570 } 571 } 572 // Call history listing request 573 else { 574 ArrayList<String> nameList = mVcardManager.loadCallHistoryList(type); 575 int requestSize = nameList.size() >= maxListCount ? maxListCount : nameList.size(); 576 int startPoint = listStartOffset; 577 int endPoint = startPoint + requestSize; 578 if (endPoint > nameList.size()) { 579 endPoint = nameList.size(); 580 } 581 if (D) Log.d(TAG, "call log list, size=" + requestSize + " offset=" + listStartOffset); 582 583 for (int j = startPoint; j < endPoint; j++) { 584 writeVCardEntry(j+1, nameList.get(j),result); 585 } 586 } 587 result.append("</vCard-listing>"); 588 589 if (V) Log.v(TAG, "itemsFound =" + itemsFound); 590 591 return pushBytes(op, result.toString()); 592 } 593 594 private int createList(final int maxListCount, final int listStartOffset, 595 final String searchValue, StringBuilder result, String type) { 596 int itemsFound = 0; 597 ArrayList<String> nameList = mVcardManager.getPhonebookNameList(mOrderBy); 598 final int requestSize = nameList.size() >= maxListCount ? maxListCount : nameList.size(); 599 final int listSize = nameList.size(); 600 String compareValue = "", currentValue; 601 602 if (D) Log.d(TAG, "search by " + type + ", requestSize=" + requestSize + " offset=" 603 + listStartOffset + " searchValue=" + searchValue); 604 605 if (type.equals("number")) { 606 // query the number, to get the names 607 ArrayList<String> names = mVcardManager.getContactNamesByNumber(searchValue); 608 for (int i = 0; i < names.size(); i++) { 609 compareValue = names.get(i).trim(); 610 if (D) Log.d(TAG, "compareValue=" + compareValue); 611 for (int pos = listStartOffset; pos < listSize && 612 itemsFound < requestSize; pos++) { 613 currentValue = nameList.get(pos); 614 if (D) Log.d(TAG, "currentValue=" + currentValue); 615 if (currentValue.startsWith(compareValue)) { 616 itemsFound++; 617 writeVCardEntry(pos, currentValue,result); 618 } 619 } 620 if (itemsFound >= requestSize) { 621 break; 622 } 623 } 624 } else { 625 if (searchValue != null) { 626 compareValue = searchValue.trim(); 627 } 628 for (int pos = listStartOffset; pos < listSize && 629 itemsFound < requestSize; pos++) { 630 currentValue = nameList.get(pos); 631 if (D) Log.d(TAG, "currentValue=" + currentValue); 632 if (searchValue == null || currentValue.startsWith(compareValue)) { 633 itemsFound++; 634 writeVCardEntry(pos, currentValue,result); 635 } 636 } 637 } 638 return itemsFound; 639 } 640 641 /** 642 * Function to send obex header back to client such as get phonebook size 643 * request 644 */ 645 private final int pushHeader(final Operation op, final HeaderSet reply) { 646 OutputStream outputStream = null; 647 648 if (D) Log.d(TAG, "Push Header"); 649 if (D) Log.d(TAG, reply.toString()); 650 651 int pushResult = ResponseCodes.OBEX_HTTP_OK; 652 try { 653 op.sendHeaders(reply); 654 outputStream = op.openOutputStream(); 655 outputStream.flush(); 656 } catch (IOException e) { 657 Log.e(TAG, e.toString()); 658 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 659 } finally { 660 if (!closeStream(outputStream, op)) { 661 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 662 } 663 } 664 return pushResult; 665 } 666 667 /** Function to send vcard data to client */ 668 private final int pushBytes(Operation op, final String vcardString) { 669 if (vcardString == null) { 670 Log.w(TAG, "vcardString is null!"); 671 return ResponseCodes.OBEX_HTTP_OK; 672 } 673 674 OutputStream outputStream = null; 675 int pushResult = ResponseCodes.OBEX_HTTP_OK; 676 try { 677 outputStream = op.openOutputStream(); 678 outputStream.write(vcardString.getBytes()); 679 if (V) Log.v(TAG, "Send Data complete!"); 680 } catch (IOException e) { 681 Log.e(TAG, "open/write outputstrem failed" + e.toString()); 682 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 683 } 684 685 if (!closeStream(outputStream, op)) { 686 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 687 } 688 689 return pushResult; 690 } 691 692 private final int handleAppParaForResponse(AppParamValue appParamValue, int size, 693 HeaderSet reply, Operation op) { 694 byte[] misnum = new byte[1]; 695 ApplicationParameter ap = new ApplicationParameter(); 696 697 // In such case, PCE only want the number of index. 698 // So response not contain any Body header. 699 if (mNeedPhonebookSize) { 700 if (V) Log.v(TAG, "Need Phonebook size in response header."); 701 mNeedPhonebookSize = false; 702 703 byte[] pbsize = new byte[2]; 704 705 pbsize[0] = (byte)((size / 256) & 0xff);// HIGH VALUE 706 pbsize[1] = (byte)((size % 256) & 0xff);// LOW VALUE 707 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID, 708 ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize); 709 710 if (mNeedNewMissedCallsNum) { 711 mNeedNewMissedCallsNum = false; 712 int nmnum = size - mMissedCallSize; 713 mMissedCallSize = size; 714 715 nmnum = nmnum > 0 ? nmnum : 0; 716 misnum[0] = (byte)nmnum; 717 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID, 718 ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum); 719 if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true, num= " 720 + nmnum); 721 } 722 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 723 724 if (D) Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size); 725 726 return pushHeader(op, reply); 727 } 728 729 // Only apply to "mch" download/listing. 730 // NewMissedCalls is used only in the response, together with Body 731 // header. 732 if (mNeedNewMissedCallsNum) { 733 if (V) Log.v(TAG, "Need new missed call num in response header."); 734 mNeedNewMissedCallsNum = false; 735 736 int nmnum = size - mMissedCallSize; 737 mMissedCallSize = size; 738 739 nmnum = nmnum > 0 ? nmnum : 0; 740 misnum[0] = (byte)nmnum; 741 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID, 742 ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum); 743 reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam()); 744 if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true, num= " 745 + nmnum); 746 747 // Only Specifies the headers, not write for now, will write to PCE 748 // together with Body 749 try { 750 op.sendHeaders(reply); 751 } catch (IOException e) { 752 Log.e(TAG, e.toString()); 753 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 754 } 755 } 756 return NEED_SEND_BODY; 757 } 758 759 private final int pullVcardListing(byte[] appParam, AppParamValue appParamValue, 760 HeaderSet reply, Operation op) { 761 String searchAttr = appParamValue.searchAttr.trim(); 762 763 if (searchAttr == null || searchAttr.length() == 0) { 764 // If searchAttr is not set by PCE, set default value per spec. 765 appParamValue.searchAttr = "0"; 766 if (D) Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default"); 767 } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) { 768 Log.w(TAG, "search attr not supported"); 769 if (searchAttr.equals("2")) { 770 // search by sound is not supported currently 771 Log.w(TAG, "do not support search by sound"); 772 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 773 } 774 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 775 } else { 776 Log.i(TAG, "searchAttr is valid: " + searchAttr); 777 } 778 779 int size = mVcardManager.getPhonebookSize(appParamValue.needTag); 780 int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op); 781 if (needSendBody != NEED_SEND_BODY) { 782 return needSendBody; 783 } 784 785 if (size == 0) { 786 if (V) Log.v(TAG, "PhonebookSize is 0, return."); 787 return ResponseCodes.OBEX_HTTP_OK; 788 } 789 790 String orderPara = appParamValue.order.trim(); 791 if (TextUtils.isEmpty(orderPara)) { 792 // If order parameter is not set by PCE, set default value per spec. 793 orderPara = "0"; 794 if (D) Log.d(TAG, "Order parameter is not set by PCE. " + 795 "Assume order by 'Indexed' by default"); 796 } else if (!orderPara.equals("0") && !orderPara.equals("1")) { 797 if (V) Log.v(TAG, "Order parameter is not supported: " + appParamValue.order); 798 if (orderPara.equals("2")) { 799 // Order by sound is not supported currently 800 Log.w(TAG, "Do not support order by sound"); 801 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 802 } 803 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 804 } else { 805 Log.i(TAG, "Order parameter is valid: " + orderPara); 806 } 807 808 if (orderPara.equals("0")) { 809 mOrderBy = ORDER_BY_INDEXED; 810 } else if (orderPara.equals("1")) { 811 mOrderBy = ORDER_BY_ALPHABETICAL; 812 } 813 814 int sendResult = sendVcardListingXml(appParamValue.needTag, op, appParamValue.maxListCount, 815 appParamValue.listStartOffset, appParamValue.searchValue, 816 appParamValue.searchAttr); 817 return sendResult; 818 } 819 820 private final int pullVcardEntry(byte[] appParam, AppParamValue appParamValue, 821 Operation op, final String name, final String current_path) { 822 if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) { 823 if (D) Log.d(TAG, "Name is Null, or the length of name < 5 !"); 824 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 825 } 826 String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1); 827 int intIndex = 0; 828 if (strIndex.trim().length() != 0) { 829 try { 830 intIndex = Integer.parseInt(strIndex); 831 } catch (NumberFormatException e) { 832 Log.e(TAG, "catch number format exception " + e.toString()); 833 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 834 } 835 } 836 837 int size = mVcardManager.getPhonebookSize(appParamValue.needTag); 838 if (size == 0) { 839 if (V) Log.v(TAG, "PhonebookSize is 0, return."); 840 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 841 } 842 843 boolean vcard21 = appParamValue.vcard21; 844 if (appParamValue.needTag == 0) { 845 Log.w(TAG, "wrong path!"); 846 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 847 } else if (appParamValue.needTag == ContentType.PHONEBOOK) { 848 if (intIndex < 0 || intIndex >= size) { 849 Log.w(TAG, "The requested vcard is not acceptable! name= " + name); 850 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 851 } else if (intIndex == 0) { 852 // For PB_PATH, 0.vcf is the phone number of this phone. 853 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,null); 854 return pushBytes(op, ownerVcard); 855 } else { 856 return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null, 857 mOrderBy ); 858 } 859 } else { 860 if (intIndex <= 0 || intIndex > size) { 861 Log.w(TAG, "The requested vcard is not acceptable! name= " + name); 862 return ResponseCodes.OBEX_HTTP_NOT_FOUND; 863 } 864 // For others (ich/och/cch/mch), 0.vcf is meaningless, and must 865 // begin from 1.vcf 866 if (intIndex >= 1) { 867 return mVcardManager.composeAndSendCallLogVcards(appParamValue.needTag, op, 868 intIndex, intIndex, vcard21); 869 } 870 } 871 return ResponseCodes.OBEX_HTTP_OK; 872 } 873 874 private final int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, 875 Operation op, final String name) { 876 // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C 877 if (name != null) { 878 int dotIndex = name.indexOf("."); 879 String vcf = "vcf"; 880 if (dotIndex >= 0 && dotIndex <= name.length()) { 881 if (name.regionMatches(dotIndex + 1, vcf, 0, vcf.length()) == false) { 882 Log.w(TAG, "name is not .vcf"); 883 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 884 } 885 } 886 } // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C 887 888 int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag); 889 int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op); 890 if (needSendBody != NEED_SEND_BODY) { 891 return needSendBody; 892 } 893 894 if (pbSize == 0) { 895 if (V) Log.v(TAG, "PhonebookSize is 0, return."); 896 return ResponseCodes.OBEX_HTTP_OK; 897 } 898 899 int requestSize = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount 900 : pbSize; 901 int startPoint = appParamValue.listStartOffset; 902 if (startPoint < 0 || startPoint >= pbSize) { 903 Log.w(TAG, "listStartOffset is not correct! " + startPoint); 904 return ResponseCodes.OBEX_HTTP_OK; 905 } 906 907 // Limit the number of call log to CALLLOG_NUM_LIMIT 908 if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) { 909 if (requestSize > CALLLOG_NUM_LIMIT) { 910 requestSize = CALLLOG_NUM_LIMIT; 911 } 912 } 913 914 int endPoint = startPoint + requestSize - 1; 915 if (endPoint > pbSize - 1) { 916 endPoint = pbSize - 1; 917 } 918 if (D) Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" + 919 startPoint + " endPoint=" + endPoint); 920 921 boolean vcard21 = appParamValue.vcard21; 922 if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) { 923 if (startPoint == 0) { 924 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,null); 925 if (endPoint == 0) { 926 return pushBytes(op, ownerVcard); 927 } else { 928 return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21, 929 ownerVcard); 930 } 931 } else { 932 return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint, 933 vcard21, null); 934 } 935 } else { 936 return mVcardManager.composeAndSendCallLogVcards(appParamValue.needTag, op, 937 startPoint + 1, endPoint + 1, vcard21); 938 } 939 } 940 941 public static boolean closeStream(final OutputStream out, final Operation op) { 942 boolean returnvalue = true; 943 try { 944 if (out != null) { 945 out.close(); 946 } 947 } catch (IOException e) { 948 Log.e(TAG, "outputStream close failed" + e.toString()); 949 returnvalue = false; 950 } 951 try { 952 if (op != null) { 953 op.close(); 954 } 955 } catch (IOException e) { 956 Log.e(TAG, "operation close failed" + e.toString()); 957 returnvalue = false; 958 } 959 return returnvalue; 960 } 961 962 // Reserved for future use. In case PSE challenge PCE and PCE input wrong 963 // session key. 964 public final void onAuthenticationFailure(final byte[] userName) { 965 } 966 967 public static final String createSelectionPara(final int type) { 968 String selection = null; 969 switch (type) { 970 case ContentType.INCOMING_CALL_HISTORY: 971 selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE; 972 break; 973 case ContentType.OUTGOING_CALL_HISTORY: 974 selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE; 975 break; 976 case ContentType.MISSED_CALL_HISTORY: 977 selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE; 978 break; 979 default: 980 break; 981 } 982 if (V) Log.v(TAG, "Call log selection: " + selection); 983 return selection; 984 } 985 986 /** 987 * XML encode special characters in the name field 988 */ 989 private void xmlEncode(String name, StringBuilder result) { 990 if (name == null) { 991 return; 992 } 993 994 final StringCharacterIterator iterator = new StringCharacterIterator(name); 995 char character = iterator.current(); 996 while (character != CharacterIterator.DONE ){ 997 if (character == '<') { 998 result.append("<"); 999 } 1000 else if (character == '>') { 1001 result.append(">"); 1002 } 1003 else if (character == '\"') { 1004 result.append("""); 1005 } 1006 else if (character == '\'') { 1007 result.append("'"); 1008 } 1009 else if (character == '&') { 1010 result.append("&"); 1011 } 1012 else { 1013 // The char is not a special one, add it to the result as is 1014 result.append(character); 1015 } 1016 character = iterator.next(); 1017 } 1018 } 1019 1020 private void writeVCardEntry(int vcfIndex, String name, StringBuilder result) { 1021 result.append("<card handle=\""); 1022 result.append(vcfIndex); 1023 result.append(".vcf\" name=\""); 1024 xmlEncode(name, result); 1025 result.append("\"/>"); 1026 } 1027 1028 private void notifyUpdateWakeLock() { 1029 Message msg = Message.obtain(mCallback); 1030 msg.what = BluetoothPbapService.MSG_ACQUIRE_WAKE_LOCK; 1031 msg.sendToTarget(); 1032 } 1033 1034 public static final void logHeader(HeaderSet hs) { 1035 Log.v(TAG, "Dumping HeaderSet " + hs.toString()); 1036 try { 1037 1038 Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT)); 1039 Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME)); 1040 Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE)); 1041 Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH)); 1042 Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601)); 1043 Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE)); 1044 Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION)); 1045 Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET)); 1046 Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP)); 1047 Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO)); 1048 Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS)); 1049 Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER)); 1050 } catch (IOException e) { 1051 Log.e(TAG, "dump HeaderSet error " + e); 1052 } 1053 } 1054 } 1055