summaryrefslogtreecommitdiffstats
path: root/src/com/android/bluetooth/map
diff options
context:
space:
mode:
authorHemant Gupta <hemantg@codeaurora.org>2014-07-17 18:47:58 +0530
committerLinux Build Service Account <lnxbuild@localhost>2014-11-04 08:26:12 -0700
commit247eb4449d0990a6a562c19c7703a7220956897e (patch)
treed3d0d0210fd44d4e21546c7b239cc9dde3ff64e1 /src/com/android/bluetooth/map
parent54dde23d150c175fbf2f7c86ab280b652ea3627b (diff)
downloadandroid_packages_apps_Bluetooth-247eb4449d0990a6a562c19c7703a7220956897e.tar.gz
android_packages_apps_Bluetooth-247eb4449d0990a6a562c19c7703a7220956897e.tar.bz2
android_packages_apps_Bluetooth-247eb4449d0990a6a562c19c7703a7220956897e.zip
MAP: Add support for Email and map bug fixes
Squashed bug fixes in map profile along with email support MAP: Support MMS pushMessage feature. Enable MMS push Message feature support. Parse multi parts in bMessage including text and attachments as per standard MIME format. Change-Id: I292f9f96d0b1215f86c95c9ae531dbe7ec6e7b05 Bluetooth: Map: Add Bug fixes from kk. Squashed commit of the following: commit 2f11cda9955871c40e0ecde465e79eadeb24b089 Author: Pradeep Panigrahi <pradeepp@codeaurora.org> Date: Fri Jul 18 00:03:10 2014 +0530 Bluetooth: Map: Bug fixes on MAP profile Generic bug fixes in map profile. Change-Id: I9b3dcf73de595f30c5c132caf6d9bb728f75191a commit 741be2f08a844e7559f6e328e5b1e86311e5a4cf Author: Ashwini Munigala <AshwiniM@codeaurora.org> Date: Fri May 30 18:25:24 2014 +0530 MAP: Fix MAP Connect issue for frequent connect/disconnect trials Ensure isWaitingAuthorization flag update during MAP Connect authorization from SocketAcceptThread to avoid user timeout disconnection for frequent remote connect/disconnect trials. Change-Id: I1502ef1bd97cc1ecaf624f371fb23a33869ab80e CRs-fixed: 667467 (cherry picked from commit 99eb62b3195782cdde8320e9fe231168b5b48a77) commit 29111c59dba3498b24c5fdfad649b83ae59a7e40 Author: Pradeep Panigrahi <pradeepp@codeaurora.org> Date: Sun Jun 22 17:33:51 2014 +0530 Bluetooth: Map: Add below fixes in map code 1) Do not allow message listing on root, telecom, msg folders. 2) Use fastXmlSerializer for creating xml folder listing element. CRs-fixed: 683145 Change-Id: I446049d21272542274cb675123e9b1735691640d commit 14713469686e72d82b05299ce0dac09f622fe16a Author: Pradeep Panigrahi <pradeepp@codeaurora.org> Date: Wed Jul 16 11:41:50 2014 +0530 Bluetooth: Map: Add only originator phone number in getMessage response In a scenario where originator vcard comprises of multiple numbers, Map server is adding all the numbers present in Originator's vcard to bMessage while responding to getMessage request. This is creating IOT issues with all the carkits, as carkits are not able to parse properly this bMessage and always showing the last contact as the message Originator. Add change to keep only the primary number with which the message is sent, on the originator vcard. Change-Id: I9b3368353ad57476357b4d9933227d39653f3328 CRs-fixed: 694556 commit 365673eb378670d8be8212b10dea34fde35a8f2b Author: Pradeep Panigrahi <pradeepp@codeaurora.org> Date: Wed Jul 9 12:28:02 2014 +0530 BT: Map: Add null check for number parameter during message listing A case where message is saved in draft with no recipeint name. when we try to fetch contact name during messagelisting, null number passed to ContactsProvider would cause IllegalArgumentException. Add null and empty check before quering contact provider. Change-Id: Id25ba00af414d252dd8b1ca6475bfa31b9278757 CRs-fixed: 690894 commit 162b129aac30c5e4081b04e4144d2baa309ea587 Author: Pradeep Panigrahi <pradeepp@codeaurora.org> Date: Tue Jun 24 11:08:48 2014 +0530 Bluetooth: Map: Parse Native format message properly Add change to parse native format message properly and call telephony interface with correct parameter. Change-Id: I52b27fa872986229914787053528e2cc53d8440b CRs-fixed: 684522 commit 2c5d6c433387c2dd863333c6f724ab6573fb4e0f Author: Pradeep Panigrahi <pradeepp@codeaurora.org> Date: Wed May 21 08:50:37 2014 +0530 Bluetooth: Map: IOT: Fix Message Display issue with FORD SYNC carkit FORD SYNC carkit is unable to parse message if line feed (\n) is present in it and displaying incomplete message. Add change to remove line feed (\n) from message and keep only carriage return (\r) in the message, to fix this IOT issue. Change-Id: Icf30097a5651be8595017aa6772cac02582c99be CRs-fixed: 667984 commit 858a4d750e05111e86d98cdbeb511d19fcd47a19 Author: Pradeep Panigrahi <pradeepp@codeaurora.org> Date: Fri May 16 10:54:56 2014 +0530 Bluetooth:Map:IOT: Fix Subject Display issue with HONDA Carkit Honda Carkit always masks subject field in message listing and hence always shows a blank subject in UI. Add change to always send subject field in Messagelisting for honda carkit,so that it can show proper subject fields in UI and better user experience. Change-Id: Icb9dd51ba0e1d1a5b45aeb417afcce6e79365b32 CRs-fixed: 664315 commit 030d6ddffc9df745f46566e93316c52913b14aac Author: Pradeep Panigrahi <pradeepp@codeaurora.org> Date: Thu May 8 13:02:58 2014 +0530 Bluetooth: Map: IOT: Fix Message Display issue with PCM carkit PCM carkit is unable to parse message if carriage return(\r) is present in it and displaying incomplete message. Add change to remove carriage return (\r) from message to fix this IOT issue. Change-Id: Ic69ffad138f2c633214a7a24c377b03c6f569d3f CRs-fixed: 660620 commit 71124336d13c61190d41dccdf830b11242892dac Author: Pradeep Panigrahi <pradeepp@codeaurora.org> Date: Mon Feb 10 12:19:05 2014 +0530 Bluetooth: MAP: Synchronize Mns function's to avoid race condition synchronize Mns shutdown and disconnect function so that if it is called from different threads at the same point, it should not lead to any crash. CRs-fixed: 613764 Change-Id: I1df0440b4189521a0fb88d981f4208f225ef2a51 commit f1a78b061cffe66b58410bf2fd6dce3527150e56 Author: Pradeep Panigrahi <pradeepp@codeaurora.org> Date: Fri Jan 31 11:27:58 2014 +0530 Bluetooth: Map: Validate BT Adapter before starting rfcomm listener Validate BT adapter handle to avoid null pointer exception, while trying to start rfcomm listener as a part of MAP profile startup. Change-Id: Ifa5f1f77ea51cab793df3695a8f7058525f67b98 CRs-fixed: 608319 Change-Id: I7467247891b45708f9ebf8710c9545bdd94d93c3 Conflicts: src/com/android/bluetooth/map/BluetoothMapService.java MAP: Support MAP S Email Instance Add MSE Implementation in Bluetooth Map Profile to support email MAS instance for EMAIL type messages. Change-Id: I65802821ebe545b605c7cfd5e9679173e780f2a1 Conflicts: src/com/android/bluetooth/map/BluetoothMapService.java Bluetooth:Map: Bug fixes in MAP profile. 1) Use Application mainlooper while calling phoneStateListener. 2) Generic bug fixes in map profile. CRs-fixed: 716711 Change-Id: Ibc874c5cba17faa8c97c8b085525a4e40531b9b2 Bluetooth: Map: Add replyToAddressing field in message lisitng element replyToAddressing field is not getting added in messageListing element even if parameter mask is set to zero. Add change to include reply to address as well in the messagelisting element. Change-Id: Id4a87685684b6eb746690489f84347271bd2f21e CRs-fixed: 718838 Bluetooth: Map: Filter email messages based on priority Add change to filter email messages based on priority if message lisiting is done with filterPriority parameter CRs-fixed: 718854 Change-Id: I1dc963ba370d13b867ce559b43e939a5dcf120c4 Bluetooth: Map: Add following fix in MAP profile Add change to filter email messages using originator email .When message listing is done with filter originator parameter set to email address then message listing is failing because filtering was only supported with originator name. Add change to filter email address using originator email as well to fix this issue. Add change to create proper v card for all reciepints in bMessage response to get message request. CRs-fixed: 718859 Change-Id: I1834d6fdc77c6fb92dd29c8bf74e6df37e0b6637 Bluetooth: Map: do not add old folder field in MessageDeleted mns event As per MAP spec 1.2 MessageDeleted event should not contain the old_folder field in mns event report. Add change for the same. CRs-fixed: 720675 Change-Id: I6259b6db1e3d7d8fb83a8c6ea9a912560c2cc4d9 Bluetooth: Map: Add change to not send mns event for mce initiate operation MCE initiated operation does not need mns event to be sent by mas server. Add change such that no mns event is sent for mce initiated operation for email instance. CRs-fixed: 720677 Change-Id: I2355a9c7b81b8ef3c9dc6d612ac440a8fee9b1ee MAP: MMS SendingSuccess event only for MCE initiated push. Report MNS SendingSuccess type event only when MMS is pushed through MAP from MCE. CRs-fixed: 715343 Change-Id: I6f291152d1d91e8eda5ed2aaba6986e018f45446 Bluetooth: Map: Add following fixes in map profile 1) Add change to send sending success mns event for only mce initiated operation. 2) Add change to send message shift mns event 3) Add change to filter reply to addressing using parameter mask. CRs-fixed: 724276 Change-Id: Iae285c63d9e0a452f44e32b58bb8c3240e07f933
Diffstat (limited to 'src/com/android/bluetooth/map')
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapAppParams.java9
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapAuthenticator.java4
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapContent.java1171
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapContentEmailObserver.java675
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapContentObserver.java284
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapFolderElement.java28
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapMessageListing.java57
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java47
-rwxr-xr-x[-rw-r--r--]src/com/android/bluetooth/map/BluetoothMapObexServer.java180
-rwxr-xr-x[-rw-r--r--]src/com/android/bluetooth/map/BluetoothMapService.java858
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapSmsPdu.java6
-rwxr-xr-x[-rw-r--r--]src/com/android/bluetooth/map/BluetoothMapUtils.java79
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapbMessage.java98
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java374
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapbMessageSms.java14
-rw-r--r--src/com/android/bluetooth/map/BluetoothMnsObexClient.java109
16 files changed, 3376 insertions, 617 deletions
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppParams.java b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
index cb607c704..25eb99d0b 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAppParams.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
@@ -168,6 +168,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) {
@@ -696,7 +699,11 @@ public class BluetoothMapAppParams {
public void setParameterMask(long parameterMask) {
if (parameterMask < 0 || parameterMask > 0xFFFFFFFFL)
throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFFFFFF");
- this.parameterMask = parameterMask;
+ if(parameterMask == 0) {
+ this.parameterMask = INVALID_VALUE_PARAMETER;
+ } else {
+ this.parameterMask = parameterMask;
+ }
}
public int getFolderListingSize() {
diff --git a/src/com/android/bluetooth/map/BluetoothMapAuthenticator.java b/src/com/android/bluetooth/map/BluetoothMapAuthenticator.java
index 2d345a133..c182820d6 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAuthenticator.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAuthenticator.java
@@ -18,8 +18,8 @@ import android.os.Handler;
import android.os.Message;
import android.util.Log;
-import javax.obex.Authenticator;
-import javax.obex.PasswordAuthentication;
+import javax.btobex.Authenticator;
+import javax.btobex.PasswordAuthentication;
/**
* BluetoothMapAuthenticator is a used by BluetoothObexServer for obex
diff --git a/src/com/android/bluetooth/map/BluetoothMapContent.java b/src/com/android/bluetooth/map/BluetoothMapContent.java
index 81cd2fdf3..9f053a84b 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -19,12 +19,15 @@ 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;
@@ -33,18 +36,32 @@ import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.Telephony.Mms;
import android.provider.Telephony.Sms;
+import android.provider.Telephony.Threads;
import android.telephony.TelephonyManager;
import android.util.Log;
+import android.text.TextUtils;
+import android.text.format.Time;
+import android.util.TimeFormatException;
+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 com.google.android.mms.pdu.PduHeaders;
+import android.database.sqlite.SQLiteException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.*;
+import org.apache.commons.codec.net.QuotedPrintableCodec;
+import org.apache.commons.codec.DecoderException;
public class BluetoothMapContent {
private static final String TAG = "BluetoothMapContent";
- private static final boolean D = false;
- private static final boolean V = false;
+ private static final boolean D = BluetoothMapService.DEBUG;
+ private static final boolean V = Log.isLoggable(BluetoothMapService.LOG_TAG, Log.VERBOSE) ? true : false;
private static final int MASK_SUBJECT = 0x1;
private static final int MASK_DATETIME = 0x2;
@@ -65,6 +82,13 @@ public class BluetoothMapContent {
private static final int MASK_SENT = 0x2000;
private static final int MASK_PROTECTED = 0x4000;
private static final int MASK_REPLYTO_ADDRESSING = 0x8000;
+ private static final String HONDA_CARKIT = "64:D4:BD";
+
+ /* OMA-TS-MMS-ENC defined many types in X-Mms-Message-Type.
+ Only m-send-req (128) m-retrieve-conf (132), m-notification-ind (130)
+ are interested by user */
+ private static final String INTERESTED_MESSAGE_TYPE_CLAUSE =
+ "( m_type = 128 OR m_type = 132 OR m_type = 130 )";
/* Type of MMS address. From Telephony.java it must be one of PduHeaders.BCC, */
/* PduHeaders.CC, PduHeaders.FROM, PduHeaders.TO. These are from PduHeaders.java */
@@ -73,8 +97,49 @@ public class BluetoothMapContent {
public static final int MMS_BCC = 0x81;
public static final int MMS_CC = 0x82;
+
+ /* Type of Email address. */
+ 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 String msgListingFolder = null;
+ 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,
@@ -103,11 +168,13 @@ public class BluetoothMapContent {
Mms.MESSAGE_BOX,
Mms.STATUS,
Mms.PRIORITY,
+ Mms.MESSAGE_TYPE,
};
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;
@@ -119,7 +186,7 @@ public class BluetoothMapContent {
mContext = context;
mResolver = mContext.getContentResolver();
if (mResolver == null) {
- Log.d(TAG, "getContentResolver failed");
+ Log.e(TAG, "getContentResolver failed");
}
}
@@ -191,7 +258,8 @@ public class BluetoothMapContent {
"\n " + Mms.DATE_SENT + " : " + c.getLong(c.getColumnIndex(Mms.DATE_SENT)) +
"\n " + Mms.READ + " : " + c.getInt(c.getColumnIndex(Mms.READ)) +
"\n " + Mms.MESSAGE_BOX + " : " + c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)) +
- "\n " + Mms.STATUS + " : " + c.getInt(c.getColumnIndex(Mms.STATUS)));
+ "\n " + Mms.STATUS + " : " + c.getInt(c.getColumnIndex(Mms.STATUS)) +
+ "\n " + Mms.MESSAGE_TYPE + " : " + c.getInt(c.getColumnIndex(Mms.MESSAGE_TYPE)));
}
private void printMmsAddr(long id) {
@@ -205,7 +273,7 @@ public class BluetoothMapContent {
selection,
null, null);
- if (c.moveToFirst()) {
+ if (c !=null && c.moveToFirst()) {
do {
String add = c.getString(c.getColumnIndex("address"));
Integer type = c.getInt(c.getColumnIndex("type"));
@@ -254,7 +322,7 @@ public class BluetoothMapContent {
null, null);
if (D) Log.d(TAG, " parts:");
- if (c.moveToFirst()) {
+ if (c !=null && c.moveToFirst()) {
do {
Long partid = c.getLong(c.getColumnIndex(BaseColumns._ID));
String ct = c.getString(c.getColumnIndex("ct"));
@@ -298,9 +366,9 @@ public class BluetoothMapContent {
printMmsAddr(id);
printMmsParts(id);
}
+ c.close();
} else {
Log.d(TAG, "query failed");
- c.close();
}
}
@@ -315,13 +383,73 @@ public class BluetoothMapContent {
while (c.moveToNext()) {
printSms(c);
}
+ c.close();
} else {
Log.d(TAG, "query failed");
- c.close();
}
}
+ /**
+ * Get SMS RecipientAddresses for DRAFT folder based on threadId
+ *
+ */
+ private String getMessageSmsRecipientAddress(int threadId){
+ String [] RECIPIENT_ID_PROJECTION = { "recipient_ids" };
+ /*
+ 1. Get Recipient Ids from Threads.CONTENT_URI
+ 2. Get Recipient Address for corresponding Id from canonical-addresses table.
+ */
+
+ Uri sAllCanonical = Uri.parse("content://mms-sms/canonical-addresses");
+ Uri sAllThreadsUri = Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build();
+ Cursor cr = null;
+ String recipientAddress= "";
+ String recipientIds = null;
+ String whereClause = "_id="+threadId;
+ if (V) Log.v(TAG, "whereClause is "+ whereClause);
+ cr = mContext.getContentResolver().query(sAllThreadsUri, RECIPIENT_ID_PROJECTION, whereClause, null,
+ null);
+ if (cr != null && cr.moveToFirst()) {
+ recipientIds = cr.getString(0);
+ if (V) Log.v(TAG, "cursor.getCount(): " + cr.getCount() + "recipientIds: "+ recipientIds +"whrClus: "+ whereClause );
+ }
+ if(cr != null){
+ cr.close();
+ cr = null;
+ }
+
+ if (V) Log.v(TAG, "recipientIds with spaces: "+ recipientIds +"\n");
+
+ if(recipientIds != null) {
+ String recipients[] = null;
+ whereClause = "";
+ recipients = recipientIds.split(" ");
+ for (String id: recipients) {
+ if(whereClause.length() != 0)
+ whereClause +=" OR ";
+ whereClause +="_id="+id;
+ }
+ if (V) Log.v(TAG, "whereClause is "+ whereClause);
+ cr = mContext.getContentResolver().query(sAllCanonical , null, whereClause, null, null);
+ if (cr != null && cr.moveToFirst()) {
+ do {
+ //TODO: Multiple Recipeints are appended with ";" for now.
+ if(recipientAddress.length() != 0 )
+ recipientAddress+=";";
+ recipientAddress+=cr.getString(cr.getColumnIndex("address"));
+ } while(cr.moveToNext());
+ }
+ if(cr != null)
+ cr.close();
+
+ }
+
+ if(V) Log.v(TAG,"Final recipientAddress : "+ recipientAddress);
+ return recipientAddress;
+
+ }
+
public void dumpMessages() {
dumpSmsTable();
dumpMmsTable();
@@ -342,6 +470,45 @@ public class BluetoothMapContent {
}
}
+ public static String decodeEncodedWord(String checkEncoded) {
+ if (checkEncoded != null && (checkEncoded.contains("=?") == false)) {
+ if(V) Log.v(TAG, " Decode NotRequired" + checkEncoded);
+ return checkEncoded;
+ }
+
+ int begin = checkEncoded.indexOf("=?", 0);
+
+ int endScan = begin + 2;
+ if (begin != -1) {
+ int qm1 = checkEncoded.indexOf('?', endScan + 2);
+ int qm2 = checkEncoded.indexOf('?', qm1 + 1);
+ if (qm2 != -1) {
+ endScan = qm2 + 1;
+ }
+ }
+
+ int end = begin == -1 ? -1 : checkEncoded.indexOf("?=", endScan);
+ if (end == -1)
+ return checkEncoded;
+ checkEncoded = checkEncoded.substring((endScan - 1), (end + 1));
+
+ // TODO: Handle encoded words as defined by MIME standard RFC 2047
+ // Encoded words will have the form =?charset?enc?Encoded word?= where
+ // enc is either 'Q' or 'q' for quoted-printable and 'B' or 'b' for Base64
+ QuotedPrintableCodec qpDecode = new QuotedPrintableCodec("UTF-8");
+ String decoded = null;
+ try {
+ decoded = qpDecode.decode(checkEncoded);
+ }catch (DecoderException e ){
+ if(V) Log.v(TAG, "decode exception");
+ return checkEncoded;
+ }
+ if (decoded == null) {
+ return checkEncoded;
+ }
+ return decoded;
+ }
+
private void setProtected(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_PROTECTED) != 0) {
@@ -353,14 +520,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 {
@@ -378,6 +555,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.getInt(c.getColumnIndex(MessageColumns.FLAG_READ));
}
String setread = null;
if (read == 1) {
@@ -418,6 +597,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);
@@ -443,6 +636,8 @@ public class BluetoothMapContent {
hasText = "no";
}
}
+ } else {
+ hasText = "yes";
}
if (D) Log.d(TAG, "setText: " + hasText);
e.setText(hasText);
@@ -467,6 +662,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);
@@ -485,6 +699,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);
@@ -502,9 +718,31 @@ public class BluetoothMapContent {
} else {
address = c.getString(c.getColumnIndex(Sms.ADDRESS));
}
+ if ((address == null) && msgListingFolder.equalsIgnoreCase("draft")) {
+ int threadIdInd = c.getColumnIndex("thread_id");
+ String threadIdStr = c.getString(threadIdInd);
+ address = getMessageSmsRecipientAddress(Integer.valueOf(threadIdStr));
+ if(V) Log.v(TAG, "threadId = " + threadIdStr + " adress:" + address +"\n");
+ }
} 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);
+ address = c.getString(toIndex);
+ if (address != null && address.contains("")) {
+ String[] recepientAddrStr = address.split("");
+ if (recepientAddrStr !=null && recepientAddrStr.length > 0) {
+ if (V){
+ Log.v(TAG, " ::Recepient addressing split String 0:: " + recepientAddrStr[0]
+ + "::Recepient addressing split String 1:: " + recepientAddrStr[1]);
+ }
+ e.setRecipientAddressing(recepientAddrStr[0].trim()); }
+ } else {
+ if (D) Log.d(TAG, "setRecipientAddressing: " + address);
+ e.setRecipientAddressing(address);
+ }
+ return;
}
if (D) Log.d(TAG, "setRecipientAddressing: " + address);
e.setRecipientAddressing(address);
@@ -515,6 +753,7 @@ public class BluetoothMapContent {
FilterInfo fi, BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_RECIPIENT_NAME) != 0) {
String name = "";
+ String firstRecipient = null;
if (fi.msgType == FilterInfo.TYPE_SMS) {
int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
if (msgType != 1) {
@@ -523,16 +762,52 @@ public class BluetoothMapContent {
} else {
name = fi.phoneAlphaTag;
}
+ if ((name == null) && msgListingFolder.equalsIgnoreCase("draft")) {
+ int threadIdInd = c.getColumnIndex("thread_id");
+ String threadIdStr = c.getString(threadIdInd);
+ firstRecipient = getMessageSmsRecipientAddress(Integer.valueOf(threadIdStr));
+ if(V) Log.v(TAG, "threadId = " + threadIdStr + " address:" + firstRecipient +"\n");
+ if (firstRecipient != null) {
+ // Get first Recipient Name for multiple recipient addressing
+ if(firstRecipient.contains(";") ){
+ firstRecipient = firstRecipient.split(";")[0];
+ } else if (firstRecipient.contains(",")){
+ firstRecipient = firstRecipient.split(",")[0];
+ }
+ name = getContactNameFromPhone(firstRecipient);
+ }
+ }
+
} else if (fi.msgType == FilterInfo.TYPE_MMS) {
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) {
+ if ((ap.getParameterMask() & MASK_REPLYTO_ADDRESSING) != 0) {
+ 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 (address == null)
+ address = "";
+ }
+ 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) {
@@ -547,6 +822,30 @@ 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(address != null) {
+ if(address.contains("")){
+ String[] senderAddrStr = address.split("");
+ if(senderAddrStr !=null && senderAddrStr.length > 0){
+ if (V){
+ Log.v(TAG, " ::Sender Addressing split String 0:: " + senderAddrStr[0]
+ + "::Sender Addressing split String 1:: " + senderAddrStr[1]);
+ }
+ e.setEmailSenderAddressing(senderAddrStr[0].trim());
+ }
+ } else{
+ if(address.indexOf('<') != -1 && address.indexOf('>') != -1) {
+ if (D) Log.d(TAG, "setSenderAddressing: " + address.substring(address.indexOf('<')+1, address.lastIndexOf('>')));
+ e.setEmailSenderAddressing(address.substring(address.indexOf('<')+1, address.lastIndexOf('>')));
+ } else {
+ if (D) Log.d(TAG, "setSenderAddressing: " + address);
+ e.setEmailSenderAddressing(address);
+ }
+ }
+ }
+ return;
}
if (D) Log.d(TAG, "setSenderAddressing: " + address);
e.setSenderAddressing(address);
@@ -569,6 +868,25 @@ 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(name != null && name.contains("")){
+ String[] senderStr = name.split("");
+ if(senderStr !=null && senderStr.length > 0){
+ if (V){
+ Log.v(TAG, " ::Sender name split String 0:: " + senderStr[0]
+ + "::Sender name split String 1:: " + senderStr[1]);
+ }
+ name = senderStr[1];
+ }
+ }
+ if (name != null) {
+ name = decodeEncodedWord(name);
+ name = name.trim();
+ }
+
}
if (D) Log.d(TAG, "setSenderName: " + name);
e.setSenderName(name);
@@ -577,26 +895,56 @@ public class BluetoothMapContent {
private void setDateTime(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
- long date = 0;
+ if ((ap.getParameterMask() & MASK_DATETIME) != 0) {
+ long date = 0;
+ int timeStamp = 0;
+
+ if (fi.msgType == FilterInfo.TYPE_SMS) {
+ date = c.getLong(c.getColumnIndex(Sms.DATE));
+ } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+ /* Use Mms.DATE for all messages. Although contract class states */
+ /* Mms.DATE_SENT are for outgoing messages. But that is not working. */
+ date = c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L;
+
+ /* int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); */
+ /* if (msgBox == Mms.MESSAGE_BOX_INBOX) { */
+ /* date = c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L; */
+ /* } 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);
+ }
+ }
- if (fi.msgType == FilterInfo.TYPE_SMS) {
- date = c.getLong(c.getColumnIndex(Sms.DATE));
- } else if (fi.msgType == FilterInfo.TYPE_MMS) {
- /* Use Mms.DATE for all messages. Although contract class states */
- /* Mms.DATE_SENT are for outgoing messages. But that is not working. */
- date = c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L;
+ private String getTextPartsMms(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);
- /* int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); */
- /* if (msgBox == Mms.MESSAGE_BOX_INBOX) { */
- /* date = c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L; */
- /* } else { */
- /* date = c.getLong(c.getColumnIndex(Mms.DATE_SENT)) * 1000L; */
- /* } */
+ 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());
}
- e.setDateTime(date);
+ if (c != null) {
+ c.close();
+ }
+ return text;
}
- private String getTextPartsMms(long id) {
+
+ private String getTextPartsEmail(long id) {
String text = "";
String selection = new String("mid=" + id);
String uriStr = String.format("content://mms/%d/part", id);
@@ -625,7 +973,8 @@ public class BluetoothMapContent {
if(subLength == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
subLength = 256;
- if ((ap.getParameterMask() & MASK_SUBJECT) != 0) {
+ if (BluetoothMapService.getRemoteDevice().getAddress().startsWith(HONDA_CARKIT) ||
+ (ap.getParameterMask() & MASK_SUBJECT) != 0) {
if (fi.msgType == FilterInfo.TYPE_SMS) {
subject = c.getString(c.getColumnIndex(Sms.BODY));
} else if (fi.msgType == FilterInfo.TYPE_MMS) {
@@ -635,6 +984,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(),
@@ -657,6 +1013,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);
@@ -671,6 +1029,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);
@@ -687,7 +1046,9 @@ public class BluetoothMapContent {
private String getContactNameFromPhone(String phone) {
String name = "";
-
+ if (TextUtils.isEmpty(phone)) {
+ return name;
+ }
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
Uri.encode(phone));
@@ -702,7 +1063,9 @@ public class BluetoothMapContent {
name = c.getString(c.getColumnIndex(Contacts.DISPLAY_NAME));
}
- c.close();
+ if (c != null) {
+ c.close();
+ }
return name;
}
@@ -723,6 +1086,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));
@@ -752,7 +1131,7 @@ public class BluetoothMapContent {
if (msgType == 1) {
String phone = fi.phoneNum;
String name = fi.phoneAlphaTag;
- if (phone != null && phone.length() > 0 && phone.matches(recip)) {
+ if (phone != null && phone.length() > 0 && phone.replaceAll("\\s","").matches(recip)) {
if (D) Log.d(TAG, "match recipient phone = " + phone);
res = true;
} else if (name != null && name.length() > 0 && name.matches(recip)) {
@@ -765,7 +1144,7 @@ public class BluetoothMapContent {
else {
String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
if (phone != null && phone.length() > 0) {
- if (phone.matches(recip)) {
+ if (phone.replaceAll("\\s","").matches(recip)) {
if (D) Log.d(TAG, "match recipient phone = " + phone);
res = true;
} else {
@@ -794,6 +1173,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;
@@ -804,6 +1185,28 @@ public class BluetoothMapContent {
return res;
}
+ private boolean matchOriginatorEmail(Cursor c, FilterInfo fi, String orig) {
+ boolean res = false;
+ String name;
+ String originatorEmail;
+ int displayNameIndex = c.getColumnIndex("displayName");
+ name = c.getString(displayNameIndex);
+ originatorEmail = c.getString(c.getColumnIndex(MessageColumns.FROM_LIST));
+ if (name != null && name.length() > 0) {
+ if (name.toLowerCase().matches(orig.toLowerCase())) {
+ if (D) Log.d(TAG, "match originator name = " + name);
+ res = true;
+ }
+ }
+ if (originatorEmail != null && originatorEmail.length() > 0) {
+ if (originatorEmail.toLowerCase().matches(orig.toLowerCase())) {
+ if (D) Log.d(TAG, "match originator email = " + originatorEmail);
+ res = true;
+ }
+ }
+ return res;
+ }
+
private boolean matchOriginatorMms(Cursor c, FilterInfo fi, String orig) {
boolean res;
long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
@@ -833,7 +1236,7 @@ public class BluetoothMapContent {
if (msgType == 1) {
String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
if (phone !=null && phone.length() > 0) {
- if (phone.matches(orig)) {
+ if (phone.replaceAll("\\s","").matches(orig)) {
if (D) Log.d(TAG, "match originator phone = " + phone);
res = true;
} else {
@@ -852,7 +1255,7 @@ public class BluetoothMapContent {
else {
String phone = fi.phoneNum;
String name = fi.phoneAlphaTag;
- if (phone != null && phone.length() > 0 && phone.matches(orig)) {
+ if (phone != null && phone.length() > 0 && phone.replaceAll("\\s","").matches(orig)) {
if (D) Log.d(TAG, "match originator phone = " + phone);
res = true;
} else if (name != null && name.length() > 0 && name.matches(orig)) {
@@ -875,6 +1278,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;
@@ -893,6 +1298,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)) {
@@ -941,14 +1384,18 @@ public class BluetoothMapContent {
where = setWhereFilterFolderTypeSms(folder);
} else if (fi.msgType == FilterInfo.TYPE_MMS) {
where = setWhereFilterFolderTypeMms(folder);
+ } else {
+ where = setWhereFilterFolderTypeEmail(folder);
+
}
return where;
}
- private String setWhereFilterReadStatus(BluetoothMapAppParams ap) {
+ private String setWhereFilterReadStatus(BluetoothMapAppParams ap, FilterInfo fi) {
String where = "";
if (ap.getFilterReadStatus() != -1) {
+ if ((fi.msgType == FilterInfo.TYPE_SMS) || (fi.msgType == FilterInfo.TYPE_MMS)) {
if ((ap.getFilterReadStatus() & 0x01) != 0) {
where = " AND read=0 ";
}
@@ -956,6 +1403,15 @@ public class BluetoothMapContent {
if ((ap.getFilterReadStatus() & 0x02) != 0) {
where = " AND read=1 ";
}
+ } else {
+ if ((ap.getFilterReadStatus() & 0x01) != 0) {
+ where = " AND flagRead=0 ";
+ }
+
+ if ((ap.getFilterReadStatus() & 0x02) != 0) {
+ where = " AND flagRead=1 ";
+ }
+ }
}
return where;
@@ -968,14 +1424,32 @@ public class BluetoothMapContent {
where = " AND date >= " + ap.getFilterPeriodBegin();
} else if (fi.msgType == FilterInfo.TYPE_MMS) {
where = " AND date >= " + (ap.getFilterPeriodBegin() / 1000L);
+ }else {
+ Time time = new Time();
+ try {
+ time.parse(ap.getFilterPeriodBeginString().trim());
+ where += " AND timeStamp >= " + time.toMillis(false);
+ } catch (TimeFormatException e) {
+ Log.d(TAG, "Bad formatted FilterPeriodBegin, Ignore"
+ + ap.getFilterPeriodBeginString());
+ }
}
}
if ((ap.getFilterPeriodEnd() != -1)) {
if (fi.msgType == FilterInfo.TYPE_SMS) {
- where += " AND date < " + ap.getFilterPeriodEnd();
+ where += " AND date <= " + ap.getFilterPeriodEnd();
} else if (fi.msgType == FilterInfo.TYPE_MMS) {
- where += " AND date < " + (ap.getFilterPeriodEnd() / 1000L);
+ where += " AND date <= " + (ap.getFilterPeriodEnd() / 1000L);
+ } else {
+ Time time = new Time();
+ try {
+ time.parse(ap.getFilterPeriodEndString().trim());
+ where += " AND timeStamp <= " + time.toMillis(false);
+ } catch (TimeFormatException e) {
+ Log.d(TAG, "Bad formatted FilterPeriodEnd, Ignore"
+ + ap.getFilterPeriodEndString());
+ }
}
}
@@ -1011,9 +1485,13 @@ public class BluetoothMapContent {
if (!c.isLast()) {
where += " OR ";
}
- p.close();
+ if (p != null) {
+ p.close();
+ }
+ }
+ if (c != null) {
+ c.close();
}
- c.close();
if (str != null && str.length() > 0) {
if (where.length() > 0) {
@@ -1114,11 +1592,34 @@ 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 = "";
where += setWhereFilterFolderType(folder, fi);
- where += setWhereFilterReadStatus(ap);
+ where += setWhereFilterReadStatus(ap, fi);
where += setWhereFilterPeriod(ap, fi);
where += setWhereFilterPriority(ap,fi);
/* where += setWhereFilterOriginator(ap, fi); */
@@ -1171,11 +1672,529 @@ public class BluetoothMapContent {
}
}
- public BluetoothMapMessageListing msgListing(String folder, BluetoothMapAppParams ap) {
+ /**
+ * 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 & 0x04) == 0) {
+ return true;
+ } else {
+ if (V) Log.v(TAG, "Invalid Message Filter");
+ 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);
+ if(senderStr[0].indexOf('<') != -1 && senderStr[0].indexOf('>') != -1) {
+ if(V) Log.v(TAG, "from addressing is " + senderName.substring(senderStr[0].indexOf('<')+1, senderStr[0].lastIndexOf('>')));
+ message.addFrom(null, senderStr[0].substring(senderStr[0].indexOf('<')+1, senderStr[0].lastIndexOf('>')));
+ } else{
+ message.addFrom(null, senderStr[0].trim());
+ }
+ }
+ } else {
+ if(V) Log.v(TAG, " senderStr is" + senderName.trim());
+ setVCardFromEmailAddress(message, senderName.trim(), true);
+ if(senderName.indexOf('<') != -1 && senderName.indexOf('>') != -1) {
+ if(V) Log.v(TAG, "from addressing is " + senderName.substring(senderName.indexOf('<')+1, senderName.lastIndexOf('>')));
+ message.addFrom(null, senderName.substring(senderName.indexOf('<')+1, senderName.lastIndexOf('>')));
+ } else{
+ 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(", \"", "; \"");
+ multiRecepients = recipientName.replace(", ", "; ");
+ 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);
+
+ if(tempEmail.indexOf('<') != -1) {
+ if(D) Log.d(TAG, "Adding to: " + tempEmail.substring(tempEmail.indexOf('<')+1,
+ tempEmail.indexOf('>')));
+ message.addTo(null,tempEmail.substring(tempEmail.indexOf('<')+1,
+ tempEmail.indexOf('>')));
+ } else {
+ message.addTo(null, tempEmail);
+ }
+ } while(emailId.hasMoreElements());
+ }
+ } else {
+ Log.v(TAG, " Setting ::Recepient name :: " + recipientName.trim());
+ setVCardFromEmailAddress(message, recipientName.trim(), false);
+ if(recipientName.indexOf('<') != -1 && recipientName.indexOf('>') != -1) {
+ if(V) Log.v(TAG, "to addressing is " + recipientName.substring(recipientName.indexOf('<')+1,
+ recipientName.lastIndexOf('>')));
+ message.addTo(null, recipientName.substring(recipientName.indexOf('<')+1,
+ recipientName.lastIndexOf('>')));
+ } else {
+ message.addTo(null, recipientName.trim());
+ }
+ }
+ }
+
+ recipientName = null;
+ multiRecepients = null;
+ if((recipientName = c.getString(c.getColumnIndex(MessageColumns.CC_LIST))) != null){
+ if(V) Log.v(TAG, " recipientName " + recipientName);
+ if(recipientName.contains("^B")){
+ String[] recepientStr = recipientName.split("^B");
+ if(recepientStr !=null && recepientStr.length > 0){
+ if (V){
+ Log.v(TAG, " recepientStr[1] " + recepientStr[1].trim());
+ Log.v(TAG, " recepientStr[0] " + recepientStr[0].trim());
+ }
+ } else if(recipientName.contains("")){
+ multiRecepients = recipientName.replace('', ';');
+ setVCardFromEmailAddress(message, recepientStr[1].trim(), false);
+ message.addCc(recepientStr[1].trim(), recepientStr[0].trim());
+ }
+ 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.addCc(multiRecepients.trim(), multiRecepients.trim());
+ }
+ } else if(recipientName.contains(",")){
+ multiRecepients = recipientName.replace(", \"", "; \"");
+ multiRecepients = recipientName.replace(", ", "; ");
+ 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.addCc(null, tempEmail);
+ } while(emailId.hasMoreElements());
+ }
+ } else {
+ if(V) Log.v(TAG, " Setting ::Recepient name :: " + recipientName.trim());
+ setVCardFromEmailAddress(message, recipientName.trim(), false);
+ if(recipientName.indexOf('<') != -1 && recipientName.indexOf('>') != -1) {
+ if(V) Log.v(TAG, "CC addressing is " + recipientName.substring(recipientName.indexOf('<')+1,
+ recipientName.lastIndexOf('>')));
+ message.addCc(null, recipientName.substring(recipientName.indexOf('<')+1,
+ recipientName.lastIndexOf('>')));
+ } else {
+ message.addCc(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 = null;
+ try {
+ c = mResolver.query(
+ uriAddress,
+ null,
+ where,
+ null, null);
+ } catch (Exception e){
+
+ Log.w(TAG, " EMAIL BODY QUERY FAILDED " + e);
+ }
+ 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)));
+ 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 + " AND " + Message.FLAG_LOADED_SELECTION, 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) && ((ap.getFilterPriority() == 0) ||
+ (ap.getFilterPriority() == 0x02) || (ap.getFilterPriority() == -1))) {
+ 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();
+ BluetoothMapMessageListingElement e = null;
+ msgListingFolder = folder;
+ Log.d(TAG, "msgListingFolder = " + msgListingFolder);
/* We overwrite the parameter mask here if it is 0 or not present, as this
* should cause all parameters to be included in the message list. */
if(ap.getParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
@@ -1184,7 +2203,6 @@ public class BluetoothMapContent {
if (V) Log.w(TAG, "msgListing(): appParameterMask is zero or not present, " +
"changing to: " + ap.getParameterMask());
}
-
/* Cache some info used throughout filtering */
FilterInfo fi = new FilterInfo();
setFilterInfo(fi);
@@ -1214,7 +2232,7 @@ public class BluetoothMapContent {
fi.msgType = FilterInfo.TYPE_MMS;
String where = setWhereFilter(folder, fi, ap);
-
+ where += " AND " + INTERESTED_MESSAGE_TYPE_CLAUSE;
Cursor c = mResolver.query(Mms.CONTENT_URI,
MMS_PROJECTION, where, null, "date DESC");
@@ -1261,6 +2279,7 @@ public class BluetoothMapContent {
if (mmsSelected(fi, ap)) {
fi.msgType = FilterInfo.TYPE_MMS;
String where = setWhereFilter(folder, fi, ap);
+ where += " AND " + INTERESTED_MESSAGE_TYPE_CLAUSE;
Cursor c = mResolver.query(Mms.CONTENT_URI,
MMS_PROJECTION, where, null, "date DESC");
@@ -1343,6 +2362,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);
@@ -1352,14 +2380,44 @@ public class BluetoothMapContent {
case SMS_CDMA:
return getSmsMessage(id, appParams.getCharset());
case MMS:
+ if(appParams.getCharset()== MAP_MESSAGE_CHARSET_NATIVE) {
+ throw new IllegalArgumentException("Invalid Charset: Native for Message Type MMS");
+ }
return getMmsMessage(id, appParams);
case EMAIL:
- throw new IllegalArgumentException("Email not implemented - invalid message handle.");
+ if(appParams.getCharset()== MAP_MESSAGE_CHARSET_NATIVE) {
+ throw new IllegalArgumentException("Invalid Charset: Native for Message Type Email");
+ }
+ 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 = {""};
+ 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) {
+ if (TextUtils.isEmpty(phone)) {
+ return;
+ }
String contactId = null, contactName = null;
String[] phoneNumbers = null;
String[] emailAddresses = null;
@@ -1379,14 +2437,15 @@ public class BluetoothMapContent {
contactId = p.getString(p.getColumnIndex(Contacts._ID));
contactName = p.getString(p.getColumnIndex(Contacts.DISPLAY_NAME));
}
- p.close();
+ if (p != null)
+ p.close();
- // Bail out if we are unable to find a contact, based on the phone number
- if(contactId == null) {
- phoneNumbers = new String[1];
- phoneNumbers[0] = phone;
- }
- else {
+ // Add only original sender's contact number in VCARD.
+ phoneNumbers = new String[1];
+ phoneNumbers[0] = phone;
+ if(contactId != null) {
+ // Do not fetch and add all the contacts in vcard to avoid IOT issues with carkits
+ /*
// Fetch all contact phone numbers
p = mResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
@@ -1402,6 +2461,7 @@ public class BluetoothMapContent {
}
p.close();
}
+ */
// Fetch contact e-mail addresses
p = mResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
@@ -1439,7 +2499,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);
@@ -1494,7 +2554,7 @@ public class BluetoothMapContent {
selection,
null, null);
/* TODO: Change the setVCard...() to return the vCard, and use the name in message.addXxx() */
- if (c.moveToFirst()) {
+ if (c != null && c.moveToFirst()) {
do {
String address = c.getString(c.getColumnIndex("address"));
Integer type = c.getInt(c.getColumnIndex("type"));
@@ -1538,16 +2598,25 @@ public class BluetoothMapContent {
try {
is = mResolver.openInputStream(uriAddress);
int len = 0;
+ if (is != null) {
while ((len = is.read(buffer)) != -1) {
+ if (os != null) {
os.write(buffer, 0, len); // We need to specify the len, as it can be != bufferSize
}
+ }
+ }
+ if (os != null) {
retVal = os.toByteArray();
+
+ }
} catch (IOException e) {
// do nothing for now
Log.w(TAG,"Error reading part data",e);
} finally {
try {
+ if (os !=null)
os.close();
+ if (is!=null)
is.close();
} catch (IOException e) {
}
@@ -1575,7 +2644,7 @@ public class BluetoothMapContent {
selection,
null, null);
- if (c.moveToFirst()) {
+ if (c != null && c.moveToFirst()) {
do {
Long partId = c.getLong(c.getColumnIndex(BaseColumns._ID));
String contentType = c.getString(c.getColumnIndex("ct"));
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentEmailObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentEmailObserver.java
new file mode 100644
index 000000000..42208f634
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapContentEmailObserver.java
@@ -0,0 +1,675 @@
+/*
+* 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.concurrent.ConcurrentHashMap;
+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.os.Message;
+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.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 = Log.isLoggable(BluetoothMapService.LOG_TAG, Log.VERBOSE) ? true : false;
+
+ private HashMap<Long, EmailBox> mEmailBoxList = new HashMap<Long, EmailBox>();
+ private ConcurrentHashMap<Long, EmailMessage> mEmailList = new ConcurrentHashMap<Long, EmailMessage>();
+ /** List of deleted message, do not notify */
+ private ConcurrentHashMap<Long, EmailMessage> mDeletedList = new ConcurrentHashMap<Long, EmailMessage>();
+ private ConcurrentHashMap<Long, EmailMessage> mEmailAddedList = new ConcurrentHashMap<Long, EmailMessage>();
+ /** List of newly deleted message, notify */
+ private ConcurrentHashMap<Long, EmailMessage> mEmailDeletedList = new ConcurrentHashMap<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 Handler mCallback = null;
+ 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, Handler callback ) {
+ super(context);
+ mCallback =callback;
+ }
+
+ private final ContentObserver mEmailAccountObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ if (V) Log.v(TAG, "onChange on thread");
+ if (BluetoothMapUtils.getEmailAccountId(mContext) == -1) {
+ if (mCallback != null) {
+ Message msg = Message.obtain(mCallback);
+ msg.what = BluetoothMapService.MSG_SERVERSESSION_CLOSE;
+ msg.arg1 = 1;
+ msg.sendToTarget();
+ if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
+ }
+ }
+ super.onChange(selfChange);
+ }
+ };
+
+ 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;
+ ConcurrentHashMap<Long, EmailMessage> oldEmailList = mEmailList;
+ ConcurrentHashMap<Long, EmailMessage> emailList = new ConcurrentHashMap<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 id "+id);
+ 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 {
+ if (oldEmailList.get(id) != null) {
+ if(!(msg.mFolderName.equalsIgnoreCase(oldEmailList.
+ get(id).mFolderName))) {
+ Log.d(TAG,"sending Message Shift Event");
+ Event evt;
+ evt = new Event("MessageShift", id, msg.mFolderName,
+ oldEmailList.get(id).mFolderName, TYPE.EMAIL);
+ sendEvent(evt);
+ }
+ }
+ }
+ }
+ } 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 (email.mFolderName.equalsIgnoreCase("trash")) {
+ evt = new Event("MessageDeleted", email.mId, "trash",null, TYPE.EMAIL);
+ sendEvent(evt);
+ } else if (email.mFolderName.equalsIgnoreCase("delete") ||
+ email.mFolderName.equalsIgnoreCase("deleted")) {
+ evt = new Event("MessageDeleted", email.mId, "deleted",null, TYPE.EMAIL);
+ sendEvent(evt);
+ } else {
+ if (mDeletedList.containsKey(email.mId)){
+ Log.d(TAG,"mDeletedList removing id "+email.mId);
+ mDeletedList.remove(email.mId);
+ Log.d(TAG,"sending Message Shift Event");
+ evt = new Event("MessageShift", email.mId, email.mFolderName,
+ "deleted", TYPE.EMAIL);
+ sendEvent(evt);
+ } else if (email.mFolderName.equalsIgnoreCase("inbox")) {
+ 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();
+ }
+ }
+
+ public void onConnect() {
+ if (V) Log.v(TAG, "onConnect() registering email account content observer");
+ try {
+ mResolver.registerContentObserver(
+ EMAIL_ACCOUNT_URI, true, mEmailAccountObserver);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "SQLite exception: " + e);
+ }
+
+ }
+
+ public void onDisconnect() {
+ if (V) Log.v(TAG, "onDisconnect() unregistering email account content observer");
+ try {
+ mResolver.unregisterContentObserver(mEmailAccountObserver);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "SQLite exception: " + e);
+ }
+ }
+
+ @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 = false;
+ long accountId = BluetoothMapUtils.getEmailAccountId(mContext);
+ Uri uri = Uri.parse("content://com.android.email.provider/message/"+handle);
+ Cursor crEmail = mResolver.query(uri, null, null, null, null);
+
+ if(crEmail != null && crEmail.moveToFirst()) {
+ if (D) Log.d(TAG, "setMessageStatusDeleted: EMAIL handle " + handle
+ + " type " + type + " value " + statusValue + "accountId: "+accountId);
+ Intent emailIn = new Intent();
+ addMceInitiatedOperation(Long.toString(handle));
+ if(statusValue == 1){
+ emailIn.setAction("com.android.email.intent.action.MAIL_SERVICE_DELETE_MESSAGE");
+ }else {
+ emailIn.setAction("com.android.email.intent.action.MAIL_SERVICE_MOVE_MESSAGE");
+ emailIn.putExtra("com.android.email.intent.extra.MESSAGE_INFO", Mailbox.TYPE_INBOX);
+ }
+
+ emailIn.putExtra("com.android.email.intent.extra.ACCOUNT", accountId);
+ emailIn.putExtra("com.android.email.intent.extra.MESSAGE_ID", handle);
+ mContext.sendBroadcast(emailIn);
+ res = true;
+ } else {
+ if(V) Log.v(TAG,"Returning from setMessage Status Deleted");
+ }
+ if (crEmail != null) {
+ crEmail.close();
+ }
+ Log.d(TAG, " END setMessageStatusDeleted: EMAIL handle " + handle + " type " + type + " value " + statusValue + "accountId: "+accountId);
+ 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("com.android.email.intent.action.MAIL_SERVICE_MESSAGE_READ");
+ emailIn.putExtra("com.android.email.intent.extra.MESSAGE_INFO",statusValue);
+ emailIn.putExtra("com.android.email.intent.extra.ACCOUNT", accountId);
+ emailIn.putExtra("com.android.email.intent.extra.MESSAGE_ID", handle);
+ mContext.sendBroadcast(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(Long.toString(handle));
+ /* 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("com.android.email.intent.action.MAIL_SERVICE_SEND_PENDING");
+ emailIn.putExtra("com.android.email.intent.extra.ACCOUNT", accountId);
+ mContext.sendBroadcast(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 ");
+ onConnect();
+ }
+ @Override
+ public void deinit() {
+ Log.d(TAG, "deinit ");
+ onDisconnect();
+ }
+
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index 204bb0239..2c4f2cbb3 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 = false;
- private static final boolean V = false;
+ private static final boolean D = BluetoothMapService.DEBUG;
+ private static final boolean V = Log.isLoggable(BluetoothMapService.LOG_TAG, Log.VERBOSE) ? true : false;
- 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,74 @@ 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);
+
+ for (BluetoothMnsMsgHndlMceInitOp op: opList) {
+ if (op.msgHandle.equalsIgnoreCase(msgHandle)){
+ location = opList.indexOf(op);
+ opList.remove(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 +329,37 @@ 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(evt.eventType.equalsIgnoreCase("SendingSuccess")) {
+ if(location != -1) {
+ try {
+ mMnsClient.sendEvent(evt.encode(), mMasId);
+ } catch (UnsupportedEncodingException ex) {
+ Log.w(TAG, ex);
+ }
+ } else {
+ Log.d(TAG, "not sending success event");
+ return;
+ }
+ } else if (location == -1) {
+ try {
+ Log.d(TAG, "sending mns event");
+ mMnsClient.sendEvent(evt.encode(), mMasId);
+ } catch (UnsupportedEncodingException ex) {
+ Log.w(TAG, ex);
+ }
}
}
@@ -360,7 +449,7 @@ public class BluetoothMapContentObserver {
for (Msg msg : mMsgListSms.values()) {
Event evt = new Event("MessageDeleted", msg.id, "deleted",
- folderSms[msg.type], mSmsType);
+ null, mSmsType);
sendEvent(evt);
}
@@ -409,10 +498,13 @@ public class BluetoothMapContentObserver {
sendEvent(evt);
msg.type = type;
- if (folderMms[type].equals("sent")) {
+ // SendingSuccess for MMS ONLY when local initiated
+ int loc = findLocationMceInitiatedOperation(Long.toString(id));
+ if (folderMms[type].equals("sent")&& loc != -1) {
evt = new Event("SendingSuccess", id,
- folderSms[type], null, TYPE.MMS);
+ folderMms[type], null, TYPE.MMS);
sendEvent(evt);
+ removeMceInitiatedOperation(loc);
}
}
msgListMms.put(id, msg);
@@ -423,7 +515,7 @@ public class BluetoothMapContentObserver {
for (Msg msg : mMsgListMms.values()) {
Event evt = new Event("MessageDeleted", msg.id, "deleted",
- folderMms[msg.type], TYPE.MMS);
+ null, TYPE.MMS);
sendEvent(evt);
}
@@ -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,15 +753,15 @@ public class BluetoothMapContentObserver {
{
/* Send message if folder is outbox */
/* to do, support MMS in the future */
- /*
- handle = sendMmsMessage(folder, phone, (BluetoothMapbMessageMmsEmail)msg);
- */
+ String phone = recipient.getFirstPhoneNumber();
+ 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,
@@ -694,6 +785,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;
}
}
@@ -706,6 +804,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) {
/*
@@ -719,6 +821,7 @@ public class BluetoothMapContentObserver {
*else if folder !outbox:
*1) push message to folder
* */
+ if (folder != null && (folder.equalsIgnoreCase("outbox")|| folder.equalsIgnoreCase("drafts") || folder.equalsIgnoreCase("draft"))) {
long handle = pushMmsToFolder(Mms.MESSAGE_BOX_DRAFTS, to_address, msg);
/* 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")) {
@@ -727,8 +830,14 @@ public class BluetoothMapContentObserver {
Intent sendIntent = new Intent("android.intent.action.MMS_SEND_OUTBOX_MSG");
Log.d(TAG, "broadcasting intent: "+sendIntent.toString());
mContext.sendBroadcast(sendIntent);
+ addMceInitiatedOperation(Long.toString(handle));
}
return handle;
+ } else {
+ /* not allowed to push mms to anything but outbox/drafts */
+ throw new IllegalArgumentException("Cannot push message to other folders than outbox/drafts");
+ }
+
}
@@ -749,6 +858,7 @@ public class BluetoothMapContentObserver {
Log.d(TAG, "moved draft MMS to outbox");
}
queryResult.close();
+ addMceInitiatedOperation(Long.toString(handle));
}else {
Log.d(TAG, "Could not move draft to outbox ");
}
@@ -801,70 +911,70 @@ public class BluetoothMapContentObserver {
}
long handle = Long.parseLong(uri.getLastPathSegment());
- if (V){
+ ArrayList<MimePart> parts = msg.getMimeParts();
+ if (parts != null) {
Log.v(TAG, " NEW URI " + uri.toString());
- }
- try {
- if(V) Log.v(TAG, "Adding " + msg.getMimeParts().size() + " parts to the data base.");
- for(MimePart part : msg.getMimeParts()) {
- int count = 0;
- count++;
- values.clear();
- if(part.contentType != null && part.contentType.toUpperCase().contains("TEXT")) {
- values.put("ct", "text/plain");
- values.put("chset", 106);
- if(part.partName != null) {
- values.put("fn", part.partName);
- values.put("name", part.partName);
- } else if(part.contentId == null && part.contentLocation == null) {
- /* We must set at least one part identifier */
- values.put("fn", "text_" + count +".txt");
- values.put("name", "text_" + count +".txt");
- }
- if(part.contentId != null) {
- values.put("cid", part.contentId);
+ try {
+ for(MimePart part : parts) {
+ int count = 0;
+ count++;
+ values.clear();
+ if(part.contentType != null &&
+ part.contentType.toUpperCase().contains("TEXT")) {
+ values.put("ct", "text/plain");
+ values.put("chset", 106);
+ if(part.partName != null) {
+ values.put("fn", part.partName);
+ values.put("name", part.partName);
+ } else if(part.contentId == null && part.contentLocation == null) {
+ /* We must set at least one part identifier */
+ values.put("fn", "text_" + count +".txt");
+ values.put("name", "text_" + count +".txt");
+ }
+ if(part.contentId != null) {
+ values.put("cid", part.contentId);
+ }
+ if(part.contentLocation != null)
+ values.put("cl", part.contentLocation);
+ if(part.contentDisposition != null)
+ values.put("cd", part.contentDisposition);
+ values.put("text", new String(part.data, "UTF-8"));
+ uri = Uri.parse("content://mms/" + handle + "/part");
+ uri = cr.insert(uri, values);
+ if(V) Log.v(TAG, "Added TEXT part");
+ } else if (part.contentType != null &&
+ part.contentType.toUpperCase().contains("SMIL")) {
+ values.put("seq", -1);
+ values.put("ct", "application/smil");
+ if(part.contentId != null)
+ values.put("cid", part.contentId);
+ if(part.contentLocation != null)
+ values.put("cl", part.contentLocation);
+ if(part.contentDisposition != null)
+ values.put("cd", part.contentDisposition);
+ values.put("fn", "smil.xml");
+ values.put("name", "smil.xml");
+ values.put("text", new String(part.data, "UTF-8"));
+
+ uri = Uri.parse("content://mms/" + handle + "/part");
+ uri = cr.insert(uri, values);
+ if(V) Log.v(TAG, "Added SMIL part");
+ } else /*VIDEO/AUDIO/IMAGE*/ {
+ writeMmsDataPart(handle, part, count);
+ if(V) Log.v(TAG, "Added OTHER part");
+ }
+ if (uri != null && V){
+ Log.v(TAG, "Added part with content-type: "+ part.contentType +
+ " to Uri: " + uri.toString());
+ }
}
- if(part.contentLocation != null)
- values.put("cl", part.contentLocation);
- if(part.contentDisposition != null)
- values.put("cd", part.contentDisposition);
- values.put("text", new String(part.data, "UTF-8"));
- uri = Uri.parse("content://mms/" + handle + "/part");
- uri = cr.insert(uri, values);
- if(V) Log.v(TAG, "Added TEXT part");
-
- } else if (part.contentType != null && part.contentType.toUpperCase().contains("SMIL")){
-
- values.put("seq", -1);
- values.put("ct", "application/smil");
- if(part.contentId != null)
- values.put("cid", part.contentId);
- if(part.contentLocation != null)
- values.put("cl", part.contentLocation);
- if(part.contentDisposition != null)
- values.put("cd", part.contentDisposition);
- values.put("fn", "smil.xml");
- values.put("name", "smil.xml");
- values.put("text", new String(part.data, "UTF-8"));
-
- uri = Uri.parse("content://mms/" + handle + "/part");
- uri = cr.insert(uri, values);
- if(V) Log.v(TAG, "Added SMIL part");
-
- }else /*VIDEO/AUDIO/IMAGE*/ {
- writeMmsDataPart(handle, part, count);
- if(V) Log.v(TAG, "Added OTHER part");
- }
- if (uri != null && V){
- Log.v(TAG, "Added part with content-type: "+ part.contentType + " to Uri: " + uri.toString());
+ addMceInitiatedOperation("+");
+ } catch (UnsupportedEncodingException e) {
+ Log.w(TAG, e);
+ } catch (IOException e) {
+ Log.w(TAG, e);
}
}
- } catch (UnsupportedEncodingException e) {
- Log.w(TAG, e);
- } catch (IOException e) {
- Log.w(TAG, e);
- }
-
values.clear();
values.put("contact_id", "null");
values.put("address", "insert-address-token");
@@ -1078,7 +1188,7 @@ public class BluetoothMapContentObserver {
Cursor cursor = mResolver.query(msgInfo.uri, ID_PROJECTION, null, null, null);
try {
- if (cursor.moveToFirst()) {
+ if (cursor != null && cursor.moveToFirst()) {
int messageId = cursor.getInt(0);
Uri updateUri = ContentUris.withAppendedId(UPDATE_STATUS_URI, messageId);
@@ -1096,7 +1206,8 @@ public class BluetoothMapContentObserver {
Log.d(TAG, "Can't find message for status update: " + messageUri);
}
} finally {
- cursor.close();
+ if (cursor != null)
+ cursor.close();
}
if (status == 0) {
@@ -1170,7 +1281,8 @@ public class BluetoothMapContentObserver {
"thread_id = " + DELETED_THREAD_ID, null);
}
- private PhoneStateListener mPhoneListener = new PhoneStateListener() {
+ private PhoneStateListener mPhoneListener = new PhoneStateListener (Long.MAX_VALUE - 1,
+ Looper.getMainLooper()) {
@Override
public void onServiceStateChanged(ServiceState serviceState) {
Log.d(TAG, "Phone service state change: " + serviceState.getState());
diff --git a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
index d3909ddad..ded4b8876 100644
--- a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
@@ -19,6 +19,12 @@ import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
+import android.util.Xml;
+import java.util.List;
+import java.nio.charset.Charset;
+import com.android.internal.util.FastXmlSerializer;
import org.xmlpull.v1.XmlSerializer;
import android.util.Xml;
@@ -30,7 +36,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;
@@ -87,7 +93,7 @@ public class BluetoothMapFolderElement {
*/
public BluetoothMapFolderElement getSubFolder(String folderName){
for(BluetoothMapFolderElement subFolder : subFolders){
- if(subFolder.getName().equals(folderName))
+ if(subFolder.getName().equalsIgnoreCase(folderName))
return subFolder;
}
return null;
@@ -95,7 +101,7 @@ public class BluetoothMapFolderElement {
public byte[] encode(int offset, int count) throws UnsupportedEncodingException {
StringWriter sw = new StringWriter();
- XmlSerializer xmlMsgElement = Xml.newSerializer();
+ XmlSerializer xmlMsgElement = new FastXmlSerializer();
int i, stopIndex;
if(offset > subFolders.size())
throw new IllegalArgumentException("FolderListingEncode: offset > subFolders.size()");
@@ -106,17 +112,17 @@ public class BluetoothMapFolderElement {
try {
xmlMsgElement.setOutput(sw);
- xmlMsgElement.startDocument(null, null);
- xmlMsgElement.text("\n");
- xmlMsgElement.startTag("", "folder-listing");
- xmlMsgElement.attribute("", "version", "1.0");
+ xmlMsgElement.startDocument("UTF-8", true);
+ xmlMsgElement.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ xmlMsgElement.startTag(null, "folder-listing");
+ xmlMsgElement.attribute(null, "version", "1.0");
for(i = offset; i<stopIndex; i++)
{
- xmlMsgElement.startTag("", "folder");
- xmlMsgElement.attribute("", "name", subFolders.get(i).getName());
- xmlMsgElement.endTag("", "folder");
+ xmlMsgElement.startTag(null, "folder");
+ xmlMsgElement.attribute(null, "name", subFolders.get(i).getName());
+ xmlMsgElement.endTag(null, "folder");
}
- xmlMsgElement.endTag("", "folder-listing");
+ xmlMsgElement.endTag(null, "folder-listing");
xmlMsgElement.endDocument();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
index eeda447ba..c81ad69e0 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
@@ -21,7 +21,10 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.android.internal.util.FastXmlSerializer;
-
+import java.io.UnsupportedEncodingException;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
import org.xmlpull.v1.XmlSerializer;
import android.util.Log;
@@ -37,6 +40,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"))
{
@@ -51,8 +55,10 @@ 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");
return 0;
}
@@ -74,28 +80,65 @@ public class BluetoothMapMessageListing {
* if UTF-8 encoding is unsupported on the platform.
*/
public byte[] encode() throws UnsupportedEncodingException {
- StringWriter sw = new StringWriter();
+ Log.d(TAG, "encoding to UTF-8 format");
XmlSerializer xmlMsgElement = new FastXmlSerializer();
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ OutputStreamWriter myOutputStreamWriter = null;
try {
- xmlMsgElement.setOutput(sw);
+ myOutputStreamWriter = new OutputStreamWriter(outputStream, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, "Failed to encode: charset=" + "UTF-8");
+ return null;
+ }
+
+ try {
+ String str1;
+ String str2 = "<?xml version=\"1.0\"?>";
+ xmlMsgElement.setOutput(myOutputStreamWriter);
xmlMsgElement.startDocument("UTF-8", true);
xmlMsgElement.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ xmlMsgElement.text("\n");
xmlMsgElement.startTag(null, "MAP-msg-listing");
xmlMsgElement.attribute(null, "version", "1.0");
// Do the XML encoding of list
+ if(list != null) {
for (BluetoothMapMessageListingElement element : list) {
+ try {
element.encode(xmlMsgElement); // Append the list element
- }
- xmlMsgElement.endTag(null, "MAP-msg-listing");
- xmlMsgElement.endDocument();
} catch (IllegalArgumentException e) {
+ xmlMsgElement.endTag(null, "msg");
Log.w(TAG, e.toString());
} catch (IllegalStateException e) {
Log.w(TAG, e.toString());
} catch (IOException e) {
Log.w(TAG, e.toString());
}
- return sw.toString().getBytes("UTF-8");
+ }
+ }
+ xmlMsgElement.endTag(null, "MAP-msg-listing");
+ xmlMsgElement.endDocument();
+
+ try {
+ str1 = outputStream.toString("UTF-8");
+ Log.v(TAG, "Printing XML-Converted String: " + str1);
+ int line1 = 0;
+ line1 = str1.indexOf("\n");
+ str2 += str1.substring(line1 + 1);
+ if (list.size() > 0) {
+ int indxHandle = str2.indexOf("msg handle");
+ str1 = "<" + str2.substring(indxHandle);
+ str2 = str2.substring(0, (indxHandle - 1)) + str1;
+ }
+
+ return str2.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, "Failed to encode: charset=" + "UTF-8");
+ return null;
+ }
+ } catch (IOException e) {
+ Log.e(TAG, e.toString());
+ return null;
+ }
}
public void sort() {
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
index 03b20009b..d3d892e1c 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
@@ -21,7 +21,16 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import org.xmlpull.v1.XmlSerializer;
-
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
+import android.util.Xml;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.ByteArrayInputStream;
+import java.io.BufferedReader;
+import java.io.StringWriter;
+import java.util.List;
+import java.nio.charset.Charset;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import android.util.Xml;
@@ -32,8 +41,8 @@ public class BluetoothMapMessageListingElement
implements Comparable<BluetoothMapMessageListingElement> {
private static final String TAG = "BluetoothMapMessageListingElement";
- private static final boolean D = false;
- private static final boolean V = false;
+ private static final boolean D = BluetoothMapService.DEBUG;
+ private static final boolean V = Log.isLoggable(BluetoothMapService.LOG_TAG, Log.VERBOSE) ? true : false;
private long cpHandle = 0; /* The content provider handle - without type information */
private String mapHandle = null; /* The map hex-string handle with type information */
@@ -97,6 +106,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,16 +231,33 @@ 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(null, "msg");
xmlMsgElement.attribute(null, "handle", mapHandle);
- if(subject != null)
- xmlMsgElement.attribute(null, "subject", subject);
+ if(subject != null) {
+ InputStreamReader isr = new InputStreamReader(new ByteArrayInputStream(subject.getBytes(Charset.forName("UTF-8"))), Charset.forName("UTF-8"));
+ BufferedReader reader = new BufferedReader(isr);
+ StringBuilder sb = new StringBuilder();
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line);
+ }
+ xmlMsgElement.attribute(null, "subject", sb.toString());
+ }
+
if(dateTime != 0)
xmlMsgElement.attribute(null, "datetime", this.getDateTimeString());
- if(senderName != null)
- xmlMsgElement.attribute(null, "sender_name", senderName);
+ if(senderName != null) {
+ InputStreamReader isr = new InputStreamReader(new ByteArrayInputStream(senderName.getBytes(Charset.forName("UTF-8"))), Charset.forName("UTF-8"));
+ BufferedReader reader = new BufferedReader(isr);
+ StringBuilder sb = new StringBuilder();
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line);
+ }
+ xmlMsgElement.attribute(null, "sender_name", sb.toString());
+ }
if(senderAddressing != null)
xmlMsgElement.attribute(null, "sender_addressing", senderAddressing);
if(replytoAddressing != null)
@@ -255,6 +285,7 @@ public class BluetoothMapMessageListingElement
if(protect != null)
xmlMsgElement.attribute(null, "protected", protect);
xmlMsgElement.endTag(null, "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 6eaf55896..040b4b646 100644..100755
--- a/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -32,13 +32,16 @@ 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 {
private static final String TAG = "BluetoothMapObexServer";
private static final boolean D = BluetoothMapService.DEBUG;
- private static final boolean V = BluetoothMapService.VERBOSE;
+ private static final boolean V = Log.isLoggable(BluetoothMapService.LOG_TAG, Log.VERBOSE) ? true : false;
private static final int UUID_LENGTH = 16;
@@ -66,21 +69,55 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
private Context mContext;
+ 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.
*/
@@ -96,6 +133,21 @@ 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
+ tmpFolder.addFolder("inbox"); // root/telecom/msg/inbox
+ tmpFolder.addFolder("outbox");
+ tmpFolder.addFolder("sent");
+ tmpFolder.addFolder("drafts");
+ }
+
@Override
public int onConnect(final HeaderSet request, HeaderSet reply) {
if (D) Log.d(TAG, "onConnect():");
@@ -118,6 +170,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());
@@ -194,7 +250,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());
@@ -223,7 +284,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");
@@ -239,11 +300,11 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
}
try {
- if(folderName == null || folderName.trim().isEmpty()) {
+ if(folderName == null || folderName.equals("")) {
folderName = mCurrentFolder.getName();
}
- folderName = folderName.toLowerCase();
- 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;
}
@@ -256,7 +317,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.
}
@@ -296,7 +357,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.
}
@@ -324,6 +385,7 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
@Override
public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
final boolean create) {
+ if (D) Log.d(TAG, "onSetPath():");
String folderName;
BluetoothMapFolderElement folder;
notifyUpdateWakeLock();
@@ -350,21 +412,30 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
mCurrentFolder = mCurrentFolder.getRoot();
}
else {
+ if(folderName.equalsIgnoreCase("draft") && mMasId ==1) {
+ folderName="Drafts";
+ }
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;
}
@Override
public void onClose() {
+ if (V) Log.v(TAG, "BluetoothMapObexServer: onClose");
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.");
}
@@ -372,6 +443,7 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
@Override
public int onGet(Operation op) {
+ if (V) Log.v(TAG, "BluetoothMapObexServer: onGet");
notifyUpdateWakeLock();
sIsAborted = false;
HeaderSet request;
@@ -459,7 +531,23 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
BluetoothMapMessageListing outList;
if(folderName == null || folderName.length() == 0 ) {
folderName = mCurrentFolder.getName();
+ } else if(folderName.equalsIgnoreCase("draft") && mMasId ==1) {
+ folderName="Drafts";
+ }
+ 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(mMasId == 0 && (folderName.equalsIgnoreCase("root") ||
+ folderName.equalsIgnoreCase("telecom") ||
+ folderName.equalsIgnoreCase("msg"))) {
+ Log.e(TAG, "messagelisting for invalid folder");
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
+
if(appParams == null){
appParams = new BluetoothMapAppParams();
appParams.setMaxListCount(1024);
@@ -478,15 +566,23 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
appParams.setStartOffset(0);
if(appParams.getMaxListCount() != 0) {
+ 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 {
+ 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);
Log.d(TAG, "not setting body and end of body header");
op.noBodyHeader();
@@ -563,10 +659,15 @@ 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;
+ ArrayList<BluetoothMapFolderElement> tempSubFolders = new ArrayList<BluetoothMapFolderElement>();
if(appParams == null){
appParams = new BluetoothMapAppParams();
appParams.setMaxListCount(1024);
@@ -584,6 +685,58 @@ 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]);
+ }
+ }
+ }
+
+ tempSubFolders.clear();
+ for (BluetoothMapFolderElement fold : mCurrentFolder.subFolders) {
+ tempSubFolders.add(fold);
+ }
+
+ if(!(mCurrentFolder.getName().equals("root") ||
+ mCurrentFolder.getName().equals("telecom"))) {
+
+ for (int i = 0; i < tempSubFolders.size(); i ++) {
+ if(!(finalList.contains(tempSubFolders.get(i).getName()))) {
+ if (V) Log.v(TAG, " removing : "+ tempSubFolders.get(i).getName());
+ mCurrentFolder.subFolders.remove(tempSubFolders.get(i));
+ }
+ }
+
+ 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) {
outBytes = mCurrentFolder.encode(listStartOffset, maxListCount);
@@ -655,6 +808,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);
@@ -664,7 +818,9 @@ public class BluetoothMapObexServer extends ServerRequestHandler {
Log.w(TAG,"sendGetMessageRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
} catch (IllegalArgumentException e) {
- Log.w(TAG,"sendGetMessageRsp: IllegalArgumentException (e.g. invalid handle) - sending OBEX_HTTP_BAD_REQUEST", e);
+ Log.w(TAG,
+ "sendGetMessageRsp: IllegalArgumentException (e.g. invalid handle or charset) - sending OBEX_HTTP_BAD_REQUEST"
+ , e);
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
index ba2f9a55b..f3e7049df 100644..100755
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.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
@@ -19,6 +20,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import java.util.HashMap;
import android.app.AlarmManager;
import javax.btobex.ServerSession;
@@ -54,6 +56,7 @@ import com.android.bluetooth.btservice.ProfileService;
public class BluetoothMapService extends ProfileService {
private static final String TAG = "BluetoothMapService";
+ public static final String LOG_TAG = "BluetoothMap";
/**
* To enable MAP DEBUG/VERBOSE logging - run below cmd in adb shell, and
@@ -64,7 +67,7 @@ public class BluetoothMapService extends ProfileService {
public static final boolean DEBUG = true;
- public static final boolean VERBOSE = false;
+ public static boolean VERBOSE;
/**
* Intent indicating incoming obex authentication request which is from
@@ -116,21 +119,15 @@ public class BluetoothMapService extends ProfileService {
private BluetoothAdapter mAdapter;
- private SocketAcceptThread mAcceptThread = null;
-
private BluetoothMapAuthenticator mAuth = null;
- private BluetoothMapObexServer mMapServer;
-
private ServerSession mServerSession = null;
private BluetoothMnsObexClient mBluetoothMnsObexClient = null;
- private BluetoothServerSocket mServerSocket = null;
-
private BluetoothSocket mConnSocket = null;
- private BluetoothDevice mRemoteDevice = null;
+ private static BluetoothDevice mRemoteDevice = null;
private static String sRemoteDeviceName = null;
@@ -145,305 +142,37 @@ public class BluetoothMapService extends ProfileService {
private boolean isWaitingAuthorization = false;
private boolean removeTimeoutMsg = false;
+ // package and class name to which we send intent to check message access access permission
+ private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
+ private static final String ACCESS_AUTHORITY_CLASS =
+ "com.android.settings.bluetooth.BluetoothPermissionRequest";
+
private static final ParcelUuid[] MAP_UUIDS = {
BluetoothUuid.MAP,
BluetoothUuid.MNS,
};
- public BluetoothMapService() {
- mState = BluetoothMap.STATE_DISCONNECTED;
- }
-
- private void startRfcommSocketListener() {
- if (DEBUG) Log.d(TAG, "Map Service startRfcommSocketListener");
-
- 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 ");
+ 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;
- } else {
- Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
- }
- return initSocketOK;
- }
+ 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};
- 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 (mWakeLock != null) {
- mWakeLock.release();
- mWakeLock = null;
- }
-
- 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");
-
- // 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();
- }
-
- mBluetoothMnsObexClient = new BluetoothMnsObexClient(this, mRemoteDevice,
- mSessionStatusHandler);
- 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);
-
- mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
- mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
- .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
-
- if (VERBOSE) {
- Log.v(TAG, "startObexServerSession() success!");
- }
- }
-
- private void stopObexServerSession() {
- if (DEBUG) Log.d(TAG, "MAP Service stopObexServerSession");
-
- mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK);
- mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
-
- // Release the wake lock if obex transaction is over
- if (mWakeLock != null) {
- mWakeLock.release();
- mWakeLock = null;
- }
-
- 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);
- }
- 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();
- } catch (IOException ex) {
- Log.e(TAG, "catch exception starting obex server session"
- + ex.toString());
- }
- } else {
- Intent intent = new
- Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
- intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
- BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
-
- isWaitingAuthorization = true;
- sendOrderedBroadcast(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() {
@@ -454,7 +183,7 @@ public class BluetoothMapService extends ProfileService {
switch (msg.what) {
case START_LISTENER:
if (mAdapter.isEnabled()) {
- startRfcommSocketListener();
+ mConnectionManager.startAll();
}
break;
case USER_TIMEOUT:
@@ -465,10 +194,11 @@ public class BluetoothMapService extends ProfileService {
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;
@@ -510,7 +240,7 @@ public class BluetoothMapService extends ProfileService {
return mState;
}
- public BluetoothDevice getRemoteDevice() {
+ public static BluetoothDevice getRemoteDevice() {
return mRemoteDevice;
}
private void setState(int state) {
@@ -551,16 +281,9 @@ 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();
-
+ //do no call close service else map service will close
+ //closeService();
+ mConnectionManager.stopObexServerSessionAll();
setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
result = true;
break;
@@ -586,6 +309,7 @@ public class BluetoothMapService extends ProfileService {
Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
int connectionState;
synchronized (this) {
+ if (bondedDevices != null) {
for (BluetoothDevice device : bondedDevices) {
ParcelUuid[] featureUuids = device.getUuids();
if (!BluetoothUuid.containsAnyUuid(featureUuids, MAP_UUIDS)) {
@@ -599,6 +323,7 @@ public class BluetoothMapService extends ProfileService {
}
}
}
+ }
return deviceList;
}
@@ -629,12 +354,15 @@ public class BluetoothMapService extends ProfileService {
@Override
protected IProfileServiceBinder initBinder() {
+ Log.d(TAG, "Inside initBinder");
return new BluetoothMapBinder(this);
}
@Override
protected boolean start() {
if (DEBUG) Log.d(TAG, "start()");
+ VERBOSE = Log.isLoggable(LOG_TAG, Log.VERBOSE) ? true : false;
+ if (VERBOSE) Log.v(TAG, "verbose logging is enabled");
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
@@ -644,11 +372,15 @@ public class BluetoothMapService extends ProfileService {
} catch (Exception e) {
Log.w(TAG,"Unable to register map receiver",e);
}
- mInterrupted = false;
+ mConnectionManager.init();
mAdapter = BluetoothAdapter.getDefaultAdapter();
// start RFCOMM listener
+ if(mAdapter ==null) {
+ Log.w(TAG,"Local BT device is not enabled");
+ } else {
mSessionStatusHandler.sendMessage(mSessionStatusHandler
.obtainMessage(START_LISTENER));
+ }
return true;
}
@@ -673,6 +405,479 @@ public class BluetoothMapService extends ProfileService {
return true;
}
+ class BluetoothMapObexConnectionManager {
+ private ArrayList<BluetoothMapObexConnection> mConnections =
+ new ArrayList<BluetoothMapObexConnection>();
+ private HashMap<Integer, String> MapClientList = new HashMap<Integer, String>();
+
+ 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 ));
+ MapClientList.put(i, null);
+ }
+ }
+
+ 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 void addToMapClientList(String remoteAddr, int masId) {
+ Log.d(TAG,"Adding to mapClient List masid "+masId+" bdaddr "+remoteAddr);
+ MapClientList.put(masId, remoteAddr);
+ }
+
+ public void removeFromMapClientList(int masId) {
+ Log.d(TAG,"Removing from the list, masid "+masId);
+ MapClientList.put(masId, null);
+ }
+
+ public boolean isAllowedConnection(BluetoothDevice remoteDevice, int masId) {
+ String remoteAddress = remoteDevice.getAddress();
+ if (remoteAddress == null) {
+ if (VERBOSE) Log.v(TAG, "Connection request from unknown device");
+ return false;
+ }
+ if(MapClientList.get(masId)==null) {
+ if(MapClientList.get((masId^1)) == null) {
+ if (VERBOSE) Log.v(TAG, "Allow Connection request from " +remoteAddress
+ + "when no other device is connected");
+ return true;
+ } else if(MapClientList.get((masId^1)).equalsIgnoreCase(remoteAddress)) {
+ Log.d(TAG, "Allow Connection request from " +remoteAddress);
+ Log.d(TAG, "when mas" +(masId^1) +"is connected to " +MapClientList.get((masId^1)));
+ return true;
+ } else {
+ Log.d(TAG, "Dont Allow Connection request from " +remoteAddress
+ + "when mas" +(masId^1) +"is connected to" +MapClientList.get((masId^1)));
+ return false;
+ }
+ }
+ Log.d(TAG,"connection not allowed from " + remoteAddress);
+ return false;
+ }
+ }
+
+ 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;
+ mInterrupted = false;
+
+ // 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.listenUsingRfcommWithServiceRecord("Email Message Access",BluetoothUuid.MAS.getUuid());
+ else
+ mServerSocket = mAdapter.listenUsingRfcommWithServiceRecord("SMS/MMS Message Access", BluetoothUuid.MAS.getUuid());
+ initSocketOK = true;
+ } 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, "initRfcommSocket failed as BT is (being) turned off");
+ break;
+ }
+
+ 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 (VERBOSE) {
+ Log.v(TAG, "Close Server Socket : " );
+ }
+ 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();
+ if(VERBOSE) Log.d(TAG, "after getting application context");
+ if(mBluetoothMnsObexClient == null)
+ mBluetoothMnsObexClient = new BluetoothMnsObexClient(context, mRemoteDevice);
+ mBluetoothMnsObexClient.initObserver(mSessionStatusHandler, 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);
+ mConnectionManager.removeFromMapClientList(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;
+ }
+ }
+
+ mConnectionManager.removeFromMapClientList(mMasId);
+ 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);
+ }
+ if (!mConnectionManager.isAllowedConnection(mRemoteDevice,mMasId)) {
+ mConnSocket.close();
+ mConnSocket = null;
+ continue;
+ }
+
+ mConnectionManager.addToMapClientList(mRemoteDevice.getAddress(), mMasId);
+
+ mConnectionManager.setWaitingForConfirmation(mMasId);
+ isWaitingAuthorization = true;
+ 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);
+
+ 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 (DEBUG) Log.v(TAG, "Accept exception: " + ex.toString());
+ }
+ }
+ }
+
+ void shutdown() {
+ stopped = true;
+ interrupt();
+ }
+ }
+ };
+
private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver();
private class MapBroadcastReceiver extends BroadcastReceiver {
@@ -680,6 +885,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);
@@ -697,14 +903,12 @@ public class BluetoothMapService extends ProfileService {
sendBroadcast(timeoutIntent, BLUETOOTH_PERM);
isWaitingAuthorization = false;
removeTimeoutMsg = false;
- stopObexServerSession();
}
// Release all resources
closeService();
} else if (state == BluetoothAdapter.STATE_ON) {
if (DEBUG) Log.d(TAG, "STATE_ON");
- mInterrupted = false;
// start RFCOMM listener
mSessionStatusHandler.sendMessage(mSessionStatusHandler
.obtainMessage(START_LISTENER));
@@ -730,22 +934,16 @@ public class BluetoothMapService extends ProfileService {
BluetoothDevice.CONNECTION_ACCESS_NO) ==
BluetoothDevice.CONNECTION_ACCESS_YES) {
//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();
}
- try {
- if (mConnSocket != null) {
- // start obex server and rfcomm connection
- startObexServerSession();
+ if (DEBUG) Log.d(TAG, "calling initiateObexServerSession");
+ mConnectionManager.initiateObexServerSession(mRemoteDevice);
+
} else {
- stopObexServerSession();
- }
- } catch (IOException ex) {
- Log.e(TAG, "Caught the error: " + ex.toString());
- }
- } else {
- stopObexServerSession();
+ Log.d(TAG, "calling stopObexServerSessionWaiting");
+ mConnectionManager.stopObexServerSessionWaiting();
}
} else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) &&
isWaitingAuthorization) {
@@ -770,7 +968,7 @@ public class BluetoothMapService extends ProfileService {
sendBroadcast(timeoutIntent, BLUETOOTH_PERM);
isWaitingAuthorization = false;
removeTimeoutMsg = false;
- stopObexServerSession();
+ mConnectionManager.stopObexServerSessionWaiting();
}
}
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java b/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java
index 2070c2693..caf8b02e3 100644
--- a/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java
+++ b/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java
@@ -46,7 +46,7 @@ import com.android.internal.telephony.gsm.SmsMessage.SubmitPdu;
public class BluetoothMapSmsPdu {
private static final String TAG = "BluetoothMapSmsPdu";
- private static final boolean V = false;
+ private static final boolean V = Log.isLoggable(BluetoothMapService.LOG_TAG, Log.VERBOSE) ? true : false;
private static int INVALID_VALUE = -1;
public static int SMS_TYPE_GSM = 1;
public static int SMS_TYPE_CDMA = 2;
@@ -283,12 +283,12 @@ public class BluetoothMapSmsPdu {
}
private int gsmSubmitGetTpUdlOffset() {
- switch(((data[0] & 0xff) & (0x08 | 0x04))>>2) {
+ switch(((data[0] & 0xff) & (0x10 | 0x08))>>3) {
case 0: // Not TP-VP present
return gsmSubmitGetTpPidOffset() + 2;
case 1: // TP-VP relative format
- return gsmSubmitGetTpPidOffset() + 2 + 1;
case 2: // TP-VP enhanced format
+ return gsmSubmitGetTpPidOffset() + 2 + 1;
case 3: // TP-VP absolute format
break;
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapUtils.java b/src/com/android/bluetooth/map/BluetoothMapUtils.java
index e57cf16b1..79ea22259 100644..100755
--- 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
@@ -21,7 +33,7 @@ package com.android.bluetooth.map;
public class BluetoothMapUtils {
private static final String TAG = "MapUtils";
- private static final boolean V = BluetoothMapService.VERBOSE;
+ private static final boolean V = Log.isLoggable(BluetoothMapService.LOG_TAG, Log.VERBOSE) ? true : false;
/* We use the upper 5 bits for the type mask - avoid using the top bit, since it
* indicates a negative value, hence corrupting the formatter when converting to
* type String. (I really miss the unsigned type in Java:))
@@ -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 de3de663e..152439496 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 = false;
- protected static final boolean V = false;
+ protected static final boolean D = BluetoothMapService.DEBUG;
+ protected static final boolean V = Log.isLoggable(BluetoothMapService.LOG_TAG, Log.VERBOSE) ? true : false;
private static final String VERSION = "VERSION:1.0";
public static int INVALID_VALUE = -1;
@@ -152,28 +152,34 @@ 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;
}
- public void encode(StringBuilder sb)
+ public void encode(StringBuilder sb) throws UnsupportedEncodingException
{
sb.append("BEGIN:VCARD").append("\r\n");
sb.append("VERSION:").append(version).append("\r\n");
if(version.equals("3.0") && formattedName != null)
{
- sb.append("FN:").append(formattedName).append("\r\n");
+ sb.append("FN:").append(new String(formattedName.getBytes("UTF-8"),"UTF-8")).append("\r\n");
}
if (name != null)
- sb.append("N:").append(name).append("\r\n");
+ sb.append("N:").append(new String(name.getBytes("UTF-8"),"UTF-8")).append("\r\n");
for(String phoneNumber : phoneNumbers)
{
sb.append("TEL:").append(phoneNumber).append("\r\n");
}
for(String emailAddress : emailAddresses)
{
- sb.append("EMAIL:").append(emailAddress).append("\r\n");
+ sb.append("EMAIL:").append(new String(emailAddress.getBytes("UTF-8"),"UTF-8")).append("\r\n");
}
sb.append("END:VCARD").append("\r\n");
}
@@ -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.
*/
@@ -365,6 +417,19 @@ public abstract class BluetoothMapbMessage {
}
return data;
}
+
+ public String getStringTerminator(String terminator) {
+ 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();
+ }
};
public BluetoothMapbMessage(){
@@ -548,6 +613,11 @@ public abstract class BluetoothMapbMessage {
}
if(line.contains("BEGIN:BBODY")){
if(D) Log.d(TAG,"Decoding bbody");
+
+ if(type == TYPE.EMAIL){
+ //TODO: Support Attachments also.
+ parseBodyEmail(reader.getStringTerminator("END:BBODY"));
+ } else
parseBody(reader);
}
}
@@ -614,23 +684,17 @@ public abstract class BluetoothMapbMessage {
* Since errata ???(bluetooth.org is down at the moment) introduced escaping of END:MSG
* in the actual message content, it is now safe to use the END:MSG tag as terminator,
* and simply ignore the length field.*/
- byte[] rawData = reader.getDataBytes(bMsgLength - (line.getBytes().length + 2)); // 2 added to compensate for the removed \r\n
- String data;
- try {
- data = new String(rawData, "UTF-8");
+ //byte[] rawData = reader.getDataBytes(bMsgLength - (line.getBytes().length + 2)); // 2 added to compensate for the removed \r\n
+ String data = reader.getStringTerminator("END:MSG");
if(V) {
Log.v(TAG,"MsgLength: " + bMsgLength);
- Log.v(TAG,"line.getBytes().length: " + line.getBytes().length);
+ Log.v(TAG,"data.getBytes().length: " + data.getBytes().length);
String debug = line.replaceAll("\\n", "<LF>\n");
debug = debug.replaceAll("\\r", "<CR>");
Log.v(TAG,"The line: \"" + debug + "\"");
debug = data.replaceAll("\\n", "<LF>\n");
debug = debug.replaceAll("\\r", "<CR>");
- Log.v(TAG,"The msgString: \"" + debug + "\"");
- }
- } catch (UnsupportedEncodingException e) {
- Log.w(TAG,e);
- throw new IllegalArgumentException("Unable to convert to UTF-8");
+ Log.v(TAG,"The msgString: \"" + data + "\"");
}
/* Decoding of MSG:
* 1) split on "\r\nEND:MSG\r\n"
@@ -662,6 +726,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 0fcba3bbe..abc57829d 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java
@@ -27,8 +27,15 @@ 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 = Log.isLoggable(BluetoothMapService.LOG_TAG, Log.VERBOSE) ? true : false;
+ 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,7 +86,13 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
public void encodePlainText(StringBuilder sb) throws UnsupportedEncodingException {
if(contentType != null && contentType.toUpperCase().contains("TEXT")) {
+ if(data != null) {
+ 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. */
} else {
@@ -94,6 +107,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 +158,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;
}
@@ -218,6 +239,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 +261,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")) {
@@ -248,6 +277,10 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
}
public int getSize() {
int message_size = 0;
+ if (parts == null) {
+ Log.e(TAG, " parts is null. returning ");
+ return message_size;
+ }
for(MimePart part : parts) {
message_size += part.data.length;
}
@@ -261,7 +294,7 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
* @param addresses the reformatted address substrings to encode.
*/
public void encodeHeaderAddresses(StringBuilder sb, String headerName,
- ArrayList<Rfc822Token> addresses) {
+ ArrayList<Rfc822Token> addresses) throws UnsupportedEncodingException {
/* TODO: Do we need to encode the addresses if they contain illegal characters?
* This depends of the outcome of errata 4176. The current spec. states to use UTF-8
* where possible, but the RFCs states to use US-ASCII for the headers - hence encoding
@@ -278,7 +311,7 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
sb.append("\r\n "); // Append a FWS (folding whitespace)
lineLength = 0;
}
- sb.append(address.toString()).append(";");
+ sb.append(new String(address.toString().getBytes("UTF-8"),"UTF-8")).append(";");
lineLength += partLength;
}
sb.append("\r\n");
@@ -310,7 +343,7 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
sb.append("?=\r\n");
}*/
if (subject != null)
- sb.append("Subject: ").append(subject).append("\r\n");
+ sb.append("Subject: ").append(new String(subject.getBytes("UTF-8"),"UTF-8")).append("\r\n");
if(from != null)
encodeHeaderAddresses(sb, "From: ", from); // This includes folding if needed.
if(sender != null)
@@ -336,7 +369,62 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
if(contentType != null)
sb.append("Content-Type: ").append(contentType).append("; boundary=").append(getBoundary()).append("\r\n");
}
- 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 (parts != null) {
+ 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()));
+ }
+ }
+ } else {
+ Log.e(TAG, " parts is null.");
+ }
+
+ 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
@@ -383,17 +471,19 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
encoding = "8BIT"; // The encoding used
encodeHeaders(sb);
- 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. */
- }
- } else {
- for(MimePart part : parts) {
- count++;
- part.encode(sb, getBoundary(), (count == parts.size()));
- }
- }
+ if(parts != null) {
+ 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. */
+ }
+ } else {
+ for(MimePart part : parts) {
+ count++;
+ part.encode(sb, getBoundary(), (count == parts.size()));
+ }
+ }
+ }
mmsBody = sb.toString();
if(mmsBody != null) {
@@ -481,7 +571,6 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
{
if(contentTypeParts[j].contains("boundary")) {
boundary = contentTypeParts[j].split("boundary[\\s]*=", 2)[1].trim();
- boundary = boundary.replaceAll("\"", ""); // " is allowed around a boundary, but is not allowed as part of the boundary
}
}
}
@@ -496,60 +585,81 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
}
private void parseMmsMimePart(String partStr) {
- String[] parts = partStr.split("\r\n\r\n", 2); // Split the header from the body
- String body;
- MimePart newPart = addMimePart();
- String partEncoding = encoding; /* Use the overall encoding as default */
- if(parts.length != 2) {
- body = partStr;
+ String[] parts = partStr.split("\r\n\r\n", 2); // Split the header from
+ // the body
+ String body = null;
+ if (parts.length != 2) {
+ // body = partStr;
+ throw new IllegalArgumentException(
+ "Mime part not formatted correctly: No Header");
} else {
body = parts[1];
- String[] headers = parts[0].split("\r\n");
-
- for(String header : headers) {
- if(header.length() == 0)
- continue;
-
- if(header.trim() == "" || header.trim().equals("--")) // Skip empty lines(the \r\n after the boundary tag) and endBoundary tags
- continue;
- String[] headerParts = header.split(":",2);
- if(headerParts.length != 2) {
- //throw new IllegalArgumentException("part-Header not formatted correctly: " + header);
- // If we find a part without headers, treat the entire content as body
- body = partStr;
- break;
- }
- String headerType = headerParts[0].toUpperCase();
- String headerValue = headerParts[1].trim();
- if(headerType.contains("CONTENT-TYPE")) {
- // TODO: extract charset? Only UTF-8 is allowed for TEXT typed parts
- newPart.contentType = headerValue;
- Log.d(TAG, "*** CONTENT-TYPE: " + newPart.contentType);
- }
- else if(headerType.contains("CONTENT-LOCATION")) {
- // This is used if the smil refers to a file name in its src=
- newPart.contentLocation = headerValue;
- newPart.partName = headerValue;
- }
- else if(headerType.contains("CONTENT-TRANSFER-ENCODING")) {
- partEncoding = headerValue;
- }
- else if(headerType.contains("CONTENT-ID")) {
- // This is used if the smil refers to a cid:<xxx> in it's src=
- newPart.contentId = headerValue;
- }
- else if(headerType.contains("CONTENT-DISPOSITION")) {
- // This is used if the smil refers to a cid:<xxx> in it's src=
- newPart.contentDisposition = headerValue;
- }
- else {
- if(D) Log.w(TAG,"Skipping unknown part-header: " + headerType + " (" + header + ")");
- }
+ }
+ String[] headers = parts[0].split("\r\n");
+ MimePart newPart = null;
+ String partEncoding = encoding; /* Use the overall encoding as default */
+
+ for (String header : headers) {
+ if (header.length() == 0)
+ continue;
+
+ if (header.trim() == "" || header.trim().equals("--")) // Skip empty
+ // lines(the
+ // \r\n
+ // after the
+ // boundary
+ // tag) and
+ // endBoundary
+ // tags
+ continue;
+ String[] headerParts = header.split(":", 2);
+ if (headerParts.length != 2) {
+ throw new IllegalArgumentException(
+ "part-Header not Formatted correctly: " + header);
+ }
+ if (newPart == null) {
+ if (V)
+ Log.v(TAG, "Add new MimePart\n");
+ newPart = addMimePart();
+ }
+ String headerType = headerParts[0].toUpperCase();
+ String headerValue = headerParts[1].trim();
+ if (headerType.contains("CONTENT-TYPE")) {
+ // TODO: extract charset? Only UTF-8 is allowed for TEXT typed
+ // parts
+ newPart.contentType = headerValue;
+ Log.d(TAG, "*** CONTENT-TYPE: " + newPart.contentType);
+ } else if (headerType.contains("CONTENT-LOCATION")) {
+ // This is used if the smil refers to a file name in its src=
+ newPart.contentLocation = headerValue;
+ newPart.partName = headerValue;
+ } else if (headerType.contains("CONTENT-TRANSFER-ENCODING")) {
+ partEncoding = headerValue;
+ } else if (headerType.contains("CONTENT-ID")) {
+ // This is used if the smil refers to a cid:<xxx> in it's src=
+ newPart.contentId = headerValue;
+ } else if (headerType.contains("CONTENT-DISPOSITION")) {
+ // This is used if the smil refers to a cid:<xxx> in it's src=
+ newPart.contentDisposition = headerValue;
+ } else {
+ if (D)
+ Log.w(TAG, "Skipping unknown part-header: " + headerType
+ + " (" + header + ")");
}
}
// 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();
@@ -569,6 +679,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()));
+ break;
+ } 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:
@@ -599,7 +833,6 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
messageBody = messageParts[1];
}
}
-
if(boundary == null)
{
// If the boundary is not set, handle as non-multi-part
@@ -611,12 +844,17 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
}
else
{
- mimeParts = messageBody.split("--" + boundary);
- for(int i = 1; i < mimeParts.length - 1; i++) {
+ mimeParts = messageBody.split("--" + boundary.replaceAll("\"",""));
+ for(int i = 0; i < mimeParts.length -1; i++) {
String part = mimeParts[i];
- if (part != null && (part.length() > 0))
- parseMmsMimePart(part);
- }
+ if (part != null && (part.length() > 0)) {
+ try {
+ parseMmsMimePart(part);
+ } catch (IllegalArgumentException e) {
+ Log.d(TAG, " part-Header not formatted correctly: " + e);
+ }
+ }
+ }
}
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java b/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java
index 8107bd8fe..2da76fd81 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java
@@ -23,9 +23,13 @@ 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 = Log.isLoggable(BluetoothMapService.LOG_TAG, Log.VERBOSE) ? true : false;
private ArrayList<SmsPdu> smsBodyPdus = null;
private String smsBody = null;
+ private String PCM_CARKIT = "9C:DF:03";
+ private String FORD_SYNC_CARKIT ="00:1E:AE";
public void setSmsBodyPdus(ArrayList<SmsPdu> smsBodyPdus) {
this.smsBodyPdus = smsBodyPdus;
@@ -77,6 +81,16 @@ public class BluetoothMapbMessageSms extends BluetoothMapbMessage {
*/
if(smsBody != null) {
String tmpBody = smsBody.replaceAll("END:MSG", "/END\\:MSG"); // Replace any occurrences of END:MSG with \END:MSG
+ if(V) Log.v(TAG,"smsBody is" +smsBody);
+
+ /* fix iot issue with PCM carkit where carkit is unable to parse
+ message if carriage return is present in it */
+ if(BluetoothMapService.getRemoteDevice().getAddress().startsWith(PCM_CARKIT)) {
+ tmpBody = tmpBody.replaceAll("\r", "");
+ } else if(BluetoothMapService.getRemoteDevice().getAddress().startsWith(FORD_SYNC_CARKIT)) {
+ tmpBody = tmpBody.replaceAll("\n", "");
+ }
+
bodyFragments.add(tmpBody.getBytes("UTF-8"));
}else if (smsBodyPdus != null && smsBodyPdus.size() > 0) {
for (SmsPdu pdu : smsBodyPdus) {
diff --git a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
index 6a0563b4e..526e45178 100644
--- a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
+++ b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
@@ -47,8 +47,8 @@ import javax.btobex.ResponseCodes;
public class BluetoothMnsObexClient {
private static final String TAG = "BluetoothMnsObexClient";
- private static final boolean D = false;
- private static final boolean V = false;
+ private static final boolean D = BluetoothMapService.DEBUG;
+ private static final boolean V = Log.isLoggable(BluetoothMapService.LOG_TAG, Log.VERBOSE) ? true : false;
private ObexTransport mTransport;
private Context mContext;
@@ -58,9 +58,11 @@ public class BluetoothMnsObexClient {
private ClientSession mClientSession;
private boolean mConnected = false;
BluetoothDevice mRemoteDevice;
- private Handler mCallback = null;
private BluetoothMapContentObserver mObserver;
+ private BluetoothMapContentObserver mEmailObserver;
private boolean mObserverRegistered = false;
+ private boolean mEmailObserverRegistered = false;
+ private Handler mCallback = null;
// Used by the MAS to forward notification registrations
public static final int MSG_MNS_NOTIFICATION_REGISTRATION = 1;
@@ -70,8 +72,7 @@ public class BluetoothMnsObexClient {
ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
- public BluetoothMnsObexClient(Context context, BluetoothDevice remoteDevice,
- Handler callback) {
+ public BluetoothMnsObexClient(Context context, BluetoothDevice remoteDevice) {
if (remoteDevice == null) {
throw new NullPointerException("Obex transport is null");
}
@@ -81,16 +82,16 @@ public class BluetoothMnsObexClient {
mHandler = new MnsObexClientHandler(looper);
mContext = context;
mRemoteDevice = remoteDevice;
- mCallback = callback;
- 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 +121,7 @@ public class BluetoothMnsObexClient {
* Call this when the MAS client requests a de-registration on events.
*/
public synchronized void disconnect() {
+ if(D) Log.d(TAG, "BluetoothMnsObexClient: disconnect");
try {
if (mClientSession != null) {
mClientSession.disconnect(null);
@@ -149,15 +151,17 @@ public class BluetoothMnsObexClient {
Log.e(TAG, "mTransport.close error: " + e.getMessage());
}
}
+ if(D) Log.d(TAG, "BluetoothMnsObexClient: exiting from disconnect");
}
/**
* Shutdown the MNS.
*/
- public void shutdown() {
+ public synchronized void shutdown() {
/* should shutdown handler thread first to make sure
* handleRegistration won't be called when disconnet
*/
+ if(D) Log.d(TAG, "BluetoothMnsObexClient: shutdown");
if (mHandler != null) {
// Shut down the thread
mHandler.removeCallbacksAndMessages(null);
@@ -171,6 +175,14 @@ public class BluetoothMnsObexClient {
/* Disconnect if connected */
disconnect();
+ if(mEmailObserverRegistered) {
+ mEmailObserver.unregisterObserver();
+ mEmailObserverRegistered = false;
+ }
+ if (mEmailObserver != null) {
+ mEmailObserver.deinit();
+ mEmailObserver = null;
+ }
if(mObserverRegistered) {
mObserver.unregisterObserver();
mObserverRegistered = false;
@@ -179,6 +191,16 @@ 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");
}
private HeaderSet hsConnect = null;
@@ -186,30 +208,38 @@ public class BluetoothMnsObexClient {
public void handleRegistration(int masId, int notificationStatus){
Log.d(TAG, "handleRegistration( " + masId + ", " + notificationStatus + ")");
- if((isConnected() == false) &&
+ synchronized (this) {
+ if((mEmailObserverRegistered == false) && (mObserverRegistered == false) &&
(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES)) {
Log.d(TAG, "handleRegistration: connect");
connect();
}
if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_NO) {
- // Unregister - should we disconnect, or keep the connection? - the spec. says nothing about this.
- if(mObserverRegistered == true) {
+ if(masId == 1 && mEmailObserverRegistered ) {
+ mEmailObserver.unregisterObserver();
+ mEmailObserverRegistered = false;
+ } else if(mObserverRegistered) {
mObserver.unregisterObserver();
mObserverRegistered = false;
+ }
+ if((mEmailObserverRegistered ==false) && (mObserverRegistered == false)) {
+ Log.d(TAG, "handleRegistration: disconnect");
disconnect();
}
} 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() {
@@ -258,10 +288,44 @@ public class BluetoothMnsObexClient {
synchronized (this) {
mWaitingForRemote = false;
}
+ Log.d(TAG, "Exiting from connect");
+ }
+
+ public void initObserver( Handler callback, int masInstanceId) {
+ mCallback = callback;
+ if(masInstanceId == 1 ){
+ mEmailObserver = new BluetoothMapContentEmailObserver(mContext,callback);
+ 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");
boolean error = false;
int responseCode = -1;
HeaderSet request;
@@ -299,7 +363,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) {
@@ -331,8 +395,13 @@ public class BluetoothMnsObexClient {
if (bytesWritten == eventBytes.length) {
Log.i(TAG, "SendEvent finished send length" + eventBytes.length);
+ if (outputStream != null) {
+ if (V) Log.v(TAG, "Closing outputStream");
+ outputStream.close();
+ }
} else {
error = true;
+ outputStream.close();
putOperation.abort();
Log.i(TAG, "SendEvent interrupted");
}
@@ -368,7 +437,7 @@ public class BluetoothMnsObexClient {
Log.e(TAG, "Error when closing stream after send " + e.getMessage());
}
}
-
+ if(D) Log.d(TAG, "BluetoothMnsObexClient: Exiting sendEvent");
return responseCode;
}