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