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