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