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