blob: 9a775683cd533aaf8dd502a1f9683784c4648bf8 [file] [log] [blame]
/*
* Copyright (C) 2013 Samsung System LSI
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bluetooth.map;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import android.os.Environment;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
public abstract class BluetoothMapbMessage {
protected static String TAG = "BluetoothMapbMessage";
protected static final boolean D = BluetoothMapService.DEBUG;
protected static final boolean V = BluetoothMapService.VERBOSE;
private String mVersionString = "VERSION:1.0";
public static int INVALID_VALUE = -1;
protected int mAppParamCharset = BluetoothMapAppParams.INVALID_VALUE_PARAMETER;
/* BMSG attributes */
private String mStatus = null; // READ/UNREAD
protected TYPE mType = null; // SMS/MMS/EMAIL
private String mFolder = null;
/* BBODY attributes */
private long mPartId = INVALID_VALUE;
protected String mEncoding = null;
protected String mCharset = null;
private String mLanguage = null;
private int mBMsgLength = INVALID_VALUE;
private ArrayList<vCard> mOriginator = null;
private ArrayList<vCard> mRecipient = null;
public static class vCard {
/* VCARD attributes */
private String mVersion;
private String mName = null;
private String mFormattedName = null;
private String[] mPhoneNumbers = {};
private String[] mEmailAddresses = {};
private int mEnvLevel = 0;
private String[] mBtUcis = {};
private String[] mBtUids = {};
/**
* Construct a version 3.0 vCard
* @param name Structured
* @param formattedName Formatted name
* @param phoneNumbers a String[] of phone numbers
* @param emailAddresses a String[] of email addresses
* @param the bmessage envelope level (0 is the top/most outer level)
*/
public vCard(String name, String formattedName, String[] phoneNumbers,
String[] emailAddresses, int envLevel) {
this.mEnvLevel = envLevel;
this.mVersion = "3.0";
this.mName = name != null ? name : "";
this.mFormattedName = formattedName != null ? formattedName : "";
setPhoneNumbers(phoneNumbers);
if (emailAddresses != null)
this.mEmailAddresses = emailAddresses;
}
/**
* Construct a version 2.1 vCard
* @param name Structured name
* @param phoneNumbers a String[] of phone numbers
* @param emailAddresses a String[] of email addresses
* @param the bmessage envelope level (0 is the top/most outer level)
*/
public vCard(String name, String[] phoneNumbers,
String[] emailAddresses, int envLevel) {
this.mEnvLevel = envLevel;
this.mVersion = "2.1";
this.mName = name != null ? name : "";
setPhoneNumbers(phoneNumbers);
if (emailAddresses != null)
this.mEmailAddresses = emailAddresses;
}
/**
* Construct a version 3.0 vCard
* @param name Structured name
* @param formattedName Formatted name
* @param phoneNumbers a String[] of phone numbers
* @param emailAddresses a String[] of email addresses if available, else null
* @param btUids a String[] of X-BT-UIDs if available, else null
* @param btUcis a String[] of X-BT-UCIs if available, else null
*/
public vCard(String name, String formattedName,
String[] phoneNumbers,
String[] emailAddresses,
String[] btUids,
String[] btUcis) {
this.mVersion = "3.0";
this.mName = (name != null) ? name : "";
this.mFormattedName = (formattedName != null) ? formattedName : "";
setPhoneNumbers(phoneNumbers);
if (emailAddresses != null) {
this.mEmailAddresses = emailAddresses;
}
if (btUcis != null) {
this.mBtUcis = btUcis;
}
}
/**
* Construct a version 2.1 vCard
* @param name Structured Name
* @param phoneNumbers a String[] of phone numbers
* @param emailAddresses a String[] of email addresses
*/
public vCard(String name, String[] phoneNumbers, String[] emailAddresses) {
this.mVersion = "2.1";
this.mName = name != null ? name : "";
setPhoneNumbers(phoneNumbers);
if (emailAddresses != null)
this.mEmailAddresses = emailAddresses;
}
private void setPhoneNumbers(String[] numbers) {
if(numbers != null && numbers.length > 0) {
mPhoneNumbers = new String[numbers.length];
for(int i = 0, n = numbers.length; i < n; i++){
String networkNumber = PhoneNumberUtils.extractNetworkPortion(numbers[i]);
/* extractNetworkPortion can return N if the number is a service
* "number" = a string with the a name in (i.e. "Some-Tele-company" would
* return N because of the N in compaNy)
* Hence we need to check if the number is actually a string with alpha chars.
* */
String strippedNumber = PhoneNumberUtils.stripSeparators(numbers[i]);
Boolean alpha = false;
if(strippedNumber != null){
alpha = strippedNumber.matches("[0-9]*[a-zA-Z]+[0-9]*");
}
if(networkNumber != null && networkNumber.length() > 1 && !alpha) {
mPhoneNumbers[i] = networkNumber;
} else {
mPhoneNumbers[i] = numbers[i];
}
}
}
}
public String getFirstPhoneNumber() {
if(mPhoneNumbers.length > 0) {
return mPhoneNumbers[0];
} else
return null;
}
public int getEnvLevel() {
return mEnvLevel;
}
public String getName() {
return mName;
}
public String getFirstEmail() {
if(mEmailAddresses.length > 0) {
return mEmailAddresses[0];
} else
return null;
}
public String getFirstBtUci() {
if(mBtUcis.length > 0) {
return mBtUcis[0];
} else
return null;
}
public String getFirstBtUid() {
if(mBtUids.length > 0) {
return mBtUids[0];
} else
return null;
}
public void encode(StringBuilder sb)
{
sb.append("BEGIN:VCARD").append("\r\n");
sb.append("VERSION:").append(mVersion).append("\r\n");
if(mVersion.equals("3.0") && mFormattedName != null)
{
sb.append("FN:").append(mFormattedName).append("\r\n");
}
if (mName != null)
sb.append("N:").append(mName).append("\r\n");
for(String phoneNumber : mPhoneNumbers)
{
sb.append("TEL:").append(phoneNumber).append("\r\n");
}
for(String emailAddress : mEmailAddresses)
{
sb.append("EMAIL:").append(emailAddress).append("\r\n");
}
for(String btUid : mBtUids)
{
sb.append("X-BT-UID:").append(btUid).append("\r\n");
}
for(String btUci : mBtUcis)
{
sb.append("X-BT-UCI:").append(btUci).append("\r\n");
}
sb.append("END:VCARD").append("\r\n");
}
/**
* Parse a vCard from a BMgsReader, where a line containing "BEGIN:VCARD"
* have just been read.
* @param reader
* @param envLevel
* @return
*/
public static vCard parseVcard(BMsgReader reader, int envLevel) {
String formattedName = null;
String name = null;
ArrayList<String> phoneNumbers = null;
ArrayList<String> emailAddresses = null;
ArrayList<String> btUids = null;
ArrayList<String> btUcis = null;
String[] parts;
String line = reader.getLineEnforce();
while(!line.contains("END:VCARD")){
line = line.trim();
if(line.startsWith("N:")){
parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':'
if(parts.length == 2) {
name = parts[1];
} else
name = "";
}
else if(line.startsWith("FN:")){
parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':'
if(parts.length == 2) {
formattedName = parts[1];
} else
formattedName = "";
}
else if(line.startsWith("TEL:")){
parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':'
if(parts.length == 2) {
String[] subParts = parts[1].split("[^\\\\];");
if(phoneNumbers == null)
phoneNumbers = new ArrayList<String>(1);
// only keep actual phone number
phoneNumbers.add(subParts[subParts.length-1]);
} else {}
// Empty phone number - ignore
}
else if(line.startsWith("EMAIL:")){
parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
if(parts.length == 2) {
String[] subParts = parts[1].split("[^\\\\];");
if(emailAddresses == null)
emailAddresses = new ArrayList<String>(1);
// only keep actual email address
emailAddresses.add(subParts[subParts.length-1]);
} else {}
// Empty email address entry - ignore
}
else if(line.startsWith("X-BT-UCI:")){
parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
if(parts.length == 2) {
String[] subParts = parts[1].split("[^\\\\];");
if(btUcis == null)
btUcis = new ArrayList<String>(1);
btUcis.add(subParts[subParts.length-1]); // only keep actual UCI
} else {}
// Empty UCIentry - ignore
}
else if(line.startsWith("X-BT-UID:")){
parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
if(parts.length == 2) {
String[] subParts = parts[1].split("[^\\\\];");
if(btUids == null)
btUids = new ArrayList<String>(1);
btUids.add(subParts[subParts.length-1]); // only keep actual UID
} else {}
// Empty UID entry - ignore
}
line = reader.getLineEnforce();
}
return new vCard(name, formattedName,
phoneNumbers == null?
null : phoneNumbers.toArray(new String[phoneNumbers.size()]),
emailAddresses == null ?
null : emailAddresses.toArray(new String[emailAddresses.size()]),
envLevel);
}
};
private static class BMsgReader {
InputStream mInStream;
public BMsgReader(InputStream is)
{
this.mInStream = is;
}
private byte[] getLineAsBytes() {
int readByte;
/* TODO: Actually the vCard spec. allows to break lines by using a newLine
* followed by a white space character(space or tab). Not sure this is a good idea to
* implement as the Bluetooth MAP spec. illustrates vCards using tab alignment,
* hence actually showing an invalid vCard format...
* If we read such a folded line, the folded part will be skipped in the parser
* UPDATE: Check if we actually do unfold before parsing the input stream
*/
ByteArrayOutputStream output = new ByteArrayOutputStream();
try {
while ((readByte = mInStream.read()) != -1) {
if (readByte == '\r') {
if ((readByte = mInStream.read()) != -1 && readByte == '\n') {
if(output.size() == 0)
continue; /* Skip empty lines */
else
break;
} else {
output.write('\r');
}
} else if (readByte == '\n' && output.size() == 0) {
/* Empty line - skip */
continue;
}
output.write(readByte);
}
} catch (IOException e) {
Log.w(TAG, e);
return null;
}
return output.toByteArray();
}
/**
* Read a line of text from the BMessage.
* @return the next line of text, or null at end of file, or if UTF-8 is not supported.
*/
public String getLine() {
try {
byte[] line = getLineAsBytes();
if (line.length == 0)
return null;
else
return new String(line, "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.w(TAG, e);
return null;
}
}
/**
* same as getLine(), but throws an exception, if we run out of lines.
* Use this function when ever more lines are needed for the bMessage to be complete.
* @return the next line
*/
public String getLineEnforce() {
String line = getLine();
if (line == null)
throw new IllegalArgumentException("Bmessage too short");
return line;
}
/**
* Reads a line from the InputStream, and examines if the subString
* matches the line read.
* @param subString
* The string to match against the line.
* @throws IllegalArgumentException
* If the expected substring is not found.
*
*/
public void expect(String subString) throws IllegalArgumentException{
String line = getLine();
if(line == null || subString == null){
throw new IllegalArgumentException("Line or substring is null");
}else if(!line.toUpperCase().contains(subString.toUpperCase()))
throw new IllegalArgumentException("Expected \"" + subString + "\" in: \""
+ line + "\"");
}
/**
* Same as expect(String), but with two strings.
* @param subString
* @param subString2
* @throws IllegalArgumentException
* If one or all of the strings are not found.
*/
public void expect(String subString, String subString2) throws IllegalArgumentException{
String line = getLine();
if(!line.toUpperCase().contains(subString.toUpperCase()))
throw new IllegalArgumentException("Expected \"" + subString + "\" in: \""
+ line + "\"");
if(!line.toUpperCase().contains(subString2.toUpperCase()))
throw new IllegalArgumentException("Expected \"" + subString + "\" in: \""
+ line + "\"");
}
/**
* Read a part of the bMessage as raw data.
* @param length the number of bytes to read
* @return the byte[] containing the number of bytes or null if an error occurs or EOF is
* reached before length bytes have been read.
*/
public byte[] getDataBytes(int length) {
byte[] data = new byte[length];
try {
int bytesRead;
int offset=0;
while ((bytesRead = mInStream.read(data, offset, length-offset))
!= (length - offset)) {
if(bytesRead == -1)
return null;
offset += bytesRead;
}
} catch (IOException e) {
Log.w(TAG, e);
return null;
}
return data;
}
};
public BluetoothMapbMessage(){
}
public String getVersionString() {
return mVersionString;
}
/**
* Set the version string for VCARD
* @param version the actual number part of the version string i.e. 1.0
* */
public void setVersionString(String version) {
this.mVersionString = "VERSION:"+version;
}
public static BluetoothMapbMessage parse(InputStream bMsgStream,
int appParamCharset) throws IllegalArgumentException{
BMsgReader reader;
String line = "";
BluetoothMapbMessage newBMsg = null;
boolean status = false;
boolean statusFound = false;
TYPE type = null;
String folder = null;
/* This section is used for debug. It will write the incoming message to a file on the
* SD-card, hence should only be used for test/debug.
* If an error occurs, it will result in a OBEX_HTTP_PRECON_FAILED to be send to the client,
* even though the message might be formatted correctly, hence only enable this code for
* test. */
if(V) {
/* Read the entire stream into a file on the SD card*/
File sdCard = Environment.getExternalStorageDirectory();
File dir = new File (sdCard.getAbsolutePath() + "/bluetooth/log/");
dir.mkdirs();
File file = new File(dir, "receivedBMessage.txt");
FileOutputStream outStream = null;
boolean failed = false;
int writtenLen = 0;
try {
/* overwrite if it does already exist */
outStream = new FileOutputStream(file, false);
byte[] buffer = new byte[4*1024];
int len = 0;
while ((len = bMsgStream.read(buffer)) > 0) {
outStream.write(buffer, 0, len);
writtenLen += len;
}
} catch (FileNotFoundException e) {
Log.e(TAG,"Unable to create output stream",e);
} catch (IOException e) {
Log.e(TAG,"Failed to copy the received message",e);
if(writtenLen != 0)
failed = true; /* We failed to write the complete file,
hence the received message is lost... */
} finally {
if(outStream != null)
try {
outStream.close();
} catch (IOException e) {
}
}
/* Return if we corrupted the incoming bMessage. */
if(failed) {
throw new IllegalArgumentException(); /* terminate this function with an error. */
}
if (outStream == null) {
/* We failed to create the log-file, just continue using the original bMsgStream. */
} else {
/* overwrite the bMsgStream using the file written to the SD-Card */
try {
bMsgStream.close();
} catch (IOException e) {
/* Ignore if we cannot close the stream. */
}
/* Open the file and overwrite bMsgStream to read from the file */
try {
bMsgStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
Log.e(TAG,"Failed to open the bMessage file", e);
/* terminate this function with an error */
throw new IllegalArgumentException();
}
}
Log.i(TAG, "The incoming bMessage have been dumped to " + file.getAbsolutePath());
} /* End of if(V) log-section */
reader = new BMsgReader(bMsgStream);
reader.expect("BEGIN:BMSG");
reader.expect("VERSION");
line = reader.getLineEnforce();
// Parse the properties - which end with either a VCARD or a BENV
while(!line.contains("BEGIN:VCARD") && !line.contains("BEGIN:BENV")) {
if(line.contains("STATUS")){
String arg[] = line.split(":");
if (arg != null && arg.length == 2) {
if (arg[1].trim().equals("READ")) {
status = true;
} else if (arg[1].trim().equals("UNREAD")) {
status =false;
} else {
throw new IllegalArgumentException("Wrong value in 'STATUS': " + arg[1]);
}
} else {
throw new IllegalArgumentException("Missing value for 'STATUS': " + line);
}
}
if(line.contains("EXTENDEDDATA")){
String arg[] = line.split(":");
if (arg != null && arg.length == 2) {
String value = arg[1].trim();
//FIXME what should we do with this
Log.i(TAG,"We got extended data with: "+value);
}
}
if(line.contains("TYPE")) {
String arg[] = line.split(":");
if (arg != null && arg.length == 2) {
String value = arg[1].trim();
/* Will throw IllegalArgumentException if value is wrong */
type = TYPE.valueOf(value);
if(appParamCharset == BluetoothMapAppParams.CHARSET_NATIVE
&& type != TYPE.SMS_CDMA && type != TYPE.SMS_GSM) {
throw new IllegalArgumentException("Native appParamsCharset "
+"only supported for SMS");
}
switch(type) {
case SMS_CDMA:
case SMS_GSM:
newBMsg = new BluetoothMapbMessageSms();
break;
case MMS:
newBMsg = new BluetoothMapbMessageMime();
break;
case EMAIL:
newBMsg = new BluetoothMapbMessageEmail();
break;
case IM:
newBMsg = new BluetoothMapbMessageMime();
break;
default:
break;
}
} else {
throw new IllegalArgumentException("Missing value for 'TYPE':" + line);
}
}
if(line.contains("FOLDER")) {
String[] arg = line.split(":");
if (arg != null && arg.length == 2) {
folder = arg[1].trim();
}
// This can be empty for push message - hence ignore if there is no value
}
line = reader.getLineEnforce();
}
if(newBMsg == null)
throw new IllegalArgumentException("Missing bMessage TYPE: "+
"- unable to parse body-content");
newBMsg.setType(type);
newBMsg.mAppParamCharset = appParamCharset;
if(folder != null)
newBMsg.setCompleteFolder(folder);
if(statusFound)
newBMsg.setStatus(status);
// Now check for originator VCARDs
while(line.contains("BEGIN:VCARD")){
if(D) Log.d(TAG,"Decoding vCard");
newBMsg.addOriginator(vCard.parseVcard(reader,0));
line = reader.getLineEnforce();
}
if(line.contains("BEGIN:BENV")) {
newBMsg.parseEnvelope(reader, 0);
} else
throw new IllegalArgumentException("Bmessage has no BEGIN:BENV - line:" + line);
/* TODO: Do we need to validate the END:* tags? They are only needed if someone puts
* additional info below the END:MSG - in which case we don't handle it.
* We need to parse the message based on the length field, to ensure MAP 1.0
* compatibility, since this spec. do not suggest to escape the end-tag if it
* occurs inside the message text.
*/
try {
bMsgStream.close();
} catch (IOException e) {
/* Ignore if we cannot close the stream. */
}
return newBMsg;
}
private void parseEnvelope(BMsgReader reader, int level) {
String line;
line = reader.getLineEnforce();
if(D) Log.d(TAG,"Decoding envelope level " + level);
while(line.contains("BEGIN:VCARD")){
if(D) Log.d(TAG,"Decoding recipient vCard level " + level);
if(mRecipient == null)
mRecipient = new ArrayList<vCard>(1);
mRecipient.add(vCard.parseVcard(reader, level));
line = reader.getLineEnforce();
}
if(line.contains("BEGIN:BENV")) {
if(D) Log.d(TAG,"Decoding nested envelope");
parseEnvelope(reader, ++level); // Nested BENV
}
if(line.contains("BEGIN:BBODY")){
if(D) Log.d(TAG,"Decoding bbody");
parseBody(reader);
}
}
private void parseBody(BMsgReader reader) {
String line;
line = reader.getLineEnforce();
while(!line.contains("END:")) {
if(line.contains("PARTID:")) {
String arg[] = line.split(":");
if (arg != null && arg.length == 2) {
try {
mPartId = Long.parseLong(arg[1].trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Wrong value in 'PARTID': " + arg[1]);
}
} else {
throw new IllegalArgumentException("Missing value for 'PARTID': " + line);
}
}
else if(line.contains("ENCODING:")) {
String arg[] = line.split(":");
if (arg != null && arg.length == 2) {
mEncoding = arg[1].trim();
// If needed validation will be done when the value is used
} else {
throw new IllegalArgumentException("Missing value for 'ENCODING': " + line);
}
}
else if(line.contains("CHARSET:")) {
String arg[] = line.split(":");
if (arg != null && arg.length == 2) {
mCharset = arg[1].trim();
// If needed validation will be done when the value is used
} else {
throw new IllegalArgumentException("Missing value for 'CHARSET': " + line);
}
}
else if(line.contains("LANGUAGE:")) {
String arg[] = line.split(":");
if (arg != null && arg.length == 2) {
mLanguage = arg[1].trim();
// If needed validation will be done when the value is used
} else {
throw new IllegalArgumentException("Missing value for 'LANGUAGE': " + line);
}
}
else if(line.contains("LENGTH:")) {
String arg[] = line.split(":");
if (arg != null && arg.length == 2) {
try {
mBMsgLength = Integer.parseInt(arg[1].trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Wrong value in 'LENGTH': " + arg[1]);
}
} else {
throw new IllegalArgumentException("Missing value for 'LENGTH': " + line);
}
}
else if(line.contains("BEGIN:MSG")) {
if(mBMsgLength == INVALID_VALUE)
throw new IllegalArgumentException("Missing value for 'LENGTH'. " +
"Unable to read remaining part of the message");
/* For SMS: Encoding of MSG is always UTF-8 compliant, regardless of any properties,
since PDUs are encodes as hex-strings */
/* PTS has a bug regarding the message length, and sets it 2 bytes too short, hence
* using the length field to determine the amount of data to read, might not be the
* best solution.
* Since errata ???(bluetooth.org is down at the moment) introduced escaping of
* END:MSG in the actual message content, it is now safe to use the END:MSG tag
* as terminator, and simply ignore the length field.*/
/* 2 added to compensate for the removed \r\n */
byte[] rawData = reader.getDataBytes(mBMsgLength - (line.getBytes().length + 2));
String data;
try {
data = new String(rawData, "UTF-8");
if(V) {
Log.v(TAG,"MsgLength: " + mBMsgLength);
Log.v(TAG,"line.getBytes().length: " + line.getBytes().length);
String debug = line.replaceAll("\\n", "<LF>\n");
debug = debug.replaceAll("\\r", "<CR>");
Log.v(TAG,"The line: \"" + debug + "\"");
debug = data.replaceAll("\\n", "<LF>\n");
debug = debug.replaceAll("\\r", "<CR>");
Log.v(TAG,"The msgString: \"" + debug + "\"");
}
} catch (UnsupportedEncodingException e) {
Log.w(TAG,e);
throw new IllegalArgumentException("Unable to convert to UTF-8");
}
/* Decoding of MSG:
* 1) split on "\r\nEND:MSG\r\n"
* 2) delete "BEGIN:MSG\r\n" for each msg
* 3) replace any occurrence of "\END:MSG" with "END:MSG"
* 4) based on charset from application properties either store as String[] or
* decode to raw PDUs
* */
String messages[] = data.split("\r\nEND:MSG\r\n");
parseMsgInit();
for(int i = 0; i < messages.length; i++) {
messages[i] = messages[i].replaceFirst("^BEGIN:MSG\r\n", "");
messages[i] = messages[i].replaceAll("\r\n([/]*)/END\\:MSG", "\r\n$1END:MSG");
messages[i] = messages[i].trim();
parseMsgPart(messages[i]);
}
}
line = reader.getLineEnforce();
}
}
/**
* Parse the 'message' part of <bmessage-body-content>"
* @param msgPart
*/
public abstract void parseMsgPart(String msgPart);
/**
* Set initial values before parsing - will be called is a message body is found
* during parsing.
*/
public abstract void parseMsgInit();
public abstract byte[] encode() throws UnsupportedEncodingException;
public void setStatus(boolean read) {
if(read)
this.mStatus = "READ";
else
this.mStatus = "UNREAD";
}
public void setType(TYPE type) {
this.mType = type;
}
/**
* @return the type
*/
public TYPE getType() {
return mType;
}
public void setCompleteFolder(String folder) {
this.mFolder = folder;
}
public void setFolder(String folder) {
this.mFolder = "telecom/msg/" + folder;
}
public String getFolder() {
return mFolder;
}
public void setEncoding(String encoding) {
this.mEncoding = encoding;
}
public ArrayList<vCard> getOriginators() {
return mOriginator;
}
public void addOriginator(vCard originator) {
if(this.mOriginator == null)
this.mOriginator = new ArrayList<vCard>();
this.mOriginator.add(originator);
}
/**
* Add a version 3.0 vCard with a formatted name
* @param name e.g. Bonde;Casper
* @param formattedName e.g. "Casper Bonde"
* @param phoneNumbers
* @param emailAddresses
*/
public void addOriginator(String name, String formattedName,
String[] phoneNumbers,
String[] emailAddresses,
String[] btUids,
String[] btUcis) {
if(mOriginator == null)
mOriginator = new ArrayList<vCard>();
mOriginator.add(new vCard(name, formattedName, phoneNumbers,
emailAddresses, btUids, btUcis));
}
public void addOriginator(String[] btUcis, String[] btUids) {
if(mOriginator == null)
mOriginator = new ArrayList<vCard>();
mOriginator.add(new vCard(null,null,null,null,btUids, btUcis));
}
/** Add a version 2.1 vCard with only a name.
*
* @param name e.g. Bonde;Casper
* @param phoneNumbers
* @param emailAddresses
*/
public void addOriginator(String name, String[] phoneNumbers, String[] emailAddresses) {
if(mOriginator == null)
mOriginator = new ArrayList<vCard>();
mOriginator.add(new vCard(name, phoneNumbers, emailAddresses));
}
public ArrayList<vCard> getRecipients() {
return mRecipient;
}
public void setRecipient(vCard recipient) {
if(this.mRecipient == null)
this.mRecipient = new ArrayList<vCard>();
this.mRecipient.add(recipient);
}
public void addRecipient(String[] btUcis, String[] btUids) {
if(mRecipient == null)
mRecipient = new ArrayList<vCard>();
mRecipient.add(new vCard(null,null,null,null,btUids, btUcis));
}
public void addRecipient(String name, String formattedName,
String[] phoneNumbers,
String[] emailAddresses,
String[] btUids,
String[] btUcis) {
if(mRecipient == null)
mRecipient = new ArrayList<vCard>();
mRecipient.add(new vCard(name, formattedName, phoneNumbers,
emailAddresses,btUids, btUcis));
}
public void addRecipient(String name, String[] phoneNumbers, String[] emailAddresses) {
if(mRecipient == null)
mRecipient = new ArrayList<vCard>();
mRecipient.add(new vCard(name, phoneNumbers, emailAddresses));
}
/**
* Convert a byte[] of data to a hex string representation, converting each nibble to the
* corresponding hex char.
* NOTE: There is not need to escape instances of "\r\nEND:MSG" in the binary data represented
* as a string as only the characters [0-9] and [a-f] is used.
* @param pduData the byte-array of data.
* @param scAddressData the byte-array of the encoded sc-Address.
* @return the resulting string.
*/
protected String encodeBinary(byte[] pduData, byte[] scAddressData) {
StringBuilder out = new StringBuilder((pduData.length + scAddressData.length)*2);
for(int i = 0; i < scAddressData.length; i++) {
out.append(Integer.toString((scAddressData[i] >> 4) & 0x0f,16)); // MS-nibble first
out.append(Integer.toString( scAddressData[i] & 0x0f,16));
}
for(int i = 0; i < pduData.length; i++) {
out.append(Integer.toString((pduData[i] >> 4) & 0x0f,16)); // MS-nibble first
out.append(Integer.toString( pduData[i] & 0x0f,16));
/*out.append(Integer.toHexString(data[i]));*/ /* This is the same as above, but does not
* include the needed 0's
* e.g. it converts the value 3 to "3"
* and not "03" */
}
return out.toString();
}
/**
* Decodes a binary hex-string encoded UTF-8 string to the represented binary data set.
* @param data The string representation of the data - must have an even number of characters.
* @return the byte[] represented in the data.
*/
protected byte[] decodeBinary(String data) {
byte[] out = new byte[data.length()/2];
String value;
if(D) Log.d(TAG,"Decoding binary data: START:" + data + ":END");
for(int i = 0, j = 0, n = out.length; i < n; i++)
{
value = data.substring(j++, ++j);
// same as data.substring(2*i, 2*i+1+1) - substring() uses end-1 for last index
out[i] = (byte)(Integer.valueOf(value, 16) & 0xff);
}
if(D) {
StringBuilder sb = new StringBuilder(out.length);
for(int i = 0, n = out.length; i < n; i++)
{
sb.append(String.format("%02X",out[i] & 0xff));
}
Log.d(TAG,"Decoded binary data: START:" + sb.toString() + ":END");
}
return out;
}
public byte[] encodeGeneric(ArrayList<byte[]> bodyFragments) throws UnsupportedEncodingException
{
StringBuilder sb = new StringBuilder(256);
byte[] msgStart, msgEnd;
sb.append("BEGIN:BMSG").append("\r\n");
sb.append(mVersionString).append("\r\n");
sb.append("STATUS:").append(mStatus).append("\r\n");
sb.append("TYPE:").append(mType.name()).append("\r\n");
if(mFolder.length() > 512)
sb.append("FOLDER:").append(
mFolder.substring(mFolder.length()-512, mFolder.length())).append("\r\n");
else
sb.append("FOLDER:").append(mFolder).append("\r\n");
if(!mVersionString.contains("1.0")){
sb.append("EXTENDEDDATA:").append("\r\n");
}
if(mOriginator != null){
for(vCard element : mOriginator)
element.encode(sb);
}
/* If we need the three levels of env. at some point - we do have a level in the
* vCards that could be used to determine the levels of the envelope.
*/
sb.append("BEGIN:BENV").append("\r\n");
if(mRecipient != null){
for(vCard element : mRecipient) {
if(V) Log.v(TAG, "encodeGeneric: recipient email" + element.getFirstEmail());
element.encode(sb);
}
}
sb.append("BEGIN:BBODY").append("\r\n");
if(mEncoding != null && mEncoding != "")
sb.append("ENCODING:").append(mEncoding).append("\r\n");
if(mCharset != null && mCharset != "")
sb.append("CHARSET:").append(mCharset).append("\r\n");
int length = 0;
/* 22 is the length of the 'BEGIN:MSG' and 'END:MSG' + 3*CRLF */
for (byte[] fragment : bodyFragments) {
length += fragment.length + 22;
}
sb.append("LENGTH:").append(length).append("\r\n");
// Extract the initial part of the bMessage string
msgStart = sb.toString().getBytes("UTF-8");
sb = new StringBuilder(31);
sb.append("END:BBODY").append("\r\n");
sb.append("END:BENV").append("\r\n");
sb.append("END:BMSG").append("\r\n");
msgEnd = sb.toString().getBytes("UTF-8");
try {
ByteArrayOutputStream stream = new ByteArrayOutputStream(
msgStart.length + msgEnd.length + length);
stream.write(msgStart);
for (byte[] fragment : bodyFragments) {
stream.write("BEGIN:MSG\r\n".getBytes("UTF-8"));
stream.write(fragment);
stream.write("\r\nEND:MSG\r\n".getBytes("UTF-8"));
}
stream.write(msgEnd);
if(V) Log.v(TAG,stream.toString("UTF-8"));
return stream.toByteArray();
} catch (IOException e) {
Log.w(TAG,e);
return null;
}
}
}