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