BluetoothMapbMessage.java revision fd6603b8bf9ed72dcc8bd59aaef3209251b6e17c
1/* 2* Copyright (C) 2013 Samsung System LSI 3* Licensed under the Apache License, Version 2.0 (the "License"); 4* you may not use this file except in compliance with the License. 5* You may obtain a copy of the License at 6* 7* http://www.apache.org/licenses/LICENSE-2.0 8* 9* Unless required by applicable law or agreed to in writing, software 10* distributed under the License is distributed on an "AS IS" BASIS, 11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12* See the License for the specific language governing permissions and 13* limitations under the License. 14*/ 15package com.android.bluetooth.map; 16 17import java.io.ByteArrayOutputStream; 18import java.io.IOException; 19import java.io.InputStream; 20import java.io.UnsupportedEncodingException; 21import java.util.ArrayList; 22 23import android.telephony.PhoneNumberUtils; 24import android.util.Log; 25import com.android.bluetooth.map.BluetoothMapUtils.TYPE; 26 27public abstract class BluetoothMapbMessage { 28 29 protected static String TAG = "BluetoothMapbMessage"; 30 protected static final boolean D = true; 31 protected static final boolean V = true; 32 private static final String VERSION = "VERSION:1.0"; 33 34 public static int INVALID_VALUE = -1; 35 36 protected int appParamCharset = BluetoothMapAppParams.INVALID_VALUE_PARAMETER; 37 38 // TODO: Reevaluate if strings are the best types for the members. 39 40 /* BMSG attributes */ 41 private String status = null; // READ/UNREAD 42 protected TYPE type = null; // SMS/MMS/EMAIL 43 44 private String folder = null; 45 46 /* BBODY attributes */ 47 private long partId = INVALID_VALUE; 48 protected String encoding = null; 49 protected String charset = null; 50 private String language = null; 51 52 private int bMsgLength = INVALID_VALUE; 53 54 private ArrayList<vCard> originator = null; 55 private ArrayList<vCard> recipient = null; 56 57 58 public static class vCard { 59 /* VCARD attributes */ 60 private String version; 61 private String name = null; 62 private String formattedName = null; 63 private String[] phoneNumbers = {}; 64 private String[] emailAddresses = {}; 65 private int envLevel = 0; 66 67 /** 68 * Construct a version 3.0 vCard 69 * @param name Structured 70 * @param formattedName Formatted name 71 * @param phoneNumbers a String[] of phone numbers 72 * @param emailAddresses a String[] of email addresses 73 * @param the bmessage envelope level (0 is the top/most outer level) 74 */ 75 public vCard(String name, String formattedName, String[] phoneNumbers, 76 String[] emailAddresses, int envLevel) { 77 this.envLevel = envLevel; 78 this.version = "3.0"; 79 this.name = name != null ? name : ""; 80 this.formattedName = formattedName != null ? formattedName : ""; 81 setPhoneNumbers(phoneNumbers); 82 if (emailAddresses != null) 83 this.emailAddresses = emailAddresses; 84 } 85 86 /** 87 * Construct a version 2.1 vCard 88 * @param name Structured name 89 * @param phoneNumbers a String[] of phone numbers 90 * @param emailAddresses a String[] of email addresses 91 * @param the bmessage envelope level (0 is the top/most outer level) 92 */ 93 public vCard(String name, String[] phoneNumbers, 94 String[] emailAddresses, int envLevel) { 95 this.envLevel = envLevel; 96 this.version = "2.1"; 97 this.name = name != null ? name : ""; 98 setPhoneNumbers(phoneNumbers); 99 if (emailAddresses != null) 100 this.emailAddresses = emailAddresses; 101 } 102 103 /** 104 * Construct a version 3.0 vCard 105 * @param name Structured name 106 * @param formattedName Formatted name 107 * @param phoneNumbers a String[] of phone numbers 108 * @param emailAddresses a String[] of email addresses 109 */ 110 public vCard(String name, String formattedName, String[] phoneNumbers, String[] emailAddresses) { 111 this.version = "3.0"; 112 this.name = name != null ? name : ""; 113 this.formattedName = formattedName != null ? formattedName : ""; 114 setPhoneNumbers(phoneNumbers); 115 if (emailAddresses != null) 116 this.emailAddresses = emailAddresses; 117 } 118 119 /** 120 * Construct a version 2.1 vCard 121 * @param name Structured Name 122 * @param phoneNumbers a String[] of phone numbers 123 * @param emailAddresses a String[] of email addresses 124 */ 125 public vCard(String name, String[] phoneNumbers, String[] emailAddresses) { 126 this.version = "2.1"; 127 this.name = name != null ? name : ""; 128 setPhoneNumbers(phoneNumbers); 129 if (emailAddresses != null) 130 this.emailAddresses = emailAddresses; 131 } 132 133 private void setPhoneNumbers(String[] numbers) { 134 if(numbers != null && numbers.length > 0) 135 { 136 phoneNumbers = new String[numbers.length]; 137 for(int i = 0, n = numbers.length; i < n; i++){ 138 phoneNumbers[i] = PhoneNumberUtils.extractNetworkPortion(numbers[i]); 139 } 140 } 141 } 142 143 public String getFirstPhoneNumber() { 144 if(phoneNumbers.length > 0) { 145 return phoneNumbers[0]; 146 } else 147 throw new IllegalArgumentException("No Phone number"); 148 } 149 150 public int getEnvLevel() { 151 return envLevel; 152 } 153 154 public void encode(StringBuilder sb) 155 { 156 sb.append("BEGIN:VCARD").append("\r\n"); 157 sb.append("VERSION:").append(version).append("\r\n"); 158 if (version.equals("3.0") && formattedName != null) 159 { 160 sb.append("FN:").append(formattedName).append("\r\n"); 161 } 162 if (name != null) 163 sb.append("N:").append(name).append("\r\n"); 164 for (String phoneNumber : phoneNumbers) 165 { 166 sb.append("TEL:").append(phoneNumber).append("\r\n"); 167 } 168 for (String emailAddress : emailAddresses) 169 { 170 sb.append("EMAIL:").append(emailAddress).append("\r\n"); 171 } 172 sb.append("END:VCARD").append("\r\n"); 173 } 174 175 /** 176 * Parse a vCard from a BMgsReader, where a line containing "BEGIN:VCARD" have just been read. 177 * @param reader 178 * @param originator 179 * @return 180 */ 181 public static vCard parseVcard(BMsgReader reader, int envLevel) { 182 String formattedName = null; 183 String name = null; 184 ArrayList<String> phoneNumbers = null; 185 ArrayList<String> emailAddresses = null; 186 String[] parts; 187 String line = reader.getLineEnforce(); 188 189 while(!line.contains("END:VCARD")) { 190 line = line.trim(); 191 if(line.startsWith("N:")){ 192 parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':' 193 if(parts.length == 2) { 194 name = parts[1]; 195 } else 196 name = ""; 197 } 198 else if(line.startsWith("FN:")){ 199 parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':' 200 if(parts.length == 2) { 201 formattedName = parts[1]; 202 } else 203 formattedName = ""; 204 } 205 else if(line.startsWith("TEL:")){ 206 parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':' 207 if(parts.length == 2) { 208 String[] subParts = parts[1].split("[^\\\\];"); 209 if(phoneNumbers == null) 210 phoneNumbers = new ArrayList<String>(1); 211 phoneNumbers.add(subParts[subParts.length-1]); // only keep actual phone number 212 } else {} 213 // Empty phone number - ignore 214 } 215 else if(line.startsWith("EMAIL:")){ 216 parts = line.split("[^\\\\]:"); // Split on "un-escaped" : 217 if(parts.length == 2) { 218 String[] subParts = parts[1].split("[^\\\\];"); 219 if(emailAddresses == null) 220 emailAddresses = new ArrayList<String>(1); 221 emailAddresses.add(subParts[subParts.length-1]); // only keep actual email address 222 } else {} 223 // Empty email address entry - ignore 224 } 225 line = reader.getLineEnforce(); 226 } 227 return new vCard(name, formattedName, 228 phoneNumbers == null? null : phoneNumbers.toArray(new String[phoneNumbers.size()]), 229 emailAddresses == null ? null : emailAddresses.toArray(new String[emailAddresses.size()]), 230 envLevel); 231 } 232 }; 233 234 private static class BMsgReader { 235 InputStream mInStream; 236 public BMsgReader(InputStream is) 237 { 238 this.mInStream = is; 239 } 240 241 private byte[] getLineAsBytes() { 242 int readByte; 243 244 /* TODO: Actually the vCard spec. allows to break lines by using a newLine 245 * followed by a white space character(space or tab). Not sure this is a good idea to implement 246 * as the Bluetooth MAP spec. illustrates vCards using tab alignment, hence actually 247 * showing an invalid vCard format... 248 * If we read such a folded line, the folded part will be skipped in the parser 249 */ 250 251 ByteArrayOutputStream output = new ByteArrayOutputStream(); 252 try { 253 while ((readByte = mInStream.read()) != -1) { 254 if (readByte == '\r') { 255 if ((readByte = mInStream.read()) != -1 && readByte == '\n') { 256 if(output.size() == 0) 257 continue; /* Skip empty lines */ 258 else 259 break; 260 } else { 261 output.write('\r'); 262 } 263 } else if (readByte == '\n' && output.size() == 0) { 264 /* Empty line - skip */ 265 continue; 266 } 267 268 output.write(readByte); 269 } 270 } catch (IOException e) { 271 Log.w(TAG, e); 272 return null; 273 } 274 return output.toByteArray(); 275 } 276 277 /** 278 * Read a line of text from the BMessage. 279 * @return the next line of text, or null at end of file, or if UTF-8 is not supported. 280 */ 281 public String getLine() { 282 try { 283 byte[] line = getLineAsBytes(); 284 if (line.length == 0) 285 return null; 286 else 287 return new String(line, "UTF-8"); 288 } catch (UnsupportedEncodingException e) { 289 Log.w(TAG, e); 290 return null; 291 } 292 } 293 294 /** 295 * same as getLine(), but throws an exception, if we run out of lines. 296 * Use this function when ever more lines are needed for the bMessage to be complete. 297 * @return the next line 298 */ 299 public String getLineEnforce() { 300 String line = getLine(); 301 if (line == null) 302 throw new IllegalArgumentException("Bmessage too short"); 303 return line; 304 } 305 306 307 /** 308 * Reads a line from the InputStream, and examines if the subString 309 * matches the line read. 310 * @param subString 311 * The string to match against the line. 312 * @throws IllegalArgumentException 313 * If the expected substring is not found. 314 * 315 */ 316 public void expect(String subString) throws IllegalArgumentException{ 317 String line = getLine(); 318 if (!line.contains(subString)) 319 // TODO: Should this be case insensitive? (Either use toUpper() or matches()) 320 throw new IllegalArgumentException("Expected \"" + subString + "\" in: \"" + line + "\""); 321 } 322 323 /** 324 * Same as expect(String), but with two strings. 325 * @param subString 326 * @param subString2 327 * @throws IllegalArgumentException 328 * If one or all of the strings are not found. 329 */ 330 public void expect(String subString, String subString2) throws IllegalArgumentException{ 331 String line = getLine(); 332 if(!line.contains(subString)) // TODO: Should this be case insensitive? (Either use toUpper() or matches()) 333 throw new IllegalArgumentException("Expected \"" + subString + "\" in: \"" + line + "\""); 334 if(!line.contains(subString2)) // TODO: Should this be case insensitive? (Either use toUpper() or matches()) 335 throw new IllegalArgumentException("Expected \"" + subString + "\" in: \"" + line + "\""); 336 } 337 338 /** 339 * Read a part of the bMessage as raw data. 340 * @param length the number of bytes to read 341 * @return the byte[] containing the number of bytes or null if an error occurs or EOF is reached 342 * before length bytes have been read. 343 */ 344 public byte[] getDataBytes(int length) { 345 byte[] data = new byte[length]; 346 try { 347 int bytesRead; 348 int offset=0; 349 while ((bytesRead = mInStream.read(data, offset, length-offset)) != length) { 350 if(bytesRead == -1) 351 return null; 352 offset += bytesRead; 353 } 354 } catch (IOException e) { 355 Log.w(TAG, e); 356 return null; 357 } 358 return data; 359 } 360 }; 361 362 public BluetoothMapbMessage() { 363 364 } 365 366 public static BluetoothMapbMessage parse(InputStream bMsgStream, int appParamCharset) throws IllegalArgumentException{ 367 BMsgReader reader = new BMsgReader(bMsgStream); 368 String line = ""; 369 BluetoothMapbMessage newBMsg = null; 370 reader.expect("BEGIN:BMSG"); 371 reader.expect("VERSION","1.0"); 372 boolean status = false; 373 boolean statusFound = false; 374 TYPE type = null; 375 String folder = null; 376 377 line = reader.getLineEnforce(); 378 // Parse the properties - which end with either a VCARD or a BENV 379 while(!line.contains("BEGIN:VCARD") && !line.contains("BEGIN:BENV")) { 380 if(line.contains("STATUS")){ 381 String arg[] = line.split(":"); 382 if (arg != null && arg.length == 2) { 383 if (arg[1].trim().equals("READ")) { 384 status = true; 385 } else if (arg[1].trim().equals("UNREAD")) { 386 status =false; 387 } else { 388 throw new IllegalArgumentException("Wrong value in 'STATUS': " + arg[1]); 389 } 390 } else { 391 throw new IllegalArgumentException("Missing value for 'STATUS': " + line); 392 } 393 } 394 if(line.contains("TYPE")) { 395 String arg[] = line.split(":"); 396 if (arg != null && arg.length == 2) { 397 String value = arg[1].trim(); 398 type = TYPE.valueOf(value); // Will throw IllegalArgumentException if value is wrong 399 if(appParamCharset == BluetoothMapAppParams.CHARSET_NATIVE 400 && type != TYPE.SMS_CDMA && type != TYPE.SMS_GSM) { 401 throw new IllegalArgumentException("Native appParamsCharset only supported for SMS"); 402 } 403 switch(type) { 404 case SMS_CDMA: 405 case SMS_GSM: 406 newBMsg = new BluetoothMapbMessageSms(); 407 break; 408 case MMS: 409 case EMAIL: 410 newBMsg = new BluetoothMapbMessageMmsEmail(); 411 break; 412 default: 413 break; 414 } 415 } else { 416 throw new IllegalArgumentException("Missing value for 'TYPE':" + line); 417 } 418 } 419 if(line.contains("FOLDER")) { 420 String[] arg = line.split(":"); 421 if (arg != null && arg.length == 2) { 422 folder = arg[1].trim(); 423 } else { 424 throw new IllegalArgumentException("Missing value for 'FOLDER':" + line); 425 } 426 } 427 line = reader.getLineEnforce(); 428 } 429 if(newBMsg == null) 430 throw new IllegalArgumentException("Missing bMessage TYPE: - unable to parse body-content"); 431 newBMsg.setType(type); 432 newBMsg.appParamCharset = appParamCharset; 433 if(folder != null) 434 newBMsg.setFolder(folder); 435 if(statusFound) 436 newBMsg.setStatus(status); 437 438 // Now check for originator VCARDs 439 while(line.contains("BEGIN:VCARD")){ 440 if(D) Log.d(TAG,"Decoding vCard"); 441 newBMsg.addOriginator(vCard.parseVcard(reader,0)); 442 line = reader.getLineEnforce(); 443 } 444 if(line.contains("BEGIN:BENV")) { 445 newBMsg.parseEnvelope(reader, 0); 446 } else 447 throw new IllegalArgumentException("Bmessage has no BEGIN:BENV - line:" + line); 448 449 /* TODO: Do we need to validate the END:* tags? They are only needed if someone puts additional info 450 * below the END:MSG - in which case we don't handle it. 451 */ 452 return newBMsg; 453 } 454 455 private void parseEnvelope(BMsgReader reader, int level) { 456 String line; 457 line = reader.getLineEnforce(); 458 if(D) Log.d(TAG,"Decoding envelope level " + level); 459 460 while(line.contains("BEGIN:VCARD")){ 461 if(D) Log.d(TAG,"Decoding recipient vCard level " + level); 462 if(recipient == null) 463 recipient = new ArrayList<vCard>(1); 464 recipient.add(vCard.parseVcard(reader, level)); 465 line = reader.getLineEnforce(); 466 } 467 if(line.contains("BEGIN:BENV")) { 468 if(D) Log.d(TAG,"Decoding nested envelope"); 469 parseEnvelope(reader, ++level); // Nested BENV 470 } 471 if(line.contains("BEGIN:BBODY")){ 472 if(D) Log.d(TAG,"Decoding bbody"); 473 parseBody(reader); 474 } 475 } 476 477 private void parseBody(BMsgReader reader) { 478 String line; 479 line = reader.getLineEnforce(); 480 while(!line.contains("END:")) { 481 if(line.contains("PARTID:")) { 482 String arg[] = line.split(":"); 483 if (arg != null && arg.length == 2) { 484 try { 485 partId = Long.parseLong(arg[1].trim()); 486 } catch (NumberFormatException e) { 487 throw new IllegalArgumentException("Wrong value in 'PARTID': " + arg[1]); 488 } 489 } else { 490 throw new IllegalArgumentException("Missing value for 'PARTID': " + line); 491 } 492 } 493 else if(line.contains("ENCODING:")) { 494 String arg[] = line.split(":"); 495 if (arg != null && arg.length == 2) { 496 encoding = arg[1].trim(); // TODO: Validate ? 497 } else { 498 throw new IllegalArgumentException("Missing value for 'ENCODING': " + line); 499 } 500 } 501 else if(line.contains("CHARSET:")) { 502 String arg[] = line.split(":"); 503 if (arg != null && arg.length == 2) { 504 charset = arg[1].trim(); // TODO: Validate ? 505 } else { 506 throw new IllegalArgumentException("Missing value for 'CHARSET': " + line); 507 } 508 } 509 else if(line.contains("LANGUAGE:")) { 510 String arg[] = line.split(":"); 511 if (arg != null && arg.length == 2) { 512 language = arg[1].trim(); // TODO: Validate ? 513 } else { 514 throw new IllegalArgumentException("Missing value for 'LANGUAGE': " + line); 515 } 516 } 517 else if(line.contains("LENGTH:")) { 518 String arg[] = line.split(":"); 519 if (arg != null && arg.length == 2) { 520 try { 521 bMsgLength = Integer.parseInt(arg[1].trim()); 522 } catch (NumberFormatException e) { 523 throw new IllegalArgumentException("Wrong value in 'LENGTH': " + arg[1]); 524 } 525 } else { 526 throw new IllegalArgumentException("Missing value for 'LENGTH': " + line); 527 } 528 } 529 else if(line.contains("BEGIN:MSG")) { 530 if(bMsgLength == INVALID_VALUE) 531 throw new IllegalArgumentException("Missing value for 'LENGTH'. Unable to read remaining part of the message"); 532 // For SMS: Encoding of MSG is always UTF-8 compliant, regardless of any properties, since PDUs are encodes as hex-strings 533 /* PTS has a bug regarding the message length, and sets it 2 bytes too short, hence 534 * using the length field to determine the amount of data to read, might not be the 535 * best solution. 536 * Since errata ???(bluetooth.org is down at the moment) introduced escaping of END:MSG 537 * in the actual message content, it is now safe to use the END:MSG tag as terminator, 538 * and simply ignore the length field.*/ 539 byte[] rawData = reader.getDataBytes(bMsgLength - (line.getBytes().length + 2)); // 2 added to compensate for the removed \r\n 540 String data; 541 try { 542 data = new String(rawData, "UTF-8"); 543 if(V) { 544 Log.v(TAG,"MsgLength: " + bMsgLength); 545 Log.v(TAG,"line.getBytes().length: " + line.getBytes().length); 546 String debug = line.replaceAll("\\n", "<LF>\n"); 547 debug = debug.replaceAll("\\r", "<CR>"); 548 Log.v(TAG,"The line: \"" + debug + "\""); 549 debug = data.replaceAll("\\n", "<LF>\n"); 550 debug = debug.replaceAll("\\r", "<CR>"); 551 Log.v(TAG,"The msgString: \"" + debug + "\""); 552 } 553 } catch (UnsupportedEncodingException e) { 554 Log.w(TAG,e); 555 throw new IllegalArgumentException("Unable to convert to UTF-8"); 556 } 557 /* Decoding of MSG: 558 * 1) split on "\r\nEND:MSG\r\n" 559 * 2) delete "BEGIN:MSG\r\n" for each msg 560 * 3) replace any occurrence of "\END:MSG" with "END:MSG" 561 * 4) based on charset from application properties either store as String[] or decode to raw PDUs 562 * */ 563 String messages[] = data.split("\r\nEND:MSG\r\n"); 564 parseMsgInit(); 565 for(int i = 0; i < messages.length; i++) { 566 messages[i] = messages[i].replaceFirst("^BEGIN:MGS\r\n", ""); 567 messages[i] = messages[i].replaceAll("\r\n([/]*)/END\\:MSG", "\r\n$1END:MSG"); 568 messages[i] = messages[i].trim(); 569 parseMsgPart(messages[i]); 570 } 571 } 572 line = reader.getLineEnforce(); 573 } 574 } 575 576 /** 577 * Parse the 'message' part of <bmessage-body-content>" 578 * @param msgPart 579 */ 580 public abstract void parseMsgPart(String msgPart); 581 /** 582 * Set initial values before parsing - will be called is a message body is found 583 * during parsing. 584 */ 585 public abstract void parseMsgInit(); 586 587 public abstract byte[] encode() throws UnsupportedEncodingException; 588 589 public void setStatus(boolean read) { 590 if(read) 591 this.status = "READ"; 592 else 593 this.status = "UNREAD"; 594 } 595 596 public void setType(TYPE type) { 597 this.type = type; 598 } 599 600 /** 601 * @return the type 602 */ 603 public TYPE getType() { 604 return type; 605 } 606 607 public void setFolder(String folder) { 608 this.folder = "telecom/msg/" + folder; 609 } 610 611 public void setEncoding(String encoding) { 612 this.encoding = encoding; 613 } 614 615 public ArrayList<vCard> getOriginators() { 616 return originator; 617 } 618 619 public void addOriginator(vCard originator) { 620 if(this.originator == null) 621 this.originator = new ArrayList<vCard>(); 622 this.originator.add(originator); 623 } 624 625 /** 626 * Add a version 3.0 vCard with a formatted name 627 * @param name e.g. Bonde;Casper 628 * @param formattedName e.g. "Casper Bonde" 629 * @param phoneNumbers 630 * @param emailAddresses 631 */ 632 public void addOriginator(String name, String formattedName, String[] phoneNumbers, String[] emailAddresses) { 633 if(originator == null) 634 originator = new ArrayList<vCard>(); 635 originator.add(new vCard(name, formattedName, phoneNumbers, emailAddresses)); 636 } 637 638 /** Add a version 2.1 vCard with only a name. 639 * 640 * @param name e.g. Bonde;Casper 641 * @param phoneNumbers 642 * @param emailAddresses 643 */ 644 public void addOriginator(String name, String[] phoneNumbers, String[] emailAddresses) { 645 if(originator == null) 646 originator = new ArrayList<vCard>(); 647 originator.add(new vCard(name, phoneNumbers, emailAddresses)); 648 } 649 650 public ArrayList<vCard> getRecipients() { 651 return recipient; 652 } 653 654 public void setRecipient(vCard recipient) { 655 if(this.recipient == null) 656 this.recipient = new ArrayList<vCard>(); 657 this.recipient.add(recipient); 658 } 659 660 public void addRecipient(String name, String formattedName, String[] phoneNumbers, String[] emailAddresses) { 661 if(recipient == null) 662 recipient = new ArrayList<vCard>(); 663 recipient.add(new vCard(name, formattedName, phoneNumbers, emailAddresses)); 664 } 665 666 public void addRecipient(String name, String[] phoneNumbers, String[] emailAddresses) { 667 if(recipient == null) 668 recipient = new ArrayList<vCard>(); 669 recipient.add(new vCard(name, phoneNumbers, emailAddresses)); 670 } 671 672 /** 673 * Convert a byte[] of data to a hex string representation, converting each nibble to the corresponding 674 * hex char. 675 * NOTE: There is not need to escape instances of "\r\nEND:MSG" in the binary data represented as a string 676 * as only the characters [0-9] and [a-f] is used. 677 * @param pduData the byte-array of data. 678 * @param scAddressData the byte-array of the encoded sc-Address. 679 * @return the resulting string. 680 */ 681 protected String encodeBinary(byte[] pduData, byte[] scAddressData) { 682 StringBuilder out = new StringBuilder((pduData.length + scAddressData.length)*2); 683 for(int i = 0; i < scAddressData.length; i++) { 684 out.append(Integer.toString((scAddressData[i] >> 4) & 0x0f,16)); // MS-nibble first 685 out.append(Integer.toString( scAddressData[i] & 0x0f,16)); 686 } 687 for(int i = 0; i < pduData.length; i++) { 688 out.append(Integer.toString((pduData[i] >> 4) & 0x0f,16)); // MS-nibble first 689 out.append(Integer.toString( pduData[i] & 0x0f,16)); 690 /*out.append(Integer.toHexString(data[i]));*/ /* This is the same as above, but does not include the needed 0's 691 e.g. it converts the value 3 to "3" and not "03" */ 692 } 693 return out.toString(); 694 } 695 696 /** 697 * Decodes a binary hex-string encoded UTF-8 string to the represented binary data set. 698 * @param data The string representation of the data - must have an even number of characters. 699 * @return the byte[] represented in the data. 700 */ 701 protected byte[] decodeBinary(String data) { 702 byte[] out = new byte[data.length()/2]; 703 String value; 704 if(D) Log.d(TAG,"Decoding binary data: START:" + data + ":END"); 705 for(int i = 0, j = 0, n = out.length; i < n; i++) 706 { 707 value = data.substring(j++, j++); // same as data.substring(2*i, 2*i+1) 708 out[i] = Byte.valueOf(value, 16); 709 } 710 return out; 711 } 712 713 public byte[] encodeGeneric(ArrayList<byte[]> bodyFragments) throws UnsupportedEncodingException 714 { 715 StringBuilder sb = new StringBuilder(256); 716 byte[] msgStart, msgEnd; 717 sb.append("BEGIN:BMSG").append("\r\n"); 718 sb.append(VERSION).append("\r\n"); 719 sb.append("STATUS:").append(status).append("\r\n"); 720 sb.append("TYPE:").append(type.name()).append("\r\n"); 721 sb.append("FOLDER:").append(folder).append("\r\n"); 722 if(originator != null){ 723 for(vCard element : originator) 724 element.encode(sb); 725 } 726 /* TODO: Do we need the three levels of env? - e.g. for e-mail. - we do have a level in the 727 * vCards that could be used to determine the the levels of the envelope. 728 */ 729 730 sb.append("BEGIN:BENV").append("\r\n"); 731 if(recipient != null){ 732 for(vCard element : recipient) 733 element.encode(sb); 734 } 735 sb.append("BEGIN:BBODY").append("\r\n"); 736 if(encoding != null && encoding != "") 737 sb.append("ENCODING:").append(encoding).append("\r\n"); 738 if(charset != null && charset != "") 739 sb.append("CHARSET:").append(charset).append("\r\n"); 740 741 742 int length = 0; 743 /* 22 is the length of the 'BEGIN:MSG' and 'END:MSG' + 3*CRLF */ 744 for (byte[] fragment : bodyFragments) { 745 length += fragment.length + 22; 746 } 747 sb.append("LENGTH:").append(length).append("\r\n"); 748 749 // Extract the initial part of the bMessage string 750 msgStart = sb.toString().getBytes("UTF-8"); 751 752 sb = new StringBuilder(31); 753 sb.append("END:BBODY").append("\r\n"); 754 sb.append("END:BENV").append("\r\n"); 755 sb.append("END:BMSG").append("\r\n"); 756 757 msgEnd = sb.toString().getBytes("UTF-8"); 758 759 try { 760 761 ByteArrayOutputStream stream = new ByteArrayOutputStream(msgStart.length + msgEnd.length + length); 762 stream.write(msgStart); 763 764 for (byte[] fragment : bodyFragments) { 765 stream.write("BEGIN:MSG\r\n".getBytes("UTF-8")); 766 stream.write(fragment); 767 stream.write("\r\nEND:MSG\r\n".getBytes("UTF-8")); 768 } 769 stream.write(msgEnd); 770 771 if(V) Log.v(TAG,stream.toString("UTF-8")); 772 return stream.toByteArray(); 773 } catch (IOException e) { 774 Log.w(TAG,e); 775 return null; 776 } 777 } 778} 779