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