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