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