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