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