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