summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPradeep Panigrahi <pradeepp@codeaurora.org>2013-12-09 15:19:02 +0530
committerPradeep Panigrahi <pradeepp@codeaurora.org>2013-12-17 14:10:43 +0530
commita44ee746ec6382ee7160449f0deef28121d5fba8 (patch)
treee47fe9966d16860112796d18b4208c7345454c0f
parent8a636b95a14a14ccc6152f4532f11a2212cabbc7 (diff)
downloadandroid_packages_apps_Bluetooth-a44ee746ec6382ee7160449f0deef28121d5fba8.tar.gz
android_packages_apps_Bluetooth-a44ee746ec6382ee7160449f0deef28121d5fba8.tar.bz2
android_packages_apps_Bluetooth-a44ee746ec6382ee7160449f0deef28121d5fba8.zip
MAP: Add MAP Server Email type and instance support.
Add MSE Implementation in Bluetooth Map Profile to support email MAS instance for EMAIL type messages. Support MNS operations for email from single instance. Change-Id: I89a9f63c571f3bb1cd8fc06fec39176d62e04889
-rw-r--r--Android.mk1
-rw-r--r--AndroidManifest.xml4
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapAppParams.java3
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapContent.java748
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapContentEmailObserver.java596
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapContentObserver.java140
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapFolderElement.java2
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapMessageListing.java18
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java7
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapObexServer.java152
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapService.java792
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapUtils.java77
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapbMessage.java86
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java215
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapbMessageSms.java2
-rw-r--r--src/com/android/bluetooth/map/BluetoothMnsObexClient.java132
16 files changed, 2592 insertions, 383 deletions
diff --git a/Android.mk b/Android.mk
index 4c24df918..511418f6a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -12,6 +12,7 @@ LOCAL_CERTIFICATE := platform
LOCAL_JNI_SHARED_LIBRARIES := libbluetooth_jni
LOCAL_JAVA_LIBRARIES := javax.obex telephony-common mms-common
LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard
+LOCAL_STATIC_JAVA_LIBRARIES += com.android.emailcommon
LOCAL_REQUIRED_MODULES := libbluetooth_jni bluetooth.default
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index cc454d2d0..6195bb537 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -59,6 +59,10 @@
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
+<uses-permission android:name="com.android.email.permission.ACCESS_PROVIDER"/>
+ <uses-permission android:name="com.android.email.permission.READ_ATTACHMENT"/>
+
+
<!-- For PBAP Owner Vcard Info -->
<uses-permission android:name="android.permission.READ_PROFILE"/>
<application
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppParams.java b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
index 554f422a8..6f8e1ea2d 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAppParams.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
@@ -165,6 +165,9 @@ public class BluetoothMapAppParams {
while (i < appParams.length) {
tagId = appParams[i++] & 0xff; // Convert to unsigned to support values above 127
tagLength = appParams[i++] & 0xff; // Convert to unsigned to support values above 127
+ Log.d(TAG, "tagId is "+ tagId );
+ Log.d(TAG, "tagLength is "+ tagLength );
+ Log.d(TAG, "appParams[i] is "+ appParams[i]);
switch (tagId) {
case MAX_LIST_COUNT:
if (tagLength != MAX_LIST_COUNT_LEN) {
diff --git a/src/com/android/bluetooth/map/BluetoothMapContent.java b/src/com/android/bluetooth/map/BluetoothMapContent.java
index fb8acc9d7..963bb09a8 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -19,12 +19,16 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
+import android.text.Html;
+import android.text.format.Time;
+
import org.apache.http.util.ByteArrayBuffer;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
+import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns;
@@ -35,15 +39,25 @@ import android.provider.Telephony.Mms;
import android.provider.Telephony.Sms;
import android.telephony.TelephonyManager;
import android.util.Log;
+import com.android.emailcommon.provider.EmailContent;
+import com.android.emailcommon.provider.EmailContent.Message;
+import com.android.emailcommon.provider.EmailContent.MessageColumns;
+import com.android.emailcommon.provider.EmailContent.SyncColumns;
+
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.google.android.mms.pdu.CharacterSets;
+import android.database.sqlite.SQLiteException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.*;
+
public class BluetoothMapContent {
private static final String TAG = "BluetoothMapContent";
- private static final boolean D = true;
- private static final boolean V = true;
+ private static final boolean D = BluetoothMapService.DEBUG;
+ private static final boolean V = BluetoothMapService.VERBOSE;
private static final int MASK_SUBJECT = 0x1;
private static final int MASK_DATETIME = 0x2;
@@ -72,8 +86,48 @@ public class BluetoothMapContent {
public static final int MMS_BCC = 0x81;
public static final int MMS_CC = 0x82;
+ /* Type of Email address. From Telephony.java it must be one of PduHeaders.BCC, */
+ /* PduHeaders.CC, PduHeaders.FROM, PduHeaders.TO. These are from PduHeaders.java */
+ public static final int EMAIL_FROM = 0x89;
+ public static final int EMAIL_TO = 0x97;
+ public static final int EMAIL_BCC = 0x81;
+ public static final int EMAIL_CC = 0x82;
+ public static final String AUTHORITY = "com.android.email.provider";
+ public static final Uri EMAIL_URI = Uri.parse("content://" + AUTHORITY);
+ public static final Uri EMAIL_ACCOUNT_URI = Uri.withAppendedPath(EMAIL_URI, "account");
+ public static final Uri EMAIL_BOX_URI = Uri.withAppendedPath(EMAIL_URI, "mailbox");
+ public static final Uri EMAIL_MESSAGE_URI = Uri.withAppendedPath(EMAIL_URI, "message");
+ public static final String RECORD_ID = "_id";
+ public static final String DISPLAY_NAME = "displayName";
+ public static final String SERVER_ID = "serverId";
+ public static final String ACCOUNT_KEY = "accountKey";
+ public static final String MAILBOX_KEY = "mailboxKey";
+ public static final String EMAIL_ADDRESS = "emailAddress";
+ public static final String IS_DEFAULT = "isDefault";
+ public static final String EMAIL_TYPE = "type";
+ public static final String[] EMAIL_BOX_PROJECTION = new String[] {
+ RECORD_ID, DISPLAY_NAME, ACCOUNT_KEY, EMAIL_TYPE };
+
private Context mContext;
private ContentResolver mResolver;
+ private static final String[] ACCOUNT_ID_PROJECTION = new String[] {
+ RECORD_ID, EMAIL_ADDRESS, IS_DEFAULT
+ };
+
+ static final String[] EMAIL_PROJECTION = new String[] {
+ EmailContent.RECORD_ID,
+ MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
+ MessageColumns.SUBJECT, MessageColumns.FLAG_READ,
+ MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS,
+ SyncColumns.SERVER_ID, MessageColumns.DRAFT_INFO,
+ MessageColumns.MESSAGE_ID, MessageColumns.MAILBOX_KEY,
+ MessageColumns.ACCOUNT_KEY, MessageColumns.FROM_LIST,
+ MessageColumns.TO_LIST, MessageColumns.CC_LIST,
+ MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST,
+ SyncColumns.SERVER_TIMESTAMP, MessageColumns.MEETING_INFO,
+ MessageColumns.SNIPPET, MessageColumns.PROTOCOL_SEARCH_INFO,
+ MessageColumns.THREAD_TOPIC
+ };
static final String[] SMS_PROJECTION = new String[] {
BaseColumns._ID,
@@ -106,6 +160,7 @@ public class BluetoothMapContent {
private class FilterInfo {
public static final int TYPE_SMS = 0;
public static final int TYPE_MMS = 1;
+ public static final int TYPE_EMAIL = 2;
int msgType = TYPE_SMS;
int phoneType = 0;
@@ -117,7 +172,7 @@ public class BluetoothMapContent {
mContext = context;
mResolver = mContext.getContentResolver();
if (mResolver == null) {
- Log.d(TAG, "getContentResolver failed");
+ Log.e(TAG, "getContentResolver failed");
}
}
@@ -351,14 +406,24 @@ public class BluetoothMapContent {
private void setSent(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
+ String sent = null;
if ((ap.getParameterMask() & MASK_SENT) != 0) {
int msgType = 0;
if (fi.msgType == FilterInfo.TYPE_SMS) {
msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
} else if (fi.msgType == FilterInfo.TYPE_MMS) {
msgType = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
+ } else {
+ msgType = c.getInt(c.getColumnIndex(MessageColumns.MAILBOX_KEY));
+ if (msgType == 4) {
+ sent = "yes";
+ } else {
+ sent = "no";
+ }
+ if (D) Log.d(TAG, "setSent: " + sent);
+ e.setSent(sent);
+ return;
}
- String sent = null;
if (msgType == 2) {
sent = "yes";
} else {
@@ -376,6 +441,8 @@ public class BluetoothMapContent {
read = c.getInt(c.getColumnIndex(Sms.READ));
} else if (fi.msgType == FilterInfo.TYPE_MMS) {
read = c.getInt(c.getColumnIndex(Mms.READ));
+ } else {
+ read = c.getColumnIndex(MessageColumns.FLAG_READ);
}
String setread = null;
if (read == 1) {
@@ -409,6 +476,20 @@ public class BluetoothMapContent {
int size = 0;
if (fi.msgType == FilterInfo.TYPE_MMS) {
size = c.getInt(c.getColumnIndex(Mms.MESSAGE_SIZE));
+ } else if (fi.msgType == FilterInfo.TYPE_EMAIL) {
+ Uri uri = Uri.parse("content://com.android.email.provider/attachment");
+ long msgId = Long.valueOf(c.getString(c.getColumnIndex("_id")));
+ String where = setWhereFilterMessagekey(msgId);
+ Cursor cr = mResolver.query(
+ uri, new String[]{"size"}, where , null, null);
+ if (cr != null && cr.moveToFirst()) {
+ do {
+ size += cr.getInt(0);
+ } while (cr.moveToNext());
+ }
+ if (cr != null) {
+ cr.close();
+ }
}
if (D) Log.d(TAG, "setAttachmentSize: " + size);
e.setAttachmentSize(size);
@@ -434,6 +515,8 @@ public class BluetoothMapContent {
hasText = "no";
}
}
+ } else {
+ hasText = "yes";
}
if (D) Log.d(TAG, "setText: " + hasText);
e.setText(hasText);
@@ -458,6 +541,25 @@ public class BluetoothMapContent {
size = subject.length();
} else if (fi.msgType == FilterInfo.TYPE_MMS) {
size = c.getInt(c.getColumnIndex(Mms.MESSAGE_SIZE));
+ } else {
+ long msgId = Long.valueOf(c.getString(c.getColumnIndex("_id")));
+ String[] EMAIL_MSGSIZE_PROJECTION = new String[] { "LENGTH(textContent)", "LENGTH(htmlContent)" };
+ String textContent, htmlContent;
+ Uri uri = Uri.parse("content://com.android.email.provider/body");
+ String where = setWhereFilterMessagekey(msgId);
+ Cursor cr = mResolver.query(
+ uri, EMAIL_MSGSIZE_PROJECTION, where , null, null);
+ if (cr != null && cr.moveToFirst()) {
+ do {
+ size = cr.getInt(0);
+ if(size == -1 || size == 0)
+ size = cr.getInt(1);
+ break;
+ } while (cr.moveToNext());
+ }
+ if (cr != null) {
+ cr.close();
+ }
}
if (D) Log.d(TAG, "setSize: " + size);
e.setSize(size);
@@ -476,6 +578,8 @@ public class BluetoothMapContent {
}
} else if (fi.msgType == FilterInfo.TYPE_MMS) {
type = TYPE.MMS;
+ } else {
+ type = TYPE.EMAIL;
}
if (D) Log.d(TAG, "setType: " + type);
e.setType(type);
@@ -485,7 +589,7 @@ public class BluetoothMapContent {
private void setRecipientAddressing(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_RECIPIENT_ADDRESSING) != 0) {
- String address = "";
+ String address = null;
if (fi.msgType == FilterInfo.TYPE_SMS) {
int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
if (msgType == 1) {
@@ -496,6 +600,10 @@ public class BluetoothMapContent {
} else if (fi.msgType == FilterInfo.TYPE_MMS) {
long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
address = getAddressMms(mResolver, id, MMS_TO);
+ } else {
+ int toIndex = c.getColumnIndex(MessageColumns.TO_LIST);
+ if (D) Log.d(TAG, "setRecipientAddressing: " +c.getString(toIndex));
+ address = c.getString(toIndex);
}
if (D) Log.d(TAG, "setRecipientAddressing: " + address);
e.setRecipientAddressing(address);
@@ -505,7 +613,7 @@ public class BluetoothMapContent {
private void setRecipientName(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_RECIPIENT_NAME) != 0) {
- String name = "";
+ String name = null;
if (fi.msgType == FilterInfo.TYPE_SMS) {
int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
if (msgType != 1) {
@@ -518,12 +626,28 @@ public class BluetoothMapContent {
long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
String phone = getAddressMms(mResolver, id, MMS_TO);
name = getContactNameFromPhone(phone);
+ } else {
+ int toIndex = c.getColumnIndex(MessageColumns.TO_LIST);
+ if (D) Log.d(TAG, "setRecipientName: " +c.getString(toIndex));
+ name = c.getString(toIndex);
}
if (D) Log.d(TAG, "setRecipientName: " + name);
e.setRecipientName(name);
}
}
+ private void setReplytoAddressing(BluetoothMapMessageListingElement e, Cursor c,
+ FilterInfo fi, BluetoothMapAppParams ap) {
+ String address = null;
+ if (fi.msgType == FilterInfo.TYPE_EMAIL) {
+ int replyToInd = c.getColumnIndex(MessageColumns.REPLY_TO_LIST);
+ if (D) Log.d(TAG, "setReplytoAddressing: " +c.getString(replyToInd));
+ address = c.getString(replyToInd);
+ }
+ if (D) Log.d(TAG, "setReplytoAddressing: " + address);
+ e.setReplytoAddressing(address);
+ }
+
private void setSenderAddressing(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_SENDER_ADDRESSING) != 0) {
@@ -538,6 +662,12 @@ public class BluetoothMapContent {
} else if (fi.msgType == FilterInfo.TYPE_MMS) {
long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
address = getAddressMms(mResolver, id, MMS_FROM);
+ } else {
+ int fromIndex = c.getColumnIndex(MessageColumns.FROM_LIST);
+ address = c.getString(fromIndex);
+ if (D) Log.d(TAG, "setSenderAddressing: " + address);
+ e.setEmailSenderAddressing(address);
+ return;
}
if (D) Log.d(TAG, "setSenderAddressing: " + address);
e.setSenderAddressing(address);
@@ -547,7 +677,7 @@ public class BluetoothMapContent {
private void setSenderName(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_SENDER_NAME) != 0) {
- String name = "";
+ String name = null;
if (fi.msgType == FilterInfo.TYPE_SMS) {
int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
if (msgType == 1) {
@@ -560,6 +690,10 @@ public class BluetoothMapContent {
long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
String phone = getAddressMms(mResolver, id, MMS_FROM);
name = getContactNameFromPhone(phone);
+ } else { //email case
+ int displayNameIndex = c.getColumnIndex(MessageColumns.DISPLAY_NAME);
+ if (D) Log.d(TAG, "setSenderName: " +c.getString(displayNameIndex));
+ name = c.getString(displayNameIndex);
}
if (D) Log.d(TAG, "setSenderName: " + name);
e.setSenderName(name);
@@ -569,6 +703,7 @@ public class BluetoothMapContent {
private void setDateTime(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
long date = 0;
+ int timeStamp = 0;
if (fi.msgType == FilterInfo.TYPE_SMS) {
date = c.getLong(c.getColumnIndex(Sms.DATE));
@@ -583,6 +718,10 @@ public class BluetoothMapContent {
/* } else { */
/* date = c.getLong(c.getColumnIndex(Mms.DATE_SENT)) * 1000L; */
/* } */
+ } else {
+ timeStamp = c.getColumnIndex(MessageColumns.TIMESTAMP);
+ String timestamp = c.getString(timeStamp);
+ date =Long.valueOf(timestamp);
}
e.setDateTime(date);
}
@@ -609,6 +748,29 @@ public class BluetoothMapContent {
return text;
}
+
+ private String getTextPartsEmail(long id) {
+ String text = "";
+ String selection = new String("mid=" + id);
+ String uriStr = String.format("content://mms/%d/part", id);
+ Uri uriAddress = Uri.parse(uriStr);
+ Cursor c = mResolver.query(uriAddress, null, selection,
+ null, null);
+
+ if (c != null && c.moveToFirst()) {
+ do {
+ String ct = c.getString(c.getColumnIndex("ct"));
+ if (ct.equals("text/plain")) {
+ text += c.getString(c.getColumnIndex("text"));
+ }
+ } while(c.moveToNext());
+ }
+ if (c != null) {
+ c.close();
+ }
+ return text;
+ }
+
private void setSubject(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
String subject = "";
@@ -626,6 +788,13 @@ public class BluetoothMapContent {
long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
subject = getTextPartsMms(id);
}
+ } else {
+ subject = c.getString(c.getColumnIndex(MessageColumns.SUBJECT));
+ if (subject == null || subject.length() == 0) {
+ /* Get subject from mms text body parts - if any exists */
+ long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+ subject = getTextPartsEmail(id);
+ }
}
if (subject != null) {
subject = subject.substring(0, Math.min(subject.length(),
@@ -648,6 +817,8 @@ public class BluetoothMapContent {
}
} else if (fi.msgType == FilterInfo.TYPE_MMS) {
type = TYPE.MMS;
+ } else {
+ type = TYPE.EMAIL;
}
if (D) Log.d(TAG, "setHandle: " + handle + " - Type: " + type.name());
e.setHandle(handle, type);
@@ -662,6 +833,7 @@ public class BluetoothMapContent {
setDateTime(e, c, fi, ap);
setSenderName(e, c, fi, ap);
setSenderAddressing(e, c, fi, ap);
+ //setReplytoAddressing(e, c, fi, ap);
setRecipientName(e, c, fi, ap);
setRecipientAddressing(e, c, fi, ap);
setType(e, c, fi, ap);
@@ -677,7 +849,7 @@ public class BluetoothMapContent {
}
private String getContactNameFromPhone(String phone) {
- String name = "";
+ String name = null;
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
Uri.encode(phone));
@@ -714,6 +886,22 @@ public class BluetoothMapContent {
return addr;
}
+ private boolean matchRecipientEmail(Cursor c, FilterInfo fi, String recip) {
+ boolean res = false;
+ String name = null;
+ int toIndex = c.getColumnIndex("toList");
+ name = c.getString(toIndex);
+
+ if (name != null && name.length() > 0) {
+ if (name.matches(recip)) {
+ if (D) Log.d(TAG, "match recipient name = " + name);
+ res = true;
+ } else {
+ res = false;
+ }
+ }
+ return res;
+ }
private boolean matchRecipientMms(Cursor c, FilterInfo fi, String recip) {
boolean res;
long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
@@ -785,6 +973,8 @@ public class BluetoothMapContent {
res = matchRecipientSms(c, fi, recip);
} else if (fi.msgType == FilterInfo.TYPE_MMS) {
res = matchRecipientMms(c, fi, recip);
+ } else if (fi.msgType == FilterInfo.TYPE_EMAIL) {
+ res = matchRecipientEmail(c, fi, recip);
} else {
if (D) Log.d(TAG, "Unknown msg type: " + fi.msgType);
res = false;
@@ -795,6 +985,23 @@ public class BluetoothMapContent {
return res;
}
+ private boolean matchOriginatorEmail(Cursor c, FilterInfo fi, String orig) {
+ boolean res = false;
+ String name;
+ int displayNameIndex = c.getColumnIndex("displayName");
+ name = c.getString(displayNameIndex);
+
+ if (name != null && name.length() > 0) {
+ if (name.matches(orig)) {
+ if (D) Log.d(TAG, "match originator name = " + name);
+ res = true;
+ } else {
+ res = false;
+ }
+ }
+ return res;
+ }
+
private boolean matchOriginatorMms(Cursor c, FilterInfo fi, String orig) {
boolean res;
long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
@@ -866,6 +1073,8 @@ public class BluetoothMapContent {
res = matchOriginatorSms(c, fi, orig);
} else if (fi.msgType == FilterInfo.TYPE_MMS) {
res = matchOriginatorMms(c, fi, orig);
+ } else if (fi.msgType == FilterInfo.TYPE_EMAIL) {
+ res = matchOriginatorEmail(c, fi, orig);
} else {
Log.d(TAG, "Unknown msg type: " + fi.msgType);
res = false;
@@ -884,6 +1093,44 @@ public class BluetoothMapContent {
}
}
+
+ private String getQueryWithMailBoxKey(String folder, long id) {
+ Log.d(TAG, "Inside getQueryWithMailBoxKey ");
+ String query = "mailboxKey = -1";;
+ String folderId;
+ Uri uri = Uri.parse("content://com.android.email.provider/mailbox");
+ if (folder == null) {
+ return null;
+ }
+ if (folder.contains("'")){
+ folder = folder.replace("'", "''");
+ }
+ Cursor cr = mResolver.query(
+ uri, null, "(" + ACCOUNT_KEY + "=" + id +
+ ") AND (UPPER(displayName) = '"+ folder.toUpperCase()+"')" , null, null);
+ if (cr != null) {
+ if ( cr.moveToFirst()) {
+ do {
+ folderId = cr.getString(cr.getColumnIndex("_id"));
+ query = "mailboxKey = "+ folderId;
+ break;
+ } while ( cr.moveToNext());
+ }
+ cr.close();
+ }
+ if(D) Log.d(TAG, "Returning "+query);
+ return query;
+ }
+
+ private String setWhereFilterFolderTypeEmail(String folder) {
+ String where = "";
+ Log.d(TAG, "Inside setWhereFilterFolderTypeEmail ");
+ Log.d(TAG, "folder is " + folder);
+ where = getQueryWithMailBoxKey(folder,BluetoothMapUtils.getEmailAccountId(mContext)/*1*/);
+ if(D) Log.d(TAG, "where query is " + where);
+ return where;
+ }
+
private String setWhereFilterFolderTypeSms(String folder) {
String where = "";
if ("inbox".equalsIgnoreCase(folder)) {
@@ -932,6 +1179,9 @@ public class BluetoothMapContent {
where = setWhereFilterFolderTypeSms(folder);
} else if (fi.msgType == FilterInfo.TYPE_MMS) {
where = setWhereFilterFolderTypeMms(folder);
+ } else {
+ where = setWhereFilterFolderTypeEmail(folder);
+
}
return where;
@@ -1088,6 +1338,29 @@ public class BluetoothMapContent {
return where;
}
+ private String setWhereFilterAccountKey(long id) {
+ String where = "";
+ where = "accountkey=" + id;
+ return where;
+ }
+
+ private String setWhereFilterMessagekey(long id) {
+ String where = "";
+ where = " messageKey = " + id;
+ return where;
+ }
+
+ private String setWhereFilterServerId(String path) {
+ String where = "";
+ if(path.equals("msg")) {
+ where = "serverId =" + DISPLAY_NAME;
+ }
+ else {
+ where = "serverId LIKE '%" + path + "/%'";
+ }
+ return where;
+ }
+
private String setWhereFilter(String folder, FilterInfo fi, BluetoothMapAppParams ap) {
String where = "";
@@ -1144,6 +1417,431 @@ public class BluetoothMapContent {
}
}
+ /**
+ * Return Email sub folder list for the id and serverId path
+ * @param context the calling Context
+ * @param id the email account id
+ * @return the list of server id's of Email sub folder
+ */
+ public List<String> getEmailFolderListAtPath(Context context, long id, String path) {
+ if (V) Log.v(TAG, "getEmailFolderListAtPath: id = " + id + "path: " + path);
+ String where = "";
+ ArrayList<String> list = new ArrayList<String>();
+ where += setWhereFilterAccountKey(id);
+ where += " AND ";
+ where += setWhereFilterServerId(path);
+ Cursor cr = context.getContentResolver().query(EMAIL_BOX_URI, null, where, null, null);
+ if (cr != null) {
+ if (cr.moveToFirst()) {
+ final int columnIndex = cr.getColumnIndex(SERVER_ID);
+ do {
+ final String value = cr.getString(columnIndex);
+ list.add(value);
+ if (V) Log.v(TAG, "adding: " + value);
+ } while (cr.moveToNext());
+ }
+ cr.close();
+ }
+ return list;
+ }
+
+ private boolean emailSelected(FilterInfo fi, BluetoothMapAppParams ap) {
+ int msgType = ap.getFilterMessageType();
+ if(D) Log.d(TAG, "emailSelected, msgType : " + msgType);
+
+ if (msgType == -1)
+ return true;
+
+ if ((msgType & 0x16) == 0)
+ return true;
+
+ return false;
+
+ }
+
+ private void printEmail(Cursor c) {
+ if (D) Log.d(TAG, "printEmail " +
+ c.getLong(c.getColumnIndex("_id")) +
+ "\n " + "displayName" + " : " + c.getString(c.getColumnIndex("displayName")) +
+ "\n " + "subject" + " : " + c.getString(c.getColumnIndex("subject")) +
+ "\n " + "flagRead" + " : " + c.getString(c.getColumnIndex("flagRead")) +
+ "\n " + "flagAttachment" + " : " + c.getInt(c.getColumnIndex("flagAttachment")) +
+ "\n " + "flags" + " : " + c.getInt(c.getColumnIndex("flags")) +
+ "\n " + "syncServerId" + " : " + c.getInt(c.getColumnIndex("syncServerId")) +
+ "\n " + "clientId" + " : " + c.getInt(c.getColumnIndex("clientId")) +
+ "\n " + "messageId" + " : " + c.getInt(c.getColumnIndex("messageId")) +
+ "\n " + "timeStamp" + " : " + c.getInt(c.getColumnIndex("timeStamp")) +
+ "\n " + "mailboxKey" + " : " + c.getInt(c.getColumnIndex("mailboxKey")) +
+ "\n " + "accountKey" + " : " + c.getInt(c.getColumnIndex("accountKey")) +
+ "\n " + "fromList" + " : " + c.getString(c.getColumnIndex("fromList")) +
+ "\n " + "toList" + " : " + c.getString(c.getColumnIndex("toList")) +
+ "\n " + "ccList" + " : " + c.getString(c.getColumnIndex("ccList")) +
+ "\n " + "bccList" + " : " + c.getString(c.getColumnIndex("bccList")) +
+ "\n " + "replyToList" + " : " + c.getString(c.getColumnIndex("replyToList")) +
+ "\n " + "meetingInfo" + " : " + c.getString(c.getColumnIndex("meetingInfo")) +
+ "\n " + "snippet" + " : " + c.getString(c.getColumnIndex("snippet")) +
+ "\n " + "protocolSearchInfo" + " : " + c.getString(c.getColumnIndex("protocolSearchInfo")) +
+ "\n " + "threadTopic" + " : " + c.getString(c.getColumnIndex("threadTopic")));
+ }
+
+ /**
+ * Get the folder name (MAP representation) for Email based on the
+ * mailboxKey value in message table
+ */
+ private String getContainingFolderEmail(int folderId) {
+ Cursor cr;
+ String folderName = null;
+ String whereClause = "_id = " + folderId;
+ cr = mResolver.query(
+ Uri.parse("content://com.android.email.provider/mailbox"),
+ null, whereClause, null, null);
+ if (cr != null) {
+ if (cr.getCount() > 0) {
+ cr.moveToFirst();
+ folderName = cr.getString(cr.getColumnIndex(MessageColumns.DISPLAY_NAME));
+ }
+ cr.close();
+ }
+ if (V) Log.v(TAG, "getContainingFolderEmail returning " + folderName);
+ return folderName;
+ }
+
+
+ private void extractEmailAddresses(long id, BluetoothMapbMessageMmsEmail message) {
+ if (V) Log.v(TAG, "extractEmailAddresses with id " + id);
+ String urlEmail = "content://com.android.email.provider/message";
+ Uri uriEmail = Uri.parse(urlEmail);
+ StringTokenizer emailId;
+ String tempEmail = null;
+ Cursor c = mResolver.query(uriEmail, EMAIL_PROJECTION, "_id = " + id, null, null);
+ if (c != null && c.moveToFirst()) {
+ String senderName = null;
+ if((senderName = c.getString(c.getColumnIndex(MessageColumns.FROM_LIST))) != null ) {
+ if(V) Log.v(TAG, " senderName " + senderName);
+ if(senderName.contains("")){
+ String[] senderStr = senderName.split("");
+ if(senderStr !=null && senderStr.length > 0){
+ if(V) Log.v(TAG, " senderStr[1] " + senderStr[1].trim());
+ if(V) Log.v(TAG, " senderStr[0] " + senderStr[0].trim());
+ setVCardFromEmailAddress(message, senderStr[1].trim(), true);
+ message.addFrom(null, senderStr[0].trim());
+ }
+ } else {
+ if(V) Log.v(TAG, " senderStr is" + senderName.trim());
+ setVCardFromEmailAddress(message, senderName.trim(), true);
+ message.addFrom(null, senderName.trim());
+ }
+ }
+ String recipientName = null;
+ String multiRecepients = null;
+ if((recipientName = c.getString(c.getColumnIndex(MessageColumns.TO_LIST))) != null){
+ if(V) Log.v(TAG, " recipientName " + recipientName);
+ if(recipientName.contains("")){
+ String[] recepientStr = recipientName.split("");
+ if(recepientStr !=null && recepientStr.length > 0){
+ if (V){
+ Log.v(TAG, " recepientStr[1] " + recepientStr[1].trim());
+ Log.v(TAG, " recepientStr[0] " + recepientStr[0].trim());
+ }
+ setVCardFromEmailAddress(message, recepientStr[1].trim(), false);
+ message.addTo(recepientStr[1].trim(), recepientStr[0].trim());
+ }
+ } else if(recipientName.contains("")){
+ multiRecepients = recipientName.replace('', ';');
+ if(multiRecepients != null){
+ if (V){
+ Log.v(TAG, " Setting ::Recepient name :: " + multiRecepients.trim());
+ }
+ emailId = new StringTokenizer(multiRecepients.trim(),";");
+ do {
+ setVCardFromEmailAddress(message, emailId.nextElement().toString(), false);
+ } while(emailId.hasMoreElements());
+
+ message.addTo(multiRecepients.trim(), multiRecepients.trim());
+ }
+ } else if(recipientName.contains(",")){
+ multiRecepients = recipientName.replace(',', ';');
+ if(multiRecepients != null){
+ if (V){
+ Log.v(TAG, "Setting ::Recepient name :: " + multiRecepients.trim());
+ }
+ emailId = new StringTokenizer(multiRecepients.trim(),";");
+ do {
+ tempEmail = emailId.nextElement().toString();
+ setVCardFromEmailAddress(message, tempEmail, false);
+ message.addTo(null, tempEmail);
+ } while(emailId.hasMoreElements());
+ }
+ } else {
+ Log.v(TAG, " Setting ::Recepient name :: " + recipientName.trim());
+ setVCardFromEmailAddress(message, recipientName.trim(), false);
+ message.addTo(null, recipientName.trim());
+ }
+ }
+ }
+ }
+
+ /**
+ * Read out a mms data part and return the data in a byte array.
+ * @param partid the content provider id of the mms.
+ * @return
+ */
+ private byte[] readEmailDataPart(long partid) {
+ String uriStr = String.format("content://mms/part/%d", partid);
+ Uri uriAddress = Uri.parse(uriStr);
+ InputStream is = null;
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ int bufferSize = 8192;
+ byte[] buffer = new byte[bufferSize];
+ byte[] retVal = null;
+
+ try {
+ is = mResolver.openInputStream(uriAddress);
+ int len = 0;
+ while ((len = is.read(buffer)) != -1) {
+ os.write(buffer, 0, len); // We need to specify the len, as it can be != bufferSize
+ }
+ retVal = os.toByteArray();
+ } catch (IOException e) {
+ // do nothing for now
+ Log.w(TAG,"Error reading part data",e);
+ } finally {
+ try {
+ os.close();
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ return retVal;
+ }
+
+
+ /**
+ * Read out the mms parts and update the bMessage object provided i {@linkplain message}
+ * @param id the content provider ID of the message
+ * @param message the bMessage object to add the information to
+ */
+ private void extractEmailParts(long id, BluetoothMapbMessageMmsEmail message)
+ {
+ if (V) Log.v(TAG, "extractEmailParts with id " + id);
+ String where = setWhereFilterMessagekey(id);
+ String emailBody = "";
+ Uri uriAddress = Uri.parse("content://com.android.email.provider/body");
+ BluetoothMapbMessageMmsEmail.MimePart part;
+ Cursor c = mResolver.query(
+ uriAddress,
+ null,
+ where,
+ null, null);
+
+ if(c != null) {
+ if (V) Log.v(TAG, "cursor not null");
+ if (c.moveToFirst()) {
+ emailBody = c.getString(c.getColumnIndex("textContent"));
+ if (emailBody == null || emailBody.length() == 0) {
+ String msgBody = c.getString(c.getColumnIndex("htmlContent"));
+ if (msgBody != null) {
+ msgBody = msgBody.replaceAll("(?s)(<title>)(.*?)(</title>)", "");
+ msgBody = msgBody.replaceAll("(?s)(<style type=\"text/css\".*?>)(.*?)(</style>)", "");
+ CharSequence msgText = Html.fromHtml(msgBody);
+ emailBody = msgText.toString();
+ emailBody = emailBody.replaceAll("(?s)(<!--)(.*?)(-->)", "");
+ // Solves problem with Porche Car-kit and Gmails.
+ // Changes unix style line conclusion to DOS style
+ emailBody = emailBody.replaceAll("(?s)(\\r)", "");
+ emailBody = emailBody.replaceAll("(?s)(\\n)", "\r\n");
+ }
+ message.setEmailBody(emailBody);
+ }
+
+ Long partId = c.getLong(c.getColumnIndex(BaseColumns._ID));
+ String contentType = "Content-Type: text/plain; charset=\"UTF-8\"";
+ String name = null;//c.getString(c.getColumnIndex("displayName"));
+ String text = null;
+
+ if(D) Log.d(TAG, " _id : " + partId +
+ "\n ct : " + contentType +
+ "\n partname : " + name);
+
+ part = message.addMimePart();
+ part.contentType = contentType;
+ part.partName = name;
+
+ try {
+ if(emailBody != null) {
+ part.data = emailBody.getBytes("UTF-8");
+ part.charsetName = "utf-8";
+ }
+ } catch (NumberFormatException e) {
+ Log.d(TAG,"extractEmailParts",e);
+ part.data = null;
+ part.charsetName = null;
+ } catch (UnsupportedEncodingException e) {
+ Log.d(TAG,"extractEmailParts",e);
+ part.data = null;
+ part.charsetName = null;
+ } finally {
+ }
+ }
+ }
+ message.updateCharset();
+ message.setEncoding("8BIT");
+ }
+
+ /**
+ *
+ * @param id the content provider id for the message to fetch.
+ * @param appParams The application parameter object received from the client.
+ * @return a byte[] containing the utf-8 encoded bMessage to send to the client.
+ * @throws UnsupportedEncodingException if UTF-8 is not supported,
+ * which is guaranteed to be supported on an android device
+ */
+ public byte[] getEmailMessage(long id, BluetoothMapAppParams appParams) throws UnsupportedEncodingException {
+ if (V) Log.v(TAG, "getEmailMessage with is " + id);
+ int msgBox, threadId;
+ String urlEmail = "content://com.android.email.provider/message";
+ Uri uriEmail = Uri.parse(urlEmail);
+ BluetoothMapbMessageMmsEmail message = new BluetoothMapbMessageMmsEmail();
+ Cursor c = mResolver.query(uriEmail, EMAIL_PROJECTION, "_id = " + id, null, null);
+ if(c != null && c.moveToFirst())
+ {
+ message.setType(TYPE.EMAIL);
+
+ // The EMAIL info:
+ if (c.getString(c.getColumnIndex(MessageColumns.FLAG_READ)).equalsIgnoreCase("1"))
+ message.setStatus(true);
+ else
+ message.setStatus(false);
+
+ msgBox = c.getInt(c.getColumnIndex(MessageColumns.MAILBOX_KEY));
+ message.setFolder(getContainingFolderEmail(msgBox));
+
+ message.setSubject(c.getString(c.getColumnIndex(MessageColumns.SUBJECT)));
+ message.setContentType("Content-Type: text/plain; charset=\"UTF-8\"");
+ message.setDate(c.getLong(c.getColumnIndex(MessageColumns.TIMESTAMP)) * 1000L);
+ message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true);
+
+ // The parts
+ extractEmailParts(id, message);
+
+ // The addresses
+ extractEmailAddresses(id, message);
+
+ c.close();
+
+ return message.encodeEmail();
+ }
+ else if(c != null) {
+ c.close();
+ }
+
+ throw new IllegalArgumentException("MMS handle not found");
+ }
+
+ public BluetoothMapMessageListing msgListingEmail(String folder, BluetoothMapAppParams ap) {
+ Log.d(TAG, "msgListing: folder = " + folder);
+ String urlEmail = "content://com.android.email.provider/message";
+ Uri uriEmail = Uri.parse(urlEmail);
+ BluetoothMapMessageListing bmList = new BluetoothMapMessageListing();
+ BluetoothMapMessageListingElement e = null;
+
+ /* Cache some info used throughout filtering */
+ FilterInfo fi = new FilterInfo();
+ setFilterInfo(fi);
+
+ if (emailSelected(fi, ap)) {
+ fi.msgType = FilterInfo.TYPE_EMAIL;
+
+ String where = setWhereFilter(folder, fi, ap);
+ Log.d(TAG, "where clause is = " + where);
+ try {
+ Cursor c = mResolver.query(uriEmail,
+ EMAIL_PROJECTION, where, null, "timeStamp desc");
+ if(c == null) {
+ Log.e(TAG, "Cursor is null. Returning from here");
+ }
+
+ if (c != null) {
+ while (c.moveToNext()) {
+ if (matchAddresses(c, fi, ap)) {
+ printEmail(c);
+ e = element(c, fi, ap);
+ bmList.add(e);
+ }
+ }
+ c.close();
+ }
+ } catch (SQLiteException es) {
+ Log.e(TAG, "SQLite exception: " + es);
+ }
+ }
+
+ /* Enable this if post sorting and segmenting needed */
+ bmList.sort();
+ bmList.segment(ap.getMaxListCount(), ap.getStartOffset());
+
+ return bmList;
+ }
+
+ public int msgListingSizeEmail(String folder, BluetoothMapAppParams ap) {
+ if (D) Log.d(TAG, "msgListingSize: folder = " + folder);
+ int cnt = 0;
+ String urlEmail = "content://com.android.email.provider/message";
+ Uri uriEmail = Uri.parse(urlEmail);
+
+ /* Cache some info used throughout filtering */
+ FilterInfo fi = new FilterInfo();
+ setFilterInfo(fi);
+
+ if (emailSelected(fi, ap)) {
+ fi.msgType = FilterInfo.TYPE_EMAIL;
+ String where = setWhereFilter(folder, fi, ap);
+ Cursor c = mResolver.query(uriEmail,
+ EMAIL_PROJECTION, where, null, "timeStamp desc");
+
+ if (c != null) {
+ cnt += c.getCount();
+ c.close();
+ }
+ }
+
+ if (D) Log.d(TAG, "msgListingSize: size = " + cnt);
+ return cnt;
+ }
+ /**
+ * Return true if there are unread messages in the requested list of messages
+ * @param folder folder where the message listing should come from
+ * @param ap application parameter object
+ * @return true if unread messages are in the list, else false
+ */
+ public boolean msgListingHasUnreadEmail(String folder, BluetoothMapAppParams ap) {
+ if (D) Log.d(TAG, "msgListingHasUnread: folder = " + folder);
+ int cnt = 0;
+ String urlEmail = "content://com.android.email.provider/message";
+ Uri uriEmail = Uri.parse(urlEmail);
+
+ /* Cache some info used throughout filtering */
+ FilterInfo fi = new FilterInfo();
+ setFilterInfo(fi);
+
+ if (emailSelected(fi, ap)) {
+ fi.msgType = FilterInfo.TYPE_EMAIL;
+ String where = setWhereFilterFolderType(folder, fi);
+ where += " AND flagRead=0 ";
+ where += setWhereFilterPeriod(ap, fi);
+ Cursor c = mResolver.query(uriEmail,
+ EMAIL_PROJECTION, where, null, "timeStamp desc");
+
+ if (c != null) {
+ cnt += c.getCount();
+ c.close();
+ }
+ }
+
+ if (D) Log.d(TAG, "msgListingHasUnread: numUnread = " + cnt);
+ return (cnt>0)?true:false;
+ }
+
public BluetoothMapMessageListing msgListing(String folder, BluetoothMapAppParams ap) {
Log.d(TAG, "msgListing: folder = " + folder);
BluetoothMapMessageListing bmList = new BluetoothMapMessageListing();
@@ -1306,6 +2004,15 @@ public class BluetoothMapContent {
}
return "";
}
+ public void msgUpdate() {
+ if (D) Log.d(TAG, "Message Update");
+ long accountId = BluetoothMapUtils.getEmailAccountId(mContext);
+ if (V) Log.v(TAG, " Account id for Inbox Update: " +accountId);
+ Intent emailIn = new Intent();
+ emailIn.setAction("com.android.email.intent.action.MAIL_SERVICE_WAKEUP");
+ emailIn.putExtra("com.android.email.intent.extra.ACCOUNT", accountId);
+ mContext.sendBroadcast(emailIn);
+ }
public byte[] getMessage(String handle, BluetoothMapAppParams appParams) throws UnsupportedEncodingException{
TYPE type = BluetoothMapUtils.getMsgTypeFromHandle(handle);
@@ -1317,11 +2024,32 @@ public class BluetoothMapContent {
case MMS:
return getMmsMessage(id, appParams);
case EMAIL:
- throw new IllegalArgumentException("Email not implemented - invalid message handle.");
+ return getEmailMessage(id, appParams);
}
throw new IllegalArgumentException("Invalid message handle.");
}
+ private void setVCardFromEmailAddress(BluetoothMapbMessage message, String emailAddr, boolean incoming) {
+ if(D) Log.d(TAG, "setVCardFromEmailAddress, emailAdress is " +emailAddr);
+ String contactId = null, contactName = null;
+ String[] phoneNumbers = null;
+ String[] emailAddresses = new String[1];
+ StringTokenizer emailId;
+ Cursor p;
+
+ if(incoming == true) {
+ emailAddresses[0] = emailAddr;
+ if(V) Log.v(TAG,"Adding addOriginator " + emailAddresses[0]);
+ message.addOriginator(emailAddr, phoneNumbers, emailAddresses);
+ }
+ else
+ {
+ emailAddresses[0] = emailAddr;
+ if(V) Log.v(TAG,"Adding Receipient " + emailAddresses[0]);
+ message.addRecipient(emailAddr, phoneNumbers, emailAddresses);
+ }
+ }
+
private void setVCardFromPhoneNumber(BluetoothMapbMessage message, String phone, boolean incoming) {
String contactId = null, contactName = null;
String[] phoneNumbers = null;
@@ -1402,7 +2130,7 @@ public class BluetoothMapContent {
if(c != null && c.moveToFirst())
{
- if(V) Log.d(TAG,"c.count: " + c.getCount());
+ if(V) Log.v(TAG,"c.count: " + c.getCount());
if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
message.setType(TYPE.SMS_GSM);
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentEmailObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentEmailObserver.java
new file mode 100644
index 000000000..3f710f651
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapContentEmailObserver.java
@@ -0,0 +1,596 @@
+/*
+* Copyright (c) 2013, The Linux Foundation. All rights reserved.
+* Not a Contribution.
+* 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.ByteArrayInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Collection;
+import org.xmlpull.v1.XmlSerializer;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.BaseColumns;
+import android.provider.Telephony;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.Sms;
+import android.provider.Telephony.Sms.Inbox;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SmsManager;
+import android.telephony.SmsMessage;
+import android.telephony.TelephonyManager;
+import android.database.sqlite.SQLiteException;
+import android.util.Log;
+import android.util.Xml;
+import android.os.Looper;
+import com.android.emailcommon.provider.EmailContent;
+import com.android.emailcommon.provider.EmailContent.Message;
+import com.android.emailcommon.provider.EmailContent.MessageColumns;
+import com.android.emailcommon.provider.EmailContent.SyncColumns;
+import com.android.emailcommon.provider.Mailbox;
+
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.bluetooth.map.BluetoothMapbMessageMmsEmail.MimePart;
+import com.google.android.mms.pdu.PduHeaders;
+import android.text.format.Time;
+
+public class BluetoothMapContentEmailObserver extends BluetoothMapContentObserver {
+ private static final String TAG = "BluetoothMapContentEmailObserver";
+ private static final boolean D = BluetoothMapService.DEBUG;
+ private static final boolean V = BluetoothMapService.VERBOSE;
+
+ private HashMap<Long, EmailBox> mEmailBoxList = new HashMap<Long, EmailBox>();
+ private HashMap<Long, EmailMessage> mEmailList = new HashMap<Long, EmailMessage>();
+ /** List of deleted message, do not notify */
+ private HashMap<Long, EmailMessage> mDeletedList = new HashMap<Long, EmailMessage>();
+ private HashMap<Long, EmailMessage> mEmailAddedList = new HashMap<Long, EmailMessage>();
+ /** List of newly deleted message, notify */
+ private HashMap<Long, EmailMessage> mEmailDeletedList = new HashMap<Long, EmailMessage>();
+ public static final int EMAIL_BOX_COLUMN_RECORD_ID = 0;
+ public static final int EMAIL_BOX_COLUMN_DISPLAY_NAME = 1;
+ public static final int EMAIL_BOX_COLUMN_ACCOUNT_KEY = 2;
+ public static final int EMAIL_BOX_COLUMN_TYPE = 3;
+ public static final String AUTHORITY = "com.android.email.provider";
+ public static final Uri EMAIL_URI = Uri.parse("content://" + AUTHORITY);
+ public static final Uri EMAIL_ACCOUNT_URI = Uri.withAppendedPath(EMAIL_URI, "account");
+ public static final Uri EMAIL_BOX_URI = Uri.withAppendedPath(EMAIL_URI, "mailbox");
+ public static final Uri EMAIL_MESSAGE_URI = Uri.withAppendedPath(EMAIL_URI, "message");
+ public static final int MSG_COL_RECORD_ID = 0;
+ public static final int MSG_COL_MAILBOX_KEY = 1;
+ public static final int MSG_COL_ACCOUNT_KEY = 2;
+ private static final String EMAIL_TO_MAP[] = {
+ "inbox", // TYPE_INBOX = 0;
+ "", // TYPE_MAIL = 1;
+ "", // TYPE_PARENT = 2;
+ "draft", // TYPE_DRAFTS = 3;
+ "outbox", // TYPE_OUTBOX = 4;
+ "sent", // TYPE_SENT = 5;
+ "deleted", // TYPE_TRASH = 6;
+ "" // TYPE_JUNK = 7;
+ };
+
+ // Types of mailboxes. From EmailContent.java
+ // inbox
+ public static final int TYPE_INBOX = 0;
+ // draft
+ public static final int TYPE_DRAFT = 3;
+ // outbox
+ public static final int TYPE_OUTBOX = 4;
+ // sent
+ public static final int TYPE_SENT = 5;
+ // deleted
+ public static final int TYPE_DELETED = 6;
+
+ private long mAccountKey;
+ private static final int UPDATE = 0;
+ private static final int THRESHOLD = 3000; // 3 sec
+
+ public static final String RECORD_ID = "_id";
+ public static final String ACCOUNT_KEY = "accountKey";
+ public static final String DISPLAY_NAME = "displayName";
+ public static final String EMAILTYPE = "type";
+ public static final String MAILBOX_KEY = "mailboxKey";
+ public static final String EMAIL_ADDRESS = "emailAddress";
+ public static final String IS_DEFAULT = "isDefault";
+ private static final String[] ACCOUNT_ID_PROJECTION = new String[] {
+ RECORD_ID, EMAIL_ADDRESS, IS_DEFAULT, DISPLAY_NAME
+ };
+ public static final String[] EMAIL_MESSAGE_PROJECTION = new String[] {
+ RECORD_ID, MAILBOX_KEY, ACCOUNT_KEY
+ };
+ public static final String[] EMAIL_BOX_PROJECTION = new String[] {
+ RECORD_ID, DISPLAY_NAME, ACCOUNT_KEY, EMAILTYPE
+ };
+ public long id;
+ public String folderName;
+
+ public BluetoothMapContentEmailObserver(final Context context ) {
+ super(context);
+ }
+
+ private final ContentObserver mObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ onChange(selfChange, null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (V) Log.v(TAG, "onChange on thread: " + Thread.currentThread().getId()
+ + " Uri: " + uri.toString() + " selfchange: " + selfChange);
+
+ if (mHandler.hasMessages(UPDATE)) {
+ mHandler.removeMessages(UPDATE);
+ }
+ mHandler.sendEmptyMessageDelayed(UPDATE, THRESHOLD);
+ }
+ private Handler mHandler = new Handler(Looper.getMainLooper()) {
+ private static final String TAG = "EmailContentObserver.Hanlder";
+ @Override
+ public void handleMessage(android.os.Message msg) {
+ if (V) Log.v(TAG, "handleMessage(" + msg.what + ") mas Id: " + mMasId);
+ switch (msg.what) {
+ case UPDATE:
+ new Thread(new Runnable() {
+ public void run() {
+ updateEmailBox();
+ update(false);
+ sendEvents();
+ }
+ }, "Email Content Observer Thread").start();
+ break;
+ }
+ }
+ };
+ };
+
+ private boolean isMapFolder(int type) {
+ if (type == TYPE_INBOX || type == TYPE_OUTBOX || type == TYPE_SENT ||
+ type == TYPE_DRAFT || type == TYPE_DELETED) {
+ return true;
+ }
+ return false;
+ }
+
+ static class EmailBox {
+ long mId;
+ String mDisplayName;
+ long mAccountKey;
+ int mType;
+
+ public EmailBox(long id, String displayName, long accountKey, int type) {
+ mId = id;
+ mDisplayName = displayName;
+ mAccountKey = accountKey;
+ mType = type;
+ }
+
+ @Override
+ public String toString() {
+ return "[id:" + mId + ", display name:" + mDisplayName + ", account key:" +
+ mAccountKey + ", type:" + mType + "]";
+ }
+ }
+
+ static class EmailMessage {
+ long mId;
+ long mAccountKey;
+ String mFolderName;
+ int mType;
+
+ public EmailMessage(long id, long accountKey, String folderName, int type) {
+ mId = id;
+ mAccountKey = accountKey;
+ mFolderName = folderName;
+ mType = type;
+ }
+
+ @Override
+ public String toString() {
+ return "[id:" + mId + ", folder name:" + mFolderName + ", account key:" + mAccountKey +
+ ", type:" + mType + "]";
+ }
+ }
+
+ @Override
+ public void registerObserver(BluetoothMnsObexClient mns, int masId) {
+ if (D) Log.d(TAG, "registerObserver");
+ mAccountKey = BluetoothMapUtils.getEmailAccountId(mContext);
+ mMasId = masId;
+ mMnsClient = mns;
+ updateEmailBox();
+ update(true);
+ try {
+ mResolver.registerContentObserver(EMAIL_URI, true, mObserver);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "SQLite exception: " + e);
+ }
+ }
+
+ void updateEmailBox() {
+ if (D) Log.d(TAG, "updateEmailBox");
+ mEmailBoxList.clear();
+ final ContentResolver resolver = mContext.getContentResolver();
+ Cursor crBox;
+ try {
+ crBox = resolver.query(EMAIL_BOX_URI, EMAIL_BOX_PROJECTION, null, null, null);
+ if (crBox != null) {
+ if (crBox.moveToFirst()) {
+ do {
+ final long id = crBox.getLong(EMAIL_BOX_COLUMN_RECORD_ID);
+ final String displayName = crBox.getString(EMAIL_BOX_COLUMN_DISPLAY_NAME);
+ final long accountKey = crBox.getLong(EMAIL_BOX_COLUMN_ACCOUNT_KEY);
+ final int type = crBox.getInt(EMAIL_BOX_COLUMN_TYPE);
+ final EmailBox box = new EmailBox(id, displayName, accountKey, type);
+ mEmailBoxList.put(id, box);
+ if (V) Log.v(TAG, box.toString());
+ } while (crBox.moveToNext());
+ }
+ crBox.close();
+ }
+ } catch (SQLiteException e) {
+ crBox = null;
+ Log.e(TAG, "SQLite exception: " + e);
+ }
+ }
+
+ private void clear() {
+ mEmailList.clear();
+ mDeletedList.clear();
+ mEmailAddedList.clear();
+ mEmailDeletedList.clear();
+ }
+
+ void update(boolean init) {
+ if (V) Log.d(TAG, "update");
+ if (init) {
+ clear();
+ }
+ mEmailAddedList.clear();
+ mEmailDeletedList.clear();
+
+ final ContentResolver resolver = mContext.getContentResolver();
+ Cursor crEmail;
+ try {
+ crEmail = resolver.query(EMAIL_MESSAGE_URI, EMAIL_MESSAGE_PROJECTION,
+ null, null, null);
+ if (crEmail != null) {
+ if (crEmail.moveToFirst()) {
+ final HashMap<Long, EmailBox> boxList = mEmailBoxList;
+ HashMap<Long, EmailMessage> oldEmailList = mEmailList;
+ HashMap<Long, EmailMessage> emailList = new HashMap<Long, EmailMessage>();
+ do {
+ final long accountKey = crEmail.getLong(MSG_COL_ACCOUNT_KEY);
+ if (accountKey != mAccountKey) {
+ continue;
+ }
+ id = crEmail.getLong(MSG_COL_RECORD_ID);
+ final long mailboxKey = crEmail.getLong(MSG_COL_MAILBOX_KEY);
+ if (boxList.containsKey(mailboxKey)) {
+ final EmailBox box = boxList.get(mailboxKey);
+ if (box == null) {
+ continue;
+ }
+ folderName = isMapFolder(box.mType)
+ ? EMAIL_TO_MAP[box.mType] : box.mDisplayName;
+ final EmailMessage msg = new EmailMessage(id, accountKey,
+ folderName, box.mType);
+ if (box.mType == TYPE_DELETED) {
+ if (init) {
+ mDeletedList.put(id, msg);
+ } else if (!mDeletedList.containsKey(id) &&
+ !mEmailDeletedList.containsKey(id)) {
+ if(V) Log.v(TAG,"Putting in deleted list");
+ mEmailDeletedList.put(id, msg);
+ }
+ } else if (box.mType == TYPE_OUTBOX) {
+ // Do nothing got outbox folder
+ } else {
+ emailList.put(id, msg);
+ if (!oldEmailList.containsKey(id) && !init &&
+ !mEmailAddedList.containsKey(id)) {
+ Log.v(TAG,"Putting in added list");
+ mEmailAddedList.put(id, msg);
+ }
+ }
+ } else {
+ Log.e(TAG, "Mailbox is not updated");
+ }
+ } while (crEmail.moveToNext());
+ mEmailList = emailList;
+ }
+ crEmail.close();
+ }
+ } catch (SQLiteException e) {
+ crEmail = null;
+ Log.e(TAG, "SQLite exception: " + e);
+ }
+ }
+
+ private void sendEvents() {
+ if (mEmailAddedList.size() > 0) {
+ Event evt;
+ Collection<EmailMessage> values = mEmailAddedList.values();
+ if(values != null) {
+ for (EmailMessage email : values) {
+ if (email.mFolderName.equalsIgnoreCase("sent")) {
+ if(D) Log.d(TAG,"sending SendingSuccess mns event");
+ if(D) Log.d(TAG,"email.mId is "+email.mId);
+ if(D) Log.d(TAG,"email.mType is "+email.mType);
+ if(D) Log.d(TAG,"folder name is "+email.mFolderName);
+ evt = new Event("SendingSuccess", email.mId, email.mFolderName,
+ null, TYPE.EMAIL);
+ sendEvent(evt);
+ } else {
+ if(D) Log.d(TAG,"sending NewMessage mns event");
+ if(D) Log.d(TAG,"email.mId is "+email.mId);
+ if(D) Log.d(TAG,"email.mType is "+email.mType);
+ if(D) Log.d(TAG,"folder name is "+email.mFolderName);
+ evt = new Event("NewMessage", email.mId, folderName,
+ null, TYPE.EMAIL);
+ sendEvent(evt);
+ }
+ }
+ }
+ mEmailAddedList.clear();
+ }
+
+ if (mEmailDeletedList.size() > 0) {
+ mDeletedList.putAll(mEmailDeletedList);
+ Collection<EmailMessage> values = mEmailDeletedList.values();
+ for (EmailMessage email : values) {
+ if(D) Log.d(TAG,"sending MessageDeleted mns event");
+ if(D) Log.d(TAG,"email.mId is "+email.mId);
+ if(D) Log.d(TAG,"email.mType is "+email.mType);
+ if(D) Log.d(TAG,"folder name is "+email.mFolderName);
+
+ Event evt = new Event("MessageDeleted", email.mId, "deleted",
+ null, TYPE.EMAIL);
+ sendEvent(evt);
+ }
+ mEmailDeletedList.clear();
+ }
+ }
+
+ @Override
+ public void unregisterObserver() {
+ if (D) Log.d(TAG, "unregisterObserver");
+ mResolver.unregisterContentObserver(mObserver);
+ mMnsClient = null;
+ }
+
+ @Override
+ public boolean setMessageStatusDeleted(long handle, TYPE type, int statusValue) {
+ boolean res = true;
+
+ long accountId = BluetoothMapUtils.getEmailAccountId(mContext);
+ if (D) Log.d(TAG, "setMessageStatusDeleted: EMAIL handle " + handle
+ + " type " + type + " value " + statusValue + "accountId: "+accountId);
+ Intent emailIn = new Intent();
+ if(statusValue == 1){
+ addMceInitiatedOperation(Long.toString(handle));
+ emailIn.setAction("org.codeaurora.email.intent.action.MAIL_SERVICE_DELETE_MESSAGE");
+ }else {
+ emailIn.setAction("org.codeaurora.email.intent.action.MAIL_SERVICE_MOVE_MESSAGE");
+ emailIn.putExtra("org.codeaurora.email.intent.extra.MESSAGE_INFO", Mailbox.TYPE_INBOX);
+ }
+
+ emailIn.putExtra("com.android.email.intent.extra.ACCOUNT", accountId);
+ emailIn.putExtra("org.codeaurora.email.intent.extra.MESSAGE_ID", handle);
+ mContext.startService(emailIn);
+ return res;
+ }
+
+ @Override
+ public boolean setMessageStatusRead(long handle, TYPE type, int statusValue) {
+ boolean res = true;
+
+
+ Intent emailIn = new Intent();
+ long accountId = BluetoothMapUtils.getEmailAccountId(mContext);
+ if (D) Log.d(TAG, "setMessageStatusRead: EMAIL handle " + handle
+ + " type " + type + " value " + statusValue+ "accounId: " +accountId);
+
+ emailIn.setAction("org.codeaurora.email.intent.action.MAIL_SERVICE_MESSAGE_READ");
+ emailIn.putExtra("org.codeaurora.email.intent.extra.MESSAGE_INFO",statusValue);
+ emailIn.putExtra("com.android.email.intent.extra.ACCOUNT", accountId);
+ emailIn.putExtra("org.codeaurora.email.intent.extra.MESSAGE_ID", handle);
+ mContext.startService(emailIn);
+ return res;
+
+ }
+
+ /**
+ * Adds an Email to the Email ContentProvider
+ */
+ private long pushEmailToFolder( String folder, String toAddress, BluetoothMapbMessageMmsEmail msg) {
+ String msgBody = msg.getEmailBody();
+ int folderType = BluetoothMapUtils.getSystemMailboxGuessType(folder);
+ int folderId = -1;
+ long accountId = -1;
+ String originatorName = "";
+ String originatorEmail = "";
+ Time timeObj = new Time();
+ timeObj.setToNow();
+ //Fetch AccountId, originator email and displayName from DB.
+ Cursor cr = mContext.getContentResolver().query(EMAIL_ACCOUNT_URI,
+ ACCOUNT_ID_PROJECTION, null, null, null);
+ if (cr != null) {
+ if (cr.moveToFirst()) {
+ accountId = cr.getLong(0);
+ Log.v(TAG, "id = " + accountId);
+ originatorEmail = cr.getString(1);
+ Log.v(TAG, "email = " + originatorEmail);
+ originatorName = cr.getString(3);
+ Log.v(TAG, "Name = " + originatorName);
+ }
+ cr.close();
+ } else {
+ Log.v(TAG, "Account CURSOR NULL");
+ }
+ if (accountId == -1) {
+ Log.v(TAG, "INTERNAL ERROR For ACCNT ID");
+ return -1;
+ }
+ cr=null;
+ //Fetch FolderId for Folder Type
+ String whereClause = "TYPE = '"+folderType+"'";
+ cr = mContext.getContentResolver().query(
+ Uri.parse("content://com.android.email.provider/mailbox"),
+ null, whereClause, null, null);
+ if (cr != null) {
+ if (cr.getCount() > 0) {
+ cr.moveToFirst();
+ folderId = cr.getInt(cr.getColumnIndex("_id"));
+ }
+ cr.close();
+ }
+ if (folderId == -1) {
+ Log.v(TAG, "INTERNAL ERROR For Folder ID ");
+ return -1;
+ }
+ if (V){
+ Log.v(TAG, "-------------");
+ Log.v(TAG, "To address " + toAddress);
+ Log.v(TAG, "Text " + msgBody);
+ Log.v(TAG, "Originator email address:: " + originatorEmail);
+ Log.v(TAG, "Originator email name:: " + originatorName);
+ Log.v(TAG, "Time Stamp:: " + timeObj.toMillis(false));
+ Log.v(TAG, "Account Key:: " + accountId);
+ Log.v(TAG, "Folder Id:: " + folderId);
+ Log.v(TAG, "Folder Name:: " + folder);
+ Log.v(TAG, "Subject" + msg.getSubject());
+ }
+ ContentValues values = new ContentValues();
+ values.put("syncServerTimeStamp", 0);
+ values.put("syncServerId", "5:65");
+ values.put("displayName", originatorName);
+ values.put("timeStamp", timeObj.toMillis(false));
+ values.put("subject", msg.getSubject().trim());
+ values.put("flagLoaded", "1");
+ values.put("flagFavorite", "0");
+ values.put("flagAttachment", "0");
+ if(folderType == Mailbox.TYPE_DRAFTS)
+ values.put("flags", "1179648");
+ else
+ values.put("flags", "0");
+ values.put("accountKey", accountId);
+ values.put("fromList", originatorEmail.trim());
+
+ values.put("mailboxKey", folderId);
+ values.put("toList", toAddress.trim());
+ values.put("flagRead", 0);
+ Uri uri = mContext.getContentResolver().insert(
+ Uri.parse("content://com.android.email.provider/message"), values);
+ if (V){
+ Log.v(TAG, " NEW URI " + (uri == null ? "null" : uri.toString()));
+ }
+ if (uri == null) {
+ Log.v(TAG, "INTERNAL ERROR : NEW URI NULL");
+ return -1;
+ }
+ String str = uri.toString();
+ Log.v(TAG, " CREATE URI " + str);
+ String[] splitStr = str.split("/");
+ if (splitStr.length < 5) {
+ return -1;
+ }
+ if (V){
+ Log.v(TAG, " NEW HANDLE " + splitStr[4]);
+ }
+ //Insert msgBody in DB Provider BODY TABLE
+ ContentValues valuesBody = new ContentValues();
+ valuesBody.put("messageKey", splitStr[4]);
+ valuesBody.put("textContent", msgBody);
+
+ mContext.getContentResolver().insert(
+ Uri.parse("content://com.android.email.provider/body"), valuesBody);
+ long msgId;
+ msgId = Long.valueOf(splitStr[4]);
+ return msgId;
+
+ }
+
+ @Override
+ public long sendEmailMessage(String folder, String[] toList, BluetoothMapbMessageMmsEmail msg) {
+ Log.d(TAG, "sendMessage for " + folder);
+ /*
+ *strategy:
+ *1) parse message into parts
+ *if folder is drafts:
+ *2) push message to draft
+ *if folder is outbox:
+ *3) push message to outbox (to trigger the email app to add msg to pending_messages list)
+ *4) send intent to email app in order to wake it up.
+ *else if folder draft(s):
+ *1) push message to folder
+ * */
+ if (folder != null && (folder.equalsIgnoreCase("outbox")|| folder.equalsIgnoreCase("drafts"))) {
+ //Consolidate All recipients as toList
+ StringBuilder address = new StringBuilder();
+ for(String s : toList) {
+ address.append(s);
+ address.append(";");
+ }
+ long handle = pushEmailToFolder(folder, address.toString(), msg);
+ addMceInitiatedOperation("+");
+ /* if invalid handle (-1) then just return the handle - else continue sending (if folder is outbox) */
+ if (BluetoothMapAppParams.INVALID_VALUE_PARAMETER != handle && folder.equalsIgnoreCase("outbox")) {
+ Intent emailIn = new Intent();
+ long accountId = BluetoothMapUtils.getEmailAccountId(mContext);
+ Log.d(TAG, "pushToEmail : handle SEND MAIL" + handle + "accounId: " +accountId);
+ emailIn.setAction("org.codeaurora.email.intent.action.MAIL_SERVICE_SEND_PENDING");
+ emailIn.putExtra("com.android.email.intent.extra.ACCOUNT", accountId);
+ mContext.startService(emailIn);
+ }
+ return handle;
+ } else {
+ /* not allowed to push email to anything but outbox/drafts */
+ throw new IllegalArgumentException("Cannot push email message to other folders than outbox/drafts");
+ }
+ }
+ @Override
+ public void init() {
+ Log.d(TAG, "init ");
+ }
+ @Override
+ public void deinit() {
+ Log.d(TAG, "init ");
+ }
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index 4a2f76e6d..2dfccd3d5 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -27,6 +27,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.List;
import org.xmlpull.v1.XmlSerializer;
@@ -42,6 +43,7 @@ import android.content.IntentFilter;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
+import android.text.format.Time;
import android.os.Handler;
import android.provider.BaseColumns;
import android.provider.Telephony;
@@ -57,6 +59,7 @@ import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.Xml;
import android.os.Looper;
+import android.os.Message;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.map.BluetoothMapbMessageMmsEmail.MimePart;
@@ -65,13 +68,13 @@ import com.google.android.mms.pdu.PduHeaders;
public class BluetoothMapContentObserver {
private static final String TAG = "BluetoothMapContentObserver";
- private static final boolean D = true;
- private static final boolean V = true;
+ private static final boolean D = BluetoothMapService.DEBUG;
+ private static final boolean V = BluetoothMapService.VERBOSE;
- private Context mContext;
- private ContentResolver mResolver;
- private BluetoothMnsObexClient mMnsClient;
- private int mMasId;
+ protected Context mContext;
+ protected ContentResolver mResolver;
+ protected BluetoothMnsObexClient mMnsClient;
+ protected int mMasId;
public static final int DELETED_THREAD_ID = -1;
@@ -164,7 +167,7 @@ public class BluetoothMapContentObserver {
"outbox",
};
- private class Event {
+ public class Event {
String eventType;
long handle;
String folder;
@@ -227,7 +230,7 @@ public class BluetoothMapContentObserver {
}
}
- private class Msg {
+ public class Msg {
long id;
int type;
@@ -243,6 +246,87 @@ public class BluetoothMapContentObserver {
private Map<Long, Msg> mMsgListMms =
Collections.synchronizedMap(new HashMap<Long, Msg>());
+ /*
+ * Class to hold message handle for MCE Initiated operation
+ */
+ public class BluetoothMnsMsgHndlMceInitOp {
+ public String msgHandle;
+ Time time;
+ }
+
+ /*
+ * Keep track of Message Handles on which the operation was
+ * initiated by MCE
+ */
+ List<BluetoothMnsMsgHndlMceInitOp> opList = new ArrayList<BluetoothMnsMsgHndlMceInitOp>();
+
+ /*
+ * Adds the Message Handle to the list for tracking
+ * MCE initiated operation
+ */
+ public void addMceInitiatedOperation(String msgHandle) {
+ if (V) Log.v(TAG, "addMceInitiatedOperation for handle " + msgHandle);
+ BluetoothMnsMsgHndlMceInitOp op = new BluetoothMnsMsgHndlMceInitOp();
+ op.msgHandle = msgHandle;
+ op.time = new Time();
+ op.time.setToNow();
+ opList.add(op);
+ }
+ /*
+ * Removes the Message Handle from the list for tracking
+ * MCE initiated operation
+ */
+ public void removeMceInitiatedOperation(int location) {
+ if (V) Log.v(TAG, "removeMceInitiatedOperation for location " + location);
+ opList.remove(location);
+ }
+
+ /*
+ * Finds the location in the list of the given msgHandle, if
+ * available. "+" indicates the next (any) operation
+ */
+ public int findLocationMceInitiatedOperation( String msgHandle) {
+ int location = -1;
+
+ Time currentTime = new Time();
+ currentTime.setToNow();
+
+ if (V) Log.v(TAG, "findLocationMceInitiatedOperation " + msgHandle);
+
+ List<BluetoothMnsMsgHndlMceInitOp> staleOpList = new ArrayList<BluetoothMnsMsgHndlMceInitOp>();
+ for (BluetoothMnsMsgHndlMceInitOp op: opList) {
+ if (currentTime.toMillis(false) - op.time.toMillis(false) > 10000) {
+ // add stale entries
+ staleOpList.add(op);
+ }
+ }
+ if (!staleOpList.isEmpty()) {
+ for (BluetoothMnsMsgHndlMceInitOp op: staleOpList) {
+ // Remove stale entries
+ opList.remove(op);
+ }
+ }
+
+ for (BluetoothMnsMsgHndlMceInitOp op: opList) {
+ if (op.msgHandle.equalsIgnoreCase(msgHandle)){
+ location = opList.indexOf(op);
+ break;
+ }
+ }
+
+ if (location == -1) {
+ for (BluetoothMnsMsgHndlMceInitOp op: opList) {
+ if (op.msgHandle.equalsIgnoreCase("+")) {
+ location = opList.indexOf(op);
+ break;
+ }
+ }
+ }
+ if (V) Log.v(TAG, "findLocationMce loc" + location);
+ return location;
+ }
+
+
public void registerObserver(BluetoothMnsObexClient mns, int masId) {
if (V) Log.d(TAG, "registerObserver");
/* Use MmsSms Uri since the Sms Uri is not notified on deletes */
@@ -258,19 +342,27 @@ public class BluetoothMapContentObserver {
mMnsClient = null;
}
- private void sendEvent(Event evt) {
+ public void sendEvent(Event evt) {
Log.d(TAG, "sendEvent: " + evt.eventType + " " + evt.handle + " "
+ evt.folder + " " + evt.oldFolder + " " + evt.msgType.name());
+ int location = -1;
if (mMnsClient == null || mMnsClient.isConnected() == false) {
Log.d(TAG, "sendEvent: No MNS client registered or connected- don't send event");
return;
}
-
- try {
- mMnsClient.sendEvent(evt.encode(), mMasId);
- } catch (UnsupportedEncodingException ex) {
- /* do nothing */
+ String msgHandle = BluetoothMapUtils.getMapHandle(evt.handle,evt.msgType);
+ Log.d(TAG, "msgHandle is "+msgHandle);
+ location = findLocationMceInitiatedOperation(Long.toString(evt.handle));
+ Log.d(TAG, "location is "+location);
+ if(location == -1) {
+ try {
+ mMnsClient.sendEvent(evt.encode(), mMasId);
+ } catch (UnsupportedEncodingException ex) {
+ Log.w(TAG, ex);
+ }
+ } else {
+ removeMceInitiatedOperation(location);
}
}
@@ -609,7 +701,7 @@ public class BluetoothMapContentObserver {
return res;
}
- private class PushMsgInfo {
+ protected class PushMsgInfo {
long id;
int transparent;
int retry;
@@ -653,7 +745,6 @@ public class BluetoothMapContentObserver {
if(recipient.getEnvLevel() == 0) // Only send the message to the top level recipient
{
/* Only send to first address */
- String phone = recipient.getFirstPhoneNumber();
boolean read = false;
boolean deliveryReport = true;
@@ -662,17 +753,17 @@ public class BluetoothMapContentObserver {
{
/* Send message if folder is outbox */
/* to do, support MMS in the future */
- /*
+ /*String phone = recipient.getFirstPhoneNumber();
if (folder.equals("outbox")) {
handle = sendMmsMessage(folder, phone, (BluetoothMapbMessageMmsEmail)msg);
- }
- */
+ }*/
break;
}
case SMS_GSM: //fall-through
case SMS_CDMA:
{
/* Add the message to the database */
+ String phone = recipient.getFirstPhoneNumber();
String msgBody = ((BluetoothMapbMessageSms) msg).getSmsBody();
Uri contentUri = Uri.parse("content://sms/" + folder);
Uri uri = Sms.addMessageToUri(mResolver, contentUri, phone, msgBody,
@@ -696,6 +787,13 @@ public class BluetoothMapContentObserver {
}
case EMAIL:
{
+ /* Send message if folder is outbox */
+ /* to do, support EMAIL in the future */
+ Log.d(TAG, "AccountId " + BluetoothMapUtils.getEmailAccountId(mContext));
+ if(folder.equalsIgnoreCase("draft"))
+ folder="Drafts";
+ handle = sendEmailMessage(folder, recipient.getEmailAddresses(),
+ (BluetoothMapbMessageMmsEmail)msg);
break;
}
}
@@ -708,6 +806,10 @@ public class BluetoothMapContentObserver {
}
+ public long sendEmailMessage(String folder,String[] toList, BluetoothMapbMessageMmsEmail msg) {
+ return -1;
+ }
+
public long sendMmsMessage(String folder,String to_address, BluetoothMapbMessageMmsEmail msg) {
/*
diff --git a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
index 6d29a1d86..e7e4b3409 100644
--- a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
@@ -30,7 +30,7 @@ import android.util.Xml;
public class BluetoothMapFolderElement {
private String name;
private BluetoothMapFolderElement parent = null;
- private ArrayList<BluetoothMapFolderElement> subFolders;
+ protected ArrayList<BluetoothMapFolderElement> subFolders;
public BluetoothMapFolderElement( String name, BluetoothMapFolderElement parrent ){
this.name = name;
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
index 1b80b52f9..3f83a8bc9 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
@@ -36,6 +36,7 @@ public class BluetoothMapMessageListing {
}
public void add(BluetoothMapMessageListingElement element) {
list.add(element);
+ Log.d(TAG, "list size is " +list.size());
/* update info regarding whether the list contains unread messages */
if (element.getRead().equalsIgnoreCase("no"))
{
@@ -50,6 +51,7 @@ public class BluetoothMapMessageListing {
public int getCount() {
if(list != null)
{
+ Log.d(TAG, "returning " + list.size());
return list.size();
}
Log.e(TAG, "list is null returning 0");
@@ -85,18 +87,24 @@ public class BluetoothMapMessageListing {
// Do the XML encoding of list
if(list != null) {
for (BluetoothMapMessageListingElement element : list) {
- element.encode(xmlMsgElement); // Append the list element
+ try {
+ element.encode(xmlMsgElement); // Append the list element
+ } catch (IllegalArgumentException e) {
+ xmlMsgElement.endTag("", "msg");
+ Log.w(TAG, e.toString());
+ } catch (IllegalStateException e) {
+ Log.w(TAG, e.toString());
+ } catch (IOException e) {
+ Log.w(TAG, e.toString());
+ }
}
}
xmlMsgElement.endTag("", "MAP-msg-listing");
xmlMsgElement.endDocument();
- } catch (IllegalArgumentException e) {
- Log.w(TAG, e.toString());
- } catch (IllegalStateException e) {
- Log.w(TAG, e.toString());
} catch (IOException e) {
Log.w(TAG, e.toString());
}
+ Log.d(TAG, "Exiting encode ");
return sw.toString().getBytes("UTF-8");
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
index c8672db1f..a9699b340 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
@@ -97,6 +97,10 @@ public class BluetoothMapMessageListingElement
return senderAddressing;
}
+ public void setEmailSenderAddressing(String senderAddressing) {
+ this.senderAddressing = senderAddressing;
+ }
+
public void setSenderAddressing(String senderAddressing) {
/* TODO: This should depend on the type - for email, the addressing is an email address
* Consider removing this again - to allow strings.
@@ -218,7 +222,7 @@ public class BluetoothMapMessageListingElement
* */
public void encode(XmlSerializer xmlMsgElement) throws IllegalArgumentException, IllegalStateException, IOException
{
-
+ Log.d(TAG, "Inside encode");
// contruct the XML tag for a single msg in the msglisting
xmlMsgElement.startTag("", "msg");
xmlMsgElement.attribute("", "handle", mapHandle);
@@ -255,6 +259,7 @@ public class BluetoothMapMessageListingElement
if(protect != null)
xmlMsgElement.attribute("", "protect", protect);
xmlMsgElement.endTag("", "msg");
+ Log.d(TAG, "Exiting encode");
}
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
index 3751fa42a..983456c13 100644
--- a/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -34,6 +34,9 @@ import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
+import java.util.List;
+import java.util.ArrayList;
+
public class BluetoothMapObexServer extends ServerRequestHandler {
@@ -70,21 +73,55 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
private PowerManager.WakeLock mWakeLock = null;
+ private int mMasId;
+ // Types of mailboxes. From EmailContent.java
+ // inbox
+ public static final int TYPE_INBOX = 0;
+ // draft
+ public static final int TYPE_DRAFT = 3;
+ // outbox
+ public static final int TYPE_OUTBOX = 4;
+ // sent
+ public static final int TYPE_SENT = 5;
+ // deleted
+ public static final int TYPE_DELETED = 6;
+
+ public static final String INBOX = "inbox";
+ public static final String OUTBOX = "outbox";
+ public static final String SENT = "sent";
+ public static final String DELETED = "deleted";
+ public static final String DRAFT = "draft";
+ public static final String DRAFTS = "drafts";
+ public static final String UNDELIVERED = "undelivered";
+ public static final String FAILED = "failed";
+ public static final String QUEUED = "queued";
+
+ private static final int[] SPECIAL_MAILBOX_TYPES
+ = {TYPE_INBOX, TYPE_DRAFT, TYPE_OUTBOX, TYPE_SENT, TYPE_DELETED};
+ private static final String[] SPECIAL_MAILBOX_MAP_NAME
+ = {INBOX, DRAFT, OUTBOX, SENT, DELETED};
+
public static boolean sIsAborted = false;
BluetoothMapContent mOutContent;
public BluetoothMapObexServer(Handler callback, Context context,
- BluetoothMnsObexClient mns) {
+ BluetoothMnsObexClient mns, int masId) {
super();
mCallback = callback;
mContext = context;
- mOutContent = new BluetoothMapContent(mContext);
+ mMasId = masId;
mMnsClient = mns;
- buildFolderStructure(); /* Build the default folder structure, and set
+ mOutContent = new BluetoothMapContent(mContext);
+ if(mMasId == 0) {
+ buildFolderStructure();
+ } else {
+ buildFolderStructureEmail(); /* Build the default folder structure, and set
mCurrentFolder to root folder */
+ }
}
+
/**
* Build the default minimal folder structure, as defined in the MAP specification.
*/
@@ -100,6 +137,17 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
tmpFolder.addFolder("draft");
}
+
+ /**
+ * Build the default minimal folder structure, as defined in the MAP specification.
+ */
+ private void buildFolderStructureEmail(){
+ mCurrentFolder = new BluetoothMapFolderElement("root", null); // This will be the root element
+ BluetoothMapFolderElement tmpFolder;
+ tmpFolder = mCurrentFolder.addFolder("telecom"); // root/telecom
+ tmpFolder = tmpFolder.addFolder("msg"); // root/telecom/msg
+ }
+
@Override
public int onConnect(final HeaderSet request, HeaderSet reply) {
if (D) Log.d(TAG, "onConnect():");
@@ -130,6 +178,10 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
}
}
+ if (mMasId == 1) {
+ if (BluetoothMapUtils.getEmailAccountId(mContext) == -1)
+ return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+ }
reply.setHeader(HeaderSet.WHO, uuid);
} catch (IOException e) {
Log.e(TAG, e.toString());
@@ -220,7 +272,12 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
if(V) {
Log.d(TAG,"TYPE_MESSAGE_UPDATE:");
}
+ if(mMasId == 1) {
+ mOutContent.msgUpdate();
+ return ResponseCodes.OBEX_HTTP_OK;
+ } else {
return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
+ }
}else if(type.equals(TYPE_SET_NOTIFICATION_REGISTRATION)) {
if(V) {
Log.d(TAG,"TYPE_SET_NOTIFICATION_REGISTRATION: NotificationStatus: " + appParams.getNotificationStatus());
@@ -249,7 +306,7 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
if(mns != null) {
Message msg = Message.obtain(mns);
msg.what = BluetoothMnsObexClient.MSG_MNS_NOTIFICATION_REGISTRATION;
- msg.arg1 = 0; // TODO: Add correct MAS ID, as specified in the SDP record.
+ msg.arg1 = mMasId;
msg.arg2 = appParams.getNotificationStatus();
msg.sendToTarget();
if(D) Log.d(TAG,"MSG_MNS_NOTIFICATION_REGISTRATION");
@@ -268,7 +325,8 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
if(folderName == null || folderName.equals("")) {
folderName = mCurrentFolder.getName();
}
- if(!folderName.equals("outbox") && !folderName.equals("draft")) {
+ if(!folderName.equalsIgnoreCase("outbox") && !folderName.equalsIgnoreCase("draft") &&
+ !folderName.equalsIgnoreCase("drafts")) {
if(D) Log.d(TAG, "Push message only allowed to outbox and draft. folderName: " + folderName);
return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
}
@@ -281,7 +339,7 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
bMsgStream = op.openInputStream();
message = BluetoothMapbMessage.parse(bMsgStream, appParams.getCharset()); // Decode the messageBody
// Send message
- BluetoothMapContentObserver observer = mMnsClient.getContentObserver();
+ BluetoothMapContentObserver observer = mMnsClient.getContentObserver(mMasId);
if (observer == null) {
return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
}
@@ -320,7 +378,7 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
msgHandle == null) {
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
}
- BluetoothMapContentObserver observer = mMnsClient.getContentObserver();
+ BluetoothMapContentObserver observer = mMnsClient.getContentObserver(mMasId);
if (observer == null) {
return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
}
@@ -386,9 +444,13 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
folder = mCurrentFolder.getSubFolder(folderName);
if(folder != null)
mCurrentFolder = folder;
- else
+ else {
+ Log.e(TAG, " folder name " +folderName +"not found");
+ Log.e(TAG, " Change folder failed");
+ Log.e(TAG, " Do message listing before changing the folder");
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
+ }
if (V) Log.d(TAG, "Current Folder: " + mCurrentFolder.getName());
return ResponseCodes.OBEX_HTTP_OK;
}
@@ -400,6 +462,7 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
if (mCallback != null) {
Message msg = Message.obtain(mCallback);
msg.what = BluetoothMapService.MSG_SERVERSESSION_CLOSE;
+ msg.arg1 = mMasId;
msg.sendToTarget();
if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
}
@@ -502,11 +565,15 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
BluetoothMapMessageListing outList;
if(folderName == null) {
folderName = mCurrentFolder.getName();
+ } else if(folderName.equalsIgnoreCase("draft") && mMasId ==1) {
+ folderName="Drafts";
}
- Log.d(TAG, "sendMessageListingRsp for folder " +folderName);
- if(mCurrentFolder.getSubFolder(folderName) == null) {
- Log.d(TAG, "Proper Path not set. returning from here");
- return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ if(D) Log.d(TAG, "folderName is "+folderName);
+ if(D) Log.d(TAG, "mCurrentFolder is "+mCurrentFolder.getName());
+ if(mCurrentFolder.getSubFolder(folderName) == null &&
+ !(mCurrentFolder.getName().equalsIgnoreCase(folderName))) {
+ Log.d(TAG, "Path not set. returning from here");
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
if(appParams == null){
appParams = new BluetoothMapAppParams();
@@ -526,15 +593,23 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
appParams.setStartOffset(0);
if(appParams.getMaxListCount() != 0) {
- outList = mOutContent.msgListing(folderName, appParams);
- // Generate the byte stream
- outAppParams.setMessageListingSize(outList.getCount());
- outBytes = outList.encode();
- hasUnread = outList.hasUnread();
+ if(mMasId == 0)
+ outList = mOutContent.msgListing(folderName, appParams);
+ else
+ outList = mOutContent.msgListingEmail(folderName, appParams);
+ // Generate the byte stream
+ outAppParams.setMessageListingSize(outList.getCount());
+ outBytes = outList.encode();
+ hasUnread = outList.hasUnread();
}
else {
- listSize = mOutContent.msgListingSize(folderName, appParams);
- hasUnread = mOutContent.msgListingHasUnread(folderName, appParams);
+ if(mMasId == 0){
+ listSize = mOutContent.msgListingSize(folderName, appParams);
+ hasUnread = mOutContent.msgListingHasUnread(folderName, appParams);
+ } else {
+ listSize = mOutContent.msgListingSizeEmail(folderName, appParams);
+ hasUnread = mOutContent.msgListingHasUnreadEmail(folderName, appParams);
+ }
outAppParams.setMessageListingSize(listSize);
op.noBodyHeader();
}
@@ -609,9 +684,13 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
private int sendFolderListingRsp(Operation op, BluetoothMapAppParams appParams){
OutputStream outStream = null;
byte[] outBytes = null;
+ List<String> list;
+ ArrayList<String> finalList = new ArrayList<String>();
+ int type;
BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
int maxChunkSize, bytesWritten = 0;
HeaderSet replyHeaders = new HeaderSet();
+ int curType;
int bytesToWrite, maxListCount, listStartOffset;
if(appParams == null){
appParams = new BluetoothMapAppParams();
@@ -630,6 +709,40 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
if(maxListCount == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
maxListCount = 1024;
+ Log.v(TAG,"mMasId is " + mMasId);
+ if(mMasId == 1) {
+ long id = BluetoothMapUtils.getEmailAccountId(mContext);
+ list = mOutContent.getEmailFolderListAtPath(mContext,id,mCurrentFolder.getName());
+ if(mCurrentFolder.getName().equals("telecom") || mCurrentFolder.getName().equals("msg")) {
+ if(V) Log.v(TAG, "Doing no processing");
+ for (String str : list) {
+ finalList.add(str);
+ }
+ } else {
+ if (V) Log.v(TAG, "processing for special folders");
+ for (String str : list) {
+ String folderStr = str.substring(mCurrentFolder.getName().length()+ 1);
+ if(V) Log.v(TAG, "folderStr is " +folderStr);
+ String folder[] = folderStr.split("/");
+ if(folder.length == 1){
+ if (V) Log.v(TAG, " Add Folder:" + folder[0]);
+ finalList.add(folder[0]);
+ }
+ }
+ }
+ for (int i = 0; i < mCurrentFolder.subFolders.size(); i ++) {
+ if(finalList.contains(mCurrentFolder.subFolders.get(i).getName())) {
+ if (V) Log.v(TAG, " listing already contains, hence removing folder : "
+ + mCurrentFolder.subFolders.get(i).getName());
+ finalList.remove(mCurrentFolder.subFolders.get(i).getName());
+ }
+ }
+ if (V) Log.v(TAG, "final list");
+ for (String str : finalList) {
+ if (V) Log.v(TAG, "" + str);
+ mCurrentFolder.addFolder(str);
+ }
+ }
if(maxListCount != 0)
{
@@ -699,6 +812,7 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
byte[] outBytes;
int maxChunkSize, bytesToWrite, bytesWritten = 0;
long msgHandle;
+ if (V) Log.v(TAG, "sendGetMessageRsp for handle " + handle);
try {
outBytes = mOutContent.getMessage(handle, appParams);
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
index 3344f175e..8f2478e80 100644
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -64,9 +64,7 @@ public class BluetoothMapService extends ProfileService {
* "setprop log.tag.BluetoothMapService DEBUG"; enable both VERBOSE and
* DEBUG log: "setprop log.tag.BluetoothMapService VERBOSE"
*/
-
public static final boolean DEBUG = true;
-
public static final boolean VERBOSE = true;
/**
@@ -109,14 +107,12 @@ public class BluetoothMapService extends ProfileService {
private static final int DISCONNECT_MAP = 3;
- private BluetoothAdapter mAdapter;
+ private PowerManager.WakeLock mWakeLock = null;
- private SocketAcceptThread mAcceptThread = null;
+ private BluetoothAdapter mAdapter;
private BluetoothMapAuthenticator mAuth = null;
- private BluetoothMapObexServer mMapServer;
-
private ServerSession mServerSession = null;
private BluetoothMnsObexClient mBluetoothMnsObexClient = null;
@@ -146,256 +142,27 @@ public class BluetoothMapService extends ProfileService {
BluetoothUuid.MNS,
};
- public BluetoothMapService() {
- mState = BluetoothMap.STATE_DISCONNECTED;
- }
+ public static final int MESSAGE_TYPE_EMAIL = 1 << 0;
+ public static final int MESSAGE_TYPE_SMS_GSM = 1 << 1;
+ public static final int MESSAGE_TYPE_SMS_CDMA = 1 << 2;
+ public static final int MESSAGE_TYPE_MMS = 1 << 3;
+ public static final int MESSAGE_TYPE_SMS = MESSAGE_TYPE_SMS_GSM | MESSAGE_TYPE_SMS_CDMA;
+ public static final int MESSAGE_TYPE_SMS_MMS = MESSAGE_TYPE_SMS | MESSAGE_TYPE_MMS;
- private void startRfcommSocketListener() {
- if (DEBUG) Log.d(TAG, "Map Service startRfcommSocketListener");
+ private boolean mIsEmailEnabled = true;
+ public static final int MAX_INSTANCES = 2;
+ BluetoothMapObexConnectionManager mConnectionManager = null;
+ public static final int MAS_INS_INFO[] = {MESSAGE_TYPE_SMS_MMS, MESSAGE_TYPE_EMAIL};
- if (mAcceptThread == null) {
- mAcceptThread = new SocketAcceptThread();
- mAcceptThread.setName("BluetoothMapAcceptThread");
- mAcceptThread.start();
- }
- }
-
- private final boolean initSocket() {
- if (DEBUG) Log.d(TAG, "Map Service initSocket");
-
- boolean initSocketOK = false;
- final int CREATE_RETRY_TIME = 10;
-
- // It's possible that create will fail in some cases. retry for 10 times
- for (int i = 0; (i < CREATE_RETRY_TIME) && !mInterrupted; i++) {
- initSocketOK = true;
- try {
- // It is mandatory for MSE to support initiation of bonding and
- // encryption.
- mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord
- ("MAP SMS/MMS", BluetoothUuid.MAS.getUuid());
-
- } catch (IOException e) {
- Log.e(TAG, "Error create RfcommServerSocket " + e.toString());
- initSocketOK = false;
- }
- if (!initSocketOK) {
- // Need to break out of this loop if BT is being turned off.
- if (mAdapter == null) break;
- int state = mAdapter.getState();
- if ((state != BluetoothAdapter.STATE_TURNING_ON) &&
- (state != BluetoothAdapter.STATE_ON)) {
- Log.w(TAG, "initServerSocket failed as BT is (being) turned off");
- break;
- }
- try {
- if (VERBOSE) Log.v(TAG, "wait 300 ms");
- Thread.sleep(300);
- } catch (InterruptedException e) {
- Log.e(TAG, "socketAcceptThread thread was interrupted (3)");
- }
- } else {
- break;
- }
- }
- if (mInterrupted) {
- initSocketOK = false;
- // close server socket to avoid resource leakage
- closeServerSocket();
- }
-
- if (initSocketOK) {
- if (VERBOSE) Log.v(TAG, "Succeed to create listening socket ");
-
- } else {
- Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
- }
- return initSocketOK;
- }
-
- private final synchronized void closeServerSocket() {
- // exit SocketAcceptThread early
- if (mServerSocket != null) {
- try {
- // this will cause mServerSocket.accept() return early with IOException
- mServerSocket.close();
- mServerSocket = null;
- } catch (IOException ex) {
- Log.e(TAG, "Close Server Socket error: " + ex);
- }
- }
- }
- private final synchronized void closeConnectionSocket() {
- if (mConnSocket != null) {
- try {
- mConnSocket.close();
- mConnSocket = null;
- } catch (IOException e) {
- Log.e(TAG, "Close Connection Socket error: " + e.toString());
- }
- }
+ public BluetoothMapService() {
+ mState = BluetoothMap.STATE_DISCONNECTED;
+ mConnectionManager = new BluetoothMapObexConnectionManager();
+ if (VERBOSE)
+ Log.v(TAG, "BluetoothMapService: mIsEmailEnabled: " + mIsEmailEnabled);
}
-
private final void closeService() {
- if (DEBUG) Log.d(TAG, "MAP Service closeService in");
-
- // exit initSocket early
- mInterrupted = true;
- closeServerSocket();
-
- if (mAcceptThread != null) {
- try {
- mAcceptThread.shutdown();
- mAcceptThread.join();
- mAcceptThread = null;
- } catch (InterruptedException ex) {
- Log.w(TAG, "mAcceptThread close error" + ex);
- }
- }
-
- if (mServerSession != null) {
- mServerSession.close();
- mServerSession = null;
- }
-
- if (mBluetoothMnsObexClient != null) {
- mBluetoothMnsObexClient.shutdown();
- mBluetoothMnsObexClient = null;
- }
-
- closeConnectionSocket();
-
- if (mSessionStatusHandler != null) {
- mSessionStatusHandler.removeCallbacksAndMessages(null);
- }
- isWaitingAuthorization = false;
-
- if (VERBOSE) Log.v(TAG, "MAP Service closeService out");
- }
-
- private final void startObexServerSession() throws IOException {
- if (DEBUG) Log.d(TAG, "Map Service startObexServerSession");
-
- mBluetoothMnsObexClient = new BluetoothMnsObexClient(this, mRemoteDevice);
- mMapServer = new BluetoothMapObexServer(mSessionStatusHandler, this,
- mBluetoothMnsObexClient);
- synchronized (this) {
- // We need to get authentication now that obex server is up
- mAuth = new BluetoothMapAuthenticator(mSessionStatusHandler);
- mAuth.setChallenged(false);
- mAuth.setCancelled(false);
- }
- // setup RFCOMM transport
- BluetoothMapRfcommTransport transport = new BluetoothMapRfcommTransport(mConnSocket);
- mServerSession = new ServerSession(transport, mMapServer, mAuth);
- setState(BluetoothMap.STATE_CONNECTED);
- if (VERBOSE) {
- Log.v(TAG, "startObexServerSession() success!");
- }
- }
-
- private void stopObexServerSession() {
- if (DEBUG) Log.d(TAG, "MAP Service stopObexServerSession");
-
- if (mServerSession != null) {
- mServerSession.close();
- mServerSession = null;
- }
-
- mAcceptThread = null;
-
- if(mBluetoothMnsObexClient != null) {
- mBluetoothMnsObexClient.shutdown();
- mBluetoothMnsObexClient = null;
- }
- closeConnectionSocket();
-
- // Last obex transaction is finished, we start to listen for incoming
- // connection again
- if (mAdapter.isEnabled()) {
- startRfcommSocketListener();
- }
- setState(BluetoothMap.STATE_DISCONNECTED);
- }
-
-
-
- /**
- * A thread that runs in the background waiting for remote rfcomm
- * connect.Once a remote socket connected, this thread shall be
- * shutdown.When the remote disconnect,this thread shall run again waiting
- * for next request.
- */
- private class SocketAcceptThread extends Thread {
-
- private boolean stopped = false;
-
- @Override
- public void run() {
- BluetoothServerSocket serverSocket;
- if (mServerSocket == null) {
- if (!initSocket()) {
- return;
- }
- }
-
- while (!stopped) {
- try {
- if (DEBUG) Log.d(TAG, "Accepting socket connection...");
- serverSocket = mServerSocket;
- if(serverSocket == null) {
- Log.w(TAG, "mServerSocket is null");
- break;
- }
- mConnSocket = serverSocket.accept();
- if (DEBUG) Log.d(TAG, "Accepted socket connection...");
- synchronized (BluetoothMapService.this) {
- if (mConnSocket == null) {
- Log.w(TAG, "mConnSocket is null");
- break;
- }
- mRemoteDevice = mConnSocket.getRemoteDevice();
- }
- if (mRemoteDevice == null) {
- Log.i(TAG, "getRemoteDevice() = null");
- break;
- }
-
- sRemoteDeviceName = mRemoteDevice.getName();
- // In case getRemoteName failed and return null
- if (TextUtils.isEmpty(sRemoteDeviceName)) {
- sRemoteDeviceName = getString(R.string.defaultname);
- }
-
- Intent intent = new
- Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
- intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
- intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
- BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
- isWaitingAuthorization = true;
- sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
-
- if (DEBUG) Log.d(TAG, "waiting for authorization for connection from: "
- + sRemoteDeviceName);
- //Queue USER_TIMEOUT to disconnect MAP OBEX session. If user doesn't
- //accept or reject authorization request
- removeTimeoutMsg = true;
- mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
- .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
-
- stopped = true; // job done ,close this thread;
- } catch (IOException ex) {
- stopped=true;
- if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
- }
- }
- }
-
- void shutdown() {
- stopped = true;
- interrupt();
- }
+ if (VERBOSE) Log.v(TAG, "closeService");
+ mConnectionManager.closeAll();
}
private final Handler mSessionStatusHandler = new Handler() {
@@ -406,7 +173,7 @@ public class BluetoothMapService extends ProfileService {
switch (msg.what) {
case START_LISTENER:
if (mAdapter.isEnabled()) {
- startRfcommSocketListener();
+ mConnectionManager.startAll();
}
break;
case USER_TIMEOUT:
@@ -416,11 +183,11 @@ public class BluetoothMapService extends ProfileService {
BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
sendBroadcast(intent, BLUETOOTH_PERM);
isWaitingAuthorization = false;
- removeTimeoutMsg = false;
- stopObexServerSession();
+ mConnectionManager.stopObexServerSessionWaiting();
break;
case MSG_SERVERSESSION_CLOSE:
- stopObexServerSession();
+ final int masId = msg.arg1;
+ mConnectionManager.stopObexServerSession(masId);
break;
case MSG_SESSION_ESTABLISHED:
break;
@@ -482,16 +249,7 @@ public class BluetoothMapService extends ProfileService {
if (getRemoteDevice().equals(device)) {
switch (mState) {
case BluetoothMap.STATE_CONNECTED:
- if (mServerSession != null) {
- mServerSession.close();
- mServerSession = null;
- }
- if(mBluetoothMnsObexClient != null) {
- mBluetoothMnsObexClient.shutdown();
- mBluetoothMnsObexClient = null;
- }
- closeConnectionSocket();
-
+ closeService();
setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
result = true;
break;
@@ -560,6 +318,7 @@ public class BluetoothMapService extends ProfileService {
@Override
protected IProfileServiceBinder initBinder() {
+ Log.d(TAG, "Inside initBinder");
return new BluetoothMapBinder(this);
}
@@ -575,6 +334,7 @@ public class BluetoothMapService extends ProfileService {
Log.w(TAG,"Unable to register map receiver",e);
}
mInterrupted = false;
+ mConnectionManager.init();
mAdapter = BluetoothAdapter.getDefaultAdapter();
// start RFCOMM listener
mSessionStatusHandler.sendMessage(mSessionStatusHandler
@@ -603,6 +363,480 @@ public class BluetoothMapService extends ProfileService {
return true;
}
+
+ class BluetoothMapObexConnectionManager {
+ private ArrayList<BluetoothMapObexConnection> mConnections =
+ new ArrayList<BluetoothMapObexConnection>();
+
+ public BluetoothMapObexConnectionManager() {
+ int numberOfSupportedInstances = MAX_INSTANCES;
+ if(VERBOSE)
+ Log.v(TAG, "BluetoothMapObexConnectionManager: mIsEmailEnabled: " + mIsEmailEnabled);
+ if(!mIsEmailEnabled) {
+ numberOfSupportedInstances = 1; /*Email instance not supported*/
+ }
+ for (int i = 0; i < numberOfSupportedInstances; i ++) {
+ mConnections.add(new BluetoothMapObexConnection(
+ MAS_INS_INFO[i], i ));
+ }
+ }
+
+ public void initiateObexServerSession(BluetoothDevice device) {
+ try {Log.d(TAG, "inside initiateObexServerSession");
+ for (BluetoothMapObexConnection connection : mConnections) {
+ if (connection.mConnSocket != null && connection.mWaitingForConfirmation) {
+ connection.mWaitingForConfirmation = false;
+ Log.d(TAG, "calling startobexServerSession for masid "+connection.mMasId);
+ connection.startObexServerSession(device);
+ }
+ }
+ } catch (IOException ex) {
+ Log.e(TAG, "Caught the error: " + ex.toString());
+ }
+ }
+
+ public void setWaitingForConfirmation(int masId) {
+ Log.d(TAG, "Inside setWaitingForConfirmation");
+ if (masId < mConnections.size()) {
+ final BluetoothMapObexConnection connect = mConnections.get(masId);
+ connect.mWaitingForConfirmation = true;
+ } else {
+ Log.e(TAG, "Attempt to set waiting for user confirmation for MAS id: " + masId);
+ Log.e(TAG, "out of index");
+ }
+ }
+
+ public void stopObexServerSession(int masId) {
+ int serverSessionConnected = 0;
+ for (BluetoothMapObexConnection connection : mConnections) {
+ if (connection.mConnSocket != null) {
+ serverSessionConnected++;
+ }
+ }
+ // stop mnsclient session if only one session was connected.
+ if(serverSessionConnected == 1) {
+ if (mBluetoothMnsObexClient != null) {
+ mBluetoothMnsObexClient.shutdown();
+ mBluetoothMnsObexClient = null;
+ }
+ }
+
+ if (masId < mConnections.size()) {
+ final BluetoothMapObexConnection connect = mConnections.get(masId);
+ if (connect.mConnSocket != null) {
+ connect.stopObexServerSession();
+ } else {
+ Log.w(TAG, "Attempt to stop OBEX Server session for MAS id: " + masId);
+ Log.w(TAG, "when there is no connected socket");
+ }
+ } else {
+ Log.e(TAG, "Attempt to stop OBEX Server session for MAS id: " + masId);
+ Log.e(TAG, "out of index");
+ }
+
+ }
+
+ public void stopObexServerSessionWaiting() {
+
+ int serverSessionConnected = 0;
+ for (BluetoothMapObexConnection connection : mConnections) {
+ if (connection.mConnSocket != null) {
+ serverSessionConnected++;
+ }
+ }
+ // stop mnsclient session if only one session was connected.
+ if(serverSessionConnected == 1) {
+ if (mBluetoothMnsObexClient != null) {
+ mBluetoothMnsObexClient.shutdown();
+ mBluetoothMnsObexClient = null;
+ }
+ }
+
+ for (BluetoothMapObexConnection connection : mConnections) {
+ if (connection.mConnSocket != null && connection.mWaitingForConfirmation) {
+ connection.mWaitingForConfirmation = false;
+ connection.stopObexServerSession();
+ }
+ }
+ }
+
+ public void stopObexServerSessionAll() {
+ if (mBluetoothMnsObexClient != null) {
+ mBluetoothMnsObexClient.shutdown();
+ mBluetoothMnsObexClient = null;
+ }
+ for (BluetoothMapObexConnection connection : mConnections) {
+ if (connection.mConnSocket != null) {
+ connection.stopObexServerSession();
+ }
+ }
+ }
+
+ public void closeAll() {
+ if (mBluetoothMnsObexClient != null) {
+ mBluetoothMnsObexClient.shutdown();
+ mBluetoothMnsObexClient = null;
+ }
+
+ for (BluetoothMapObexConnection connection : mConnections) {
+ connection.mInterrupted = true;
+ connection.closeConnection();
+ }
+ }
+
+ public void startAll() {
+ for (BluetoothMapObexConnection connection : mConnections) {
+ connection.startRfcommSocketListener();
+ }
+ }
+
+ public void init() {
+ for (BluetoothMapObexConnection connection: mConnections) {
+ connection.mInterrupted = false;
+ }
+ }
+
+ public boolean isAllowedConnection(BluetoothDevice remoteDevice) {
+ String remoteAddress = remoteDevice.getAddress();
+ if (remoteAddress == null) {
+ if (VERBOSE) Log.v(TAG, "Connection request from unknown device");
+ return false;
+ }
+ final int size = mConnections.size();
+ for (int i = 0; i < size; i ++) {
+ final BluetoothMapObexConnection connection = mConnections.get(i);
+ BluetoothSocket socket = connection.mConnSocket;
+ if (socket != null) {
+ BluetoothDevice device = socket.getRemoteDevice();
+ if (device != null) {
+ String address = device.getAddress();
+ if (address != null) {
+ if (remoteAddress.equalsIgnoreCase(address)) {
+ if (VERBOSE) {
+ Log.v(TAG, "Connection request from " + remoteAddress);
+ Log.v(TAG, "when MAS id:" + i + " is connected to " + address);
+ }
+ return true;
+ } else {
+ if (VERBOSE) {
+ Log.v(TAG, "Connection request from " + remoteAddress);
+ Log.v(TAG, "when MAS id:" + i + " is connected to " + address);
+ }
+ return false;
+ }
+ } else {
+ // shall not happen, connected device must has address
+ // just for null pointer dereference
+ Log.w(TAG, "Connected device has no address!");
+ }
+ }
+ }
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG, "Connection request from " + remoteAddress);
+ Log.v(TAG, "when no MAS instance is connected.");
+ }
+ return true;
+ }
+ }
+
+ private class BluetoothMapObexConnection {
+ private volatile boolean mInterrupted;
+ private BluetoothMapObexServer mMapServer = null;
+ private BluetoothServerSocket mServerSocket = null;
+ private SocketAcceptThread mAcceptThread = null;
+ private BluetoothSocket mConnSocket = null;
+ private ServerSession mServerSession = null;
+ private int mSupportedMessageTypes;
+ private int mMasId;
+ boolean mWaitingForConfirmation = false;
+
+ public BluetoothMapObexConnection(int supportedMessageTypes, int masId) {
+ Log.d(TAG, "inside BluetoothMapObexConnection");
+ Log.d(TAG, "supportedMessageTypes "+supportedMessageTypes);
+ Log.d(TAG, "masId "+masId);
+ mSupportedMessageTypes = supportedMessageTypes;
+ mMasId = masId;
+ }
+
+ private void startRfcommSocketListener() {
+ if (VERBOSE){
+ Log.v(TAG, "Map Service startRfcommSocketListener");
+ Log.v(TAG, "mMasId is "+mMasId);
+ }
+ if (mServerSocket == null) {
+ if (!initSocket()) {
+ closeConnection();
+ return;
+ }
+ }
+ if (mAcceptThread == null) {
+ mAcceptThread = new SocketAcceptThread(mMasId);
+ mAcceptThread.setName("BluetoothMapAcceptThread " + mMasId);
+ mAcceptThread.start();
+ }
+ }
+
+ private final boolean initSocket() {
+ if (VERBOSE) {
+ Log.v(TAG, "Map Service initSocket");
+ Log.v(TAG, "mMasId is "+mMasId);
+ }
+
+ boolean initSocketOK = false;
+ final int CREATE_RETRY_TIME = 10;
+
+ // It's possible that create will fail in some cases. retry for 10 times
+ for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {
+ try {
+ if(mSupportedMessageTypes == MESSAGE_TYPE_EMAIL)
+ mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord("Email Message Access",BluetoothUuid.MAS.getUuid());
+ else
+ mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord("SMS/MMS Message Access", BluetoothUuid.MAS.getUuid());
+ initSocketOK = true;
+ } catch (IOException e) {
+ Log.e(TAG, "Error create RfcommServerSocket " + e.toString());
+ initSocketOK = false;
+ }
+
+ if (!initSocketOK) {
+ synchronized (this) {
+ try {
+ if (VERBOSE) Log.v(TAG, "wait 3 seconds");
+ Thread.sleep(300);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "socketAcceptThread thread was interrupted (3)");
+ mInterrupted = true;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+
+ if (initSocketOK) {
+ if (VERBOSE)
+ Log.v(TAG, "Succeed to create listening socket for mMasId "
+ + mMasId);
+ } else {
+ Log.e(TAG, "Error to create listening socket after "
+ + CREATE_RETRY_TIME + " try");
+ }
+ return initSocketOK;
+ }
+
+ private final synchronized void closeServerSocket() {
+ // exit SocketAcceptThread early
+ if (mServerSocket != null) {
+ try {
+ // this will cause mServerSocket.accept() return early with IOException
+ mServerSocket.close();
+ mServerSocket = null;
+ } catch (IOException ex) {
+ Log.e(TAG, "Close Server Socket error: " + ex);
+ }
+ }
+ }
+ private final synchronized void closeConnectionSocket() {
+ if (mConnSocket != null) {
+ try {
+ mConnSocket.close();
+ mConnSocket = null;
+ } catch (IOException e) {
+ Log.e(TAG, "Close Connection Socket error: " + e.toString());
+ }
+ }
+ }
+
+ private final void closeConnection() {
+ if (DEBUG) Log.d(TAG, "MAP Service closeService in");
+ // exit initSocket early
+ mInterrupted = true;
+ closeServerSocket();
+ if (mAcceptThread != null) {
+ try {
+ mAcceptThread.shutdown();
+ mAcceptThread.join();
+ mAcceptThread = null;
+ } catch (InterruptedException ex) {
+ Log.w(TAG, "mAcceptThread close error" + ex);
+ }
+ }
+ if (mServerSession != null) {
+ mServerSession.close();
+ mServerSession = null;
+ }
+ closeConnectionSocket();
+ if (mSessionStatusHandler != null) {
+ mSessionStatusHandler.removeCallbacksAndMessages(null);
+ }
+ isWaitingAuthorization = false;
+ if (VERBOSE) Log.v(TAG, "MAP Service closeService out");
+ }
+
+ private final void startObexServerSession(BluetoothDevice device) throws IOException {
+ if (DEBUG) {
+ Log.d(TAG, "Map Service startObexServerSession");
+ Log.d(TAG, "mMasId is "+mMasId);
+ }
+ Context context = getApplicationContext();
+ Log.d(TAG, "after getting application context");
+ // acquire the wakeLock before start Obex transaction thread
+ if (mWakeLock == null) {
+ PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "StartingObexMapTransaction");
+ mWakeLock.setReferenceCounted(false);
+ mWakeLock.acquire();
+ }
+ if(mBluetoothMnsObexClient == null)
+ mBluetoothMnsObexClient = new BluetoothMnsObexClient(context, mRemoteDevice);
+ mBluetoothMnsObexClient.initObserver(mMasId);
+ mMapServer = new BluetoothMapObexServer(mSessionStatusHandler, context,
+ mBluetoothMnsObexClient, mMasId);
+ synchronized (this) {
+ // We need to get authentication now that obex server is up
+ mAuth = new BluetoothMapAuthenticator(mSessionStatusHandler);
+ mAuth.setChallenged(false);
+ mAuth.setCancelled(false);
+ }
+ // setup RFCOMM transport
+ BluetoothMapRfcommTransport transport = new BluetoothMapRfcommTransport(mConnSocket);
+ mServerSession = new ServerSession(transport, mMapServer, mAuth);
+ setState(BluetoothMap.STATE_CONNECTED);
+ if (DEBUG) {
+ Log.d(TAG, "startObexServerSession() success!");
+ Log.d(TAG, "mMasId is "+mMasId);
+ }
+ }
+
+ private void stopObexServerSession() {
+ if (DEBUG) {
+ Log.d(TAG, "Map Service stopObexServerSession ");
+ Log.d(TAG, "mMasId is "+mMasId);
+ }
+
+ if (mAcceptThread != null) {
+ try {
+ mAcceptThread.shutdown();
+ mAcceptThread.join();
+ } catch (InterruptedException ex) {
+ Log.w(TAG, "mAcceptThread close error" + ex);
+ } finally {
+ mAcceptThread = null;
+ }
+ }
+
+ if (mServerSession != null) {
+ mServerSession.close();
+ mServerSession = null;
+ }
+
+ if(mBluetoothMnsObexClient != null)
+ mBluetoothMnsObexClient.deinitObserver(mMasId);
+ closeConnectionSocket();
+
+ // Last obex transaction is finished, we start to listen for incoming
+ // connection again
+ if (mAdapter.isEnabled()) {
+ startRfcommSocketListener();
+ }
+ setState(BluetoothMap.STATE_DISCONNECTED);
+ }
+
+ /**
+ * A thread that runs in the background waiting for remote rfcomm
+ * connect.Once a remote socket connected, this thread shall be
+ * shutdown.When the remote disconnect,this thread shall run again waiting
+ * for next request.
+ */
+ private class SocketAcceptThread extends Thread {
+ private boolean stopped = false;
+ private int mMasId;
+
+ public SocketAcceptThread(int masId) {
+ Log.d(TAG, "inside SocketAcceptThread");
+ mMasId = masId;
+ }
+ @Override
+ public void run() {
+ BluetoothServerSocket serverSocket;
+ if (mServerSocket == null) {
+ if (!initSocket()) {
+ return;
+ }
+ }
+
+ while (!stopped) {
+ try {
+ if (DEBUG) Log.d(TAG, "Accepting socket connection...");
+ serverSocket = mServerSocket;
+ if(serverSocket == null) {
+ Log.w(TAG, "mServerSocket is null");
+ break;
+ }
+ mConnSocket = serverSocket.accept();
+ if (DEBUG) Log.d(TAG, "Accepted socket connection...");
+ synchronized (BluetoothMapService.this) {
+ if (mConnSocket == null) {
+ Log.w(TAG, "mConnSocket is null");
+ break;
+ }
+ mRemoteDevice = mConnSocket.getRemoteDevice();
+ }
+ if (mRemoteDevice == null) {
+ Log.i(TAG, "getRemoteDevice() = null");
+ break;
+ }
+
+ sRemoteDeviceName = mRemoteDevice.getName();
+ // In case getRemoteName failed and return null
+ if (TextUtils.isEmpty(sRemoteDeviceName)) {
+ sRemoteDeviceName = getString(R.string.defaultname);
+ }
+ boolean trust = mRemoteDevice.getTrustState();
+ if (DEBUG) Log.d(TAG, "GetTrustState() = " + trust);
+
+ if (trust) {
+ try {
+ if (DEBUG) Log.d(TAG, "incoming connection accepted from: "
+ + sRemoteDeviceName + " automatically as trusted device");
+ startObexServerSession(mRemoteDevice);
+ } catch (IOException ex) {
+ Log.e(TAG, "catch exception starting obex server session"
+ + ex.toString());
+ }
+ } else {
+ mConnectionManager.setWaitingForConfirmation(mMasId);
+ Intent intent = new
+ Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
+ intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
+ intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
+ BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
+ sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+ isWaitingAuthorization = true;
+
+ if (DEBUG) Log.d(TAG, "waiting for authorization for connection from: "
+ + sRemoteDeviceName);
+
+ }
+ stopped = true; // job done ,close this thread;
+ } catch (IOException ex) {
+ stopped=true;
+ if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
+ }
+ }
+ }
+
+ void shutdown() {
+ stopped = true;
+ interrupt();
+ }
+ }
+ };
+
private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver();
private class MapBroadcastReceiver extends BroadcastReceiver {
@@ -610,6 +844,7 @@ public class BluetoothMapService extends ProfileService {
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.d(TAG, "onReceive");
String action = intent.getAction();
+ if (DEBUG) Log.d(TAG, "onReceive, action "+action);
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR);
@@ -627,7 +862,6 @@ public class BluetoothMapService extends ProfileService {
sendBroadcast(timeoutIntent, BLUETOOTH_PERM);
isWaitingAuthorization = false;
removeTimeoutMsg = false;
- stopObexServerSession();
}
// Release all resources
@@ -659,19 +893,21 @@ public class BluetoothMapService extends ProfileService {
if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
BluetoothDevice.CONNECTION_ACCESS_NO) ==
BluetoothDevice.CONNECTION_ACCESS_YES) {
- //bluetooth connection accepted by user
- try {
- if (mConnSocket != null) {
- // start obex server and rfcomm connection
- startObexServerSession();
- } else {
- stopObexServerSession();
- }
- } catch (IOException ex) {
- Log.e(TAG, "Caught the error: " + ex.toString());
+ //bluetooth connection accepted by user
+ if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
+ boolean result = mRemoteDevice.setTrust(true);
+ if (DEBUG) Log.d(TAG, "setTrust() result=" + result);
+ }
+
+ if(mIsEmailEnabled) {
+ // todo updateEmailAccount();
}
+ if (DEBUG) Log.d(TAG, "calling initiateObexServerSession");
+ mConnectionManager.initiateObexServerSession(mRemoteDevice);
+
} else {
- stopObexServerSession();
+ Log.d(TAG, "calling stopObexServerSessionWaiting");
+ mConnectionManager.stopObexServerSessionWaiting();
}
}
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapUtils.java b/src/com/android/bluetooth/map/BluetoothMapUtils.java
index e57cf16b1..43514dde4 100644
--- a/src/com/android/bluetooth/map/BluetoothMapUtils.java
+++ b/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2013 Samsung System LSI
+* Copyright (C) 2013, The Linux Foundation. All rights reserved.
* 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
@@ -13,7 +14,18 @@
* limitations under the License.
*/
package com.android.bluetooth.map;
-
+import android.util.Log;
+import android.net.Uri;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.*;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.sqlite.SQLiteException;
+import com.android.emailcommon.provider.Mailbox;
/**
* Various utility methods and generic defines that can be used throughout MAPS
@@ -31,6 +43,22 @@ public class BluetoothMapUtils {
private static final long HANDLE_TYPE_EMAIL_MASK = 0x2<<59;
private static final long HANDLE_TYPE_SMS_GSM_MASK = 0x4<<59;
private static final long HANDLE_TYPE_SMS_CDMA_MASK = 0x8<<59;
+ public static final String AUTHORITY = "com.android.email.provider";
+ public static final Uri EMAIL_URI = Uri.parse("content://" + AUTHORITY);
+ public static final Uri EMAIL_ACCOUNT_URI = Uri.withAppendedPath(EMAIL_URI, "account");
+ public static final String RECORD_ID = "_id";
+ public static final String DISPLAY_NAME = "displayName";
+ public static final String EMAIL_ADDRESS = "emailAddress";
+ public static final String ACCOUNT_KEY = "accountKey";
+ public static final String IS_DEFAULT = "isDefault";
+ public static final String EMAIL_TYPE = "type";
+ public static final String[] EMAIL_BOX_PROJECTION = new String[] {
+ RECORD_ID, DISPLAY_NAME, ACCOUNT_KEY, EMAIL_TYPE };
+ private static Context mContext;
+ private static ContentResolver mResolver;
+ private static final String[] ACCOUNT_ID_PROJECTION = new String[] {
+ RECORD_ID, EMAIL_ADDRESS, IS_DEFAULT
+ };
/**
* This enum is used to convert from the bMessage type property to a type safe
@@ -71,6 +99,53 @@ public class BluetoothMapUtils {
return mapHandle;
}
+ public static int getSystemMailboxGuessType(String folderName) {
+
+ if(folderName.equalsIgnoreCase("outbox")){
+ return Mailbox.TYPE_OUTBOX;
+ } else if(folderName.equalsIgnoreCase("inbox")){
+ return Mailbox.TYPE_INBOX;
+ } else if(folderName.equalsIgnoreCase("drafts")){
+ return Mailbox.TYPE_DRAFTS;
+ } else if(folderName.equalsIgnoreCase("Trash")){
+ return Mailbox.TYPE_TRASH;
+ } else if(folderName.equalsIgnoreCase("Sent")){
+ return Mailbox.TYPE_SENT;
+ } else if(folderName.equalsIgnoreCase("Junk")){
+ return Mailbox.TYPE_JUNK;
+ } else if(folderName.equalsIgnoreCase("Sent")){
+ return Mailbox.TYPE_STARRED;
+ } else if(folderName.equalsIgnoreCase("Unread")){
+ return Mailbox.TYPE_UNREAD;
+ }
+ //UNKNOWN
+ return -1;
+ }
+ /**
+ * Get Account id for Default Email app
+ * @return the Account id value
+ */
+ static public long getEmailAccountId(Context context) {
+ if (V) Log.v(TAG, "getEmailAccountIdList()");
+ long id = -1;
+ ArrayList<Long> list = new ArrayList<Long>();
+ Context mContext = context;
+ mResolver = mContext.getContentResolver();
+ try {
+ Cursor cursor = mResolver.query(EMAIL_ACCOUNT_URI,
+ ACCOUNT_ID_PROJECTION, null, null, null);
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ id = cursor.getLong(0);
+ if (V) Log.v(TAG, "id = " + id);
+ }
+ cursor.close();
+ }
+ } catch (SQLiteException e) {
+ Log.e(TAG, "SQLite exception: " + e);
+ }
+ return id;
+ }
/**
* Convert a handle string the the raw long representation, including the type bit.
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessage.java b/src/com/android/bluetooth/map/BluetoothMapbMessage.java
index 35fdba115..a5959185d 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessage.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessage.java
@@ -33,8 +33,8 @@ import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
public abstract class BluetoothMapbMessage {
protected static String TAG = "BluetoothMapbMessage";
- protected static final boolean D = true;
- protected static final boolean V = true;
+ protected static final boolean D = BluetoothMapService.DEBUG;
+ protected static final boolean V = BluetoothMapService.VERBOSE;
private static final String VERSION = "VERSION:1.0";
public static int INVALID_VALUE = -1;
@@ -152,6 +152,12 @@ public abstract class BluetoothMapbMessage {
} else
throw new IllegalArgumentException("No Phone number");
}
+ public String[] getEmailAddresses() {
+ if(emailAddresses.length > 0) {
+ return emailAddresses;
+ } else
+ throw new IllegalArgumentException("No Recipient Email Address");
+ }
public int getEnvLevel() {
return envLevel;
@@ -244,6 +250,36 @@ public abstract class BluetoothMapbMessage {
this.mInStream = is;
}
+ private byte[] getLineTerminatorAsBytes() {
+ int readByte;
+
+ /* Donot skip Empty Line.
+ */
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ try {
+ while ((readByte = mInStream.read()) != -1) {
+ if (readByte == '\r') {
+ if ((readByte = mInStream.read()) != -1 && readByte == '\n') {
+ if(output.size() == 0){
+ Log.v(TAG,"outputsize 0");
+ output.write('\r');
+ output.write('\n');
+ }
+ break;
+ } else {
+ output.write('\r');
+ }
+ }
+ output.write(readByte);
+ }
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ return null;
+ }
+ return output.toByteArray();
+ }
+
private byte[] getLineAsBytes() {
int readByte;
@@ -281,6 +317,22 @@ public abstract class BluetoothMapbMessage {
}
/**
+ * Read a line of text from the BMessage including empty lines.
+ * @return the next line of text, or null at end of file, or if UTF-8 is not supported.
+ */
+ public String getLineTerminator() {
+ try {
+ byte[] line = getLineTerminatorAsBytes();
+ if (line.length == 0)
+ return null;
+ else
+ return new String(line, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ Log.w(TAG, e);
+ return null;
+ }
+ }
+ /**
* 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.
*/
@@ -367,14 +419,17 @@ public abstract class BluetoothMapbMessage {
}
public String getStringTerminator(String terminator) {
- StringBuilder dataStr= new StringBuilder();
- String lineCur = getLine();
- while( lineCur != null && (!lineCur.equals(terminator)))
- {
- dataStr.append(lineCur);
- lineCur = getLine();
- }
- return dataStr.toString();
+ StringBuilder dataStr= new StringBuilder();
+ String lineCur = getLineTerminator();
+ while( lineCur != null && (!lineCur.equals(terminator)))
+ {
+ dataStr.append(lineCur);
+ if(! lineCur.equals("\r\n")) {
+ dataStr.append("\r\n");
+ }
+ lineCur = getLineTerminator();
+ }
+ return dataStr.toString();
}
};
@@ -559,8 +614,13 @@ public abstract class BluetoothMapbMessage {
}
if(line.contains("BEGIN:BBODY")){
if(D) Log.d(TAG,"Decoding bbody");
- parseBody(reader);
- }
+
+ if(type == TYPE.EMAIL){
+ //TODO: Support Attachments also.
+ parseBodyEmail(reader.getStringTerminator("END:BBODY"));
+ } else
+ parseBody(reader);
+ }
}
private void parseBody(BMsgReader reader) {
@@ -667,6 +727,8 @@ public abstract class BluetoothMapbMessage {
*/
public abstract void parseMsgInit();
+ public void parseBodyEmail (String msg){
+ }
public abstract byte[] encode() throws UnsupportedEncodingException;
public void setStatus(boolean read) {
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java b/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java
index 0f1feac9f..9bbf90d6d 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java
@@ -27,8 +27,16 @@ import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
import android.util.Base64;
import android.util.Log;
+import java.util.Random;
+import android.util.Log;
+import java.io.IOException;
+
public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
+ private static final boolean D = BluetoothMapService.DEBUG;
+ private static final boolean V = BluetoothMapService.VERBOSE;
+ protected static String TAG = "BluetoothMapbMessageEmail";
+ private static final String CRLF = "\r\n";
public static class MimePart {
public long _id = INVALID_VALUE; /* The _id from the content provider, can be used to sort the parts if needed */
@@ -79,6 +87,10 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
public void encodePlainText(StringBuilder sb) throws UnsupportedEncodingException {
if(contentType != null && contentType.toUpperCase().contains("TEXT")) {
+ sb.append(contentType).append("\r\n");
+ sb.append("Content-Transfer-Encoding: 8bit").append("\r\n");
+ sb.append("Content-Disposition:inline").append("\r\n")
+ .append("\r\n");
sb.append(new String(data,"UTF-8")).append("\r\n");
} else if(contentType != null && contentType.toUpperCase().contains("/SMIL")) {
/* Skip the smil.xml, as no-one knows what it is. */
@@ -94,6 +106,7 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
private long date = INVALID_VALUE;
private String subject = null;
+ private String emailBody = null;
private ArrayList<Rfc822Token> from = null; // Shall not be empty
private ArrayList<Rfc822Token> sender = null; // Shall not be empty
private ArrayList<Rfc822Token> to = null; // Shall not be empty
@@ -144,8 +157,15 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
return subject;
}
public void setSubject(String subject) {
+ if(D) Log.d(TAG,"setting Subject to" +subject);
this.subject = subject;
}
+
+ public void setEmailBody(String emailBody) {
+ if(D) Log.d(TAG,"setting setEmailBody to" +emailBody);
+ this.emailBody= emailBody;
+ }
+
public ArrayList<Rfc822Token> getFrom() {
return from;
}
@@ -217,6 +237,9 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
}
public String getMessageId() {
return messageId;
+ }
+ public String getEmailBody() {
+ return emailBody;
}
public void setContentType(String contentType) {
this.contentType = contentType;
@@ -237,7 +260,12 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
return includeAttachments;
}
public void updateCharset() {
+ if(D) Log.d(TAG, " Inside updateCharset ");
charset = null;
+ if (parts == null) {
+ Log.e(TAG, " parts is null. returning ");
+ return;
+ }
for(MimePart part : parts) {
if(part.contentType != null &&
part.contentType.toUpperCase().contains("TEXT")) {
@@ -339,6 +367,58 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
sb.append("\r\n"); // If no headers exists, we still need two CRLF, hence keep it out of the if above.
}
+ /**
+ * Encode the bMessage as an EMAIL
+ * @return
+ * @throws UnsupportedEncodingException
+ */
+ public byte[] encodeEmail() throws UnsupportedEncodingException
+ {
+ if (V) Log.v(TAG, "Inside encodeEmail ");
+ ArrayList<byte[]> bodyFragments = new ArrayList<byte[]>();
+ StringBuilder sb = new StringBuilder ();
+ int count = 0;
+ String emailBody;
+ Random randomGenerator = new Random();
+ int randomInt = randomGenerator.nextInt(1000);
+ String boundary = "MessageBoundary."+randomInt;
+
+ encodeHeaders(sb);
+ sb.append("Mime-Version: 1.0").append("\r\n");
+ sb.append(
+ "Content-Type: multipart/mixed; boundary=\""+boundary+"\"")
+ .append("\r\n");
+ sb.append("Content-Transfer-Encoding: 8bit").append("\r\n")
+ .append("\r\n");
+ sb.append("MIME Message").append("\r\n");
+ sb.append("--"+boundary).append("\r\n");
+
+ Log.v(TAG, "after encode header sb is "+ sb.toString());
+ if(getIncludeAttachments() == false) {
+ for(MimePart part : parts) {
+ part.encodePlainText(sb); /* We call encode on all parts, to include a tag, where an attachment is missing. */
+ sb.append("--"+boundary+"--").append("\r\n");
+ }
+ } else {
+ for(MimePart part : parts) {
+ count++;
+ part.encode(sb, getBoundary(), (count == parts.size()));
+ }
+ }
+
+ emailBody = sb.toString();
+ if (V) Log.v(TAG, "emailBody is "+emailBody);
+
+ if(emailBody != null) {
+ String tmpBody = emailBody.replaceAll("END:MSG", "/END\\:MSG"); // Replace any occurrences of END:MSG with \END:MSG
+ bodyFragments.add(tmpBody.getBytes("UTF-8"));
+ } else {
+ bodyFragments.add(new byte[0]);
+ }
+
+ return encodeGeneric(bodyFragments);
+ }
+
/* Notes on MMS
* ------------
* According to rfc4356 all headers of a MMS converted to an E-mail must use
@@ -545,6 +625,16 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
// Now for the body
newPart.data = decodeBody(body, partEncoding);
}
+ private static String parseSubjectEmail(String body) {
+ int pos = body.indexOf("Subject:");
+ if (pos > 0) {
+ int beginVersionPos = pos + (("Subject:").length());
+ int endVersionPos = body.indexOf("\n", beginVersionPos);
+ return body.substring(beginVersionPos, endVersionPos);
+ } else {
+ return "";
+ }
+ }
private void parseMmsMimeBody(String body) {
MimePart newPart = addMimePart();
@@ -564,6 +654,130 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
}
return null;
}
+ private static String parseContentTypeEmail(String bmsg, String boundary) {
+ int pos1 = bmsg.indexOf("--"+boundary);
+ int pos = bmsg.indexOf("Content-Type:", pos1);
+ if (pos > 0) {
+
+ int beginVersionPos = pos + (("Content-Type:").length());
+ int endVersionPos = bmsg.indexOf(CRLF, beginVersionPos);
+ return bmsg.substring(beginVersionPos, endVersionPos);
+
+ } else {
+
+ return null;
+
+ }
+ }
+
+ private static String parseBoundaryEmail(String body) {
+ int pos = body.indexOf("boundary=\"");
+ if (pos > 0) {
+ int beginVersionPos = pos + (("boundary=\"").length());
+ int endVersionPos = body.indexOf("\"", beginVersionPos);
+ return body.substring(beginVersionPos, endVersionPos);
+ } else {
+ return null;
+ }
+ }
+ @Override
+ public void parseBodyEmail(String body) throws IllegalArgumentException {
+
+ int beginVersionPos = -1;
+ int rfc822Flag = 0;
+ int mimeFlag = 0;
+ int beginVersionPos1 = -1;
+ String contentType;
+ int pos1 = 0;
+ //PARSE SUBJECT
+ setSubject(parseSubjectEmail(body));
+ //Parse Boundary
+ String boundary = parseBoundaryEmail(body);
+ if(boundary != null && !boundary.equalsIgnoreCase("")) {
+ pos1 = body.indexOf("--"+boundary);
+ mimeFlag = 1;
+ }
+ else {
+ pos1 = body.indexOf("Date:");
+ mimeFlag = 0;
+ }
+ int contentIndex = body.indexOf("Content-Type",pos1);
+ if(contentIndex > 0) {
+ contentType = parseContentTypeEmail(body, boundary);
+ if(contentType != null && contentType.trim().equalsIgnoreCase("message/rfc822")){
+ rfc822Flag = 1;
+ }
+ }
+ int pos = body.indexOf(CRLF, pos1) + CRLF.length();
+ while (pos > 0) {
+ if(body.startsWith(CRLF, pos)) {
+ beginVersionPos = pos + CRLF.length();
+ break;
+ } else {
+ final int next = body.indexOf(CRLF, pos);
+ if (next == -1) {
+ // throw new IllegalArgumentException("Ill-formatted bMessage, no empty line");
+ // PTS: Instead of throwing Exception, return MSG
+ int beginMsg = body.indexOf("BEGIN:MSG");
+ if (beginMsg == -1) {
+ throw new IllegalArgumentException("Ill-formatted bMessage, no BEGIN:MSG");
+ }
+ int endMsg = body.indexOf("END:MSG", beginMsg);
+ if (endMsg == -1) {
+ throw new IllegalArgumentException("Ill-formatted bMessage, no END:MSG");
+ }
+ setEmailBody(body.substring(beginMsg + "BEGIN:MSG".length(), endMsg - CRLF.length()));
+ } else {
+ pos = next + CRLF.length();
+ }
+ }
+ }
+ if(beginVersionPos > 0) {
+ int endVersionPos;
+ if(rfc822Flag == 0){
+ if(mimeFlag == 0) {
+ endVersionPos = body.indexOf("END:MSG", beginVersionPos) ;
+ if (endVersionPos != -1) {
+ setEmailBody(body.substring(beginVersionPos, (endVersionPos - CRLF.length())));
+ } else {
+ setEmailBody(body.substring(beginVersionPos));
+ }
+ } else {
+ endVersionPos = (body.indexOf("--"+boundary+"--", beginVersionPos) - CRLF.length());
+ }
+ try {
+ setEmailBody(body.substring(beginVersionPos, endVersionPos));
+ } catch (IndexOutOfBoundsException e) {
+ throw new IllegalArgumentException("Ill-formatted bMessage, no end boundary");
+ }
+ } else if(rfc822Flag == 1) {
+ endVersionPos = (body.indexOf("--"+boundary+"--", beginVersionPos));
+ try {
+ body = body.substring(beginVersionPos, endVersionPos);
+ } catch (IndexOutOfBoundsException e) {
+ throw new IllegalArgumentException("Ill-formatted bMessage, no end boundary");
+ }
+ int pos2 = body.indexOf(CRLF) + CRLF.length();
+ while (pos2 > 0) {
+ if(body.startsWith(CRLF, pos2)) {
+ beginVersionPos1 = pos2 + CRLF.length();
+ break;
+ } else {
+ final int next = body.indexOf(CRLF, pos2);
+ if (next == -1) {
+ throw new IllegalArgumentException("Ill-formatted bMessage, no empty line");
+ } else {
+ pos2 = next + CRLF.length();
+ }
+ }
+ }
+ if(beginVersionPos1 > 0){
+ setEmailBody(body.substring(beginVersionPos1));
+ }
+ }
+ }
+ Log.v(TAG, "fetch body Email NULL:");
+ }
private void parseMms(String message) {
/* Overall strategy for decoding:
@@ -634,5 +848,4 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
public byte[] encode() throws UnsupportedEncodingException {
return encodeMms();
}
-
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java b/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java
index 8107bd8fe..d8eea8109 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java
@@ -23,6 +23,8 @@ import com.android.bluetooth.map.BluetoothMapSmsPdu.SmsPdu;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
public class BluetoothMapbMessageSms extends BluetoothMapbMessage {
+ private static final boolean D = BluetoothMapService.DEBUG;
+ private static final boolean V = BluetoothMapService.VERBOSE;
private ArrayList<SmsPdu> smsBodyPdus = null;
private String smsBody = null;
diff --git a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
index bdbebd86f..5f5360f76 100644
--- a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
+++ b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
@@ -61,7 +61,9 @@ public class BluetoothMnsObexClient {
private boolean mConnected = false;
BluetoothDevice mRemoteDevice;
private BluetoothMapContentObserver mObserver;
+ private BluetoothMapContentObserver mEmailObserver;
private boolean mObserverRegistered = false;
+ private boolean mEmailObserverRegistered = false;
private PowerManager.WakeLock mWakeLock = null;
// Used by the MAS to forward notification registrations
@@ -82,15 +84,16 @@ public class BluetoothMnsObexClient {
mHandler = new MnsObexClientHandler(looper);
mContext = context;
mRemoteDevice = remoteDevice;
- mObserver = new BluetoothMapContentObserver(mContext);
- mObserver.init();
}
public Handler getMessageHandler() {
return mHandler;
}
- public BluetoothMapContentObserver getContentObserver() {
+ public BluetoothMapContentObserver getContentObserver(int masInstanceId) {
+ if(masInstanceId == 1)
+ return mEmailObserver;
+
return mObserver;
}
@@ -120,6 +123,7 @@ public class BluetoothMnsObexClient {
* Call this when the MAS client requests a de-registration on events.
*/
public void disconnect() {
+ if(D) Log.d(TAG, "BluetoothMnsObexClient: disconnect");
try {
if (mClientSession != null) {
mClientSession.disconnect(null);
@@ -149,6 +153,7 @@ public class BluetoothMnsObexClient {
Log.e(TAG, "mTransport.close error: " + e.getMessage());
}
}
+ if(D) Log.d(TAG, "BluetoothMnsObexClient: exiting from disconnect");
}
/**
@@ -158,6 +163,8 @@ public class BluetoothMnsObexClient {
/* should shutdown handler thread first to make sure
* handleRegistration won't be called when disconnet
*/
+ if(D) Log.d(TAG, "BluetoothMnsObexClient: shutdown");
+ acquireMnsLock();
if (mHandler != null) {
// Shut down the thread
mHandler.removeCallbacksAndMessages(null);
@@ -167,12 +174,18 @@ public class BluetoothMnsObexClient {
}
mHandler = null;
}
- if(D) Log.d(TAG, "BluetoothMnsObexClient: exiting from disconnect");
- releaseMnsLock();
/* Disconnect if connected */
disconnect();
+ if(mEmailObserverRegistered) {
+ mEmailObserver.unregisterObserver();
+ mEmailObserverRegistered = false;
+ }
+ if (mEmailObserver != null) {
+ mEmailObserver.deinit();
+ mEmailObserver = null;
+ }
if(mObserverRegistered) {
mObserver.unregisterObserver();
mObserverRegistered = false;
@@ -181,6 +194,17 @@ public class BluetoothMnsObexClient {
mObserver.deinit();
mObserver = null;
}
+ if (mHandler != null) {
+ // Shut down the thread
+ mHandler.removeCallbacksAndMessages(null);
+ Looper looper = mHandler.getLooper();
+ if (looper != null) {
+ looper.quit();
+ }
+ mHandler = null;
+ }
+ if(D) Log.d(TAG, "BluetoothMnsObexClient: exiting from shutdown");
+ releaseMnsLock();
}
private HeaderSet hsConnect = null;
@@ -188,36 +212,41 @@ public class BluetoothMnsObexClient {
public void handleRegistration(int masId, int notificationStatus){
Log.d(TAG, "handleRegistration( " + masId + ", " + notificationStatus + ")");
- if((isConnected() == false) &&
+ //if((isConnected() == false) &&
+ if((mEmailObserverRegistered | mObserverRegistered) == false &&
(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES)) {
Log.d(TAG, "handleRegistration: connect");
connect();
}
+ synchronized (this) {
if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_NO) {
- Log.d(TAG, "handleRegistration: disconnect");
- disconnect();
- // Unregister - should we disconnect, or keep the connection? - the spec. says nothing about this.
- if(mObserverRegistered == true) {
- mObserver.unregisterObserver();
- mObserverRegistered = false;
- disconnect();
- }
+ if(masId == 1 && mEmailObserverRegistered ) {
+ mEmailObserver.unregisterObserver();
+ mEmailObserverRegistered = false;
+ } else if(mObserverRegistered) {
+ mObserver.unregisterObserver();
+ mObserverRegistered = false;
+ }
+ //disconnect();
+ //handleRegisration disable should only disable registerobserver for masId NOT Disconnct the MNSOBEXCLIENT
} else if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) {
/* Connect if we do not have a connection, and start the content observers providing
* this thread as Handler.
*/
- synchronized (this) {
- if(mObserverRegistered == false && mObserver != null) {
- mObserver.registerObserver(this, masId);
- mObserverRegistered = true;
+ if(masId == 1 && mEmailObserverRegistered == false && mEmailObserver != null) {
+ mEmailObserver.registerObserver(this, masId);
+ mEmailObserverRegistered = true;
+ } else if(mObserverRegistered == false && mObserver != null) {
+ mObserver.registerObserver(this, masId);
+ mObserverRegistered = true;
}
- }
+ }
}
}
public void connect() {
- Log.d(TAG, "handleRegistration: connect 2");
+ Log.d(TAG, "handleRegistration: connect2");
acquireMnsLock();
BluetoothSocket btSocket = null;
@@ -267,6 +296,38 @@ public class BluetoothMnsObexClient {
releaseMnsLock();
}
+ public void initObserver( int masInstanceId) {
+ if(masInstanceId == 1 ){
+ mEmailObserver = new BluetoothMapContentEmailObserver(mContext);
+ //mEmailObserver = new BluetoothMapContentObserver(mContext);
+ mEmailObserver.init();
+ }else {
+ mObserver = new BluetoothMapContentObserver(mContext);
+ mObserver.init();
+ }
+ }
+ public void deinitObserver( int masInstanceId) {
+ if(masInstanceId == 1 ){
+ if(mEmailObserverRegistered) {
+ mEmailObserver.unregisterObserver();
+ mEmailObserverRegistered = false;
+ }
+ if (mEmailObserver != null) {
+ mEmailObserver.deinit();
+ mEmailObserver = null;
+ }
+ }else {
+ if (mObserverRegistered) {
+ mObserver.unregisterObserver();
+ mObserverRegistered = false;
+ }
+ if (mObserver != null) {
+ mObserver.deinit();
+ mObserver = null;
+ }
+ }
+ }
+
public int sendEvent(byte[] eventBytes, int masInstanceId) {
Log.d(TAG, "BluetoothMnsObexClient: sendEvent");
@@ -275,13 +336,6 @@ public class BluetoothMnsObexClient {
int responseCode = -1;
HeaderSet request;
int maxChunkSize, bytesToWrite, bytesWritten = 0;
- ClientSession clientSession = mClientSession;
-
- if ((!mConnected) || (clientSession == null)) {
- Log.w(TAG, "sendEvent after disconnect:" + mConnected);
- return responseCode;
- }
-
request = new HeaderSet();
BluetoothMapAppParams appParams = new BluetoothMapAppParams();
appParams.setMasInstanceId(masInstanceId);
@@ -306,7 +360,7 @@ public class BluetoothMnsObexClient {
// Send the header first and then the body
try {
if (V) Log.v(TAG, "Send headerset Event ");
- putOperation = (ClientOperation)clientSession.put(request);
+ putOperation = (ClientOperation)mClientSession.put(request);
// TODO - Should this be kept or Removed
} catch (IOException e) {
@@ -338,8 +392,17 @@ public class BluetoothMnsObexClient {
if (bytesWritten == eventBytes.length) {
Log.i(TAG, "SendEvent finished send length" + eventBytes.length);
+ if (putOperation != null) {
+ if (V) Log.v(TAG, "Closing putOperation");
+ putOperation.close();
+ }
+ if (outputStream != null) {
+ if (V) Log.v(TAG, "Closing outputStream");
+ outputStream.close();
+ }
} else {
error = true;
+ outputStream.close();
putOperation.abort();
Log.i(TAG, "SendEvent interrupted");
}
@@ -351,16 +414,12 @@ public class BluetoothMnsObexClient {
handleSendException(e.toString());
error = true;
} finally {
+ Log.v(TAG, "finally");
try {
- if (outputStream != null) {
- outputStream.close();
- }
- } catch (IOException e) {
- Log.e(TAG, "Error when closing stream after send " + e.getMessage());
- }
- try {
- if ((!error) && (putOperation != null)) {
+ if (!error) {
+ if (V) Log.v(TAG, "Getting response Code");
responseCode = putOperation.getResponseCode();
+ if (V) Log.v(TAG, "response code is" + responseCode);
if (responseCode != -1) {
if (V) Log.v(TAG, "Put response code " + responseCode);
if (responseCode != ResponseCodes.OBEX_HTTP_OK) {
@@ -369,6 +428,7 @@ public class BluetoothMnsObexClient {
}
}
if (putOperation != null) {
+ if (V) Log.v(TAG, "Closing putOperation");
putOperation.close();
}
} catch (IOException e) {