Update to Bluetooth MAP 1.2 (server)

- Change folder name lookup to a map
  Replaced the arrays used to convert mailbox ID/msg type to a folder name with a static map.
  This is to avoid null pointer exception for unknown  values, and to catch any changes in
  the ID/type values at compile time in stead of runtime.
  Bug-id:16874441
- Added Instance Information support and Extended Event support.
  Still missing integration wiht SDP MAP feature bit mask support
- Adding Abstract implementation to support conversations
- added  IM account handling, IM type definition, Application paramenters.
- addedgetConversactionList functionality
- added method to strip encoding in headers
- Fixed messagelist showing both email address and name in the name fields.
- Fixed Index out of bounds exception was hit when the subject contained invalid chars.
- Added functionality to support the getConversationListReq
  Works for SMS/MMS, Email and IM
  For Email/IM it depends on the convoContact table in the contract.
   For SMS/MMS it uses the contact number+ name if available in contact database.
- Added new parameters to msgListing also in contract class
- Added Test framework for "near system level" tests
  Currently only includes an entry point for single device tests.
- Added support for setOwnerStatus
- Added support for vcard type X-BT-UID
- Introduced type SignedLongLong to handle 128 bit values which needs to be handled as hex-strings.
- Added convocontact notification events for IM
- Added support for IM getMessage
- Added setEventFilter function.
- Added event filtering before enquing an event to be send.
- Added selective observers, depending on the active filter.
- Fixed timestamp to be from seconds to seconds (not from milisec)
- Fixed version number in bMessage if remote featurebit is set for v 1.1
- Added content encoding to QP for text that are not USACII
- Corrected the addresses in to/from for IM messages
- Added btuid and btuci to vcard
- Fixed (some) longlines
- Added extendedData support (empty when sending, just logging when receiving)
- Fixed Email folderName compairison changed to ignore case
- Fixed problem with names containing "null"
- BluetoothMapbMessageMms changed to BluetoothMapbMessageMime
- Fixrf addOriginator in getMessage request
- Add missing subjects in events for SMS
- Don't send ReadStatusChanged when pushing a message
- Temp way of adding names/uci to IM msg listing
- Added messageHandle filtering in msgListing
- Convolisting parameter mask support
- Added support for using handle when filtering in root folder during msgLising
- Added subject to event in sms
- Fixed so attribute_mime_type is only sent when parameter is requested
- Fixed feature bit check to messageListing version
- Fixed leaking cursors
- Added support for database identifier
- Added folder and conversation version counters

Change-Id: I4d2954b795aa7ed2a41dd034384da30f240b518f
diff --git a/Android.mk b/Android.mk
index eb1b579..7d2fdfa 100644
--- a/Android.mk
+++ b/Android.mk
@@ -7,8 +7,8 @@
         $(call all-java-files-under, lib/mapapi)
 
 LOCAL_MODULE := bluetooth.mapsapi
-
-include $(BUILD_JAVA_LIBRARY)
+LOCAL_MULTILIB := 32
+include $(BUILD_STATIC_JAVA_LIBRARY)
 
 include $(CLEAR_VARS)
 
@@ -21,8 +21,8 @@
 LOCAL_CERTIFICATE := platform
 
 LOCAL_JNI_SHARED_LIBRARIES := libbluetooth_jni
-LOCAL_JAVA_LIBRARIES := javax.obex telephony-common bluetooth.mapsapi
-LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard
+LOCAL_JAVA_LIBRARIES := javax.obex telephony-common
+LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard  bluetooth.mapsapi
 
 LOCAL_REQUIRED_MODULES := bluetooth.default
 LOCAL_MULTILIB := 32
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e879a75..b0a3ef8 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -27,6 +27,8 @@
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <!-- WRITE_CONTACTS is used for test cases only -->
+    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
     <uses-permission android:name="android.permission.READ_CALL_LOG" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
@@ -234,13 +236,13 @@
             android:enabled="@bool/profile_supported_map" >
             <intent-filter>
                 <action android:name="android.bluetooth.IBluetoothMap" />
-                <action android:name="android.btmap.intent.action.SHOW_MAPS_EMAIL_SETTINGS" />
+                <action android:name="android.btmap.intent.action.SHOW_MAPS_SETTINGS" />
                 <action android:name="com.android.bluetooth.map.USER_CONFIRM_TIMEOUT"/>
             </intent-filter>
         </service>
-         <activity android:name=".map.BluetoothMapEmailSettings"
+         <activity android:name=".map.BluetoothMapSettings"
                   android:process="@string/process"
-                  android:label="@string/bluetooth_map_email_settings_title"
+                  android:label="@string/bluetooth_map_settings_title"
                   android:excludeFromRecents="true"
                   android:configChanges="orientation|keyboardHidden"
                   android:enabled="@bool/profile_supported_map">
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index 02c293e..aa54d4d 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -22,9 +22,10 @@
 #include "cutils/properties.h"
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/Log.h"
+
 #include <string.h>
 #include <pthread.h>
-#include <binder/Parcel.h>
+
 #include <sys/stat.h>
 #include <fcntl.h>
 
@@ -471,7 +472,6 @@
     acl_state_changed_callback,
     callback_thread_event,
     dut_mode_recv_callback,
-
     le_test_mode_recv_callback,
     energy_info_recv_callback
 };
@@ -488,7 +488,8 @@
   .group = NULL
 };
 
-static bool set_wake_alarm_callout(uint64_t delay_millis, bool should_wake, alarm_cb cb, void *data) {
+static bool set_wake_alarm_callout(uint64_t delay_millis, bool should_wake,
+        alarm_cb cb, void *data) {
     JNIEnv *env;
     JavaVM *vm = AndroidRuntime::getJavaVM();
     jint status = vm->GetEnv((void **)&env, JNI_VERSION_1_6);
@@ -507,7 +508,8 @@
     sAlarmCallbackData = data;
 
     jboolean jshould_wake = should_wake ? JNI_TRUE : JNI_FALSE;
-    jboolean ret = env->CallBooleanMethod(sJniAdapterServiceObj, method_setWakeAlarm, (jlong)delay_millis, jshould_wake);
+    jboolean ret = env->CallBooleanMethod(sJniAdapterServiceObj, method_setWakeAlarm,
+            (jlong)delay_millis, jshould_wake);
     if (!ret) {
         sAlarmCallback = NULL;
         sAlarmCallbackData = NULL;
@@ -1042,7 +1044,8 @@
 }
 
 static int createSocketChannelNative(JNIEnv *env, jobject object, jint type,
-                                     jstring name_str, jbyteArray uuidObj, jint channel, jint flag) {
+                                     jstring name_str, jbyteArray uuidObj, 
+                                     jint channel, jint flag) {
     const char *service_name = NULL;
     jbyte *uuid = NULL;
     int socket_fd;
diff --git a/jni/com_android_bluetooth_sdp.cpp b/jni/com_android_bluetooth_sdp.cpp
index 8c1bc0f..a266bfa 100644
--- a/jni/com_android_bluetooth_sdp.cpp
+++ b/jni/com_android_bluetooth_sdp.cpp
@@ -148,7 +148,7 @@
     ALOGD("%s UUID %.*X",__FUNCTION__,16, (uint8_t*)uuid);
 
 
-    if ( (ret = sBluetoothSdpInterface->sdp_search((bt_bdaddr_t *)addr,
+    if ((ret = sBluetoothSdpInterface->sdp_search((bt_bdaddr_t *)addr,
                     (const uint8_t*)uuid)) != BT_STATUS_SUCCESS) {
         ALOGE("SDP Search initialization failed: %d", ret);
         goto Fail;
diff --git a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
index 32018ba..6a40a27 100644
--- a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
+++ b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
@@ -21,7 +21,7 @@
 
 
 /**
- * This class defines the minimum sets of data needed for an E-mail client to
+ * This class defines the minimum sets of data needed for a client to
  * implement to claim support for the Bluetooth Message Access Profile.
  * Access to three data sets are needed:
  * <ul>
@@ -35,10 +35,12 @@
  *     might provide.</li>
  *   <li>Folder data set with the folder structure for the messages. Each message is linked to an
  *     entry in this data set.</li>
+ *   <li>Conversation data set with the thread structure of the messages. Each message is linked
+ *     to an entry in this data set.</li>
  * </ul>
  *
  * To enable that the Bluetooth Message Access Server can detect the content provider implementing
- * this interface, the {@code provider} tag for the bluetooth related content provider must
+ * this interface, the {@code provider} tag for the Bluetooth related content provider must
  * have an intent-filter like the following in the manifest:
  * <pre class="prettyprint">&lt;provider  android:authorities="[PROVIDER AUTHORITY]"
               android:exported="true"
@@ -66,8 +68,10 @@
      * Provider interface that should be used as intent-filter action in the provider section
      * of the manifest file.
      */
-    public static final String PROVIDER_INTERFACE = "android.bluetooth.action.BLUETOOTH_MAP_PROVIDER";
-
+    public static final String PROVIDER_INTERFACE_EMAIL =
+            "android.bluetooth.action.BLUETOOTH_MAP_PROVIDER";
+    public static final String PROVIDER_INTERFACE_IM =
+            "android.bluetooth.action.BLUETOOTH_MAP_IM_PROVIDER";
     /**
      * The Bluetooth Message Access profile allows a remote BT-MAP client to trigger
      * an update of a folder for a specific e-mail account, register for reception
@@ -98,6 +102,48 @@
     public static final String EXTRA_UPDATE_FOLDER_ID = "UpdateFolderId";
 
     /**
+     * The Bluetooth Message Access profile allows a remote BT-MAP Client to update
+     * the owners presence and chat state
+     *
+     * ContentProvider.call() is used for these purposes, and the METHOD_SET_OWNER_STATUS
+     * method name shall trigger a change in owner/users presence or chat properties for an
+     * account or conversation.
+     *
+     * This shall be a non blocking call simply setting the properties, and the change should
+     * be sent to the remote server/users, depending on what property is changed.
+     * Bundle extra parameter will carry following values:
+     *   EXTRA_ACCOUNT_ID containing the account_id
+     *   EXTRA_PRESENCE_STATE containing the presence state of the owner account
+     *   EXTRA_PRESENCE_STATUS containing the presence status text from the owner
+     *   EXTRA_LAST_ACTIVE containing the last activity time stamp of the owner account
+     *   EXTRA_CHAT_STATE containing the chat state of a specific conversation
+     *   EXTRA_CONVERSATION_ID containing the conversation that is changed
+     */
+    public static final String METHOD_SET_OWNER_STATUS = "SetOwnerStatus";
+    public static final String EXTRA_ACCOUNT_ID = "AccountId"; // Is this needed
+    public static final String EXTRA_PRESENCE_STATE = "PresenceState";
+    public static final String EXTRA_PRESENCE_STATUS = "PresenceStatus";
+    public static final String EXTRA_LAST_ACTIVE = "LastActive";
+    public static final String EXTRA_CHAT_STATE = "ChatState";
+    public static final String EXTRA_CONVERSATION_ID = "ConversationId";
+
+    /**
+     * The Bluetooth Message Access profile can inform the messaging application of the Bluetooth
+     * state, whether is is turned 'on' or 'off'
+     *
+     * ContentProvider.call() is used for these purposes, and the METHOD_SET_BLUETOOTH_STATE
+     * method name shall trigger a change in owner/users presence or chat properties for an
+     * account or conversation.
+     *
+     * This shall be a non blocking call simply setting the properties.
+     *
+     * Bundle extra parameter will carry following values:
+     *   EXTRA_BLUETOOTH_STATE containing the state of the Bluetooth connectivity
+     */
+    public static final String METHOD_SET_BLUETOOTH_STATE = "SetBtState";
+    public static final String EXTRA_BLUETOOTH_STATE = "BluetoothState";
+
+    /**
      * These column names are used as last path segment of the URI (getLastPathSegment()).
      * Access to a specific row in the tables is done by using the where-clause, hence
      * support for .../#id if not needed for the Email clients.
@@ -105,11 +151,13 @@
      *   content://ProviderAuthority/TABLE_ACCOUNT
      *   content://ProviderAuthority/account_id/TABLE_MESSAGE
      *   content://ProviderAuthority/account_id/TABLE_FOLDER
-     */
+     *   content://ProviderAuthority/account_id/TABLE_CONVERSATION
+     *   content://ProviderAuthority/account_id/TABLE_CONVOCONTACT
+     **/
 
     /**
      * Build URI representing the given Accounts data-set in a
-     * bluetooth provider. When queried, the direct URI for the account
+     * Bluetooth provider. When queried, the direct URI for the account
      * with the given accountID is returned.
      */
     public static Uri buildAccountUri(String authority) {
@@ -130,7 +178,7 @@
     }
     /**
      * Build URI representing the entire Message table in a
-     * bluetooth provider.
+     * Bluetooth provider.
      */
     public static Uri buildMessageUri(String authority) {
         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
@@ -140,7 +188,7 @@
     }
     /**
      * Build URI representing the given Message data-set in a
-     * bluetooth provider. When queried, the URI for the Messages
+     * Bluetooth provider. When queried, the URI for the Messages
      * with the given accountID is returned.
      */
     public static Uri buildMessageUri(String authority, String accountId) {
@@ -152,7 +200,7 @@
     }
     /**
      * Build URI representing the given Message data-set with specific messageId in a
-     * bluetooth provider. When queried, the direct URI for the account
+     * Bluetooth provider. When queried, the direct URI for the account
      * with the given accountID is returned.
      */
     public static Uri buildMessageUriWithId(String authority, String accountId,String messageId) {
@@ -165,7 +213,7 @@
     }
     /**
      * Build URI representing the given Message data-set in a
-     * bluetooth provider. When queried, the direct URI for the account
+     * Bluetooth provider. When queried, the direct URI for the folder
      * with the given accountID is returned.
      */
     public static Uri buildFolderUri(String authority, String accountId) {
@@ -177,11 +225,66 @@
     }
 
     /**
+     * Build URI representing the given Message data-set in a
+     * Bluetooth provider. When queried, the direct URI for the conversation
+     * with the given accountID is returned.
+     */
+    public static Uri buildConversationUri(String authority, String accountId) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(authority)
+                .appendPath(accountId)
+                .appendPath(TABLE_CONVERSATION)
+                .build();
+    }
+
+    /**
+     * Build URI representing the given Contact data-set in a
+     * Bluetooth provider. When queried, the direct URI for the contacts
+     * with the given accountID is returned.
+     */
+    public static Uri buildConvoContactsUri(String authority) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(authority)
+                .appendPath(TABLE_CONVOCONTACT)
+                .build();
+    }
+
+    /**
+     * Build URI representing the given Contact data-set in a
+     * Bluetooth provider. When queried, the direct URI for the contacts
+     * with the given accountID is returned.
+     */
+    public static Uri buildConvoContactsUri(String authority, String accountId) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(authority)
+                .appendPath(accountId)
+                .appendPath(TABLE_CONVOCONTACT)
+                .build();
+    }
+    /**
+     * Build URI representing the given Contact data-set in a
+     * Bluetooth provider. When queried, the direct URI for the contact
+     * with the given contactID and accountID is returned.
+     */
+    public static Uri buildConvoContactsUriWithId(String authority, String accountId,
+            String contactId) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(authority)
+                .appendPath(accountId)
+                .appendPath(TABLE_CONVOCONTACT)
+                .appendPath(contactId)
+                .build();
+    }
+    /**
      *  @hide
      */
-    public static final String TABLE_ACCOUNT = "Account";
-    public static final String TABLE_MESSAGE = "Message";
-    public static final String TABLE_FOLDER  = "Folder";
+    public static final String TABLE_ACCOUNT        = "Account";
+    public static final String TABLE_MESSAGE        = "Message";
+    public static final String TABLE_MESSAGE_PART   = "Part";
+    public static final String TABLE_FOLDER         = "Folder";
+    public static final String TABLE_CONVERSATION   = "Conversation";
+    public static final String TABLE_CONVOCONTACT   = "ConvoContact";
+
 
     /**
      * Mandatory folders for the Bluetooth message access profile.
@@ -189,11 +292,22 @@
      * E.g. as a mapping for them such that the naming will match the underlying
      * matching folder ID's.
      */
-    public static final String FOLDER_NAME_INBOX   = "inbox";
-    public static final String FOLDER_NAME_OUTBOX  = "outbox";
-    public static final String FOLDER_NAME_SENT    = "sent";
-    public static final String FOLDER_NAME_DELETED = "deleted";
-    public static final String FOLDER_NAME_DRAFT   = "draft";
+    public static final String FOLDER_NAME_INBOX   = "INBOX";
+    public static final String FOLDER_NAME_SENT    = "SENT";
+    public static final String FOLDER_NAME_OUTBOX  = "OUTBOX";
+    public static final String FOLDER_NAME_DRAFT   = "DRAFT";
+    public static final String FOLDER_NAME_DELETED = "DELETED";
+    public static final String FOLDER_NAME_OTHER   = "OTHER";
+
+    /**
+     * Folder IDs to be used with Instant Messaging virtual folders
+     */
+    public static final long FOLDER_ID_OTHER      = 0;
+    public static final long FOLDER_ID_INBOX      = 1;
+    public static final long FOLDER_ID_SENT       = 2;
+    public static final long FOLDER_ID_DRAFT      = 3;
+    public static final long FOLDER_ID_OUTBOX     = 4;
+    public static final long FOLDER_ID_DELETED    = 5;
 
 
     /**
@@ -296,15 +410,107 @@
          *
          * This setting shall not be used to enforce whether or not an account should be shared
          * or not if the account is bound by an administrative security policy. In this case
-         * the email app should not list the account at all if it is not to be shareable over BT.
+         * the email app should not list the account at all if it is not to be sharable over BT.
          *
          * <P>Type: INTEGER (boolean) hide = 0, show = 1</P>
          */
         public static final String FLAG_EXPOSE = "flag_expose";
 
+
+        /**
+         * The account unique identifier representing this account. For most IM clients this will be
+         * the fully qualified user name to which an invite message can be sent, from another use.
+         *
+         * e.g.: "map_test_user_12345@gmail.com" - for a Hangouts account
+         *
+         * This value will only be visible to authenticated Bluetooth devices, and will be
+         * transmitted using an encrypted link.
+         * <P>Type: TEXT</P>
+         * read-only
+         */
+        public static final String ACCOUNT_UCI = "account_uci";
+
+
+        /**
+         * The Bluetooth SIG maintains a list of assigned numbers(text strings) for IM clients.
+         * If your client/account has such a string, this is the place to return it.
+         * If supported by both devices, the presence of this prefix will make it possible to
+         * respond to a message by making a voice-call, using the same account information.
+         * (The call will be made using the HandsFree profile)
+         * https://www.bluetooth.org/en-us/specification/assigned-numbers/uniform-caller-identifiers
+         *
+         * e.g.: "hgus" - for Hangouts
+         *
+         * <P>Type: TEXT</P>
+         * read-only
+         */
+        public static final String ACCOUNT_UCI_PREFIX = "account_uci_PREFIX";
+
     }
 
     /**
+     * Message Data Parts Table
+     * The columns needed to contain the actual data of the messageparts in IM messages.
+     * Each "part" has its own row and represent a single mime-part in a multipart-mime
+     * formatted message.
+     *
+     */
+    public interface MessagePartColumns {
+
+        /**
+         * The unique ID for a row.
+         * <P>Type: INTEGER (long)</P>
+         * read-only
+         */
+        public static final String _ID = "_id";
+        // FIXME add message parts for IM attachments
+        /**
+         * is this a text part  yes/no?
+         * <P>Type: TEXT</P>
+         * read-only
+         */
+        public static final String TEXT = "text";
+
+        /**
+         * The charset used in the content if it is text or 8BIT if it is
+         * binary data
+         *
+         * <P>Type: TEXT</P>
+         * read-only
+         */
+        public static final String CHARSET = "charset";
+
+        /**
+         * The filename representing the data file of the raw data in the database
+         * If this is empty, then it must be text and part of the message body.
+         * This is the name that the data will have when it is included as attachment
+         *
+         * <P>Type: TEXT</P>
+         * read-only
+         */
+
+        public static final String FILENAME = "filename";
+
+        /**
+         * Identifier for the content in the data. This can be used to
+         * refer directly to the data in the body part.
+         *
+         * <P>Type: TEXT</P>
+         * read-only
+         */
+
+        public static final String CONTENT_ID = "cid";
+
+        /**
+         * The raw data in either text format or binary format
+         *
+         * <P>Type: BLOB</P>
+         * read-only
+         */
+        public static final String RAW_DATA = "raw_data";
+
+    }
+    /**
      * The actual message table containing all messages.
      * Content that must support filtering using WHERE clauses:
      *   - To, From, Cc, Bcc, Date, ReadFlag, PriorityFlag, folder_id, account_id
@@ -319,7 +525,7 @@
      * date etc) written through file-i/o takes precedence over the inserted values and should
      * overwrite them.
      */
-    public interface MessageColumns {
+    public interface MessageColumns extends EmailMessageColumns {
 
         /**
          * The unique ID for a row.
@@ -336,6 +542,14 @@
          */
         public static final String DATE = "date";
 
+        //TODO REMOVE WHEN Parts Table is in place
+        /**
+         * Message body. Used by Instant Messaging
+         * <P>Type: TEXT</P>
+         * read-only.
+         */
+        public static final String BODY = "body";
+
         /**
          * Message subject.
          * <P>Type: TEXT</P>
@@ -359,11 +573,18 @@
 
         /**
          * Reception state - the amount of the message that have been loaded from the server.
-         * <P>Type: INTEGER see RECEPTION_STATE_ constants below </P>
+         * <P>Type: TEXT see RECEPTION_STATE_* constants below </P>
          * read-only
          */
         public static final String RECEPTION_STATE = "reception_state";
 
+        /**
+         * Delivery state - the amount of the message that have been loaded from the server.
+         * <P>Type: TEXT see DELIVERY_STATE_* constants below </P>
+         * read-only
+         */
+        public static final String DEVILERY_STATE = "delivery_state";
+
         /** To be able to filter messages with attachments, we need this flag.
          * <P>Type: INTEGER (boolean) no attachment = 0, attachment = 1 </P>
          * read-only
@@ -375,6 +596,12 @@
          */
         public static final String ATTACHMENT_SIZE = "attachment_size";
 
+        /** The mine type of the attachments for the message.
+         * <P>Type: TEXT </P>
+         * read-only
+         */
+        public static final String ATTACHMENT_MINE_TYPES = "attachment_mime_types";
+
         /** The overall size in bytes of the message including any attachments.
          * This value is informative only and should be the size an email client
          * would display as size for the message.
@@ -406,6 +633,40 @@
         public static final String TO_LIST = "to_list";
 
         /**
+         * The unique ID for a row in the folder table in which this message belongs.
+         * <P>Type: INTEGER (long)</P>
+         * read/write
+         */
+        public static final String FOLDER_ID = "folder_id";
+
+        /**
+         * The unique ID for a row in the account table which owns this message.
+         * <P>Type: INTEGER (long)</P>
+         * read-only
+         */
+        public static final String ACCOUNT_ID = "account_id";
+
+        /**
+         * The ID identify the thread/conversation a message belongs to.
+         * If no thread id is available, set value to "-1"
+         * <P>Type: INTEGER (long)</P>
+         * read-only
+         */
+        public static final String THREAD_ID = "thread_id";
+
+        /**
+         * The Name of the thread/conversation a message belongs to.
+         * <P>Type: TEXT</P>
+         * read-only
+         */
+        public static final String THREAD_NAME = "thread_name";
+    }
+
+    public interface EmailMessageColumns {
+
+
+
+        /**
          * A comma-delimited list of CC addresses in RFC2822 format.
          * The list must be compatible with Rfc822Tokenizer.tokenize();
          * <P>Type: TEXT</P>
@@ -429,30 +690,19 @@
          */
         public static final String REPLY_TO_LIST = "reply_to_List";
 
-        /**
-         * The unique ID for a row in the folder table in which this message belongs.
-         * <P>Type: INTEGER (long)</P>
-         * read/write
-         */
-        public static final String FOLDER_ID = "folder_id";
 
-        /**
-         * The unique ID for a row in the account table which owns this message.
-         * <P>Type: INTEGER (long)</P>
-         * read-only
-         */
-        public static final String ACCOUNT_ID = "account_id";
-
-        /**
-         * The ID identify the thread a message belongs to. If no thread id is available,
-         * set value to "-1"
-         * <P>Type: INTEGER (long)</P>
-         * read-only
-         */
-        public static final String THREAD_ID = "thread_id";
     }
 
     /**
+     * Indicates the complete message has been delivered to the recipient.
+     */
+    public static final String DELIVERY_STATE_DELIVERED = "delivered";
+    /**
+     * Indicates that the complete message has been sent from the MSE to the remote network.
+     */
+    public static final String DELIVERY_STATE_SENT = "sent";
+
+    /**
      * Indicates that the message, including any attachments, has been received from the
      * server to the device.
      */
@@ -510,6 +760,313 @@
          */
         public static final String PARENT_FOLDER_ID = "parent_id";
     }
+
+    /**
+     * Message conversation structure. Enables use of a conversation structure for messages across
+     * folders, further binding contacts to conversations.
+     * Content that must be supplied:
+     *   - Name, LastActivity, ReadStatus, VersionCounter
+     * Content that must support update:
+     *   - READ_STATUS, LAST_ACTIVITY and VERSION_COUNTER (VERSION_COUNTER used to validity of _ID)
+     * Additional insert of a new conversation with the following values shall be supported:
+     *   - FOLDER_ID
+     * When querying this table, the cursor returned must contain one row for each contact member
+     * in a thread.
+     * For filter/search parameters attributes to the URI will be used. The following columns must
+     * support filtering:
+     *  - ConvoContactColumns.NAME
+     *  - ConversationColumns.THREAD_ID
+     *  - ConversationColumns.LAST_ACTIVITY
+     *  - ConversationColumns.READ_STATUS
+     */
+    public interface ConversationColumns extends ConvoContactColumns {
+
+        /**
+         * The unique ID for a row.
+         * <P>Type: INTEGER (long)</P>
+         * read-only
+         */
+// Should not be needed anymore        public static final String _ID = "_id";
+
+        /**
+         * The unique ID for a Thread.
+         * <P>Type: INTEGER (long)</P>
+         * read-only
+         */
+        public static final String THREAD_ID = "thread_id";
+
+        /**
+         * The unique ID for a row.
+         * <P>Type: INTEGER (long)</P>
+         * read-only
+         */
+// TODO: IS THIS NECESSARY - or do we need the thread ID to hold thread Id from message
+//       or can we be sure we are in control and can use the _ID and put that in the message DB
+        //public static final String THREAD_ID = "thread_id";
+
+        /**
+         * The type of conversation, see {@link ConversationType}
+         * <P>Type: TEXT</P>
+         * read-only
+         */
+// TODO: IS THIS NECESSARY - no conversation type is available in the latest,
+//        guess it can be found from number of contacts in the conversation
+        //public static final String TYPE = "type";
+
+        /**
+         * The name of the conversation, e.g. group name in case of group chat
+         * <P>Type: TEXT</P>
+         * read-only
+         */
+        public static final String THREAD_NAME = "thread_name";
+
+        /**
+         * The time stamp of the last activity in the conversation as a unix timestamp
+         * (miliseconds since 00:00:00 UTC 1/1-1970)
+         * <P>Type: INTEGER (long)</P>
+         * read-only
+         */
+        public static final String LAST_THREAD_ACTIVITY = "last_thread_activity";
+
+        /**
+         * The status on the conversation, either 'read' or 'unread'
+         *  <P>Type: INTEGER (boolean) unread = 0, read = 1</P>
+         * read/write
+         */
+        public static final String READ_STATUS = "read_status";
+
+        /**
+         * A counter that keep tack of version of the table content, count up on ID reuse
+         * <P>Type: INTEGER (long)</P>
+         * read-only
+         */
+// TODO: IS THIS NECESSARY - skal den ligge i databasen?
+        // CB: If we need it, it must be in the database, or initialized with a random value at
+        //     BT-ON
+        // UPDATE: TODO: Change to the last_activity time stamp (as a long value). This will
+        //         provide the information needed for BT clients - currently unused
+        public static final String VERSION_COUNTER = "version_counter";
+
+        /**
+         * A short description of the latest activity on conversation - typically
+         * part of the last message.
+         * <P>Type: TEXT</P>
+         * read-only
+         */
+        public static final String SUMMARY = "convo_summary";
+
+
+    }
+
+    /**
+     * MAP enables access to contacts for the conversation
+     * The conversation table must provide filtering (using WHERE clauses) of following entries:
+     *   - convo_id linking contacts to conversations
+     *   - x_bt_uid linking contacts to PBAP contacts
+     * The conversation contact table must have a convo_id and a name for each entry.
+     */
+    public interface ConvoContactColumns extends ChatStatusColumns, PresenceColumns {
+        /**
+         * The unique ID for a contact in Conversation
+         * <P>Type: INTEGER (long)</P>
+         * read-only
+         */
+// Should not be needed anymore        public static final String _ID = "_id";
+
+        /**
+        * The ID of the conversation the contact is part of.
+        * <P>Type: INTEGER (long)</P>
+        * read-only
+        */
+        public static final String CONVO_ID = "convo_id";
+
+        /**
+         * The name of contact in instant message application
+         * <P>Type: TEXT</P>
+         * read-only
+         */
+        public static final String NAME = "name";
+
+        /**
+         * The nickname of contact in instant message group chat conversation.
+         * <P>Type: TEXT</P>
+         * read-only
+         */
+        public static final String NICKNAME = "nickname";
+
+
+        /**
+         * The unique ID for all Bluetooth contacts available through PBAP.
+         * <P>Type: INTEGER (long)</P>
+         * read-only
+         */
+        public static final String X_BT_UID = "x_bt_uid";
+
+        /**
+         * The unique ID for the contact within the domain of the interfacing service.
+         * (UCI: Unique Call Identity)
+         * It is expected that a message send to this ID will reach the recipient regardless
+         * through which interface the message is send.
+         * For E-mail this will be the e-mail address, for Google+ this will be the e-mail address
+         * associated with the contact account.
+         * This ID
+         * <P>Type: TEXT</P>
+         * read-only
+         */
+        public static final String UCI = "x_bt_uci";
+    }
+
+    /**
+     * The name of query parameter used to filter on recipient
+     */
+    public static final String FILTER_RECIPIENT_SUBSTRING = "rec_sub_str";
+
+    /**
+     * The name of query parameter used to filter on originator
+     */
+    public static final String FILTER_ORIGINATOR_SUBSTRING = "org_sub_str";
+
+    /**
+     * The name of query parameter used to filter on read status.
+     *  - true - return only threads with all messages marked as read
+     *  - false - return only threads with one or more unread messages
+     *  - omitted as query parameter - do not filter on read status
+     */
+    public static final String FILTER_READ_STATUS = "read";
+
+    /**
+     * Time in ms since epoch. For conversations this will be for last activity
+     * as a unix timestamp (miliseconds since 00:00:00 UTC 1/1-1970)
+     */
+    public static final String FILTER_PERIOD_BEGIN = "t_begin";
+
+    /**
+     * Time in ms since epoch. For conversations this will be for last activity
+     * as a unix timestamp (miliseconds since 00:00:00 UTC 1/1-1970)
+     */
+    public static final String FILTER_PERIOD_END = "t_end";
+
+    /**
+     * Filter for a specific ThreadId
+     */
+    public static final String FILTER_THREAD_ID = "thread_id";
+
+
+    public interface ChatState {
+        int UNKNOWN     = 0;
+        int INACITVE    = 1;
+        int ACITVE      = 2;
+        int COMPOSING   = 3;
+        int PAUSED      = 4;
+        int GONE        = 5;
+    }
+
+    /**
+     * Instant Messaging contact chat state information
+     * MAP enables access to contacts chat state for the instant messaging application
+     * The chat state table must provide filtering (use of WHERE clauses) of the following entries:
+     *   - contact_id (linking chat state to contacts)
+     *   - thread_id (linking chat state to conversations and messages)
+     * The presence table must have a contact_id for each entry.
+     */
+    public interface ChatStatusColumns {
+
+//        /**
+//         * The contact ID of a instant messaging contact.
+//         * <P>Type: TEXT </P>
+//         * read-only
+//         */
+//        public static final String CONTACT_ID = "contact_id";
+//
+//        /**
+//         * The thread id for a conversation.
+//         * <P>Type: INTEGER (long)</P>
+//         * read-only
+//         */
+//        public static final String CONVO_ID = "convo_id";
+
+        /**
+         * The chat state of contact in conversation, see {@link ChatState}
+         * <P>Type: INTERGER</P>
+         * read-only
+         */
+        public static final String CHAT_STATE = "chat_state";
+
+//        /**
+//         * The geo location of the contact
+//         * <P>Type: TEXT</P>
+//         * read-only
+//         */
+//// TODO: IS THIS NEEDED - not in latest specification
+//        public static final String GEOLOC = "geoloc";
+
+        /**
+         * The time stamp of the last time this contact was active in the conversation
+         * <P>Type: INTEGER (long)</P>
+         * read-only
+         */
+        public static final String LAST_ACTIVE = "last_active";
+
+    }
+
+    public interface PresenceState {
+        int UNKNOWN         = 0;
+        int OFFLINE         = 1;
+        int ONLINE          = 2;
+        int AWAY            = 3;
+        int DO_NOT_DISTURB  = 4;
+        int BUSY            = 5;
+        int IN_A_MEETING    = 6;
+    }
+
+    /**
+     * Instant Messaging contact presence information
+     * MAP enables access to contacts presences information for the instant messaging application
+     * The presence table must provide filtering (use of WHERE clauses) of the following entries:
+     *   - contact_id (linking contacts to presence)
+     * The presence table must have a contact_id for each entry.
+     */
+    public interface PresenceColumns {
+
+//        /**
+//         * The contact ID of a instant messaging contact.
+//         * <P>Type: TEXT </P>
+//         * read-only
+//         */
+//        public static final String CONTACT_ID = "contact_id";
+
+        /**
+         * The presence state of contact, see {@link PresenceState}
+         * <P>Type: INTERGER</P>
+         * read-only
+         */
+        public static final String PRESENCE_STATE = "presence_state";
+
+        /**
+         * The priority of contact presence
+         * <P>Type: INTERGER</P>
+         * read-only
+         */
+// TODO: IS THIS NEEDED - not in latest specification
+        public static final String PRIORITY = "priority";
+
+        /**
+         * The last status text from contact
+         * <P>Type: TEXT</P>
+         * read-only
+         */
+        public static final String STATUS_TEXT = "status_text";
+
+        /**
+         * The time stamp of the last time the contact was online
+         * <P>Type: INTEGER (long)</P>
+         * read-only
+         */
+        public static final String LAST_ONLINE = "last_online";
+
+    }
+
+
     /**
      * A projection of all the columns in the Message table
      */
@@ -517,21 +1074,43 @@
         MessageColumns._ID,
         MessageColumns.DATE,
         MessageColumns.SUBJECT,
-        MessageColumns.FLAG_READ,
-        MessageColumns.FLAG_ATTACHMENT,
+        //TODO REMOVE WHEN Parts Table is in place
+        MessageColumns.BODY,
+        MessageColumns.MESSAGE_SIZE,
         MessageColumns.FOLDER_ID,
-        MessageColumns.ACCOUNT_ID,
+        MessageColumns.FLAG_READ,
+        MessageColumns.FLAG_PROTECTED,
+        MessageColumns.FLAG_HIGH_PRIORITY,
+        MessageColumns.FLAG_ATTACHMENT,
+        MessageColumns.ATTACHMENT_SIZE,
         MessageColumns.FROM_LIST,
         MessageColumns.TO_LIST,
         MessageColumns.CC_LIST,
         MessageColumns.BCC_LIST,
         MessageColumns.REPLY_TO_LIST,
+        MessageColumns.RECEPTION_STATE,
+        MessageColumns.DEVILERY_STATE,
+        MessageColumns.THREAD_ID
+    };
+
+    public static final String[] BT_INSTANT_MESSAGE_PROJECTION = new String[] {
+        MessageColumns._ID,
+        MessageColumns.DATE,
+        MessageColumns.SUBJECT,
+        MessageColumns.MESSAGE_SIZE,
+        MessageColumns.FOLDER_ID,
+        MessageColumns.FLAG_READ,
         MessageColumns.FLAG_PROTECTED,
         MessageColumns.FLAG_HIGH_PRIORITY,
-        MessageColumns.MESSAGE_SIZE,
+        MessageColumns.FLAG_ATTACHMENT,
         MessageColumns.ATTACHMENT_SIZE,
+        MessageColumns.ATTACHMENT_MINE_TYPES,
+        MessageColumns.FROM_LIST,
+        MessageColumns.TO_LIST,
         MessageColumns.RECEPTION_STATE,
-        MessageColumns.THREAD_ID
+        MessageColumns.DEVILERY_STATE,
+        MessageColumns.THREAD_ID,
+        MessageColumns.THREAD_NAME
     };
 
     /**
@@ -544,6 +1123,18 @@
     };
 
     /**
+     * A projection of all the columns in the Account table
+     * TODO: Is this the way to differentiate
+     */
+    public static final String[] BT_IM_ACCOUNT_PROJECTION = new String[] {
+        AccountColumns._ID,
+        AccountColumns.ACCOUNT_DISPLAY_NAME,
+        AccountColumns.FLAG_EXPOSE,
+        AccountColumns.ACCOUNT_UCI,
+        AccountColumns.ACCOUNT_UCI_PREFIX
+    };
+
+    /**
      * A projection of all the columns in the Folder table
      */
     public static final String[] BT_FOLDER_PROJECTION = new String[] {
@@ -554,4 +1145,74 @@
     };
 
 
+    /**
+     * A projection of all the columns in the Conversation table
+     */
+    public static final String[] BT_CONVERSATION_PROJECTION = new String[] {
+        /* Thread information */
+        ConversationColumns.THREAD_ID,
+        ConversationColumns.THREAD_NAME,
+        ConversationColumns.READ_STATUS,
+        ConversationColumns.LAST_THREAD_ACTIVITY,
+        ConversationColumns.VERSION_COUNTER,
+        ConversationColumns.SUMMARY,
+        /* Contact information */
+        ConversationColumns.UCI,
+        ConversationColumns.NAME,
+        ConversationColumns.NICKNAME,
+        ConversationColumns.CHAT_STATE,
+        ConversationColumns.LAST_ACTIVE,
+        ConversationColumns.X_BT_UID,
+        ConversationColumns.PRESENCE_STATE,
+        ConversationColumns.STATUS_TEXT,
+        ConversationColumns.PRIORITY
+    };
+
+    /**
+     * A projection of the Contact Info and Presence columns in the Contact Info in table
+     */
+    public static final String[] BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION = new String[] {
+        ConvoContactColumns.UCI,
+        ConvoContactColumns.CONVO_ID,
+        ConvoContactColumns.NAME,
+        ConvoContactColumns.NICKNAME,
+        ConvoContactColumns.X_BT_UID,
+        ConvoContactColumns.CHAT_STATE,
+        ConvoContactColumns.LAST_ACTIVE,
+        ConvoContactColumns.PRESENCE_STATE,
+        ConvoContactColumns.PRIORITY,
+        ConvoContactColumns.STATUS_TEXT,
+        ConvoContactColumns.LAST_ONLINE
+    };
+
+    /**
+     * A projection of the Contact Info the columns in Contacts Info table
+     */
+    public static final String[] BT_CONTACT_PROJECTION = new String[] {
+        ConvoContactColumns.UCI,
+        ConvoContactColumns.CONVO_ID,
+        ConvoContactColumns.X_BT_UID,
+        ConvoContactColumns.NAME,
+        ConvoContactColumns.NICKNAME
+    };
+
+
+    /**
+     * A projection of all the columns in the Chat Status table
+     */
+    public static final String[] BT_CHATSTATUS_PROJECTION = new String[] {
+        ChatStatusColumns.CHAT_STATE,
+        ChatStatusColumns.LAST_ACTIVE,
+    };
+
+    /**
+     * A projection of all the columns in the Presence table
+     */
+    public static final String[] BT_PRESENCE_PROJECTION = new String[] {
+        PresenceColumns.PRESENCE_STATE,
+        PresenceColumns.PRIORITY,
+        PresenceColumns.STATUS_TEXT,
+        PresenceColumns.LAST_ONLINE
+    };
+
 }
diff --git a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapIMProvider.java b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapIMProvider.java
new file mode 100644
index 0000000..d0c642c
--- /dev/null
+++ b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapIMProvider.java
@@ -0,0 +1,689 @@
+/*
+* Copyright (C) 2015 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.mapapi;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * A base implementation of the BluetoothMapContract.
+ * A base class for a ContentProvider that allows access to Instant messages from a Bluetooth
+ * device through the Message Access Profile.
+ */
+public abstract class BluetoothMapIMProvider extends ContentProvider {
+
+    private static final String TAG = "BluetoothMapIMProvider";
+    private static final boolean D = true;
+
+    private static final int MATCH_ACCOUNT = 1;
+    private static final int MATCH_MESSAGE = 3;
+    private static final int MATCH_CONVERSATION = 4;
+    private static final int MATCH_CONVOCONTACT = 5;
+
+    protected ContentResolver mResolver;
+
+    private Uri CONTENT_URI = null;
+    private String mAuthority;
+    private UriMatcher mMatcher;
+
+    /**
+     * @return the CONTENT_URI exposed. This will be used to send out notifications.
+     */
+    abstract protected Uri getContentUri();
+
+    /**
+     * Implementation is provided by the parent class.
+     */
+    @Override
+    public void attachInfo(Context context, ProviderInfo info) {
+       mAuthority = info.authority;
+
+       mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+       mMatcher.addURI(mAuthority, BluetoothMapContract.TABLE_ACCOUNT, MATCH_ACCOUNT);
+       mMatcher.addURI(mAuthority, "#/"+ BluetoothMapContract.TABLE_MESSAGE, MATCH_MESSAGE);
+       mMatcher.addURI(mAuthority, "#/"+ BluetoothMapContract.TABLE_CONVERSATION,
+               MATCH_CONVERSATION);
+       mMatcher.addURI(mAuthority, "#/"+ BluetoothMapContract.TABLE_CONVOCONTACT,
+               MATCH_CONVOCONTACT);
+
+       // Sanity check our setup
+       if (!info.exported) {
+           throw new SecurityException("Provider must be exported");
+       }
+       // Enforce correct permissions are used
+       if (!android.Manifest.permission.BLUETOOTH_MAP.equals(info.writePermission)){
+           throw new SecurityException("Provider must be protected by " +
+                   android.Manifest.permission.BLUETOOTH_MAP);
+       }
+       if(D) Log.d(TAG,"attachInfo() mAuthority = " + mAuthority);
+
+       mResolver = context.getContentResolver();
+       super.attachInfo(context, info);
+   }
+
+    /**
+     * This function shall be called when any Account database content have changed
+     * to Notify any attached observers.
+     * @param accountId the ID of the account that changed. Null is a valid value,
+     *        if accountId is unknown or multiple accounts changed.
+     */
+    protected void onAccountChanged(String accountId) {
+        Uri newUri = null;
+
+        if(mAuthority == null){
+            return;
+        }
+        if(accountId == null){
+            newUri = BluetoothMapContract.buildAccountUri(mAuthority);
+        } else {
+            newUri = BluetoothMapContract.buildAccountUriwithId(mAuthority, accountId);
+        }
+
+        if(D) Log.d(TAG,"onAccountChanged() accountId = " + accountId + " URI: " + newUri);
+        mResolver.notifyChange(newUri, null);
+    }
+
+    /**
+     * This function shall be called when any Message database content have changed
+     * to notify any attached observers.
+     * @param accountId Null is a valid value, if accountId is unknown, but
+     *        recommended for increased performance.
+     * @param messageId Null is a valid value, if multiple messages changed or the
+     *        messageId is unknown, but recommended for increased performance.
+     */
+    protected void onMessageChanged(String accountId, String messageId) {
+        Uri newUri = null;
+
+        if(mAuthority == null){
+            return;
+        }
+        if(accountId == null){
+            newUri = BluetoothMapContract.buildMessageUri(mAuthority);
+        } else {
+            if(messageId == null)
+            {
+                newUri = BluetoothMapContract.buildMessageUri(mAuthority,accountId);
+            } else {
+                newUri = BluetoothMapContract.buildMessageUriWithId(mAuthority,accountId,
+                        messageId);
+            }
+        }
+        if(D) Log.d(TAG,"onMessageChanged() accountId = " + accountId
+                + " messageId = " + messageId + " URI: " + newUri);
+        mResolver.notifyChange(newUri, null);
+    }
+
+
+    /**
+     * This function shall be called when any Message database content have changed
+     * to notify any attached observers.
+     * @param accountId Null is a valid value, if accountId is unknown, but
+     *        recommended for increased performance.
+     * @param contactId Null is a valid value, if multiple contacts changed or the
+     *        contactId is unknown, but recommended for increased performance.
+     */
+    protected void onContactChanged(String accountId, String contactId) {
+        Uri newUri = null;
+
+        if(mAuthority == null){
+            return;
+        }
+        if(accountId == null){
+            newUri = BluetoothMapContract.buildConvoContactsUri(mAuthority);
+        } else {
+            if(contactId == null)
+            {
+                newUri = BluetoothMapContract.buildConvoContactsUri(mAuthority,accountId);
+            } else {
+                newUri = BluetoothMapContract.buildConvoContactsUriWithId(mAuthority, accountId,
+                        contactId);
+            }
+        }
+        if(D) Log.d(TAG,"onContactChanged() accountId = " + accountId
+                + " contactId = " + contactId + " URI: " + newUri);
+        mResolver.notifyChange(newUri, null);
+    }
+
+    /**
+     * Not used, this is just a dummy implementation.
+     * TODO: We might need to something intelligent here after introducing IM
+     */
+    @Override
+    public String getType(Uri uri) {
+        return "InstantMessage";
+    }
+
+    /**
+     * The MAP specification states that a delete request from MAP client is a folder shift to the
+     * 'deleted' folder.
+     * Only use case of delete() is when transparency is requested for push messages, then
+     * message should not remain in sent folder and therefore must be deleted
+     */
+    @Override
+    public int delete(Uri uri, String where, String[] selectionArgs) {
+        if (D) Log.d(TAG, "delete(): uri=" + uri.toString() );
+        int result = 0;
+
+        String table = uri.getPathSegments().get(1);
+        if(table == null)
+            throw new IllegalArgumentException("Table missing in URI");
+        // the id of the entry to be deleted from the database
+        String messageId = uri.getLastPathSegment();
+        if (messageId == null)
+            throw new IllegalArgumentException("Message ID missing in update values!");
+
+        String accountId = getAccountId(uri);
+        if (accountId == null)
+            throw new IllegalArgumentException("Account ID missing in update values!");
+
+        long callingId = Binder.clearCallingIdentity();
+        try {
+            if(table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
+                return deleteMessage(accountId, messageId);
+            } else {
+                if (D) Log.w(TAG, "Unknown table name: " + table);
+                return result;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    /**
+     * This function deletes a message.
+     * @param accountId the ID of the Account
+     * @param messageId the ID of the message to delete.
+     * @return the number of messages deleted - 0 if the message was not found.
+     */
+    abstract protected int deleteMessage(String accountId, String messageId);
+
+    /**
+     * Insert is used to add new messages to the data base.
+     * Insert message approach:
+     *   - Insert an empty message to get an _id with only a folder_id
+     *   - Open the _id for write
+     *   - Write the message content
+     *     (When the writer completes, this provider should do an update of the message)
+     */
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        String table = uri.getLastPathSegment();
+        if(table == null)
+            throw new IllegalArgumentException("Table missing in URI");
+
+        String accountId = getAccountId(uri);
+        if (accountId == null)
+            throw new IllegalArgumentException("Account ID missing in URI");
+
+        // TODO: validate values?
+
+        String id; // the id of the entry inserted into the database
+        long callingId = Binder.clearCallingIdentity();
+        Log.d(TAG, "insert(): uri=" + uri.toString() + " - getLastPathSegment() = " +
+                uri.getLastPathSegment());
+        try {
+            if(table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
+                id = insertMessage(accountId, values);
+                if(D) Log.i(TAG, "insert() ID: " + id);
+                return Uri.parse(uri.toString() + "/" + id);
+            } else {
+                Log.w(TAG, "Unknown table name: " + table);
+                return null;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+
+    /**
+     * Inserts an empty message into the Message data base in the specified folder.
+     * This is done before the actual message content is written by fileIO.
+     * @param accountId the ID of the account
+     * @param folderId the ID of the folder to create a new message in.
+     * @return the message id as a string
+     */
+    abstract protected String insertMessage(String accountId, ContentValues values);
+
+     /**
+     * Utility function to build a projection based on a projectionMap.
+     *
+     *   "btColumnName" -> "imColumnName as btColumnName" for each entry.
+     *
+     * This supports SQL statements in the column name entry.
+     * @param projection
+     * @param projectionMap <string, string>
+     * @return the converted projection
+     */
+    protected String[] convertProjection(String[] projection, Map<String,String> projectionMap) {
+        String[] newProjection = new String[projection.length];
+        for(int i = 0; i < projection.length; i++) {
+            newProjection[i] = projectionMap.get(projection[i]) + " as " + projection[i];
+        }
+        return newProjection;
+    }
+
+    /**
+     * This query needs to map from the data used in the e-mail client to
+     * BluetoothMapContract type of data.
+     */
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        long callingId = Binder.clearCallingIdentity();
+        try {
+            String accountId = null;
+            if(D)Log.w(TAG, "query(): uri =" + mAuthority + " uri=" + uri.toString());
+
+            switch (mMatcher.match(uri)) {
+                case MATCH_ACCOUNT:
+                    return queryAccount(projection, selection, selectionArgs, sortOrder);
+                case MATCH_MESSAGE:
+                    // TODO: Extract account from URI
+                    accountId = getAccountId(uri);
+                    return queryMessage(accountId, projection, selection, selectionArgs, sortOrder);
+                case MATCH_CONVERSATION:
+                    accountId = getAccountId(uri);
+                    String value;
+                    String searchString =
+                            uri.getQueryParameter(BluetoothMapContract.FILTER_ORIGINATOR_SUBSTRING);
+                    Long periodBegin = null;
+                    value = uri.getQueryParameter(BluetoothMapContract.FILTER_PERIOD_BEGIN);
+                    if(value != null) {
+                        periodBegin = Long.parseLong(value);
+                    }
+                    Long periodEnd = null;
+                    value = uri.getQueryParameter(BluetoothMapContract.FILTER_PERIOD_END);
+                    if(value != null) {
+                        periodEnd = Long.parseLong(value);
+                    }
+                    Boolean read = null;
+                    value = uri.getQueryParameter(BluetoothMapContract.FILTER_READ_STATUS);
+                    if(value != null) {
+                        read = value.equalsIgnoreCase("true");
+                    }
+                    Long threadId = null;
+                    value = uri.getQueryParameter(BluetoothMapContract.FILTER_THREAD_ID);
+                    if(value != null) {
+                        threadId = Long.parseLong(value);
+                    }
+                    return queryConversation(accountId, threadId, read, periodEnd, periodBegin,
+                            searchString, projection, sortOrder);
+                case MATCH_CONVOCONTACT:
+                    accountId = getAccountId(uri);
+                    long contactId = 0;
+                    return queryConvoContact(accountId, contactId, projection,
+                            selection, selectionArgs, sortOrder);
+                default:
+                    throw new UnsupportedOperationException("Unsupported Uri " + uri);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    /**
+     * Query account information.
+     * This function shall return only exposable e-mail accounts. Hence shall not
+     * return accounts that has policies suggesting not to be shared them.
+     * @param projection
+     * @param selection
+     * @param selectionArgs
+     * @param sortOrder
+     * @return a cursor to the accounts that are subject to exposure over BT.
+     */
+    abstract protected Cursor queryAccount(String[] projection, String selection,
+            String[] selectionArgs, String sortOrder);
+
+    /**
+     * For the message table the selection (where clause) can only include the following columns:
+     *    date: less than, greater than and equals
+     *    flagRead: = 1 or = 0
+     *    flagPriority: = 1 or = 0
+     *    folder_id: the ID of the folder only equals
+     *    toList: partial name/address search
+     *    fromList: partial name/address search
+     * Additionally the COUNT and OFFSET shall be supported.
+     * @param accountId the ID of the account
+     * @param projection
+     * @param selection
+     * @param selectionArgs
+     * @param sortOrder
+     * @return a cursor to query result
+     */
+    abstract protected Cursor queryMessage(String accountId, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder);
+
+    /**
+     * For the Conversation table the selection (where clause) can only include
+     * the following columns:
+     *    _id: the ID of the conversation only equals
+     *    name: partial name search
+     *    last_activity: less than, greater than and equals
+     *    version_counter: updated IDs are regenerated
+     * Additionally the COUNT and OFFSET shall be supported.
+     * @param accountId the ID of the account
+     * @param threadId the ID of the conversation
+     * @param projection
+     * @param selection
+     * @param selectionArgs
+     * @param sortOrder
+     * @return a cursor to query result
+     */
+//    abstract protected Cursor queryConversation(Long threadId, String[] projection,
+//    String selection, String[] selectionArgs, String sortOrder);
+
+    /**
+     * Query for conversations with contact information. The expected result is a cursor pointing
+     * to one row for each contact in a conversation.
+     * E.g.:
+     * ThreadId | ThreadName | ... | ContactName | ContactPrecence | ... |
+     *        1 |  "Bowling" | ... |        Hans |               1 | ... |
+     *        1 |  "Bowling" | ... |       Peter |               2 | ... |
+     *        2 |         "" | ... |       Peter |               2 | ... |
+     *        3 |         "" | ... |        Hans |               1 | ... |
+     *
+    * @param accountId the ID of the account
+     * @param threadId filter on a single threadId - null if no filtering is needed.
+     * @param read filter on a read status:
+     *             null: no filtering on read is needed.
+     *             true: return only threads that has NO unread messages.
+     *             false: return only threads that has unread messages.
+     * @param periodEnd   last_activity time stamp of the the newest thread to include in the
+     *                    result.
+     * @param periodBegin last_activity time stamp of the the oldest thread to include in the
+     *                    result.
+     * @param searchString if not null, include only threads that has contacts that matches the
+     *                     searchString as part of the contact name or nickName.
+     * @param projection A list of the columns that is needed in the result
+     * @param sortOrder  the sort order
+     * @return a Cursor representing the query result.
+     */
+    abstract protected Cursor queryConversation(String accountId, Long threadId, Boolean read,
+            Long periodEnd, Long periodBegin, String searchString, String[] projection,
+            String sortOrder);
+
+    /**
+     * For the ConvoContact table the selection (where clause) can only include the
+     * following columns:
+     *    _id: the ID of the contact only equals
+     *    convo_id: id of conversation contact is part of
+     *    name: partial name search
+     *    x_bt_uid: the ID of the bt uid only equals
+     *    chat_state: active, inactive, gone, composing, paused
+     *    last_active: less than, greater than and equals
+     *    presence_state: online, do_not_disturb, away, offline
+     *    priority: level of priority 0 - 100
+     *    last_online: less than, greater than and equals
+     * @param accountId the ID of the account
+     * @param contactId the ID of the contact
+     * @param projection
+     * @param selection
+     * @param selectionArgs
+     * @param sortOrder
+     * @return a cursor to query result
+     */
+    abstract protected Cursor queryConvoContact(String accountId, Long contactId,
+            String[] projection, String selection, String[] selectionArgs, String sortOrder);
+
+    /**
+     * update()
+     * Messages can be modified in the following cases:
+     *  - the folder_key of a message - hence the message can be moved to a new folder,
+     *                                  but the content cannot be modified.
+     *  - the FLAG_READ state can be changed.
+     * Conversations can be modified in the following cases:
+     *  - the read status - changing between read, unread
+     *  - the last activity - the time stamp of last message sent of received in the conversation
+     * ConvoContacts can be modified in the following cases:
+     *  - the chat_state - chat status of the contact in conversation
+     *  - the last_active - the time stamp of last action in the conversation
+     *  - the presence_state - the time stamp of last time contact online
+     *  - the status - the status text of the contact available in a conversation
+     *  - the last_online - the time stamp of last time contact online
+     * The selection statement will always be selection of a message ID, when updating a message,
+     * hence this function will be called multiple times if multiple messages must be updated
+     * due to the nature of the Bluetooth Message Access profile.
+     */
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+
+        String table = uri.getLastPathSegment();
+        if(table == null){
+            throw new IllegalArgumentException("Table missing in URI");
+        }
+        if(selection != null) {
+            throw new IllegalArgumentException("selection shall not be used, ContentValues " +
+                    "shall contain the data");
+        }
+
+        long callingId = Binder.clearCallingIdentity();
+        if(D)Log.w(TAG, "update(): uri=" + uri.toString() + " - getLastPathSegment() = " +
+                uri.getLastPathSegment());
+        try {
+            if(table.equals(BluetoothMapContract.TABLE_ACCOUNT)) {
+                String accountId = values.getAsString(BluetoothMapContract.AccountColumns._ID);
+                if(accountId == null) {
+                    throw new IllegalArgumentException("Account ID missing in update values!");
+                }
+                Integer exposeFlag = values.getAsInteger(
+                        BluetoothMapContract.AccountColumns.FLAG_EXPOSE);
+                if(exposeFlag == null){
+                    throw new IllegalArgumentException("Expose flag missing in update values!");
+                }
+                return updateAccount(accountId, exposeFlag);
+            } else if(table.equals(BluetoothMapContract.TABLE_FOLDER)) {
+                return 0; // We do not support changing folders
+            } else if(table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
+                String accountId = getAccountId(uri);
+                if(accountId == null) {
+                    throw new IllegalArgumentException("Account ID missing in update values!");
+                }
+                Long messageId = values.getAsLong(BluetoothMapContract.MessageColumns._ID);
+                if(messageId == null) {
+                    throw new IllegalArgumentException("Message ID missing in update values!");
+                }
+                Long folderId = values.getAsLong(BluetoothMapContract.MessageColumns.FOLDER_ID);
+                Boolean flagRead = values.getAsBoolean(
+                        BluetoothMapContract.MessageColumns.FLAG_READ);
+                return updateMessage(accountId, messageId, folderId, flagRead);
+            } else if(table.equals(BluetoothMapContract.TABLE_CONVERSATION)) {
+                return 0; // We do not support changing conversation
+            } else if(table.equals(BluetoothMapContract.TABLE_CONVOCONTACT)) {
+                return 0; // We do not support changing contacts
+            } else {
+                if(D)Log.w(TAG, "Unknown table name: " + table);
+                return 0;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    /**
+     * Update an entry in the account table. Only the expose flag will be
+     * changed through this interface.
+     * @param accountId the ID of the account to change.
+     * @param flagExpose the updated value.
+     * @return the number of entries changed - 0 if account not found or value cannot be changed.
+     */
+    abstract protected int updateAccount(String accountId, Integer flagExpose);
+
+    /**
+     * Update an entry in the message table.
+     * @param accountId ID of the account to which the messageId relates
+     * @param messageId the ID of the message to update
+     * @param folderId the new folder ID value to set - ignore if null.
+     * @param flagRead the new flagRead value to set - ignore if null.
+     * @return
+     */
+    abstract protected int updateMessage(String accountId, Long messageId, Long folderId,
+            Boolean flagRead);
+
+    /**
+     * Utility function to Creates a ContentValues object based on a modified valuesSet.
+     * To be used after changing the keys and optionally values of a valueSet obtained
+     * from a ContentValues object received in update().
+     * @param valueSet the values as received in the contentProvider
+     * @param keyMap the key map <btKey, emailKey>
+     * @return a new ContentValues object with the keys replaced as specified in the
+     * keyMap
+     */
+    protected ContentValues createContentValues(Set<Entry<String,Object>> valueSet,
+            Map<String, String> keyMap) {
+        ContentValues values = new ContentValues(valueSet.size());
+        for(Entry<String,Object> ent : valueSet) {
+            String key = keyMap.get(ent.getKey()); // Convert the key name
+            Object value = ent.getValue();
+            if(value == null) {
+                values.putNull(key);
+            } else if(ent.getValue() instanceof Boolean) {
+                values.put(key, (Boolean) value);
+            } else if(ent.getValue() instanceof Byte) {
+                values.put(key, (Byte) value);
+            } else if(ent.getValue() instanceof byte[]) {
+                values.put(key, (byte[]) value);
+            } else if(ent.getValue() instanceof Double) {
+                values.put(key, (Double) value);
+            } else if(ent.getValue() instanceof Float) {
+                values.put(key, (Float) value);
+            } else if(ent.getValue() instanceof Integer) {
+                values.put(key, (Integer) value);
+            } else if(ent.getValue() instanceof Long) {
+                values.put(key, (Long) value);
+            } else if(ent.getValue() instanceof Short) {
+                values.put(key, (Short) value);
+            } else if(ent.getValue() instanceof String) {
+                values.put(key, (String) value);
+            } else {
+                throw new IllegalArgumentException("Unknown data type in content value");
+            }
+        }
+        return values;
+    }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        long callingId = Binder.clearCallingIdentity();
+        if(D)Log.w(TAG, "call(): method=" + method + " arg=" + arg + "ThreadId: "
+                + Thread.currentThread().getId());
+        int ret = -1;
+        try {
+            if(method.equals(BluetoothMapContract.METHOD_UPDATE_FOLDER)) {
+                long accountId = extras.getLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, -1);
+                if(accountId == -1) {
+                    Log.w(TAG, "No account ID in CALL");
+                    return null;
+                }
+                long folderId = extras.getLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID, -1);
+                if(folderId == -1) {
+                    Log.w(TAG, "No folder ID in CALL");
+                    return null;
+                }
+                ret = syncFolder(accountId, folderId);
+            } else if (method.equals(BluetoothMapContract.METHOD_SET_OWNER_STATUS)) {
+                int presenceState = extras.getInt(BluetoothMapContract.EXTRA_PRESENCE_STATE);
+                String presenceStatus = extras.getString(
+                        BluetoothMapContract.EXTRA_PRESENCE_STATUS);
+                long lastActive = extras.getLong(BluetoothMapContract.EXTRA_LAST_ACTIVE);
+                int chatState = extras.getInt(BluetoothMapContract.EXTRA_CHAT_STATE);
+                String convoId = extras.getString(BluetoothMapContract.EXTRA_CONVERSATION_ID);
+                ret = setOwnerStatus(presenceState, presenceStatus, lastActive, chatState, convoId);
+
+            } else if (method.equals(BluetoothMapContract.METHOD_SET_BLUETOOTH_STATE)) {
+                boolean bluetoothState = extras.getBoolean(
+                        BluetoothMapContract.EXTRA_BLUETOOTH_STATE);
+                ret = setBluetoothStatus(bluetoothState);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+        if(ret == 0) {
+            return new Bundle();
+        }
+        return null;
+    }
+
+    /**
+     * Trigger a sync of the specified folder.
+     * @param accountId the ID of the account that owns the folder
+     * @param folderId the ID of the folder.
+     * @return 0 at success
+     */
+    abstract protected int syncFolder(long accountId, long folderId);
+
+    /**
+     * Set the properties that should change presence or chat state of owner
+     * e.g. when the owner is active on a BT client device but not on the BT server device
+     * where the IM application is installed, it should still be possible to show an active status.
+     * @param presenceState should follow the contract specified values
+     * @param presenceStatus string the owners current status
+     * @param lastActive time stamp of the owners last activity
+     * @param chatState should follow the contract specified values
+     * @param convoId ID to the conversation to change
+     * @return 0 at success
+     */
+    abstract protected int setOwnerStatus(int presenceState, String presenceStatus,
+            long lastActive, int chatState, String convoId);
+
+    /**
+     * Notify the application of the Bluetooth state
+     * @param bluetoothState 'on' of 'off'
+     * @return 0 at success
+     */
+    abstract protected int setBluetoothStatus(boolean bluetoothState);
+
+
+
+    /**
+     * Need this to suppress warning in unit tests.
+     */
+    @Override
+    public void shutdown() {
+        // Don't call super.shutdown(), which emits a warning...
+    }
+
+    /**
+     * Extract the BluetoothMapContract.AccountColumns._ID from the given URI.
+     */
+    public static String getAccountId(Uri uri) {
+        final List<String> segments = uri.getPathSegments();
+        if (segments.size() < 1) {
+            throw new IllegalArgumentException("No AccountId pressent in URI: " + uri);
+        }
+        return segments.get(0);
+    }
+}
diff --git a/res/layout/bluetooth_map_email_settings.xml b/res/layout/bluetooth_map_settings.xml
similarity index 78%
rename from res/layout/bluetooth_map_email_settings.xml
rename to res/layout/bluetooth_map_settings.xml
index 02acdd8..f256a02 100644
--- a/res/layout/bluetooth_map_email_settings.xml
+++ b/res/layout/bluetooth_map_settings.xml
@@ -17,21 +17,21 @@
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/bluetooth_map_email_settings_liniar_layout"
+    android:id="@+id/bluetooth_map_settings_liniar_layout"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
-    tools:context=".BluetoothMapEmailSettings" >
+    tools:context=".BluetoothMapSettings" >
 
     <TextView
-        android:id="@+id/bluetooth_map_email_settings_description_text_view"
+        android:id="@+id/bluetooth_map_settings_description_text_view"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
 
-        android:text="@string/bluetooth_map_email_settings_intro" />
+        android:text="@string/bluetooth_map_settings_intro" />
 
     <ExpandableListView
-        android:id="@+id/bluetooth_map_email_settings_list_view"
+        android:id="@+id/bluetooth_map_settings_list_view"
         android:layout_width="fill_parent"
         android:layout_height="0dip"
         android:layout_weight="3"
diff --git a/res/layout/bluetooth_map_email_settings_account_group.xml b/res/layout/bluetooth_map_settings_account_group.xml
similarity index 83%
rename from res/layout/bluetooth_map_email_settings_account_group.xml
rename to res/layout/bluetooth_map_settings_account_group.xml
index abb8de8..7bbfca4 100644
--- a/res/layout/bluetooth_map_email_settings_account_group.xml
+++ b/res/layout/bluetooth_map_settings_account_group.xml
@@ -21,7 +21,7 @@
     android:layout_height="match_parent"
     android:orientation="vertical" >
     <ImageView
-        android:id="@+id/bluetooth_map_email_settings_group_icon"
+        android:id="@+id/bluetooth_map_settings_group_icon"
         android:layout_width="64dp"
         android:layout_height="64dp"
         android:layout_alignParentLeft="true"
@@ -31,12 +31,12 @@
         android:layout_marginLeft="30dp"
          />
     <TextView
-        android:id ="@+id/bluetooth_map_email_settings_group_text_view"
+        android:id ="@+id/bluetooth_map_settings_group_text_view"
         android:layout_width ="wrap_content"
         android:layout_height ="wrap_content"
-        android:layout_toRightOf="@id/bluetooth_map_email_settings_group_icon"/>
+        android:layout_toRightOf="@id/bluetooth_map_settings_group_icon"/>
     <CheckBox
-        android:id ="@+id/bluetooth_map_email_settings_group_checkbox"
+        android:id ="@+id/bluetooth_map_settings_group_checkbox"
         android:layout_width ="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignParentRight="true"
diff --git a/res/layout/bluetooth_map_email_settings_account_item.xml b/res/layout/bluetooth_map_settings_account_item.xml
similarity index 86%
rename from res/layout/bluetooth_map_email_settings_account_item.xml
rename to res/layout/bluetooth_map_settings_account_item.xml
index f0469d5..b6fbf8a 100644
--- a/res/layout/bluetooth_map_email_settings_account_item.xml
+++ b/res/layout/bluetooth_map_settings_account_item.xml
@@ -24,17 +24,17 @@
     android:orientation="horizontal"
     android:paddingLeft="51dp"
     android:paddingRight="25dp"
-    tools:context=".BluetoothMapEmailSettings" android:baselineAligned="false">
+    tools:context=".BluetoothMapSettings" android:baselineAligned="false">
 
         <TextView
-            android:id="@+id/bluetooth_map_email_settings_item_text_view"
+            android:id="@+id/bluetooth_map_settings_item_text_view"
             android:layout_width="match_parent"
             android:layout_height="51dp"
             android:layout_weight="1"
             android:gravity="left|center_vertical"/>
 
         <CheckBox
-            android:id="@+id/bluetooth_map_email_settings_item_check"
+            android:id="@+id/bluetooth_map_settings_item_check"
             android:layout_width="110dp"
             android:layout_height="51dp"
             android:layout_gravity="right"
diff --git a/res/layout/bluetooth_transfer_item.xml b/res/layout/bluetooth_transfer_item.xml
index ffc1052..520763b 100644
--- a/res/layout/bluetooth_transfer_item.xml
+++ b/res/layout/bluetooth_transfer_item.xml
@@ -27,7 +27,7 @@
         android:layout_height="@android:dimen/app_icon_size"
         android:layout_alignParentTop="true"
         android:layout_alignParentLeft="true"
-        android:contentDescription ="@string/bluetooth_map_email_settings_app_icon"
+        android:contentDescription ="@string/bluetooth_map_settings_app_icon"
         android:scaleType="center"
     />
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f1e2ca1..df9407d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -231,11 +231,11 @@
     <string name="process" translate="false"><xliff:g id="x" /></string>
 
 
-    <string name="bluetooth_map_email_settings_save">Save</string>
-    <string name="bluetooth_map_email_settings_cancel">Cancel</string>
-    <string name="bluetooth_map_email_settings_intro">Select the email accounts you want to share through Bluetooth. You still have to accept any acceess to the accounts when connecting.</string>
-    <string name="bluetooth_map_email_settings_count">Slots left:</string>
-    <string name="bluetooth_map_email_settings_app_icon">Application Icon</string>
-    <string name="bluetooth_map_email_settings_title">Bluetooth Message Sharing Settings</string>
-    <string name="bluetooth_map_email_settings_no_account_slots_left">Cannot select account. 0 slots left</string>
+    <string name="bluetooth_map_settings_save">Save</string>
+    <string name="bluetooth_map_settings_cancel">Cancel</string>
+    <string name="bluetooth_map_settings_intro">Select the accounts you want to share through Bluetooth. You still have to accept any acceess to the accounts when connecting.</string>
+    <string name="bluetooth_map_settings_count">Slots left:</string>
+    <string name="bluetooth_map_settings_app_icon">Application Icon</string>
+    <string name="bluetooth_map_settings_title">Bluetooth Message Sharing Settings</string>
+    <string name="bluetooth_map_settings_no_account_slots_left">Cannot select account. 0 slots left</string>
 </resources>
diff --git a/src/com/android/bluetooth/BluetoothObexTransport.java b/src/com/android/bluetooth/BluetoothObexTransport.java
index 4ed5141..216ced2 100644
--- a/src/com/android/bluetooth/BluetoothObexTransport.java
+++ b/src/com/android/bluetooth/BluetoothObexTransport.java
@@ -73,14 +73,14 @@
     }
 
     public int getMaxTransmitPacketSize() {
-        if(mSocket.getConnectionType() != BluetoothSocket.TYPE_L2CAP) {
-            return -1;
+        if (mSocket.getConnectionType() != BluetoothSocket.TYPE_L2CAP) {
+           return -1;
         }
         return mSocket.getMaxTransmitPacketSize();
     }
 
     public int getMaxReceivePacketSize() {
-        if(mSocket.getConnectionType() != BluetoothSocket.TYPE_L2CAP) {
+        if (mSocket.getConnectionType() != BluetoothSocket.TYPE_L2CAP) {
             return -1;
         }
         return mSocket.getMaxReceivePacketSize();
diff --git a/src/com/android/bluetooth/SignedLongLong.java b/src/com/android/bluetooth/SignedLongLong.java
new file mode 100644
index 0000000..42fd4a8
--- /dev/null
+++ b/src/com/android/bluetooth/SignedLongLong.java
@@ -0,0 +1,126 @@
+/*
+* Copyright (C) 2015 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;
+
+import java.io.UnsupportedEncodingException;
+
+import com.android.bluetooth.map.BluetoothMapUtils;
+
+/**
+ * Class to represent a 128bit value using two long member variables.
+ * Has functionality to convert to/from hex-strings.
+ * Mind that since signed variables are used to store the value internally
+ * is used, the MSB/LSB long values can be negative.
+ */
+public class SignedLongLong implements Comparable<SignedLongLong> {
+
+    private long mMostSigBits;
+    private long mLeastSigBits;
+
+    public SignedLongLong(long leastSigBits, long mostSigBits) {
+        this.mMostSigBits = mostSigBits;
+        this.mLeastSigBits = leastSigBits;
+    }
+
+    /**
+     * Create a SignedLongLong from a Hex-String without "0x" prefix
+     * @param value the hex-string
+     * @return the created object
+     * @throws UnsupportedEncodingException
+     */
+    public static SignedLongLong fromString(String value) throws UnsupportedEncodingException {
+        String lsbStr, msbStr;
+        long lsb = 0, msb = 0;
+
+        lsbStr = msbStr = null;
+        if(value == null) throw new NullPointerException();
+        value=value.trim();
+        int valueLength = value.length();
+        if(valueLength == 0 || valueLength > 32) {
+            throw new NumberFormatException("invalid string length: " + valueLength);
+        }
+        if(valueLength <= 16){
+            lsbStr = value;
+        } else {
+            lsbStr = value.substring(valueLength-16, valueLength);
+            msbStr = value.substring(0, valueLength-16);
+            msb = BluetoothMapUtils.getLongFromString(msbStr);
+        }
+        lsb = BluetoothMapUtils.getLongFromString(lsbStr);
+        return new SignedLongLong(lsb, msb);
+    }
+
+    @Override
+    public int compareTo(SignedLongLong another) {
+        if(mMostSigBits == another.mMostSigBits) {
+            if(mLeastSigBits == another.mLeastSigBits) {
+                return 0;
+            }
+            if(mLeastSigBits < another.mLeastSigBits) {
+                return -1;
+            }
+            return 1;
+        }
+        if(mMostSigBits < another.mMostSigBits) {
+            return -1;
+        }
+        return 1;
+    }
+
+    @Override
+    public String toString() {
+        return toHexString();
+    }
+
+    /**
+     *
+     * @return a hex-string representation of the object values
+     */
+    public String toHexString(){
+        return BluetoothMapUtils.getLongLongAsString(mLeastSigBits, mMostSigBits);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        SignedLongLong other = (SignedLongLong) obj;
+        if (mLeastSigBits != other.mLeastSigBits) {
+            return false;
+        }
+        if (mMostSigBits != other.mMostSigBits) {
+            return false;
+        }
+        return true;
+    }
+
+    public long getMostSignificantBits() {
+        return mMostSigBits;
+    }
+
+    public long getLeastSignificantBits() {
+        return mLeastSigBits;
+    }
+
+}
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index 3363855..af160c1 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -174,7 +174,8 @@
     private boolean mNativeAvailable;
     private boolean mCleaningUp;
     private HashMap<String,Integer> mProfileServicesState = new HashMap<String,Integer>();
-    private RemoteCallbackList<IBluetoothCallback> mCallbacks;//Only BluetoothManagerService should be registered
+    //Only BluetoothManagerService should be registered
+    private RemoteCallbackList<IBluetoothCallback> mCallbacks;
     private int mCurrentRequestId;
     private boolean mQuietmode = false;
 
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index dd752df..e49b486 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -239,7 +239,7 @@
             type = types[j];
             val = values[j];
             if(val.length <= 0)
-                errorLog("devicePropertyChangedCallback: bdDevice: " + bdDevice 
+                errorLog("devicePropertyChangedCallback: bdDevice: " + bdDevice
                         + ", value is empty for type: " + type);
             else {
                 synchronized(mObject) {
diff --git a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsItem.java b/src/com/android/bluetooth/map/BluetoothMapAccountItem.java
similarity index 63%
rename from src/com/android/bluetooth/map/BluetoothMapEmailSettingsItem.java
rename to src/com/android/bluetooth/map/BluetoothMapAccountItem.java
index ec1e148..1a493b8 100644
--- a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsItem.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAccountItem.java
@@ -23,30 +23,50 @@
  * It can be used for both Email Apps (group Parent item) and Accounts (Group child Item).
  *
  */
-public class BluetoothMapEmailSettingsItem implements Comparable<BluetoothMapEmailSettingsItem>{
-    private static final String TAG = "BluetoothMapEmailSettingsItem";
+public class BluetoothMapAccountItem implements Comparable<BluetoothMapAccountItem>{
+    private static final String TAG = "BluetoothMapAccountItem";
 
     private static final boolean D = BluetoothMapService.DEBUG;
     private static final boolean V = BluetoothMapService.VERBOSE;
 
     protected boolean mIsChecked;
-    private String mName;
-    private String mPackageName;
-    private String mId;
-    private String mProviderAuthority;
-    private Drawable mIcon;
-    public String mBase_uri;
-    public String mBase_uri_no_account;
-    public BluetoothMapEmailSettingsItem(String id, String name, String packageName, String authority, Drawable icon) {
+    private final String mName;
+    private final String mPackageName;
+    private final String mId;
+    private final String mProviderAuthority;
+    private final Drawable mIcon;
+    private final BluetoothMapUtils.TYPE mType;
+    public final String mBase_uri;
+    public final String mBase_uri_no_account;
+    private final String mUci;
+    private final String mUciPrefix;
+
+    public BluetoothMapAccountItem(String id, String name, String packageName, String authority,
+            Drawable icon, BluetoothMapUtils.TYPE appType, String uci, String uciPrefix) {
         this.mName = name;
         this.mIcon = icon;
         this.mPackageName = packageName;
         this.mId = id;
         this.mProviderAuthority = authority;
+        this.mType = appType;
         this.mBase_uri_no_account = "content://" + authority;
         this.mBase_uri = mBase_uri_no_account + "/"+id;
+        this.mUci = uci;
+        this.mUciPrefix = uciPrefix;
     }
 
+    public static BluetoothMapAccountItem create(String id, String name, String packageName,
+            String authority, Drawable icon, BluetoothMapUtils.TYPE appType) {
+        return new BluetoothMapAccountItem(id, name, packageName, authority,
+                icon, appType, null, null);
+    }
+
+    public static BluetoothMapAccountItem create(String id, String name, String packageName,
+            String authority, Drawable icon, BluetoothMapUtils.TYPE appType, String uci,
+            String uciPrefix) {
+        return new BluetoothMapAccountItem(id, name, packageName, authority,
+                icon, appType, uci, uciPrefix);
+    }
     public long getAccountId() {
         if(mId != null) {
             return Long.parseLong(mId);
@@ -54,8 +74,24 @@
         return -1;
     }
 
+    public String getUci() {
+        return mUci;
+    }
+
+    public String getUciPrefix(){
+        return mUciPrefix;
+    }
+
+    public String getUciFull(){
+        if(mUci == null)
+            return null;
+        if(mUciPrefix == null)
+            return null;
+        return new StringBuilder(mUciPrefix).append(":").append(mUci).toString();
+    }
+
     @Override
-    public int compareTo(BluetoothMapEmailSettingsItem other) {
+    public int compareTo(BluetoothMapAccountItem other) {
 
         if(!other.mId.equals(this.mId)){
             if(V) Log.d(TAG, "Wrong id : " + this.mId + " vs " + other.mId);
@@ -66,17 +102,23 @@
             return -1;
         }
         if(!other.mPackageName.equals(this.mPackageName)){
-            if(V) Log.d(TAG, "Wrong packageName : " + this.mPackageName + " vs " + other.mPackageName);
+            if(V) Log.d(TAG, "Wrong packageName : " + this.mPackageName + " vs "
+                    + other.mPackageName);
              return -1;
         }
         if(!other.mProviderAuthority.equals(this.mProviderAuthority)){
-            if(V) Log.d(TAG, "Wrong providerName : " + this.mProviderAuthority + " vs " + other.mProviderAuthority);
+            if(V) Log.d(TAG, "Wrong providerName : " + this.mProviderAuthority + " vs " 
+                    + other.mProviderAuthority);
             return -1;
         }
         if(other.mIsChecked != this.mIsChecked){
             if(V) Log.d(TAG, "Wrong isChecked : " + this.mIsChecked + " vs " + other.mIsChecked);
             return -1;
         }
+        if(!other.mType.equals(this.mType)){
+            if(V) Log.d(TAG, "Wrong appType : " + this.mType + " vs " + other.mType);
+             return -1;
+        }
         return 0;
     }
 
@@ -101,7 +143,7 @@
             return false;
         if (getClass() != obj.getClass())
             return false;
-        BluetoothMapEmailSettingsItem other = (BluetoothMapEmailSettingsItem) obj;
+        BluetoothMapAccountItem other = (BluetoothMapAccountItem) obj;
         if (mId == null) {
             if (other.mId != null)
                 return false;
@@ -122,6 +164,11 @@
                 return false;
         } else if (!mProviderAuthority.equals(other.mProviderAuthority))
             return false;
+        if (mType == null) {
+            if (other.mType != null)
+                return false;
+        } else if (!mType.equals(other.mType))
+            return false;
         return true;
     }
 
@@ -134,39 +181,24 @@
         return mIcon;
     }
 
-    public void setIcon(Drawable icon) {
-        this.mIcon = icon;
-    }
-
     public String getName() {
         return mName;
     }
 
-    public void setName(String name) {
-        this.mName = name;
-    }
-
     public String getId() {
         return mId;
     }
 
-    public void setId(String id) {
-        this.mId = id;
-    }
-
     public String getPackageName() {
         return mPackageName;
     }
 
-    public void setPackageName(String packageName) {
-        this.mPackageName = packageName;
-    }
-
     public String getProviderAuthority() {
         return mProviderAuthority;
     }
 
-    public void setProviderAuthority(String providerAuthority) {
-        this.mProviderAuthority = providerAuthority;
+    public BluetoothMapUtils.TYPE getType() {
+        return mType;
     }
-}
\ No newline at end of file
+
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java b/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
new file mode 100644
index 0000000..5e14387
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
@@ -0,0 +1,246 @@
+/*
+* Copyright (C) 2014 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.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import com.android.bluetooth.map.BluetoothMapAccountItem;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+
+
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+import android.text.format.DateUtils;
+import android.util.Log;
+
+
+public class BluetoothMapAccountLoader {
+    private static final String TAG = "BluetoothMapAccountLoader";
+    private static final boolean D = BluetoothMapService.DEBUG;
+    private static final boolean V = BluetoothMapService.VERBOSE;
+    private Context mContext = null;
+    private PackageManager mPackageManager = null;
+    private ContentResolver mResolver;
+    private int mAccountsEnabledCount = 0;
+    private ContentProviderClient mProviderClient = null;
+    private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
+
+    public BluetoothMapAccountLoader(Context ctx)
+    {
+        mContext = ctx;
+    }
+
+    /**
+     * Method to look through all installed packages system-wide and find those that contain one of
+     * the BT-MAP intents in their manifest file. For each app the list of accounts are fetched
+     * using the method parseAccounts().
+     * @return LinkedHashMap with the packages as keys(BluetoothMapAccountItem) and
+     *          values as ArrayLists of BluetoothMapAccountItems.
+     */
+    public LinkedHashMap<BluetoothMapAccountItem,
+                         ArrayList<BluetoothMapAccountItem>> parsePackages(boolean includeIcon) {
+
+        LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> groups =
+                new LinkedHashMap<BluetoothMapAccountItem,
+                                  ArrayList<BluetoothMapAccountItem>>();
+        Intent[] searchIntents = new Intent[2];
+        //Array <Intent> searchIntents = new Array <Intent>();
+        searchIntents[0] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_EMAIL);
+        searchIntents[1] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_IM);
+        // reset the counter every time this method is called.
+        mAccountsEnabledCount=0;
+        // find all installed packages and filter out those that do not support Bluetooth Map.
+        // this is done by looking for a apps with content providers containing the intent-filter
+        // in the manifest file.
+        mPackageManager = mContext.getPackageManager();
+
+        for (Intent searchIntent : searchIntents) {
+            List<ResolveInfo> resInfos =
+                mPackageManager.queryIntentContentProviders(searchIntent, 0);
+            if (resInfos != null ) {
+                if(D) Log.d(TAG,"Found " + resInfos.size() + " application(s) with intent "
+                        + searchIntent.getAction().toString());
+                BluetoothMapUtils.TYPE msgType = (searchIntent.getAction().toString() ==
+                        BluetoothMapContract.PROVIDER_INTERFACE_EMAIL) ?
+                        BluetoothMapUtils.TYPE.EMAIL : BluetoothMapUtils.TYPE.IM;
+                for (ResolveInfo rInfo : resInfos) {
+                    if(D) Log.d(TAG,"ResolveInfo " + rInfo.toString());
+                    // We cannot rely on apps that have been force-stopped in the
+                    // application settings menu.
+                    if ((rInfo.providerInfo.applicationInfo.flags &
+                            ApplicationInfo.FLAG_STOPPED) == 0) {
+                        BluetoothMapAccountItem app = createAppItem(rInfo, includeIcon, msgType);
+                        if (app != null){
+                            ArrayList<BluetoothMapAccountItem> accounts = parseAccounts(app);
+                            // we do not want to list apps without accounts
+                            if(accounts.size() > 0)
+                            {// we need to make sure that the "select all" checkbox
+                             // is checked if all accounts in the list are checked
+                                app.mIsChecked = true;
+                                for (BluetoothMapAccountItem acc: accounts)
+                                {
+                                    if(!acc.mIsChecked)
+                                    {
+                                        app.mIsChecked = false;
+                                        break;
+                                    }
+                                }
+                                groups.put(app, accounts);
+                            }
+                        }
+                    } else {
+                        if(D)Log.d(TAG,"Ignoring force-stopped authority "
+                                + rInfo.providerInfo.authority +"\n");
+                    }
+                }
+            }
+            else {
+                if(D) Log.d(TAG,"Found no applications");
+            }
+        }
+        return groups;
+    }
+
+    public BluetoothMapAccountItem createAppItem(ResolveInfo rInfo, boolean includeIcon,
+            BluetoothMapUtils.TYPE type) {
+        String provider = rInfo.providerInfo.authority;
+        if(provider != null) {
+            String name = rInfo.loadLabel(mPackageManager).toString();
+            if(D)Log.d(TAG,rInfo.providerInfo.packageName + " - " + name +
+                            " - meta-data(provider = " + provider+")\n");
+            BluetoothMapAccountItem app = BluetoothMapAccountItem.create(
+                    "0",
+                    name,
+                    rInfo.providerInfo.packageName,
+                    provider,
+                    (includeIcon == false)? null : rInfo.loadIcon(mPackageManager),
+                    type);
+            return app;
+        }
+
+        return null;
+    }
+
+    /**
+     * Method for getting the accounts under a given contentprovider from a package.
+     * @param app The parent app object
+     * @return An ArrayList of BluetoothMapAccountItems containing all the accounts from the app
+     */
+    public ArrayList<BluetoothMapAccountItem> parseAccounts(BluetoothMapAccountItem app)  {
+        Cursor c = null;
+        if(D) Log.d(TAG,"Finding accounts for app "+app.getPackageName());
+        ArrayList<BluetoothMapAccountItem> children = new ArrayList<BluetoothMapAccountItem>();
+        // Get the list of accounts from the email apps content resolver (if possible)
+        mResolver = mContext.getContentResolver();
+        try{
+            mProviderClient = mResolver.acquireUnstableContentProviderClient(
+                    Uri.parse(app.mBase_uri_no_account));
+            if (mProviderClient == null) {
+                throw new RemoteException("Failed to acquire provider for " + app.getPackageName());
+            }
+            mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
+
+            Uri uri = Uri.parse(app.mBase_uri_no_account + "/"
+                                + BluetoothMapContract.TABLE_ACCOUNT);
+
+            if(app.getType() == TYPE.IM) {
+                c = mProviderClient.query(uri, BluetoothMapContract.BT_IM_ACCOUNT_PROJECTION,
+                        null, null, BluetoothMapContract.AccountColumns._ID+" DESC");
+            } else {
+                c = mProviderClient.query(uri, BluetoothMapContract.BT_ACCOUNT_PROJECTION,
+                        null, null, BluetoothMapContract.AccountColumns._ID+" DESC");
+            }
+        } catch (RemoteException e){
+            if(D)Log.d(TAG,"Could not establish ContentProviderClient for "+app.getPackageName()+
+                    " - returning empty account list" );
+            return children;
+        } finally {
+            mProviderClient.release();
+        }
+
+        if (c != null) {
+            c.moveToPosition(-1);
+            int idIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns._ID);
+            int dispNameIndex = c.getColumnIndex(
+                    BluetoothMapContract.AccountColumns.ACCOUNT_DISPLAY_NAME);
+            int exposeIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns.FLAG_EXPOSE);
+            int uciIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns.ACCOUNT_UCI);
+            int uciPreIndex = c.getColumnIndex(
+                    BluetoothMapContract.AccountColumns.ACCOUNT_UCI_PREFIX);
+            while (c.moveToNext()) {
+                if(D)Log.d(TAG,"Adding account " + c.getString(dispNameIndex) +
+                        " with ID " + String.valueOf(c.getInt(idIndex)));
+                String uci = null;
+                String uciPrefix = null;
+                if(app.getType() == TYPE.IM){
+                    uci = c.getString(uciIndex);
+                    uciPrefix = c.getString(uciPreIndex);
+                    if(D)Log.d(TAG,"   Account UCI " + uci);
+                }
+
+                BluetoothMapAccountItem child = BluetoothMapAccountItem.create(
+                        String.valueOf((c.getInt(idIndex))),
+                        c.getString(dispNameIndex),
+                        app.getPackageName(),
+                        app.getProviderAuthority(),
+                        null,
+                        app.getType(),
+                        uci,
+                        uciPrefix);
+
+                child.mIsChecked = (c.getInt(exposeIndex) != 0);
+                child.mIsChecked = true; // TODO: Revert when this works
+                /* update the account counter
+                 * so we can make sure that not to many accounts are checked. */
+                if(child.mIsChecked)
+                {
+                    mAccountsEnabledCount++;
+                }
+                children.add(child);
+            }
+            c.close();
+        } else {
+            if(D)Log.d(TAG, "query failed");
+        }
+        return children;
+    }
+    /**
+     * Gets the number of enabled accounts in total across all supported apps.
+     * NOTE that this method should not be called before the parsePackages method
+     * has been successfully called.
+     * @return number of enabled accounts
+     */
+    public int getAccountsEnabledCount() {
+        if(D)Log.d(TAG,"Enabled Accounts count:"+ mAccountsEnabledCount);
+        return mAccountsEnabledCount;
+    }
+
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppObserver.java b/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
new file mode 100644
index 0000000..4b3ecc1
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
@@ -0,0 +1,331 @@
+/*
+* Copyright (C) 2014 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.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+import android.util.Log;
+
+/**
+ * Class to construct content observers for for email applications on the system.
+ *
+ *
+ */
+
+public class BluetoothMapAppObserver{
+
+    private static final String TAG = "BluetoothMapAppObserver";
+
+    private static final boolean D = BluetoothMapService.DEBUG;
+    private static final boolean V = BluetoothMapService.VERBOSE;
+    /*  */
+    private LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> mFullList;
+    private LinkedHashMap<String,ContentObserver> mObserverMap =
+            new LinkedHashMap<String,ContentObserver>();
+    private ContentResolver mResolver;
+    private Context mContext;
+    private BroadcastReceiver mReceiver;
+    private PackageManager mPackageManager = null;
+    BluetoothMapAccountLoader mLoader;
+    BluetoothMapService mMapService = null;
+
+    public BluetoothMapAppObserver(final Context context, BluetoothMapService mapService) {
+        mContext    = context;
+        mMapService = mapService;
+        mResolver   = context.getContentResolver();
+        mLoader     = new BluetoothMapAccountLoader(mContext);
+        mFullList   = mLoader.parsePackages(false); /* Get the current list of apps */
+        createReceiver();
+        initObservers();
+    }
+
+
+    private BluetoothMapAccountItem getApp(String authoritiesName) {
+        if(V) Log.d(TAG, "getApp(): Looking for " + authoritiesName);
+        for(BluetoothMapAccountItem app:mFullList.keySet()){
+            if(V) Log.d(TAG, "  Comparing: " + app.getProviderAuthority());
+            if(app.getProviderAuthority().equals(authoritiesName)) {
+                if(V) Log.d(TAG, "  found " + app.mBase_uri_no_account);
+                return app;
+            }
+        }
+        if(V) Log.d(TAG, "  NOT FOUND!");
+        return null;
+    }
+
+    private void handleAccountChanges(String packageNameWithProvider) {
+
+        if(D)Log.d(TAG,"handleAccountChanges (packageNameWithProvider: "
+                        +packageNameWithProvider+"\n");
+        //String packageName = packageNameWithProvider.replaceFirst("\\.[^\\.]+$", "");
+        BluetoothMapAccountItem app = getApp(packageNameWithProvider);
+        if(app != null) {
+            ArrayList<BluetoothMapAccountItem> newAccountList = mLoader.parseAccounts(app);
+            ArrayList<BluetoothMapAccountItem> oldAccountList = mFullList.get(app);
+            ArrayList<BluetoothMapAccountItem> addedAccountList =
+                    (ArrayList<BluetoothMapAccountItem>)newAccountList.clone();
+            ArrayList<BluetoothMapAccountItem> removedAccountList = mFullList.get(app);
+            // Same as oldAccountList.clone
+
+            mFullList.put(app, newAccountList);
+            for(BluetoothMapAccountItem newAcc: newAccountList){
+                for(BluetoothMapAccountItem oldAcc: oldAccountList){
+                    if(newAcc.getId() == oldAcc.getId()){
+                        // For each match remove from both removed and added lists
+                        removedAccountList.remove(oldAcc);
+                        addedAccountList.remove(newAcc);
+                        if(!newAcc.getName().equals(oldAcc.getName()) && newAcc.mIsChecked){
+                            // Name Changed and the acc is visible - Change Name in SDP record
+                            mMapService.updateMasInstances(
+                                    BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED);
+                            if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED");
+                        }
+                        if(newAcc.mIsChecked != oldAcc.mIsChecked) {
+                            // Visibility changed
+                            if(newAcc.mIsChecked){
+                                // account added - create SDP record
+                                mMapService.updateMasInstances(
+                                        BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
+                                if(V)Log.d(TAG, "UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " +
+                                        "isChecked changed");
+                            } else {
+                                // account removed - remove SDP record
+                                mMapService.updateMasInstances(
+                                        BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
+                                if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " +
+                                        "isChecked changed");
+                            }
+                        }
+                        break;
+                    }
+                }
+            }
+            // Notify on any removed accounts
+            for(BluetoothMapAccountItem removedAcc: removedAccountList){
+                mMapService.updateMasInstances(
+                        BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
+                if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " + removedAcc);
+            }
+            // Notify on any new accounts
+            for(BluetoothMapAccountItem addedAcc: addedAccountList){
+                mMapService.updateMasInstances(
+                        BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
+                if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " + addedAcc);
+            }
+
+        } else {
+            Log.e(TAG, "Received change notification on package not registered for notifications!");
+
+        }
+    }
+
+    /**
+     * Adds a new content observer to the list of content observers.
+     * The key for the observer is the uri as string
+     * @param uri uri for the package that supports MAP email
+     */
+
+    public void registerObserver(BluetoothMapAccountItem app) {
+        Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
+        if (V) Log.d(TAG, "registerObserver for URI "+uri.toString()+"\n");
+        ContentObserver observer = new ContentObserver(new Handler()) {
+            @Override
+            public void onChange(boolean selfChange) {
+                onChange(selfChange, null);
+            }
+
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId()
+                        + " Uri: " + uri + " selfchange: " + selfChange);
+                if(uri != null) {
+                    handleAccountChanges(uri.getHost());
+                } else {
+                    Log.e(TAG, "Unable to handle change as the URI is NULL!");
+                }
+
+            }
+        };
+        mObserverMap.put(uri.toString(), observer);
+        mResolver.registerContentObserver(uri, true, observer);
+    }
+
+    public void unregisterObserver(BluetoothMapAccountItem app) {
+        Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
+        if (V) Log.d(TAG, "unregisterObserver("+uri.toString()+")\n");
+        mResolver.unregisterContentObserver(mObserverMap.get(uri.toString()));
+        mObserverMap.remove(uri.toString());
+    }
+
+    private void initObservers(){
+        if(D)Log.d(TAG,"initObservers()");
+        for(BluetoothMapAccountItem app: mFullList.keySet()){
+            registerObserver(app);
+        }
+    }
+
+    private void deinitObservers(){
+        if(D)Log.d(TAG,"deinitObservers()");
+        for(BluetoothMapAccountItem app: mFullList.keySet()){
+            unregisterObserver(app);
+        }
+    }
+
+    private void createReceiver(){
+        if(D)Log.d(TAG,"createReceiver()\n");
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        intentFilter.addDataScheme("package");
+        mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if(D)Log.d(TAG,"onReceive\n");
+                String action = intent.getAction();
+
+                if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+                    Uri data = intent.getData();
+                    String packageName = data.getEncodedSchemeSpecificPart();
+                    if(D)Log.d(TAG,"The installed package is: "+ packageName);
+
+                    BluetoothMapUtils.TYPE msgType = BluetoothMapUtils.TYPE.NONE;
+                    ResolveInfo resolveInfo = null;
+                    Intent[] searchIntents = new Intent[2];
+                    //Array <Intent> searchIntents = new Array <Intent>();
+                    searchIntents[0] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_EMAIL);
+                    searchIntents[1] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_IM);
+                    // Find all installed packages and filter out those that support Bluetooth Map.
+
+                    mPackageManager = mContext.getPackageManager();
+
+                    for (Intent searchIntent : searchIntents) {
+                        List<ResolveInfo> resInfos =
+                                mPackageManager.queryIntentContentProviders(searchIntent, 0);
+                        if (resInfos != null ) {
+                            if(D) Log.d(TAG,"Found " + resInfos.size()
+                                    + " application(s) with intent "
+                                    + searchIntent.getAction().toString());
+                            for (ResolveInfo rInfo : resInfos) {
+                                if(rInfo != null) {
+                                    // Find out if package contain Bluetooth MAP support
+                                    if (packageName.equals(rInfo.providerInfo.packageName)) {
+                                        resolveInfo = rInfo;
+                                        if(searchIntent.getAction() ==
+                                                BluetoothMapContract.PROVIDER_INTERFACE_EMAIL){
+                                            msgType = BluetoothMapUtils.TYPE.EMAIL;
+                                        } else if (searchIntent.getAction() ==
+                                                BluetoothMapContract.PROVIDER_INTERFACE_IM){
+                                            msgType = BluetoothMapUtils.TYPE.IM;
+                                        }
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    // if application found with Bluetooth MAP support add to list
+                    if(resolveInfo != null) {
+                        if(D) Log.d(TAG,"Found " + resolveInfo.providerInfo.packageName
+                                + " application of type " + msgType);
+                        BluetoothMapAccountItem app = mLoader.createAppItem(resolveInfo,
+                                false, msgType);
+                        if(app != null) {
+                            registerObserver(app);
+                            // Add all accounts to mFullList
+                            ArrayList<BluetoothMapAccountItem> newAccountList =
+                                    mLoader.parseAccounts(app);
+                            mFullList.put(app, newAccountList);
+                        }
+                    }
+
+                }
+                else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+                    Uri data = intent.getData();
+                    String packageName = data.getEncodedSchemeSpecificPart();
+                    if(D)Log.d(TAG,"The removed package is: "+ packageName);
+                    BluetoothMapAccountItem app = getApp(packageName);
+                    /* Find the object and remove from fullList */
+                    if(app != null) {
+                        unregisterObserver(app);
+                        mFullList.remove(app);
+                    }
+                }
+            }
+        };
+        mContext.registerReceiver(mReceiver,intentFilter);
+    }
+
+    private void removeReceiver(){
+        if(D)Log.d(TAG,"removeReceiver()\n");
+        mContext.unregisterReceiver(mReceiver);
+    }
+
+    /**
+     * Method to get a list of the accounts (across all apps) that are set to be shared
+     * through MAP.
+     * @return Arraylist<BluetoothMapAccountItem> containing all enabled accounts
+     */
+    public ArrayList<BluetoothMapAccountItem> getEnabledAccountItems(){
+        if(D)Log.d(TAG,"getEnabledAccountItems()\n");
+        ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>();
+        for(BluetoothMapAccountItem app:mFullList.keySet()){
+            ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
+            for(BluetoothMapAccountItem acc: accountList){
+                if(acc.mIsChecked) {
+                    list.add(acc);
+                }
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Method to get a list of the accounts (across all apps).
+     * @return Arraylist<BluetoothMapAccountItem> containing all accounts
+     */
+    public ArrayList<BluetoothMapAccountItem> getAllAccountItems(){
+        if(D)Log.d(TAG,"getAllAccountItems()\n");
+        ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>();
+        for(BluetoothMapAccountItem app:mFullList.keySet()){
+            ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
+            list.addAll(accountList);
+        }
+        return list;
+    }
+
+
+    /**
+     * Cleanup all resources - must be called to avoid leaks.
+     */
+    public void shutdown() {
+        deinitObservers();
+        removeReceiver();
+    }
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppParams.java b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
index df05612..4aaed63 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAppParams.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
@@ -21,9 +21,12 @@
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Date;
+import java.util.UUID;
 
 import android.util.Log;
 
+import com.android.bluetooth.SignedLongLong;
+
 /**
  * This class encapsulates the appParams needed for MAP.
  */
@@ -32,99 +35,148 @@
     private static final String TAG = "BluetoothMapAppParams";
 
     private static final int MAX_LIST_COUNT           = 0x01;
-    private static final int MAX_LIST_COUNT_LEN       = 0x02; //, 0x0000, 0xFFFF),
     private static final int START_OFFSET             = 0x02;
-    private static final int START_OFFSET_LEN         = 0x02; //, 0x0000, 0xFFFF),
     private static final int FILTER_MESSAGE_TYPE      = 0x03;
-    private static final int FILTER_MESSAGE_TYPE_LEN  = 0x01; //, 0x0000, 0x000f),
     private static final int FILTER_PERIOD_BEGIN      = 0x04;
     private static final int FILTER_PERIOD_END        = 0x05;
     private static final int FILTER_READ_STATUS       = 0x06;
-    private static final int FILTER_READ_STATUS_LEN   = 0x01; //, 0x0000, 0x0002),
     private static final int FILTER_RECIPIENT         = 0x07;
     private static final int FILTER_ORIGINATOR        = 0x08;
     private static final int FILTER_PRIORITY          = 0x09;
-    private static final int FILTER_PRIORITY_LEN      = 0x01; //, 0x0000, 0x0002),
     private static final int ATTACHMENT               = 0x0A;
-    private static final int ATTACHMENT_LEN           = 0x01; //, 0x0000, 0x0001),
     private static final int TRANSPARENT              = 0x0B;
-    private static final int TRANSPARENT_LEN          = 0x01; //, 0x0000, 0x0001),
     private static final int RETRY                    = 0x0C;
-    private static final int RETRY_LEN                = 0x01; //, 0x0000, 0x0001),
     private static final int NEW_MESSAGE              = 0x0D;
-    private static final int NEW_MESSAGE_LEN          = 0x01; //, 0x0000, 0x0001),
     private static final int NOTIFICATION_STATUS      = 0x0E;
-    private static final int NOTIFICATION_STATUS_LEN  = 0x01; //, 0x0000, 0xFFFF),
     private static final int MAS_INSTANCE_ID          = 0x0F;
-    private static final int MAS_INSTANCE_ID_LEN      = 0x01; //, 0x0000, 0x00FF),
     private static final int PARAMETER_MASK           = 0x10;
-    private static final int PARAMETER_MASK_LEN       = 0x04; //, 0x0000, 0x0000),
     private static final int FOLDER_LISTING_SIZE      = 0x11;
-    private static final int FOLDER_LISTING_SIZE_LEN  = 0x02; //, 0x0000, 0xFFFF),
     private static final int MESSAGE_LISTING_SIZE     = 0x12;
-    private static final int MESSAGE_LISTING_SIZE_LEN = 0x02; //, 0x0000, 0xFFFF),
     private static final int SUBJECT_LENGTH           = 0x13;
-    private static final int SUBJECT_LENGTH_LEN       = 0x01; //, 0x0000, 0x00FF),
     private static final int CHARSET                  = 0x14;
-    private static final int CHARSET_LEN              = 0x01; //, 0x0000, 0x0001),
     private static final int FRACTION_REQUEST         = 0x15;
-    private static final int FRACTION_REQUEST_LEN     = 0x01; //, 0x0000, 0x0001),
     private static final int FRACTION_DELIVER         = 0x16;
-    private static final int FRACTION_DELIVER_LEN     = 0x01; //, 0x0000, 0x0001),
     private static final int STATUS_INDICATOR         = 0x17;
-    private static final int STATUS_INDICATOR_LEN     = 0x01; //, 0x0000, 0x0001),
     private static final int STATUS_VALUE             = 0x18;
-    private static final int STATUS_VALUE_LEN         = 0x01; //, 0x0000, 0x0001),
     private static final int MSE_TIME                 = 0x19;
+    private static final int DATABASE_INDETIFIER      = 0x1A;
+    private static final int CONVO_LIST_VER_COUNTER   = 0x1B;
+    private static final int PRESENCE_AVAILABLE       = 0x1C;
+    private static final int PRESENCE_TEXT            = 0x1D;
+    private static final int LAST_ACTIVITY            = 0x1E;
+    private static final int CHAT_STATE               = 0x1F;
+    private static final int FILTER_CONVO_ID          = 0x20;
+    private static final int CONVO_LISTING_SIZE       = 0x21;
+    private static final int FILTER_PRESENCE          = 0x22;
+    private static final int FILTER_UID_PRESENT       = 0x23;
+    private static final int CHAT_STATE_CONVO_ID      = 0x24;
+    private static final int FOLDER_VER_COUNTER       = 0x25;
+    private static final int FILTER_MESSAGE_HANDLE    = 0x26;
+    private static final int NOTIFICATION_FILTER      = 0x27;
+    private static final int CONVO_PARAMETER_MASK     = 0x28;
 
-    public static final int INVALID_VALUE_PARAMETER = -1;
-    public static final int NOTIFICATION_STATUS_NO = 0;
-    public static final int NOTIFICATION_STATUS_YES = 1;
-    public static final int STATUS_INDICATOR_READ = 0;
-    public static final int STATUS_INDICATOR_DELETED = 1;
-    public static final int STATUS_VALUE_YES = 1;
-    public static final int STATUS_VALUE_NO = 0;
-    public static final int CHARSET_NATIVE = 0;
-    public static final int CHARSET_UTF8 = 1;
-    public static final int FRACTION_REQUEST_FIRST = 0;
-    public static final int FRACTION_REQUEST_NEXT = 1;
-    public static final int FRACTION_DELIVER_MORE = 0;
-    public static final int FRACTION_DELIVER_LAST = 1;
+    // Length defined for Application Parameters
+    private static final int MAX_LIST_COUNT_LEN       = 0x02; //, 0x0000, 0xFFFF),
+    private static final int START_OFFSET_LEN         = 0x02; //, 0x0000, 0xFFFF),
+    private static final int FILTER_MESSAGE_TYPE_LEN  = 0x01; //, 0x0000, 0x000f),
+    private static final int FILTER_READ_STATUS_LEN   = 0x01; //, 0x0000, 0x0002),
+    private static final int FILTER_PRIORITY_LEN      = 0x01; //, 0x0000, 0x0002),
+    private static final int ATTACHMENT_LEN           = 0x01; //, 0x0000, 0x0001),
+    private static final int TRANSPARENT_LEN          = 0x01; //, 0x0000, 0x0001),
+    private static final int RETRY_LEN                = 0x01; //, 0x0000, 0x0001),
+    private static final int NEW_MESSAGE_LEN          = 0x01; //, 0x0000, 0x0001),
+    private static final int NOTIFICATION_STATUS_LEN  = 0x01; //, 0x0000, 0xFFFF),
+    private static final int MAS_INSTANCE_ID_LEN      = 0x01; //, 0x0000, 0x00FF),
+    private static final int PARAMETER_MASK_LEN       = 0x04; //, 0x0000, 0x0000),
+    private static final int FOLDER_LISTING_SIZE_LEN  = 0x02; //, 0x0000, 0xFFFF),
+    private static final int MESSAGE_LISTING_SIZE_LEN = 0x02; //, 0x0000, 0xFFFF),
+    private static final int SUBJECT_LENGTH_LEN       = 0x01; //, 0x0000, 0x00FF),
+    private static final int CHARSET_LEN              = 0x01; //, 0x0000, 0x0001),
+    private static final int FRACTION_REQUEST_LEN     = 0x01; //, 0x0000, 0x0001),
+    private static final int FRACTION_DELIVER_LEN     = 0x01; //, 0x0000, 0x0001),
+    private static final int STATUS_INDICATOR_LEN     = 0x01; //, 0x0000, 0x0001),
+    private static final int STATUS_VALUE_LEN         = 0x01; //, 0x0000, 0x0001),
+    private static final int DATABASE_INDETIFIER_LEN  = 0x10;
+    private static final int CONVO_LIST_VER_COUNTER_LEN = 0x10;
+    private static final int PRESENCE_AVAILABLE_LEN   = 0X01;
+    private static final int CHAT_STATE_LEN           = 0x01;
+    private static final int CHAT_STATE_CONVO_ID_LEN  = 0x10;
+    private static final int FILTER_CONVO_ID_LEN      = 0x20;
+    private static final int CONVO_LISTING_SIZE_LEN   = 0x02;
+    private static final int FILTER_PRESENCE_LEN      = 0x01;
+    private static final int FILTER_UID_PRESENT_LEN   = 0x01;
+    private static final int FOLDER_VER_COUNTER_LEN   = 0x10;
+    private static final int FILTER_MESSAGE_HANDLE_LEN= 0x10;
+    private static final int NOTIFICATION_FILTER_LEN  = 0x04;
+    private static final int CONVO_PARAMETER_MASK_LEN = 0x04;
 
+    // Default values
+    public static final int INVALID_VALUE_PARAMETER     =-1;
+    public static final int NOTIFICATION_STATUS_NO      = 0;
+    public static final int NOTIFICATION_STATUS_YES     = 1;
+    public static final int STATUS_INDICATOR_READ       = 0;
+    public static final int STATUS_INDICATOR_DELETED    = 1;
+    public static final int STATUS_VALUE_YES            = 1;
+    public static final int STATUS_VALUE_NO             = 0;
+    public static final int CHARSET_NATIVE              = 0;
+    public static final int CHARSET_UTF8                = 1;
+    public static final int FRACTION_REQUEST_FIRST      = 0;
+    public static final int FRACTION_REQUEST_NEXT       = 1;
+    public static final int FRACTION_DELIVER_MORE       = 0;
+    public static final int FRACTION_DELIVER_LAST       = 1;
 
-    public static final int FILTER_NO_SMS_GSM  = 0x01;
-    public static final int FILTER_NO_SMS_CDMA = 0x02;
-    public static final int FILTER_NO_EMAIL    = 0x04;
-    public static final int FILTER_NO_MMS      = 0x08;
+    public static final int FILTER_NO_SMS_GSM    = 0x01;
+    public static final int FILTER_NO_SMS_CDMA   = 0x02;
+    public static final int FILTER_NO_EMAIL      = 0x04;
+    public static final int FILTER_NO_MMS        = 0x08;
+    public static final int FILTER_NO_IM         = 0x10;
+    public static final int FILTER_MSG_TYPE_MASK = 0x1F;
 
-    /* Default values for omitted application parameters */
-    public static final long PARAMETER_MASK_ALL_ENABLED = 0xFFFF; // TODO: Update when bit 16-31 will be used.
-
-    private int mMaxListCount        = INVALID_VALUE_PARAMETER;
-    private int mStartOffset         = INVALID_VALUE_PARAMETER;
-    private int mFilterMessageType   = INVALID_VALUE_PARAMETER;
-    private long mFilterPeriodBegin  = INVALID_VALUE_PARAMETER;
-    private long mFilterPeriodEnd    = INVALID_VALUE_PARAMETER;
-    private int mFilterReadStatus    = INVALID_VALUE_PARAMETER;
-    private String mFilterRecipient   = null;
-    private String mFilterOriginator  = null;
-    private int mFilterPriority      = INVALID_VALUE_PARAMETER;
-    private int mAttachment          = INVALID_VALUE_PARAMETER;
-    private int mTransparent         = INVALID_VALUE_PARAMETER;
-    private int mRetry               = INVALID_VALUE_PARAMETER;
-    private int mNewMessage          = INVALID_VALUE_PARAMETER;
-    private int mNotificationStatus  = INVALID_VALUE_PARAMETER;
-    private int mMasInstanceId       = INVALID_VALUE_PARAMETER;
-    private long mParameterMask      = INVALID_VALUE_PARAMETER;
-    private int mFolderListingSize   = INVALID_VALUE_PARAMETER;
-    private int mMessageListingSize  = INVALID_VALUE_PARAMETER;
-    private int mSubjectLength       = INVALID_VALUE_PARAMETER;
-    private int mCharset             = INVALID_VALUE_PARAMETER;
-    private int mFractionRequest     = INVALID_VALUE_PARAMETER;
-    private int mFractionDeliver     = INVALID_VALUE_PARAMETER;
-    private int mStatusIndicator     = INVALID_VALUE_PARAMETER;
-    private int mStatusValue         = INVALID_VALUE_PARAMETER;
-    private long mMseTime            = INVALID_VALUE_PARAMETER;
+    private int mMaxListCount                   = INVALID_VALUE_PARAMETER;
+    private int mStartOffset                    = INVALID_VALUE_PARAMETER;
+    private int mFilterMessageType              = INVALID_VALUE_PARAMETER;
+    // It seems like these are not implemented...
+    private long mFilterPeriodBegin             = INVALID_VALUE_PARAMETER;
+    private long mFilterPeriodEnd               = INVALID_VALUE_PARAMETER;
+    private int mFilterReadStatus               = INVALID_VALUE_PARAMETER;
+    private String mFilterRecipient             = null;
+    private String mFilterOriginator            = null;
+    private int mFilterPriority                 = INVALID_VALUE_PARAMETER;
+    private int mAttachment                     = INVALID_VALUE_PARAMETER;
+    private int mTransparent                    = INVALID_VALUE_PARAMETER;
+    private int mRetry                          = INVALID_VALUE_PARAMETER;
+    private int mNewMessage                     = INVALID_VALUE_PARAMETER;
+    private int mNotificationStatus             = INVALID_VALUE_PARAMETER;
+    private long mNotificationFilter            = INVALID_VALUE_PARAMETER;
+    private int mMasInstanceId                  = INVALID_VALUE_PARAMETER;
+    private long mParameterMask                 = INVALID_VALUE_PARAMETER;
+    private int mFolderListingSize              = INVALID_VALUE_PARAMETER;
+    private int mMessageListingSize             = INVALID_VALUE_PARAMETER;
+    private int mConvoListingSize               = INVALID_VALUE_PARAMETER;
+    private int mSubjectLength                  = INVALID_VALUE_PARAMETER;
+    private int mCharset                        = INVALID_VALUE_PARAMETER;
+    private int mFractionRequest                = INVALID_VALUE_PARAMETER;
+    private int mFractionDeliver                = INVALID_VALUE_PARAMETER;
+    private int mStatusIndicator                = INVALID_VALUE_PARAMETER;
+    private int mStatusValue                    = INVALID_VALUE_PARAMETER;
+    private long mMseTime                       = INVALID_VALUE_PARAMETER;
+    // TODO: Change to use SignedLongLong?
+    private long mConvoListingVerCounterLow     = INVALID_VALUE_PARAMETER;
+    private long mConvoListingVerCounterHigh    = INVALID_VALUE_PARAMETER;
+    private long mDatabaseIdentifierLow         = INVALID_VALUE_PARAMETER;
+    private long mDatabaseIdentifierHigh        = INVALID_VALUE_PARAMETER;
+    private long mFolderVerCounterLow           = INVALID_VALUE_PARAMETER;
+    private long mFolderVerCounterHigh          = INVALID_VALUE_PARAMETER;
+    private int mPresenceAvailability           = INVALID_VALUE_PARAMETER;
+    private String mPresenceStatus              = null;
+    private long mLastActivity                  = INVALID_VALUE_PARAMETER;
+    private int mChatState                      = INVALID_VALUE_PARAMETER;
+    private SignedLongLong mFilterConvoId       = null;
+    private int mFilterPresence                 = INVALID_VALUE_PARAMETER;
+    private int mFilterUidPresent               = INVALID_VALUE_PARAMETER;
+    private SignedLongLong mChatStateConvoId    = null;
+    private long mFilterMsgHandle               = INVALID_VALUE_PARAMETER;
+    private long mConvoParameterMask            = INVALID_VALUE_PARAMETER;
 
     /**
      * Default constructor, used to build an application parameter object to be
@@ -158,7 +210,7 @@
     }
 
     /**
-     * Parse an application parameter OBEX header stored in a ByteArray.
+     * Parse an application parameter OBEX header stored in a byte array.
      *
      * @param appParams
      *            the byte array containing the application parameters OBEX
@@ -183,185 +235,319 @@
                 if (tagLength != MAX_LIST_COUNT_LEN) {
                     Log.w(TAG, "MAX_LIST_COUNT: Wrong length received: " + tagLength
                                + " expected: " + MAX_LIST_COUNT_LEN);
-                    break;
+                } else {
+                    setMaxListCount(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
                 }
-                setMaxListCount(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
                 break;
             case START_OFFSET:
                 if (tagLength != START_OFFSET_LEN) {
                     Log.w(TAG, "START_OFFSET: Wrong length received: " + tagLength + " expected: "
                                + START_OFFSET_LEN);
-                    break;
+                } else {
+                    setStartOffset(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
                 }
-                setStartOffset(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
                 break;
             case FILTER_MESSAGE_TYPE:
                 if (tagLength != FILTER_MESSAGE_TYPE_LEN) {
-                    Log.w(TAG, "FILTER_MESSAGE_TYPE: Wrong length received: " + tagLength + " expected: "
-                            + FILTER_MESSAGE_TYPE_LEN);
-                    break;
+                    Log.w(TAG, "FILTER_MESSAGE_TYPE: Wrong length received: " + tagLength
+                            + " expected: " + FILTER_MESSAGE_TYPE_LEN);
+                } else {
+                    setFilterMessageType(appParams[i] & 0x1f);
                 }
-                    setFilterMessageType(appParams[i] & 0x0f);
                 break;
             case FILTER_PERIOD_BEGIN:
                 if(tagLength != 0) {
                     setFilterPeriodBegin(new String(appParams, i, tagLength));
+                } else {
+                    Log.w(TAG, "FILTER_PERIOD_BEGIN: Wrong length received: " + tagLength +
+                            " expected to be more than 0");
                 }
                 break;
             case FILTER_PERIOD_END:
                 if(tagLength != 0) {
                     setFilterPeriodEnd(new String(appParams, i, tagLength));
+                } else {
+                    Log.w(TAG, "FILTER_PERIOD_END: Wrong length received: " + tagLength +
+                            " expected to be more than 0");
                 }
                 break;
             case FILTER_READ_STATUS:
                 if (tagLength != FILTER_READ_STATUS_LEN) {
-                     Log.w(TAG, "FILTER_READ_STATUS: Wrong length received: " + tagLength + " expected: "
-                             + FILTER_READ_STATUS_LEN);
-                     break;
-                 }
-                setFilterReadStatus(appParams[i] & 0x03); // Lower two bits
+                     Log.w(TAG, "FILTER_READ_STATUS: Wrong length received: " + tagLength +
+                             " expected: " + FILTER_READ_STATUS_LEN);
+                } else {
+                    setFilterReadStatus(appParams[i] & 0x03); // Lower two bits
+                }
                 break;
             case FILTER_RECIPIENT:
                 if(tagLength != 0) {
                     setFilterRecipient(new String(appParams, i, tagLength));
+                } else {
+                    Log.w(TAG, "FILTER_RECIPIENT: Wrong length received: " + tagLength +
+                            " expected to be more than 0");
                 }
                 break;
             case FILTER_ORIGINATOR:
                 if(tagLength != 0) {
                     setFilterOriginator(new String(appParams, i, tagLength));
+                } else {
+                    Log.w(TAG, "FILTER_ORIGINATOR: Wrong length received: " + tagLength +
+                            " expected to be more than 0");
                 }
                 break;
             case FILTER_PRIORITY:
                 if (tagLength != FILTER_PRIORITY_LEN) {
-                     Log.w(TAG, "FILTER_PRIORITY: Wrong length received: " + tagLength + " expected: "
-                             + FILTER_PRIORITY_LEN);
-                     break;
+                     Log.w(TAG, "FILTER_PRIORITY: Wrong length received: " + tagLength +
+                             " expected: " + FILTER_PRIORITY_LEN);
+                } else {
+                    setFilterPriority(appParams[i] & 0x03); // Lower two bits
                 }
-                setFilterPriority(appParams[i] & 0x03); // Lower two bits
                 break;
             case ATTACHMENT:
                 if (tagLength != ATTACHMENT_LEN) {
                      Log.w(TAG, "ATTACHMENT: Wrong length received: " + tagLength + " expected: "
                              + ATTACHMENT_LEN);
-                     break;
+                } else {
+                    setAttachment(appParams[i] & 0x01); // Lower bit
                 }
-                setAttachment(appParams[i] & 0x01); // Lower bit
                 break;
             case TRANSPARENT:
                 if (tagLength != TRANSPARENT_LEN) {
                      Log.w(TAG, "TRANSPARENT: Wrong length received: " + tagLength + " expected: "
                              + TRANSPARENT_LEN);
-                     break;
+                } else {
+                    setTransparent(appParams[i] & 0x01); // Lower bit
                 }
-                setTransparent(appParams[i] & 0x01); // Lower bit
                 break;
             case RETRY:
                 if (tagLength != RETRY_LEN) {
                      Log.w(TAG, "RETRY: Wrong length received: " + tagLength + " expected: "
                              + RETRY_LEN);
-                     break;
+                } else {
+                    setRetry(appParams[i] & 0x01); // Lower bit
                 }
-                setRetry(appParams[i] & 0x01); // Lower bit
                 break;
             case NEW_MESSAGE:
                 if (tagLength != NEW_MESSAGE_LEN) {
                      Log.w(TAG, "NEW_MESSAGE: Wrong length received: " + tagLength + " expected: "
                              + NEW_MESSAGE_LEN);
-                     break;
+                } else {
+                    setNewMessage(appParams[i] & 0x01); // Lower bit
                 }
-                setNewMessage(appParams[i] & 0x01); // Lower bit
                 break;
             case NOTIFICATION_STATUS:
                 if (tagLength != NOTIFICATION_STATUS_LEN) {
-                     Log.w(TAG, "NOTIFICATION_STATUS: Wrong length received: " + tagLength + " expected: "
-                             + NOTIFICATION_STATUS_LEN);
-                     break;
+                     Log.w(TAG, "NOTIFICATION_STATUS: Wrong length received: " + tagLength +
+                             " expected: " + NOTIFICATION_STATUS_LEN);
+                } else {
+                    setNotificationStatus(appParams[i] & 0x01); // Lower bit
                 }
-                setNotificationStatus(appParams[i] & 0x01); // Lower bit
+                break;
+            case NOTIFICATION_FILTER:
+                if (tagLength != NOTIFICATION_FILTER_LEN) {
+                     Log.w(TAG, "NOTIFICATION_FILTER: Wrong length received: " + tagLength +
+                             " expected: " + NOTIFICATION_FILTER_LEN);
+                } else {
+                    setNotificationFilter(appParamBuf.getInt(i) & 0xffffffffL); // 4 bytes
+                }
                 break;
             case MAS_INSTANCE_ID:
                 if (tagLength != MAS_INSTANCE_ID_LEN) {
-                    Log.w(TAG, "MAS_INSTANCE_ID: Wrong length received: " + tagLength + " expected: "
-                            + MAS_INSTANCE_ID_LEN);
-                    break;
+                    Log.w(TAG, "MAS_INSTANCE_ID: Wrong length received: " + tagLength +
+                            " expected: " + MAS_INSTANCE_ID_LEN);
+                } else {
+                    setMasInstanceId(appParams[i] & 0xff);
                 }
-                setMasInstanceId(appParams[i] & 0xff);
                 break;
             case PARAMETER_MASK:
                 if (tagLength != PARAMETER_MASK_LEN) {
-                    Log.w(TAG, "PARAMETER_MASK: Wrong length received: " + tagLength + " expected: "
-                            + PARAMETER_MASK_LEN);
-                    break;
+                    Log.w(TAG, "PARAMETER_MASK: Wrong length received: " + tagLength +
+                            " expected: " + PARAMETER_MASK_LEN);
+                } else {
+                    setParameterMask(appParamBuf.getInt(i) & 0xffffffffL); // Make it unsigned
                 }
-                setParameterMask(appParamBuf.getInt(i) & 0xffffffffL); // Make it unsigned
                 break;
             case FOLDER_LISTING_SIZE:
                 if (tagLength != FOLDER_LISTING_SIZE_LEN) {
-                    Log.w(TAG, "FOLDER_LISTING_SIZE: Wrong length received: " + tagLength + " expected: "
-                            + FOLDER_LISTING_SIZE_LEN);
-                    break;
+                    Log.w(TAG, "FOLDER_LISTING_SIZE: Wrong length received: " + tagLength +
+                            " expected: " + FOLDER_LISTING_SIZE_LEN);
+                } else {
+                    setFolderListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
                 }
-                setFolderListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
                 break;
             case MESSAGE_LISTING_SIZE:
                 if (tagLength != MESSAGE_LISTING_SIZE_LEN) {
-                    Log.w(TAG, "MESSAGE_LISTING_SIZE: Wrong length received: " + tagLength + " expected: "
-                            + MESSAGE_LISTING_SIZE_LEN);
-                    break;
+                    Log.w(TAG, "MESSAGE_LISTING_SIZE: Wrong length received: " + tagLength +
+                            " expected: " + MESSAGE_LISTING_SIZE_LEN);
+                } else {
+                    setMessageListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
                 }
-                setMessageListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
                 break;
             case SUBJECT_LENGTH:
                 if (tagLength != SUBJECT_LENGTH_LEN) {
-                    Log.w(TAG, "SUBJECT_LENGTH: Wrong length received: " + tagLength + " expected: "
-                            + SUBJECT_LENGTH_LEN);
-                    break;
+                    Log.w(TAG, "SUBJECT_LENGTH: Wrong length received: " + tagLength +
+                            " expected: " + SUBJECT_LENGTH_LEN);
+                } else {
+                    setSubjectLength(appParams[i] & 0xff);
                 }
-                setSubjectLength(appParams[i] & 0xff);
                 break;
             case CHARSET:
                 if (tagLength != CHARSET_LEN) {
                     Log.w(TAG, "CHARSET: Wrong length received: " + tagLength + " expected: "
                             + CHARSET_LEN);
-                    break;
+                } else {
+                    setCharset(appParams[i] & 0x01); // Lower bit
                 }
-                setCharset(appParams[i] & 0x01); // Lower bit
                 break;
             case FRACTION_REQUEST:
                 if (tagLength != FRACTION_REQUEST_LEN) {
-                    Log.w(TAG, "FRACTION_REQUEST: Wrong length received: " + tagLength + " expected: "
-                            + FRACTION_REQUEST_LEN);
-                    break;
+                    Log.w(TAG, "FRACTION_REQUEST: Wrong length received: " + tagLength +
+                            " expected: " + FRACTION_REQUEST_LEN);
+                } else {
+                    setFractionRequest(appParams[i] & 0x01); // Lower bit
                 }
-                setFractionRequest(appParams[i] & 0x01); // Lower bit
                 break;
             case FRACTION_DELIVER:
                 if (tagLength != FRACTION_DELIVER_LEN) {
-                    Log.w(TAG, "FRACTION_DELIVER: Wrong length received: " + tagLength + " expected: "
-                            + FRACTION_DELIVER_LEN);
-                    break;
+                    Log.w(TAG, "FRACTION_DELIVER: Wrong length received: " + tagLength +
+                            " expected: " + FRACTION_DELIVER_LEN);
+                } else {
+                    setFractionDeliver(appParams[i] & 0x01); // Lower bit
                 }
-                setFractionDeliver(appParams[i] & 0x01); // Lower bit
                 break;
             case STATUS_INDICATOR:
                 if (tagLength != STATUS_INDICATOR_LEN) {
-                    Log.w(TAG, "STATUS_INDICATOR: Wrong length received: " + tagLength + " expected: "
-                            + STATUS_INDICATOR_LEN);
-                    break;
+                    Log.w(TAG, "STATUS_INDICATOR: Wrong length received: " + tagLength +
+                            " expected: " + STATUS_INDICATOR_LEN);
+                } else {
+                    setStatusIndicator(appParams[i] & 0x01); // Lower bit
                 }
-                setStatusIndicator(appParams[i] & 0x01); // Lower bit
                 break;
             case STATUS_VALUE:
                 if (tagLength != STATUS_VALUE_LEN) {
                     Log.w(TAG, "STATUS_VALUER: Wrong length received: " + tagLength + " expected: "
                             + STATUS_VALUE_LEN);
-                    break;
+                } else {
+                    setStatusValue(appParams[i] & 0x01); // Lower bit
                 }
-                setStatusValue(appParams[i] & 0x01); // Lower bit
                 break;
             case MSE_TIME:
                 setMseTime(new String(appParams, i, tagLength));
                 break;
+            case DATABASE_INDETIFIER:
+                if((tagLength != DATABASE_INDETIFIER_LEN)){
+                    Log.w(TAG, "DATABASE_IDENTIFIER: Wrong length received: " + tagLength +
+                            " expected: " + DATABASE_INDETIFIER_LEN);
+                } else {
+                    setDatabaseIdentifier(appParamBuf.getLong(i)/*MSB*/,
+                            appParamBuf.getLong(i+8)/*LSB*/);
+                }
+                break;
+            case CONVO_LIST_VER_COUNTER:
+                if((tagLength != CONVO_LIST_VER_COUNTER_LEN)){
+                    Log.w(TAG, "CONVO_LIST_VER_COUNTER: Wrong length received: " + tagLength +
+                            " expected: "  + CONVO_LIST_VER_COUNTER_LEN);
+                } else {
+                    setConvoListingVerCounter(appParamBuf.getLong(i)/*MSB*/,
+                            appParamBuf.getLong(i+8)/*LSB*/);
+                }
+                break;
+            case PRESENCE_AVAILABLE:
+                if((tagLength != PRESENCE_AVAILABLE_LEN)){
+                    Log.w(TAG, "PRESENCE_AVAILABLE: Wrong length received: " + tagLength +
+                            " expected: "  + PRESENCE_AVAILABLE_LEN);
+                } else {
+                    setPresenceAvailability(appParams[i]);
+                }
+                break;
+            case PRESENCE_TEXT:
+                if(tagLength != 0) {
+                    setPresenceStatus(new String(appParams, i, tagLength));
+                } else
+                    Log.w(TAG, "PRESENCE_STATUS: Wrong length received: " + tagLength +
+                            " expected to be more than 0");
+                break;
+            case LAST_ACTIVITY:
+                if(tagLength != 0) {
+                    setLastActivity(new String(appParams, i, tagLength));
+                } else
+                    Log.w(TAG, "LAST_ACTIVITY: Wrong length received: " + tagLength +
+                            " expected to be more than 0");
+                break;
+            case CHAT_STATE:
+                if((tagLength != CHAT_STATE_LEN)){
+                    Log.w(TAG, "CHAT_STATE: Wrong length received: " + tagLength +
+                            " expected: "  + CHAT_STATE_LEN);
+                } else {
+                    setChatState(appParams[i]);
+                }
+                break;
+            case FILTER_CONVO_ID:
+                if((tagLength != 0) && (tagLength <= FILTER_CONVO_ID_LEN)){
+                    setFilterConvoId(new String(appParams, i, tagLength));
+                } else {
+                    Log.w(TAG, "FILTER_CONVO_ID: Wrong length received: " + tagLength +
+                        " expected: "  + FILTER_CONVO_ID_LEN);
+                }
+                break;
+            case CONVO_LISTING_SIZE:
+                if(tagLength != CONVO_LISTING_SIZE_LEN){
+                    Log.w(TAG, "LISTING_SIZE: Wrong length received: "+ tagLength+" expected: "+
+                            CONVO_LISTING_SIZE_LEN);
+
+                } else {
+                    setConvoListingSize(appParamBuf.getShort(i) & 0xffff);
+                }
+                break;
+            case FILTER_PRESENCE:
+                if((tagLength != FILTER_PRESENCE_LEN)){
+                    Log.w(TAG, "FILTER_PRESENCE: Wrong length received: " + tagLength +
+                            " expected: "  + FILTER_PRESENCE_LEN);
+                } else {
+                    setFilterPresence(appParams[i]);
+                }
+                break;
+            case FILTER_UID_PRESENT:
+                if((tagLength != FILTER_UID_PRESENT_LEN)){
+                    Log.w(TAG, "FILTER_UID_PRESENT: Wrong length received: " + tagLength +
+                            " expected: "  + FILTER_UID_PRESENT_LEN);
+                } else {
+                    setFilterUidPresent(appParams[i]&0x1);
+                }
+                break;
+            case CHAT_STATE_CONVO_ID:
+                if((tagLength != CHAT_STATE_CONVO_ID_LEN)){
+                    Log.w(TAG, "CHAT_STATE_CONVO_ID: Wrong length received: " + tagLength +
+                            " expected: "  + CHAT_STATE_CONVO_ID_LEN);
+                } else {
+                    /* TODO: Is this correct convoId handling? */
+                    setChatStateConvoId(appParamBuf.getLong(i)/*MSB*/,
+                                        appParamBuf.getLong(i+8)/*LSB*/);
+                    Log.d(TAG, "CHAT_STATE_CONVO_ID: convo id " +
+                        "MSB=" + BluetoothMapUtils.getLongAsString(appParamBuf.getLong(i)) +
+                        ", LSB(+8)=" + BluetoothMapUtils.getLongAsString(appParamBuf.getLong(i+8)));
+
+                }
+                break;
+            case FOLDER_VER_COUNTER:
+                break;
+            case FILTER_MESSAGE_HANDLE:
+                if((tagLength != 0 && tagLength <= FILTER_MESSAGE_HANDLE_LEN)){
+                    setFilterMsgHandle(new String(appParams, i, tagLength));
+                } else {
+                    Log.w(TAG, "FILTER_MESSAGE_HANDLE: Wrong length received: " + tagLength +
+                            " expected: "  + FILTER_MESSAGE_HANDLE_LEN);
+                }
+
+                break;
+            case CONVO_PARAMETER_MASK:
+                if (tagLength != CONVO_PARAMETER_MASK_LEN) {
+                    Log.w(TAG, "CONVO_PARAMETER_MASK: Wrong length received: " + tagLength +
+                            " expected: " + CONVO_PARAMETER_MASK_LEN);
+                } else {
+                    setConvoParameterMask(appParamBuf.getInt(i) & 0xffffffffL); // Make it unsigned
+                }
+                break;
             default:
                 // Just skip unknown Tags, no need to report error
                 Log.w(TAG, "Unknown TagId received ( 0x" + Integer.toString(tagId, 16)
@@ -382,15 +568,18 @@
      */
     private int getParamMaxLength() throws UnsupportedEncodingException {
         int length = 0;
-        length += 25 * 2; // tagId + tagLength
-        length += 27; // fixed sizes
-        length += getFilterPeriodBegin() == INVALID_VALUE_PARAMETER ? 0 : 15;
-        length += getFilterPeriodEnd() == INVALID_VALUE_PARAMETER ? 0 : 15;
+        length += 38 * 2; // tagId + tagLength
+        length += 33+4*16; // fixed sizes TODO: Update when spec is ready
+        length += getFilterPeriodBegin() == INVALID_VALUE_PARAMETER ? 0 : 20;
+        length += getFilterPeriodEnd() == INVALID_VALUE_PARAMETER ? 0 : 20;
         if (getFilterRecipient() != null)
             length += getFilterRecipient().getBytes("UTF-8").length;
         if (getFilterOriginator() != null)
             length += getFilterOriginator().getBytes("UTF-8").length;
         length += getMseTime() == INVALID_VALUE_PARAMETER ? 0 : 20;
+        if(getPresenceStatus() != null)
+            length += getPresenceStatus().getBytes("UTF-8").length;
+        length += (getLastActivity() == INVALID_VALUE_PARAMETER) ? 0 : 20;
         return length;
     }
 
@@ -476,6 +665,11 @@
             appParamBuf.put((byte) NOTIFICATION_STATUS_LEN);
             appParamBuf.putShort((short) getNotificationStatus());
         }
+        if (getNotificationFilter() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) NOTIFICATION_FILTER);
+            appParamBuf.put((byte) NOTIFICATION_FILTER_LEN);
+            appParamBuf.putInt((int) getNotificationFilter());
+        }
         if (getMasInstanceId() != INVALID_VALUE_PARAMETER) {
             appParamBuf.put((byte) MAS_INSTANCE_ID);
             appParamBuf.put((byte) MAS_INSTANCE_ID_LEN);
@@ -531,6 +725,80 @@
             appParamBuf.put((byte) getMseTimeString().getBytes("UTF-8").length);
             appParamBuf.put(getMseTimeString().getBytes("UTF-8"));
         }
+        // Note: New for IM
+        if (getDatabaseIdentifier() != null) {
+            appParamBuf.put((byte)DATABASE_INDETIFIER);
+            appParamBuf.put((byte)DATABASE_INDETIFIER_LEN);
+            appParamBuf.put(getDatabaseIdentifier());
+        }
+        if (getConvoListingVerCounter() != null) {
+            appParamBuf.put((byte)CONVO_LIST_VER_COUNTER);
+            appParamBuf.put((byte)CONVO_LIST_VER_COUNTER_LEN);
+            appParamBuf.put(getConvoListingVerCounter());
+        }
+        if (getPresenceAvailability() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte)PRESENCE_AVAILABLE);
+            appParamBuf.put((byte)PRESENCE_AVAILABLE_LEN);
+            appParamBuf.putInt((int)getPresenceAvailability());
+        }
+        if (getPresenceStatus()!= null) {
+            appParamBuf.put((byte)PRESENCE_TEXT);
+            appParamBuf.put((byte)getPresenceStatus().getBytes("UTF-8").length);
+            appParamBuf.put(getPresenceStatus().getBytes());
+        }
+        if (getLastActivity() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte)LAST_ACTIVITY);
+            appParamBuf.put((byte)getLastActivityString().getBytes("UTF-8").length);
+            appParamBuf.put(getLastActivityString().getBytes());
+        }
+        if (getChatState() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte)CHAT_STATE);
+            appParamBuf.put((byte)CHAT_STATE_LEN);
+            appParamBuf.putShort((short)getChatState());
+        }
+        if (getFilterConvoId() != null) {
+            appParamBuf.put((byte)FILTER_CONVO_ID);
+            appParamBuf.put((byte)FILTER_CONVO_ID_LEN);
+            appParamBuf.putLong(getFilterConvoId().getMostSignificantBits());
+            appParamBuf.putLong(getFilterConvoId().getLeastSignificantBits());
+        }
+        if (getConvoListingSize() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte)CONVO_LISTING_SIZE);
+            appParamBuf.put((byte)CONVO_LISTING_SIZE_LEN);
+            appParamBuf.putShort((short)getConvoListingSize());
+        }
+        if (getFilterPresence() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte)FILTER_PRESENCE);
+            appParamBuf.put((byte)FILTER_PRESENCE_LEN);
+            appParamBuf.putShort((short)getFilterPresence());
+        }
+        if (getFilterUidPresent() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte)FILTER_UID_PRESENT);
+            appParamBuf.put((byte)FILTER_UID_PRESENT_LEN);
+            appParamBuf.putShort((short)getFilterUidPresent());
+        }
+        if (getChatStateConvoId() != null) {
+            appParamBuf.put((byte)CHAT_STATE_CONVO_ID);
+            appParamBuf.put((byte)CHAT_STATE_CONVO_ID_LEN);
+            appParamBuf.putLong(getChatStateConvoId().getMostSignificantBits());
+            appParamBuf.putLong(getChatStateConvoId().getLeastSignificantBits());
+        }
+        if (getFolderVerCounter() != null) {
+            appParamBuf.put((byte)FOLDER_VER_COUNTER);
+            appParamBuf.put((byte)FOLDER_VER_COUNTER_LEN);
+            appParamBuf.put(getFolderVerCounter());
+        }
+        if (getFilterMsgHandle() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte)FILTER_MESSAGE_HANDLE);
+            appParamBuf.put((byte)FILTER_MESSAGE_HANDLE_LEN);
+            appParamBuf.putLong(getFilterMsgHandle());
+        }
+        if (getConvoParameterMask() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) CONVO_PARAMETER_MASK);
+            appParamBuf.put((byte) CONVO_PARAMETER_MASK_LEN);
+            appParamBuf.putInt((int) getConvoParameterMask());
+        }
+
         // We need to reduce the length of the array to match the content
         retBuf = Arrays.copyOfRange(appParamBuf.array(), appParamBuf.arrayOffset(),
                                     appParamBuf.arrayOffset() + appParamBuf.position());
@@ -562,8 +830,8 @@
     }
 
     public void setFilterMessageType(int filterMessageType) throws IllegalArgumentException {
-        if (filterMessageType < 0 || filterMessageType > 0x000F)
-            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x000F");
+        if (filterMessageType < 0 || filterMessageType > 0x001F)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x001F");
         this.mFilterMessageType = filterMessageType;
     }
 
@@ -587,10 +855,45 @@
         this.mFilterPeriodBegin = date.getTime();
     }
 
+    public long getFilterLastActivityBegin() {
+        return mFilterPeriodBegin;
+    }
+    public String getFilterLastActivityBeginString() {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Date date = new Date(mFilterPeriodBegin);
+        return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+    }
+    public void setFilterLastActivityBegin(long filterPeriodBegin) {
+        this.mFilterPeriodBegin = filterPeriodBegin;
+    }
+
+    public void setFilterLastActivityBegin(String filterPeriodBegin)throws ParseException {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Date date = format.parse(filterPeriodBegin);
+        this.mFilterPeriodBegin = date.getTime();
+    }
     public long getFilterPeriodEnd() {
         return mFilterPeriodEnd;
     }
+    public long getFilterLastActivityEnd() {
+        return mFilterPeriodEnd;
+    }
 
+    public String getFilterLastActivityEndString() {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Date date = new Date(mFilterPeriodEnd);
+        return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+    }
+
+    public void setFilterLastActivityEnd(long filterPeriodEnd) {
+        this.mFilterPeriodEnd= filterPeriodEnd; //er reuse the same
+    }
+
+    public void setFilterPeriodEnd(String filterPeriodEnd) throws ParseException {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Date date = format.parse(filterPeriodEnd);
+        this.mFilterPeriodEnd = date.getTime();
+    }
     public String getFilterPeriodEndString() {
         SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
         Date date = new Date(mFilterPeriodEnd);
@@ -601,12 +904,11 @@
         this.mFilterPeriodEnd = filterPeriodEnd;
     }
 
-    public void setFilterPeriodEnd(String filterPeriodEnd) throws ParseException {
+    public void setFilterLastActivityEnd(String filterPeriodEnd) throws ParseException {
         SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
         Date date = format.parse(filterPeriodEnd);
         this.mFilterPeriodEnd = date.getTime();
     }
-
     public int getFilterReadStatus() {
         return mFilterReadStatus;
     }
@@ -643,6 +945,190 @@
         this.mFilterPriority = filterPriority;
     }
 
+    public void setDatabaseIdentifier(long idHigh, long idLow) {
+        this.mDatabaseIdentifierHigh = idHigh;
+        this.mDatabaseIdentifierLow  = idLow;
+    }
+
+    public byte[] getDatabaseIdentifier() {
+        if(mDatabaseIdentifierLow != INVALID_VALUE_PARAMETER
+                && mDatabaseIdentifierHigh != INVALID_VALUE_PARAMETER) {
+            ByteBuffer ret = ByteBuffer.allocate(16);
+            ret.putLong(mDatabaseIdentifierHigh);
+            ret.putLong(mDatabaseIdentifierLow);
+                return ret.array();
+        }else return null;
+    }
+
+    public void setConvoListingVerCounter(long countLow, long countHigh) {
+        this.mConvoListingVerCounterHigh = countHigh;
+        this.mConvoListingVerCounterLow  = countLow;
+    }
+
+    public byte[] getConvoListingVerCounter(){
+        if(mConvoListingVerCounterHigh != INVALID_VALUE_PARAMETER &&
+            mConvoListingVerCounterLow != INVALID_VALUE_PARAMETER) {
+            ByteBuffer ret = ByteBuffer.allocate(16);
+            ret.putLong(mConvoListingVerCounterHigh);
+            ret.putLong(mConvoListingVerCounterLow);
+            return ret.array();
+            } else return null;
+    }
+
+    public void setFolderVerCounter(long countLow, long countHigh) {
+        this.mFolderVerCounterHigh = countHigh;
+        this.mFolderVerCounterLow = countLow;
+    }
+
+    public byte[] getFolderVerCounter(){
+        if(mFolderVerCounterHigh != INVALID_VALUE_PARAMETER &&
+                mFolderVerCounterLow != INVALID_VALUE_PARAMETER) {
+            ByteBuffer ret = ByteBuffer.allocate(16);
+            ret.putLong(mFolderVerCounterHigh);
+            ret.putLong(mFolderVerCounterLow);
+            return ret.array();
+        } else return null;
+    }
+
+    public SignedLongLong getChatStateConvoId(){
+        return mChatStateConvoId;
+    }
+
+    public byte[] getChatStateConvoIdByteArray() {
+        if(mChatStateConvoId != null) {
+            ByteBuffer ret = ByteBuffer.allocate(16);
+            ret.putLong(mChatStateConvoId.getMostSignificantBits());
+            ret.putLong(mChatStateConvoId.getLeastSignificantBits());
+            return ret.array();
+        } else return null;
+    }
+
+    public String getChatStateConvoIdString() {
+        String str = null;
+        str = new String(this.getChatStateConvoIdByteArray());
+        return str;
+    }
+
+    public void setChatStateConvoId(long idHigh, long idLow) {
+        mChatStateConvoId = new SignedLongLong(idLow, idHigh);
+    }
+
+    public void setFilterMsgHandle(String handle) {
+            try {
+                mFilterMsgHandle = BluetoothMapUtils.getLongFromString(handle);
+            } catch (UnsupportedEncodingException e) {
+                Log.w(TAG,"Error creating long from handle string", e);
+            }
+    }
+
+    public long getFilterMsgHandle(){
+        return mFilterMsgHandle;
+    }
+
+    public String getFilterMsgHandleString() {
+        String str = null;
+        if(mFilterMsgHandle != INVALID_VALUE_PARAMETER) {
+            str = BluetoothMapUtils.getLongAsString(mFilterMsgHandle);
+        }
+        return str;
+    }
+
+    public int getFilterUidPresent() {
+        return mFilterUidPresent;
+    }
+
+    public void setFilterUidPresent(int present) {
+        if (present < 0 || present > 0x00FF)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x00FF");
+        this.mFilterUidPresent = present;
+    }
+
+    public int getFilterPresence() {
+        return mFilterPresence;
+    }
+
+
+
+    public SignedLongLong getFilterConvoId(){
+        return mFilterConvoId;
+    }
+
+    /**
+     * Get a decimal representation of the lower bits of the ConvoId - used for queries.
+     * The upper bits are used for convo-type.
+     * @return decimal representation of the convo ID.
+     */
+    public String getFilterConvoIdString() {
+        String str = null;
+        if(mFilterConvoId != null) {
+            str = BluetoothMapUtils.getLongAsString(mFilterConvoId.getLeastSignificantBits());
+        }
+        return str;
+    }
+
+
+    public void setFilterConvoId(String id) {
+        try {
+            mFilterConvoId = SignedLongLong.fromString(id);
+        } catch (UnsupportedEncodingException e) {
+            Log.w(TAG,"Error creating long from id string", e);
+        }
+    }
+
+
+    public void setChatState(int state) {
+        if (state < 0 || state > 0x00FF)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x00FF");
+        this.mChatState = state;
+    }
+
+    public int getChatState() {
+        return mChatState;
+    }
+
+    public long getLastActivity(){
+        return this.mLastActivity;
+    }
+    public String getLastActivityString(){
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmssZ");
+        Date date = new Date(mLastActivity);
+        return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+    }
+    public void setLastActivity(long last){
+        this.mLastActivity = last;
+    }
+    public void setLastActivity(String lastActivity) throws ParseException {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmssZ");
+        Date date = format.parse(lastActivity);
+        this.mLastActivity = date.getTime();
+    }
+
+    public void setPresenceStatus(String status){
+        this.mPresenceStatus = status;
+    }
+    public String getPresenceStatus(){
+        return this.mPresenceStatus;
+    }
+
+    public void setFilterPresence(int presence) {
+        if (presence < 0 || presence > 0xFFFF)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFF");
+        this.mFilterPresence = presence;
+    }
+
+    public void setPresenceAvailability(int availability) {
+        if (availability < 0 || availability > 0x00FF)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x00FF");
+        this.mPresenceAvailability = availability;
+    }
+
+    public int getPresenceAvailability() {
+        return mPresenceAvailability;
+    }
+
+    public int getSubjectLength() {
+        return mSubjectLength;
+    }
     public int getAttachment() {
         return mAttachment;
     }
@@ -693,6 +1179,17 @@
         this.mNotificationStatus = notificationStatus;
     }
 
+    public long getNotificationFilter() {
+        return mNotificationFilter;
+    }
+
+    public void setNotificationFilter(long notificationFilter) throws IllegalArgumentException {
+        if (notificationFilter < 0 || notificationFilter > 0xFFFFFFFFL)
+            throw new IllegalArgumentException(
+                    "Out of range, valid range is 0x0000 to 0xFFFFFFFFL");
+        this.mNotificationFilter = notificationFilter;
+    }
+
     public int getMasInstanceId() {
         return mMasInstanceId;
     }
@@ -713,6 +1210,16 @@
         this.mParameterMask = parameterMask;
     }
 
+    public void setConvoParameterMask(long parameterMask) {
+        if (parameterMask < 0 || parameterMask > 0xFFFFFFFFL)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFFFFFF");
+        this.mConvoParameterMask = parameterMask;
+    }
+
+    public long getConvoParameterMask(){
+        return mConvoParameterMask;
+    }
+
     public int getFolderListingSize() {
         return mFolderListingSize;
     }
@@ -733,10 +1240,15 @@
         this.mMessageListingSize = messageListingSize;
     }
 
-    public int getSubjectLength() {
-        return mSubjectLength;
+    public int getConvoListingSize() {
+        return mConvoListingSize;
     }
 
+    public void setConvoListingSize(int convoListingSize) {
+        if (convoListingSize < 0 || convoListingSize > 0xFFFF)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFF");
+        this.mConvoListingSize = convoListingSize;
+    }
     public void setSubjectLength(int subjectLength) {
         if (subjectLength < 0 || subjectLength > 0xFF)
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x00FF");
@@ -812,4 +1324,8 @@
         Date date = format.parse(mseTime);
         this.mMseTime = date.getTime();
     }
+
+
+
+
 }
diff --git a/src/com/android/bluetooth/map/BluetoothMapContent.java b/src/com/android/bluetooth/map/BluetoothMapContent.java
index 540cbbb..dfc10f0 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -14,34 +14,35 @@
 */
 package com.android.bluetooth.map;
 
-import org.apache.http.util.ByteArrayBuffer;
-
+import android.annotation.TargetApi;
 import android.content.ContentResolver;
-import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.Debug;
+import android.net.Uri.Builder;
 import android.os.ParcelFileDescriptor;
 import android.provider.BaseColumns;
-import com.android.bluetooth.mapapi.BluetoothMapContract;
-import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns;
 import android.provider.ContactsContract;
 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.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
 import android.text.util.Rfc822Token;
 import android.text.util.Rfc822Tokenizer;
 import android.util.Log;
+import android.util.SparseArray;
 
-import com.android.bluetooth.map.BluetoothMapSmsPdu.SmsPdu;
+import com.android.bluetooth.SignedLongLong;
+import com.android.bluetooth.map.BluetoothMapContentObserver.Msg;
 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+import com.android.bluetooth.mapapi.BluetoothMapContract.ConversationColumns;
 import com.google.android.mms.pdu.CharacterSets;
 import com.google.android.mms.pdu.PduHeaders;
-import com.android.bluetooth.map.BluetoothMapAppParams;
 
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
@@ -50,51 +51,103 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
-import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
 
+@TargetApi(19)
 public class BluetoothMapContent {
+
     private static final String TAG = "BluetoothMapContent";
 
     private static final boolean D = BluetoothMapService.DEBUG;
     private static final boolean V = BluetoothMapService.VERBOSE;
 
-    private static final int MASK_SUBJECT = 0x1;
-    private static final int MASK_DATETIME = 0x2;
-    private static final int MASK_SENDER_NAME = 0x4;
-    private static final int MASK_SENDER_ADDRESSING = 0x8;
+    // Parameter Mask for selection of parameters to return in listings
+    private static final int MASK_SUBJECT               = 0x00000001;
+    private static final int MASK_DATETIME              = 0x00000002;
+    private static final int MASK_SENDER_NAME           = 0x00000004;
+    private static final int MASK_SENDER_ADDRESSING     = 0x00000008;
+    private static final int MASK_RECIPIENT_NAME        = 0x00000010;
+    private static final int MASK_RECIPIENT_ADDRESSING  = 0x00000020;
+    private static final int MASK_TYPE                  = 0x00000040;
+    private static final int MASK_SIZE                  = 0x00000080;
+    private static final int MASK_RECEPTION_STATUS      = 0x00000100;
+    private static final int MASK_TEXT                  = 0x00000200;
+    private static final int MASK_ATTACHMENT_SIZE       = 0x00000400;
+    private static final int MASK_PRIORITY              = 0x00000800;
+    private static final int MASK_READ                  = 0x00001000;
+    private static final int MASK_SENT                  = 0x00002000;
+    private static final int MASK_PROTECTED             = 0x00004000;
+    private static final int MASK_REPLYTO_ADDRESSING    = 0x00008000;
+    // TODO: Duplicate in proposed spec
+    // private static final int MASK_RECEPTION_STATE       = 0x00010000;
+    private static final int MASK_DELIVERY_STATUS       = 0x00020000;
+    private static final int MASK_CONVERSATION_ID       = 0x00040000;
+    private static final int MASK_CONVERSATION_NAME     = 0x00080000;
+    private static final int MASK_FOLDER_TYPE           = 0x00100000;
+    // TODO: about to be removed from proposed spec
+    // private static final int MASK_SEQUENCE_NUMBER       = 0x00200000;
+    private static final int MASK_ATTACHMENT_MIME       = 0x00400000;
 
-    private static final int MASK_RECIPIENT_NAME = 0x10;
-    private static final int MASK_RECIPIENT_ADDRESSING = 0x20;
-    private static final int MASK_TYPE = 0x40;
-    private static final int MASK_SIZE = 0x80;
+    private static final int  CONVO_PARAM_MASK_CONVO_NAME              = 0x00000001;
+    private static final int  CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY     = 0x00000002;
+    private static final int  CONVO_PARAM_MASK_CONVO_READ_STATUS       = 0x00000004;
+    private static final int  CONVO_PARAM_MASK_CONVO_VERSION_COUNTER   = 0x00000008;
+    private static final int  CONVO_PARAM_MASK_CONVO_SUMMARY           = 0x00000010;
+    private static final int  CONVO_PARAM_MASK_PARTTICIPANTS           = 0x00000020;
+    private static final int  CONVO_PARAM_MASK_PART_UCI                = 0x00000040;
+    private static final int  CONVO_PARAM_MASK_PART_DISP_NAME          = 0x00000080;
+    private static final int  CONVO_PARAM_MASK_PART_CHAT_STATE         = 0x00000100;
+    private static final int  CONVO_PARAM_MASK_PART_LAST_ACTIVITY      = 0x00000200;
+    private static final int  CONVO_PARAM_MASK_PART_X_BT_UID           = 0x00000400;
+    private static final int  CONVO_PARAM_MASK_PART_NAME               = 0x00000800;
+    private static final int  CONVO_PARAM_MASK_PART_PRESENCE           = 0x00001000;
+    private static final int  CONVO_PARAM_MASK_PART_PRESENCE_TEXT      = 0x00002000;
+    private static final int  CONVO_PARAM_MASK_PART_PRIORITY           = 0x00004000;
 
-    private static final int MASK_RECEPTION_STATUS = 0x100;
-    private static final int MASK_TEXT = 0x200;
-    private static final int MASK_ATTACHMENT_SIZE = 0x400;
-    private static final int MASK_PRIORITY = 0x800;
+    /* Default values for omitted or 0 parameterMask application parameters */
+    // MAP specification states that the default value for parameter mask are
+    // the #REQUIRED attributes in the DTD, and not all enabled
+    public static final long PARAMETER_MASK_ALL_ENABLED = 0xFFFFFFFFL;
+    public static final long PARAMETER_MASK_DEFAULT = 0x5E3L;
+    public static final long CONVO_PARAMETER_MASK_ALL_ENABLED = 0xFFFFFFFFL;
+    public static final long CONVO_PARAMETER_MASK_DEFAULT =
+            CONVO_PARAM_MASK_CONVO_NAME |
+            CONVO_PARAM_MASK_PARTTICIPANTS |
+            CONVO_PARAM_MASK_PART_UCI |
+            CONVO_PARAM_MASK_PART_DISP_NAME;
 
-    private static final int MASK_READ = 0x1000;
-    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 int FILTER_READ_STATUS_UNREAD_ONLY = 0x01;
+    private static final int FILTER_READ_STATUS_READ_ONLY   = 0x02;
+    private static final int FILTER_READ_STATUS_ALL         = 0x00;
 
     /* 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 */
-    public static final int MMS_FROM = 0x89;
-    public static final int MMS_TO = 0x97;
-    public static final int MMS_BCC = 0x81;
-    public static final int MMS_CC = 0x82;
+    public static final int MMS_FROM    = 0x89;
+    public static final int MMS_TO      = 0x97;
+    public static final int MMS_BCC     = 0x81;
+    public static final int MMS_CC      = 0x82;
 
     public static final String INSERT_ADDRES_TOKEN = "insert-address-token";
 
-    private Context mContext;
-    private ContentResolver mResolver;
-    private String mBaseEmailUri = null;
+    private final Context mContext;
+    private final ContentResolver mResolver;
+    private final String mBaseUri;
+    private final BluetoothMapAccountItem mAccount;
+    /* The MasInstance reference is used to update persistent (over a connection) version counters*/
+    private final BluetoothMapMasInstance mMasInstance;
+    private String mMessageVersion = BluetoothMapUtils.MAP_V10_STR;
+
+    private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
+    private int mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10;
 
     static final String[] SMS_PROJECTION = new String[] {
         BaseColumns._ID,
@@ -122,67 +175,237 @@
         Mms.READ,
         Mms.MESSAGE_BOX,
         Mms.STATUS,
-        Mms.PRIORITY
+        Mms.PRIORITY,
     };
 
-    private class FilterInfo {
-        public static final int TYPE_SMS = 0;
-        public static final int TYPE_MMS = 1;
-        public static final int TYPE_EMAIL = 2;
+    static final String[] SMS_CONVO_PROJECTION = new String[] {
+        BaseColumns._ID,
+        Sms.THREAD_ID,
+        Sms.ADDRESS,
+        Sms.DATE,
+        Sms.READ,
+        Sms.TYPE,
+        Sms.STATUS,
+        Sms.LOCKED,
+        Sms.ERROR_CODE
+    };
 
+    static final String[] MMS_CONVO_PROJECTION = new String[] {
+        BaseColumns._ID,
+        Mms.THREAD_ID,
+        Mms.MESSAGE_ID,
+        Mms.MESSAGE_SIZE,
+        Mms.SUBJECT,
+        Mms.CONTENT_TYPE,
+        Mms.TEXT_ONLY,
+        Mms.DATE,
+        Mms.DATE_SENT,
+        Mms.READ,
+        Mms.MESSAGE_BOX,
+        Mms.STATUS,
+        Mms.PRIORITY,
+        Mms.Addr.ADDRESS
+    };
+
+    /* CONVO LISTING projections and column indexes */
+    private static final String[] MMS_SMS_THREAD_PROJECTION = {
+        Threads._ID,
+        Threads.DATE,
+        Threads.SNIPPET,
+        Threads.SNIPPET_CHARSET,
+        Threads.READ,
+        Threads.RECIPIENT_IDS
+    };
+
+    private static final String[] CONVO_VERSION_PROJECTION = new String[] {
+        /* Thread information */
+        ConversationColumns.THREAD_ID,
+        ConversationColumns.THREAD_NAME,
+        ConversationColumns.READ_STATUS,
+        ConversationColumns.LAST_THREAD_ACTIVITY,
+        ConversationColumns.SUMMARY,
+    };
+
+    /* Optimize the Cursor access to avoid the need to do a getColumnIndex() */
+    private static final int MMS_SMS_THREAD_COL_ID;
+    private static final int MMS_SMS_THREAD_COL_DATE;
+    private static final int MMS_SMS_THREAD_COL_SNIPPET;
+    private static final int MMS_SMS_THREAD_COL_SNIPPET_CS;
+    private static final int MMS_SMS_THREAD_COL_READ;
+    private static final int MMS_SMS_THREAD_COL_RECIPIENT_IDS;
+    static {
+        // TODO: This might not work, if the projection is mapped in the content provider...
+        //       Change to init at first query? (Current use in the AOSP code is hard coded values
+        //       unrelated to the projection used)
+        List<String> projection = Arrays.asList(MMS_SMS_THREAD_PROJECTION);
+        MMS_SMS_THREAD_COL_ID = projection.indexOf(Threads._ID);
+        MMS_SMS_THREAD_COL_DATE = projection.indexOf(Threads.DATE);
+        MMS_SMS_THREAD_COL_SNIPPET = projection.indexOf(Threads.SNIPPET);
+        MMS_SMS_THREAD_COL_SNIPPET_CS = projection.indexOf(Threads.SNIPPET_CHARSET);
+        MMS_SMS_THREAD_COL_READ = projection.indexOf(Threads.READ);
+        MMS_SMS_THREAD_COL_RECIPIENT_IDS = projection.indexOf(Threads.RECIPIENT_IDS);
+    }
+
+    private class FilterInfo {
+        public static final int TYPE_SMS    = 0;
+        public static final int TYPE_MMS    = 1;
+        public static final int TYPE_EMAIL  = 2;
+        public static final int TYPE_IM     = 3;
+
+        // TODO: Change to ENUM, to ensure correct usage
         int mMsgType = TYPE_SMS;
         int mPhoneType = 0;
         String mPhoneNum = null;
         String mPhoneAlphaTag = null;
         /*column indices used to optimize queries */
-        public int mEmailColThreadId        = -1;
-        public int mEmailColProtected       = -1;
-        public int mEmailColFolder          = -1;
-        public int mMmsColFolder            = -1;
+        public int mMessageColId                = -1;
+        public int mMessageColDate              = -1;
+        public int mMessageColBody              = -1;
+        public int mMessageColSubject           = -1;
+        public int mMessageColFolder            = -1;
+        public int mMessageColRead              = -1;
+        public int mMessageColSize              = -1;
+        public int mMessageColFromAddress       = -1;
+        public int mMessageColToAddress         = -1;
+        public int mMessageColCcAddress         = -1;
+        public int mMessageColBccAddress        = -1;
+        public int mMessageColReplyTo           = -1;
+        public int mMessageColAccountId         = -1;
+        public int mMessageColAttachment        = -1;
+        public int mMessageColAttachmentSize    = -1;
+        public int mMessageColAttachmentMime    = -1;
+        public int mMessageColPriority          = -1;
+        public int mMessageColProtected         = -1;
+        public int mMessageColReception         = -1;
+        public int mMessageColDelivery          = -1;
+        public int mMessageColThreadId          = -1;
+        public int mMessageColThreadName        = -1;
+
         public int mSmsColFolder            = -1;
-        public int mEmailColRead            = -1;
         public int mSmsColRead              = -1;
-        public int mMmsColRead              = -1;
-        public int mEmailColPriority        = -1;
-        public int mMmsColAttachmentSize    = -1;
-        public int mEmailColAttachment      = -1;
-        public int mEmailColAttachementSize = -1;
-        public int mMmsColTextOnly          = -1;
-        public int mMmsColId                = -1;
         public int mSmsColId                = -1;
-        public int mEmailColSize            = -1;
         public int mSmsColSubject           = -1;
-        public int mMmsColSize              = -1;
-        public int mEmailColToAddress       = -1;
-        public int mEmailColCcAddress       = -1;
-        public int mEmailColBccAddress      = -1;
         public int mSmsColAddress           = -1;
         public int mSmsColDate              = -1;
-        public int mMmsColDate              = -1;
-        public int mEmailColDate            = -1;
-        public int mMmsColSubject           = -1;
-        public int mEmailColSubject         = -1;
         public int mSmsColType              = -1;
-        public int mEmailColFromAddress     = -1;
-        public int mEmailColId              = -1;
+        public int mSmsColThreadId          = -1;
+
+        public int mMmsColRead              = -1;
+        public int mMmsColFolder            = -1;
+        public int mMmsColAttachmentSize    = -1;
+        public int mMmsColTextOnly          = -1;
+        public int mMmsColId                = -1;
+        public int mMmsColSize              = -1;
+        public int mMmsColDate              = -1;
+        public int mMmsColSubject           = -1;
+        public int mMmsColThreadId          = -1;
+
+        public int mConvoColConvoId         = -1;
+        public int mConvoColLastActivity    = -1;
+        public int mConvoColName            = -1;
+        public int mConvoColRead            = -1;
+        public int mConvoColVersionCounter  = -1;
+        public int mConvoColSummary         = -1;
+        public int mContactColBtUid         = -1;
+        public int mContactColChatState     = -1;
+        public int mContactColContactUci    = -1;
+        public int mContactColNickname      = -1;
+        public int mContactColLastActive    = -1;
+        public int mContactColName          = -1;
+        public int mContactColPresenceState = -1;
+        public int mContactColPresenceText  = -1;
+        public int mContactColPriority      = -1;
 
 
-        public void setEmailColumns(Cursor c) {
-            mEmailColThreadId        = c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_ID);
-            mEmailColProtected       = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_PROTECTED);
-            mEmailColFolder          = c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID);
-            mEmailColRead            = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ);
-            mEmailColPriority        = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY);
-            mEmailColAttachment      = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_ATTACHMENT);
-            mEmailColAttachementSize = c.getColumnIndex(BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE);
-            mEmailColSize            = c.getColumnIndex(BluetoothMapContract.MessageColumns.MESSAGE_SIZE);
-            mEmailColToAddress       = c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST);
-            mEmailColCcAddress       = c.getColumnIndex(BluetoothMapContract.MessageColumns.CC_LIST);
-            mEmailColBccAddress      = c.getColumnIndex(BluetoothMapContract.MessageColumns.BCC_LIST);
-            mEmailColDate            = c.getColumnIndex(BluetoothMapContract.MessageColumns.DATE);
-            mEmailColSubject         = c.getColumnIndex(BluetoothMapContract.MessageColumns.SUBJECT);
-            mEmailColFromAddress     = c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST);
-            mEmailColId              = c.getColumnIndex(BluetoothMapContract.MessageColumns._ID);
+        public void setMessageColumns(Cursor c) {
+            mMessageColId               = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns._ID);
+            mMessageColDate             = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.DATE);
+            mMessageColSubject          = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.SUBJECT);
+            mMessageColFolder           = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.FOLDER_ID);
+            mMessageColRead             = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.FLAG_READ);
+            mMessageColSize             = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.MESSAGE_SIZE);
+            mMessageColFromAddress      = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.FROM_LIST);
+            mMessageColToAddress        = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.TO_LIST);
+            mMessageColAttachment       = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.FLAG_ATTACHMENT);
+            mMessageColAttachmentSize   = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE);
+            mMessageColPriority         = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY);
+            mMessageColProtected        = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.FLAG_PROTECTED);
+            mMessageColReception        = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.RECEPTION_STATE);
+            mMessageColDelivery         = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.DEVILERY_STATE);
+            mMessageColThreadId         = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.THREAD_ID);
+        }
+
+        public void setEmailMessageColumns(Cursor c) {
+            setMessageColumns(c);
+            mMessageColCcAddress        = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.CC_LIST);
+            mMessageColBccAddress       = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.BCC_LIST);
+            mMessageColReplyTo          = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.REPLY_TO_LIST);
+        }
+
+        public void setImMessageColumns(Cursor c) {
+            setMessageColumns(c);
+            mMessageColThreadName       = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.THREAD_NAME);
+            mMessageColAttachmentMime   = c.getColumnIndex(
+                    BluetoothMapContract.MessageColumns.ATTACHMENT_MINE_TYPES);
+            //TODO this is temporary as text should come from parts table instead
+            mMessageColBody = c.getColumnIndex(BluetoothMapContract.MessageColumns.BODY);
+
+        }
+
+        public void setEmailImConvoColumns(Cursor c) {
+            mConvoColConvoId            = c.getColumnIndex(
+                    BluetoothMapContract.ConversationColumns.THREAD_ID);
+            mConvoColLastActivity       = c.getColumnIndex(
+                    BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY);
+            mConvoColName               = c.getColumnIndex(
+                    BluetoothMapContract.ConversationColumns.THREAD_NAME);
+            mConvoColRead               = c.getColumnIndex(
+                    BluetoothMapContract.ConversationColumns.READ_STATUS);
+            mConvoColVersionCounter     = c.getColumnIndex(
+                    BluetoothMapContract.ConversationColumns.VERSION_COUNTER);
+            mConvoColSummary            = c.getColumnIndex(
+                    BluetoothMapContract.ConversationColumns.SUMMARY);
+            setEmailImConvoContactColumns(c);
+        }
+
+        public void setEmailImConvoContactColumns(Cursor c){
+            mContactColBtUid         = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.X_BT_UID);
+            mContactColChatState     = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.CHAT_STATE);
+            mContactColContactUci     = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.UCI);
+            mContactColNickname      = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.NICKNAME);
+            mContactColLastActive    = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE);
+            mContactColName          = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.NAME);
+            mContactColPresenceState = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE);
+            mContactColPresenceText = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.STATUS_TEXT);
+            mContactColPriority      = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.PRIORITY);
         }
 
         public void setSmsColumns(Cursor c) {
@@ -191,8 +414,9 @@
             mSmsColRead    = c.getColumnIndex(Sms.READ);
             mSmsColSubject = c.getColumnIndex(Sms.BODY);
             mSmsColAddress = c.getColumnIndex(Sms.ADDRESS);
-            mSmsColDate      = c.getColumnIndex(Sms.DATE);
-            mSmsColType      = c.getColumnIndex(Sms.TYPE);
+            mSmsColDate    = c.getColumnIndex(Sms.DATE);
+            mSmsColType    = c.getColumnIndex(Sms.TYPE);
+            mSmsColThreadId= c.getColumnIndex(Sms.THREAD_ID);
         }
 
         public void setMmsColumns(Cursor c) {
@@ -204,32 +428,40 @@
             mMmsColSize            = c.getColumnIndex(Mms.MESSAGE_SIZE);
             mMmsColDate            = c.getColumnIndex(Mms.DATE);
             mMmsColSubject         = c.getColumnIndex(Mms.SUBJECT);
-
+            mMmsColThreadId        = c.getColumnIndex(Mms.THREAD_ID);
         }
     }
 
-    public BluetoothMapContent(final Context context, String emailBaseUri) {
+    public BluetoothMapContent(final Context context, BluetoothMapAccountItem account,
+            BluetoothMapMasInstance mas) {
         mContext = context;
         mResolver = mContext.getContentResolver();
+        mMasInstance = mas;
         if (mResolver == null) {
             if (D) Log.d(TAG, "getContentResolver failed");
         }
-        mBaseEmailUri = emailBaseUri;
-    }
 
+        if(account != null){
+            mBaseUri = account.mBase_uri + "/";
+            mAccount = account;
+        } else {
+            mBaseUri = null;
+            mAccount = null;
+        }
+    }
     private static void close(Closeable c) {
         try {
           if (c != null) c.close();
         } catch (IOException e) {
         }
     }
-
     private void setProtected(BluetoothMapMessageListingElement e, Cursor c,
             FilterInfo fi, BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_PROTECTED) != 0) {
             String protect = "no";
-            if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-                int flagProtected = c.getInt(fi.mEmailColProtected);
+            if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+                fi.mMsgType == FilterInfo.TYPE_IM) {
+                int flagProtected = c.getInt(fi.mMessageColProtected);
                 if (flagProtected == 1) {
                     protect = "yes";
                 }
@@ -239,18 +471,39 @@
         }
     }
 
-    /**
-     * Email only
-     */
     private void setThreadId(BluetoothMapMessageListingElement e, Cursor c,
             FilterInfo fi, BluetoothMapAppParams ap) {
-        if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-            long threadId = c.getLong(fi.mEmailColThreadId);
-            e.setThreadId(threadId);
+        if ((ap.getParameterMask() & MASK_CONVERSATION_ID) != 0) {
+            long threadId = 0;
+            TYPE type = TYPE.SMS_GSM; // Just used for handle encoding
+            if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+                threadId = c.getLong(fi.mSmsColThreadId);
+            } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+                threadId = c.getLong(fi.mMmsColThreadId);
+                type = TYPE.MMS;// Just used for handle encoding
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+                       fi.mMsgType == FilterInfo.TYPE_IM) {
+                threadId = c.getLong(fi.mMessageColThreadId);
+                type = TYPE.EMAIL;// Just used for handle encoding
+            }
+            e.setThreadId(threadId,type);
             if (V) Log.d(TAG, "setThreadId: " + threadId + "\n");
         }
     }
 
+    private void setThreadName(BluetoothMapMessageListingElement e, Cursor c,
+            FilterInfo fi, BluetoothMapAppParams ap) {
+        // TODO: Maybe this should be valid for SMS/MMS
+        if ((ap.getParameterMask() & MASK_CONVERSATION_NAME) != 0) {
+            if (fi.mMsgType == FilterInfo.TYPE_IM) {
+                String threadName = c.getString(fi.mMessageColThreadName);
+                e.setThreadName(threadName);
+                if (V) Log.d(TAG, "setThreadName: " + threadName + "\n");
+            }
+        }
+    }
+
+
     private void setSent(BluetoothMapMessageListingElement e, Cursor c,
             FilterInfo fi, BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_SENT) != 0) {
@@ -259,8 +512,9 @@
                 msgType = c.getInt(fi.mSmsColFolder);
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 msgType = c.getInt(fi.mMmsColFolder);
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-                msgType = c.getInt(fi.mEmailColFolder);
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+                       fi.mMsgType == FilterInfo.TYPE_IM) {
+                msgType = c.getInt(fi.mMessageColFolder);
             }
             String sent = null;
             if (msgType == 2) {
@@ -280,21 +534,33 @@
             read = c.getInt(fi.mSmsColRead);
         } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
             read = c.getInt(fi.mMmsColRead);
-        } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-            read = c.getInt(fi.mEmailColRead);
+        } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+                   fi.mMsgType == FilterInfo.TYPE_IM) {
+            read = c.getInt(fi.mMessageColRead);
         }
         String setread = null;
 
         if (V) Log.d(TAG, "setRead: " + setread);
         e.setRead((read==1?true:false), ((ap.getParameterMask() & MASK_READ) != 0));
     }
+    private void setConvoRead(BluetoothMapConvoListingElement e, Cursor c,
+            FilterInfo fi, BluetoothMapAppParams ap) {
+        String setread = null;
+        int read = 0;
+            read = c.getInt(fi.mConvoColRead);
+
+
+        if (V) Log.d(TAG, "setRead: " + setread);
+        e.setRead((read==1?true:false), ((ap.getParameterMask() & MASK_READ) != 0));
+    }
 
     private void setPriority(BluetoothMapMessageListingElement e, Cursor c,
             FilterInfo fi, BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_PRIORITY) != 0) {
             String priority = "no";
-            if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-                int highPriority = c.getInt(fi.mEmailColPriority);
+            if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+                fi.mMsgType == FilterInfo.TYPE_IM) {
+                int highPriority = c.getInt(fi.mMessageColPriority);
                 if (highPriority == 1) {
                     priority = "yes";
                 }
@@ -318,10 +584,11 @@
      * the total message size. To provide a more accurate attachment size, one could
      * extract the length (in bytes) of the text parts.
      */
-    private void setAttachmentSize(BluetoothMapMessageListingElement e, Cursor c,
+    private void setAttachment(BluetoothMapMessageListingElement e, Cursor c,
             FilterInfo fi, BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_ATTACHMENT_SIZE) != 0) {
             int size = 0;
+            String attachmentMimeTypes = null;
             if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 if(c.getInt(fi.mMmsColTextOnly) == 0) {
                     size = c.getInt(fi.mMmsColAttachmentSize);
@@ -333,10 +600,11 @@
                                 + " Changing size to 1");
                         size = 1;
                     }
+                    // TODO: Add handling of attachemnt mime types
                 }
             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-                int attachment = c.getInt(fi.mEmailColAttachment);
-                size = c.getInt(fi.mEmailColAttachementSize);
+                int attachment = c.getInt(fi.mMessageColAttachment);
+                size = c.getInt(fi.mMessageColAttachmentSize);
                 if(attachment == 1 && size == 0) {
                     if (D) Log.d(TAG, "Error in message database, attachment size reported as: " + size
                             + " Changing size to 1");
@@ -344,9 +612,24 @@
                                  message has attachments, in case the e-mail client do not
                                  report a size */
                 }
+            } else if (fi.mMsgType == FilterInfo.TYPE_IM) {
+                int attachment = c.getInt(fi.mMessageColAttachment);
+                size = c.getInt(fi.mMessageColAttachmentSize);
+                if(attachment == 1 && size == 0) {
+                    size = 1; /* Ensure we indicate we have attachments in the size, it the
+                                  message has attachments, in case the e-mail client do not
+                                  report a size */
+                    attachmentMimeTypes =  c.getString(fi.mMessageColAttachmentMime);
+                }
             }
-            if (V) Log.d(TAG, "setAttachmentSize: " + size);
+            if (V) Log.d(TAG, "setAttachmentSize: " + size + "\n" +
+                              "setAttachmentMimeTypes: " + attachmentMimeTypes );
             e.setAttachmentSize(size);
+
+            if( (mMsgListingVersion > BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10)
+                    && ((ap.getParameterMask() & MASK_ATTACHMENT_MIME) != 0) ){
+                e.setAttachmentMimeTypes(attachmentMimeTypes);
+            }
         }
     }
 
@@ -362,14 +645,15 @@
                     hasText = "yes";
                 } else {
                     long id = c.getLong(fi.mMmsColId);
-                    String text = getTextPartsMms(id);
+                    String text = getTextPartsMms(mResolver, id);
                     if (text != null && text.length() > 0) {
                         hasText = "yes";
                     } else {
                         hasText = "no";
                     }
                 }
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+                       fi.mMsgType == FilterInfo.TYPE_IM) {
                 hasText = "yes";
             }
             if (V) Log.d(TAG, "setText: " + hasText);
@@ -386,6 +670,20 @@
         }
     }
 
+    private void setDeliveryStatus(BluetoothMapMessageListingElement e, Cursor c,
+            FilterInfo fi, BluetoothMapAppParams ap) {
+        if ((ap.getParameterMask() & MASK_DELIVERY_STATUS) != 0) {
+            String deliveryStatus = "delivered";
+            // TODO: Should be handled for SMS and MMS as well
+            if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+                fi.mMsgType == FilterInfo.TYPE_IM) {
+                deliveryStatus = c.getString(fi.mMessageColDelivery);
+            }
+            if (V) Log.d(TAG, "setDeliveryStatus: " + deliveryStatus);
+            e.setDeliveryStatus(deliveryStatus);
+        }
+    }
+
     private void setSize(BluetoothMapMessageListingElement e, Cursor c,
         FilterInfo fi, BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_SIZE) != 0) {
@@ -395,8 +693,9 @@
                 size = subject.length();
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 size = c.getInt(fi.mMmsColSize);
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-                size = c.getInt(fi.mEmailColSize);
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+                       fi.mMsgType == FilterInfo.TYPE_IM) {
+                size = c.getInt(fi.mMessageColSize);
             }
             if(size <= 0) {
                 // A message cannot have size 0
@@ -411,49 +710,209 @@
         }
     }
 
-    private void setType(BluetoothMapMessageListingElement e, Cursor c,
-        FilterInfo fi, BluetoothMapAppParams ap) {
-        if ((ap.getParameterMask() & MASK_TYPE) != 0) {
-            TYPE type = null;
-            if (fi.mMsgType == FilterInfo.TYPE_SMS) {
-                if (fi.mPhoneType == TelephonyManager.PHONE_TYPE_GSM) {
-                    type = TYPE.SMS_GSM;
-                } else if (fi.mPhoneType == TelephonyManager.PHONE_TYPE_CDMA) {
-                    type = TYPE.SMS_CDMA;
-                }
-            } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
-                type = TYPE.MMS;
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-                type = TYPE.EMAIL;
+    private TYPE getType(Cursor c, FilterInfo fi) {
+        TYPE type = null;
+        if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+            if (fi.mPhoneType == TelephonyManager.PHONE_TYPE_GSM) {
+                type = TYPE.SMS_GSM;
+            } else if (fi.mPhoneType == TelephonyManager.PHONE_TYPE_CDMA) {
+                type = TYPE.SMS_CDMA;
             }
-            if (V) Log.d(TAG, "setType: " + type);
-            e.setType(type);
+        } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+            type = TYPE.MMS;
+        } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
+            type = TYPE.EMAIL;
+        } else if (fi.mMsgType == FilterInfo.TYPE_IM) {
+            type = TYPE.IM;
+        }
+        if (V) Log.d(TAG, "getType: " + type);
+
+        return type;
+    }
+    private void setFolderType(BluetoothMapMessageListingElement e, Cursor c,
+            FilterInfo fi, BluetoothMapAppParams ap) {
+        if ((ap.getParameterMask() & MASK_FOLDER_TYPE) != 0) {
+            String folderType = null;
+            int folderId = 0;
+            if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+                folderId = c.getInt(fi.mSmsColFolder);
+                if (folderId == 1)
+                    folderType = BluetoothMapContract.FOLDER_NAME_INBOX;
+                else if (folderId == 2)
+                    folderType = BluetoothMapContract.FOLDER_NAME_SENT;
+                else if (folderId == 3)
+                    folderType = BluetoothMapContract.FOLDER_NAME_DRAFT;
+                else if (folderId == 4 || folderId == 5 || folderId == 6)
+                    folderType = BluetoothMapContract.FOLDER_NAME_OUTBOX;
+                else
+                    folderType = BluetoothMapContract.FOLDER_NAME_DELETED;
+            } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+                folderId = c.getInt(fi.mMmsColFolder);
+                if (folderId == 1)
+                    folderType = BluetoothMapContract.FOLDER_NAME_INBOX;
+                else if (folderId == 2)
+                    folderType = BluetoothMapContract.FOLDER_NAME_SENT;
+                else if (folderId == 3)
+                    folderType = BluetoothMapContract.FOLDER_NAME_DRAFT;
+                else if (folderId == 4)
+                    folderType = BluetoothMapContract.FOLDER_NAME_OUTBOX;
+                else
+                    folderType = BluetoothMapContract.FOLDER_NAME_DELETED;
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
+                // TODO: need to find name from id and then set folder type
+            } else if (fi.mMsgType == FilterInfo.TYPE_IM) {
+                folderId = c.getInt(fi.mMessageColFolder);
+                if (folderId == BluetoothMapContract.FOLDER_ID_INBOX)
+                    folderType = BluetoothMapContract.FOLDER_NAME_INBOX;
+                else if (folderId == BluetoothMapContract.FOLDER_ID_SENT)
+                    folderType = BluetoothMapContract.FOLDER_NAME_SENT;
+                else if (folderId == BluetoothMapContract.FOLDER_ID_DRAFT)
+                    folderType = BluetoothMapContract.FOLDER_NAME_DRAFT;
+                else if (folderId == BluetoothMapContract.FOLDER_ID_OUTBOX)
+                    folderType = BluetoothMapContract.FOLDER_NAME_OUTBOX;
+                else if (folderId == BluetoothMapContract.FOLDER_ID_DELETED)
+                    folderType = BluetoothMapContract.FOLDER_NAME_DELETED;
+                else
+                    folderType = BluetoothMapContract.FOLDER_NAME_OTHER;
+            }
+            if (V) Log.d(TAG, "setFolderType: " + folderType);
+            e.setFolderType(folderType);
         }
     }
 
-    private String setRecipientAddressingEmail(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi) {
-        String toAddress, ccAddress, bccAddress;
-        toAddress = c.getString(fi.mEmailColToAddress);
-        ccAddress = c.getString(fi.mEmailColCcAddress);
-        bccAddress = c.getString(fi.mEmailColBccAddress);
+ private String getRecipientNameEmail(BluetoothMapMessageListingElement e,
+                                      Cursor c,
+                                      FilterInfo fi) {
 
-        String address = "";
+        String toAddress, ccAddress, bccAddress;
+        toAddress = c.getString(fi.mMessageColToAddress);
+        ccAddress = c.getString(fi.mMessageColCcAddress);
+        bccAddress = c.getString(fi.mMessageColBccAddress);
+
+        StringBuilder sb = new StringBuilder();
         if (toAddress != null) {
-            address += toAddress;
+            Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(toAddress);
+            if (tokens.length != 0) {
+                if(D) Log.d(TAG, "toName count= " + tokens.length);
+                int i = 0;
+                boolean first = true;
+                while (i < tokens.length) {
+                    if(V) Log.d(TAG, "ToName = " + tokens[i].toString());
+                    String name = tokens[i].getName();
+                    if(!first) sb.append("; "); //Delimiter
+                    sb.append(name);
+                    first = false;
+                    i++;
+                }
+            }
+
             if (ccAddress != null) {
-                address += ",";
+                sb.append("; ");
             }
         }
         if (ccAddress != null) {
-            address += ccAddress;
+            Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(ccAddress);
+            if (tokens.length != 0) {
+                if(D) Log.d(TAG, "ccName count= " + tokens.length);
+                int i = 0;
+                boolean first = true;
+                while (i < tokens.length) {
+                    if(V) Log.d(TAG, "ccName = " + tokens[i].toString());
+                    String name = tokens[i].getName();
+                    if(!first) sb.append("; "); //Delimiter
+                    sb.append(name);
+                    first = false;
+                    i++;
+                }
+            }
             if (bccAddress != null) {
-                address += ",";
+                sb.append("; ");
             }
         }
         if (bccAddress != null) {
-            address += bccAddress;
+            Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(bccAddress);
+            if (tokens.length != 0) {
+                if(D) Log.d(TAG, "bccName count= " + tokens.length);
+                int i = 0;
+                boolean first = true;
+                while (i < tokens.length) {
+                    if(V) Log.d(TAG, "bccName = " + tokens[i].toString());
+                    String name = tokens[i].getName();
+                    if(!first) sb.append("; "); //Delimiter
+                    sb.append(name);
+                    first = false;
+                    i++;
+                }
+            }
         }
-        return address;
+        return sb.toString();
+    }
+
+    private String getRecipientAddressingEmail(BluetoothMapMessageListingElement e,
+                                               Cursor c,
+                                               FilterInfo fi) {
+        String toAddress, ccAddress, bccAddress;
+        toAddress = c.getString(fi.mMessageColToAddress);
+        ccAddress = c.getString(fi.mMessageColCcAddress);
+        bccAddress = c.getString(fi.mMessageColBccAddress);
+
+        StringBuilder sb = new StringBuilder();
+        if (toAddress != null) {
+            Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(toAddress);
+            if (tokens.length != 0) {
+                if(D) Log.d(TAG, "toAddress count= " + tokens.length);
+                int i = 0;
+                boolean first = true;
+                while (i < tokens.length) {
+                    if(V) Log.d(TAG, "ToAddress = " + tokens[i].toString());
+                    String email = tokens[i].getAddress();
+                    if(!first) sb.append("; "); //Delimiter
+                    sb.append(email);
+                    first = false;
+                    i++;
+                }
+            }
+
+            if (ccAddress != null) {
+                sb.append("; ");
+            }
+        }
+        if (ccAddress != null) {
+            Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(ccAddress);
+            if (tokens.length != 0) {
+                if(D) Log.d(TAG, "ccAddress count= " + tokens.length);
+                int i = 0;
+                boolean first = true;
+                while (i < tokens.length) {
+                    if(V) Log.d(TAG, "ccAddress = " + tokens[i].toString());
+                    String email = tokens[i].getAddress();
+                    if(!first) sb.append("; "); //Delimiter
+                    sb.append(email);
+                    first = false;
+                    i++;
+                }
+            }
+            if (bccAddress != null) {
+                sb.append("; ");
+            }
+        }
+        if (bccAddress != null) {
+            Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(bccAddress);
+            if (tokens.length != 0) {
+                if(D) Log.d(TAG, "bccAddress count= " + tokens.length);
+                int i = 0;
+                boolean first = true;
+                while (i < tokens.length) {
+                    if(V) Log.d(TAG, "bccAddress = " + tokens[i].toString());
+                    String email = tokens[i].getAddress();
+                    if(!first) sb.append("; "); //Delimiter
+                    sb.append(email);
+                    first = false;
+                    i++;
+                }
+            }
+        }
+        return sb.toString();
     }
 
     private void setRecipientAddressing(BluetoothMapMessageListingElement e, Cursor c,
@@ -472,7 +931,7 @@
                 address = getAddressMms(mResolver, id, MMS_TO);
             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
                 /* Might be another way to handle addresses */
-                address = setRecipientAddressingEmail(e, c,fi);
+                address = getRecipientAddressingEmail(e, c,fi);
             }
             if (V) Log.v(TAG, "setRecipientAddressing: " + address);
             if(address == null)
@@ -490,7 +949,7 @@
                 if (msgType != 1) {
                     String phone = c.getString(fi.mSmsColAddress);
                     if (phone != null && !phone.isEmpty())
-                        name = getContactNameFromPhone(phone);
+                        name = getContactNameFromPhone(phone, mResolver);
                 } else {
                     name = fi.mPhoneAlphaTag;
                 }
@@ -503,10 +962,10 @@
                     phone = e.getRecipientAddressing();
                 }
                 if (phone != null && !phone.isEmpty())
-                    name = getContactNameFromPhone(phone);
+                    name = getContactNameFromPhone(phone, mResolver);
             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
                 /* Might be another way to handle address and names */
-                name = setRecipientAddressingEmail(e,c,fi);
+                name = getRecipientNameEmail(e,c,fi);
             }
             if (V) Log.v(TAG, "setRecipientName: " + name);
             if(name == null)
@@ -518,7 +977,7 @@
     private void setSenderAddressing(BluetoothMapMessageListingElement e, Cursor c,
             FilterInfo fi, BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_SENDER_ADDRESSING) != 0) {
-            String address = null;
+            String address = "";
             String tempAddress;
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
                 int msgType = c.getInt(fi.mSmsColType);
@@ -532,11 +991,13 @@
                        hence will typically not have any SMS messages. */
                 } else {
                     address = PhoneNumberUtils.extractNetworkPortion(tempAddress);
-                    /* extractNetworkPortion can return N if the number is a service "number" = a string
-                     * with the a name in (i.e. "Some-Tele-company" would return N because of the N in compaNy)
+                    /* extractNetworkPortion can return N if the number is a service "number" =
+                     * a string with the a name in (i.e. "Some-Tele-company" would return N
+                     * because of the N in compaNy)
                      * Hence we need to check if the number is actually a string with alpha chars.
                      * */
-                    Boolean alpha = PhoneNumberUtils.stripSeparators(tempAddress).matches("[0-9]*[a-zA-Z]+[0-9]*");
+                    Boolean alpha = PhoneNumberUtils.stripSeparators(tempAddress).matches(
+                            "[0-9]*[a-zA-Z]+[0-9]*");
 
                     if(address == null || address.length() < 2 || alpha) {
                         address = tempAddress; // if the number is a service acsii text just use it
@@ -549,8 +1010,46 @@
                 if(address == null || address.length() < 1){
                     address = tempAddress; // if the number is a service acsii text just use it
                 }
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-                address = c.getString(fi.mEmailColFromAddress);
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL/* ||
+                       fi.mMsgType == FilterInfo.TYPE_IM*/) {
+                String nameEmail = c.getString(fi.mMessageColFromAddress);
+                Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
+                if (tokens.length != 0) {
+                    if(D) Log.d(TAG, "Originator count= " + tokens.length);
+                    int i = 0;
+                    boolean first = true;
+                    while (i < tokens.length) {
+                        if(V) Log.d(TAG, "SenderAddress = " + tokens[i].toString());
+                        String[] emails = new String[1];
+                        emails[0] = tokens[i].getAddress();
+                        String name = tokens[i].getName();
+                        if(!first) address += "; "; //Delimiter
+                        address += emails[0];
+                        first = false;
+                        i++;
+                    }
+                }
+            } else if(fi.mMsgType == FilterInfo.TYPE_IM) {
+                // TODO: For IM we add the contact ID in the addressing
+                long contact_id = c.getLong(fi.mMessageColFromAddress);
+                // TODO: This is a BAD hack, that we map the contact ID to a conversation ID!!!
+                //       We need to reach a conclusion on what to do
+                Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
+                Cursor contacts = mResolver.query(contactsUri,
+                                           BluetoothMapContract.BT_CONTACT_PROJECTION,
+                                           BluetoothMapContract.ConvoContactColumns.CONVO_ID
+                                           + " = " + contact_id, null, null);
+                try {
+                    // TODO this will not work for group-chats
+                    if(contacts != null && contacts.moveToFirst()){
+                        address = contacts.getString(
+                                contacts.getColumnIndex(
+                                        BluetoothMapContract.ConvoContactColumns.UCI));
+                    }
+                } finally {
+                    if (contacts != null) contacts.close();
+                }
+
             }
             if (V) Log.v(TAG, "setSenderAddressing: " + address);
             if(address == null)
@@ -562,13 +1061,13 @@
     private void setSenderName(BluetoothMapMessageListingElement e, Cursor c,
             FilterInfo fi, BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_SENDER_NAME) != 0) {
-            String name = null;
+            String name = "";
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
                 int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
                 if (msgType == 1) {
                     String phone = c.getString(fi.mSmsColAddress);
                     if (phone != null && !phone.isEmpty())
-                        name = getContactNameFromPhone(phone);
+                        name = getContactNameFromPhone(phone, mResolver);
                 } else {
                     name = fi.mPhoneAlphaTag;
                 }
@@ -581,9 +1080,44 @@
                     phone = e.getSenderAddressing();
                 }
                 if (phone != null && !phone.isEmpty() )
-                    name = getContactNameFromPhone(phone);
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-                name = c.getString(fi.mEmailColFromAddress);
+                    name = getContactNameFromPhone(phone, mResolver);
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL/*  ||
+                       fi.mMsgType == FilterInfo.TYPE_IM*/) {
+                String nameEmail = c.getString(fi.mMessageColFromAddress);
+                Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
+                if (tokens.length != 0) {
+                    if(D) Log.d(TAG, "Originator count= " + tokens.length);
+                    int i = 0;
+                    boolean first = true;
+                    while (i < tokens.length) {
+                        if(V) Log.d(TAG, "senderName = " + tokens[i].toString());
+                        String[] emails = new String[1];
+                        emails[0] = tokens[i].getAddress();
+                        String nameIn = tokens[i].getName();
+                        if(!first) name += "; "; //Delimiter
+                        name += nameIn;
+                        first = false;
+                        i++;
+                    }
+                }
+            } else if(fi.mMsgType == FilterInfo.TYPE_IM) {
+                // For IM we add the contact ID in the addressing
+                long contact_id = c.getLong(fi.mMessageColFromAddress);
+                Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
+                Cursor contacts = mResolver.query(contactsUri,
+                                           BluetoothMapContract.BT_CONTACT_PROJECTION,
+                                           BluetoothMapContract.ConvoContactColumns.CONVO_ID
+                                           + " = " + contact_id, null, null);
+                try {
+                    // TODO this will not work for group-chats
+                    if(contacts != null && contacts.moveToFirst()){
+                        name = contacts.getString(
+                                contacts.getColumnIndex(
+                                        BluetoothMapContract.ConvoContactColumns.NAME));
+                    }
+                } finally {
+                    if (contacts != null) contacts.close();
+                }
             }
             if (V) Log.v(TAG, "setSenderName: " + name);
             if(name == null)
@@ -592,6 +1126,9 @@
         }
     }
 
+
+
+
     private void setDateTime(BluetoothMapMessageListingElement e, Cursor c,
             FilterInfo fi, BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_DATETIME) != 0) {
@@ -609,34 +1146,54 @@
                 /* } else { */
                 /*     date = c.getLong(c.getColumnIndex(Mms.DATE_SENT)) * 1000L; */
                 /* } */
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-                date = c.getLong(fi.mEmailColDate);
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+                       fi.mMsgType == FilterInfo.TYPE_IM) {
+                date = c.getLong(fi.mMessageColDate);
             }
             e.setDateTime(date);
         }
     }
 
-    private String getTextPartsMms(long id) {
+
+    private void setLastActivity(BluetoothMapConvoListingElement e, Cursor c,
+            FilterInfo fi, BluetoothMapAppParams ap) {
+        long date = 0;
+        if (fi.mMsgType == FilterInfo.TYPE_SMS ||
+                fi.mMsgType == FilterInfo.TYPE_MMS ) {
+            date = c.getLong(MMS_SMS_THREAD_COL_DATE);
+        } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
+                fi.mMsgType == FilterInfo.TYPE_IM) {
+            date = c.getLong(fi.mConvoColLastActivity);
+        }
+        e.setLastActivity(date);
+        if (V) Log.v(TAG, "setDateTime: " + e.getLastActivityString());
+
+    }
+
+    static public String getTextPartsMms(ContentResolver r, long id) {
         String text = "";
         String selection = new String("mid=" + id);
         String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/part");
         Uri uriAddress = Uri.parse(uriStr);
         // TODO: maybe use a projection with only "ct" and "text"
-
-        Cursor c = mResolver.query(uriAddress, null, selection, null, null);
+        Cursor c = r.query(uriAddress, null, selection,
+            null, null);
         try {
-            while(c != null && c.moveToNext()) {
-                String ct = c.getString(c.getColumnIndex("ct"));
-                if (ct.equals("text/plain")) {
-                    String part = c.getString(c.getColumnIndex("text"));
-                    if(part != null) {
-                        text += part;
+            if (c != null && c.moveToFirst()) {
+                do {
+                    String ct = c.getString(c.getColumnIndex("ct"));
+                    if (ct.equals("text/plain")) {
+                        String part = c.getString(c.getColumnIndex("text"));
+                        if(part != null) {
+                            text += part;
+                        }
                     }
-                }
+                } while(c.moveToNext());
             }
         } finally {
-            close(c);
+            if (c != null) c.close();
         }
+
         return text;
     }
 
@@ -655,10 +1212,11 @@
                 if (subject == null || subject.length() == 0) {
                     /* Get subject from mms text body parts - if any exists */
                     long id = c.getLong(fi.mMmsColId);
-                    subject = getTextPartsMms(id);
+                    subject = getTextPartsMms(mResolver, id);
                 }
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-                subject = c.getString(fi.mEmailColSubject);
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL  ||
+                       fi.mMsgType == FilterInfo.TYPE_IM) {
+                subject = c.getString(fi.mMessageColSubject);
             }
             if (subject != null && subject.length() > subLength) {
                 subject = subject.substring(0, subLength);
@@ -675,8 +1233,9 @@
             handle = c.getLong(fi.mSmsColId);
         } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
             handle = c.getLong(fi.mMmsColId);
-        } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-            handle = c.getLong(fi.mEmailColId);
+        } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+                   fi.mMsgType == FilterInfo.TYPE_IM) {
+            handle = c.getLong(fi.mMessageColId);
         }
         if (V) Log.d(TAG, "setHandle: " + handle );
         e.setHandle(handle);
@@ -687,7 +1246,7 @@
         BluetoothMapMessageListingElement e = new BluetoothMapMessageListingElement();
         setHandle(e, c, fi, ap);
         setDateTime(e, c, fi, ap);
-        setType(e, c, fi, ap);
+        e.setType(getType(c, fi), ((ap.getParameterMask() & MASK_TYPE) != 0) ? true : false);
         setRead(e, c, fi, ap);
         // we set number and name for sender/recipient later
         // they require lookup on contacts so no need to
@@ -696,7 +1255,19 @@
         return e;
     }
 
-    private String getContactNameFromPhone(String phone) {
+    private BluetoothMapConvoListingElement createConvoElement(Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
+        BluetoothMapConvoListingElement e = new BluetoothMapConvoListingElement();
+        setLastActivity(e, c, fi, ap);
+        e.setType(getType(c, fi));
+//        setConvoRead(e, c, fi, ap);
+        e.setCursorIndex(c.getPosition());
+        return e;
+    }
+
+    /* TODO: Change to use SmsMmsContacts.getContactNameFromPhone() with proper use of
+     *       caching. */
+    public static String getContactNameFromPhone(String phone, ContentResolver resolver) {
         String name = null;
 
         Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
@@ -705,14 +1276,18 @@
         String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME};
         String selection = Contacts.IN_VISIBLE_GROUP + "=1";
         String orderBy = Contacts.DISPLAY_NAME + " ASC";
-
-        Cursor c = mResolver.query(uri, projection, selection, null, orderBy);
+        Cursor c = null;
         try {
-            if (c != null && c.moveToFirst()) {
-                name = c.getString(c.getColumnIndex(Contacts.DISPLAY_NAME));
-            };
+            c = resolver.query(uri, projection, selection, null, orderBy);
+            if(c != null) {
+                int colIndex = c.getColumnIndex(Contacts.DISPLAY_NAME);
+                if (c.getCount() >= 1) {
+                    c.moveToFirst();
+                    name = c.getString(colIndex);
+                }
+            }
         } finally {
-            close(c);
+            if(c != null) c.close();
         }
         return name;
     }
@@ -722,17 +1297,22 @@
         String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr");
         Uri uriAddress = Uri.parse(uriStr);
         String addr = null;
-
-        Cursor c = r.query(uriAddress, null, selection, null, null);
+        String[] projection = {Mms.Addr.ADDRESS};
+        Cursor c = null;
         try {
-            if (c != null && c.moveToFirst()) {
-                addr = c.getString(c.getColumnIndex(Mms.Addr.ADDRESS));
-                if (addr.equals(INSERT_ADDRES_TOKEN)) addr  = "";
+            c = r.query(uriAddress, projection, selection, null, null); // TODO: Add projection
+            int colIndex = c.getColumnIndex(Mms.Addr.ADDRESS);
+            if (c != null) {
+                if(c.moveToFirst()) {
+                    addr = c.getString(colIndex);
+                    if(addr.equals(INSERT_ADDRES_TOKEN)) {
+                        addr  = "";
+                    }
+                }
             }
         } finally {
-            close(c);
+            if (c != null) c.close();
         }
-
         return addr;
     }
 
@@ -749,7 +1329,7 @@
                 if (V) Log.v(TAG, "matchRecipientMms: match recipient phone = " + phone);
                 res = true;
             } else {
-                String name = getContactNameFromPhone(phone);
+                String name = getContactNameFromPhone(phone, mResolver);
                 if (name != null && name.length() > 0 && name.matches(recip)) {
                     if (V) Log.v(TAG, "matchRecipientMms: match recipient name = " + name);
                     res = true;
@@ -785,7 +1365,7 @@
                     if (V) Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone);
                     res = true;
                 } else {
-                    String name = getContactNameFromPhone(phone);
+                    String name = getContactNameFromPhone(phone, mResolver);
                     if (name != null && name.length() > 0 && name.matches(recip)) {
                         if (V) Log.v(TAG, "matchRecipientSms: match recipient name = " + name);
                         res = true;
@@ -829,7 +1409,7 @@
                 if (V) Log.v(TAG, "matchOriginatorMms: match originator phone = " + phone);
                 res = true;
             } else {
-                String name = getContactNameFromPhone(phone);
+                String name = getContactNameFromPhone(phone, mResolver);
                 if (name != null && name.length() > 0 && name.matches(orig)) {
                     if (V) Log.v(TAG, "matchOriginatorMms: match originator name = " + name);
                     res = true;
@@ -853,7 +1433,7 @@
                     if (V) Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone);
                     res = true;
                 } else {
-                    String name = getContactNameFromPhone(phone);
+                    String name = getContactNameFromPhone(phone, mResolver);
                     if (name != null && name.length() > 0 && name.matches(orig)) {
                         if (V) Log.v(TAG, "matchOriginatorSms: match originator name = " + name);
                         res = true;
@@ -957,14 +1537,32 @@
         return where;
     }
 
-    private String setWhereFilterFolderType(BluetoothMapFolderElement folderElement, FilterInfo fi) {
+    private String setWhereFilterFolderTypeIm(long folderId) {
         String where = "";
-        if (fi.mMsgType == FilterInfo.TYPE_SMS) {
-            where = setWhereFilterFolderTypeSms(folderElement.getName());
-        } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
-            where = setWhereFilterFolderTypeMms(folderElement.getName());
-        } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-            where = setWhereFilterFolderTypeEmail(folderElement.getEmailFolderId());
+        if (folderId > BluetoothMapContract.FOLDER_ID_OTHER) {
+            where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + folderId;
+        } else {
+            Log.e(TAG, "setWhereFilterFolderTypeIm: not valid!" );
+            throw new IllegalArgumentException("Invalid folder ID");
+        }
+        return where;
+    }
+
+    private String setWhereFilterFolderType(BluetoothMapFolderElement folderElement,
+                                            FilterInfo fi) {
+        String where = "";
+        if(folderElement.shouldIgnore()) {
+            where = "1=1";
+        } else {
+            if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+                where = setWhereFilterFolderTypeSms(folderElement.getName());
+            } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+                where = setWhereFilterFolderTypeMms(folderElement.getName());
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
+                where = setWhereFilterFolderTypeEmail(folderElement.getFolderId());
+            } else if (fi.mMsgType == FilterInfo.TYPE_IM) {
+                where = setWhereFilterFolderTypeIm(folderElement.getFolderId());
+            }
         }
         return where;
     }
@@ -988,11 +1586,11 @@
                 if ((ap.getFilterReadStatus() & 0x02) != 0) {
                     where = " AND " + Mms.READ + "= 1";
                 }
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+                       fi.mMsgType == FilterInfo.TYPE_IM) {
                 if ((ap.getFilterReadStatus() & 0x01) != 0) {
                     where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 0";
                 }
-
                 if ((ap.getFilterReadStatus() & 0x02) != 0) {
                     where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 1";
                 }
@@ -1003,27 +1601,55 @@
 
     private String setWhereFilterPeriod(BluetoothMapAppParams ap, FilterInfo fi) {
         String where = "";
+
         if ((ap.getFilterPeriodBegin() != -1)) {
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
-            where = " AND " + Sms.DATE + " >= " + ap.getFilterPeriodBegin();
+                where = " AND " + Sms.DATE + " >= " + ap.getFilterPeriodBegin();
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 where = " AND " + Mms.DATE + " >= " + (ap.getFilterPeriodBegin() / 1000L);
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-                where = " AND " + BluetoothMapContract.MessageColumns.DATE + " >= " + (ap.getFilterPeriodBegin());
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
+                       fi.mMsgType == FilterInfo.TYPE_IM) {
+                where = " AND " + BluetoothMapContract.MessageColumns.DATE +
+                        " >= " + (ap.getFilterPeriodBegin());
             }
         }
 
         if ((ap.getFilterPeriodEnd() != -1)) {
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
-            where += " AND " + Sms.DATE + " < " + ap.getFilterPeriodEnd();
+                where += " AND " + Sms.DATE + " < " + ap.getFilterPeriodEnd();
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 where += " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L);
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
-                where += " AND " + BluetoothMapContract.MessageColumns.DATE + " < " + (ap.getFilterPeriodEnd());
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
+                       fi.mMsgType == FilterInfo.TYPE_IM) {
+                where += " AND " + BluetoothMapContract.MessageColumns.DATE +
+                        " < " + (ap.getFilterPeriodEnd());
             }
         }
-
-
+        return where;
+    }
+    private String setWhereFilterLastActivity(BluetoothMapAppParams ap, FilterInfo fi) {
+            String where = "";
+        if ((ap.getFilterLastActivityBegin() != -1)) {
+            if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+                where = " AND " + Sms.DATE + " >= " + ap.getFilterLastActivityBegin();
+            } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+                where = " AND " + Mms.DATE + " >= " + (ap.getFilterLastActivityBegin() / 1000L);
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
+                      fi.mMsgType == FilterInfo.TYPE_IM ) {
+                where = " AND " + BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY +
+                        " >= " + (ap.getFilterPeriodBegin());
+            }
+        }
+        if ((ap.getFilterLastActivityEnd() != -1)) {
+            if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+                where += " AND " + Sms.DATE + " < " + ap.getFilterLastActivityEnd();
+            } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+                where += " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L);
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||fi.mMsgType == FilterInfo.TYPE_IM) {
+                where += " AND " + BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
+                      + " < " + (ap.getFilterLastActivityEnd());
+            }
+        }
         return where;
     }
 
@@ -1035,7 +1661,6 @@
             ContactsContract.Contacts.DISPLAY_NAME + " like ?",
             new String[]{str},
             ContactsContract.Contacts.DISPLAY_NAME + " ASC");
-
         try {
             while (c != null && c.moveToNext()) {
                 String contactId = c.getString(c.getColumnIndex(ContactsContract.Contacts._ID));
@@ -1044,25 +1669,30 @@
                     ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
                     new String[]{contactId},
                     null);
-
                 try {
                     while (p != null && p.moveToNext()) {
                         String number = p.getString(
                             p.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
 
                         where += " address = " + "'" + number + "'";
-                        if (!p.isLast()) where += " OR ";
+                        if (!p.isLast()) {
+                            where += " OR ";
+                        }
+                    }
+                    if (!c.isLast()) {
+                        where += " OR ";
                     }
                 } finally {
-                    close(p);
+                    if (p != null) p.close();
                 }
 
                 if (!c.isLast()) where += " OR ";
             }
         } finally {
-            close(c);
+            if (c != null) c.close();
         }
 
+
         if (str != null && str.length() > 0) {
             if (where.length() > 0) {
                 where += " OR ";
@@ -1080,10 +1710,25 @@
         /* Be aware of wild cards in the beginning of string, may not be valid? */
         if (orig != null && orig.length() > 0) {
             orig = orig.replace("*", "%");
-            where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST + " LIKE '%" +  orig + "%'";
+            where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST
+                    + " LIKE '%" +  orig + "%'";
         }
         return where;
     }
+
+    private String setWhereFilterOriginatorIM(BluetoothMapAppParams ap) {
+        String where = "";
+        String orig = ap.getFilterOriginator();
+
+        /* Be aware of wild cards in the beginning of string, may not be valid? */
+        if (orig != null && orig.length() > 0) {
+            orig = orig.replace("*", "%");
+            where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST
+                    + " LIKE '%" +  orig + "%'";
+        }
+        return where;
+    }
+
     private String setWhereFilterPriority(BluetoothMapAppParams ap, FilterInfo fi) {
         String where = "";
         int pri = ap.getFilterPriority();
@@ -1099,6 +1744,17 @@
                     Integer.toString(PduHeaders.PRIORITY_HIGH);
             }
         }
+        if(fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+           fi.mMsgType == FilterInfo.TYPE_IM)
+        {
+            if(pri == 0x0002)
+            {
+                where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "!=1";
+            }else if(pri == 0x0001) {
+                where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "=1";
+            }
+        }
+        // TODO: no priority filtering in IM
         return where;
     }
 
@@ -1117,26 +1773,117 @@
         return where;
     }
 
+    private String setWhereFilterMessageHandle(BluetoothMapAppParams ap, FilterInfo fi) {
+        String where = "";
+        long id = -1;
+        String msgHandle = ap.getFilterMsgHandleString();
+        if(msgHandle != null) {
+            id = BluetoothMapUtils.getCpHandle(msgHandle);
+            if(D)Log.d(TAG,"id: " + id);
+        }
+        if(id != -1) {
+            if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+               where = " AND " + Sms._ID + " = " + id;
+            } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+                where = " AND " + Mms._ID + " = " + id;
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+                       fi.mMsgType == FilterInfo.TYPE_IM) {
+                where = " AND " + BluetoothMapContract.MessageColumns._ID + " = " + id;
+            }
+        }
+        return where;
+    }
+
+    private String setWhereFilterThreadId(BluetoothMapAppParams ap, FilterInfo fi) {
+        String where = "";
+        long id = -1;
+        String msgHandle = ap.getFilterConvoIdString();
+        if(msgHandle != null) {
+            id = BluetoothMapUtils.getMsgHandleAsLong(msgHandle);
+            if(D)Log.d(TAG,"id: " + id);
+        }
+        if(id > 0) {
+            if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+               where = " AND " + Sms.THREAD_ID + " = " + id;
+            } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+                where = " AND " + Mms.THREAD_ID + " = " + id;
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+                       fi.mMsgType == FilterInfo.TYPE_IM) {
+                where = " AND " + BluetoothMapContract.MessageColumns.THREAD_ID + " = " + id;
+            }
+        }
+
+        return where;
+    }
+
     private String setWhereFilter(BluetoothMapFolderElement folderElement,
             FilterInfo fi, BluetoothMapAppParams ap) {
         String where = "";
-
         where += setWhereFilterFolderType(folderElement, fi);
-        if(!where.isEmpty()) {
-            where += setWhereFilterReadStatus(ap, fi);
-            where += setWhereFilterPeriod(ap, fi);
-            where += setWhereFilterPriority(ap,fi);
 
+        String msgHandleWhere = setWhereFilterMessageHandle(ap, fi);
+        /* if message handle filter is available, the other filters should be ignored */
+        if(msgHandleWhere.isEmpty()) {
+            where += setWhereFilterReadStatus(ap, fi);
+            where += setWhereFilterPriority(ap,fi);
+            where += setWhereFilterPeriod(ap, fi);
             if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
                 where += setWhereFilterOriginatorEmail(ap);
                 where += setWhereFilterRecipientEmail(ap);
             }
+            if (fi.mMsgType == FilterInfo.TYPE_IM) {
+                where += setWhereFilterOriginatorIM(ap);
+                // TODO: set 'where' filer recipient?
+            }
+            where += setWhereFilterThreadId(ap, fi);
+        } else {
+            where += msgHandleWhere;
         }
 
-
         return where;
     }
 
+
+    /* Used only for SMS/MMS */
+    private void setConvoWhereFilterSmsMms(StringBuilder selection, ArrayList<String> selectionArgs,
+            FilterInfo fi, BluetoothMapAppParams ap) {
+
+        if (smsSelected(fi, ap) || mmsSelected(ap)) {
+
+            // Filter Read Status
+            if(ap.getFilterReadStatus() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
+                if ((ap.getFilterReadStatus() & FILTER_READ_STATUS_UNREAD_ONLY) != 0) {
+                    selection.append(" AND ").append(Threads.READ).append(" = 0");
+                }
+                if ((ap.getFilterReadStatus() & FILTER_READ_STATUS_READ_ONLY) != 0) {
+                    selection.append(" AND ").append(Threads.READ).append(" = 1");
+                }
+            }
+
+            // Filter time
+            if ((ap.getFilterLastActivityBegin() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)){
+                selection.append(" AND ").append(Threads.DATE).append(" >= ")
+                .append(ap.getFilterLastActivityBegin());
+            }
+            if ((ap.getFilterLastActivityEnd() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)) {
+                selection.append(" AND ").append(Threads.DATE).append(" <= ")
+                .append(ap.getFilterLastActivityEnd());
+            }
+
+            // Filter ConvoId
+            long convoId = -1;
+            if(ap.getFilterConvoId() != null) {
+                convoId = ap.getFilterConvoId().getLeastSignificantBits();
+            }
+            if(convoId > 0) {
+                selection.append(" AND ").append(Threads._ID).append(" = ")
+                .append(Long.toString(convoId));
+            }
+        }
+    }
+
+
+
     /**
      * Determine from application parameter if sms should be included.
      * The filter mask is set for message types not selected
@@ -1150,16 +1897,19 @@
 
         if (D) Log.d(TAG, "smsSelected msgType: " + msgType);
 
-        if (msgType == -1)
+        if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
             return true;
 
-        if ((msgType & 0x03) == 0)
+        if ((msgType & (BluetoothMapAppParams.FILTER_NO_SMS_CDMA
+                |BluetoothMapAppParams.FILTER_NO_SMS_GSM)) == 0)
             return true;
 
-        if (((msgType & 0x01) == 0) && (phoneType == TelephonyManager.PHONE_TYPE_GSM))
+        if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_GSM) == 0)
+                && (phoneType == TelephonyManager.PHONE_TYPE_GSM))
             return true;
 
-        if (((msgType & 0x02) == 0) && (phoneType == TelephonyManager.PHONE_TYPE_CDMA))
+        if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_CDMA) == 0)
+                && (phoneType == TelephonyManager.PHONE_TYPE_CDMA))
             return true;
 
         return false;
@@ -1170,17 +1920,17 @@
      * The filter mask is set for message types not selected
      * @param fi
      * @param ap
-     * @return boolean true if sms is selected, false if not
+     * @return boolean true if mms is selected, false if not
      */
-    private boolean mmsSelected(FilterInfo fi, BluetoothMapAppParams ap) {
+    private boolean mmsSelected(BluetoothMapAppParams ap) {
         int msgType = ap.getFilterMessageType();
 
         if (D) Log.d(TAG, "mmsSelected msgType: " + msgType);
 
-        if (msgType == -1)
+        if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
             return true;
 
-        if ((msgType & 0x08) == 0)
+        if ((msgType & BluetoothMapAppParams.FILTER_NO_MMS) == 0)
             return true;
 
         return false;
@@ -1191,24 +1941,46 @@
      * The filter mask is set for message types not selected
      * @param fi
      * @param ap
-     * @return boolean true if sms is selected, false if not
+     * @return boolean true if email is selected, false if not
      */
-    private boolean emailSelected(FilterInfo fi, BluetoothMapAppParams ap) {
+    private boolean emailSelected(BluetoothMapAppParams ap) {
         int msgType = ap.getFilterMessageType();
 
         if (D) Log.d(TAG, "emailSelected msgType: " + msgType);
 
-        if (msgType == -1)
+        if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
             return true;
 
-        if ((msgType & 0x04) == 0)
+        if ((msgType & BluetoothMapAppParams.FILTER_NO_EMAIL) == 0)
+            return true;
+
+        return false;
+    }
+
+    /**
+     * Determine from application parameter if IM should be included.
+     * The filter mask is set for message types not selected
+     * @param fi
+     * @param ap
+     * @return boolean true if im is selected, false if not
+     */
+    private boolean imSelected(BluetoothMapAppParams ap) {
+        int msgType = ap.getFilterMessageType();
+
+        if (D) Log.d(TAG, "imSelected msgType: " + msgType);
+
+        if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+            return true;
+
+        if ((msgType & BluetoothMapAppParams.FILTER_NO_IM) == 0)
             return true;
 
         return false;
     }
 
     private void setFilterInfo(FilterInfo fi) {
-        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        TelephonyManager tm = 
+            (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
         if (tm != null) {
             fi.mPhoneType = tm.getPhoneType();
             fi.mPhoneNum = tm.getLine1Number();
@@ -1227,20 +1999,21 @@
      */
     public BluetoothMapMessageListing msgListing(BluetoothMapFolderElement folderElement,
             BluetoothMapAppParams ap) {
-        if (D) Log.d(TAG, "msgListing: folderName = " + folderElement.getName()
-                + " folderId = " + folderElement.getEmailFolderId()
-                + " messageType = " + ap.getFilterMessageType() );
-        BluetoothMapMessageListing bmList = new BluetoothMapMessageListing();
+        if (D) Log.d(TAG, "msgListing: messageType = " + ap.getFilterMessageType() );
 
+        BluetoothMapMessageListing bmList = new BluetoothMapMessageListing();
 
         /* 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 ||
                 ap.getParameterMask() == 0) {
-            ap.setParameterMask(BluetoothMapAppParams.PARAMETER_MASK_ALL_ENABLED);
+            ap.setParameterMask(PARAMETER_MASK_DEFAULT);
             if (V) Log.v(TAG, "msgListing(): appParameterMask is zero or not present, " +
-                    "changing to: " + ap.getParameterMask());
+                    "changing to default: " + ap.getParameterMask());
         }
+        if (V) Log.v(TAG, "folderElement hasSmsMmsContent = " + folderElement.hasSmsMmsContent() +
+                " folderElement.hasEmailContent = " + folderElement.hasEmailContent() +
+                " folderElement.hasImContent = " + folderElement.hasImContent());
 
         /* Cache some info used throughout filtering */
         FilterInfo fi = new FilterInfo();
@@ -1248,23 +2021,25 @@
         Cursor smsCursor = null;
         Cursor mmsCursor = null;
         Cursor emailCursor = null;
-
-        try {
-            String limit = "";
-            int countNum = ap.getMaxListCount();
-            int offsetNum = ap.getStartOffset();
-            if(ap.getMaxListCount()>0){
-                limit=" LIMIT "+ (ap.getMaxListCount()+ap.getStartOffset());
-            }
-
+        Cursor imCursor = null;
+        String limit = "";
+        int countNum = ap.getMaxListCount();
+        int offsetNum = ap.getStartOffset();
+        if(ap.getMaxListCount()>0){
+            limit=" LIMIT "+ (ap.getMaxListCount()+ap.getStartOffset());
+        }
+        try{
             if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
                 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
                                                  BluetoothMapAppParams.FILTER_NO_MMS|
-                                                 BluetoothMapAppParams.FILTER_NO_SMS_GSM)||
+                                                 BluetoothMapAppParams.FILTER_NO_SMS_GSM|
+                                                 BluetoothMapAppParams.FILTER_NO_IM)||
                    ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
                                                  BluetoothMapAppParams.FILTER_NO_MMS|
-                                                 BluetoothMapAppParams.FILTER_NO_SMS_CDMA)){
-                    //set real limit and offset if only this type is used (only if offset/limit is used
+                                                 BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
+                                                 BluetoothMapAppParams.FILTER_NO_IM)){
+                    //set real limit and offset if only this type is used
+                    // (only if offset/limit is used)
                     limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
                     if(D) Log.d(TAG, "SMS Limit => "+limit);
                     offsetNum = 0;
@@ -1272,31 +2047,32 @@
                 fi.mMsgType = FilterInfo.TYPE_SMS;
                 if(ap.getFilterPriority() != 1){ /*SMS cannot have high priority*/
                     String where = setWhereFilter(folderElement, fi, ap);
-                    if(!where.isEmpty()) {
-                        if (D) Log.d(TAG, "msgType: " + fi.mMsgType);
-                        smsCursor = mResolver.query(Sms.CONTENT_URI,
-                                SMS_PROJECTION, where, null, Sms.DATE + " DESC" + limit);
-                        if (smsCursor != null) {
-                            BluetoothMapMessageListingElement e = null;
-                            // store column index so we dont have to look them up anymore (optimization)
-                            if(D) Log.d(TAG, "Found " + smsCursor.getCount() + " sms messages.");
-                            fi.setSmsColumns(smsCursor);
-                            while (smsCursor.moveToNext()) {
-                                if (matchAddresses(smsCursor, fi, ap)) {
-                                    e = element(smsCursor, fi, ap);
-                                    bmList.add(e);
-                                }
+                    if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
+                    smsCursor = mResolver.query(Sms.CONTENT_URI,
+                            SMS_PROJECTION, where, null, Sms.DATE + " DESC" + limit);
+                    if (smsCursor != null) {
+                        BluetoothMapMessageListingElement e = null;
+                        // store column index so we dont have to look them up anymore (optimization)
+                        if(D) Log.d(TAG, "Found " + smsCursor.getCount() + " sms messages.");
+                        fi.setSmsColumns(smsCursor);
+                        while (smsCursor.moveToNext()) {
+                            if (matchAddresses(smsCursor, fi, ap)) {
+                                if(V) BluetoothMapUtils.printCursor(smsCursor);
+                                e = element(smsCursor, fi, ap);
+                                bmList.add(e);
                             }
                         }
                     }
                 }
             }
 
-            if (mmsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
+            if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) {
                 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
                                                  BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
-                                                 BluetoothMapAppParams.FILTER_NO_SMS_GSM)){
-                    //set real limit and offset if only this type is used (only if offset/limit is used
+                                                 BluetoothMapAppParams.FILTER_NO_SMS_GSM|
+                                                 BluetoothMapAppParams.FILTER_NO_IM)){
+                    //set real limit and offset if only this type is used
+                    //(only if offset/limit is used)
                     limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
                     if(D) Log.d(TAG, "MMS Limit => "+limit);
                     offsetNum = 0;
@@ -1304,17 +2080,17 @@
                 fi.mMsgType = FilterInfo.TYPE_MMS;
                 String where = setWhereFilter(folderElement, fi, ap);
                 if(!where.isEmpty()) {
-                    if (D) Log.d(TAG, "msgType: " + fi.mMsgType);
+                    if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
                     mmsCursor = mResolver.query(Mms.CONTENT_URI,
                             MMS_PROJECTION, where, null, Mms.DATE + " DESC" + limit);
                     if (mmsCursor != null) {
                         BluetoothMapMessageListingElement e = null;
                         // store column index so we dont have to look them up anymore (optimization)
                         fi.setMmsColumns(mmsCursor);
-                        int cnt = 0;
                         if(D) Log.d(TAG, "Found " + mmsCursor.getCount() + " mms messages.");
                         while (mmsCursor.moveToNext()) {
                             if (matchAddresses(mmsCursor, fi, ap)) {
+                                if(V) BluetoothMapUtils.printCursor(mmsCursor);
                                 e = element(mmsCursor, fi, ap);
                                 bmList.add(e);
                             }
@@ -1323,11 +2099,13 @@
                 }
             }
 
-            if (emailSelected(fi, ap) && folderElement.getEmailFolderId() != -1) {
+            if (emailSelected(ap) && folderElement.hasEmailContent()) {
                 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS|
                                                  BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
-                                                 BluetoothMapAppParams.FILTER_NO_SMS_GSM)){
-                    //set real limit and offset if only this type is used (only if offset/limit is used
+                                                 BluetoothMapAppParams.FILTER_NO_SMS_GSM|
+                                                 BluetoothMapAppParams.FILTER_NO_IM)){
+                    //set real limit and offset if only this type is used
+                    //(only if offset/limit is used)
                     limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
                     if(D) Log.d(TAG, "Email Limit => "+limit);
                     offsetNum = 0;
@@ -1336,17 +2114,19 @@
                 String where = setWhereFilter(folderElement, fi, ap);
 
                 if(!where.isEmpty()) {
-                    if (D) Log.d(TAG, "msgType: " + fi.mMsgType);
-                    Uri contentUri = Uri.parse(mBaseEmailUri + BluetoothMapContract.TABLE_MESSAGE);
-                    emailCursor = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
-                            where, null, BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
+                    if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
+                    Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
+                    emailCursor = mResolver.query(contentUri,
+                            BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null,
+                            BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
                     if (emailCursor != null) {
                         BluetoothMapMessageListingElement e = null;
                         // store column index so we dont have to look them up anymore (optimization)
-                        fi.setEmailColumns(emailCursor);
+                        fi.setEmailMessageColumns(emailCursor);
                         int cnt = 0;
+                        if(D) Log.d(TAG, "Found " + emailCursor.getCount() + " email messages.");
                         while (emailCursor.moveToNext()) {
-                            if(D) Log.d(TAG, "Found " + emailCursor.getCount() + " email messages.");
+                            if(V) BluetoothMapUtils.printCursor(emailCursor);
                             e = element(emailCursor, fi, ap);
                             bmList.add(e);
                         }
@@ -1355,48 +2135,92 @@
                 }
             }
 
+            if (imSelected(ap) && folderElement.hasImContent()) {
+                if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS|
+                                                 BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
+                                                 BluetoothMapAppParams.FILTER_NO_SMS_GSM|
+                                                 BluetoothMapAppParams.FILTER_NO_EMAIL)){
+                    //set real limit and offset if only this type is used
+                    //(only if offset/limit is used)
+                    limit = " LIMIT " + ap.getMaxListCount() + " OFFSET "+ ap.getStartOffset();
+                    if(D) Log.d(TAG, "IM Limit => "+limit);
+                    offsetNum = 0;
+                }
+                fi.mMsgType = FilterInfo.TYPE_IM;
+                String where = setWhereFilter(folderElement, fi, ap);
+                if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
+
+                Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
+                imCursor = mResolver.query(contentUri,
+                        BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION,
+                        where, null, BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
+                if (imCursor != null) {
+                    BluetoothMapMessageListingElement e = null;
+                    // store column index so we dont have to look them up anymore (optimization)
+                    fi.setImMessageColumns(imCursor);
+                    if (D) Log.d(TAG, "Found " + imCursor.getCount() + " im messages.");
+                    while (imCursor.moveToNext()) {
+                        if (V) BluetoothMapUtils.printCursor(imCursor);
+                        e = element(imCursor, fi, ap);
+                        bmList.add(e);
+                    }
+                }
+            }
+
             /* Enable this if post sorting and segmenting needed */
             bmList.sort();
             bmList.segment(ap.getMaxListCount(), offsetNum);
             List<BluetoothMapMessageListingElement> list = bmList.getList();
             int listSize = list.size();
             Cursor tmpCursor = null;
-            for (int x=0; x<listSize; x++){
+            for(int x=0;x<listSize;x++){
                 BluetoothMapMessageListingElement ele = list.get(x);
-                if ((ele.getType().equals(TYPE.SMS_GSM)||ele.getType().equals(TYPE.SMS_CDMA)) && smsCursor != null){
+                if((ele.getType().equals(TYPE.SMS_GSM)||ele.getType().equals(TYPE.SMS_CDMA))
+                        && smsCursor != null){
                     tmpCursor = smsCursor;
                     fi.mMsgType = FilterInfo.TYPE_SMS;
-                } else if (ele.getType().equals(TYPE.MMS) && mmsCursor != null){
+                }else if(ele.getType().equals(TYPE.MMS) && mmsCursor != null){
                     tmpCursor = mmsCursor;
                     fi.mMsgType = FilterInfo.TYPE_MMS;
-                } else if (ele.getType().equals(TYPE.EMAIL) && emailCursor != null){
+                }else if(ele.getType().equals(TYPE.EMAIL) && emailCursor != null){
                     tmpCursor = emailCursor;
                     fi.mMsgType = FilterInfo.TYPE_EMAIL;
+                }else if(ele.getType().equals(TYPE.IM) && imCursor != null){
+                    tmpCursor = imCursor;
+                    fi.mMsgType = FilterInfo.TYPE_IM;
                 }
-
-                if (tmpCursor != null && tmpCursor.moveToPosition(ele.getCursorIndex())) {
+                if(tmpCursor != null){
+                    tmpCursor.moveToPosition(ele.getCursorIndex());
                     setSenderAddressing(ele, tmpCursor, fi, ap);
                     setSenderName(ele, tmpCursor, fi, ap);
                     setRecipientAddressing(ele, tmpCursor, fi, ap);
                     setRecipientName(ele, tmpCursor, fi, ap);
                     setSubject(ele, tmpCursor, fi, ap);
                     setSize(ele, tmpCursor, fi, ap);
-                    setReceptionStatus(ele, tmpCursor, fi, ap);
                     setText(ele, tmpCursor, fi, ap);
-                    setAttachmentSize(ele, tmpCursor, fi, ap);
                     setPriority(ele, tmpCursor, fi, ap);
                     setSent(ele, tmpCursor, fi, ap);
                     setProtected(ele, tmpCursor, fi, ap);
-                    setThreadId(ele, tmpCursor, fi, ap);
+                    setReceptionStatus(ele, tmpCursor, fi, ap);
+                    setAttachment(ele, tmpCursor, fi, ap);
+
+                    if(mMsgListingVersion > BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10 ){
+                        setDeliveryStatus(ele, tmpCursor, fi, ap);
+                        setThreadId(ele, tmpCursor, fi, ap);
+                        setThreadName(ele, tmpCursor, fi, ap);
+                        setFolderType(ele, tmpCursor, fi, ap);
+                    }
                 }
             }
         } finally {
-            close(emailCursor);
-            close(smsCursor);
-            close(mmsCursor);
+            if(emailCursor != null)emailCursor.close();
+            if(smsCursor != null)smsCursor.close();
+            if(mmsCursor != null)mmsCursor.close();
+            if(imCursor != null)imCursor.close();
         }
 
-        if (D) Log.d(TAG, "messagelisting end");
+
+        if(D)Log.d(TAG, "messagelisting end");
         return bmList;
     }
 
@@ -1420,29 +2244,61 @@
             String where = setWhereFilter(folderElement, fi, ap);
             Cursor c = mResolver.query(Sms.CONTENT_URI,
                     SMS_PROJECTION, where, null, Sms.DATE + " DESC");
-
-            if (c != null) cnt = c.getCount();
-            close(c);
+            try {
+                if (c != null) {
+                    cnt = c.getCount();
+                }
+            } finally {
+                if (c != null) c.close();
+            }
         }
 
-        if (mmsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
+        if (mmsSelected(ap)  && folderElement.hasSmsMmsContent()) {
             fi.mMsgType = FilterInfo.TYPE_MMS;
             String where = setWhereFilter(folderElement, fi, ap);
             Cursor c = mResolver.query(Mms.CONTENT_URI,
                     MMS_PROJECTION, where, null, Mms.DATE + " DESC");
-            if (c != null) cnt += c.getCount();
-            close(c);
+            try {
+                if (c != null) {
+                    cnt += c.getCount();
+                }
+            } finally {
+                if (c != null) c.close();
+            }
         }
 
-        if (emailSelected(fi, ap) && folderElement.getEmailFolderId() != -1) {
+        if (emailSelected(ap) && folderElement.hasEmailContent()) {
             fi.mMsgType = FilterInfo.TYPE_EMAIL;
             String where = setWhereFilter(folderElement, fi, ap);
-            if (!where.isEmpty()) {
-                Uri contentUri = Uri.parse(mBaseEmailUri + BluetoothMapContract.TABLE_MESSAGE);
+            if(!where.isEmpty()) {
+                Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
                 Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
                         where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
-                if (c != null) cnt += c.getCount();
-                close(c);
+                try {
+                    if (c != null) {
+                        cnt += c.getCount();
+                    }
+                } finally {
+                    if (c != null) c.close();
+                }
+            }
+        }
+
+        if (imSelected(ap) && folderElement.hasImContent()) {
+            fi.mMsgType = FilterInfo.TYPE_IM;
+            String where = setWhereFilter(folderElement, fi, ap);
+            if(!where.isEmpty()) {
+                Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
+                Cursor c = mResolver.query(contentUri,
+                        BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION,
+                        where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
+                try {
+                    if (c != null) {
+                        cnt += c.getCount();
+                    }
+                } finally {
+                    if (c != null) c.close();
+                }
             }
         }
 
@@ -1472,35 +2328,68 @@
             where += setWhereFilterPeriod(ap, fi);
             Cursor c = mResolver.query(Sms.CONTENT_URI,
                 SMS_PROJECTION, where, null, Sms.DATE + " DESC");
-
-            if (c != null) cnt += c.getCount();
-            close(c);
+            try {
+                if (c != null) {
+                    cnt = c.getCount();
+                }
+            } finally {
+                if (c != null) c.close();
+            }
         }
 
-        if (mmsSelected(fi, ap)  && folderElement.hasSmsMmsContent()) {
+        if (mmsSelected(ap)  && folderElement.hasSmsMmsContent()) {
             fi.mMsgType = FilterInfo.TYPE_MMS;
             String where = setWhereFilterFolderType(folderElement, fi);
             where += " AND " + Mms.READ + "=0 ";
             where += setWhereFilterPeriod(ap, fi);
             Cursor c = mResolver.query(Mms.CONTENT_URI,
                 MMS_PROJECTION, where, null, Sms.DATE + " DESC");
-
-            if (c != null) cnt += c.getCount();
-            close(c);
+            try {
+                if (c != null) {
+                    cnt += c.getCount();
+                }
+            } finally {
+                if (c != null) c.close();
+            }
         }
 
 
-        if (emailSelected(fi, ap) && folderElement.getEmailFolderId() != -1) {
+        if (emailSelected(ap) && folderElement.getFolderId() != -1) {
             fi.mMsgType = FilterInfo.TYPE_EMAIL;
             String where = setWhereFilterFolderType(folderElement, fi);
             if(!where.isEmpty()) {
                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
                 where += setWhereFilterPeriod(ap, fi);
-                Uri contentUri = Uri.parse(mBaseEmailUri + BluetoothMapContract.TABLE_MESSAGE);
+                Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
                 Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
                         where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
-                if (c != null) cnt += c.getCount();
-                close(c);
+                try {
+                    if (c != null) {
+                        cnt += c.getCount();
+                    }
+                } finally {
+                    if (c != null) c.close();
+                }
+            }
+        }
+
+        if (imSelected(ap) && folderElement.hasImContent()) {
+            fi.mMsgType = FilterInfo.TYPE_IM;
+            String where = setWhereFilter(folderElement, fi, ap);
+            if(!where.isEmpty()) {
+                where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
+                where += setWhereFilterPeriod(ap, fi);
+                Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
+                Cursor c = mResolver.query(contentUri,
+                        BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION,
+                        where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
+                try {
+                    if (c != null) {
+                        cnt += c.getCount();
+                    }
+                } finally {
+                    if (c != null) c.close();
+                }
             }
         }
 
@@ -1509,6 +2398,826 @@
     }
 
     /**
+     * Build the conversation listing.
+     * @param ap The Application Parameters
+     * @param sizeOnly TRUE: don't populate the list members, only build the list to get the size.
+     * @return
+     */
+    public BluetoothMapConvoListing convoListing(BluetoothMapAppParams ap, boolean sizeOnly) {
+
+        if (D) Log.d(TAG, "convoListing: " + " messageType = " + ap.getFilterMessageType() );
+        BluetoothMapConvoListing convoList = new BluetoothMapConvoListing();
+
+        /* 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.getConvoParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
+                ap.getConvoParameterMask() == 0) {
+            ap.setConvoParameterMask(CONVO_PARAMETER_MASK_DEFAULT);
+            if (D) Log.v(TAG, "convoListing(): appParameterMask is zero or not present, " +
+                    "changing to default: " + ap.getConvoParameterMask());
+        }
+
+        /* Possible filters:
+         *  - Recipient name (contacts DB) or id (for SMS/MMS this is the thread-id contact-id)
+         *  - Activity start/begin
+         *  - Read status
+         *  - Thread_id
+         * The strategy for SMS/MMS
+         *   With no filter on name - use limit and offset.
+         *   With a filter on name - build the complete list of conversations and create a filter
+         *                           mechanism
+         *
+         * The strategy for IM:
+         *   Join the conversation table with the contacts table in a way that makes it possible to
+         *   get the data needed in a single query.
+         *   Manually handle limit/offset
+         * */
+
+        /* Cache some info used throughout filtering */
+        FilterInfo fi = new FilterInfo();
+        setFilterInfo(fi);
+        Cursor smsMmsCursor = null;
+        Cursor imEmailCursor = null;
+        int offsetNum;
+        if(sizeOnly) {
+            offsetNum = 0;
+        } else {
+            offsetNum = ap.getStartOffset();
+        }
+        // Inverse meaning - hence a 1 is include.
+        int msgTypesInclude = ((~ap.getFilterMessageType())
+                & BluetoothMapAppParams.FILTER_MSG_TYPE_MASK);
+        int maxThreads = ap.getMaxListCount()+ap.getStartOffset();
+
+
+        try {
+            if (smsSelected(fi, ap) || mmsSelected(ap)) {
+                String limit = "";
+                if((sizeOnly == false) && (ap.getMaxListCount()>0) &&
+                        (ap.getFilterRecipient()==null)){
+                    /* We can only use limit if we do not have a contacts filter */
+                    limit=" LIMIT " + maxThreads;
+                }
+                StringBuilder sortOrder = new StringBuilder(Threads.DATE + " DESC");
+                if((sizeOnly == false) &&
+                        ((msgTypesInclude & ~(BluetoothMapAppParams.FILTER_NO_SMS_GSM |
+                        BluetoothMapAppParams.FILTER_NO_SMS_CDMA) |
+                        BluetoothMapAppParams.FILTER_NO_MMS) == 0)
+                        && ap.getFilterRecipient() == null){
+                    // SMS/MMS messages only and no recipient filter - use optimization.
+                    limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
+                    if(D) Log.d(TAG, "SMS Limit => "+limit);
+                    offsetNum = 0;
+                }
+                StringBuilder selection = new StringBuilder(120); // This covers most cases
+                ArrayList<String> selectionArgs = new ArrayList<String>(12); // Covers all cases
+                selection.append("1=1 "); // just to simplify building the where-clause
+                setConvoWhereFilterSmsMms(selection, selectionArgs, fi, ap);
+                String[] args = null;
+                if(selectionArgs.size() > 0) {
+                    args = new String[selectionArgs.size()];
+                    selectionArgs.toArray(args);
+                }
+                Uri uri = Threads.CONTENT_URI.buildUpon()
+                        .appendQueryParameter("simple", "true").build();
+                sortOrder.append(limit);
+                if(D) Log.d(TAG, "Query using selection: " + selection.toString() +
+                        " - sortOrder: " + sortOrder.toString());
+                // TODO: Optimize: Reduce projection based on convo parameter mask
+                smsMmsCursor = mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, selection.toString(),
+                        args, sortOrder.toString());
+                if (smsMmsCursor != null) {
+                    // store column index so we don't have to look them up anymore (optimization)
+                    if(D) Log.d(TAG, "Found " + smsMmsCursor.getCount()
+                            + " sms/mms conversations.");
+                    BluetoothMapConvoListingElement convoElement = null;
+                    smsMmsCursor.moveToPosition(-1);
+                    if(ap.getFilterRecipient() == null) {
+                        int count = 0;
+                        // We have no Recipient filter, add contacts after the list is reduced
+                        while (smsMmsCursor.moveToNext()) {
+                            convoElement = createConvoElement(smsMmsCursor, fi, ap);
+                            convoList.add(convoElement);
+                            count++;
+                            if(sizeOnly == false && count >= maxThreads) {
+                                break;
+                            }
+                        }
+                    } else {
+                        // We must be able to filter on recipient, add contacts now
+                        SmsMmsContacts contacts = new SmsMmsContacts();
+                        while (smsMmsCursor.moveToNext()) {
+                            int count = 0;
+                            convoElement = createConvoElement(smsMmsCursor, fi, ap);
+                            String idsStr =
+                                    smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS);
+                            // Add elements only if we do find a contact - if not we cannot apply
+                            // the filter, hence the item is irrelevant
+                            // TODO: Perhaps the spec. should be changes to be able to search on
+                            //       phone number as well?
+                            if(addSmsMmsContacts(convoElement, contacts, idsStr,
+                                    ap.getFilterRecipient(), ap)) {
+                                convoList.add(convoElement);
+                                if(sizeOnly == false && count >= maxThreads) {
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (emailSelected(ap) || imSelected(ap)) {
+                int count = 0;
+                if(emailSelected(ap)) {
+                    fi.mMsgType = FilterInfo.TYPE_EMAIL;
+                } else if(imSelected(ap)) {
+                    fi.mMsgType = FilterInfo.TYPE_IM;
+                }
+                if (D) Log.d(TAG, "msgType: " + fi.mMsgType);
+                Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION);
+
+                contentUri = appendConvoListQueryParameters(ap, contentUri);
+                if(V) Log.v(TAG, "URI with parameters: " + contentUri.toString());
+                // TODO: Optimize: Reduce projection based on convo parameter mask
+                imEmailCursor = mResolver.query(contentUri,
+                        BluetoothMapContract.BT_CONVERSATION_PROJECTION,
+                        null, null, BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
+                        + " DESC, " + BluetoothMapContract.ConversationColumns.THREAD_ID
+                        + " ASC");
+                if (imEmailCursor != null) {
+                    BluetoothMapConvoListingElement e = null;
+                    // store column index so we don't have to look them up anymore (optimization)
+                    // Here we rely on only a single account-based message type for each MAS.
+                    fi.setEmailImConvoColumns(imEmailCursor);
+                    boolean isValid = imEmailCursor.moveToNext();
+                    if(D) Log.d(TAG, "Found " + imEmailCursor.getCount()
+                            + " EMAIL/IM conversations. isValid = " + isValid);
+                    while (isValid && ((sizeOnly == true) || (count < maxThreads))) {
+                        long threadId = imEmailCursor.getLong(fi.mConvoColConvoId);
+                        long nextThreadId;
+                        count ++;
+                        e = createConvoElement(imEmailCursor, fi, ap);
+                        convoList.add(e);
+
+                        do {
+                            nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId);
+                            if(V) Log.i(TAG, "  threadId = " + threadId + " newThreadId = " +
+                                    nextThreadId);
+                            // TODO: This seems rather inefficient in the case where we do not need
+                            //       to reduce the list.
+                        } while ((nextThreadId == threadId) &&
+                                (isValid = imEmailCursor.moveToNext() == true));
+                    }
+                }
+            }
+
+            if(D) Log.d(TAG, "Done adding conversations - list size:" +
+                    convoList.getCount());
+
+            // If sizeOnly - we are all done here - return the list as is - no need to populate the
+            // list.
+            if(sizeOnly) {
+                return convoList;
+            }
+
+            /* Enable this if post sorting and segmenting needed */
+            /* This is too early */
+            convoList.sort();
+            convoList.segment(ap.getMaxListCount(), offsetNum);
+            List<BluetoothMapConvoListingElement> list = convoList.getList();
+            int listSize = list.size();
+            if(V) Log.i(TAG, "List Size:" + listSize);
+            Cursor tmpCursor = null;
+            SmsMmsContacts contacts = new SmsMmsContacts();
+            for(int x=0;x<listSize;x++){
+                BluetoothMapConvoListingElement ele = list.get(x);
+                TYPE type = ele.getType();
+                switch(type) {
+                case SMS_CDMA:
+                case SMS_GSM:
+                case MMS: {
+                    tmpCursor = null; // SMS/MMS needs special treatment
+                    if(smsMmsCursor != null) {
+                        populateSmsMmsConvoElement(ele, smsMmsCursor, ap, contacts);
+                    }
+                    if(D) fi.mMsgType = FilterInfo.TYPE_IM;
+                    break;
+                }
+                case EMAIL:
+                    tmpCursor = imEmailCursor;
+                    fi.mMsgType = FilterInfo.TYPE_EMAIL;
+                    break;
+                case IM:
+                    tmpCursor = imEmailCursor;
+                    fi.mMsgType = FilterInfo.TYPE_IM;
+                    break;
+                default:
+                    tmpCursor = null;
+                    break;
+                }
+
+                if(D) Log.d(TAG, "Working on cursor of type " + fi.mMsgType);
+
+                if(tmpCursor != null){
+                    populateImEmailConvoElement(ele, tmpCursor, ap, fi);
+                }else {
+                    // No, it will be for SMS/MMS at the moment
+                    if(D) Log.d(TAG, "tmpCursor is Null - something is wrong - or the message is" +
+                            " of type SMS/MMS");
+                }
+            }
+        } finally {
+            if(imEmailCursor != null)imEmailCursor.close();
+            if(smsMmsCursor != null)smsMmsCursor.close();
+            if(D)Log.d(TAG, "conversation end");
+        }
+        return convoList;
+    }
+
+
+    /**
+     * Refreshes the entire list of SMS/MMS conversation version counters. Use it to generate a
+     * new ConvoListVersinoCounter in mSmsMmsConvoListVersion
+     * @return
+     */
+    /* package */
+    boolean refreshSmsMmsConvoVersions() {
+        boolean listChangeDetected = false;
+        Cursor cursor = null;
+        Uri uri = Threads.CONTENT_URI.buildUpon()
+                .appendQueryParameter("simple", "true").build();
+        cursor = mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, null,
+                null, Threads.DATE + " DESC");
+        try {
+            if (cursor != null) {
+                // store column index so we don't have to look them up anymore (optimization)
+                if(D) Log.d(TAG, "Found " + cursor.getCount()
+                        + " sms/mms conversations.");
+                BluetoothMapConvoListingElement convoElement = null;
+                cursor.moveToPosition(-1);
+                synchronized (getSmsMmsConvoList()) {
+                    int size = Math.max(getSmsMmsConvoList().size(), cursor.getCount());
+                    HashMap<Long,BluetoothMapConvoListingElement> newList =
+                            new HashMap<Long,BluetoothMapConvoListingElement>(size);
+                    while (cursor.moveToNext()) {
+                        // TODO: Extract to function, that can be called at listing, which returns
+                        //       the versionCounter(existing or new).
+                        boolean convoChanged = false;
+                        Long id = cursor.getLong(MMS_SMS_THREAD_COL_ID);
+                        convoElement = getSmsMmsConvoList().remove(id);
+                        if(convoElement == null) {
+                            // New conversation added
+                            convoElement = new BluetoothMapConvoListingElement();
+                            convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, id);
+                            listChangeDetected = true;
+                            convoElement.setVersionCounter(0);
+                        }
+                        // Currently we only need to compare name, last_activity and read_status, and
+                        // name is not used for SMS/MMS.
+                        // msg delete will be handled by update folderVersionCounter().
+                        long last_activity = cursor.getLong(MMS_SMS_THREAD_COL_DATE);
+                        boolean read = (cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ?
+                                true : false;
+
+                        if(last_activity != convoElement.getLastActivity()) {
+                            convoChanged = true;
+                            convoElement.setLastActivity(last_activity);
+                        }
+
+                        if(read != convoElement.getReadBool()) {
+                            convoChanged = true;
+                            convoElement.setRead(read, false);
+                        }
+
+                        String idsStr = cursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS);
+                        if(!idsStr.equals(convoElement.getSmsMmsContacts())) {
+                            // This should not trigger a change in conversationVersionCounter only the
+                            // ConvoListVersionCounter.
+                            listChangeDetected = true;
+                            convoElement.setSmsMmsContacts(idsStr);
+                        }
+
+                        if(convoChanged) {
+                            listChangeDetected = true;
+                            convoElement.incrementVersionCounter();
+                        }
+                        newList.put(id, convoElement);
+                    }
+                    // If we still have items on the old list, something was deleted
+                    if(getSmsMmsConvoList().size() != 0) {
+                        listChangeDetected = true;
+                    }
+                    setSmsMmsConvoList(newList);
+                }
+
+                if(listChangeDetected) {
+                    mMasInstance.updateSmsMmsConvoListVersionCounter();
+                }
+            }
+        } finally {
+            if(cursor != null) {
+                cursor.close();
+            }
+        }
+        return listChangeDetected;
+    }
+
+    /**
+     * Refreshes the entire list of SMS/MMS conversation version counters. Use it to generate a
+     * new ConvoListVersinoCounter in mSmsMmsConvoListVersion
+     * @return
+     */
+    /* package */
+    boolean refreshImEmailConvoVersions() {
+        boolean listChangeDetected = false;
+        FilterInfo fi = new FilterInfo();
+
+        Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION);
+
+        if(V) Log.v(TAG, "URI with parameters: " + contentUri.toString());
+        Cursor imEmailCursor = mResolver.query(contentUri,
+                CONVO_VERSION_PROJECTION,
+                null, null, BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
+                + " DESC, " + BluetoothMapContract.ConversationColumns.THREAD_ID
+                + " ASC");
+        try {
+            if (imEmailCursor != null) {
+                BluetoothMapConvoListingElement convoElement = null;
+                // store column index so we don't have to look them up anymore (optimization)
+                // Here we rely on only a single account-based message type for each MAS.
+                fi.setEmailImConvoColumns(imEmailCursor);
+                boolean isValid = imEmailCursor.moveToNext();
+                if(V) Log.d(TAG, "Found " + imEmailCursor.getCount()
+                        + " EMAIL/IM conversations. isValid = " + isValid);
+                synchronized (getImEmailConvoList()) {
+                    int size = Math.max(getImEmailConvoList().size(), imEmailCursor.getCount());
+                    boolean convoChanged = false;
+                    HashMap<Long,BluetoothMapConvoListingElement> newList =
+                            new HashMap<Long,BluetoothMapConvoListingElement>(size);
+                    while (isValid) {
+                        long id = imEmailCursor.getLong(fi.mConvoColConvoId);
+                        long nextThreadId;
+                        convoElement = getImEmailConvoList().remove(id);
+                        if(convoElement == null) {
+                            // New conversation added
+                            convoElement = new BluetoothMapConvoListingElement();
+                            convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id);
+                            listChangeDetected = true;
+                            convoElement.setVersionCounter(0);
+                        }
+                        String name = imEmailCursor.getString(fi.mConvoColName);
+                        String summary = imEmailCursor.getString(fi.mConvoColSummary);
+                        long last_activity = imEmailCursor.getLong(fi.mConvoColLastActivity);
+                        boolean read = (imEmailCursor.getInt(fi.mConvoColRead) == 1) ?
+                                true : false;
+
+                        if(last_activity != convoElement.getLastActivity()) {
+                            convoChanged = true;
+                            convoElement.setLastActivity(last_activity);
+                        }
+
+                        if(read != convoElement.getReadBool()) {
+                            convoChanged = true;
+                            convoElement.setRead(read, false);
+                        }
+
+                        if(name != null && !name.equals(convoElement.getName())) {
+                            convoChanged = true;
+                            convoElement.setName(name);
+                        }
+
+                        if(summary != null && !summary.equals(convoElement.getFullSummary())) {
+                            convoChanged = true;
+                            convoElement.setSummary(summary);
+                        }
+                        /* If the query returned one row for each contact, skip all the dublicates */
+                        do {
+                            nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId);
+                            if(V) Log.i(TAG, "  threadId = " + id + " newThreadId = " +
+                                    nextThreadId);
+                        } while ((nextThreadId == id) &&
+                                (isValid = imEmailCursor.moveToNext() == true));
+
+                        if(convoChanged) {
+                            listChangeDetected = true;
+                            convoElement.incrementVersionCounter();
+                        }
+                        newList.put(id, convoElement);
+                    }
+                    // If we still have items on the old list, something was deleted
+                    if(getImEmailConvoList().size() != 0) {
+                        listChangeDetected = true;
+                    }
+                    setImEmailConvoList(newList);
+                }
+            }
+        } finally {
+            if(imEmailCursor != null) {
+                imEmailCursor.close();
+            }
+        }
+
+        if(listChangeDetected) {
+            mMasInstance.updateImEmailConvoListVersionCounter();
+        }
+        return listChangeDetected;
+    }
+
+    /**
+     * Update the convoVersionCounter within the element passed as parameter.
+     * This function has the side effect to update the ConvoListVersionCounter if needed.
+     * This function ignores changes to contacts as this shall not change the convoVersionCounter,
+     * only the convoListVersion counter, which will be updated upon request.
+     * @param ele Element to update shall not be null.
+     */
+    private void updateSmsMmsConvoVersion(Cursor cursor, BluetoothMapConvoListingElement ele) {
+        long id = ele.getCpConvoId();
+        BluetoothMapConvoListingElement convoElement = getSmsMmsConvoList().get(id);
+        boolean listChangeDetected = false;
+        boolean convoChanged = false;
+        if(convoElement == null) {
+            // New conversation added
+            convoElement = new BluetoothMapConvoListingElement();
+            getSmsMmsConvoList().put(id, convoElement);
+            convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, id);
+            listChangeDetected = true;
+            convoElement.setVersionCounter(0);
+        }
+        long last_activity = cursor.getLong(MMS_SMS_THREAD_COL_DATE);
+        boolean read = (cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ?
+                true : false;
+
+        if(last_activity != convoElement.getLastActivity()) {
+            convoChanged = true;
+            convoElement.setLastActivity(last_activity);
+        }
+
+        if(read != convoElement.getReadBool()) {
+            convoChanged = true;
+            convoElement.setRead(read, false);
+        }
+
+        if(convoChanged) {
+            listChangeDetected = true;
+            convoElement.incrementVersionCounter();
+        }
+        if(listChangeDetected) {
+            mMasInstance.updateSmsMmsConvoListVersionCounter();
+        }
+        ele.setVersionCounter(convoElement.getVersionCounter());
+    }
+
+    /**
+     * Update the convoVersionCounter within the element passed as parameter.
+     * This function has the side effect to update the ConvoListVersionCounter if needed.
+     * This function ignores changes to contacts as this shall not change the convoVersionCounter,
+     * only the convoListVersion counter, which will be updated upon request.
+     * @param ele Element to update shall not be null.
+     */
+    private void updateImEmailConvoVersion(Cursor cursor, FilterInfo fi,
+            BluetoothMapConvoListingElement ele) {
+        long id = ele.getCpConvoId();
+        BluetoothMapConvoListingElement convoElement = getImEmailConvoList().get(id);
+        boolean listChangeDetected = false;
+        boolean convoChanged = false;
+        if(convoElement == null) {
+            // New conversation added
+            if(V) Log.d(TAG, "Added new conversation with ID = " + id);
+            convoElement = new BluetoothMapConvoListingElement();
+            convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id);
+            getImEmailConvoList().put(id, convoElement);
+            listChangeDetected = true;
+            convoElement.setVersionCounter(0);
+        }
+        String name = cursor.getString(fi.mConvoColName);
+        long last_activity = cursor.getLong(fi.mConvoColLastActivity);
+        boolean read = (cursor.getInt(fi.mConvoColRead) == 1) ?
+                true : false;
+
+        if(last_activity != convoElement.getLastActivity()) {
+            convoChanged = true;
+            convoElement.setLastActivity(last_activity);
+        }
+
+        if(read != convoElement.getReadBool()) {
+            convoChanged = true;
+            convoElement.setRead(read, false);
+        }
+
+        if(name != null && !name.equals(convoElement.getName())) {
+            convoChanged = true;
+            convoElement.setName(name);
+        }
+
+        if(convoChanged) {
+            listChangeDetected = true;
+            if(V) Log.d(TAG, "conversation with ID = " + id + " changed");
+            convoElement.incrementVersionCounter();
+        }
+        if(listChangeDetected) {
+            mMasInstance.updateImEmailConvoListVersionCounter();
+        }
+        ele.setVersionCounter(convoElement.getVersionCounter());
+    }
+
+    /**
+     * @param ele
+     * @param smsMmsCursor
+     * @param ap
+     * @param contacts
+     */
+    private void populateSmsMmsConvoElement(BluetoothMapConvoListingElement ele,
+            Cursor smsMmsCursor, BluetoothMapAppParams ap,
+            SmsMmsContacts contacts) {
+        smsMmsCursor.moveToPosition(ele.getCursorIndex());
+        // TODO: If we ever get beyond 31 bit, change to long
+        int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value
+
+        // TODO: How to determine whether the convo-IDs can be used across message
+        //       types?
+        ele.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS,
+                smsMmsCursor.getLong(MMS_SMS_THREAD_COL_ID));
+
+        boolean read = (smsMmsCursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ?
+                true : false;
+        if((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) {
+            ele.setRead(read, true);
+        } else {
+            ele.setRead(read, false);
+        }
+
+        if((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) {
+            long timeStamp = smsMmsCursor.getLong(MMS_SMS_THREAD_COL_DATE);
+            ele.setLastActivity(timeStamp);
+        } else {
+            // We need to delete the time stamp, if it was added for multi msg-type
+            ele.setLastActivity(-1);
+        }
+
+        if((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) {
+            updateSmsMmsConvoVersion(smsMmsCursor, ele);
+        }
+
+        if((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) {
+            ele.setName(""); // We never have a thread name for SMS/MMS
+        }
+
+        if((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) {
+            String summary = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET);
+            String cs = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET_CS);
+            if(summary != null && cs != null && !cs.equals("UTF-8")) {
+                try {
+                    // TODO: Not sure this is how to convert to UTF-8
+                    summary = new String(summary.getBytes(cs),"UTF-8");
+                } catch (UnsupportedEncodingException e){/*Cannot happen*/}
+            }
+            ele.setSummary(summary);
+        }
+
+        if((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) {
+            if(ap.getFilterRecipient() == null) {
+                // Add contacts only if not already added
+                String idsStr =
+                        smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS);
+                addSmsMmsContacts(ele, contacts, idsStr, null, ap);
+            }
+        }
+    }
+
+    /**
+     * @param ele
+     * @param tmpCursor
+     * @param fi
+     */
+    private void populateImEmailConvoElement( BluetoothMapConvoListingElement ele,
+            Cursor tmpCursor, BluetoothMapAppParams ap, FilterInfo fi) {
+        tmpCursor.moveToPosition(ele.getCursorIndex());
+        // TODO: If we ever get beyond 31 bit, change to long
+        int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value
+        long threadId = tmpCursor.getLong(fi.mConvoColConvoId);
+
+        // Mandatory field
+        ele.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, threadId);
+
+        if((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) {
+            ele.setName(tmpCursor.getString(fi.mConvoColName));
+        }
+
+        boolean reportRead = false;
+        if((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) {
+            reportRead = true;
+        }
+        ele.setRead(((1==tmpCursor.getInt(fi.mConvoColRead))?true:false), reportRead);
+
+        long timestamp = tmpCursor.getLong(fi.mConvoColLastActivity);
+        if((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) {
+            ele.setLastActivity(timestamp);
+        } else {
+            // We need to delete the time stamp, if it was added for multi msg-type
+            ele.setLastActivity(-1);
+        }
+
+
+        if((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) {
+            updateImEmailConvoVersion(tmpCursor, fi, ele);
+        }
+        if((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) {
+            ele.setSummary(tmpCursor.getString(fi.mConvoColSummary));
+        }
+        // TODO: For optimization, we could avoid joining the contact and convo tables
+        //       if we have no filter nor this bit is set.
+        if((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) {
+            do {
+                BluetoothMapConvoContactElement c = new BluetoothMapConvoContactElement();
+                if((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) != 0) {
+                    c.setBtUid(new SignedLongLong(tmpCursor.getLong(fi.mContactColBtUid),0));
+                }
+                if((parameterMask & CONVO_PARAM_MASK_PART_CHAT_STATE) != 0) {
+                    c.setChatState(tmpCursor.getInt(fi.mContactColChatState));
+                }
+                if((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE) != 0) {
+                    c.setPresenceAvailability(tmpCursor.getInt(fi.mContactColPresenceState));
+                }
+                if((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE_TEXT) != 0) {
+                    c.setPresenceStatus(tmpCursor.getString(fi.mContactColPresenceText));
+                }
+                if((parameterMask & CONVO_PARAM_MASK_PART_PRIORITY) != 0) {
+                    c.setPriority(tmpCursor.getInt(fi.mContactColPriority));
+                }
+                if((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) != 0) {
+                    c.setDisplayName(tmpCursor.getString(fi.mContactColNickname));
+                }
+                if((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) {
+                    c.setContactId(tmpCursor.getString(fi.mContactColContactUci));
+                }
+                if((parameterMask & CONVO_PARAM_MASK_PART_LAST_ACTIVITY) != 0) {
+                    c.setLastActivity(tmpCursor.getLong(fi.mContactColLastActive));
+                }
+                if((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) {
+                    c.setName(tmpCursor.getString(fi.mContactColName));
+                }
+                ele.addContact(c);
+            } while (tmpCursor.moveToNext() == true
+                    && tmpCursor.getLong(fi.mConvoColConvoId) == threadId);
+        }
+    }
+
+    /**
+     * Extract the ConvoList parameters from appParams and build the matching URI with
+     * query parameters.
+     * @param ap the appParams from the request
+     * @param contentUri the URI to append parameters to
+     * @return the new URI with the appended parameters (if any)
+     */
+    private Uri appendConvoListQueryParameters(BluetoothMapAppParams ap,
+            Uri contentUri) {
+        Builder newUri = contentUri.buildUpon();
+        String str = ap.getFilterRecipient();
+        if(str != null) {
+            str = str.trim();
+            str = str.replace("*", "%");
+            newUri.appendQueryParameter(BluetoothMapContract.FILTER_ORIGINATOR_SUBSTRING, str);
+        }
+        long time = ap.getFilterLastActivityBegin();
+        if(time > 0) {
+            newUri.appendQueryParameter(BluetoothMapContract.FILTER_PERIOD_BEGIN,
+                    Long.toString(time));
+        }
+        time = ap.getFilterLastActivityEnd();
+        if(time > 0) {
+            newUri.appendQueryParameter(BluetoothMapContract.FILTER_PERIOD_END,
+                    Long.toString(time));
+        }
+        int readStatus = ap.getFilterReadStatus();
+        if(readStatus > 0) {
+            if(readStatus == 1) {
+                // Conversations with Unread messages only
+                newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS,
+                        "false");
+            }else if(readStatus == 2) {
+                // Conversations with all read messages only
+                newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS,
+                        "true");
+            }
+            // if both are set it will be the same as requesting an empty list, but
+            // as it makes no sense with such a structure in a bit mask, we treat
+            // requesting both the same as no filtering.
+        }
+        long convoId = -1;
+        if(ap.getFilterConvoId() != null) {
+            convoId = ap.getFilterConvoId().getLeastSignificantBits();
+        }
+        if(convoId > 0) {
+            newUri.appendQueryParameter(BluetoothMapContract.FILTER_THREAD_ID,
+                    Long.toString(convoId));
+        }
+        return newUri.build();
+    }
+
+    /**
+     * Procedure if we have a filter:
+     *  - loop through all ids to examine if there is a match (this will build the cache)
+     *  - If there is a match loop again to add all contacts.
+     *
+     * Procedure if we don't have a filter
+     *  - Add all contacts
+     *
+     * @param convoElement
+     * @param contacts
+     * @param idsStr
+     * @param recipientFilter
+     * @return
+     */
+    private boolean addSmsMmsContacts( BluetoothMapConvoListingElement convoElement,
+            SmsMmsContacts contacts, String idsStr, String recipientFilter,
+            BluetoothMapAppParams ap) {
+        BluetoothMapConvoContactElement contactElement;
+        int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value
+        boolean foundContact = false;
+        String[] ids = idsStr.split(" ");
+        long[] longIds = new long[ids.length];
+        if(recipientFilter != null) {
+            recipientFilter = recipientFilter.trim();
+        }
+
+        for (int i = 0; i < ids.length; i++) {
+            long longId;
+            try {
+                longId = Long.parseLong(ids[i]);
+                longIds[i] = longId;
+                if(recipientFilter == null) {
+                    // If there is not filter, all we need to do is to parse the ids
+                    foundContact = true;
+                    continue;
+                }
+                String addr = contacts.getPhoneNumber(mResolver, longId);
+                if(addr == null) {
+                    // This can only happen if all messages from a contact is deleted while
+                    // performing the query.
+                    continue;
+                }
+                MapContact contact =
+                        contacts.getContactNameFromPhone(addr, mResolver, recipientFilter);
+                if(D) {
+                    Log.d(TAG, "  id " + longId + ": " + addr);
+                    if(contact != null) {
+                        Log.d(TAG,"  contact name: " + contact.getName() + "  X-BT-UID: "
+                                + contact.getXBtUid());
+                    }
+                }
+                if(contact == null) {
+                    continue;
+                }
+                foundContact = true;
+            } catch (NumberFormatException ex) {
+                // skip this id
+                continue;
+            }
+        }
+
+        if(foundContact == true) {
+            foundContact = false;
+            for (long id : longIds) {
+                String addr = contacts.getPhoneNumber(mResolver, id);
+                if(addr == null) {
+                    // This can only happen if all messages from a contact is deleted while
+                    // performing the query.
+                    continue;
+                }
+                foundContact = true;
+                MapContact contact = contacts.getContactNameFromPhone(addr, mResolver);
+
+                if(contact == null) {
+                    // We do not have a contact, we need to manually add one
+                    contactElement = new BluetoothMapConvoContactElement();
+                    if((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) {
+                        contactElement.setName(addr); // Use the phone number as name
+                    }
+                    if((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) {
+                        contactElement.setContactId(addr);
+                    }
+                } else {
+                    contactElement = BluetoothMapConvoContactElement
+                            .createFromMapContact(contact, addr);
+                    // Remove the parameters not to be reported
+                    if((parameterMask & CONVO_PARAM_MASK_PART_UCI) == 0) {
+                        contactElement.setContactId(null);
+                    }
+                    if((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) == 0) {
+                        contactElement.setBtUid(null);
+                    }
+                    if((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) == 0) {
+                        contactElement.setDisplayName(null);
+                    }
+                }
+                convoElement.addContact(contactElement);
+            }
+        }
+        return foundContact;
+    }
+
+    /**
      * Get the folder name of an SMS message or MMS message.
      * @param c the cursor pointing at the message
      * @return the folder name.
@@ -1534,8 +3243,10 @@
     }
 
     public byte[] getMessage(String handle, BluetoothMapAppParams appParams,
-            BluetoothMapFolderElement folderElement) throws UnsupportedEncodingException{
+            BluetoothMapFolderElement folderElement, String version)
+            throws UnsupportedEncodingException{
         TYPE type = BluetoothMapUtils.getMsgTypeFromHandle(handle);
+        mMessageVersion = version;
         long id = BluetoothMapUtils.getCpHandle(handle);
         if(appParams.getFractionRequest() == BluetoothMapAppParams.FRACTION_REQUEST_NEXT) {
             throw new IllegalArgumentException("FRACTION_REQUEST_NEXT does not make sence as" +
@@ -1549,14 +3260,19 @@
             return getMmsMessage(id, appParams);
         case EMAIL:
             return getEmailMessage(id, appParams, folderElement);
+        case IM:
+            return getIMMessage(id, appParams, folderElement);
         }
         throw new IllegalArgumentException("Invalid message handle.");
     }
 
-    private String setVCardFromPhoneNumber(BluetoothMapbMessage message, String phone, boolean incoming) {
+    private String setVCardFromPhoneNumber(BluetoothMapbMessage message,
+            String phone,
+            boolean incoming) {
         String contactId = null, contactName = null;
         String[] phoneNumbers = null;
         String[] emailAddresses = null;
+        Cursor p;
 
         Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
                 Uri.encode(phone));
@@ -1566,49 +3282,71 @@
         String orderBy = Contacts._ID + " ASC";
 
         // Get the contact _ID and name
-        Cursor p = mResolver.query(uri, projection, selection, null, orderBy);
-
+        p = mResolver.query(uri, projection, selection, null, orderBy);
         try {
-            if (p != null && p.moveToFirst()) {
+            if (p != null && p.getCount() >= 1) {
+                p.moveToFirst();
                 contactId = p.getString(p.getColumnIndex(Contacts._ID));
                 contactName = p.getString(p.getColumnIndex(Contacts.DISPLAY_NAME));
             }
 
-            // 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 {
-                // use only actual phone number
-                phoneNumbers = new String[1];
-                phoneNumbers[0] = phone;
-
-                // Fetch contact e-mail addresses
-                close (p);
-                p = mResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
-                        ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
-                        new String[]{contactId},
-                        null);
-                if (p != null) {
-                    int i = 0;
-                    emailAddresses = new String[p.getCount()];
-                    while (p != null && p.moveToNext()) {
-                        String emailAddress = p.getString(
-                            p.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS));
-                        emailAddresses[i++] = emailAddress;
-                    }
-                }
-            }
         } finally {
-            close(p);
+            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 {
+            // use only actual phone number
+            phoneNumbers = new String[1];
+            phoneNumbers[0] = phone;
+
+            try {
+                if (p != null && p.moveToFirst()) {
+                    contactId = p.getString(p.getColumnIndex(Contacts._ID));
+                    contactName = p.getString(p.getColumnIndex(Contacts.DISPLAY_NAME));
+                }
+
+                // 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 {
+                    // use only actual phone number
+                    phoneNumbers = new String[1];
+                    phoneNumbers[0] = phone;
+
+                    // Fetch contact e-mail addresses
+                    close (p);
+                    p = mResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
+                            ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
+                            new String[]{contactId},
+                            null);
+                    if (p != null) {
+                        int i = 0;
+                        emailAddresses = new String[p.getCount()];
+                        while (p != null && p.moveToNext()) {
+                            String emailAddress = p.getString(
+                                    p.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS));
+                            emailAddresses[i++] = emailAddress;
+                        }
+                    }
+                }
+            } finally {
+                close(p);
+            }
+        }
         if (incoming == true) {
             if(V) Log.d(TAG, "Adding originator for phone:" + phone);
-            message.addOriginator(contactName, contactName, phoneNumbers, emailAddresses); // Use version 3.0 as we only have a formatted name
+            // Use version 3.0 as we only have a formatted name
+            message.addOriginator(contactName, contactName, phoneNumbers, emailAddresses,null,null);
         } else {
             if(V) Log.d(TAG, "Adding recipient for phone:" + phone);
-            message.addRecipient(contactName, contactName, phoneNumbers, emailAddresses); // Use version 3.0 as we only have a formatted name
+            // Use version 3.0 as we only have a formatted name
+            message.addRecipient(contactName, contactName, phoneNumbers, emailAddresses,null,null);
         }
         return contactName;
     }
@@ -1628,51 +3366,56 @@
             throw new IllegalArgumentException("SMS handle not found");
         }
 
-        try {
-            if(V) Log.v(TAG,"c.count: " + c.getCount());
+        try{
+            if(c != null && c.moveToFirst())
+            {
+                if(V) Log.v(TAG,"c.count: " + c.getCount());
 
-            if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
-                message.setType(TYPE.SMS_GSM);
-            } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
-                message.setType(TYPE.SMS_CDMA);
-            }
-
-            String read = c.getString(c.getColumnIndex(Sms.READ));
-            if (read.equalsIgnoreCase("1"))
-                message.setStatus(true);
-            else
-                message.setStatus(false);
-
-            type = c.getInt(c.getColumnIndex(Sms.TYPE));
-            threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
-            message.setFolder(getFolderName(type, threadId));
-
-            msgBody = c.getString(c.getColumnIndex(Sms.BODY));
-
-            String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
-
-            time = c.getLong(c.getColumnIndex(Sms.DATE));
-            if(type == 1) // Inbox message needs to set the vCard as originator
-                setVCardFromPhoneNumber(message, phone, true);
-            else          // Other messages sets the vCard as the recipient
-                setVCardFromPhoneNumber(message, phone, false);
-
-            if(charset == MAP_MESSAGE_CHARSET_NATIVE) {
-                if(type == 1) //Inbox
-                    message.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus(msgBody, phone, time));
+                if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
+                    message.setType(TYPE.SMS_GSM);
+                } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+                    message.setType(TYPE.SMS_CDMA);
+                }
+                message.setVersionString(mMessageVersion);
+                String read = c.getString(c.getColumnIndex(Sms.READ));
+                if (read.equalsIgnoreCase("1"))
+                    message.setStatus(true);
                 else
-                    message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(msgBody, phone));
-            } else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ {
-                message.setSmsBody(msgBody);
+                    message.setStatus(false);
+
+                type = c.getInt(c.getColumnIndex(Sms.TYPE));
+                threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
+                message.setFolder(getFolderName(type, threadId));
+
+                msgBody = c.getString(c.getColumnIndex(Sms.BODY));
+
+                String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
+
+                time = c.getLong(c.getColumnIndex(Sms.DATE));
+                if(type == 1) // Inbox message needs to set the vCard as originator
+                    setVCardFromPhoneNumber(message, phone, true);
+                else          // Other messages sets the vCard as the recipient
+                    setVCardFromPhoneNumber(message, phone, false);
+
+                if(charset == MAP_MESSAGE_CHARSET_NATIVE) {
+                    if(type == 1) //Inbox
+                        message.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus(msgBody,
+                                    phone, time));
+                    else
+                        message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(msgBody, phone));
+                } else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ {
+                    message.setSmsBody(msgBody);
+                }
+                return message.encode();
             }
         } finally {
-            close(c);
+            if (c != null) c.close();
         }
 
         return message.encode();
     }
 
-    private void extractMmsAddresses(long id, BluetoothMapbMessageMms message) {
+    private void extractMmsAddresses(long id, BluetoothMapbMessageMime message) {
         final String[] projection = null;
         String selection = new String(Mms.Addr.MSG_ID + "=" + id);
         String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr");
@@ -1681,44 +3424,48 @@
 
         Cursor c = mResolver.query( uriAddress, projection, selection, null, null);
         try {
-            while (c != null && c.moveToNext()) {
-                String address = c.getString(c.getColumnIndex(Mms.Addr.ADDRESS));
-                if(address.equals(INSERT_ADDRES_TOKEN))
-                    continue;
-                Integer type = c.getInt(c.getColumnIndex(Mms.Addr.TYPE));
-                switch(type) {
-                case MMS_FROM:
-                    contactName = setVCardFromPhoneNumber(message, address, true);
-                    message.addFrom(contactName, address);
-                    break;
-                case MMS_TO:
-                    contactName = setVCardFromPhoneNumber(message, address, false);
-                    message.addTo(contactName, address);
-                    break;
-                case MMS_CC:
-                    contactName = setVCardFromPhoneNumber(message, address, false);
-                    message.addCc(contactName, address);
-                    break;
-                case MMS_BCC:
-                    contactName = setVCardFromPhoneNumber(message, address, false);
-                    message.addBcc(contactName, address);
-                    break;
-                default:
-                    break;
-                }
+            if (c.moveToFirst()) {
+                do {
+                    String address = c.getString(c.getColumnIndex(Mms.Addr.ADDRESS));
+                    if(address.equals(INSERT_ADDRES_TOKEN))
+                        continue;
+                    Integer type = c.getInt(c.getColumnIndex(Mms.Addr.TYPE));
+                    switch(type) {
+                    case MMS_FROM:
+                        contactName = setVCardFromPhoneNumber(message, address, true);
+                        message.addFrom(contactName, address);
+                        break;
+                    case MMS_TO:
+                        contactName = setVCardFromPhoneNumber(message, address, false);
+                        message.addTo(contactName, address);
+                        break;
+                    case MMS_CC:
+                        contactName = setVCardFromPhoneNumber(message, address, false);
+                        message.addCc(contactName, address);
+                        break;
+                    case MMS_BCC:
+                        contactName = setVCardFromPhoneNumber(message, address, false);
+                        message.addBcc(contactName, address);
+                        break;
+                    default:
+                        break;
+                    }
+                } while(c.moveToNext());
             }
         } finally {
-            close (c);
+            if (c != null) c.close();
         }
     }
 
+
     /**
-     * Read out a mms data part and return the data in a byte array.
-     * @param partid the content provider id of the mms.
+     * Read out a mime data part and return the data in a byte array.
+     * @param contentPartUri TODO
+     * @param partid the content provider id of the Mime Part.
      * @return
      */
-    private byte[] readMmsDataPart(long partid) {
-        String uriStr = new String(Mms.CONTENT_URI + "/part/" + partid);
+    private byte[] readRawDataPart(Uri contentPartUri, long partid) {
+        String uriStr = new String(contentPartUri+"/"+ partid);
         Uri uriAddress = Uri.parse(uriStr);
         InputStream is = null;
         ByteArrayOutputStream os = new ByteArrayOutputStream();
@@ -1748,7 +3495,7 @@
      * @param id the content provider ID of the message
      * @param message the bMessage object to add the information to
      */
-    private void extractMmsParts(long id, BluetoothMapbMessageMms message)
+    private void extractMmsParts(long id, BluetoothMapbMessageMime message)
     {
         /* Handling of filtering out non-text parts for exclude
          * attachments is handled within the bMessage object. */
@@ -1756,62 +3503,137 @@
         String selection = new String(Mms.Part.MSG_ID + "=" + id);
         String uriStr = new String(Mms.CONTENT_URI + "/"+ id + "/part");
         Uri uriAddress = Uri.parse(uriStr);
-        BluetoothMapbMessageMms.MimePart part;
+        BluetoothMapbMessageMime.MimePart part;
         Cursor c = mResolver.query(uriAddress, projection, selection, null, null);
-
         try {
-            while(c != null && c.moveToNext()) {
-                Long partId = c.getLong(c.getColumnIndex(BaseColumns._ID));
-                String contentType = c.getString(c.getColumnIndex(Mms.Part.CONTENT_TYPE));
-                String name = c.getString(c.getColumnIndex(Mms.Part.NAME));
-                String charset = c.getString(c.getColumnIndex(Mms.Part.CHARSET));
-                String filename = c.getString(c.getColumnIndex(Mms.Part.FILENAME));
-                String text = c.getString(c.getColumnIndex(Mms.Part.TEXT));
-                Integer fd = c.getInt(c.getColumnIndex(Mms.Part._DATA));
-                String cid = c.getString(c.getColumnIndex(Mms.Part.CONTENT_ID));
-                String cl = c.getString(c.getColumnIndex(Mms.Part.CONTENT_LOCATION));
-                String cdisp = c.getString(c.getColumnIndex(Mms.Part.CONTENT_DISPOSITION));
+            if (c.moveToFirst()) {
+                do {
+                    Long partId = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                    String contentType = c.getString(c.getColumnIndex(Mms.Part.CONTENT_TYPE));
+                    String name = c.getString(c.getColumnIndex(Mms.Part.NAME));
+                    String charset = c.getString(c.getColumnIndex(Mms.Part.CHARSET));
+                    String filename = c.getString(c.getColumnIndex(Mms.Part.FILENAME));
+                    String text = c.getString(c.getColumnIndex(Mms.Part.TEXT));
+                    Integer fd = c.getInt(c.getColumnIndex(Mms.Part._DATA));
+                    String cid = c.getString(c.getColumnIndex(Mms.Part.CONTENT_ID));
+                    String cl = c.getString(c.getColumnIndex(Mms.Part.CONTENT_LOCATION));
+                    String cdisp = c.getString(c.getColumnIndex(Mms.Part.CONTENT_DISPOSITION));
 
-                if(V) Log.d(TAG, "     _id : " + partId +
-                        "\n     ct : " + contentType +
-                        "\n     partname : " + name +
-                        "\n     charset : " + charset +
-                        "\n     filename : " + filename +
-                        "\n     text : " + text +
-                        "\n     fd : " + fd +
-                        "\n     cid : " + cid +
-                        "\n     cl : " + cl +
-                        "\n     cdisp : " + cdisp);
+                    if(V) Log.d(TAG, "     _id : " + partId +
+                            "\n     ct : " + contentType +
+                            "\n     partname : " + name +
+                            "\n     charset : " + charset +
+                            "\n     filename : " + filename +
+                            "\n     text : " + text +
+                            "\n     fd : " + fd +
+                            "\n     cid : " + cid +
+                            "\n     cl : " + cl +
+                            "\n     cdisp : " + cdisp);
 
-                part = message.addMimePart();
-                part.mContentType = contentType;
-                part.mPartName = name;
-                part.mContentId = cid;
-                part.mContentLocation = cl;
-                part.mContentDisposition = cdisp;
+                    part = message.addMimePart();
+                    part.mContentType = contentType;
+                    part.mPartName = name;
+                    part.mContentId = cid;
+                    part.mContentLocation = cl;
+                    part.mContentDisposition = cdisp;
 
-                try {
-                    if(text != null) {
-                        part.mData = text.getBytes("UTF-8");
-                        part.mCharsetName = "utf-8";
-                    } else {
-                        part.mData = readMmsDataPart(partId);
-                        if(charset != null)
-                            part.mCharsetName = CharacterSets.getMimeName(Integer.parseInt(charset));
+                    try {
+                        if(text != null) {
+                            part.mData = text.getBytes("UTF-8");
+                            part.mCharsetName = "utf-8";
+                        } else {
+                            part.mData =
+                                    readRawDataPart(Uri.parse(Mms.CONTENT_URI+"/part"), partId);
+                            if(charset != null) {
+                                part.mCharsetName =
+                                        CharacterSets.getMimeName(Integer.parseInt(charset));
+                            }
+                        }
+                    } catch (NumberFormatException e) {
+                        Log.d(TAG,"extractMmsParts",e);
+                        part.mData = null;
+                        part.mCharsetName = null;
+                    } catch (UnsupportedEncodingException e) {
+                        Log.d(TAG,"extractMmsParts",e);
+                        part.mData = null;
+                        part.mCharsetName = null;
+                    } finally {
                     }
-                } catch (NumberFormatException e) {
-                    Log.d(TAG,"extractMmsParts",e);
-                    part.mData = null;
-                    part.mCharsetName = null;
-                } catch (UnsupportedEncodingException e) {
-                    Log.d(TAG,"extractMmsParts",e);
-                    part.mData = null;
-                    part.mCharsetName = null;
-                }
-                part.mFileName = filename;
+                    part.mFileName = filename;
+                } while(c.moveToNext());
+                message.updateCharset();
+            }
+
+        } finally {
+            if(c != null) c.close();
+        }
+    }
+    /**
+     * 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 extractIMParts(long id, BluetoothMapbMessageMime message)
+    {
+        /* Handling of filtering out non-text parts for exclude
+         * attachments is handled within the bMessage object. */
+        final String[] projection = null;
+        String selection = new String(BluetoothMapContract.MessageColumns._ID + "=" + id);
+        String uriStr = new String(mBaseUri
+                                         + BluetoothMapContract.TABLE_MESSAGE + "/"+ id + "/part");
+        Uri uriAddress = Uri.parse(uriStr);
+        BluetoothMapbMessageMime.MimePart part;
+        Cursor c = mResolver.query(uriAddress, projection, selection, null, null);
+        try{
+            if (c.moveToFirst()) {
+                do {
+                    Long partId = c.getLong(
+                                  c.getColumnIndex(BluetoothMapContract.MessagePartColumns._ID));
+                    String charset = c.getString(
+                           c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CHARSET));
+                    String filename = c.getString(
+                           c.getColumnIndex(BluetoothMapContract.MessagePartColumns.FILENAME));
+                    String text = c.getString(
+                           c.getColumnIndex(BluetoothMapContract.MessagePartColumns.TEXT));
+                    String body = c.getString(
+                           c.getColumnIndex(BluetoothMapContract.MessagePartColumns.RAW_DATA));
+                    String cid = c.getString(
+                           c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CONTENT_ID));
+
+                    if(V) Log.d(TAG, "     _id : " + partId +
+                            "\n     charset : " + charset +
+                            "\n     filename : " + filename +
+                            "\n     text : " + text +
+                            "\n     cid : " + cid);
+
+                    part = message.addMimePart();
+                    part.mContentId = cid;
+                    try {
+                        if(text.equalsIgnoreCase("yes")) {
+                            part.mData = body.getBytes("UTF-8");
+                            part.mCharsetName = "utf-8";
+                        } else {
+                            part.mData = readRawDataPart(Uri.parse(mBaseUri
+                                             + BluetoothMapContract.TABLE_MESSAGE_PART) , partId);
+                            if(charset != null)
+                                part.mCharsetName = CharacterSets.getMimeName(
+                                                                        Integer.parseInt(charset));
+                        }
+                    } catch (NumberFormatException e) {
+                        Log.d(TAG,"extractIMParts",e);
+                        part.mData = null;
+                        part.mCharsetName = null;
+                    } catch (UnsupportedEncodingException e) {
+                        Log.d(TAG,"extractIMParts",e);
+                        part.mData = null;
+                        part.mCharsetName = null;
+                    } finally {
+                    }
+                    part.mFileName = filename;
+                } while(c.moveToNext());
             }
         } finally {
-            close(c);
+            if(c != null) c.close();
         }
 
         message.updateCharset();
@@ -1825,41 +3647,51 @@
      * @throws UnsupportedEncodingException if UTF-8 is not supported,
      * which is guaranteed to be supported on an android device
      */
-    public byte[] getMmsMessage(long id, BluetoothMapAppParams appParams) throws UnsupportedEncodingException {
+    public byte[] getMmsMessage(long id,BluetoothMapAppParams appParams)
+                                                        throws UnsupportedEncodingException {
         int msgBox, threadId;
         if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE)
-            throw new IllegalArgumentException("MMS charset native not allowed for MMS - must be utf-8");
+            throw new IllegalArgumentException("MMS charset native not allowed for MMS"
+                                                                            +" - must be utf-8");
 
-        BluetoothMapbMessageMms message = new BluetoothMapbMessageMms();
+        BluetoothMapbMessageMime message = new BluetoothMapbMessageMime();
         Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, "_ID = " + id, null, null);
-        if (c == null || !c.moveToFirst()) {
-            throw new IllegalArgumentException("MMS handle not found");
-        }
-
         try {
-            message.setType(TYPE.MMS);
+            if(c != null && c.moveToFirst())
+            {
+                message.setType(TYPE.MMS);
+                message.setVersionString(mMessageVersion);
 
-            // The MMS info:
-            String read = c.getString(c.getColumnIndex(Mms.READ));
-            if (read.equalsIgnoreCase("1"))
-                message.setStatus(true);
-            else
-                message.setStatus(false);
+                // The MMS info:
+                String read = c.getString(c.getColumnIndex(Mms.READ));
+                if (read.equalsIgnoreCase("1"))
+                    message.setStatus(true);
+                else
+                    message.setStatus(false);
 
-            msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
-            threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
-            message.setFolder(getFolderName(msgBox, threadId));
-            message.setSubject(c.getString(c.getColumnIndex(Mms.SUBJECT)));
-            message.setMessageId(c.getString(c.getColumnIndex(Mms.MESSAGE_ID)));
-            message.setContentType(c.getString(c.getColumnIndex(Mms.CONTENT_TYPE)));
-            message.setDate(c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L);
-            message.setTextOnly(c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) == 0 ? false : true);
-            message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true);
+                msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
+                threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
+                message.setFolder(getFolderName(msgBox, threadId));
+                message.setSubject(c.getString(c.getColumnIndex(Mms.SUBJECT)));
+                message.setMessageId(c.getString(c.getColumnIndex(Mms.MESSAGE_ID)));
+                message.setContentType(c.getString(c.getColumnIndex(Mms.CONTENT_TYPE)));
+                message.setDate(c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L);
+                message.setTextOnly(c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) == 0 ? false : true);
+                message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true);
+                // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used
+                // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is
 
-            extractMmsParts(id, message);
-            extractMmsAddresses(id, message);
+                // The parts
+                extractMmsParts(id, message);
+
+                // The addresses
+                extractMmsAddresses(id, message);
+
+
+                return message.encode();
+            }
         } finally {
-            close(c);
+            if (c != null) c.close();
         }
 
         return message.encode();
@@ -1888,114 +3720,273 @@
            throw new IllegalArgumentException("EMAIL charset not UTF-8");
 
        BluetoothMapbMessageEmail message = new BluetoothMapbMessageEmail();
-       Uri contentUri = Uri.parse(mBaseEmailUri + BluetoothMapContract.TABLE_MESSAGE);
-       Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = " + id, null, null);
-       if (c != null && c.moveToFirst()) {
-           throw new IllegalArgumentException("EMAIL handle not found");
-       }
-
+       Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
+       Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = "
+               + id, null, null);
        try {
-           BluetoothMapFolderElement folderElement;
+           if(c != null && c.moveToFirst())
+           {
+               BluetoothMapFolderElement folderElement;
+               FileInputStream is = null;
+               ParcelFileDescriptor fd = null;
+               try {
+                   // Handle fraction requests
+                   int fractionRequest = appParams.getFractionRequest();
+                   if (fractionRequest != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
+                       // Fraction requested
+                       if(V) {
+                           String fractionStr = (fractionRequest == 0) ? "FIRST" : "NEXT";
+                           Log.v(TAG, "getEmailMessage - FractionRequest " + fractionStr
+                                   +  " - send compete message" );
+                       }
+                       // Check if message is complete and if not - request message from server
+                       if (c.getString(c.getColumnIndex(
+                               BluetoothMapContract.MessageColumns.RECEPTION_STATE)).equalsIgnoreCase(
+                                       BluetoothMapContract.RECEPTION_STATE_COMPLETE) == false)  {
+                           // TODO: request message from server
+                           Log.w(TAG, "getEmailMessage - receptionState not COMPLETE -  Not Implemented!" );
+                       }
+                   }
+                   // Set read status:
+                   String read = c.getString(
+                                        c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
+                   if (read != null && read.equalsIgnoreCase("1"))
+                       message.setStatus(true);
+                   else
+                       message.setStatus(false);
 
-           // Handle fraction requests
-           int fractionRequest = appParams.getFractionRequest();
-           if (fractionRequest != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
-               // Fraction requested
-               if(V) {
-                   String fractionStr = (fractionRequest == 0) ? "FIRST" : "NEXT";
-                   Log.v(TAG, "getEmailMessage - FractionRequest " + fractionStr
-                           +  " - send compete message" );
+                   // Set message type:
+                   message.setType(TYPE.EMAIL);
+                   message.setVersionString(mMessageVersion);
+                   // Set folder:
+                   long folderId = c.getLong(
+                                       c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
+                   folderElement = currentFolder.getFolderById(folderId);
+                   message.setCompleteFolder(folderElement.getFullPath());
+
+                   // Set recipient:
+                   String nameEmail = c.getString(
+                                       c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST));
+                   Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
+                   if (tokens.length != 0) {
+                       if(D) Log.d(TAG, "Recipient count= " + tokens.length);
+                       int i = 0;
+                       while (i < tokens.length) {
+                           if(V) Log.d(TAG, "Recipient = " + tokens[i].toString());
+                           String[] emails = new String[1];
+                           emails[0] = tokens[i].getAddress();
+                           String name = tokens[i].getName();
+                           message.addRecipient(name, name, null, emails, null, null);
+                           i++;
+                       }
+                   }
+
+                   // Set originator:
+                   nameEmail = c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST));
+                   tokens = Rfc822Tokenizer.tokenize(nameEmail);
+                   if (tokens.length != 0) {
+                       if(D) Log.d(TAG, "Originator count= " + tokens.length);
+                       int i = 0;
+                       while (i < tokens.length) {
+                           if(V) Log.d(TAG, "Originator = " + tokens[i].toString());
+                           String[] emails = new String[1];
+                           emails[0] = tokens[i].getAddress();
+                           String name = tokens[i].getName();
+                           message.addOriginator(name, name, null, emails, null, null);
+                           i++;
+                       }
+                   }
+               } finally {
+                   if(c != null) c.close();
                }
-               // Check if message is complete and if not - request message from server
-               if (c.getString(c.getColumnIndex(
-                       BluetoothMapContract.MessageColumns.RECEPTION_STATE)).equalsIgnoreCase(
-                               BluetoothMapContract.RECEPTION_STATE_COMPLETE) == false)  {
-                   // TODO: request message from server
-                   Log.w(TAG, "getEmailMessage - receptionState not COMPLETE -  Not Implemented!" );
+               // Find out if we get attachments
+               String attStr = (appParams.getAttachment() == 0) ?
+                                           "/" +  BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS : "";
+               Uri uri = Uri.parse(contentUri + "/" + id + attStr);
+
+               // Get email message body content
+               int count = 0;
+               try {
+                   fd = mResolver.openFileDescriptor(uri, "r");
+                   is = new FileInputStream(fd.getFileDescriptor());
+                   StringBuilder email = new StringBuilder("");
+                   byte[] buffer = new byte[1024];
+                   while((count = is.read(buffer)) != -1) {
+                       // TODO: Handle breaks within a UTF8 character
+                       email.append(new String(buffer,0,count));
+                       if(V) Log.d(TAG, "Email part = "
+                                         + new String(buffer,0,count)
+                                         + " count=" + count);
+                   }
+                   // Set email message body:
+                   message.setEmailBody(email.toString());
+               } catch (FileNotFoundException e) {
+                   Log.w(TAG, e);
+               } catch (NullPointerException e) {
+                   Log.w(TAG, e);
+               } catch (IOException e) {
+                   Log.w(TAG, e);
+               } finally {
+                   try {
+                       if(is != null) is.close();
+                   } catch (IOException e) {}
+                   try {
+                       if(fd != null) fd.close();
+                   } catch (IOException e) {}
                }
-           }
-           // Set read status:
-           String read = c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
-           if (read != null && read.equalsIgnoreCase("1"))
-               message.setStatus(true);
-           else
-               message.setStatus(false);
-
-           // Set message type:
-           message.setType(TYPE.EMAIL);
-
-           // Set folder:
-           long folderId = c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
-           folderElement = currentFolder.getEmailFolderById(folderId);
-           message.setCompleteFolder(folderElement.getFullPath());
-
-           // Set recipient:
-           String nameEmail = c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST));
-           Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
-           if (tokens.length != 0) {
-               if(D) Log.d(TAG, "Recipient count= " + tokens.length);
-               int i = 0;
-               while (i < tokens.length) {
-                   if(V) Log.d(TAG, "Recipient = " + tokens[i].toString());
-                   String[] emails = new String[1];
-                   emails[0] = tokens[i].getAddress();
-                   String name = tokens[i].getName();
-                   message.addRecipient(name, name, null, emails);
-                   i++;
-               }
-           }
-
-           // Set originator:
-           nameEmail = c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST));
-           tokens = Rfc822Tokenizer.tokenize(nameEmail);
-           if (tokens.length != 0) {
-               if(D) Log.d(TAG, "Originator count= " + tokens.length);
-               int i = 0;
-               while (i < tokens.length) {
-                   if(V) Log.d(TAG, "Originator = " + tokens[i].toString());
-                   String[] emails = new String[1];
-                   emails[0] = tokens[i].getAddress();
-                   String name = tokens[i].getName();
-                   message.addOriginator(name, name, null, emails);
-                   i++;
-               }
-           }
-
-           // Find out if we get attachments
-           String attStr = (appParams.getAttachment() == 0) ?  "/" +  BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS : "";
-           Uri uri = Uri.parse(contentUri + "/" + id + attStr);
-
-           // Get email message body content
-           int count = 0;
-           FileInputStream is = null;
-           ParcelFileDescriptor fd = null;
-
-           try {
-               fd = mResolver.openFileDescriptor(uri, "r");
-               is = new FileInputStream(fd.getFileDescriptor());
-               StringBuilder email = new StringBuilder("");
-               byte[] buffer = new byte[1024];
-               while((count = is.read(buffer)) != -1) {
-                   // TODO: Handle breaks within a UTF8 character
-                   email.append(new String(buffer,0,count));
-                   if(V) Log.d(TAG, "Email part = " + new String(buffer,0,count) + " count=" + count);
-               }
-               // Set email message body:
-               message.setEmailBody(email.toString());
-           } catch (FileNotFoundException e) {
-               Log.w(TAG, e);
-           } catch (NullPointerException e) {
-               Log.w(TAG, e);
-           } catch (IOException e) {
-               Log.w(TAG, e);
-           } finally {
-               close(is);
-               close(fd);
+               return message.encode();
            }
        } finally {
-           close(c);
+           if (c != null) c.close();
+       }
+       throw new IllegalArgumentException("EMAIL handle not found");
+   }
+   /**
+   *
+   * @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
+   */
+
+   /**
+   *
+   * @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[] getIMMessage(long id,
+           BluetoothMapAppParams appParams,
+           BluetoothMapFolderElement folderElement)
+                   throws UnsupportedEncodingException {
+       long threadId, folderId;
+
+       if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE)
+           throw new IllegalArgumentException(
+                   "IM charset native not allowed for IM - must be utf-8");
+
+       BluetoothMapbMessageMime message = new BluetoothMapbMessageMime();
+       Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
+       Cursor c = mResolver.query(contentUri,
+               BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = " + id, null, null);
+       Cursor contacts = null;
+       try {
+           if(c != null && c.moveToFirst()) {
+               message.setType(TYPE.IM);
+               message.setVersionString(mMessageVersion);
+
+               // The IM message info:
+               int read =
+                       c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
+               if (read == 1)
+                   message.setStatus(true);
+               else
+                   message.setStatus(false);
+
+               threadId =
+                       c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_ID));
+               folderId =
+                       c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
+               folderElement = folderElement.getFolderById(folderId);
+               message.setCompleteFolder(folderElement.getFullPath());
+               message.setSubject(c.getString(
+                       c.getColumnIndex(BluetoothMapContract.MessageColumns.SUBJECT)));
+               message.setMessageId(c.getString(
+                       c.getColumnIndex(BluetoothMapContract.MessageColumns._ID)));
+               message.setDate(c.getLong(
+                       c.getColumnIndex(BluetoothMapContract.MessageColumns.DATE)));
+               message.setTextOnly(c.getInt(c.getColumnIndex(
+                       BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE)) != 0 ? false : true);
+
+               message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true);
+
+               // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used
+               // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is
+
+               // The parts
+
+               //FIXME use the parts when ready - until then use the body column for text-only
+               //  extractIMParts(id, message);
+               //FIXME next few lines are temporary code
+               MimePart part = message.addMimePart();
+               part.mData = c.getString((c.getColumnIndex(
+                       BluetoothMapContract.MessageColumns.BODY))).getBytes("UTF-8");
+               part.mCharsetName = "utf-8";
+               part.mContentId = "0";
+               part.mContentType = "text/plain";
+               message.updateCharset();
+               // FIXME end temp code
+
+               Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
+               contacts = mResolver.query(contactsUri,
+                       BluetoothMapContract.BT_CONTACT_PROJECTION,
+                       BluetoothMapContract.ConvoContactColumns.CONVO_ID
+                       + " = " + threadId, null, null);
+               // TODO this will not work for group-chats
+               if(contacts != null && contacts.moveToFirst()){
+                   String name = contacts.getString(contacts.getColumnIndex(
+                           BluetoothMapContract.ConvoContactColumns.NAME));
+                   String btUid[] = new String[1];
+                   btUid[0]= contacts.getString(contacts.getColumnIndex(
+                           BluetoothMapContract.ConvoContactColumns.X_BT_UID));
+                   String nickname = contacts.getString(contacts.getColumnIndex(
+                           BluetoothMapContract.ConvoContactColumns.NICKNAME));
+                   String btUci[] = new String[1];
+                   String btOwnUci[] = new String[1];
+                   btOwnUci[0] = mAccount.getUciFull();
+                   btUci[0] = contacts.getString(contacts.getColumnIndex(
+                           BluetoothMapContract.ConvoContactColumns.UCI));
+                   if(folderId == BluetoothMapContract.FOLDER_ID_SENT
+                           || folderId == BluetoothMapContract.FOLDER_ID_OUTBOX) {
+                       message.addRecipient(nickname,name,null, null, btUid, btUci);
+                       message.addOriginator(null, btOwnUci);
+
+                   }else {
+                       message.addOriginator(nickname,name,null, null, btUid, btUci);
+                       message.addRecipient(null, btOwnUci);
+
+                   }
+               }
+               return message.encode();
+           }
+       } finally {
+           if(c != null) c.close();
+           if(contacts != null) contacts.close();
        }
 
-       return message.encode();
+       throw new IllegalArgumentException("IM handle not found");
    }
+
+   public void setRemoteFeatureMask(int featureMask){
+       this.mRemoteFeatureMask = featureMask;
+       if(V) Log.d(TAG, "setRemoteFeatureMask");
+       if((this.mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)
+               == BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) {
+           if(V) Log.d(TAG, "setRemoteFeatureMask MAP_MESSAGE_LISTING_FORMAT_V11");
+           this.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
+       }
+   }
+
+   public int getRemoteFeatureMask(){
+       return this.mRemoteFeatureMask;
+   }
+
+    HashMap<Long,BluetoothMapConvoListingElement> getSmsMmsConvoList() {
+        return mMasInstance.getSmsMmsConvoList();
+    }
+
+    void setSmsMmsConvoList(HashMap<Long,BluetoothMapConvoListingElement> smsMmsConvoList) {
+        mMasInstance.setSmsMmsConvoList(smsMmsConvoList);
+    }
+
+    HashMap<Long,BluetoothMapConvoListingElement> getImEmailConvoList() {
+        return mMasInstance.getImEmailConvoList();
+    }
+
+    void setImEmailConvoList(HashMap<Long,BluetoothMapConvoListingElement> imEmailConvoList) {
+        mMasInstance.setImEmailConvoList(imEmailConvoList);
+    }
 }
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index dd14c82..d59ab87 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -1,20 +1,61 @@
 /*
-* Copyright (C) 2014 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.
-*/
+ * Copyright (C) 2014 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.Closeable;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentProviderClient;
+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.content.IntentFilter.MalformedMimeTypeException;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+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.text.format.DateUtils;
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns;
+import com.google.android.mms.pdu.PduHeaders;
+
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -32,64 +73,44 @@
 
 import javax.obex.ResponseCodes;
 
-import org.xmlpull.v1.XmlSerializer;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ContentProviderClient;
-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.content.IntentFilter.MalformedMimeTypeException;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.provider.BaseColumns;
-import com.android.bluetooth.mapapi.BluetoothMapContract;
-import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns;
-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.text.format.DateUtils;
-import android.util.Log;
-import android.util.Xml;
-import android.os.Looper;
-
-import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
-import com.android.bluetooth.map.BluetoothMapbMessageMms.MimePart;
-import com.google.android.mms.pdu.PduHeaders;
-
+@TargetApi(19)
 public class BluetoothMapContentObserver {
     private static final String TAG = "BluetoothMapContentObserver";
 
     private static final boolean D = BluetoothMapService.DEBUG;
     private static final boolean V = BluetoothMapService.VERBOSE;
 
-    private static final String EVENT_TYPE_DELETE = "MessageDeleted";
-    private static final String EVENT_TYPE_SHIFT  = "MessageShift";
-    private static final String EVENT_TYPE_NEW    = "NewMessage";
+    private static final String EVENT_TYPE_NEW              = "NewMessage";
+    private static final String EVENT_TYPE_DELETE           = "MessageDeleted";
+    private static final String EVENT_TYPE_REMOVED          = "MessageRemoved";
+    private static final String EVENT_TYPE_SHIFT            = "MessageShift";
     private static final String EVENT_TYPE_DELEVERY_SUCCESS = "DeliverySuccess";
     private static final String EVENT_TYPE_SENDING_SUCCESS  = "SendingSuccess";
     private static final String EVENT_TYPE_SENDING_FAILURE  = "SendingFailure";
     private static final String EVENT_TYPE_DELIVERY_FAILURE = "DeliveryFailure";
+    private static final String EVENT_TYPE_READ_STATUS      = "ReadStatusChanged";
+    private static final String EVENT_TYPE_CONVERSATION     = "ConversationChanged";
+    private static final String EVENT_TYPE_PRESENCE         = "ParticipantPresenceChanged";
+    private static final String EVENT_TYPE_CHAT_STATE       = "ParticipantChatStateChanged";
 
+    private static final long EVENT_FILTER_NEW_MESSAGE                  = 1L;
+    private static final long EVENT_FILTER_MESSAGE_DELETED              = 1L<<1;
+    private static final long EVENT_FILTER_MESSAGE_SHIFT                = 1L<<2;
+    private static final long EVENT_FILTER_SENDING_SUCCESS              = 1L<<3;
+    private static final long EVENT_FILTER_SENDING_FAILED               = 1L<<4;
+    private static final long EVENT_FILTER_DELIVERY_SUCCESS             = 1L<<5;
+    private static final long EVENT_FILTER_DELIVERY_FAILED              = 1L<<6;
+    private static final long EVENT_FILTER_MEMORY_FULL                  = 1L<<7; // Unused
+    private static final long EVENT_FILTER_MEMORY_AVAILABLE             = 1L<<8; // Unused
+    private static final long EVENT_FILTER_READ_STATUS_CHANGED          = 1L<<9;
+    private static final long EVENT_FILTER_CONVERSATION_CHANGED         = 1L<<10;
+    private static final long EVENT_FILTER_PARTICIPANT_PRESENCE_CHANGED = 1L<<11;
+    private static final long EVENT_FILTER_PARTICIPANT_CHATSTATE_CHANGED= 1L<<12;
+    private static final long EVENT_FILTER_MESSAGE_REMOVED              = 1L<<13;
 
+    // TODO: If we are requesting a large message from the network, on a slow connection
+    //       20 seconds might not be enough... But then again 20 seconds is long for other
+    //       cases.
     private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
 
     private Context mContext;
@@ -100,12 +121,28 @@
     private int mMasId;
     private boolean mEnableSmsMms = false;
     private boolean mObserverRegistered = false;
-    private BluetoothMapEmailSettingsItem mAccount;
+    private BluetoothMapAccountItem mAccount;
     private String mAuthority = null;
 
+    // Default supported feature bit mask is 0x1f
+    private int mMapSupportedFeatures = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
+    // Default event report version is 1.0
+    private int mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V10;
+
     private BluetoothMapFolderElement mFolders =
             new BluetoothMapFolderElement("DUMMY", null); // Will be set by the MAS when generated.
     private Uri mMessageUri = null;
+    private Uri mContactUri = null;
+
+    private boolean mTransmitEvents = true;
+
+    /* To make the filter update atomic, we declare it volatile.
+     * To avoid a penalty when using it, copy the value to a local
+     * non-volatile variable when used more than once.
+     * Actually we only ever use the lower 4 bytes of this variable,
+     * hence we could manage without the volatile keyword, but as
+     * we tend to copy ways of doing things, we better do it right:-) */
+    private volatile long mEventFilter = 0xFFFFFFFFL;
 
     public static final int DELETED_THREAD_ID = -1;
 
@@ -117,13 +154,6 @@
 
     private TYPE mSmsType;
 
-    private static void close(Closeable c) {
-        try {
-            if (c != null) c.close();
-        } catch (IOException e) {
-        }
-    }
-
     static final String[] SMS_PROJECTION = new String[] {
         Sms._ID,
         Sms.THREAD_ID,
@@ -140,59 +170,253 @@
     static final String[] SMS_PROJECTION_SHORT = new String[] {
         Sms._ID,
         Sms.THREAD_ID,
-        Sms.TYPE
+        Sms.TYPE,
+        Sms.READ
+    };
+
+    static final String[] SMS_PROJECTION_SHORT_EXT = new String[] {
+        Sms._ID,
+        Sms.THREAD_ID,
+        Sms.ADDRESS,
+        Sms.BODY,
+        Sms.DATE,
+        Sms.READ,
+        Sms.TYPE,
     };
 
     static final String[] MMS_PROJECTION_SHORT = new String[] {
         Mms._ID,
         Mms.THREAD_ID,
         Mms.MESSAGE_TYPE,
-        Mms.MESSAGE_BOX
+        Mms.MESSAGE_BOX,
+        Mms.READ
     };
 
-    static final String[] EMAIL_PROJECTION_SHORT = new String[] {
+    static final String[] MMS_PROJECTION_SHORT_EXT = new String[] {
+        Mms._ID,
+        Mms.THREAD_ID,
+        Mms.MESSAGE_TYPE,
+        Mms.MESSAGE_BOX,
+        Mms.READ,
+        Mms.DATE,
+        Mms.SUBJECT,
+        Mms.PRIORITY
+    };
+
+    static final String[] MSG_PROJECTION_SHORT = new String[] {
         BluetoothMapContract.MessageColumns._ID,
         BluetoothMapContract.MessageColumns.FOLDER_ID,
         BluetoothMapContract.MessageColumns.FLAG_READ
     };
 
+    static final String[] MSG_PROJECTION_SHORT_EXT = new String[] {
+        BluetoothMapContract.MessageColumns._ID,
+        BluetoothMapContract.MessageColumns.FOLDER_ID,
+        BluetoothMapContract.MessageColumns.FLAG_READ,
+        BluetoothMapContract.MessageColumns.DATE,
+        BluetoothMapContract.MessageColumns.SUBJECT,
+        BluetoothMapContract.MessageColumns.FROM_LIST,
+        BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY
+    };
+
+    static final String[] MSG_PROJECTION_SHORT_EXT2 = new String[] {
+        BluetoothMapContract.MessageColumns._ID,
+        BluetoothMapContract.MessageColumns.FOLDER_ID,
+        BluetoothMapContract.MessageColumns.FLAG_READ,
+        BluetoothMapContract.MessageColumns.DATE,
+        BluetoothMapContract.MessageColumns.SUBJECT,
+        BluetoothMapContract.MessageColumns.FROM_LIST,
+        BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY,
+        BluetoothMapContract.MessageColumns.THREAD_ID,
+        BluetoothMapContract.MessageColumns.THREAD_NAME
+    };
 
     public BluetoothMapContentObserver(final Context context,
-                                       BluetoothMnsObexClient mnsClient,
-                                       BluetoothMapMasInstance masInstance,
-                                       BluetoothMapEmailSettingsItem account,
-                                       boolean enableSmsMms) throws RemoteException {
+            BluetoothMnsObexClient mnsClient,
+            BluetoothMapMasInstance masInstance,
+            BluetoothMapAccountItem account,
+            boolean enableSmsMms) throws RemoteException {
         mContext = context;
         mResolver = mContext.getContentResolver();
         mAccount = account;
         mMasInstance = masInstance;
         mMasId = mMasInstance.getMasId();
+
+        mMapSupportedFeatures = mMasInstance.getRemoteFeatureMask();
+        if (D) Log.d(TAG, "BluetoothMapContentObserver: Supported features " +
+                Integer.toHexString(mMapSupportedFeatures) ) ;
+
+        if((BluetoothMapUtils.MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT
+                & mMapSupportedFeatures) != 0){
+            mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
+        }
+        // Make sure support for all formats result in latest version returned
+        if((BluetoothMapUtils.MAP_FEATURE_EVENT_REPORT_V12_BIT
+                & mMapSupportedFeatures) != 0){
+            mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+        }
+
         if(account != null) {
             mAuthority = Uri.parse(account.mBase_uri).getAuthority();
             mMessageUri = Uri.parse(account.mBase_uri + "/" + BluetoothMapContract.TABLE_MESSAGE);
+            if (mAccount.getType() == TYPE.IM) {
+                mContactUri = Uri.parse(account.mBase_uri + "/"
+                        + BluetoothMapContract.TABLE_CONVOCONTACT);
+            }
+            // TODO: We need to release this again!
             mProviderClient = mResolver.acquireUnstableContentProviderClient(mAuthority);
             if (mProviderClient == null) {
                 throw new RemoteException("Failed to acquire provider for " + mAuthority);
             }
             mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
+            mContactList = mMasInstance.getContactList();
+            if(mContactList == null) {
+                setContactList(new HashMap<String, BluetoothMapConvoContactElement>(), false);
+                initContactsList();
+            }
         }
-
         mEnableSmsMms = enableSmsMms;
         mSmsType = getSmsType();
         mMnsClient = mnsClient;
+        /* Get the cached list - if any, else create */
+        mMsgListSms = mMasInstance.getMsgListSms();
+        boolean doInit = false;
+        if(mEnableSmsMms) {
+            if(mMsgListSms == null) {
+                setMsgListSms(new HashMap<Long, Msg>(), false);
+                doInit = true;
+            }
+            mMsgListMms = mMasInstance.getMsgListMms();
+            if(mMsgListMms == null) {
+                setMsgListMms(new HashMap<Long, Msg>(), false);
+                doInit = true;
+            }
+        }
+        if(mAccount != null) {
+            mMsgListMsg = mMasInstance.getMsgListMsg();
+            if(mMsgListMsg == null) {
+                setMsgListMsg(new HashMap<Long, Msg>(), false);
+                doInit = true;
+            }
+        }
+        if(doInit) {
+            initMsgList();
+        }
     }
 
+
+    private Map<Long, Msg> getMsgListSms() {
+        return mMsgListSms;
+    }
+
+
+    private void setMsgListSms(Map<Long, Msg> msgListSms, boolean changesDetected) {
+        mMsgListSms = msgListSms;
+        if(changesDetected) {
+            mMasInstance.updateFolderVersionCounter();
+        }
+        mMasInstance.setMsgListSms(msgListSms);
+    }
+
+
+    private Map<Long, Msg> getMsgListMms() {
+        return mMsgListMms;
+    }
+
+
+    private void setMsgListMms(Map<Long, Msg> msgListMms, boolean changesDetected) {
+        mMsgListMms = msgListMms;
+        if(changesDetected) {
+            mMasInstance.updateFolderVersionCounter();
+        }
+        mMasInstance.setMsgListMms(msgListMms);
+    }
+
+
+    private Map<Long, Msg> getMsgListMsg() {
+        return mMsgListMsg;
+    }
+
+
+    private void setMsgListMsg(Map<Long, Msg> msgListMsg, boolean changesDetected) {
+        mMsgListMsg = msgListMsg;
+        if(changesDetected) {
+            mMasInstance.updateFolderVersionCounter();
+        }
+        mMasInstance.setMsgListMsg(msgListMsg);
+    }
+
+    private Map<String, BluetoothMapConvoContactElement> getContactList() {
+        return mContactList;
+    }
+
+
     /**
-     * Set the folder structure to be used for this instance.
-     * @param folderStructure
+     * Currently we only have data for IM / email contacts
+     * @param contactList
+     * @param changesDetected that is not chat state changed nor presence state changed.
      */
-    public void setFolderStructure(BluetoothMapFolderElement folderStructure) {
-        this.mFolders = folderStructure;
+    private void setContactList(Map<String, BluetoothMapConvoContactElement> contactList,
+            boolean changesDetected) {
+        mContactList = contactList;
+        if(changesDetected) {
+            mMasInstance.updateImEmailConvoListVersionCounter();
+        }
+        mMasInstance.setContactList(contactList);
+    }
+
+    private static boolean sendEventNewMessage(long eventFilter) {
+        return ((eventFilter & EVENT_FILTER_NEW_MESSAGE) > 0);
+    }
+
+    private static boolean sendEventMessageDeleted(long eventFilter) {
+        return ((eventFilter & EVENT_FILTER_MESSAGE_DELETED) > 0);
+    }
+
+    private static boolean sendEventMessageShift(long eventFilter) {
+        return ((eventFilter & EVENT_FILTER_MESSAGE_SHIFT) > 0);
+    }
+
+    private static boolean sendEventSendingSuccess(long eventFilter) {
+        return ((eventFilter & EVENT_FILTER_SENDING_SUCCESS) > 0);
+    }
+
+    private static boolean sendEventSendingFailed(long eventFilter) {
+        return ((eventFilter & EVENT_FILTER_SENDING_FAILED) > 0);
+    }
+
+    private static boolean sendEventDeliverySuccess(long eventFilter) {
+        return ((eventFilter & EVENT_FILTER_DELIVERY_SUCCESS) > 0);
+    }
+
+    private static boolean sendEventDeliveryFailed(long eventFilter) {
+        return ((eventFilter & EVENT_FILTER_DELIVERY_FAILED) > 0);
+    }
+
+    private static boolean sendEventReadStatusChanged(long eventFilter) {
+        return ((eventFilter & EVENT_FILTER_READ_STATUS_CHANGED) > 0);
+    }
+
+    private static boolean sendEventConversationChanged(long eventFilter) {
+        return ((eventFilter & EVENT_FILTER_CONVERSATION_CHANGED) > 0);
+    }
+
+    private static boolean sendEventParticipantPresenceChanged(long eventFilter) {
+        return ((eventFilter & EVENT_FILTER_PARTICIPANT_PRESENCE_CHANGED) > 0);
+    }
+
+    private static boolean sendEventParticipantChatstateChanged(long eventFilter) {
+        return ((eventFilter & EVENT_FILTER_PARTICIPANT_CHATSTATE_CHANGED) > 0);
+    }
+
+    private static boolean sendEventMessageRemoved(long eventFilter) {
+        return ((eventFilter & EVENT_FILTER_MESSAGE_REMOVED) > 0);
     }
 
     private TYPE getSmsType() {
         TYPE smsType = null;
-        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        TelephonyManager tm = (TelephonyManager) mContext.getSystemService(
+                Context.TELEPHONY_SERVICE);
 
         if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
             smsType = TYPE.SMS_GSM;
@@ -203,7 +427,8 @@
         return smsType;
     }
 
-    private final ContentObserver mObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+    private final ContentObserver mObserver = new ContentObserver(
+            new Handler(Looper.getMainLooper())) {
         @Override
         public void onChange(boolean selfChange) {
             onChange(selfChange, null);
@@ -211,58 +436,162 @@
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
+            if(uri == null) {
+                Log.w(TAG, "onChange() with URI == null - not handled.");
+                return;
+            }
             if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId()
-                + " Uri: " + uri.toString() + " selfchange: " + selfChange);
+                    + " Uri: " + uri.toString() + " selfchange: " + selfChange);
 
-            handleMsgListChanges(uri);
+            if(uri.toString().contains(BluetoothMapContract.TABLE_CONVOCONTACT))
+                handleContactListChanges(uri);
+            else
+                handleMsgListChanges(uri);
         }
     };
 
-    private static final String folderSms[] = {
-        "",
-        BluetoothMapContract.FOLDER_NAME_INBOX,
-        BluetoothMapContract.FOLDER_NAME_SENT,
-        BluetoothMapContract.FOLDER_NAME_DRAFT,
-        BluetoothMapContract.FOLDER_NAME_OUTBOX,
-        BluetoothMapContract.FOLDER_NAME_OUTBOX,
-        BluetoothMapContract.FOLDER_NAME_OUTBOX,
-        BluetoothMapContract.FOLDER_NAME_INBOX,
-        BluetoothMapContract.FOLDER_NAME_INBOX,
-    };
+    private static final HashMap<Integer, String> FOLDER_SMS_MAP;
+    static {
+        FOLDER_SMS_MAP = new HashMap<Integer, String>();
+        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_INBOX,  BluetoothMapContract.FOLDER_NAME_INBOX);
+        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_SENT,  BluetoothMapContract.FOLDER_NAME_SENT);
+        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_DRAFT,  BluetoothMapContract.FOLDER_NAME_DRAFT);
+        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_OUTBOX,  BluetoothMapContract.FOLDER_NAME_OUTBOX);
+        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_FAILED,  BluetoothMapContract.FOLDER_NAME_OUTBOX);
+        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_QUEUED,  BluetoothMapContract.FOLDER_NAME_OUTBOX);
+    }
 
-    private static final String folderMms[] = {
-        "",
-        BluetoothMapContract.FOLDER_NAME_INBOX,
-        BluetoothMapContract.FOLDER_NAME_SENT,
-        BluetoothMapContract.FOLDER_NAME_DRAFT,
-        BluetoothMapContract.FOLDER_NAME_OUTBOX,
-    };
+    private static String getSmsFolderName(int type) {
+        String name = FOLDER_SMS_MAP.get(type);
+        if(name != null) {
+            return name;
+        }
+        Log.e(TAG, "New SMS mailbox types have been introduced, without an update in BT...");
+        return "Unknown";
+    }
+
+
+    private static final HashMap<Integer, String> FOLDER_MMS_MAP;
+    static {
+        FOLDER_MMS_MAP = new HashMap<Integer, String>();
+        FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_INBOX,  BluetoothMapContract.FOLDER_NAME_INBOX);
+        FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_SENT,   BluetoothMapContract.FOLDER_NAME_SENT);
+        FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_DRAFTS, BluetoothMapContract.FOLDER_NAME_DRAFT);
+        FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_OUTBOX, BluetoothMapContract.FOLDER_NAME_OUTBOX);
+    }
+
+    private static String getMmsFolderName(int mailbox) {
+        String name = FOLDER_MMS_MAP.get(mailbox);
+        if(name != null) {
+            return name;
+        }
+        Log.e(TAG, "New MMS mailboxes have been introduced, without an update in BT...");
+        return "Unknown";
+    }
+
+    /**
+     * Set the folder structure to be used for this instance.
+     * @param folderStructure
+     */
+    public void setFolderStructure(BluetoothMapFolderElement folderStructure) {
+        this.mFolders = folderStructure;
+    }
+
+    private class ConvoContactInfo {
+        public int mConvoColConvoId         = -1;
+        public int mConvoColLastActivity    = -1;
+        public int mConvoColName            = -1;
+        //        public int mConvoColRead            = -1;
+        //        public int mConvoColVersionCounter  = -1;
+        public int mContactColUci           = -1;
+        public int mContactColConvoId       = -1;
+        public int mContactColName          = -1;
+        public int mContactColNickname      = -1;
+        public int mContactColBtUid         = -1;
+        public int mContactColChatState     = -1;
+        public int mContactColContactId     = -1;
+        public int mContactColLastActive    = -1;
+        public int mContactColPresenceState = -1;
+        public int mContactColPresenceText  = -1;
+        public int mContactColPriority      = -1;
+        public int mContactColLastOnline    = -1;
+
+        public void setConvoColunms(Cursor c) {
+            //            mConvoColConvoId         = c.getColumnIndex(
+            //                    BluetoothMapContract.ConversationColumns.THREAD_ID);
+            //            mConvoColLastActivity    = c.getColumnIndex(
+            //                    BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY);
+            //            mConvoColName            = c.getColumnIndex(
+            //                    BluetoothMapContract.ConversationColumns.THREAD_NAME);
+            mContactColConvoId       = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.CONVO_ID);
+            mContactColName          = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.NAME);
+            mContactColNickname      = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.NICKNAME);
+            mContactColBtUid         = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.X_BT_UID);
+            mContactColChatState     = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.CHAT_STATE);
+            mContactColUci           = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.UCI);
+            mContactColNickname      = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.NICKNAME);
+            mContactColLastActive    = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE);
+            mContactColName          = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.NAME);
+            mContactColPresenceState = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE);
+            mContactColPresenceText  = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.STATUS_TEXT);
+            mContactColPriority      = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.PRIORITY);
+            mContactColLastOnline    = c.getColumnIndex(
+                    BluetoothMapContract.ConvoContactColumns.LAST_ONLINE);
+        }
+    }
 
     private class Event {
         String eventType;
         long handle;
-        String folder;
-        String oldFolder;
+        String folder = null;
+        String oldFolder = null;
         TYPE msgType;
+        /* Extended event parameters in MAP Event version 1.1 */
+        String datetime = null; // OBEX time "YYYYMMDDTHHMMSS"
+        String uci = null;
+        String subject = null;
+        String senderName = null;
+        String priority = null;
+        /* Event parameters in MAP Event version 1.2 */
+        String conversationName = null;
+        long conversationID = -1;
+        int presenceState = BluetoothMapContract.PresenceState.UNKNOWN;
+        String presenceStatus = null;
+        int chatState = BluetoothMapContract.ChatState.UNKNOWN;
 
         final static String PATH = "telecom/msg/";
 
-        public Event(String eventType, long handle, String folder,
-            String oldFolder, TYPE msgType) {
-
-            this.eventType = eventType;
-            this.handle = handle;
-            if (folder != null) {
-                if(msgType == TYPE.EMAIL) {
-                    this.folder = folder;
+        private void setFolderPath(String name, TYPE type) {
+            if (name != null) {
+                if(type == TYPE.EMAIL || type == TYPE.IM) {
+                    this.folder = name;
                 } else {
-                    this.folder = PATH + folder;
+                    this.folder = PATH + name;
                 }
             } else {
                 this.folder = null;
             }
+        }
+
+        public Event(String eventType, long handle, String folder,
+                String oldFolder, TYPE msgType) {
+            this.eventType = eventType;
+            this.handle = handle;
+            setFolderPath(folder, msgType);
             if (oldFolder != null) {
-                if(msgType == TYPE.EMAIL) {
+                if(msgType == TYPE.EMAIL || msgType == TYPE.IM) {
                     this.oldFolder = oldFolder;
                 } else {
                     this.oldFolder = PATH + oldFolder;
@@ -273,19 +602,110 @@
             this.msgType = msgType;
         }
 
+        public Event(String eventType, long handle, String folder, TYPE msgType) {
+            this.eventType = eventType;
+            this.handle = handle;
+            setFolderPath(folder, msgType);
+            this.msgType = msgType;
+        }
+
+        /* extended event type 1.1 */
+        public Event(String eventType, long handle, String folder, TYPE msgType,
+                String datetime, String subject, String senderName, String priority) {
+            this.eventType = eventType;
+            this.handle = handle;
+            setFolderPath(folder, msgType);
+            this.msgType = msgType;
+            this.datetime = datetime;
+            if (subject != null) {
+                this.subject = BluetoothMapUtils.stripInvalidChars(subject);
+            }
+            if (senderName != null) {
+                this.senderName = BluetoothMapUtils.stripInvalidChars(senderName);
+            }
+            this.priority = priority;
+        }
+
+        /* extended event type 1.2 message events */
+        public Event(String eventType, long handle, String folder, TYPE msgType,
+                String datetime, String subject, String senderName, String priority,
+                long conversationID, String conversationName) {
+            this.eventType = eventType;
+            this.handle = handle;
+            setFolderPath(folder, msgType);
+            this.msgType = msgType;
+            this.datetime = datetime;
+            if (subject != null) {
+                this.subject = BluetoothMapUtils.stripInvalidChars(subject);
+            }
+            if (senderName != null) {
+                this.senderName = BluetoothMapUtils.stripInvalidChars(senderName);
+            }
+            if (conversationID != 0) {
+                this.conversationID = conversationID;
+            }
+            if (conversationName != null) {
+                this.conversationName = BluetoothMapUtils.stripInvalidChars(conversationName);
+            }
+            this.priority = priority;
+        }
+
+        /* extended event type 1.2 for conversation, presence or chat state changed events */
+        public Event(String eventType, String uci, TYPE msgType, String name, String priority,
+                String lastActivity, long conversationID, String conversationName,
+                int presenceState, String presenceStatus, int chatState) {
+            this.eventType = eventType;
+            this.uci = uci;
+            this.msgType = msgType;
+            if (name != null) {
+                this.senderName = BluetoothMapUtils.stripInvalidChars(name);
+            }
+            this.priority = priority;
+            this.datetime = lastActivity;
+            if (conversationID != 0) {
+                this.conversationID = conversationID;
+            }
+            if (conversationName != null) {
+                this.conversationName = BluetoothMapUtils.stripInvalidChars(conversationName);
+            }
+            if (presenceState != BluetoothMapContract.PresenceState.UNKNOWN) {
+                this.presenceState = presenceState;
+            }
+            if (presenceStatus != null) {
+                this.presenceStatus = BluetoothMapUtils.stripInvalidChars(presenceStatus);
+            }
+            if (chatState != BluetoothMapContract.ChatState.UNKNOWN) {
+                this.chatState = chatState;
+            }
+        }
+
         public byte[] encode() throws UnsupportedEncodingException {
             StringWriter sw = new StringWriter();
             XmlSerializer xmlEvtReport = Xml.newSerializer();
+
             try {
                 xmlEvtReport.setOutput(sw);
-                xmlEvtReport.startDocument(null, null);
+                xmlEvtReport.startDocument("UTF-8", true);
                 xmlEvtReport.text("\r\n");
                 xmlEvtReport.startTag("", "MAP-event-report");
-                xmlEvtReport.attribute("", "version", "1.0");
-
+                if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+                    xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V10_STR);
+                } else if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+                    xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V11_STR);
+                } else {
+                    xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V12_STR);
+                }
                 xmlEvtReport.startTag("", "event");
                 xmlEvtReport.attribute("", "type", eventType);
-                xmlEvtReport.attribute("", "handle", BluetoothMapUtils.getMapHandle(handle, msgType));
+                if (eventType.equals(EVENT_TYPE_CONVERSATION) ||
+                        eventType.equals(EVENT_TYPE_PRESENCE) ||
+                        eventType.equals(EVENT_TYPE_CHAT_STATE)) {
+                    xmlEvtReport.attribute("", "participant_uci", uci);
+                } else {
+                    xmlEvtReport.attribute("", "handle",
+                            BluetoothMapUtils.getMapHandle(handle, msgType));
+                }
+
                 if (folder != null) {
                     xmlEvtReport.attribute("", "folder", folder);
                 }
@@ -293,8 +713,56 @@
                     xmlEvtReport.attribute("", "old_folder", oldFolder);
                 }
                 xmlEvtReport.attribute("", "msg_type", msgType.name());
-                xmlEvtReport.endTag("", "event");
 
+                /* If MAP event report version is above 1.0 send
+                 * extended event report parameters */
+                if (datetime != null) {
+                    xmlEvtReport.attribute("", "datetime", datetime);
+                }
+                if (subject != null) {
+                    xmlEvtReport.attribute("", "subject",
+                            subject.substring(0,subject.length() < 256 ? subject.length() : 256));
+                }
+                if (senderName != null) {
+                    xmlEvtReport.attribute("", "senderName", senderName);
+                }
+                if (priority != null) {
+                    xmlEvtReport.attribute("", "priority", priority);
+                }
+
+                //}
+                /* Include conversation information from event version 1.2 */
+                if (mMapEventReportVersion > BluetoothMapUtils.MAP_EVENT_REPORT_V11 ) {
+                    if (conversationName != null) {
+                        xmlEvtReport.attribute("", "conversation_name", conversationName);
+                    }
+                    if (conversationID != -1) {
+                        // Convert provider conversation handle to string incl type
+                        xmlEvtReport.attribute("", "conversation_id",
+                                BluetoothMapUtils.getMapConvoHandle(conversationID, msgType));
+                    }
+                    if (eventType.equals(EVENT_TYPE_PRESENCE)) {
+                        if (presenceState != 0) {
+                            // Convert provider conversation handle to string incl type
+                            xmlEvtReport.attribute("", "presence_availability",
+                                    String.valueOf(presenceState));
+                        }
+                        if (presenceStatus != null) {
+                            // Convert provider conversation handle to string incl type
+                            xmlEvtReport.attribute("", "presence_status",
+                                    presenceStatus.substring(
+                                            0,presenceStatus.length() < 256 ? subject.length() : 256));
+                        }
+                    }
+                    if (eventType.equals(EVENT_TYPE_PRESENCE)) {
+                        if (chatState != 0) {
+                            // Convert provider conversation handle to string incl type
+                            xmlEvtReport.attribute("", "chat_state", String.valueOf(chatState));
+                        }
+                    }
+
+                }
+                xmlEvtReport.endTag("", "event");
                 xmlEvtReport.endTag("", "MAP-event-report");
                 xmlEvtReport.endDocument();
             } catch (IllegalArgumentException e) {
@@ -311,7 +779,7 @@
         }
     }
 
-    private class Msg {
+    /*package*/ class Msg {
         long id;
         int type;               // Used as folder for SMS/MMS
         int threadId;           // Used for SMS/MMS at delete
@@ -319,15 +787,18 @@
         long oldFolderId = -1;  // Used for email undelete
         boolean localInitiatedSend = false; // Used for MMS to filter out events
         boolean transparent = false; // Used for EMAIL to delete message sent with transparency
+        int flagRead = -1;      // Message status read/unread
 
-        public Msg(long id, int type, int threadId) {
+        public Msg(long id, int type, int threadId, int readFlag) {
             this.id = id;
             this.type = type;
             this.threadId = threadId;
+            this.flagRead = readFlag;
         }
-        public Msg(long id, long folderId) {
+        public Msg(long id, long folderId, int readFlag) {
             this.id = id;
             this.folderId = folderId;
+            this.flagRead = readFlag;
         }
 
         /* Eclipse generated hashCode() and equals() to make
@@ -357,11 +828,13 @@
         }
     }
 
-    private Map<Long, Msg> mMsgListSms = new HashMap<Long, Msg>();
+    private Map<Long, Msg> mMsgListSms = null;
 
-    private Map<Long, Msg> mMsgListMms = new HashMap<Long, Msg>();
+    private Map<Long, Msg> mMsgListMms = null;
 
-    private Map<Long, Msg> mMsgListEmail = new HashMap<Long, Msg>();
+    private Map<Long, Msg> mMsgListMsg = null;
+
+    private Map<String, BluetoothMapConvoContactElement> mContactList = null;
 
     public int setNotificationRegistration(int notificationStatus) throws RemoteException {
         // Forward the request to the MNS thread as a message - including the MAS instance ID.
@@ -375,7 +848,8 @@
             mns.sendMessageDelayed(msg, 10); // Send message without forcing a context switch
             /* Some devices - e.g. PTS needs to get the unregister confirm before we actually
              * disconnect the MNS. */
-            if(D) Log.d(TAG,"setNotificationRegistration() MSG_MNS_NOTIFICATION_REGISTRATION send to MNS");
+            if(D) Log.d(TAG,"setNotificationRegistration() MSG_MNS_NOTIFICATION_REGISTRATION " +
+                    "send to MNS");
         } else {
             // This should not happen except at shutdown.
             if(D) Log.d(TAG,"setNotificationRegistration() Unable to send registration request");
@@ -389,18 +863,43 @@
         return ResponseCodes.OBEX_HTTP_OK;
     }
 
+    boolean eventMaskContainsContacts(long mask) {
+        return sendEventParticipantPresenceChanged(mask);
+    }
+
+    boolean eventMaskContainsCovo(long mask) {
+        return (sendEventConversationChanged(mask)
+                || sendEventParticipantChatstateChanged(mask));
+    }
+
+    /* Overwrite the existing notification filter. Will register/deregister observers for
+     * the Contacts and Conversation table as needed. We keep the message observer
+     * at all times. */
+    /*package*/ synchronized void setNotificationFilter(long newFilter) {
+        long oldFilter = mEventFilter;
+        mEventFilter = newFilter;
+        /* Contacts */
+        if(!eventMaskContainsContacts(oldFilter) &&
+                eventMaskContainsContacts(newFilter)) {
+            // TODO:
+            // Enable the observer
+            // Reset the contacts list
+        }
+        /* Conversations */
+        if(!eventMaskContainsCovo(oldFilter) &&
+                eventMaskContainsCovo(newFilter)) {
+            // TODO:
+            // Enable the observer
+            // Reset the conversations list
+        }
+    }
+
     public void registerObserver() throws RemoteException{
         if (V) Log.d(TAG, "registerObserver");
 
         if (mObserverRegistered)
             return;
 
-        /* Use MmsSms Uri since the Sms Uri is not notified on deletes */
-        if(mEnableSmsMms){
-            //this is sms/mms
-            mResolver.registerContentObserver(MmsSms.CONTENT_URI, false, mObserver);
-            mObserverRegistered = true;
-        }
         if(mAccount != null) {
 
             mProviderClient = mResolver.acquireUnstableContentProviderClient(mAuthority);
@@ -409,8 +908,28 @@
             }
             mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
 
+            // If there is a change in the database before we init the lists we will be sending
+            // loads of events - hence init before register.
+            if(mAccount.getType() == TYPE.IM) {
+                // Further add contact list tracking
+                initContactsList();
+            }
+        }
+        // If there is a change in the database before we init the lists we will be sending
+        // loads of events - hence init before register.
+        initMsgList();
+
+        /* Use MmsSms Uri since the Sms Uri is not notified on deletes */
+        if(mEnableSmsMms){
+            //this is sms/mms
+            mResolver.registerContentObserver(MmsSms.CONTENT_URI, false, mObserver);
+            mObserverRegistered = true;
+        }
+
+        if(mAccount != null) {
             /* For URI's without account ID */
-            Uri uri = Uri.parse(mAccount.mBase_uri_no_account + "/" + BluetoothMapContract.TABLE_MESSAGE);
+            Uri uri = Uri.parse(mAccount.mBase_uri_no_account + "/"
+                    + BluetoothMapContract.TABLE_MESSAGE);
             if(D) Log.d(TAG, "Registering observer for: " + uri);
             mResolver.registerContentObserver(uri, true, mObserver);
 
@@ -419,9 +938,23 @@
             uri = Uri.parse(mAccount.mBase_uri + "/" + BluetoothMapContract.TABLE_MESSAGE);
             if(D) Log.d(TAG, "Registering observer for: " + uri);
             mResolver.registerContentObserver(uri, true, mObserver);
+
+            if(mAccount.getType() == TYPE.IM) {
+
+                uri = Uri.parse(mAccount.mBase_uri_no_account + "/"
+                        + BluetoothMapContract.TABLE_CONVOCONTACT);
+                if(D) Log.d(TAG, "Registering observer for: " + uri);
+                mResolver.registerContentObserver(uri, true, mObserver);
+
+                /* For URI's with account ID - is handled the same way as without ID, but is
+                 * only triggered for MAS instances with matching account ID. */
+                uri = Uri.parse(mAccount.mBase_uri + "/" + BluetoothMapContract.TABLE_CONVOCONTACT);
+                if(D) Log.d(TAG, "Registering observer for: " + uri);
+                mResolver.registerContentObserver(uri, true, mObserver);
+            }
+
             mObserverRegistered = true;
         }
-        initMsgList();
     }
 
     public void unregisterObserver() {
@@ -434,19 +967,150 @@
         }
     }
 
+    /**
+     * Per design it is only possible to call the refreshXxxx functions sequentially, hence it
+     * is safe to modify mTransmitEvents without synchronization.
+     */
+    /* package */ void refreshFolderVersionCounter() {
+        if (mObserverRegistered) {
+            // As we have observers, we already keep the counter up-to-date.
+            return;
+        }
+        /* We need to perform the same functionality, as when we receive a notification change,
+           hence we:
+            - disable the event transmission
+            - triggers the code for updates
+            - enable the event transmission */
+        mTransmitEvents = false;
+        try {
+            if(mEnableSmsMms) {
+                handleMsgListChangesSms();
+                handleMsgListChangesMms();
+            }
+            if(mAccount != null) {
+                try {
+                    handleMsgListChangesMsg(mMessageUri);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to update FolderVersionCounter. - Not fatal, but can cause" +
+                            " undesirable user experience!", e);
+                }
+            }
+        } finally {
+            // Ensure we always enable events again
+            mTransmitEvents = true;
+        }
+    }
+
+    /* package */ void refreshConvoListVersionCounter() {
+        if (mObserverRegistered) {
+            // As we have observers, we already keep the counter up-to-date.
+            return;
+        }
+        /* We need to perform the same functionality, as when we receive a notification change,
+        hence we:
+         - disable event transmission
+         - triggers the code for updates
+         - enable event transmission */
+        mTransmitEvents = false;
+        try {
+            if((mAccount != null) && (mContactUri != null)) {
+                handleContactListChanges(mContactUri);
+            }
+        } finally {
+            // Ensure we always enable events again
+            mTransmitEvents = true;
+        }
+    }
+
     private void sendEvent(Event evt) {
-        Log.d(TAG, "sendEvent: " + evt.eventType + " " + evt.handle + " "
-        + evt.folder + " " + evt.oldFolder + " " + evt.msgType.name());
+
+        if(mTransmitEvents == false) {
+            if(V) Log.v(TAG, "mTransmitEvents == false - don't send event.");
+            return;
+        }
+
+        if(D)Log.d(TAG, "sendEvent: " + evt.eventType + " " + evt.handle + " " + evt.folder + " "
+                + evt.oldFolder + " " + evt.msgType.name() + " " + evt.datetime + " "
+                + evt.subject + " " + evt.senderName + " " + evt.priority );
 
         if (mMnsClient == null || mMnsClient.isConnected() == false) {
             Log.d(TAG, "sendEvent: No MNS client registered or connected- don't send event");
             return;
         }
 
+        /* Enable use of the cache for checking the filter */
+        long eventFilter = mEventFilter;
+
+        /* This should have been a switch on the string, but it is not allowed in Java 1.6 */
+        /* WARNING: Here we do pointer compare for the string to speed up things, that is.
+         * HENCE: always use the EVENT_TYPE_"defines" */
+        if(evt.eventType == EVENT_TYPE_NEW) {
+            if(!sendEventNewMessage(eventFilter)) {
+                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                return;
+            }
+        } else if(evt.eventType == EVENT_TYPE_DELETE) {
+            if(!sendEventMessageDeleted(eventFilter)) {
+                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                return;
+            }
+        } else if(evt.eventType == EVENT_TYPE_REMOVED) {
+            if(!sendEventMessageRemoved(eventFilter)) {
+                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                return;
+            }
+        } else if(evt.eventType == EVENT_TYPE_SHIFT) {
+            if(!sendEventMessageShift(eventFilter)) {
+                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                return;
+            }
+        } else if(evt.eventType == EVENT_TYPE_DELEVERY_SUCCESS) {
+            if(!sendEventDeliverySuccess(eventFilter)) {
+                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                return;
+            }
+        } else if(evt.eventType == EVENT_TYPE_SENDING_SUCCESS) {
+            if(!sendEventSendingSuccess(eventFilter)) {
+                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                return;
+            }
+        } else if(evt.eventType == EVENT_TYPE_SENDING_FAILURE) {
+            if(!sendEventSendingFailed(eventFilter)) {
+                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                return;
+            }
+        } else if(evt.eventType == EVENT_TYPE_DELIVERY_FAILURE) {
+            if(!sendEventDeliveryFailed(eventFilter)) {
+                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                return;
+            }
+        } else if(evt.eventType == EVENT_TYPE_READ_STATUS) {
+            if(!sendEventReadStatusChanged(eventFilter)) {
+                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                return;
+            }
+        } else if(evt.eventType == EVENT_TYPE_CONVERSATION) {
+            if(!sendEventConversationChanged(eventFilter)) {
+                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                return;
+            }
+        } else if(evt.eventType == EVENT_TYPE_PRESENCE) {
+            if(!sendEventParticipantPresenceChanged(eventFilter)) {
+                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                return;
+            }
+        } else if(evt.eventType == EVENT_TYPE_CHAT_STATE) {
+            if(!sendEventParticipantChatstateChanged(eventFilter)) {
+                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                return;
+            }
+        }
+
         try {
             mMnsClient.sendEvent(evt.encode(), mMasId);
         } catch (UnsupportedEncodingException ex) {
             /* do nothing */
+            if (D) Log.e(TAG, "Exception - should not happen: ",ex);
         }
     }
 
@@ -458,145 +1122,274 @@
             HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>();
 
             Cursor c = mResolver.query(Sms.CONTENT_URI,
-                SMS_PROJECTION_SHORT, null, null, null);
-
+                    SMS_PROJECTION_SHORT, null, null, null);
             try {
-                while (c != null && c.moveToNext()) {
-                    long id = c.getLong(c.getColumnIndex(Sms._ID));
-                    int type = c.getInt(c.getColumnIndex(Sms.TYPE));
-                    int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
+                if (c != null && c.moveToFirst()) {
+                    do {
+                        long id = c.getLong(c.getColumnIndex(Sms._ID));
+                        int type = c.getInt(c.getColumnIndex(Sms.TYPE));
+                        int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
+                        int read = c.getInt(c.getColumnIndex(Sms.READ));
 
-                    Msg msg = new Msg(id, type, threadId);
-                    msgListSms.put(id, msg);
+                        Msg msg = new Msg(id, type, threadId, read);
+                        msgListSms.put(id, msg);
+                    } while (c.moveToNext());
                 }
             } finally {
-                close(c);
+                if (c != null) c.close();
             }
 
-            synchronized(mMsgListSms) {
-                mMsgListSms.clear();
-                mMsgListSms = msgListSms;
+            synchronized(getMsgListSms()) {
+                getMsgListSms().clear();
+                setMsgListSms(msgListSms, true); // Set initial folder version counter
             }
 
             HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>();
 
             c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION_SHORT, null, null, null);
-
             try {
-                while (c != null && c.moveToNext()) {
-                    long id = c.getLong(c.getColumnIndex(Mms._ID));
-                    int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
-                    int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
+                if (c != null && c.moveToFirst()) {
+                    do {
+                        long id = c.getLong(c.getColumnIndex(Mms._ID));
+                        int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
+                        int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
+                        int read = c.getInt(c.getColumnIndex(Mms.READ));
 
-                    Msg msg = new Msg(id, type, threadId);
-                    msgListMms.put(id, msg);
+                        Msg msg = new Msg(id, type, threadId, read);
+                        msgListMms.put(id, msg);
+                    } while (c.moveToNext());
                 }
             } finally {
-                close(c);
+                if (c != null) c.close();
             }
 
-            synchronized(mMsgListMms) {
-                mMsgListMms.clear();
-                mMsgListMms = msgListMms;
+            synchronized(getMsgListMms()) {
+                getMsgListMms().clear();
+                setMsgListMms(msgListMms, true); // Set initial folder version counter
             }
         }
 
         if(mAccount != null) {
-            HashMap<Long, Msg> msgListEmail = new HashMap<Long, Msg>();
+            HashMap<Long, Msg> msgList = new HashMap<Long, Msg>();
             Uri uri = mMessageUri;
-            Cursor c = mProviderClient.query(uri, EMAIL_PROJECTION_SHORT, null, null, null);
+            Cursor c = mProviderClient.query(uri, MSG_PROJECTION_SHORT, null, null, null);
 
             try {
-                while (c != null && c.moveToNext()) {
-                    long id = c.getLong(c.getColumnIndex(MessageColumns._ID));
-                    long folderId = c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
-
-                    Msg msg = new Msg(id, folderId);
-                    msgListEmail.put(id, msg);
+                if (c != null && c.moveToFirst()) {
+                    do {
+                        long id = c.getLong(c.getColumnIndex(MessageColumns._ID));
+                        long folderId = c.getInt(c.getColumnIndex(
+                                BluetoothMapContract.MessageColumns.FOLDER_ID));
+                        int readFlag = c.getInt(c.getColumnIndex(
+                                BluetoothMapContract.MessageColumns.FLAG_READ));
+                        Msg msg = new Msg(id, folderId, readFlag);
+                        msgList.put(id, msg);
+                    } while (c.moveToNext());
                 }
             } finally {
-                close(c);
+                if (c != null) c.close();
             }
 
-            synchronized(mMsgListEmail) {
-                mMsgListEmail.clear();
-                mMsgListEmail = msgListEmail;
+            synchronized(getMsgListMsg()) {
+                getMsgListMsg().clear();
+                setMsgListMsg(msgList, true);
             }
         }
     }
 
+    private void initContactsList() throws RemoteException {
+        if (V) Log.d(TAG, "initContactsList");
+        if(mContactUri == null) {
+            if (D) Log.d(TAG, "initContactsList() no mContactUri - nothing to init");
+            return;
+        }
+        Uri uri = mContactUri;
+        Cursor c = mProviderClient.query(uri,
+                BluetoothMapContract.BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION,
+                null, null, null);
+        Map<String, BluetoothMapConvoContactElement> contactList =
+                new HashMap<String, BluetoothMapConvoContactElement>();
+        try {
+            if (c != null && c.moveToFirst()) {
+                ConvoContactInfo cInfo = new ConvoContactInfo();
+                cInfo.setConvoColunms(c);
+                do {
+                    long convoId = c.getLong(cInfo.mContactColConvoId);
+                    if (convoId == 0)
+                        continue;
+                    if (V) BluetoothMapUtils.printCursor(c);
+                    String uci = c.getString(cInfo.mContactColUci);
+                    String name = c.getString(cInfo.mContactColName);
+                    String displayName = c.getString(cInfo.mContactColNickname);
+                    String presenceStatus = c.getString(cInfo.mContactColPresenceText);
+                    int presenceState = c.getInt(cInfo.mContactColPresenceState);
+                    long lastActivity = c.getLong(cInfo.mContactColLastActive);
+                    int chatState = c.getInt(cInfo.mContactColChatState);
+                    int priority = c.getInt(cInfo.mContactColPriority);
+                    String btUid = c.getString(cInfo.mContactColBtUid);
+                    BluetoothMapConvoContactElement contact =
+                            new BluetoothMapConvoContactElement(uci, name, displayName,
+                                    presenceStatus, presenceState, lastActivity, chatState,
+                                    priority, btUid);
+                    contactList.put(uci, contact);
+                } while (c.moveToNext());
+            }
+        } finally {
+            if (c != null) c.close();
+        }
+        synchronized(getContactList()) {
+            getContactList().clear();
+            setContactList(contactList, true);
+        }
+    }
+
     private void handleMsgListChangesSms() {
         if (V) Log.d(TAG, "handleMsgListChangesSms");
 
         HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>();
+        boolean listChanged = false;
 
-        Cursor c = mResolver.query(Sms.CONTENT_URI,
-            SMS_PROJECTION_SHORT, null, null, null);
-
-        synchronized(mMsgListSms) {
+        Cursor c;
+        if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+            c = mResolver.query(Sms.CONTENT_URI,
+                    SMS_PROJECTION_SHORT, null, null, null);
+        } else {
+            c = mResolver.query(Sms.CONTENT_URI,
+                    SMS_PROJECTION_SHORT_EXT, null, null, null);
+        }
+        synchronized(getMsgListSms()) {
             try {
-                while (c != null && c.moveToNext()) {
-                    long id = c.getLong(c.getColumnIndex(Sms._ID));
-                    int type = c.getInt(c.getColumnIndex(Sms.TYPE));
-                    int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
+                if (c != null && c.moveToFirst()) {
+                    do {
+                        long id = c.getLong(c.getColumnIndex(Sms._ID));
+                        int type = c.getInt(c.getColumnIndex(Sms.TYPE));
+                        int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
+                        int read = c.getInt(c.getColumnIndex(Sms.READ));
 
-                    Msg msg = mMsgListSms.remove(id);
+                        Msg msg = getMsgListSms().remove(id);
 
-                    /* We must filter out any actions made by the MCE, hence do not send e.g. a message
-                     * deleted and/or MessageShift for messages deleted by the MCE. */
+                        /* We must filter out any actions made by the MCE, hence do not send e.g.
+                         * a message deleted and/or MessageShift for messages deleted by the MCE. */
 
-                    if (msg == null) {
-                        /* New message */
-                        msg = new Msg(id, type, threadId);
-                        msgListSms.put(id, msg);
-
-                        /* Incoming message from the network */
-                        Event evt = new Event(EVENT_TYPE_NEW, id, folderSms[type],
-                            null, mSmsType);
-                        sendEvent(evt);
-                    } else {
-                        /* Existing message */
-                        if (type != msg.type) {
-                            Log.d(TAG, "new type: " + type + " old type: " + msg.type);
-                            String oldFolder = folderSms[msg.type];
-                            String newFolder = folderSms[type];
-                            // Filter out the intermediate outbox steps
-                            if(!oldFolder.equals(newFolder)) {
-                                Event evt = new Event(EVENT_TYPE_SHIFT, id, folderSms[type],
-                                    oldFolder, mSmsType);
-                                sendEvent(evt);
+                        if (msg == null) {
+                            /* New message */
+                            msg = new Msg(id, type, threadId, read);
+                            msgListSms.put(id, msg);
+                            listChanged = true;
+                            Event evt;
+                            if (mTransmitEvents == true && // extract contact details only if needed
+                                    mMapEventReportVersion >
+                            BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+                                String date = BluetoothMapUtils.getDateTimeString(
+                                        c.getLong(c.getColumnIndex(Sms.DATE)));
+                                String subject = c.getString(c.getColumnIndex(Sms.BODY));
+                                String name = "";
+                                String phone = "";
+                                if (type == 1) { //inbox
+                                    phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
+                                    if (phone != null && !phone.isEmpty()) {
+                                        name = BluetoothMapContent.getContactNameFromPhone(phone,
+                                                mResolver);
+                                        if(name == null || name.isEmpty()){
+                                            name = phone;
+                                        }
+                                    }else{
+                                        name = phone;
+                                    }
+                                } else {
+                                    TelephonyManager tm =
+                                            (TelephonyManager)mContext.getSystemService(
+                                            Context.TELEPHONY_SERVICE);
+                                    if (tm != null) {
+                                        phone = tm.getLine1Number();
+                                        name = tm.getLine1AlphaTag();
+                                        if(name == null || name.isEmpty()){
+                                            name = phone;
+                                        }
+                                    }
+                                }
+                                String priority = "no";// no priority for sms
+                                /* Incoming message from the network */
+                                if (mMapEventReportVersion ==
+                                        BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+                                    evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type),
+                                            mSmsType, date, subject, name, priority);
+                                } else {
+                                    evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type),
+                                            mSmsType, date, subject, name, priority,
+                                            (long)threadId, null);
+                                }
+                            } else {
+                                /* Incoming message from the network */
+                                evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type),
+                                        null, mSmsType);
                             }
-                            msg.type = type;
-                        } else if(threadId != msg.threadId) {
-                            Log.d(TAG, "Message delete change: type: " + type + " old type: " + msg.type
-                                    + "\n    threadId: " + threadId + " old threadId: " + msg.threadId);
-                            if(threadId == DELETED_THREAD_ID) { // Message deleted
-                                Event evt = new Event(EVENT_TYPE_DELETE, id, BluetoothMapContract.FOLDER_NAME_DELETED,
-                                    folderSms[msg.type], mSmsType);
-                                sendEvent(evt);
-                                msg.threadId = threadId;
-                            } else { // Undelete
-                                Event evt = new Event(EVENT_TYPE_SHIFT, id, folderSms[msg.type],
-                                    BluetoothMapContract.FOLDER_NAME_DELETED, mSmsType);
-                                sendEvent(evt);
-                                msg.threadId = threadId;
+                            sendEvent(evt);
+                        } else {
+                            /* Existing message */
+                            if (type != msg.type) {
+                                listChanged = true;
+                                Log.d(TAG, "new type: " + type + " old type: " + msg.type);
+                                String oldFolder = getSmsFolderName(msg.type);
+                                String newFolder = getSmsFolderName(type);
+                                // Filter out the intermediate outbox steps
+                                if(!oldFolder.equalsIgnoreCase(newFolder)) {
+                                    Event evt = new Event(EVENT_TYPE_SHIFT, id,
+                                            getSmsFolderName(type), oldFolder, mSmsType);
+                                    sendEvent(evt);
+                                }
+                                msg.type = type;
+                            } else if(threadId != msg.threadId) {
+                                listChanged = true;
+                                Log.d(TAG, "Message delete change: type: " + type
+                                        + " old type: " + msg.type
+                                        + "\n    threadId: " + threadId
+                                        + " old threadId: " + msg.threadId);
+                                if(threadId == DELETED_THREAD_ID) { // Message deleted
+                                    // TODO:
+                                    // We shall only use the folder attribute, but can't remember
+                                    // wether to set it to "deleted" or the name of the folder
+                                    // from which the message have been deleted.
+                                    Event evt = new Event(EVENT_TYPE_DELETE, id,
+                                            BluetoothMapContract.FOLDER_NAME_DELETED,
+                                            getSmsFolderName(msg.type), mSmsType);
+                                    sendEvent(evt);
+                                    msg.threadId = threadId;
+                                } else { // Undelete
+                                    Event evt = new Event(EVENT_TYPE_SHIFT, id,
+                                            getSmsFolderName(msg.type),
+                                            BluetoothMapContract.FOLDER_NAME_DELETED, mSmsType);
+                                    sendEvent(evt);
+                                    msg.threadId = threadId;
+                                }
                             }
+                            if(read != msg.flagRead) {
+                                listChanged = true;
+                                msg.flagRead = read;
+                                if (mMapEventReportVersion >
+                                        BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+                                    Event evt = new Event(EVENT_TYPE_READ_STATUS, id,
+                                            getSmsFolderName(msg.type), mSmsType);
+                                    sendEvent(evt);
+                                }
+                            }
+                            msgListSms.put(id, msg);
                         }
-                        msgListSms.put(id, msg);
-                    }
+                    } while (c.moveToNext());
                 }
             } finally {
-                close(c);
+                if (c != null) c.close();
             }
 
-            for (Msg msg : mMsgListSms.values()) {
+            for (Msg msg : getMsgListSms().values()) {
                 Event evt = new Event(EVENT_TYPE_DELETE, msg.id,
-                                        BluetoothMapContract.FOLDER_NAME_DELETED,
-                                        folderSms[msg.type], mSmsType);
+                        BluetoothMapContract.FOLDER_NAME_DELETED,
+                        getSmsFolderName(msg.type), mSmsType);
                 sendEvent(evt);
+                listChanged = true;
             }
 
-            mMsgListSms = msgListSms;
+            setMsgListSms(msgListSms, listChanged);
         }
     }
 
@@ -604,191 +1397,322 @@
         if (V) Log.d(TAG, "handleMsgListChangesMms");
 
         HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>();
+        boolean listChanged = false;
+        Cursor c;
+        if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+            c = mResolver.query(Mms.CONTENT_URI,
+                    MMS_PROJECTION_SHORT, null, null, null);
+        } else {
+            c = mResolver.query(Mms.CONTENT_URI,
+                    MMS_PROJECTION_SHORT_EXT, null, null, null);
+        }
 
-        Cursor c = mResolver.query(Mms.CONTENT_URI,
-            MMS_PROJECTION_SHORT, null, null, null);
+        synchronized(getMsgListMms()) {
+            try{
+                if (c != null && c.moveToFirst()) {
+                    do {
+                        long id = c.getLong(c.getColumnIndex(Mms._ID));
+                        int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
+                        int mtype = c.getInt(c.getColumnIndex(Mms.MESSAGE_TYPE));
+                        int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
+                        // TODO: Go through code to see if we have an issue with mismatch in types
+                        //       for threadId. Seems to be a long in DB??
+                        int read = c.getInt(c.getColumnIndex(Mms.READ));
 
-        synchronized(mMsgListMms) {
-            try {
-                while (c != null && c.moveToNext()) {
-                    long id = c.getLong(c.getColumnIndex(Mms._ID));
-                    int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
-                    int mtype = c.getInt(c.getColumnIndex(Mms.MESSAGE_TYPE));
-                    int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
+                        Msg msg = getMsgListMms().remove(id);
 
-                    Msg msg = mMsgListMms.remove(id);
+                        /* We must filter out any actions made by the MCE, hence do not send
+                         * e.g. a message deleted and/or MessageShift for messages deleted by the
+                         * MCE.*/
 
-                    /* We must filter out any actions made by the MCE, hence do not send e.g. a message
-                     * deleted and/or MessageShift for messages deleted by the MCE. */
-
-                    if (msg == null) {
-                        /* New message - only notify on retrieve conf */
-                        if (folderMms[type].equals(BluetoothMapContract.FOLDER_NAME_INBOX) &&
-                            mtype != MESSAGE_TYPE_RETRIEVE_CONF) {
+                        if (msg == null) {
+                            /* New message - only notify on retrieve conf */
+                            listChanged = true;
+                            if (getMmsFolderName(type).equalsIgnoreCase(
+                                    BluetoothMapContract.FOLDER_NAME_INBOX) &&
+                                    mtype != MESSAGE_TYPE_RETRIEVE_CONF) {
                                 continue;
-                        }
-
-                        msg = new Msg(id, type, threadId);
-                        msgListMms.put(id, msg);
-
-                        /* Incoming message from the network */
-                        Event evt = new Event(EVENT_TYPE_NEW, id, folderMms[type],
-                                null, TYPE.MMS);
-                        sendEvent(evt);
-                    } else {
-                        /* Existing message */
-                        if (type != msg.type) {
-                            Log.d(TAG, "new type: " + type + " old type: " + msg.type);
+                            }
+                            msg = new Msg(id, type, threadId, read);
+                            msgListMms.put(id, msg);
                             Event evt;
-                            if(msg.localInitiatedSend == false) {
-                                // Only send events about local initiated changes
-                                evt = new Event(EVENT_TYPE_SHIFT, id, folderMms[type],
-                                        folderMms[msg.type], TYPE.MMS);
-                                sendEvent(evt);
-                            }
-                            msg.type = type;
+                            if (mTransmitEvents == true && // extract contact details only if needed
+                                    mMapEventReportVersion !=
+                                    BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+                                String date = BluetoothMapUtils.getDateTimeString(
+                                        c.getLong(c.getColumnIndex(Mms.DATE)));
+                                String subject = c.getString(c.getColumnIndex(Mms.SUBJECT));
+                                if (subject == null || subject.length() == 0) {
+                                    /* Get subject from mms text body parts - if any exists */
+                                    subject = BluetoothMapContent.getTextPartsMms(mResolver, id);
+                                }
+                                int tmpPri = c.getInt(c.getColumnIndex(Mms.PRIORITY));
+                                Log.d(TAG, "TEMP handleMsgListChangesMms, " +
+                                        "newMessage 'read' state: " + read +
+                                        "priority: " + tmpPri);
 
-                            if (folderMms[type].equals(BluetoothMapContract.FOLDER_NAME_SENT)
-                                    && msg.localInitiatedSend == true) {
-                                msg.localInitiatedSend = false; // Stop tracking changes for this message
-                                evt = new Event(EVENT_TYPE_SENDING_SUCCESS, id,
-                                    folderSms[type], null, TYPE.MMS);
-                                sendEvent(evt);
+                                String address = BluetoothMapContent.getAddressMms(
+                                        mResolver,id,BluetoothMapContent.MMS_FROM);
+                                String priority = "no";
+                                if(tmpPri == PduHeaders.PRIORITY_HIGH)
+                                    priority = "yes";
+
+                                /* Incoming message from the network */
+                                if (mMapEventReportVersion ==
+                                        BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+                                    evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type),
+                                            TYPE.MMS, date, subject, address, priority);
+                                } else {
+                                    evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type),
+                                            TYPE.MMS, date, subject, address, priority,
+                                            (long)threadId, null);
+                                }
+
+                            } else {
+                                /* Incoming message from the network */
+                                evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type),
+                                        null, TYPE.MMS);
                             }
-                        } else if(threadId != msg.threadId) {
-                            Log.d(TAG, "Message delete change: type: " + type + " old type: " + msg.type
-                                    + "\n    threadId: " + threadId + " old threadId: " + msg.threadId);
-                            if(threadId == DELETED_THREAD_ID) { // Message deleted
-                                Event evt = new Event(EVENT_TYPE_DELETE, id, BluetoothMapContract.FOLDER_NAME_DELETED,
-                                    folderMms[msg.type], TYPE.MMS);
-                                sendEvent(evt);
-                                msg.threadId = threadId;
-                            } else { // Undelete
-                                Event evt = new Event(EVENT_TYPE_SHIFT, id, folderMms[msg.type],
-                                    BluetoothMapContract.FOLDER_NAME_DELETED, TYPE.MMS);
-                                sendEvent(evt);
-                                msg.threadId = threadId;
+
+                            sendEvent(evt);
+                        } else {
+                            /* Existing message */
+                            if (type != msg.type) {
+                                Log.d(TAG, "new type: " + type + " old type: " + msg.type);
+                                Event evt;
+                                listChanged = true;
+                                if(msg.localInitiatedSend == false) {
+                                    // Only send events about local initiated changes
+                                    evt = new Event(EVENT_TYPE_SHIFT, id, getMmsFolderName(type),
+                                            getMmsFolderName(msg.type), TYPE.MMS);
+                                    sendEvent(evt);
+                                }
+                                msg.type = type;
+
+                                if (getMmsFolderName(type).equalsIgnoreCase(
+                                        BluetoothMapContract.FOLDER_NAME_SENT)
+                                        && msg.localInitiatedSend == true) {
+                                    // Stop tracking changes for this message
+                                    msg.localInitiatedSend = false;
+                                    evt = new Event(EVENT_TYPE_SENDING_SUCCESS, id,
+                                            getMmsFolderName(type), null, TYPE.MMS);
+                                    sendEvent(evt);
+                                }
+                            } else if(threadId != msg.threadId) {
+                                Log.d(TAG, "Message delete change: type: " + type + " old type: "
+                                        + msg.type
+                                        + "\n    threadId: " + threadId + " old threadId: "
+                                        + msg.threadId);
+                                listChanged = true;
+                                if(threadId == DELETED_THREAD_ID) { // Message deleted
+                                    Event evt = new Event(EVENT_TYPE_DELETE, id,
+                                            BluetoothMapContract.FOLDER_NAME_DELETED,
+                                            getMmsFolderName(msg.type), TYPE.MMS);
+                                    sendEvent(evt);
+                                    msg.threadId = threadId;
+                                } else { // Undelete
+                                    Event evt = new Event(EVENT_TYPE_SHIFT, id,
+                                            getMmsFolderName(msg.type),
+                                            BluetoothMapContract.FOLDER_NAME_DELETED, TYPE.MMS);
+                                    sendEvent(evt);
+                                    msg.threadId = threadId;
+                                }
                             }
+                            if(read != msg.flagRead) {
+                                listChanged = true;
+                                msg.flagRead = read;
+                                if (mMapEventReportVersion >
+                                        BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+                                    Event evt = new Event(EVENT_TYPE_READ_STATUS, id,
+                                            getMmsFolderName(msg.type), TYPE.MMS);
+                                    sendEvent(evt);
+                                }
+                            }
+                            msgListMms.put(id, msg);
                         }
-                        msgListMms.put(id, msg);
-                    }
+                    } while (c.moveToNext());
+
                 }
             } finally {
-                close(c);
+                if (c != null) c.close();
             }
-
-            for (Msg msg : mMsgListMms.values()) {
+            for (Msg msg : getMsgListMms().values()) {
                 Event evt = new Event(EVENT_TYPE_DELETE, msg.id,
-                                        BluetoothMapContract.FOLDER_NAME_DELETED,
-                                        folderMms[msg.type], TYPE.MMS);
+                        BluetoothMapContract.FOLDER_NAME_DELETED,
+                        getMmsFolderName(msg.type), TYPE.MMS);
                 sendEvent(evt);
+                listChanged = true;
             }
-            mMsgListMms = msgListMms;
+            setMsgListMms(msgListMms, listChanged);
         }
     }
 
-    private void handleMsgListChangesEmail(Uri uri)  throws RemoteException{
-        if (V) Log.v(TAG, "handleMsgListChangesEmail uri: " + uri.toString());
+    private void handleMsgListChangesMsg(Uri uri)  throws RemoteException{
+        if (V) Log.v(TAG, "handleMsgListChangesMsg uri: " + uri.toString());
 
         // TODO: Change observer to handle accountId and message ID if present
 
-        HashMap<Long, Msg> msgListEmail = new HashMap<Long, Msg>();
-
-        Cursor c = mProviderClient.query(mMessageUri, EMAIL_PROJECTION_SHORT, null, null, null);
-
-        synchronized(mMsgListEmail) {
+        HashMap<Long, Msg> msgList = new HashMap<Long, Msg>();
+        Cursor c;
+        boolean listChanged = false;
+        if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+            c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT, null, null, null);
+        } else if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+            c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT_EXT, null, null, null);
+        } else {
+            c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT_EXT2, null, null, null);
+        }
+        synchronized(getMsgListMsg()) {
             try {
-                while (c != null && c.moveToNext()) {
-                    long id = c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns._ID));
-                    int folderId = c.getInt(c.getColumnIndex(
-                            BluetoothMapContract.MessageColumns.FOLDER_ID));
-                    Msg msg = mMsgListEmail.remove(id);
-                    BluetoothMapFolderElement folderElement = mFolders.getEmailFolderById(folderId);
-                    String newFolder;
-                    if(folderElement != null) {
-                        newFolder = folderElement.getFullPath();
-                    } else {
-                        newFolder = "unknown"; // This can happen if a new folder is created while connected
-                    }
-
-                    /* We must filter out any actions made by the MCE, hence do not send e.g. a message
-                     * deleted and/or MessageShift for messages deleted by the MCE. */
-
-                    if (msg == null) {
-                        /* New message */
-                        msg = new Msg(id, folderId);
-                        msgListEmail.put(id, msg);
-                        Event evt = new Event(EVENT_TYPE_NEW, id, newFolder,
-                            null, TYPE.EMAIL);
-                        sendEvent(evt);
-                    } else {
-                        /* Existing message */
-                        if (folderId != msg.folderId) {
-                            if (D) Log.d(TAG, "new folderId: " + folderId + " old folderId: " + msg.folderId);
-                            BluetoothMapFolderElement oldFolderElement = mFolders.getEmailFolderById(msg.folderId);
-                            String oldFolder;
-                            if(oldFolderElement != null) {
-                                oldFolder = oldFolderElement.getFullPath();
-                            } else {
-                                // This can happen if a new folder is created while connected
-                                oldFolder = "unknown";
-                            }
-                            BluetoothMapFolderElement deletedFolder =
-                                    mFolders.getEmailFolderByName(BluetoothMapContract.FOLDER_NAME_DELETED);
-                            BluetoothMapFolderElement sentFolder =
-                                    mFolders.getEmailFolderByName(BluetoothMapContract.FOLDER_NAME_SENT);
-                            /*
-                             *  If the folder is now 'deleted', send a deleted-event in stead of a shift
-                             *  or if message is sent initiated by MAP Client, then send sending-success
-                             *  otherwise send folderShift
-                             */
-                            if(deletedFolder != null && deletedFolder.getEmailFolderId() == folderId) {
-                                Event evt = new Event(EVENT_TYPE_DELETE, msg.id, newFolder,
-                                        oldFolder, TYPE.EMAIL);
-                                sendEvent(evt);
-                            } else if(sentFolder != null
-                                      && sentFolder.getEmailFolderId() == folderId
-                                      && msg.localInitiatedSend == true) {
-                                if(msg.transparent) {
-                                    mResolver.delete(ContentUris.withAppendedId(mMessageUri, id), null, null);
+                if (c != null && c.moveToFirst()) {
+                    do {
+                        long id = c.getLong(c.getColumnIndex(
+                                BluetoothMapContract.MessageColumns._ID));
+                        int folderId = c.getInt(c.getColumnIndex(
+                                BluetoothMapContract.MessageColumns.FOLDER_ID));
+                        int readFlag = c.getInt(c.getColumnIndex(
+                                BluetoothMapContract.MessageColumns.FLAG_READ));
+                        Msg msg = getMsgListMsg().remove(id);
+                        BluetoothMapFolderElement folderElement = mFolders.getFolderById(folderId);
+                        String newFolder;
+                        if(folderElement != null) {
+                            newFolder = folderElement.getFullPath();
+                        } else {
+                            // This can happen if a new folder is created while connected
+                            newFolder = "unknown";
+                        }
+                        /* We must filter out any actions made by the MCE, hence do not send e.g.
+                         * a message deleted and/or MessageShift for messages deleted by the MCE. */
+                        if (msg == null) {
+                            listChanged = true;
+                            /* New message - created with message unread */
+                            msg = new Msg(id, folderId, 0, readFlag);
+                            msgList.put(id, msg);
+                            Event evt;
+                            /* Incoming message from the network */
+                            if (mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+                                String date = BluetoothMapUtils.getDateTimeString(
+                                        c.getLong(c.getColumnIndex(
+                                                BluetoothMapContract.MessageColumns.DATE)));
+                                String subject = c.getString(c.getColumnIndex(
+                                        BluetoothMapContract.MessageColumns.SUBJECT));
+                                String address = c.getString(c.getColumnIndex(
+                                        BluetoothMapContract.MessageColumns.FROM_LIST));
+                                String priority = "no";
+                                if(c.getInt(c.getColumnIndex(
+                                        BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY))
+                                        == 1)
+                                    priority = "yes";
+                                if (mMapEventReportVersion ==
+                                        BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+                                    evt = new Event(EVENT_TYPE_NEW, id, newFolder,
+                                            mAccount.getType(), date, subject, address, priority);
                                 } else {
-                                    msg.localInitiatedSend = false;
-                                    Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id,
-                                                          oldFolder, null, TYPE.EMAIL);
-                                    sendEvent(evt);
+                                    long thread_id = c.getLong(c.getColumnIndex(
+                                            BluetoothMapContract.MessageColumns.THREAD_ID));
+                                    String thread_name = c.getString(c.getColumnIndex(
+                                            BluetoothMapContract.MessageColumns.THREAD_NAME));
+                                    evt = new Event(EVENT_TYPE_NEW, id, newFolder,
+                                            mAccount.getType(), date, subject, address, priority,
+                                            thread_id, thread_name);
                                 }
                             } else {
-                                Event evt = new Event(EVENT_TYPE_SHIFT, id, newFolder,
-                                                      oldFolder, TYPE.EMAIL);
-                                sendEvent(evt);
+                                evt = new Event(EVENT_TYPE_NEW, id, newFolder, null, TYPE.EMAIL);
                             }
-                            msg.folderId = folderId;
+                            sendEvent(evt);
+                        } else {
+                            /* Existing message */
+                            if (folderId != msg.folderId && msg.folderId != -1) {
+                                if (D) Log.d(TAG, "new folderId: " + folderId + " old folderId: "
+                                        + msg.folderId);
+                                BluetoothMapFolderElement oldFolderElement =
+                                        mFolders.getFolderById(msg.folderId);
+                                String oldFolder;
+                                listChanged = true;
+                                if(oldFolderElement != null) {
+                                    oldFolder = oldFolderElement.getFullPath();
+                                } else {
+                                    // This can happen if a new folder is created while connected
+                                    oldFolder = "unknown";
+                                }
+                                BluetoothMapFolderElement deletedFolder =
+                                        mFolders.getFolderByName(
+                                                BluetoothMapContract.FOLDER_NAME_DELETED);
+                                BluetoothMapFolderElement sentFolder =
+                                        mFolders.getFolderByName(
+                                                BluetoothMapContract.FOLDER_NAME_SENT);
+                                /*
+                                 *  If the folder is now 'deleted', send a deleted-event in stead of
+                                 *  a shift or if message is sent initiated by MAP Client, then send
+                                 *  sending-success otherwise send folderShift
+                                 */
+                                if(deletedFolder != null && deletedFolder.getFolderId()
+                                        == folderId) {
+                                    Event evt = new Event(EVENT_TYPE_DELETE, msg.id, newFolder,
+                                            oldFolder, mAccount.getType());
+                                    sendEvent(evt);
+                                } else if(sentFolder != null
+                                        && sentFolder.getFolderId() == folderId
+                                        && msg.localInitiatedSend == true) {
+                                    if(msg.transparent) {
+                                        mResolver.delete(
+                                                ContentUris.withAppendedId(mMessageUri, id),
+                                                null, null);
+                                    } else {
+                                        msg.localInitiatedSend = false;
+                                        Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id,
+                                                oldFolder, null, mAccount.getType());
+                                        sendEvent(evt);
+                                    }
+                                } else {
+                                    if (!oldFolder.equalsIgnoreCase("root")) {
+                                        Event evt = new Event(EVENT_TYPE_SHIFT, id, newFolder,
+                                                oldFolder, mAccount.getType());
+                                        sendEvent(evt);
+                                    }
+                                }
+                                msg.folderId = folderId;
+                            }
+                            if(readFlag != msg.flagRead) {
+                                listChanged = true;
+
+                                if (mMapEventReportVersion >
+                                BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+                                    Event evt = new Event(EVENT_TYPE_READ_STATUS, id, newFolder,
+                                            mAccount.getType());
+                                    sendEvent(evt);
+                                    msg.flagRead = readFlag;
+                                }
+                            }
+
+                            msgList.put(id, msg);
                         }
-                        msgListEmail.put(id, msg);
-                    }
+                    } while (c.moveToNext());
                 }
             } finally {
-                close(c);
+                if (c != null) c.close();
             }
-
             // For all messages no longer in the database send a delete notification
-            for (Msg msg : mMsgListEmail.values()) {
-                BluetoothMapFolderElement oldFolderElement = mFolders.getEmailFolderById(msg.folderId);
+            for (Msg msg : getMsgListMsg().values()) {
+                BluetoothMapFolderElement oldFolderElement = mFolders.getFolderById(msg.folderId);
                 String oldFolder;
+                listChanged = true;
                 if(oldFolderElement != null) {
                     oldFolder = oldFolderElement.getFullPath();
                 } else {
                     oldFolder = "unknown";
                 }
-                /* Some e-mail clients delete the message after sending, and creates a new message in sent.
-                 * We cannot track the message anymore, hence send both a send success and delete message.
+                /* Some e-mail clients delete the message after sending, and creates a
+                 * new message in sent. We cannot track the message anymore, hence send both a
+                 * send success and delete message.
                  */
                 if(msg.localInitiatedSend == true) {
                     msg.localInitiatedSend = false;
                     // If message is send with transparency don't set folder as message is deleted
                     if (msg.transparent)
                         oldFolder = null;
-                    Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id, oldFolder, null, TYPE.EMAIL);
+                    Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id, oldFolder, null,
+                            mAccount.getType());
                     sendEvent(evt);
                 }
                 /* As this message deleted is only send on a real delete - don't set folder.
@@ -796,29 +1720,253 @@
                  */
                 if (!msg.transparent) {
 
-                    Event evt = new Event(EVENT_TYPE_DELETE, msg.id, null, oldFolder, TYPE.EMAIL);
+                    Event evt = new Event(EVENT_TYPE_DELETE, msg.id, null, oldFolder,
+                            mAccount.getType());
                     sendEvent(evt);
                 }
             }
-            mMsgListEmail = msgListEmail;
+            setMsgListMsg(msgList, listChanged);
         }
     }
 
     private void handleMsgListChanges(Uri uri) {
         if(uri.getAuthority().equals(mAuthority)) {
             try {
-                handleMsgListChangesEmail(uri);
-            }catch(RemoteException e){
+                if(D) Log.d(TAG, "handleMsgListChanges: account type = "
+                        + mAccount.getType().toString());
+                handleMsgListChangesMsg(uri);
+            } catch(RemoteException e) {
                 mMasInstance.restartObexServerSession();
-                Log.w(TAG, "Problems contacting the ContentProvider in mas Instance "+mMasId+" restaring ObexServerSession");
+                Log.w(TAG, "Problems contacting the ContentProvider in mas Instance "
+                        + mMasId + " restaring ObexServerSession");
             }
 
-        } else {
+        }
+        // TODO: check to see if there could be problem with IM and SMS in one instance
+        if (mEnableSmsMms) {
             handleMsgListChangesSms();
             handleMsgListChangesMms();
         }
     }
 
+    private void handleContactListChanges(Uri uri) {
+        if (uri.getAuthority().equals(mAuthority)) {
+            try {
+                if (V) Log.v(TAG,"handleContactListChanges uri: " + uri.toString());
+                Cursor c = null;
+                boolean listChanged = false;
+                try {
+                    ConvoContactInfo cInfo = new ConvoContactInfo();
+
+                    if (mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V10
+                            && mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+                        c = mProviderClient
+                                .query(mContactUri,
+                                        BluetoothMapContract.
+                                        BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION,
+                                        null, null, null);
+                        cInfo.setConvoColunms(c);
+                    } else {
+                        if (V) Log.v(TAG,"handleContactListChanges MAP version does not" +
+                                "support convocontact notifications");
+                        return;
+                    }
+
+                    HashMap<String, BluetoothMapConvoContactElement> contactList =
+                            new HashMap<String,
+                            BluetoothMapConvoContactElement>(getContactList().size());
+
+                    synchronized (getContactList()) {
+                        if (c != null && c.moveToFirst()) {
+                            do {
+                                String uci = c.getString(cInfo.mContactColUci);
+                                long convoId = c.getLong(cInfo.mContactColConvoId);
+                                if (convoId == 0)
+                                    continue;
+
+                                if (V) BluetoothMapUtils.printCursor(c);
+
+                                BluetoothMapConvoContactElement contact =
+                                        getContactList().remove(uci);
+
+                                /*
+                                 * We must filter out any actions made by the
+                                 * MCE, hence do not send e.g. a message deleted
+                                 * and/or MessageShift for messages deleted by
+                                 * the MCE.
+                                 */
+                                if (contact == null) {
+                                    listChanged = true;
+                                    /*
+                                     * New contact - added to conversation and
+                                     * tracked here
+                                     */
+                                    if (mMapEventReportVersion
+                                            != BluetoothMapUtils.MAP_EVENT_REPORT_V10
+                                            && mMapEventReportVersion
+                                            != BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+                                        Event evt;
+                                        String name = c
+                                                .getString(cInfo.mContactColName);
+                                        String displayName = c
+                                                .getString(cInfo.mContactColNickname);
+                                        String presenceStatus = c
+                                                .getString(cInfo.mContactColPresenceText);
+                                        int presenceState = c
+                                                .getInt(cInfo.mContactColPresenceState);
+                                        long lastActivity = c
+                                                .getLong(cInfo.mContactColLastActive);
+                                        int chatState = c
+                                                .getInt(cInfo.mContactColChatState);
+                                        int priority = c
+                                                .getInt(cInfo.mContactColPriority);
+                                        String btUid = c
+                                                .getString(cInfo.mContactColBtUid);
+
+                                        // Get Conversation information for
+                                        // event
+//                                        Uri convoUri = Uri
+//                                                .parse(mAccount.mBase_uri
+//                                                        + "/"
+//                                                        + BluetoothMapContract.TABLE_CONVERSATION);
+//                                        String whereClause = "contacts._id = "
+//                                                + convoId;
+//                                        Cursor cConvo = mProviderClient
+//                                                .query(convoUri,
+//                                                       BluetoothMapContract.BT_CONVERSATION_PROJECTION,
+//                                                       whereClause, null, null);
+                                        // TODO: will move out of the loop when merged with CB's
+                                        // changes make sure to look up col index out side loop
+                                        String convoName = null;
+//                                        if (cConvo != null
+//                                                && cConvo.moveToFirst()) {
+//                                            convoName = cConvo
+//                                                    .getString(cConvo
+//                                                            .getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME));
+//                                        }
+
+                                        contact = new BluetoothMapConvoContactElement(
+                                                uci, name, displayName,
+                                                presenceStatus, presenceState,
+                                                lastActivity, chatState,
+                                                priority, btUid);
+
+                                        contactList.put(uci, contact);
+
+                                        evt = new Event(
+                                                EVENT_TYPE_CONVERSATION,
+                                                uci,
+                                                mAccount.getType(),
+                                                name,
+                                                String.valueOf(priority),
+                                                BluetoothMapUtils
+                                                .getDateTimeString(lastActivity),
+                                                convoId, convoName,
+                                                presenceState, presenceStatus,
+                                                chatState);
+
+                                        sendEvent(evt);
+                                    }
+
+                                } else {
+                                    // Not new - compare updated content
+//                                    Uri convoUri = Uri
+//                                            .parse(mAccount.mBase_uri
+//                                                    + "/"
+//                                                    + BluetoothMapContract.TABLE_CONVERSATION);
+                                    // TODO: Should be changed to own provider interface name
+//                                    String whereClause = "contacts._id = "
+//                                            + convoId;
+//                                    Cursor cConvo = mProviderClient
+//                                            .query(convoUri,
+//                                                    BluetoothMapContract.BT_CONVERSATION_PROJECTION,
+//                                                    whereClause, null, null);
+//                                    // TODO: will move out of the loop when merged with CB's
+//                                    // changes make sure to look up col index out side loop
+                                    String convoName = null;
+//                                    if (cConvo != null && cConvo.moveToFirst()) {
+//                                        convoName = cConvo
+//                                                .getString(cConvo
+//                                                        .getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME));
+//                                    }
+
+                                    // Check if presence is updated
+                                    int presenceState = c.getInt(cInfo.mContactColPresenceState);
+                                    String presenceStatus = c.getString(
+                                            cInfo.mContactColPresenceText);
+                                    String currentPresenceStatus = contact
+                                            .getPresenceStatus();
+                                    if (contact.getPresenceAvailability() != presenceState
+                                            || currentPresenceStatus != presenceStatus) {
+                                        long lastOnline = c
+                                                .getLong(cInfo.mContactColLastOnline);
+                                        contact.setPresenceAvailability(presenceState);
+                                        contact.setLastActivity(lastOnline);
+                                        if (currentPresenceStatus != null
+                                                && !currentPresenceStatus
+                                                .equals(presenceStatus)) {
+                                            contact.setPresenceStatus(presenceStatus);
+                                        }
+                                        Event evt = new Event(
+                                                EVENT_TYPE_PRESENCE,
+                                                uci,
+                                                mAccount.getType(),
+                                                contact.getName(),
+                                                String.valueOf(contact
+                                                        .getPriority()),
+                                                        BluetoothMapUtils
+                                                        .getDateTimeString(lastOnline),
+                                                        convoId, convoName,
+                                                        presenceState, presenceStatus,
+                                                        0);
+                                        sendEvent(evt);
+                                    }
+
+                                    // Check if chat state is updated
+                                    int chatState = c.getInt(cInfo.mContactColChatState);
+                                    if (contact.getChatState() != chatState) {
+                                        // Get DB timestamp
+                                        long lastActivity = c.getLong(cInfo.mContactColLastActive);
+                                        contact.setLastActivity(lastActivity);
+                                        contact.setChatState(chatState);
+                                        Event evt = new Event(
+                                                EVENT_TYPE_CHAT_STATE,
+                                                uci,
+                                                mAccount.getType(),
+                                                contact.getName(),
+                                                String.valueOf(contact
+                                                        .getPriority()),
+                                                        BluetoothMapUtils
+                                                        .getDateTimeString(lastActivity),
+                                                        convoId, convoName, 0, null,
+                                                        chatState);
+                                        sendEvent(evt);
+                                    }
+                                    contactList.put(uci, contact);
+                                }
+                            } while (c.moveToNext());
+                        }
+                        if(getContactList().size() > 0) {
+                            // one or more contacts were deleted, hence the conversation listing
+                            // version counter should change.
+                            listChanged = true;
+                        }
+                        setContactList(contactList, listChanged);
+                    } // end synchronized
+                } finally {
+                    if (c != null) c.close();
+                }
+            } catch (RemoteException e) {
+                mMasInstance.restartObexServerSession();
+                Log.w(TAG,
+                        "Problems contacting the ContentProvider in mas Instance "
+                                + mMasId + " restaring ObexServerSession");
+            }
+
+        }
+        // TODO: conversation contact updates if IM and SMS(MMS in one instance
+    }
+
     private boolean setEmailMessageStatusDelete(BluetoothMapFolderElement mCurrentFolder,
             String uriStr, long handle, int status) {
         boolean res = false;
@@ -827,15 +1975,15 @@
         int updateCount = 0;
         ContentValues contentValues = new ContentValues();
         BluetoothMapFolderElement deleteFolder = mFolders.
-                getEmailFolderByName(BluetoothMapContract.FOLDER_NAME_DELETED);
+                getFolderByName(BluetoothMapContract.FOLDER_NAME_DELETED);
         contentValues.put(BluetoothMapContract.MessageColumns._ID, handle);
-        synchronized(mMsgListEmail) {
-            Msg msg = mMsgListEmail.get(handle);
+        synchronized(getMsgListMsg()) {
+            Msg msg = getMsgListMsg().get(handle);
             if (status == BluetoothMapAppParams.STATUS_VALUE_YES) {
                 /* Set deleted folder id */
                 long folderId = -1;
                 if(deleteFolder != null) {
-                    folderId = deleteFolder.getEmailFolderId();
+                    folderId = deleteFolder.getFolderId();
                 }
                 contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID,folderId);
                 updateCount = mResolver.update(uri, contentValues, null, null);
@@ -845,7 +1993,8 @@
                     res = true;
                     if (msg != null) {
                         msg.oldFolderId = msg.folderId;
-                        // Update the folder ID to avoid triggering an event for MCE initiated actions.
+                        /* Update the folder ID to avoid triggering an event for MCE
+                         * initiated actions. */
                         msg.folderId = folderId;
                     }
                     if(D) Log.d(TAG, "Deleted MSG: " + handle + " from folderId: " + folderId);
@@ -857,27 +2006,38 @@
                 /* Undelete message. move to old folder if we know it,
                  * else move to inbox - as dictated by the spec. */
                 if(msg != null && deleteFolder != null &&
-                        msg.folderId == deleteFolder.getEmailFolderId()) {
+                        msg.folderId == deleteFolder.getFolderId()) {
                     /* Only modify messages in the 'Deleted' folder */
                     long folderId = -1;
+                    BluetoothMapFolderElement inboxFolder = mCurrentFolder.
+                            getFolderByName(BluetoothMapContract.FOLDER_NAME_INBOX);
                     if (msg != null && msg.oldFolderId != -1) {
                         folderId = msg.oldFolderId;
                     } else {
-                        BluetoothMapFolderElement inboxFolder = mCurrentFolder.
-                                getEmailFolderByName(BluetoothMapContract.FOLDER_NAME_INBOX);
                         if(inboxFolder != null) {
-                            folderId = inboxFolder.getEmailFolderId();
+                            folderId = inboxFolder.getFolderId();
                         }
-                        if(D)Log.d(TAG,"We did not delete the message, hence the old folder is unknown. Moving to inbox.");
+                        if(D)Log.d(TAG,"We did not delete the message, hence the old folder " +
+                                "is unknown. Moving to inbox.");
                     }
                     contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId);
                     updateCount = mResolver.update(uri, contentValues, null, null);
                     if(updateCount > 0) {
                         res = true;
-                        // Update the folder ID to avoid triggering an event for MCE initiated actions.
-                        msg.folderId = folderId;
+                        /* Update the folder ID to avoid triggering an event for MCE
+                         * initiated actions. */
+                        /* UPDATE: Actually the BT-Spec. states that an undelete is a move of the
+                         * message to INBOX - clearified in errata 5591.
+                         * Therefore we update the cache to INBOX-folderId - to trigger a message
+                         * shift event to the old-folder. */
+                        if(inboxFolder != null) {
+                            msg.folderId = inboxFolder.getFolderId();
+                        } else {
+                            msg.folderId = folderId;
+                        }
                     } else {
-                        if(D)Log.d(TAG,"We did not delete the message, hence the old folder is unknown. Moving to inbox.");
+                        if(D)Log.d(TAG,"We did not delete the message, hence the old folder " +
+                                "is unknown. Moving to inbox.");
                     }
                 }
             }
@@ -885,7 +2045,7 @@
                 BluetoothMapFolderElement folderElement;
                 String folderName = "unknown";
                 if (msg != null) {
-                    folderElement = mCurrentFolder.getEmailFolderById(msg.folderId);
+                    folderElement = mCurrentFolder.getFolderById(msg.folderId);
                     if(folderElement != null) {
                         folderName = folderElement.getName();
                     }
@@ -910,15 +2070,14 @@
         boolean res = false;
         Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
         Cursor c = mResolver.query(uri, null, null, null, null);
-
         try {
             if (c != null && c.moveToFirst()) {
                 /* Move to deleted folder, or delete if already in deleted folder */
                 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
                 if (threadId != DELETED_THREAD_ID) {
                     /* Set deleted thread id */
-                    synchronized(mMsgListMms) {
-                        Msg msg = mMsgListMms.get(handle);
+                    synchronized(getMsgListMms()) {
+                        Msg msg = getMsgListMms().get(handle);
                         if(msg != null) { // This will always be the case
                             msg.threadId = DELETED_THREAD_ID;
                         }
@@ -926,8 +2085,8 @@
                     updateThreadId(uri, Mms.THREAD_ID, DELETED_THREAD_ID);
                 } else {
                     /* Delete from observer message list to avoid delete notifications */
-                    synchronized(mMsgListMms) {
-                        mMsgListMms.remove(handle);
+                    synchronized(getMsgListMms()) {
+                        getMsgListMms().remove(handle);
                     }
                     /* Delete message */
                     mResolver.delete(uri, null, null);
@@ -935,7 +2094,7 @@
                 res = true;
             }
         } finally {
-            close(c);
+            if (c != null) c.close();
         }
 
         return res;
@@ -945,43 +2104,48 @@
         boolean res = false;
         Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
         Cursor c = mResolver.query(uri, null, null, null, null);
-
         try {
             if (c != null && c.moveToFirst()) {
                 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
                 if (threadId == DELETED_THREAD_ID) {
                     /* Restore thread id from address, or if no thread for address
-                    * create new thread by insert and remove of fake message */
+                     * create new thread by insert and remove of fake message */
                     String address;
                     long id = c.getLong(c.getColumnIndex(Mms._ID));
                     int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
                     if (msgBox == Mms.MESSAGE_BOX_INBOX) {
                         address = BluetoothMapContent.getAddressMms(mResolver, id,
-                            BluetoothMapContent.MMS_FROM);
+                                BluetoothMapContent.MMS_FROM);
                     } else {
                         address = BluetoothMapContent.getAddressMms(mResolver, id,
-                            BluetoothMapContent.MMS_TO);
+                                BluetoothMapContent.MMS_TO);
                     }
                     Set<String> recipients = new HashSet<String>();
                     recipients.addAll(Arrays.asList(address));
                     Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients);
-                    synchronized(mMsgListMms) {
-                        Msg msg = mMsgListMms.get(handle);
+                    synchronized(getMsgListMms()) {
+                        Msg msg = getMsgListMms().get(handle);
                         if(msg != null) { // This will always be the case
                             msg.threadId = oldThreadId.intValue();
+                            // Spec. states that undelete shall shift the message to Inbox.
+                            // Hence we need to trigger a message shift from INBOX to old-folder
+                            // after undelete.
+                            // We do this by changing the cached folder value to being inbox - hence
+                            // the event handler will se the update as the message have been shifted
+                            // from INBOX to old-folder. (Errata 5591 clearifies this)
+                            msg.type = Mms.MESSAGE_BOX_INBOX;
                         }
                     }
                     updateThreadId(uri, Mms.THREAD_ID, oldThreadId);
                 } else {
                     Log.d(TAG, "Message not in deleted folder: handle " + handle
-                        + " threadId " + threadId);
+                            + " threadId " + threadId);
                 }
                 res = true;
             }
         } finally {
-            close(c);
+            if (c != null) c.close();
         }
-
         return res;
     }
 
@@ -989,14 +2153,13 @@
         boolean res = false;
         Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
         Cursor c = mResolver.query(uri, null, null, null, null);
-
         try {
             if (c != null && c.moveToFirst()) {
                 /* Move to deleted folder, or delete if already in deleted folder */
                 int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
                 if (threadId != DELETED_THREAD_ID) {
-                    synchronized(mMsgListSms) {
-                        Msg msg = mMsgListSms.get(handle);
+                    synchronized(getMsgListSms()) {
+                        Msg msg = getMsgListSms().get(handle);
                         if(msg != null) { // This will always be the case
                             msg.threadId = DELETED_THREAD_ID;
                         }
@@ -1005,8 +2168,8 @@
                     updateThreadId(uri, Sms.THREAD_ID, DELETED_THREAD_ID);
                 } else {
                     /* Delete from observer message list to avoid delete notifications */
-                    synchronized(mMsgListSms) {
-                        mMsgListSms.remove(handle);
+                    synchronized(getMsgListSms()) {
+                        getMsgListSms().remove(handle);
                     }
                     /* Delete message */
                     mResolver.delete(uri, null, null);
@@ -1014,9 +2177,8 @@
                 res = true;
             }
         } finally {
-            close(c);
+            if (c != null) c.close();
         }
-
         return res;
     }
 
@@ -1024,7 +2186,6 @@
         boolean res = false;
         Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
         Cursor c = mResolver.query(uri, null, null, null, null);
-
         try {
             if (c != null && c.moveToFirst()) {
                 int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
@@ -1033,34 +2194,58 @@
                     Set<String> recipients = new HashSet<String>();
                     recipients.addAll(Arrays.asList(address));
                     Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients);
-                    synchronized(mMsgListSms) {
-                        Msg msg = mMsgListSms.get(handle);
-                        if(msg != null) { // This will always be the case
-                            msg.threadId = oldThreadId.intValue(); // The threadId is specified as an int, so it is safe to truncate
+                    synchronized(getMsgListSms()) {
+                        Msg msg = getMsgListSms().get(handle);
+                        if(msg != null) {
+                            msg.threadId = oldThreadId.intValue();
+                            /* This will always be the case
+                             * The threadId is specified as an int, so it is safe to truncate
+                             * TODO: Test that this will trigger a message-shift from Inbox
+                             * to old-folder
+                             **/
+                            /* Spec. states that undelete shall shift the message to Inbox.
+                             * Hence we need to trigger a message shift from INBOX to old-folder
+                             * after undelete.
+                             * We do this by changing the cached folder value to being inbox - hence
+                             * the event handler will se the update as the message have been shifted
+                             * from INBOX to old-folder. (Errata 5591 clearifies this)
+                             * */
+                            msg.type = Sms.MESSAGE_TYPE_INBOX;
                         }
                     }
                     updateThreadId(uri, Sms.THREAD_ID, oldThreadId);
                 } else {
                     Log.d(TAG, "Message not in deleted folder: handle " + handle
-                        + " threadId " + threadId);
+                            + " threadId " + threadId);
                 }
                 res = true;
             }
         } finally {
-            close(c);
+            if (c != null) c.close();
         }
-
         return res;
     }
 
+    /**
+     *
+     * @param handle
+     * @param type
+     * @param mCurrentFolder
+     * @param uriStr
+     * @param statusValue
+     * @return true is success
+     */
     public boolean setMessageStatusDeleted(long handle, TYPE type,
             BluetoothMapFolderElement mCurrentFolder, String uriStr, int statusValue) {
         boolean res = false;
         if (D) Log.d(TAG, "setMessageStatusDeleted: handle " + handle
-            + " type " + type + " value " + statusValue);
+                + " type " + type + " value " + statusValue);
 
         if (type == TYPE.EMAIL) {
             res = setEmailMessageStatusDelete(mCurrentFolder, uriStr, handle, statusValue);
+        } else if (type == TYPE.IM) {
+            // TODO: to do when deleting IM message
+            if (D) Log.d(TAG, "setMessageStatusDeleted: IM not handled" );
         } else {
             if (statusValue == BluetoothMapAppParams.STATUS_VALUE_YES) {
                 if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
@@ -1076,7 +2261,6 @@
                 }
             }
         }
-
         return res;
     }
 
@@ -1088,23 +2272,30 @@
      * @param statusValue
      * @return true at success
      */
-    public boolean setMessageStatusRead(long handle, TYPE type, String uriStr, int statusValue) throws RemoteException{
+    public boolean setMessageStatusRead(long handle, TYPE type, String uriStr, int statusValue)
+            throws RemoteException{
         int count = 0;
 
         if (D) Log.d(TAG, "setMessageStatusRead: handle " + handle
-            + " type " + type + " value " + statusValue);
+                + " type " + type + " value " + statusValue);
 
-        /* Approved MAP spec errata 3445 states that read status initiated */
-        /* by the MCE shall change the MSE read status. */
-
+        /* Approved MAP spec errata 3445 states that read status initiated
+         * by the MCE shall change the MSE read status. */
         if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
-            Uri uri = Sms.Inbox.CONTENT_URI;//ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
+            Uri uri = Sms.Inbox.CONTENT_URI;
             ContentValues contentValues = new ContentValues();
             contentValues.put(Sms.READ, statusValue);
             contentValues.put(Sms.SEEN, statusValue);
             String where = Sms._ID+"="+handle;
             String values = contentValues.toString();
-            if (D) Log.d(TAG, " -> SMS Uri: " + uri.toString() + " Where " + where + " values " + values);
+            if (D) Log.d(TAG, " -> SMS Uri: " + uri.toString() +
+                    " Where " + where + " values " + values);
+            synchronized(getMsgListSms()) {
+                Msg msg = getMsgListSms().get(handle);
+                if(msg != null) { // This will always be the case
+                    msg.flagRead = statusValue;
+                }
+            }
             count = mResolver.update(uri, contentValues, where, null);
             if (D) Log.d(TAG, " -> "+count +" rows updated!");
 
@@ -1113,14 +2304,26 @@
             if (D) Log.d(TAG, " -> MMS Uri: " + uri.toString());
             ContentValues contentValues = new ContentValues();
             contentValues.put(Mms.READ, statusValue);
+            synchronized(getMsgListMms()) {
+                Msg msg = getMsgListMms().get(handle);
+                if(msg != null) { // This will always be the case
+                    msg.flagRead = statusValue;
+                }
+            }
             count = mResolver.update(uri, contentValues, null, null);
             if (D) Log.d(TAG, " -> "+count +" rows updated!");
-
-        } if (type == TYPE.EMAIL) {
+        } else if (type == TYPE.EMAIL ||
+                type == TYPE.IM) {
             Uri uri = mMessageUri;
             ContentValues contentValues = new ContentValues();
             contentValues.put(BluetoothMapContract.MessageColumns.FLAG_READ, statusValue);
             contentValues.put(BluetoothMapContract.MessageColumns._ID, handle);
+            synchronized(getMsgListMsg()) {
+                Msg msg = getMsgListMsg().get(handle);
+                if(msg != null) { // This will always be the case
+                    msg.flagRead = statusValue;
+                }
+            }
             count = mProviderClient.update(uri, contentValues, null, null);
         }
 
@@ -1143,7 +2346,7 @@
         int statusDelivered; // Set to != 0 if a single part deliver fail is received.
 
         public PushMsgInfo(long id, int transparent,
-            int retry, String phone, Uri uri) {
+                int retry, String phone, Uri uri) {
             this.id = id;
             this.transparent = transparent;
             this.retry = retry;
@@ -1158,7 +2361,7 @@
     }
 
     private Map<Long, PushMsgInfo> mPushMsgList =
-        Collections.synchronizedMap(new HashMap<Long, PushMsgInfo>());
+            Collections.synchronizedMap(new HashMap<Long, PushMsgInfo>());
 
     public long pushMessage(BluetoothMapbMessage msg, BluetoothMapFolderElement folderElement,
             BluetoothMapAppParams ap, String emailBaseUri)
@@ -1193,12 +2396,12 @@
             ParcelFileDescriptor fdOut = null;
             Uri uriInsert = Uri.parse(emailBaseUri + BluetoothMapContract.TABLE_MESSAGE);
             if (D) Log.d(TAG, "pushMessage - uriInsert= " + uriInsert.toString() +
-                    ", intoFolder id=" + folderElement.getEmailFolderId());
+                    ", intoFolder id=" + folderElement.getFolderId());
 
-            synchronized(mMsgListEmail) {
+            synchronized(getMsgListMsg()) {
                 // Now insert the empty message into folder
                 ContentValues values = new ContentValues();
-                folderId = folderElement.getEmailFolderId();
+                folderId = folderElement.getFolderId();
                 values.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId);
                 Uri uriNew = mProviderClient.insert(uriInsert, values);
                 if (D) Log.d(TAG, "pushMessage - uriNew= " + uriNew.toString());
@@ -1222,23 +2425,25 @@
                     } catch (IOException e) {Log.w(TAG, e);}
                     try {
                         if(fdOut != null)
-                             fdOut.close();
+                            fdOut.close();
                     } catch (IOException e) {Log.w(TAG, e);}
                 }
 
                 /* Extract the data for the inserted message, and store in local mirror, to
                  * avoid sending a NewMessage Event. */
-                Msg newMsg = new Msg(handle, folderId);
+                /*TODO: We need to add the new 1.1 parameter as well:-) e.g. read*/
+                Msg newMsg = new Msg(handle, folderId, 1); // TODO: Create define for read-state
                 newMsg.transparent = (transparent == 1) ? true : false;
-                if ( folderId == folderElement.getEmailFolderByName(
-                        BluetoothMapContract.FOLDER_NAME_OUTBOX).getEmailFolderId() ) {
+                if ( folderId == folderElement.getFolderByName(
+                        BluetoothMapContract.FOLDER_NAME_OUTBOX).getFolderId() ) {
                     newMsg.localInitiatedSend = true;
                 }
-                mMsgListEmail.put(handle, newMsg);
+                getMsgListMsg().put(handle, newMsg);
             }
         } else { // type SMS_* of MMS
             for (BluetoothMapbMessage.vCard recipient : recipientList) {
-                if(recipient.getEnvLevel() == 0) // Only send the message to the top level recipient
+                // Only send the message to the top level recipient
+                if(recipient.getEnvLevel() == 0)
                 {
                     /* Only send to first address */
                     String phone = recipient.getFirstPhoneNumber();
@@ -1252,16 +2457,18 @@
                      * then convert the MMS to type SMS and then proceed
                      */
                     if (msg.getType().equals(TYPE.MMS) &&
-                            (((BluetoothMapbMessageMms) msg).getTextOnly() == true)) {
-                        msgBody = ((BluetoothMapbMessageMms) msg).getMessageAsText();
+                            (((BluetoothMapbMessageMime) msg).getTextOnly() == true)) {
+                        msgBody = ((BluetoothMapbMessageMime) msg).getMessageAsText();
                         SmsManager smsMng = SmsManager.getDefault();
                         ArrayList<String> parts = smsMng.divideMessage(msgBody);
                         int smsParts = parts.size();
                         if (smsParts  <= CONVERT_MMS_TO_SMS_PART_COUNT ) {
-                            if (D) Log.d(TAG, "pushMessage - converting MMS to SMS, sms parts=" + smsParts );
+                            if (D) Log.d(TAG, "pushMessage - converting MMS to SMS, sms parts="
+                                    + smsParts );
                             msg.setType(mSmsType);
                         } else {
-                            if (D) Log.d(TAG, "pushMessage - MMS text only but to big to convert to SMS");
+                            if (D) Log.d(TAG, "pushMessage - MMS text only but to big to " +
+                                    "convert to SMS");
                             msgBody = null;
                         }
 
@@ -1269,55 +2476,66 @@
 
                     if (msg.getType().equals(TYPE.MMS)) {
                         /* Send message if folder is outbox else just store in draft*/
-                        handle = sendMmsMessage(folder, phone, (BluetoothMapbMessageMms)msg);
+                        handle = sendMmsMessage(folder, phone, (BluetoothMapbMessageMime)msg);
                     } else if (msg.getType().equals(TYPE.SMS_GSM) ||
                             msg.getType().equals(TYPE.SMS_CDMA) ) {
                         /* Add the message to the database */
                         if(msgBody == null)
                             msgBody = ((BluetoothMapbMessageSms) msg).getSmsBody();
 
-                        /* We need to lock the SMS list while updating the database, to avoid sending
-                         * events on MCE initiated operation. */
+                        /* We need to lock the SMS list while updating the database,
+                         * to avoid sending events on MCE initiated operation. */
                         Uri contentUri = Uri.parse(Sms.CONTENT_URI+ "/" + folder);
                         Uri uri;
-                        synchronized(mMsgListSms) {
+                        synchronized(getMsgListSms()) {
                             uri = Sms.addMessageToUri(mResolver, contentUri, phone, msgBody,
-                                "", System.currentTimeMillis(), read, deliveryReport);
+                                    "", System.currentTimeMillis(), read, deliveryReport);
 
                             if(V) Log.v(TAG, "Sms.addMessageToUri() returned: " + uri);
                             if (uri == null) {
-                                if (D) Log.d(TAG, "pushMessage - failure on add to uri " + contentUri);
+                                if (D) Log.d(TAG, "pushMessage - failure on add to uri "
+                                        + contentUri);
                                 return -1;
                             }
                             Cursor c = mResolver.query(uri, SMS_PROJECTION_SHORT, null, null, null);
+
+                            /* Extract the data for the inserted message, and store in local mirror,
+                             * to avoid sending a NewMessage Event. */
                             try {
-                                /* Extract the data for the inserted message, and store in local mirror, to
-                                * avoid sending a NewMessage Event. */
                                 if (c != null && c.moveToFirst()) {
                                     long id = c.getLong(c.getColumnIndex(Sms._ID));
                                     int type = c.getInt(c.getColumnIndex(Sms.TYPE));
                                     int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
-                                    Msg newMsg = new Msg(id, type, threadId);
-                                    mMsgListSms.put(id, newMsg);
+                                    int readFlag = c.getInt(c.getColumnIndex(Sms.READ));
+                                    if(V) Log.v(TAG, "add message with id=" + id +
+                                            " type=" + type + " threadId=" + threadId +
+                                            " readFlag=" + readFlag + "to mMsgListSms");
+                                    Msg newMsg = new Msg(id, type, threadId, readFlag);
+                                    getMsgListSms().put(id, newMsg);
+                                    c.close();
                                 } else {
-                                    return -1; // This can only happen, if the message is deleted just as it is added
+                                    Log.w(TAG,"Message: " + uri + " no longer exist!");
+                                    /* This can only happen, if the message is deleted
+                                     * just as it is added */
+                                    return -1;
                                 }
                             } finally {
-                                close(c);
+                                if (c != null) c.close();
                             }
 
                             handle = Long.parseLong(uri.getLastPathSegment());
 
                             /* Send message if folder is outbox */
-                            if (folder.equals(BluetoothMapContract.FOLDER_NAME_OUTBOX)) {
+                            if (folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)) {
                                 PushMsgInfo msgInfo = new PushMsgInfo(handle, transparent,
-                                    retry, phone, uri);
+                                        retry, phone, uri);
                                 mPushMsgList.put(handle, msgInfo);
                                 sendMessage(msgInfo, msgBody);
                                 if(V) Log.v(TAG, "sendMessage returned...");
-                            }
-                            /* sendMessage causes the message to be deleted and reinserted, hence we need to lock
-                             * the list while this is happening. */
+                            } /* else just added to draft */
+
+                            /* sendMessage causes the message to be deleted and reinserted,
+                             * hence we need to lock the list while this is happening. */
                         }
                     } else {
                         if (D) Log.d(TAG, "pushMessage - failure on type " );
@@ -1331,7 +2549,7 @@
         return handle;
     }
 
-    public long sendMmsMessage(String folder, String to_address, BluetoothMapbMessageMms msg) {
+    public long sendMmsMessage(String folder, String to_address, BluetoothMapbMessageMime msg) {
         /*
          *strategy:
          *1) parse message into parts
@@ -1346,8 +2564,10 @@
         if (folder != null && (folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)
                 ||  folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_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(BluetoothMapContract.FOLDER_NAME_OUTBOX)) {
+            /* if invalid handle (-1) then just return the handle
+             * - else continue sending (if folder is outbox) */
+            if (BluetoothMapAppParams.INVALID_VALUE_PARAMETER != handle &&
+                    folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)) {
                 moveDraftToOutbox(handle);
                 Intent sendIntent = new Intent("android.intent.action.MMS_SEND_OUTBOX_MSG");
                 if (D) Log.d(TAG, "broadcasting intent: "+sendIntent.toString());
@@ -1356,33 +2576,36 @@
             return handle;
         } else {
             /* not allowed to push mms to anything but outbox/draft */
-            throw  new IllegalArgumentException("Cannot push message to other folders than outbox/draft");
+            throw  new IllegalArgumentException("Cannot push message to other " +
+                    "folders than outbox/draft");
         }
     }
 
     private void moveDraftToOutbox(long handle) {
         /*Move message by changing the msg_box value in the content provider database */
-        if (handle != -1) return;
-
-        String whereClause = " _id= " + handle;
-        Uri uri = Mms.CONTENT_URI;
-        Cursor queryResult = mResolver.query(uri, null, whereClause, null, null);
-        try {
-            if (queryResult != null && queryResult.moveToFirst()) {
-                ContentValues data = new ContentValues();
-                /* set folder to be outbox */
-                data.put(Mms.MESSAGE_BOX, Mms.MESSAGE_BOX_OUTBOX);
-                mResolver.update(uri, data, whereClause, null);
-                if (D) Log.d(TAG, "Moved draft MMS to outbox");
-            } else {
-                if (D) Log.d(TAG, "Could not move draft to outbox ");
+        if (handle != -1) {
+            String whereClause = " _id= " + handle;
+            Uri uri = Mms.CONTENT_URI;
+            Cursor queryResult = mResolver.query(uri, null, whereClause, null, null);
+            try {
+                if (queryResult != null) {
+                    if (queryResult.getCount() > 0) {
+                        queryResult.moveToFirst();
+                        ContentValues data = new ContentValues();
+                        /* set folder to be outbox */
+                        data.put(Mms.MESSAGE_BOX, Mms.MESSAGE_BOX_OUTBOX);
+                        mResolver.update(uri, data, whereClause, null);
+                        if (D) Log.d(TAG, "moved draft MMS to outbox");
+                    }
+                } else {
+                    if (D) Log.d(TAG, "Could not move draft to outbox ");
+                }
+            } finally {
+                if (queryResult != null) queryResult.close();
             }
-        } finally {
-            queryResult.close();
         }
     }
-
-    private long pushMmsToFolder(int folder, String to_address, BluetoothMapbMessageMms msg) {
+    private long pushMmsToFolder(int folder, String to_address, BluetoothMapbMessageMime msg) {
         /**
          * strategy:
          * 1) parse msg into parts + header
@@ -1424,7 +2647,8 @@
         values.put(Mms.THREAD_ID, Telephony.Threads.getOrCreateThreadId(mContext, recipients));
         Uri uri = Mms.CONTENT_URI;
 
-        synchronized (mMsgListMms) {
+        synchronized (getMsgListMms()) {
+
             uri = mResolver.insert(uri, values);
 
             if (uri == null) {
@@ -1447,10 +2671,11 @@
 
                     Msg newMsg = new Msg(id, type, threadId);
                     newMsg.localInitiatedSend = true;
-                    mMsgListMms.put(id, newMsg);
+                    getMsgListMms().put(id, newMsg);
+                    c.close();
                 }
             } finally {
-                close(c);
+                if (c != null) c.close();
             }
         } // Done adding changes, unlock access to mMsgListMms to allow sending MMS events again
 
@@ -1459,15 +2684,18 @@
 
         try {
             if(msg.getMimeParts() == null) {
-                /* Perhaps this message have been deleted, and no longer have any content, but only headers */
+                /* Perhaps this message have been deleted, and no longer have any content,
+                 * but only headers */
                 Log.w(TAG, "No MMS parts present...");
             } else {
-                if(V) Log.v(TAG, "Adding " + msg.getMimeParts().size() + " parts to the data base.");
-                int count = 0;
+                if(V) Log.v(TAG, "Adding " + msg.getMimeParts().size()
+                        + " parts to the data base.");
                 for(MimePart part : msg.getMimeParts()) {
-                    ++count;
+                    int count = 0;
+                    count++;
                     values.clear();
-                    if(part.mContentType != null &&  part.mContentType.toUpperCase().contains("TEXT")) {
+                    if(part.mContentType != null &&
+                            part.mContentType.toUpperCase().contains("TEXT")) {
                         values.put(Mms.Part.CONTENT_TYPE, "text/plain");
                         values.put(Mms.Part.CHARSET, 106);
                         if(part.mPartName != null) {
@@ -1506,8 +2734,8 @@
                         uri = mResolver.insert(uri, values);
                         if(V) Log.v(TAG, "Added TEXT part");
 
-                    } else if (part.mContentType != null &&  part.mContentType.toUpperCase().contains("SMIL")){
-
+                    } else if (part.mContentType != null &&
+                            part.mContentType.toUpperCase().contains("SMIL")){
                         values.put(Mms.Part.SEQ, -1);
                         values.put(Mms.Part.CONTENT_TYPE, "application/smil");
                         if(part.mContentId != null) {
@@ -1536,7 +2764,8 @@
                         if (V) Log.v(TAG, "Added OTHER part");
                     }
                     if (uri != null){
-                        if (V) Log.v(TAG, "Added part with content-type: "+ part.mContentType + " to Uri: " + uri.toString());
+                        if (V) Log.v(TAG, "Added part with content-type: " + part.mContentType
+                                + " to Uri: " + uri.toString());
                     }
                 }
             }
@@ -1676,13 +2905,13 @@
         Log.d(TAG, "sendMessage to " + msgInfo.phone);
 
         smsMng.sendMultipartTextMessage(msgInfo.phone, null, parts, sentIntents,
-            deliveryIntents);
+                deliveryIntents);
     }
 
     private static final String ACTION_MESSAGE_DELIVERY =
-        "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY";
+            "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY";
     public static final String ACTION_MESSAGE_SENT =
-        "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT";
+            "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT";
 
     public static final String EXTRA_MESSAGE_SENT_HANDLE = "HANDLE";
     public static final String EXTRA_MESSAGE_SENT_RESULT = "result";
@@ -1738,10 +2967,13 @@
             }
 
             if (action.equals(ACTION_MESSAGE_SENT)) {
-                int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT, Activity.RESULT_CANCELED);
+                int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT,
+                        Activity.RESULT_CANCELED);
                 msgInfo.partsSent++;
                 if(result != Activity.RESULT_OK) {
-                    // If just one of the parts in the message fails, we need to send the entire message again
+                    /* If just one of the parts in the message fails, we need to send the
+                     * entire message again
+                     */
                     msgInfo.failedSent = true;
                 }
                 if(D) Log.d(TAG, "onReceive: msgInfo.partsSent = " + msgInfo.partsSent
@@ -1766,6 +2998,7 @@
                     status = message.getStatus();
                     if(status != 0/*0 is success*/) {
                         msgInfo.statusDelivered = status;
+                        if(D) Log.d(TAG, "msgInfo.statusDelivered = " + status);
                     }
                 }
                 if (msgInfo.partsDelivered == msgInfo.parts) {
@@ -1798,7 +3031,7 @@
                 }
 
                 Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msgInfo.id,
-                    folderSms[Sms.MESSAGE_TYPE_SENT], null, mSmsType);
+                        getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType);
                 sendEvent(evt);
 
             } else {
@@ -1808,7 +3041,7 @@
                     msgInfo.partsSent = 0; // Reset counter for the retry
                     msgInfo.failedSent = false;
                     Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id,
-                        folderSms[Sms.MESSAGE_TYPE_OUTBOX], null, mSmsType);
+                            getSmsFolderName(Sms.MESSAGE_TYPE_OUTBOX), null, mSmsType);
                     sendEvent(evt);
                 } else {
                     if (msgInfo.transparent == 0) {
@@ -1821,15 +3054,15 @@
                     }
 
                     Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id,
-                        folderSms[Sms.MESSAGE_TYPE_FAILED], null, mSmsType);
+                            getSmsFolderName(Sms.MESSAGE_TYPE_FAILED), null, mSmsType);
                     sendEvent(evt);
                 }
             }
 
             if (delete == true) {
                 /* Delete from Observer message list to avoid delete notifications */
-                synchronized(mMsgListSms) {
-                    mMsgListSms.remove(msgInfo.id);
+                synchronized(getMsgListSms()) {
+                    getMsgListSms().remove(msgInfo.id);
                 }
 
                 /* Delete from DB */
@@ -1849,7 +3082,8 @@
 
                     Uri updateUri = ContentUris.withAppendedId(UPDATE_STATUS_URI, messageId);
 
-                    if(D) Log.d(TAG, "actionMessageDelivery: uri=" + messageUri + ", status=" + msgInfo.statusDelivered);
+                    if(D) Log.d(TAG, "actionMessageDelivery: uri=" + messageUri + ", status="
+                            + msgInfo.statusDelivered);
 
                     ContentValues contentValues = new ContentValues(2);
 
@@ -1860,16 +3094,16 @@
                     Log.d(TAG, "Can't find message for status update: " + messageUri);
                 }
             } finally {
-                cursor.close();
+                if (cursor != null) cursor.close();
             }
 
             if (msgInfo.statusDelivered == 0) {
                 Event evt = new Event(EVENT_TYPE_DELEVERY_SUCCESS, msgInfo.id,
-                    folderSms[Sms.MESSAGE_TYPE_SENT], null, mSmsType);
+                        getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType);
                 sendEvent(evt);
             } else {
-                Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id,
-                    folderSms[Sms.MESSAGE_TYPE_SENT], null, mSmsType);
+                Event evt = new Event(EVENT_TYPE_DELIVERY_FAILURE, msgInfo.id,
+                        getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType);
                 sendEvent(evt);
             }
 
@@ -1901,7 +3135,7 @@
         } else {
             /*if (retry == 1) {
                  The retry feature only works while connected, else we fail the send,
-                 * and move the message to failed, to let the user/app resend manually later.
+             * and move the message to failed, to let the user/app resend manually later.
             } else */{
                 if (transparent == 0) {
                     if (!Sms.moveMessageToFolder(context, uri,
@@ -1926,56 +3160,65 @@
     }
 
     private void registerPhoneServiceStateListener() {
-        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(
+                Context.TELEPHONY_SERVICE);
         tm.listen(mPhoneListener, PhoneStateListener.LISTEN_SERVICE_STATE);
     }
 
     private void unRegisterPhoneServiceStateListener() {
-        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(
+                Context.TELEPHONY_SERVICE);
         tm.listen(mPhoneListener, PhoneStateListener.LISTEN_NONE);
     }
 
     private void resendPendingMessages() {
         /* Send pending messages in outbox */
         String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX;
-        Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, null);
-
+        Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
+                null);
         try {
-            while (c!= null && c.moveToNext()) {
-                long id = c.getLong(c.getColumnIndex(Sms._ID));
-                String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
-                PushMsgInfo msgInfo = mPushMsgList.get(id);
-                if (msgInfo == null || msgInfo.resend == false || msgInfo.sendInProgress == true) {
-                    continue;
-                }
-                msgInfo.sendInProgress = true;
-                sendMessage(msgInfo, msgBody);
+            if (c != null && c.moveToFirst()) {
+                do {
+                    long id = c.getLong(c.getColumnIndex(Sms._ID));
+                    String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
+                    PushMsgInfo msgInfo = mPushMsgList.get(id);
+                    if (msgInfo == null || msgInfo.resend == false ||
+                            msgInfo.sendInProgress == true) {
+                        continue;
+                    }
+                    msgInfo.sendInProgress = true;
+                    sendMessage(msgInfo, msgBody);
+                } while (c.moveToNext());
             }
         } finally {
-            close(c);
+            if (c != null) c.close();
         }
+
+
     }
 
     private void failPendingMessages() {
         /* Move pending messages from outbox to failed */
         String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX;
-        Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, null);
-        if (c == null) return;
-
+        Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
+                null);
         try {
-            while (c!= null && c.moveToNext()) {
-                long id = c.getLong(c.getColumnIndex(Sms._ID));
-                String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
-                PushMsgInfo msgInfo = mPushMsgList.get(id);
-                if (msgInfo == null || msgInfo.resend == false) {
-                    continue;
-                }
-                Sms.moveMessageToFolder(mContext, msgInfo.uri,
-                    Sms.MESSAGE_TYPE_FAILED, 0);
+            if (c != null && c.moveToFirst()) {
+                do {
+                    long id = c.getLong(c.getColumnIndex(Sms._ID));
+                    String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
+                    PushMsgInfo msgInfo = mPushMsgList.get(id);
+                    if (msgInfo == null || msgInfo.resend == false) {
+                        continue;
+                    }
+                    Sms.moveMessageToFolder(mContext, msgInfo.uri,
+                            Sms.MESSAGE_TYPE_FAILED, 0);
+                } while (c.moveToNext());
             }
         } finally {
-            close(c);
+            if (c != null) c.close();
         }
+
     }
 
     private void removeDeletedMessages() {
diff --git a/src/com/android/bluetooth/map/BluetoothMapConvoContactElement.java b/src/com/android/bluetooth/map/BluetoothMapConvoContactElement.java
new file mode 100644
index 0000000..8ee9728
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapConvoContactElement.java
@@ -0,0 +1,348 @@
+/*
+* Copyright (C) 2015 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.IOException;
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+
+import android.util.Log;
+
+import com.android.bluetooth.SignedLongLong;
+
+public class BluetoothMapConvoContactElement
+    implements Comparable<BluetoothMapConvoContactElement> {
+
+    public static final long CONTACT_ID_TYPE_SMS_MMS = 1;
+    public static final long CONTACT_ID_TYPE_EMAIL   = 2;
+    public static final long CONTACT_ID_TYPE_IM      = 3;
+
+    private static final String XML_ATT_PRIORITY = "priority";
+    private static final String XML_ATT_PRESENCE_STATUS = "presence_status";
+    private static final String XML_ATT_PRESENCE_AVAILABILITY = "presence_availability";
+    private static final String XML_ATT_X_BT_UID = "x_bt_uid";
+    private static final String XML_ATT_LAST_ACTIVITY = "last_activity";
+    private static final String XML_ATT_CHAT_STATE = "chat_state";
+    private static final String XML_ATT_NAME = "name";
+    private static final String XML_ATT_DISPLAY_NAME = "display_name";
+    private static final String XML_ATT_UCI = "x_bt_uci";
+    protected static final String XML_TAG_CONVOCONTACT = "convocontact";
+    private static final String TAG = "BluetoothMapConvoContactElement";
+    private static final boolean D = false;
+    private static final boolean V = false;
+
+    private String mUci = null;
+    private String mName = null;
+    private String mDisplayName = null;
+    private String mPresenceStatus = null;
+    private int mPresenceAvailability = -1;
+    private int mPriority = -1;
+    private long mLastActivity = -1;
+    private SignedLongLong mBtUid = null;
+    private int mChatState = -1;
+
+    public static BluetoothMapConvoContactElement createFromMapContact(MapContact contact,
+            String address) {
+        BluetoothMapConvoContactElement newElement = new BluetoothMapConvoContactElement();
+        newElement.mUci = address;
+        // TODO: For now we use the ID as BT-UID
+        newElement.mBtUid = new SignedLongLong(contact.getId(),0);
+        newElement.mDisplayName = contact.getName();
+        return newElement;
+    }
+
+    public BluetoothMapConvoContactElement(String uci, String name, String displayName,
+            String presenceStatus, int presenceAvailability, long lastActivity, int chatState,
+            int priority, String btUid) {
+        this.mUci = uci;
+        this.mName = name;
+        this.mDisplayName = displayName;
+        this.mPresenceStatus = presenceStatus;
+        this.mPresenceAvailability = presenceAvailability;
+        this.mLastActivity = lastActivity;
+        this.mChatState = chatState;
+        this.mPresenceStatus = presenceStatus;
+        this.mPriority = priority;
+        if(btUid != null) {
+            try {
+                this.mBtUid = SignedLongLong.fromString(btUid);
+            } catch (UnsupportedEncodingException e) {
+                Log.w(TAG,e);
+            }
+        }
+    }
+
+    public BluetoothMapConvoContactElement() {
+        // TODO Auto-generated constructor stub
+    }
+
+    public String getPresenceStatus() {
+        return mPresenceStatus;
+    }
+
+    public String getDisplayName() {
+        return mDisplayName;
+    }
+
+    public void setDisplayName(String displayName) {
+        this.mDisplayName = displayName;
+    }
+
+    public void setPresenceStatus(String presenceStatus) {
+        this.mPresenceStatus = presenceStatus;
+    }
+
+    public int getPresenceAvailability() {
+        return mPresenceAvailability;
+    }
+
+    public void setPresenceAvailability(int presenceAvailability) {
+        this.mPresenceAvailability = presenceAvailability;
+    }
+
+    public int getPriority() {
+        return mPriority;
+    }
+
+    public void setPriority(int priority) {
+        this.mPriority = priority;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void setName(String name) {
+        this.mName = name;
+    }
+
+    public String getBtUid() {
+        return mBtUid.toHexString();
+    }
+
+    public void setBtUid(SignedLongLong btUid) {
+        this.mBtUid = btUid;
+    }
+
+    public int getChatState() {
+        return mChatState;
+    }
+
+    public void setChatState(int chatState) {
+        this.mChatState = chatState;
+    }
+
+    public void setChatState(String chatState) {
+        this.mChatState = Integer.valueOf(chatState);
+    }
+
+
+    public String getLastActivityString() {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Date date = new Date(mLastActivity);
+        return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+    }
+
+    public void setLastActivity(long dateTime) {
+        this.mLastActivity = dateTime;
+    }
+
+    public void setLastActivity(String lastActivity) throws ParseException {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Date date = format.parse(lastActivity);
+        this.mLastActivity = date.getTime();
+    }
+
+    public void setContactId(String uci) {
+        this.mUci = uci;
+    }
+
+    public String getContactId(){
+        return mUci;
+    }
+
+    public int compareTo(BluetoothMapConvoContactElement e) {
+        if (this.mLastActivity < e.mLastActivity) {
+            return 1;
+        } else if (this.mLastActivity > e.mLastActivity) {
+            return -1;
+        } else {
+            return 0;
+        }
+    }
+
+    /* Encode the MapConvoContactElement into the StringBuilder reference.
+     * Here we have taken the choice not to report empty attributes, to reduce the
+     * amount of data to be transfered over BT. */
+    public void encode(XmlSerializer xmlConvoElement)
+            throws IllegalArgumentException, IllegalStateException, IOException
+    {
+            // construct the XML tag for a single contact in the convolisting element.
+            xmlConvoElement.startTag(null, XML_TAG_CONVOCONTACT);
+            if(mUci != null) {
+                xmlConvoElement.attribute(null, XML_ATT_UCI, mUci);
+            }
+            if(mDisplayName != null) {
+                xmlConvoElement.attribute(null, XML_ATT_DISPLAY_NAME,
+                            BluetoothMapUtils.stripInvalidChars(mDisplayName));
+            }
+            if(mName != null) {
+                xmlConvoElement.attribute(null, XML_ATT_NAME,
+                        BluetoothMapUtils.stripInvalidChars(mName));
+            }
+            if(mChatState != -1) {
+                xmlConvoElement.attribute(null, XML_ATT_CHAT_STATE, String.valueOf(mChatState));
+            }
+            if(mLastActivity != -1) {
+                xmlConvoElement.attribute(null, XML_ATT_LAST_ACTIVITY,
+                        this.getLastActivityString());
+            }
+            if(mBtUid != null) {
+                xmlConvoElement.attribute(null, XML_ATT_X_BT_UID, mBtUid.toHexString());
+            }
+            if(mPresenceAvailability != -1) {
+                xmlConvoElement.attribute(null,  XML_ATT_PRESENCE_AVAILABILITY,
+                        String.valueOf(mPresenceAvailability));
+            }
+            if(mPresenceStatus != null) {
+                xmlConvoElement.attribute(null,  XML_ATT_PRESENCE_STATUS, mPresenceStatus);
+            }
+            if(mPriority != -1) {
+                xmlConvoElement.attribute(null, XML_ATT_PRIORITY, String.valueOf(mPriority));
+            }
+
+            xmlConvoElement.endTag(null, XML_TAG_CONVOCONTACT);
+    }
+
+
+    /**
+     * Call this function to create a BluetoothMapConvoContactElement. Will consume the end-tag.
+     * @param parser must point into XML_TAG_CONVERSATION tag, hence attributes can be read.
+     * @return
+     * @throws IOException
+     * @throws XmlPullParserException
+     */
+    public static BluetoothMapConvoContactElement createFromXml(XmlPullParser parser)
+            throws ParseException, XmlPullParserException, IOException {
+        int count = parser.getAttributeCount();
+        BluetoothMapConvoContactElement newElement;
+        if(count<1) {
+            throw new IllegalArgumentException(XML_TAG_CONVOCONTACT +
+                    " is not decorated with attributes");
+        }
+        newElement = new BluetoothMapConvoContactElement();
+        for (int i = 0; i<count; i++) {
+            String attributeName = parser.getAttributeName(i).trim();
+            String attributeValue = parser.getAttributeValue(i);
+            if(attributeName.equalsIgnoreCase(XML_ATT_UCI)) {
+                newElement.mUci = attributeValue;
+            } else if(attributeName.equalsIgnoreCase(XML_ATT_NAME)) {
+                newElement.mName = attributeValue;
+            } else if(attributeName.equalsIgnoreCase(XML_ATT_DISPLAY_NAME)) {
+                newElement.mDisplayName = attributeValue;
+            } else if(attributeName.equalsIgnoreCase(XML_ATT_CHAT_STATE)) {
+                newElement.setChatState(attributeValue);
+            } else if(attributeName.equalsIgnoreCase(XML_ATT_LAST_ACTIVITY)) {
+                newElement.setLastActivity(attributeValue);
+            } else if(attributeName.equalsIgnoreCase(XML_ATT_X_BT_UID)) {
+                newElement.setBtUid(SignedLongLong.fromString(attributeValue));
+            } else if(attributeName.equalsIgnoreCase(XML_ATT_PRESENCE_AVAILABILITY)) {
+                newElement.mPresenceAvailability = Integer.parseInt(attributeValue);
+            } else if(attributeName.equalsIgnoreCase(XML_ATT_PRESENCE_STATUS)) {
+                newElement.setPresenceStatus(attributeValue);
+            } else if(attributeName.equalsIgnoreCase(XML_ATT_PRIORITY)) {
+                newElement.setPriority(Integer.parseInt(attributeValue));
+            } else {
+                if(D) Log.i(TAG,"Unknown XML attribute: " + parser.getAttributeName(i));
+            }
+        }
+        parser.nextTag(); // Consume the end-tag
+        return newElement;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        BluetoothMapConvoContactElement other = (BluetoothMapConvoContactElement) obj;
+/*      As we use equals only for test, we don't compare auto assigned values
+ *      if (mBtUid == null) {
+            if (other.mBtUid != null) {
+                return false;
+            }
+        } else if (!mBtUid.equals(other.mBtUid)) {
+            return false;
+        }*/
+        if (mChatState != other.mChatState) {
+            return false;
+        }
+        if (mDisplayName == null) {
+            if (other.mDisplayName != null) {
+                return false;
+            }
+        } else if (!mDisplayName.equals(other.mDisplayName)) {
+            return false;
+        }
+/*      As we use equals only for test, we don't compare auto assigned values
+ *      if (mId == null) {
+            if (other.mId != null) {
+                return false;
+            }
+        } else if (!mId.equals(other.mId)) {
+            return false;
+        }*/
+        if (mLastActivity != other.mLastActivity) {
+            return false;
+        }
+        if (mName == null) {
+            if (other.mName != null) {
+                return false;
+            }
+        } else if (!mName.equals(other.mName)) {
+            return false;
+        }
+        if (mPresenceAvailability != other.mPresenceAvailability) {
+            return false;
+        }
+        if (mPresenceStatus == null) {
+            if (other.mPresenceStatus != null) {
+                return false;
+            }
+        } else if (!mPresenceStatus.equals(other.mPresenceStatus)) {
+            return false;
+        }
+        if (mPriority != other.mPriority) {
+            return false;
+        }
+        return true;
+    }
+
+}
+
+
diff --git a/src/com/android/bluetooth/map/BluetoothMapConvoListing.java b/src/com/android/bluetooth/map/BluetoothMapConvoListing.java
new file mode 100644
index 0000000..cda95a6
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapConvoListing.java
@@ -0,0 +1,224 @@
+/*
+* Copyright (C) 2015 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.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+public class BluetoothMapConvoListing {
+    private boolean hasUnread = false;
+    private static final String TAG = "BluetoothMapConvoListing";
+    private static final boolean D = BluetoothMapService.DEBUG;
+    private static final String XML_TAG = "MAP-convo-listing";
+
+    private List<BluetoothMapConvoListingElement> mList;
+
+    public BluetoothMapConvoListing(){
+     mList = new ArrayList<BluetoothMapConvoListingElement>();
+    }
+    public void add(BluetoothMapConvoListingElement element) {
+        mList.add(element);
+        /* update info regarding whether the list contains unread conversations */
+        if (element.getReadBool())
+        {
+            hasUnread = true;
+        }
+    }
+
+    /**
+     * Used to fetch the number of BluetoothMapConvoListingElement elements in the list.
+     * @return the number of elements in the list.
+     */
+    public int getCount() {
+        if(mList != null)
+        {
+            return mList.size();
+        }
+        return 0;
+    }
+
+    /**
+     * does the list contain any unread messages
+     * @return true if unread messages have been added to the list, else false
+     */
+    public boolean hasUnread()
+    {
+        return hasUnread;
+    }
+
+
+    /**
+     *  returns the entire list as a list
+     * @return list
+     */
+    public List<BluetoothMapConvoListingElement> getList(){
+        return mList;
+    }
+
+    /**
+     * Encode the list of BluetoothMapMessageListingElement(s) into a UTF-8
+     * formatted XML-string in a trimmed byte array
+     *
+     * @return a reference to the encoded byte array.
+     * @throws UnsupportedEncodingException
+     *             if UTF-8 encoding is unsupported on the platform.
+     */
+    public byte[] encode() throws UnsupportedEncodingException {
+        StringWriter sw = new StringWriter();
+        XmlSerializer xmlConvoElement = new FastXmlSerializer();
+        try {
+            xmlConvoElement.setOutput(sw);
+            xmlConvoElement.startDocument("UTF-8", true);
+            xmlConvoElement.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
+                    true);
+            xmlConvoElement.startTag(null, XML_TAG);
+            xmlConvoElement.attribute(null, "version", "1.0");
+            // Do the XML encoding of list
+            for (BluetoothMapConvoListingElement element : mList) {
+                element.encode(xmlConvoElement); // Append the list element
+            }
+            xmlConvoElement.endTag(null, XML_TAG);
+            xmlConvoElement.endDocument();
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, e);
+        } catch (IllegalStateException e) {
+            Log.w(TAG, e);
+        } catch (IOException e) {
+            Log.w(TAG, e);
+        }
+        return sw.toString().getBytes("UTF-8");
+    }
+
+    public void sort() {
+        Collections.sort(mList);
+    }
+
+    public void segment(int count, int offset) {
+        count = Math.min(count, mList.size() - offset);
+        if (count > 0) {
+            mList = mList.subList(offset, offset + count);
+            if(mList == null) {
+                mList = new ArrayList<BluetoothMapConvoListingElement>(); // Return an empty list
+            }
+        } else {
+            if(offset > mList.size()) {
+               mList = new ArrayList<BluetoothMapConvoListingElement>();
+               Log.d(TAG, "offset greater than list size. Returning empty list");
+            } else {
+               mList = mList.subList(offset, mList.size());
+            }
+        }
+    }
+
+    public void appendFromXml(InputStream xmlDocument)
+            throws XmlPullParserException, IOException, ParseException {
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            int type;
+            parser.setInput(xmlDocument, "UTF-8");
+
+            // First find the folder-listing
+            while((type=parser.next()) != XmlPullParser.END_TAG
+                    && type != XmlPullParser.END_DOCUMENT ) {
+                // Skip until we get a start tag
+                if (parser.getEventType() != XmlPullParser.START_TAG) {
+                    continue;
+                }
+                // Skip until we get a folder-listing tag
+                String name = parser.getName();
+                if(!name.equalsIgnoreCase(XML_TAG)) {
+                    if(D) Log.i(TAG,"Unknown XML tag: " + name);
+                    XmlUtils.skipCurrentTag(parser);
+                }
+                readConversations(parser);
+            }
+        } finally {
+            xmlDocument.close();
+        }
+    }
+
+    /**
+     * Parses folder elements, and add to mSubFolders.
+     * @param parser the Xml Parser currently pointing to an folder-listing tag.
+     * @throws XmlPullParserException
+     * @throws IOException
+     * @throws
+     */
+    private void readConversations(XmlPullParser parser)
+            throws XmlPullParserException, IOException, ParseException {
+        int type;
+        if(D) Log.i(TAG,"readConversations(): ");
+        while((type=parser.next()) != XmlPullParser.END_TAG
+                && type != XmlPullParser.END_DOCUMENT ) {
+            // Skip until we get a start tag
+            if (parser.getEventType() != XmlPullParser.START_TAG) {
+                continue;
+            }
+            // Skip until we get a folder-listing tag
+            String name = parser.getName();
+            if(name.trim().equalsIgnoreCase(BluetoothMapConvoListingElement.XML_TAG_CONVERSATION)
+                    == false) {
+                if(D) Log.i(TAG,"Unknown XML tag: " + name);
+                XmlUtils.skipCurrentTag(parser);
+                continue;
+            }
+            // Add a single conversation
+            add(BluetoothMapConvoListingElement.createFromXml(parser));
+        }
+    }
+
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        BluetoothMapConvoListing other = (BluetoothMapConvoListing) obj;
+        if (hasUnread != other.hasUnread) {
+            return false;
+        }
+        if (mList == null) {
+            if (other.mList != null) {
+                return false;
+            }
+        } else if (!mList.equals(other.mList)) {
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java b/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java
new file mode 100644
index 0000000..4947aa4
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java
@@ -0,0 +1,390 @@
+/*
+* 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.IOException;
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.util.Log;
+
+import com.android.bluetooth.SignedLongLong;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.internal.util.XmlUtils;
+
+public class BluetoothMapConvoListingElement
+    implements Comparable<BluetoothMapConvoListingElement> {
+
+    public static final String XML_TAG_CONVERSATION = "conversation";
+    private static final String XML_ATT_LAST_ACTIVITY = "last_activity";
+    private static final String XML_ATT_NAME = "name";
+    private static final String XML_ATT_ID = "id";
+    private static final String XML_ATT_READ = "readstatus";
+    private static final String XML_ATT_VERSION_COUNTER = "version_counter";
+    private static final String XML_ATT_SUMMARY = "summary";
+    private static final String TAG = "BluetoothMapConvoListingElement";
+    private static final boolean D = BluetoothMapService.DEBUG;
+    private static final boolean V = BluetoothMapService.VERBOSE;
+
+    private SignedLongLong mId = null;
+    private String mName = ""; //title of the conversation #REQUIRED, but allowed empty
+    private long mLastActivity = -1;
+    private boolean mRead = false;
+    private boolean mReportRead = false; // TODO: Is this needed? - false means UNKNOWN
+    private List<BluetoothMapConvoContactElement> mContacts;
+    private long mVersionCounter = -1;
+    private int mCursorIndex = 0;
+    private TYPE mType = null;
+    private String mSummary = null;
+
+ // Used only to keep track of changes to convoListVersionCounter;
+    private String mSmsMmsContacts = null;
+
+    public int getCursorIndex() {
+        return mCursorIndex;
+    }
+
+    public void setCursorIndex(int cursorIndex) {
+        this.mCursorIndex = cursorIndex;
+        if(D) Log.d(TAG, "setCursorIndex: " + cursorIndex);
+    }
+
+    public long getVersionCounter(){
+        return mVersionCounter;
+    }
+
+    public void setVersionCounter(long vcount){
+        if(D) Log.d(TAG, "setVersionCounter: " + vcount);
+        this.mVersionCounter = vcount;
+    }
+
+    public void incrementVersionCounter() {
+        mVersionCounter++;
+    }
+
+    private void setVersionCounter(String vcount){
+        if(D) Log.d(TAG, "setVersionCounter: " + vcount);
+        try {
+            this.mVersionCounter = Long.parseLong(vcount);
+        } catch (NumberFormatException e) {
+            Log.w(TAG, "unable to parse XML versionCounter:" + vcount);
+            mVersionCounter = -1;
+        }
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void setName(String name) {
+        if(D) Log.d(TAG, "setName: " + name);
+        this.mName = name;
+    }
+
+    public TYPE getType() {
+        return mType;
+    }
+
+    public void setType(TYPE type) {
+        this.mType = type;
+    }
+
+    public List<BluetoothMapConvoContactElement> getContacts() {
+        return mContacts;
+    }
+
+    public void setContacts(List<BluetoothMapConvoContactElement> contacts) {
+        this.mContacts = contacts;
+    }
+
+    public void addContact(BluetoothMapConvoContactElement contact){
+        if(mContacts == null)
+            mContacts = new ArrayList<BluetoothMapConvoContactElement>();
+        mContacts.add(contact);
+    }
+
+    public void removeContact(BluetoothMapConvoContactElement contact){
+        mContacts.remove(contact);
+    }
+
+    public void removeContact(int index){
+        mContacts.remove(index);
+    }
+
+
+    public long getLastActivity() {
+        return mLastActivity;
+    }
+
+    public String getLastActivityString() {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Date date = new Date(mLastActivity);
+        return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+    }
+
+    public void setLastActivity(long last) {
+        if(D) Log.d(TAG, "setLastActivity: " + last);
+        this.mLastActivity = last;
+    }
+
+    public void setLastActivity(String lastActivity)throws ParseException {
+        // TODO: Encode with time-zone if MCE requests it
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Date date = format.parse(lastActivity);
+        this.mLastActivity = date.getTime();
+    }
+
+    public String getRead() {
+        if(mReportRead == false) {
+            return "UNKNOWN";
+        }
+        return (mRead?"READ":"UNREAD");
+    }
+
+    public boolean getReadBool() {
+        return mRead;
+    }
+
+    public void setRead(boolean read, boolean reportRead) {
+        this.mRead = read;
+        if(D) Log.d(TAG, "setRead: " + read);
+        this.mReportRead = reportRead;
+    }
+
+    private void setRead(String value) {
+        if(value.trim().equalsIgnoreCase("yes")) {
+            mRead = true;
+        } else {
+            mRead = false;
+        }
+        mReportRead = true;
+    }
+
+    /**
+     * Set the conversation ID
+     * @param type 0 if the thread ID is valid across all message types in the instance - else
+     * use one of the CONVO_ID_xxx types.
+     * @param threadId the conversation ID
+     */
+    public void setConvoId(long type, long threadId) {
+        this.mId = new SignedLongLong(threadId,type);
+        if(D) Log.d(TAG, "setConvoId: " + threadId + " type:" + type);
+    }
+
+    public String getConvoId(){
+        return mId.toHexString();
+    }
+
+    public long getCpConvoId() {
+        return mId.getLeastSignificantBits();
+    }
+
+    public void setSummary(String summary) {
+        mSummary = summary;
+    }
+
+    public String getFullSummary() {
+        return mSummary;
+    }
+
+    /* Get a valid UTF-8 string of maximum 256 bytes */
+    private String getSummary() {
+        if(mSummary != null) {
+            try {
+                return new String(BluetoothMapUtils.truncateUtf8StringToBytearray(mSummary, 256),
+                        "UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                // This cannot happen on an Android platform - UTF-8 is mandatory
+                Log.e(TAG,"Missing UTF-8 support on platform", e);
+            }
+        }
+        return null;
+    }
+
+    public String getSmsMmsContacts() {
+        return mSmsMmsContacts;
+    }
+
+    public void setSmsMmsContacts(String smsMmsContacts) {
+        mSmsMmsContacts = smsMmsContacts;
+    }
+
+    public int compareTo(BluetoothMapConvoListingElement e) {
+        if (this.mLastActivity < e.mLastActivity) {
+            return 1;
+        } else if (this.mLastActivity > e.mLastActivity) {
+            return -1;
+        } else {
+            return 0;
+        }
+    }
+
+    /* Encode the MapMessageListingElement into the StringBuilder reference.
+     * Here we have taken the choice not to report empty attributes, to reduce the
+     * amount of data to be transfered over BT. */
+    public void encode(XmlSerializer xmlConvoElement)
+            throws IllegalArgumentException, IllegalStateException, IOException
+    {
+
+            // contruct the XML tag for a single conversation in the convolisting
+            xmlConvoElement.startTag(null, XML_TAG_CONVERSATION);
+            xmlConvoElement.attribute(null, XML_ATT_ID, mId.toHexString());
+            if(mName != null) {
+                xmlConvoElement.attribute(null, XML_ATT_NAME,
+                        BluetoothMapUtils.stripInvalidChars(mName));
+            }
+            if(mLastActivity != -1) {
+                xmlConvoElement.attribute(null, XML_ATT_LAST_ACTIVITY,
+                        getLastActivityString());
+            }
+            // Even though this is implied, the value "UNKNOWN" kind of indicated it is required.
+            if(mReportRead == true) {
+                xmlConvoElement.attribute(null, XML_ATT_READ, getRead());
+            }
+            if(mVersionCounter != -1) {
+                xmlConvoElement.attribute(null, XML_ATT_VERSION_COUNTER,
+                        Long.toString(getVersionCounter()));
+            }
+            if(mSummary != null) {
+                xmlConvoElement.attribute(null, XML_ATT_SUMMARY, getSummary());
+            }
+            if(mContacts != null){
+                for(BluetoothMapConvoContactElement contact:mContacts){
+                   contact.encode(xmlConvoElement);
+                }
+            }
+            xmlConvoElement.endTag(null, XML_TAG_CONVERSATION);
+
+    }
+
+    /**
+     * Consumes a conversation tag. It is expected that the parser is beyond the start-tag event,
+     * with the name "conversation".
+     * @param parser
+     * @return
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    public static BluetoothMapConvoListingElement createFromXml(XmlPullParser parser)
+            throws XmlPullParserException, IOException, ParseException {
+        BluetoothMapConvoListingElement newElement = new BluetoothMapConvoListingElement();
+        int count = parser.getAttributeCount();
+        int type;
+        for (int i = 0; i<count; i++) {
+            String attributeName = parser.getAttributeName(i).trim();
+            String attributeValue = parser.getAttributeValue(i);
+            if(attributeName.equalsIgnoreCase(XML_ATT_ID)) {
+                newElement.mId = SignedLongLong.fromString(attributeValue);
+            } else if(attributeName.equalsIgnoreCase(XML_ATT_NAME)) {
+                newElement.mName = attributeValue;
+            } else if(attributeName.equalsIgnoreCase(XML_ATT_LAST_ACTIVITY)) {
+                newElement.setLastActivity(attributeValue);
+            } else if(attributeName.equalsIgnoreCase(XML_ATT_READ)) {
+                newElement.setRead(attributeValue);
+            } else if(attributeName.equalsIgnoreCase(XML_ATT_VERSION_COUNTER)) {
+                newElement.setVersionCounter(attributeValue);
+            } else if(attributeName.equalsIgnoreCase(XML_ATT_SUMMARY)) {
+                newElement.setSummary(attributeValue);
+            } else {
+                if(D) Log.i(TAG,"Unknown XML attribute: " + parser.getAttributeName(i));
+            }
+        }
+
+        // Now determine if we get an end-tag, or a new start tag for contacts
+        while((type=parser.next()) != XmlPullParser.END_TAG
+                && type != XmlPullParser.END_DOCUMENT ) {
+            // Skip until we get a start tag
+            if (parser.getEventType() != XmlPullParser.START_TAG) {
+                continue;
+            }
+            // Skip until we get a convocontact tag
+            String name = parser.getName().trim();
+            if(name.equalsIgnoreCase(BluetoothMapConvoContactElement.XML_TAG_CONVOCONTACT)){
+                newElement.addContact(BluetoothMapConvoContactElement.createFromXml(parser));
+            } else {
+                if(D) Log.i(TAG,"Unknown XML tag: " + name);
+                XmlUtils.skipCurrentTag(parser);
+                continue;
+            }
+        }
+        // As we have extracted all attributes, we should expect an end-tag
+        // parser.nextTag(); // consume the end-tag
+        // TODO: Is this needed? - we should already be at end-tag, as this is the top condition
+
+        return newElement;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        BluetoothMapConvoListingElement other = (BluetoothMapConvoListingElement) obj;
+        if (mContacts == null) {
+            if (other.mContacts != null) {
+                return false;
+            }
+        } else if (!mContacts.equals(other.mContacts)) {
+            return false;
+        }
+        /* As we use equals only for test, we don't compare auto assigned values
+         * if (mId == null) {
+            if (other.mId != null) {
+                return false;
+            }
+        } else if (!mId.equals(other.mId)) {
+            return false;
+        } */
+
+        if (mLastActivity != other.mLastActivity) {
+            return false;
+        }
+        if (mName == null) {
+            if (other.mName != null) {
+                return false;
+            }
+        } else if (!mName.equals(other.mName)) {
+            return false;
+        }
+        if (mRead != other.mRead) {
+            return false;
+        }
+        return true;
+    }
+
+/*    @Override
+    public boolean equals(Object o) {
+
+        return true;
+    };
+    */
+
+}
+
+
diff --git a/src/com/android/bluetooth/map/BluetoothMapEmailAppObserver.java b/src/com/android/bluetooth/map/BluetoothMapEmailAppObserver.java
deleted file mode 100644
index 38d0444..0000000
--- a/src/com/android/bluetooth/map/BluetoothMapEmailAppObserver.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
-* Copyright (C) 2014 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.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.Set;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import com.android.bluetooth.mapapi.BluetoothMapContract;
-import android.util.Log;
-
-/**
- * Class to construct content observers for for email applications on the system.
- *
- *
- */
-
-public class BluetoothMapEmailAppObserver{
-
-    private static final String TAG = "BluetoothMapEmailAppObserver";
-
-    private static final boolean D = BluetoothMapService.DEBUG;
-    private static final boolean V = BluetoothMapService.VERBOSE;
-    /*  */
-    private LinkedHashMap<BluetoothMapEmailSettingsItem, ArrayList<BluetoothMapEmailSettingsItem>> mFullList;
-    private LinkedHashMap<String,ContentObserver> mObserverMap = new LinkedHashMap<String,ContentObserver>();
-    private ContentResolver mResolver;
-    private Context mContext;
-    private BroadcastReceiver mReceiver;
-    private PackageManager mPackageManager = null;
-    BluetoothMapEmailSettingsLoader mLoader;
-    BluetoothMapService mMapService = null;
-
-    public BluetoothMapEmailAppObserver(final Context context, BluetoothMapService mapService) {
-        mContext  = context;
-        mMapService = mapService;
-        mResolver = context.getContentResolver();
-        mLoader    = new BluetoothMapEmailSettingsLoader(mContext);
-        mFullList = mLoader.parsePackages(false); /* Get the current list of apps */
-        createReceiver();
-        initObservers();
-    }
-
-
-    private BluetoothMapEmailSettingsItem getApp(String packageName) {
-        if(V) Log.d(TAG, "getApp(): Looking for " + packageName);
-        for(BluetoothMapEmailSettingsItem app:mFullList.keySet()){
-            if(V) Log.d(TAG, "  Comparing: " + app.getPackageName());
-            if(app.getPackageName().equals(packageName)) {
-                if(V) Log.d(TAG, "  found " + app.mBase_uri_no_account);
-                return app;
-            }
-        }
-        if(V) Log.d(TAG, "  NOT FOUND!");
-        return null;
-    }
-
-    private void handleAccountChanges(String packageNameWithProvider) {
-
-        if(D)Log.d(TAG,"handleAccountChanges (packageNameWithProvider: "+packageNameWithProvider+"\n");
-        String packageName = packageNameWithProvider.replaceFirst("\\.[^\\.]+$", "");
-        BluetoothMapEmailSettingsItem app = getApp(packageName);
-        if(app != null) {
-            ArrayList<BluetoothMapEmailSettingsItem> newAccountList = mLoader.parseAccounts(app);
-            ArrayList<BluetoothMapEmailSettingsItem> oldAccountList = mFullList.get(app);
-            ArrayList<BluetoothMapEmailSettingsItem> addedAccountList =
-                    (ArrayList<BluetoothMapEmailSettingsItem>)newAccountList.clone();
-            ArrayList<BluetoothMapEmailSettingsItem> removedAccountList = mFullList.get(app); // Same as oldAccountList.clone
-
-            mFullList.put(app, newAccountList);
-            for(BluetoothMapEmailSettingsItem newAcc: newAccountList){
-                for(BluetoothMapEmailSettingsItem oldAcc: oldAccountList){
-                    if(newAcc.getId() == oldAcc.getId()){
-                        // For each match remove from both removed and added lists
-                        removedAccountList.remove(oldAcc);
-                        addedAccountList.remove(newAcc);
-                        if(!newAcc.getName().equals(oldAcc.getName()) && newAcc.mIsChecked){
-                            // Name Changed and the acc is visible - Change Name in SDP record
-                            mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED);
-                            if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED");
-                        }
-                        if(newAcc.mIsChecked != oldAcc.mIsChecked) {
-                            // Visibility changed
-                            if(newAcc.mIsChecked){
-                                // account added - create SDP record
-                                mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
-                                if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_ADDED isChecked changed");
-                            } else {
-                                // account removed - remove SDP record
-                                mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
-                                if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED isChecked changed");
-                            }
-                        }
-                        break;
-                    }
-                }
-            }
-            // Notify on any removed accounts
-            for(BluetoothMapEmailSettingsItem removedAcc: removedAccountList){
-                mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
-                if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " + removedAcc);
-            }
-            // Notify on any new accounts
-            for(BluetoothMapEmailSettingsItem addedAcc: addedAccountList){
-                mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
-                if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " + addedAcc);
-            }
-
-        } else {
-            Log.e(TAG, "Received change notification on package not registered for notifications!");
-
-        }
-    }
-
-    /**
-     * Adds a new content observer to the list of content observers.
-     * The key for the observer is the uri as string
-     * @param uri uri for the package that supports MAP email
-     */
-
-    public void registerObserver(BluetoothMapEmailSettingsItem app) {
-        Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
-        if (V) Log.d(TAG, "registerObserver for URI "+uri.toString()+"\n");
-        ContentObserver observer = new ContentObserver(new Handler()) {
-            @Override
-            public void onChange(boolean selfChange) {
-                onChange(selfChange, null);
-            }
-
-            @Override
-            public void onChange(boolean selfChange, Uri uri) {
-                if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId()
-                        + " Uri: " + uri + " selfchange: " + selfChange);
-                if(uri != null) {
-                    handleAccountChanges(uri.getHost());
-                } else {
-                    Log.e(TAG, "Unable to handle change as the URI is NULL!");
-                }
-
-            }
-        };
-        mObserverMap.put(uri.toString(), observer);
-        mResolver.registerContentObserver(uri, true, observer);
-    }
-
-    public void unregisterObserver(BluetoothMapEmailSettingsItem app) {
-        Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
-        if (V) Log.d(TAG, "unregisterObserver("+uri.toString()+")\n");
-        mResolver.unregisterContentObserver(mObserverMap.get(uri.toString()));
-        mObserverMap.remove(uri.toString());
-    }
-
-    private void initObservers(){
-        if(D)Log.d(TAG,"initObservers()");
-        for(BluetoothMapEmailSettingsItem app: mFullList.keySet()){
-            registerObserver(app);
-        }
-    }
-
-    private void deinitObservers(){
-        if(D)Log.d(TAG,"deinitObservers()");
-        for(BluetoothMapEmailSettingsItem app: mFullList.keySet()){
-            unregisterObserver(app);
-        }
-    }
-
-    private void createReceiver(){
-        if(D)Log.d(TAG,"createReceiver()\n");
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
-        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        intentFilter.addDataScheme("package");
-        mReceiver= new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if(D)Log.d(TAG,"onReceive\n");
-                String action = intent.getAction();
-                if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
-                    Uri data = intent.getData();
-                    String packageName = data.getEncodedSchemeSpecificPart();
-                    if(D)Log.d(TAG,"The installed package is: "+ packageName);
-                  //  PackageInfo pInfo = getPackageInfo(packageName);
-                    ResolveInfo rInfo = mPackageManager.resolveActivity(intent, 0);
-                    BluetoothMapEmailSettingsItem app = mLoader.createAppItem(rInfo, false);
-                    if(app != null) {
-                        registerObserver(app);
-                        // Add all accounts to mFullList
-                        ArrayList<BluetoothMapEmailSettingsItem> newAccountList = mLoader.parseAccounts(app);
-                        mFullList.put(app, newAccountList);
-                    }
-                }
-                else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
-
-                    Uri data = intent.getData();
-                    String packageName = data.getEncodedSchemeSpecificPart();
-                    if(D)Log.d(TAG,"The removed package is: "+ packageName);
-                    BluetoothMapEmailSettingsItem app = getApp(packageName);
-                    /* Find the object and remove from fullList */
-                    if(app != null) {
-                        unregisterObserver(app);
-                        mFullList.remove(app);
-                    }
-                }
-            }
-        };
-        mContext.registerReceiver(mReceiver,new IntentFilter(Intent.ACTION_PACKAGE_ADDED));
-    }
-    private void removeReceiver(){
-        if(D)Log.d(TAG,"removeReceiver()\n");
-        mContext.unregisterReceiver(mReceiver);
-    }
-    private PackageInfo getPackageInfo(String packageName){
-        mPackageManager = mContext.getPackageManager();
-        try {
-            return mPackageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA|PackageManager.GET_SERVICES);
-        } catch (NameNotFoundException e) {
-            Log.e(TAG,"Error getting package metadata", e);
-        }
-        return null;
-    }
-
-    /**
-     * Method to get a list of the accounts (across all apps) that are set to be shared
-     * through MAP.
-     * @return Arraylist<BluetoothMapEmailSettingsItem> containing all enabled accounts
-     */
-    public ArrayList<BluetoothMapEmailSettingsItem> getEnabledAccountItems(){
-        if(D)Log.d(TAG,"getEnabledAccountItems()\n");
-        ArrayList<BluetoothMapEmailSettingsItem> list = new ArrayList<BluetoothMapEmailSettingsItem>();
-        for(BluetoothMapEmailSettingsItem app:mFullList.keySet()){
-            ArrayList<BluetoothMapEmailSettingsItem> accountList = mFullList.get(app);
-            for(BluetoothMapEmailSettingsItem acc: accountList){
-                if(acc.mIsChecked) {
-                    list.add(acc);
-                }
-            }
-        }
-        return list;
-    }
-
-    /**
-     * Method to get a list of the accounts (across all apps).
-     * @return Arraylist<BluetoothMapEmailSettingsItem> containing all accounts
-     */
-    public ArrayList<BluetoothMapEmailSettingsItem> getAllAccountItems(){
-        if(D)Log.d(TAG,"getAllAccountItems()\n");
-        ArrayList<BluetoothMapEmailSettingsItem> list = new ArrayList<BluetoothMapEmailSettingsItem>();
-        for(BluetoothMapEmailSettingsItem app:mFullList.keySet()){
-            ArrayList<BluetoothMapEmailSettingsItem> accountList = mFullList.get(app);
-            list.addAll(accountList);
-        }
-        return list;
-    }
-
-
-    /**
-     * Cleanup all resources - must be called to avoid leaks.
-     */
-    public void shutdown() {
-        deinitObservers();
-        removeReceiver();
-    }
-}
diff --git a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsLoader.java b/src/com/android/bluetooth/map/BluetoothMapEmailSettingsLoader.java
deleted file mode 100644
index c6a6ca7..0000000
--- a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsLoader.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
-* Copyright (C) 2014 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.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-
-import com.android.bluetooth.map.BluetoothMapEmailSettingsItem;
-
-
-
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
-import com.android.bluetooth.mapapi.BluetoothMapContract;
-import android.text.format.DateUtils;
-import android.util.Log;
-
-
-public class BluetoothMapEmailSettingsLoader {
-    private static final String TAG = "BluetoothMapEmailSettingsLoader";
-    private static final boolean D = BluetoothMapService.DEBUG;
-    private static final boolean V = BluetoothMapService.VERBOSE;
-    private Context mContext = null;
-    private PackageManager mPackageManager = null;
-    private ContentResolver mResolver;
-    private int mAccountsEnabledCount = 0;
-    private ContentProviderClient mProviderClient = null;
-    private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
-
-    public BluetoothMapEmailSettingsLoader(Context ctx)
-    {
-        mContext = ctx;
-    }
-
-    /**
-     * Method to look through all installed packages system-wide and find those that contain the
-     * BTMAP meta-tag in their manifest file. For each app the list of accounts are fetched using
-     * the method parseAccounts().
-     * @return LinkedHashMap with the packages as keys(BluetoothMapEmailSettingsItem) and
-     *          values as ArrayLists of BluetoothMapEmailSettingsItems.
-     */
-    public LinkedHashMap<BluetoothMapEmailSettingsItem,
-                         ArrayList<BluetoothMapEmailSettingsItem>> parsePackages(boolean includeIcon) {
-
-        LinkedHashMap<BluetoothMapEmailSettingsItem, ArrayList<BluetoothMapEmailSettingsItem>> groups =
-                new LinkedHashMap<BluetoothMapEmailSettingsItem,ArrayList<BluetoothMapEmailSettingsItem>>();
-        Intent searchIntent = new Intent(BluetoothMapContract.PROVIDER_INTERFACE);
-        // reset the counter every time this method is called.
-        mAccountsEnabledCount=0;
-        // find all installed packages and filter out those that do not support Map Email.
-        // this is done by looking for a apps with content providers containing the intent-filter for
-        // android.content.action.BTMAP_SHARE in the manifest file.
-        mPackageManager = mContext.getPackageManager();
-        List<ResolveInfo> resInfos = mPackageManager
-                .queryIntentContentProviders(searchIntent, 0);
-
-        if (resInfos != null) {
-            if(D) Log.d(TAG,"Found " + resInfos.size() + " applications");
-            for (ResolveInfo rInfo : resInfos) {
-                // We cannot rely on apps that have been force-stopped in the application settings menu.
-                if ((rInfo.providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) == 0) {
-                    BluetoothMapEmailSettingsItem app = createAppItem(rInfo, includeIcon);
-                    if (app != null){
-                        ArrayList<BluetoothMapEmailSettingsItem> accounts = parseAccounts(app);
-                        // we do not want to list apps without accounts
-                        if(accounts.size() >0)
-                        {
-                            // we need to make sure that the "select all" checkbox is checked if all accounts in the list are checked
-                            app.mIsChecked = true;
-                            for (BluetoothMapEmailSettingsItem acc: accounts)
-                            {
-                                if(!acc.mIsChecked)
-                                {
-                                    app.mIsChecked = false;
-                                    break;
-                                }
-                            }
-                            groups.put(app, accounts);
-                        }
-                    }
-                } else {
-                    if(D)Log.d(TAG,"Ignoring force-stopped authority "+ rInfo.providerInfo.authority +"\n");
-                }
-            }
-        }
-        else {
-            if(D) Log.d(TAG,"Found no applications");
-        }
-        return groups;
-    }
-
-    public BluetoothMapEmailSettingsItem createAppItem(ResolveInfo rInfo,
-            boolean includeIcon) {
-        String provider = rInfo.providerInfo.authority;
-        if(provider != null) {
-            String name = rInfo.loadLabel(mPackageManager).toString();
-            if(D)Log.d(TAG,rInfo.providerInfo.packageName + " - " + name + " - meta-data(provider = " + provider+")\n");
-            BluetoothMapEmailSettingsItem app = new BluetoothMapEmailSettingsItem(
-                    "0",
-                    name,
-                    rInfo.providerInfo.packageName,
-                    provider,
-                    (includeIcon == false)? null : rInfo.loadIcon(mPackageManager));
-            return app;
-        }
-
-        return null;
-    }
-
-
-    /**
-     * Method for getting the accounts under a given contentprovider from a package.
-     * This
-     * @param app The parent app object
-     * @return An ArrayList of BluetoothMapEmailSettingsItems containing all the accounts from the app
-     */
-    public ArrayList<BluetoothMapEmailSettingsItem> parseAccounts(BluetoothMapEmailSettingsItem app)  {
-        Cursor c = null;
-        if(D) Log.d(TAG,"Adding app "+app.getPackageName());
-        ArrayList<BluetoothMapEmailSettingsItem> children = new ArrayList<BluetoothMapEmailSettingsItem>();
-        // Get the list of accounts from the email apps content resolver (if possible
-        mResolver = mContext.getContentResolver();
-        try{
-            mProviderClient = mResolver.acquireUnstableContentProviderClient(Uri.parse(app.mBase_uri_no_account));
-             if (mProviderClient == null) {
-                 throw new RemoteException("Failed to acquire provider for " + app.getPackageName());
-             }
-             mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
-
-            Uri uri = Uri.parse(app.mBase_uri_no_account + "/" + BluetoothMapContract.TABLE_ACCOUNT);
-            c = mProviderClient.query(uri, BluetoothMapContract.BT_ACCOUNT_PROJECTION, null, null,
-                    BluetoothMapContract.AccountColumns._ID+" DESC");
-            while (c != null && c.moveToNext()) {
-                BluetoothMapEmailSettingsItem child = new BluetoothMapEmailSettingsItem(
-                    String.valueOf((c.getInt(c.getColumnIndex(BluetoothMapContract.AccountColumns._ID)))),
-                    c.getString(c.getColumnIndex(BluetoothMapContract.AccountColumns.ACCOUNT_DISPLAY_NAME)) ,
-                    app.getPackageName(),
-                    app.getProviderAuthority(),
-                    null);
-
-                child.mIsChecked = (c.getInt(c.getColumnIndex(BluetoothMapContract.AccountColumns.FLAG_EXPOSE))!=0);
-                /*update the account counter so we can make sure that not to many accounts are checked. */
-                if(child.mIsChecked)
-                {
-                    mAccountsEnabledCount++;
-                }
-                children.add(child);
-            }
-        } catch (RemoteException e){
-            if(D)Log.d(TAG,"Could not establish ContentProviderClient for "+app.getPackageName()+
-                    " - returning empty account list" );
-        } finally {
-            if (c != null) c.close();
-        }
-        return children;
-    }
-    /**
-     * Gets the number of enabled accounts in total across all supported apps.
-     * NOTE that this method should not be called before the parsePackages method
-     * has been successfully called.
-     * @return number of enabled accounts
-     */
-    public int getAccountsEnabledCount() {
-        if(D)Log.d(TAG,"Enabled Accounts count:"+ mAccountsEnabledCount);
-        return mAccountsEnabledCount;
-    }
-}
diff --git a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
index 3175eae..8b08fd8 100644
--- a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
@@ -1,5 +1,5 @@
 /*
-* Copyright (C) 2014 Samsung System LSI
+* Copyright (C) 2015 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
@@ -15,31 +15,39 @@
 package com.android.bluetooth.map;
 
 
-import android.util.Log;
-import org.xmlpull.v1.XmlSerializer;
-
-import com.android.internal.util.FastXmlSerializer;
-
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Locale;
-import java.util.Map.Entry;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
 
 
 /**
  * Class to contain a single folder element representation.
  *
  */
-public class BluetoothMapFolderElement {
+public class BluetoothMapFolderElement implements Comparable<BluetoothMapFolderElement>{
     private String mName;
     private BluetoothMapFolderElement mParent = null;
+    private long mFolderId = -1;
     private boolean mHasSmsMmsContent = false;
+    private boolean mHasImContent = false;
     private boolean mHasEmailContent = false;
-    private long mEmailFolderId = -1;
+
+    private boolean mIgnore = false;
+
     private HashMap<String, BluetoothMapFolderElement> mSubFolders;
 
     private static final boolean D = BluetoothMapService.DEBUG;
@@ -53,6 +61,14 @@
         mSubFolders = new HashMap<String, BluetoothMapFolderElement>();
     }
 
+    public void setIngore(boolean ignore) {
+        mIgnore = ignore;
+    }
+
+    public boolean shouldIgnore() {
+        return mIgnore;
+    }
+
     public String getName() {
         return mName;
     }
@@ -61,24 +77,29 @@
         return mHasSmsMmsContent;
     }
 
-    public long getEmailFolderId(){
-        return mEmailFolderId;
+    public long getFolderId(){
+        return mFolderId;
     }
     public boolean hasEmailContent(){
         return mHasEmailContent;
     }
 
-    public void setEmailFolderId(long emailFolderId) {
-        this.mEmailFolderId = emailFolderId;
+    public void setFolderId(long folderId) {
+        this.mFolderId = folderId;
     }
-
     public void setHasSmsMmsContent(boolean hasSmsMmsContent) {
         this.mHasSmsMmsContent = hasSmsMmsContent;
     }
-
     public void setHasEmailContent(boolean hasEmailContent) {
         this.mHasEmailContent = hasEmailContent;
     }
+    public void setHasImContent(boolean hasImContent) {
+        this.mHasImContent = hasImContent;
+    }
+
+    public boolean hasImContent(){
+        return mHasImContent;
+    }
 
     /**
      * Fetch the parent folder.
@@ -106,38 +127,38 @@
     }
 
 
-    public BluetoothMapFolderElement getEmailFolderByName(String name) {
+    public BluetoothMapFolderElement getFolderByName(String name) {
         BluetoothMapFolderElement folderElement = this.getRoot();
         folderElement = folderElement.getSubFolder("telecom");
         folderElement = folderElement.getSubFolder("msg");
         folderElement = folderElement.getSubFolder(name);
-        if (folderElement != null && folderElement.getEmailFolderId() == -1 )
+        if (folderElement != null && folderElement.getFolderId() == -1 )
             folderElement = null;
         return folderElement;
     }
 
-    public BluetoothMapFolderElement getEmailFolderById(long id) {
-        return getEmailFolderById(id, this);
+    public BluetoothMapFolderElement getFolderById(long id) {
+        return getFolderById(id, this);
     }
 
-    public static BluetoothMapFolderElement getEmailFolderById(long id,
+    public static BluetoothMapFolderElement getFolderById(long id,
             BluetoothMapFolderElement folderStructure) {
         if(folderStructure == null) {
             return null;
         }
-        return findEmailFolderById(id, folderStructure.getRoot());
+        return findFolderById(id, folderStructure.getRoot());
     }
 
-    private static BluetoothMapFolderElement findEmailFolderById(long id,
+    private static BluetoothMapFolderElement findFolderById(long id,
             BluetoothMapFolderElement folder) {
-        if(folder.getEmailFolderId() == id) {
+        if(folder.getFolderId() == id) {
             return folder;
         }
         /* Else */
         for(BluetoothMapFolderElement subFolder : folder.mSubFolders.values().toArray(
                 new BluetoothMapFolderElement[folder.mSubFolders.size()]))
         {
-            BluetoothMapFolderElement ret = findEmailFolderById(id, subFolder);
+            BluetoothMapFolderElement ret = findFolderById(id, subFolder);
             if(ret != null) {
                 return ret;
             }
@@ -148,7 +169,7 @@
 
     /**
      * Fetch the root folder.
-     * @return the parent folder or null if we are at the root folder.
+     * @return the root folder.
      */
     public BluetoothMapFolderElement getRoot() {
         BluetoothMapFolderElement rootFolder = this;
@@ -165,10 +186,12 @@
     public BluetoothMapFolderElement addFolder(String name){
         name = name.toLowerCase(Locale.US);
         BluetoothMapFolderElement newFolder = mSubFolders.get(name);
-        if(D) Log.i(TAG,"addFolder():" + name);
         if(newFolder == null) {
+            if(D) Log.i(TAG,"addFolder():" + name);
             newFolder = new BluetoothMapFolderElement(name, this);
             mSubFolders.put(name, newFolder);
+        } else {
+            if(D) Log.i(TAG,"addFolder():" + name + " already added");
         }
         return newFolder;
     }
@@ -179,35 +202,37 @@
      * @return the added folder element.
      */
     public BluetoothMapFolderElement addSmsMmsFolder(String name){
-        name = name.toLowerCase(Locale.US);
-        BluetoothMapFolderElement newFolder = mSubFolders.get(name);
-        if(D) Log.i(TAG,"addSmsMmsFolder():" + name);
-        if(newFolder == null) {
-            newFolder = new BluetoothMapFolderElement(name, this);
-            mSubFolders.put(name, newFolder);
-        }
+        if(D) Log.i(TAG,"addSmsMmsFolder()");
+        BluetoothMapFolderElement newFolder = addFolder(name);
         newFolder.setHasSmsMmsContent(true);
         return newFolder;
     }
 
     /**
+     * Add a im folder.
+     * @param name the name of the folder to add.
+     * @return the added folder element.
+     */
+    public BluetoothMapFolderElement addImFolder(String name, long idFolder){
+        if(D) Log.i(TAG,"addImFolder() id = " + idFolder);
+        BluetoothMapFolderElement newFolder = addFolder(name);
+        newFolder.setHasImContent(true);
+        newFolder.setFolderId(idFolder);
+        return newFolder;
+    }
+
+    /**
      * Add an Email folder.
      * @param name the name of the folder to add.
      * @return the added folder element.
      */
     public BluetoothMapFolderElement addEmailFolder(String name, long emailFolderId){
-        name = name.toLowerCase();
-        BluetoothMapFolderElement newFolder = mSubFolders.get(name);
-        if(V) Log.v(TAG,"addEmailFolder(): name = " + name
-                 + "id = " + emailFolderId);
-        if(newFolder == null) {
-            newFolder = new BluetoothMapFolderElement(name, this);
-            mSubFolders.put(name, newFolder);
-        }
-        newFolder.setEmailFolderId(emailFolderId);
+        if(V) Log.v(TAG,"addEmailFolder() id = " + emailFolderId);
+        BluetoothMapFolderElement newFolder = addFolder(name);
+        newFolder.setFolderId(emailFolderId);
+        newFolder.setHasEmailContent(true);
         return newFolder;
     }
-
     /**
      * Fetch the number of sub folders.
      * @return returns the number of sub folders.
@@ -244,7 +269,7 @@
             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");
+            xmlMsgElement.attribute(null, "version", BluetoothMapUtils.MAP_V10_STR);
             for(i = offset; i<stopIndex; i++)
             {
                 xmlMsgElement.startTag(null, "folder");
@@ -265,4 +290,119 @@
         }
         return sw.toString().getBytes("UTF-8");
     }
+
+    /* The functions below are useful for implementing a MAP client, reusing the object.
+     * Currently they are only used for test purposes.
+     * */
+
+    /**
+     * Append sub folders from an XML document as specified in the MAP specification.
+     * Attributes will be inherited from parent folder - with regards to message types in the
+     * folder.
+     * @param xmlDocument - InputStream with the document
+     *
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    public void appendSubfolders(InputStream xmlDocument)
+            throws XmlPullParserException, IOException {
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            int type;
+            parser.setInput(xmlDocument, "UTF-8");
+
+            // First find the folder-listing
+            while((type=parser.next()) != XmlPullParser.END_TAG
+                    && type != XmlPullParser.END_DOCUMENT ) {
+                // Skip until we get a start tag
+                if (parser.getEventType() != XmlPullParser.START_TAG) {
+                    continue;
+                }
+                // Skip until we get a folder-listing tag
+                String name = parser.getName();
+                if(!name.equalsIgnoreCase("folder-listing")) {
+                    if(D) Log.i(TAG,"Unknown XML tag: " + name);
+                    XmlUtils.skipCurrentTag(parser);
+                }
+                readFolders(parser);
+            }
+        } finally {
+            xmlDocument.close();
+        }
+    }
+
+    /**
+     * Parses folder elements, and add to mSubFolders.
+     * @param parser the Xml Parser currently pointing to an folder-listing tag.
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    public void readFolders(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        int type;
+        if(D) Log.i(TAG,"readFolders(): ");
+        while((type=parser.next()) != XmlPullParser.END_TAG
+                && type != XmlPullParser.END_DOCUMENT ) {
+            // Skip until we get a start tag
+            if (parser.getEventType() != XmlPullParser.START_TAG) {
+                continue;
+            }
+            // Skip until we get a folder-listing tag
+            String name = parser.getName();
+            if(name.trim().equalsIgnoreCase("folder") == false) {
+                if(D) Log.i(TAG,"Unknown XML tag: " + name);
+                XmlUtils.skipCurrentTag(parser);
+                continue;
+            }
+            int count = parser.getAttributeCount();
+            for (int i = 0; i<count; i++) {
+                if(parser.getAttributeName(i).trim().equalsIgnoreCase("name")) {
+                    // We found a folder, append to sub folders.
+                    BluetoothMapFolderElement element =
+                            addFolder(parser.getAttributeValue(i).trim());
+                    element.setHasEmailContent(mHasEmailContent);
+                    element.setHasImContent(mHasImContent);
+                    element.setHasSmsMmsContent(mHasSmsMmsContent);
+                } else {
+                    if(D) Log.i(TAG,"Unknown XML attribute: " + parser.getAttributeName(i));
+                }
+            }
+            parser.nextTag();
+        }
+    }
+
+    /**
+     * Recursive compare of all folder names
+     */
+    @Override
+    public int compareTo(BluetoothMapFolderElement another) {
+        if(another == null) return 1;
+        int ret = mName.compareToIgnoreCase(another.mName);
+        // TODO: Do we want to add compare of folder type?
+        if(ret == 0) {
+            ret = mSubFolders.size() - another.mSubFolders.size();
+            if(ret == 0) {
+                // Compare all sub folder elements (will do nothing if mSubFolders is empty)
+                for(BluetoothMapFolderElement subfolder : mSubFolders.values()) {
+                    BluetoothMapFolderElement subfolderAnother =
+                            another.mSubFolders.get(subfolder.getName());
+                    if(subfolderAnother == null) {
+                        if(D) Log.i(TAG, subfolder.getFullPath() + " not in another");
+                        return 1;
+                    }
+                    ret = subfolder.compareTo(subfolderAnother);
+                    if(ret != 0) {
+                        if(D) Log.i(TAG, subfolder.getFullPath() + " filed compareTo()");
+                        return ret;
+                    }
+                }
+            } else {
+                if(D) Log.i(TAG, "mSubFolders.size(): " + mSubFolders.size() +
+                        " another.mSubFolders.size(): " + another.mSubFolders.size());
+            }
+        } else {
+            if(D) Log.i(TAG, "mName: " + mName + " another.mName: " + another.mName);
+        }
+        return ret;
+    }
 }
diff --git a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
index 449b934..66e6d3b 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
@@ -15,12 +15,18 @@
 package com.android.bluetooth.map;
 
 import java.io.IOException;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
 
 import javax.obex.ServerSession;
 
 import com.android.bluetooth.BluetoothObexTransport;
 import com.android.bluetooth.IObexConnectionHandler;
 import com.android.bluetooth.ObexServerSockets;
+import com.android.bluetooth.map.BluetoothMapContentObserver.Msg;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
 import com.android.bluetooth.sdp.SdpManager;
 
 import android.bluetooth.BluetoothAdapter;
@@ -35,7 +41,8 @@
 import android.util.Log;
 
 public class BluetoothMapMasInstance implements IObexConnectionHandler {
-    private static final String TAG = "BluetoothMapMasInstance";
+    private final String TAG;
+    private static volatile int sInstanceCounter = 0;
 
     private static final boolean D = BluetoothMapService.DEBUG;
     private static final boolean V = BluetoothMapService.VERBOSE;
@@ -44,6 +51,7 @@
     private static final int SDP_MAP_MSG_TYPE_SMS_GSM  = 0x02;
     private static final int SDP_MAP_MSG_TYPE_SMS_CDMA = 0x04;
     private static final int SDP_MAP_MSG_TYPE_MMS      = 0x08;
+    private static final int SDP_MAP_MSG_TYPE_IM       = 0x10;
 
     private static final int SDP_MAP_MAS_VERSION       = 0x0102;
 
@@ -51,30 +59,46 @@
     private static final int SDP_MAP_MAS_FEATURES      = 0x0000007F;
 
     private ServerSession mServerSession = null;
-
     // The handle to the socket registration with SDP
     private ObexServerSockets mServerSockets = null;
     private int mSdpHandle = -1;
 
     // The actual incoming connection handle
     private BluetoothSocket mConnSocket = null;
-
-    private BluetoothDevice mRemoteDevice = null; // The remote connected device
-
+    // The remote connected device
+    private BluetoothDevice mRemoteDevice = null;
     private BluetoothAdapter mAdapter;
 
     private volatile boolean mInterrupted;              // Used to interrupt socket accept thread
+    private volatile boolean mShutdown = false;         // Used to interrupt socket accept thread
 
     private Handler mServiceHandler = null;             // MAP service message handler
     private BluetoothMapService mMapService = null;     // Handle to the outer MAP service
     private Context mContext = null;                    // MAP service context
     private BluetoothMnsObexClient mMnsClient = null;   // Shared MAP MNS client
-    private BluetoothMapEmailSettingsItem mAccount = null; //
-    private String mBaseEmailUri = null;                // Email client base URI for this instance
+    private BluetoothMapAccountItem mAccount = null;    //
+    private String mBaseUri = null;                     // Client base URI for this instance
     private int mMasInstanceId = -1;
     private boolean mEnableSmsMms = false;
     BluetoothMapContentObserver mObserver;
 
+    private AtomicLong mDbIndetifier = new AtomicLong();
+    private AtomicLong mFolderVersionCounter = new AtomicLong(0);
+    private AtomicLong mSmsMmsConvoListVersionCounter = new AtomicLong(0);
+    private AtomicLong mImEmailConvoListVersionCounter = new AtomicLong(0);
+
+    private Map<Long, Msg> mMsgListSms=null;
+    private Map<Long, Msg> mMsgListMms=null;
+    private Map<Long, Msg> mMsgListMsg=null;
+
+    private Map<String, BluetoothMapConvoContactElement> mContactList;
+
+    private HashMap<Long,BluetoothMapConvoListingElement> mSmsMmsConvoList =
+            new HashMap<Long, BluetoothMapConvoListingElement>();
+
+    private HashMap<Long,BluetoothMapConvoListingElement> mImEmailConvoList =
+            new HashMap<Long, BluetoothMapConvoListingElement>();
+
     private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
 
     public static final String TYPE_SMS_MMS_STR = "SMS/MMS";
@@ -90,35 +114,143 @@
      */
     public BluetoothMapMasInstance (BluetoothMapService mapService,
             Context context,
-            BluetoothMapEmailSettingsItem account,
+            BluetoothMapAccountItem account,
             int masId,
             boolean enableSmsMms) {
+        TAG = "BluetoothMapMasInstance" + sInstanceCounter++;
         mMapService = mapService;
         mServiceHandler = mapService.getHandler();
         mContext = context;
         mAccount = account;
         if(account != null) {
-            mBaseEmailUri = account.mBase_uri;
+            mBaseUri = account.mBase_uri;
         }
         mMasInstanceId = masId;
         mEnableSmsMms = enableSmsMms;
         init();
     }
 
+    /* Needed only for test */
+    protected BluetoothMapMasInstance() {
+        TAG = "BluetoothMapMasInstance" + sInstanceCounter++;
+    }
+
     @Override
     public String toString() {
-        return "MasId: " + mMasInstanceId + " Uri:" + mBaseEmailUri + " SMS/MMS:" + mEnableSmsMms;
+        return "MasId: " + mMasInstanceId + " Uri:" + mBaseUri + " SMS/MMS:" + mEnableSmsMms;
     }
 
     private void init() {
         mAdapter = BluetoothAdapter.getDefaultAdapter();
     }
 
-    public int getMasId() {
+    /**
+     * The data base identifier is used by connecting MCE devices to evaluate if cached data
+     * is still valid, hence only update this value when something actually invalidates the data.
+     * Situations where this must be called:
+     * - MAS ID's vs. server channels are scrambled (As neither MAS ID, name or server channels)
+     *   can be used by a client to uniquely identify a specific message database - except MAS id 0
+     *   we should change this value if the server channel is changed.
+     * - If a MAS instance folderVersionCounter roles over - will not happen before a long
+     *   is too small to hold a unix time-stamp, hence is not handled.
+     */
+    private void updateDbIdentifier(){
+        mDbIndetifier.set(Calendar.getInstance().getTime().getTime());
+    }
+
+    /**
+     * update the time stamp used for FOLDER version counter.
+     * Call once when a content provider notification caused applicable changes to the
+     * list of messages.
+     */
+    /* package */ void updateFolderVersionCounter() {
+        mFolderVersionCounter.incrementAndGet();
+    }
+
+    /**
+     * update the CONVO LIST version counter.
+     * Call once when a content provider notification caused applicable changes to the
+     * list of contacts, or when an update is manually triggered.
+     */
+    /* package */ void updateSmsMmsConvoListVersionCounter() {
+        mSmsMmsConvoListVersionCounter.incrementAndGet();
+    }
+
+    /* package */ void updateImEmailConvoListVersionCounter() {
+        mImEmailConvoListVersionCounter.incrementAndGet();
+    }
+
+    /* package */ Map<Long, Msg> getMsgListSms() {
+        return mMsgListSms;
+    }
+
+    /* package */ void setMsgListSms(Map<Long, Msg> msgListSms) {
+        mMsgListSms = msgListSms;
+    }
+
+    /* package */ Map<Long, Msg> getMsgListMms() {
+        return mMsgListMms;
+    }
+
+    /* package */ void setMsgListMms(Map<Long, Msg> msgListMms) {
+        mMsgListMms = msgListMms;
+    }
+
+    /* package */ Map<Long, Msg> getMsgListMsg() {
+        return mMsgListMsg;
+    }
+
+    /* package */ void setMsgListMsg(Map<Long, Msg> msgListMsg) {
+        mMsgListMsg = msgListMsg;
+    }
+
+    /* package */ Map<String, BluetoothMapConvoContactElement> getContactList() {
+        return mContactList;
+    }
+
+    /* package */ void setContactList(Map<String, BluetoothMapConvoContactElement> contactList) {
+        mContactList = contactList;
+    }
+
+    HashMap<Long,BluetoothMapConvoListingElement> getSmsMmsConvoList() {
+        return mSmsMmsConvoList;
+    }
+
+    void setSmsMmsConvoList(HashMap<Long,BluetoothMapConvoListingElement> smsMmsConvoList) {
+        mSmsMmsConvoList = smsMmsConvoList;
+    }
+
+    HashMap<Long,BluetoothMapConvoListingElement> getImEmailConvoList() {
+        return mImEmailConvoList;
+    }
+
+    void setImEmailConvoList(HashMap<Long,BluetoothMapConvoListingElement> imEmailConvoList) {
+        mImEmailConvoList = imEmailConvoList;
+    }
+
+    /* package*/
+    int getMasId() {
         return mMasInstanceId;
     }
 
-    public void startRfcommSocketListener() {
+    /* package*/
+    long getDbIdentifier() {
+        return mDbIndetifier.get();
+    }
+
+    /* package*/
+    long getFolderVersionCounter() {
+        return mFolderVersionCounter.get();
+    }
+
+    /* package */
+    long getCombinedConvoListVersionCounter() {
+        long combinedVersionCounter = mSmsMmsConvoListVersionCounter.get();
+        combinedVersionCounter += mImEmailConvoListVersionCounter.get();
+        return combinedVersionCounter;
+    }
+
+    synchronized public void startRfcommSocketListener() {
         if (D) Log.d(TAG, "Map Service startRfcommSocketListener");
 
         if (mServerSession != null) {
@@ -145,11 +277,17 @@
                 Log.e(TAG, "Failed to start the listeners");
                 return;
             }
-            if(mSdpHandle > 0) {
+            if(mSdpHandle >= 0) {
                 SdpManager.getDefaultManager().removeSdpRecord(mSdpHandle);
+                if(V) Log.d(TAG, "Removing SDP record for MAS instance: " + mMasInstanceId +
+                        " Object reference: " + this + "SDP handle: " + mSdpHandle);
             }
             mSdpHandle = createMasSdpRecord(mServerSockets.getRfcommChannel(),
                     mServerSockets.getL2capPsm());
+            // Here we might have changed crucial data, hence reset DB identifier
+            if(V) Log.d(TAG, "Creating new SDP record for MAS instance: " + mMasInstanceId +
+                    " Object reference: " + this + "SDP handle: " + mSdpHandle);
+            updateDbIdentifier();
         }
     }
 
@@ -164,17 +302,26 @@
         if(mEnableSmsMms) {
             masName = TYPE_SMS_MMS_STR;
             messageTypeFlags |= SDP_MAP_MSG_TYPE_SMS_GSM |
-//                           SDP_MAP_MSG_TYPE_SMS_CDMA|
+                           SDP_MAP_MSG_TYPE_SMS_CDMA|
                            SDP_MAP_MSG_TYPE_MMS;
         }
 
-        if(mBaseEmailUri != null) {
+        if(mBaseUri != null) {
             if(mEnableSmsMms) {
-                masName += "/" + TYPE_EMAIL_STR;
+                if(mAccount.getType() == TYPE.EMAIL) {
+                    masName += "/" + TYPE_EMAIL_STR;
+                } else if(mAccount.getType() == TYPE.IM) {
+                    masName += "/" + TYPE_IM_STR;
+                }
             } else {
                 masName = mAccount.getName();
             }
-            messageTypeFlags |= SDP_MAP_MSG_TYPE_EMAIL;
+
+            if(mAccount.getType() == TYPE.EMAIL) {
+                messageTypeFlags |= SDP_MAP_MSG_TYPE_EMAIL;
+            } else if(mAccount.getType() == TYPE.IM) {
+                messageTypeFlags |= SDP_MAP_MSG_TYPE_IM;
+            }
         }
 
         return SdpManager.getDefaultManager().createMapMasRecord(masName,
@@ -210,7 +357,7 @@
             mapServer = new BluetoothMapObexServer(mServiceHandler,
                                                     mContext,
                                                     mObserver,
-                                                    mMasInstanceId,
+                                                    this,
                                                     mAccount,
                                                     mEnableSmsMms);
 
@@ -317,9 +464,12 @@
     @Override
     public synchronized void onAcceptFailed() {
         mServerSockets = null; // Will cause a new to be created when calling start.
-        Log.e(TAG,"Failed to accept incomming connection - restarting");
-        startRfcommSocketListener();
-
+        if(mShutdown) {
+            Log.e(TAG,"Failed to accept incomming connection - " + "shutdown");
+        } else {
+            Log.e(TAG,"Failed to accept incomming connection - " + "restarting");
+            startRfcommSocketListener();
+        }
     }
 
 }
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
index 6a486c6..b904c36 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
@@ -32,13 +32,13 @@
     private static final String TAG = "BluetoothMapMessageListing";
     private static final boolean D = BluetoothMapService.DEBUG;
 
-    private List<BluetoothMapMessageListingElement> list;
+    private List<BluetoothMapMessageListingElement> mList;
 
     public BluetoothMapMessageListing(){
-     list = new ArrayList<BluetoothMapMessageListingElement>();
+        mList = new ArrayList<BluetoothMapMessageListingElement>();
     }
     public void add(BluetoothMapMessageListingElement element) {
-        list.add(element);
+        mList.add(element);
         /* update info regarding whether the list contains unread messages */
         if (element.getReadBool())
         {
@@ -51,9 +51,9 @@
      * @return the number of elements in the list.
      */
     public int getCount() {
-        if(list != null)
+        if(mList != null)
         {
-            return list.size();
+            return mList.size();
         }
         return 0;
     }
@@ -73,18 +73,22 @@
      * @return list
      */
     public List<BluetoothMapMessageListingElement> getList(){
-        return list;
+        return mList;
     }
 
     /**
      * Encode the list of BluetoothMapMessageListingElement(s) into a UTF-8
      * formatted XML-string in a trimmed byte array
      *
+     * @param version the version as a string.
+     *        Set the listing version to e.g. "1.0" or "1.1".
+     *        To make this future proof, no check is added to validate the value, hence be careful.
      * @return a reference to the encoded byte array.
      * @throws UnsupportedEncodingException
      *             if UTF-8 encoding is unsupported on the platform.
      */
-    public byte[] encode(boolean includeThreadId) throws UnsupportedEncodingException {
+    // TODO: Remove includeThreadId when MAP-IM is adopted
+    public byte[] encode(boolean includeThreadId, String version) throws UnsupportedEncodingException {
         StringWriter sw = new StringWriter();
         XmlSerializer xmlMsgElement = new FastXmlSerializer();
         try {
@@ -92,9 +96,9 @@
             xmlMsgElement.startDocument("UTF-8", true);
             xmlMsgElement.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
             xmlMsgElement.startTag(null, "MAP-msg-listing");
-            xmlMsgElement.attribute(null, "version", "1.0");
+            xmlMsgElement.attribute(null, "version", version);
             // Do the XML encoding of list
-            for (BluetoothMapMessageListingElement element : list) {
+            for (BluetoothMapMessageListingElement element : mList) {
                 element.encode(xmlMsgElement, includeThreadId); // Append the list element
             }
             xmlMsgElement.endTag(null, "MAP-msg-listing");
@@ -110,22 +114,22 @@
     }
 
     public void sort() {
-        Collections.sort(list);
+        Collections.sort(mList);
     }
 
     public void segment(int count, int offset) {
-        count = Math.min(count, list.size() - offset);
+        count = Math.min(count, mList.size() - offset);
         if (count > 0) {
-            list = list.subList(offset, offset + count);
-            if(list == null) {
-                list = new ArrayList<BluetoothMapMessageListingElement>(); // Return an empty list
+            mList = mList.subList(offset, offset + count);
+            if(mList == null) {
+                mList = new ArrayList<BluetoothMapMessageListingElement>(); // Return an empty list
             }
         } else {
-            if(offset > list.size()) {
-               list = new ArrayList<BluetoothMapMessageListingElement>();
+            if(offset > mList.size()) {
+               mList = new ArrayList<BluetoothMapMessageListingElement>();
                Log.d(TAG, "offset greater than list size. Returning empty list");
             } else {
-               list = list.subList(offset, list.size());
+               mList = mList.subList(offset, mList.size());
             }
         }
     }
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
index 04788f2..ec4005b 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
@@ -44,15 +44,21 @@
     private String mRecipientName = null;
     private String mRecipientAddressing = null;
     private TYPE mType = null;
+    private boolean mMsgTypeAppParamSet = false;
     private int mSize = -1;
     private String mText = null;
     private String mReceptionStatus = null;
+    private String mDeliveryStatus = null;
     private int mAttachmentSize = -1;
     private String mPriority = null;
     private boolean mRead = false;
     private String mSent = null;
     private String mProtect = null;
+    private String mFolderType = null;
     private String mThreadId = null;
+    private String mThreadName = null;
+    private String mAttachmentMimeTypes = null;
+
     private boolean mReportRead = false;
     private int mCursorIndex = 0;
 
@@ -77,6 +83,8 @@
     }
 
     public String getDateTimeString() {
+        /* TODO: if the feature bit mask of the client supports it, add the time-zone
+         *       (as for MSETime) */
         SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
         Date date = new Date(mDateTime);
         return format.format(date); // Format to YYYYMMDDTHHMMSS local time
@@ -138,7 +146,8 @@
         return mType;
     }
 
-    public void setType(TYPE type) {
+    public void setType(TYPE type, boolean appParamSet) {
+        this.mMsgTypeAppParamSet = appParamSet;
         this.mType = type;
     }
 
@@ -166,6 +175,14 @@
         this.mReceptionStatus = receptionStatus;
     }
 
+    public String getDeliveryStatus() {
+        return mDeliveryStatus;
+    }
+
+    public void setDeliveryStatus(String deliveryStatus) {
+        this.mDeliveryStatus = deliveryStatus;
+    }
+
     public int getAttachmentSize() {
         return mAttachmentSize;
     }
@@ -174,6 +191,14 @@
         this.mAttachmentSize = attachmentSize;
     }
 
+    public String getAttachmentMimeTypes() {
+        return mAttachmentMimeTypes;
+    }
+
+    public void setAttachmentMimeTypes(String attachmentMimeTypes) {
+        this.mAttachmentMimeTypes = attachmentMimeTypes;
+    }
+
     public String getPriority() {
         return mPriority;
     }
@@ -188,7 +213,6 @@
     public boolean getReadBool() {
         return mRead;
     }
-
     public void setRead(boolean read, boolean reportRead) {
         this.mRead = read;
         this.mReportRead = reportRead;
@@ -210,12 +234,28 @@
         this.mProtect = protect;
     }
 
-    public void setThreadId(long threadId) {
+    public void setThreadId(long threadId, TYPE type) {
         if(threadId != -1) {
-            this.mThreadId = BluetoothMapUtils.getLongAsString(threadId);
+            this.mThreadId = BluetoothMapUtils.getMapConvoHandle(threadId, type);
         }
     }
 
+    public String getThreadName() {
+        return mThreadName;
+    }
+
+    public void setThreadName(String name) {
+        this.mThreadName = name;
+    }
+
+    public String getFolderType() {
+        return mFolderType;
+    }
+
+    public void setFolderType(String folderType) {
+        this.mFolderType = folderType;
+    }
+
     public int compareTo(BluetoothMapMessageListingElement e) {
         if (this.mDateTime < e.mDateTime) {
             return 1;
@@ -226,54 +266,35 @@
         }
     }
 
-    /**
-     * Strip away any illegal XML characters, that would otherwise cause the
-     * xml serializer to throw an exception.
-     * Examples of such characters are the emojis used on Android.
-     * @param text The string to validate
-     * @return the same string if valid, otherwise a new String stripped for
-     * any illegal characters
-     */
-    private static String stripInvalidChars(String text) {
-        char out[] = new char[text.length()];
-        int i, o, l;
-        for(i=0, o=0, l=text.length(); i<l; i++){
-            char c = text.charAt(i);
-            if((c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd)) {
-                out[o++] = c;
-            } // Else we skip the character
-        }
-
-        if(i==o) {
-            return text;
-        } else { // We removed some characters, create the new string
-            return new String(out,0,o);
-        }
-    }
-
     /* Encode the MapMessageListingElement into the StringBuilder reference.
      * */
-    public void encode(XmlSerializer xmlMsgElement, boolean includeThreadId) throws IllegalArgumentException, IllegalStateException, IOException
+    public void encode(XmlSerializer xmlMsgElement, boolean includeThreadId)
+            throws IllegalArgumentException, IllegalStateException, IOException
     {
-
             // contruct the XML tag for a single msg in the msglisting
             xmlMsgElement.startTag(null, "msg");
-            xmlMsgElement.attribute(null, "handle", BluetoothMapUtils.getMapHandle(mCpHandle, mType));
-            if(mSubject != null)
-                xmlMsgElement.attribute(null, "subject", stripInvalidChars(mSubject));
+            xmlMsgElement.attribute(null, "handle",
+                    BluetoothMapUtils.getMapHandle(mCpHandle, mType));
+            if(mSubject != null){
+                String stripped = BluetoothMapUtils.stripInvalidChars(mSubject);
+                xmlMsgElement.attribute(null, "subject",
+                        stripped.substring(0,  stripped.length() < 256 ? stripped.length() : 256));
+            }
             if(mDateTime != 0)
                 xmlMsgElement.attribute(null, "datetime", this.getDateTimeString());
             if(mSenderName != null)
-                xmlMsgElement.attribute(null, "sender_name", stripInvalidChars(mSenderName));
+                xmlMsgElement.attribute(null, "sender_name",
+                        BluetoothMapUtils.stripInvalidChars(mSenderName));
             if(mSenderAddressing != null)
                 xmlMsgElement.attribute(null, "sender_addressing", mSenderAddressing);
             if(mReplytoAddressing != null)
                 xmlMsgElement.attribute(null, "replyto_addressing",mReplytoAddressing);
             if(mRecipientName != null)
-                xmlMsgElement.attribute(null, "recipient_name", stripInvalidChars(mRecipientName));
+                xmlMsgElement.attribute(null, "recipient_name",
+                        BluetoothMapUtils.stripInvalidChars(mRecipientName));
             if(mRecipientAddressing != null)
                 xmlMsgElement.attribute(null, "recipient_addressing", mRecipientAddressing);
-            if(mType != null)
+            if(mMsgTypeAppParamSet == true)
                 xmlMsgElement.attribute(null, "type", mType.name());
             if(mSize != -1)
                 xmlMsgElement.attribute(null, "size", Integer.toString(mSize));
@@ -281,8 +302,13 @@
                 xmlMsgElement.attribute(null, "text", mText);
             if(mReceptionStatus != null)
                 xmlMsgElement.attribute(null, "reception_status", mReceptionStatus);
+            if(mDeliveryStatus != null)
+                xmlMsgElement.attribute(null, "delivery_status", mDeliveryStatus);
             if(mAttachmentSize != -1)
-                xmlMsgElement.attribute(null, "attachment_size", Integer.toString(mAttachmentSize));
+                xmlMsgElement.attribute(null, "attachment_size",
+                        Integer.toString(mAttachmentSize));
+            if(mAttachmentMimeTypes != null)
+                xmlMsgElement.attribute(null, "attachment_mime_types", mAttachmentMimeTypes);
             if(mPriority != null)
                 xmlMsgElement.attribute(null, "priority", mPriority);
             if(mReportRead)
@@ -292,7 +318,11 @@
             if(mProtect != null)
                 xmlMsgElement.attribute(null, "protected", mProtect);
             if(mThreadId != null && includeThreadId == true)
-                xmlMsgElement.attribute(null, "thread_id", mThreadId);
+                xmlMsgElement.attribute(null, "conversation_id", mThreadId);
+            if(mThreadName != null && includeThreadId == true)
+                xmlMsgElement.attribute(null, "conversation_name", mThreadName);
+            if(mFolderType != null )
+                xmlMsgElement.attribute(null, "folder_type", mFolderType);
             xmlMsgElement.endTag(null, "msg");
 
     }
diff --git a/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
index f0cdb2d..b5e4e03 100644
--- a/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -1,5 +1,5 @@
 /*
-* Copyright (C) 2014 Samsung System LSI
+* Copyright (C) 2015 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
@@ -22,13 +22,15 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.os.ParcelUuid;
 import android.os.RemoteException;
-import com.android.bluetooth.mapapi.BluetoothMapContract;
 import android.text.format.DateUtils;
 import android.util.Log;
 
-import com.android.bluetooth.map.BluetoothMapUtils;
+import com.android.bluetooth.SignedLongLong;
 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.bluetooth.map.BluetoothMapMasInstance;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -65,38 +67,47 @@
              (byte)0xB0, (byte)0xDE, (byte)0x08, (byte)0x00,
              (byte)0x20, (byte)0x0C, (byte)0x9A, (byte)0x66
              };
-
+    public static final ParcelUuid MAP =
+            ParcelUuid.fromString("00001134-0000-1000-8000-00805F9B34FB");
+    public static final ParcelUuid MNS =
+            ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
+    public static final ParcelUuid MAS =
+            ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB");
     /* Message types */
-    private static final String TYPE_GET_FOLDER_LISTING  = "x-obex/folder-listing";
-    private static final String TYPE_GET_MESSAGE_LISTING = "x-bt/MAP-msg-listing";
-    private static final String TYPE_MESSAGE             = "x-bt/message";
-    private static final String TYPE_SET_MESSAGE_STATUS  = "x-bt/messageStatus";
-    private static final String TYPE_SET_NOTIFICATION_REGISTRATION = "x-bt/MAP-NotificationRegistration";
-    private static final String TYPE_MESSAGE_UPDATE      = "x-bt/MAP-messageUpdate";
+    private static final String TYPE_GET_FOLDER_LISTING              = "x-obex/folder-listing";
+    private static final String TYPE_GET_MESSAGE_LISTING             = "x-bt/MAP-msg-listing";
+    private static final String TYPE_GET_CONVO_LISTING               = "x-bt/MAP-convo-listing";
+    private static final String TYPE_MESSAGE                         = "x-bt/message";
+    private static final String TYPE_SET_MESSAGE_STATUS              = "x-bt/messageStatus";
+    private static final String TYPE_SET_NOTIFICATION_REGISTRATION
+            = "x-bt/MAP-NotificationRegistration";
+    private static final String TYPE_MESSAGE_UPDATE                  = "x-bt/MAP-messageUpdate";
+    private static final String TYPE_GET_MAS_INSTANCE_INFORMATION
+            = "x-bt/MASInstanceInformation";
+    private static final String TYPE_SET_OWNER_STATUS                = "x-bt/participant";
+    private static final String TYPE_SET_NOTIFICATION_FILTER
+            = "x-bt/MAP-notification-filter";
+
+    private static final int MAS_INSTANCE_INFORMATION_LENGTH = 200;
 
     private BluetoothMapFolderElement mCurrentFolder;
-
     private BluetoothMapContentObserver mObserver = null;
-
     private Handler mCallback = null;
-
     private Context mContext;
-
     private boolean mIsAborted = false;
-
     BluetoothMapContent mOutContent;
-
-    private String mBaseEmailUriString = null;
+    private String mBaseUriString = null;
     private long mAccountId = 0;
-    private BluetoothMapEmailSettingsItem mAccount = null;
+    private BluetoothMapAccountItem mAccount = null;
     private Uri mEmailFolderUri = null;
-
     private int mMasId = 0;
+    private BluetoothMapMasInstance mMasInstance; // TODO: change to interface?
     // updated during connect if remote has alternative value
     private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
-
     private boolean mEnableSmsMms = false;
     private boolean mThreadIdSupport = false; // true if peer supports threadId in msg listing
+    // Defaults message version is 1.0 but 1.1+ if feature bit is set
+    private String mMessageVersion = BluetoothMapUtils.MAP_V10_STR;
     private String mAuthority;
     private ContentResolver mResolver;
     private ContentProviderClient mProviderClient = null;
@@ -104,8 +115,8 @@
     public BluetoothMapObexServer(Handler callback,
                                   Context context,
                                   BluetoothMapContentObserver observer,
-                                  int masId,
-                                  BluetoothMapEmailSettingsItem account,
+                                  BluetoothMapMasInstance mas,
+                                  BluetoothMapAccountItem account,
                                   boolean enableSmsMms) throws RemoteException {
         super();
         mCallback = callback;
@@ -113,18 +124,22 @@
         mObserver = observer;
         mEnableSmsMms = enableSmsMms;
         mAccount = account;
-        mMasId = masId;
+        mMasId = mas.getMasId();
+        mMasInstance = mas;
+        mRemoteFeatureMask = mMasInstance.getRemoteFeatureMask();
 
         if(account != null && account.getProviderAuthority() != null) {
             mAccountId = account.getAccountId();
             mAuthority = account.getProviderAuthority();
             mResolver = mContext.getContentResolver();
             if (D) Log.d(TAG, "BluetoothMapObexServer(): accountId=" + mAccountId);
-            mBaseEmailUriString = account.mBase_uri + "/";
-            if (D) Log.d(TAG, "BluetoothMapObexServer(): emailBaseUri=" + mBaseEmailUriString);
-            mEmailFolderUri = BluetoothMapContract.buildFolderUri(mAuthority,
-                                                                  Long.toString(mAccountId));
-            if (D) Log.d(TAG, "BluetoothMapObexServer(): mEmailFolderUri=" + mEmailFolderUri);
+            mBaseUriString = account.mBase_uri + "/";
+            if (D) Log.d(TAG, "BluetoothMapObexServer(): baseUri=" + mBaseUriString);
+            if (account.getType() == TYPE.EMAIL) {
+                mEmailFolderUri = BluetoothMapContract.buildFolderUri(mAuthority,
+                                                                 Long.toString(mAccountId));
+                if (D) Log.d(TAG, "BluetoothMapObexServer(): mEmailFolderUri=" + mEmailFolderUri);
+            }
             mProviderClient = acquireUnstableContentProviderOrThrow();
         }
 
@@ -132,7 +147,7 @@
                                    mCurrentFolder to root folder */
         mObserver.setFolderStructure(mCurrentFolder.getRoot());
 
-        mOutContent = new BluetoothMapContent(mContext, mBaseEmailUriString);
+        mOutContent = new BluetoothMapContent(mContext, mAccount, mMasInstance);
 
     }
 
@@ -140,7 +155,8 @@
      *
      */
     private ContentProviderClient acquireUnstableContentProviderOrThrow() throws RemoteException{
-        ContentProviderClient providerClient = mResolver.acquireUnstableContentProviderClient(mAuthority);
+        ContentProviderClient providerClient =
+                            mResolver.acquireUnstableContentProviderClient(mAuthority);
         if (providerClient == null) {
             throw new RemoteException("Failed to acquire provider for " + mAuthority);
         }
@@ -152,20 +168,42 @@
      * Build the default minimal folder structure, as defined in the MAP specification.
      */
     private void buildFolderStructure() throws RemoteException{
-        mCurrentFolder = new BluetoothMapFolderElement("root", null); // This will be the root element
+        mCurrentFolder = new BluetoothMapFolderElement("root", null);//This will be the root element
+        mCurrentFolder.setHasSmsMmsContent(mEnableSmsMms);
+        boolean hasIM = false;
+        boolean hasEmail = false;
+        if (mAccount != null) {
+           if (mAccount.getType() == TYPE.IM)
+               hasIM = true;
+           if( mAccount.getType() == TYPE.EMAIL)
+               hasEmail = true;
+        }
+        mCurrentFolder.setHasImContent(hasIM);
+        mCurrentFolder.setHasEmailContent(hasEmail);
+
         BluetoothMapFolderElement tmpFolder;
         tmpFolder = mCurrentFolder.addFolder("telecom"); // root/telecom
+        tmpFolder.setHasSmsMmsContent(mEnableSmsMms);
+        tmpFolder.setHasImContent(hasIM);
+        tmpFolder.setHasEmailContent(hasEmail);
+
         tmpFolder = tmpFolder.addFolder("msg");          // root/telecom/msg
+        tmpFolder.setHasSmsMmsContent(mEnableSmsMms);
+        tmpFolder.setHasImContent(hasIM);
+        tmpFolder.setHasEmailContent(hasEmail);
 
-        addBaseFolders(tmpFolder); // Add the mandatory folders
-
+        // Add the mandatory folders
+        addBaseFolders(tmpFolder);
         if(mEnableSmsMms) {
             addSmsMmsFolders(tmpFolder);
         }
-        if(mEmailFolderUri != null) {
+        if(hasEmail) {
             if (D) Log.d(TAG, "buildFolderStructure(): " + mEmailFolderUri.toString());
             addEmailFolders(tmpFolder);
         }
+        if (hasIM) {
+            addImFolders(tmpFolder);
+        }
     }
 
     /**
@@ -173,19 +211,18 @@
      * @param root
      */
     private void addBaseFolders(BluetoothMapFolderElement root) {
-        root.addFolder(BluetoothMapContract.FOLDER_NAME_INBOX);                    // root/telecom/msg/inbox
+        root.addFolder(BluetoothMapContract.FOLDER_NAME_INBOX);         // root/telecom/msg/inbox
         root.addFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX);
         root.addFolder(BluetoothMapContract.FOLDER_NAME_SENT);
         root.addFolder(BluetoothMapContract.FOLDER_NAME_DELETED);
     }
 
-
     /**
      * Add
      * @param root
      */
     private void addSmsMmsFolders(BluetoothMapFolderElement root) {
-        root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_INBOX);                    // root/telecom/msg/inbox
+        root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_INBOX);   // root/telecom/msg/inbox
         root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX);
         root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_SENT);
         root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_DELETED);
@@ -193,17 +230,32 @@
     }
 
 
+    private void addImFolders(BluetoothMapFolderElement root) throws RemoteException {
+        // Select all parent folders
+        root.addImFolder(BluetoothMapContract.FOLDER_NAME_INBOX,
+                BluetoothMapContract.FOLDER_ID_INBOX);       // root/telecom/msg/inbox
+        root.addImFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX,
+                BluetoothMapContract.FOLDER_ID_OUTBOX);
+        root.addImFolder(BluetoothMapContract.FOLDER_NAME_SENT,
+                BluetoothMapContract.FOLDER_ID_SENT);
+        root.addImFolder(BluetoothMapContract.FOLDER_NAME_DELETED,
+                BluetoothMapContract.FOLDER_ID_DELETED);
+        root.addImFolder(BluetoothMapContract.FOLDER_NAME_DRAFT,
+                BluetoothMapContract.FOLDER_ID_DRAFT);
+    }
+
     /**
      * Recursively adds folders based on the folders in the email content provider.
      *       Add a content observer? - to refresh the folder list if any change occurs.
-     *       Consider simply deleting the entire table, and then rebuild using buildFolderStructure()
+     *       Consider simply deleting the entire table, and then rebuild using
+     *       buildFolderStructure()
      *       WARNING: there is no way to notify the client about these changes - hence
      *       we need to either keep the folder structure constant, disconnect or fail anything
      *       referring to currentFolder.
      *       It is unclear what to set as current folder to be able to go one level up...
      *       The best solution would be to keep the folder structure constant during a connection.
      * @param folder the parent folder to which subFolders needs to be added. The
-     *        folder.getEmailFolderId() will be used to query sub-folders.
+     *        folder.getFolderId() will be used to query sub-folders.
      *        Use a parentFolder with id -1 to get all folders from root.
      */
     private void addEmailFolders(BluetoothMapFolderElement parentFolder) throws RemoteException {
@@ -211,15 +263,22 @@
         BluetoothMapFolderElement newFolder;
 
         String where = BluetoothMapContract.FolderColumns.PARENT_FOLDER_ID +
-                        " = " + parentFolder.getEmailFolderId();
+                        " = " + parentFolder.getFolderId();
         Cursor c = mProviderClient.query(mEmailFolderUri,
                         BluetoothMapContract.BT_FOLDER_PROJECTION, where, null, null);
         try {
-            while (c != null && c.moveToNext()) {
-                String name = c.getString(c.getColumnIndex(BluetoothMapContract.FolderColumns.NAME));
-                long id = c.getLong(c.getColumnIndex(BluetoothMapContract.FolderColumns._ID));
-                newFolder = parentFolder.addEmailFolder(name, id);
-                addEmailFolders(newFolder); // Use recursion to add any sub folders
+            if (c != null) {
+                c.moveToPosition(-1);
+                while (c.moveToNext()) {
+                    String name = c.getString(c.getColumnIndex(
+                            BluetoothMapContract.FolderColumns.NAME));
+                    long id = c.getLong(c.getColumnIndex(BluetoothMapContract.FolderColumns._ID));
+                    newFolder = parentFolder.addEmailFolder(name, id);
+                    addEmailFolders(newFolder); // Use recursion to add any sub folders
+                }
+
+            } else {
+                if (D) Log.d(TAG, "addEmailFolders(): no elements found");
             }
         } finally {
             if (c != null) c.close();
@@ -232,11 +291,22 @@
         return true;
     }
 
+    public int getRemoteFeatureMask() {
+        return mRemoteFeatureMask;
+    }
+
+    public void setRemoteFeatureMask(int mRemoteFeatureMask) {
+        if(D) Log.d(TAG, "setRemoteFeatureMask() " + Integer.toHexString(mRemoteFeatureMask));
+        this.mRemoteFeatureMask = mRemoteFeatureMask;
+        this.mOutContent.setRemoteFeatureMask(mRemoteFeatureMask);
+    }
+
     @Override
     public int onConnect(final HeaderSet request, HeaderSet reply) {
         if (D) Log.d(TAG, "onConnect():");
         if (V) logHeader(request);
         mThreadIdSupport = false; // Always assume not supported at new connect.
+        mMessageVersion = BluetoothMapUtils.MAP_V10_STR;//always assume version 1.0 to start with
         notifyUpdateWakeLock();
         Long threadedMailKey = null;
         try {
@@ -283,6 +353,16 @@
             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
         }
 
+        if((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)
+                == BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) {
+            mThreadIdSupport = true;
+        }
+
+        if((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT)
+                == BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT){
+            mMessageVersion = BluetoothMapUtils.MAP_V11_STR;
+        }
+
         if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " +
                 "MSG_SESSION_ESTABLISHED msg.");
 
@@ -329,8 +409,8 @@
 
         try {
             request = op.getReceivedHeader();
+            if (V) logHeader(request);
             type = (String)request.getHeader(HeaderSet.TYPE);
-
             name = (String)request.getHeader(HeaderSet.NAME);
             appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
             if(appParamRaw != null)
@@ -347,11 +427,19 @@
                             + appParams.getNotificationStatus());
                 }
                 return mObserver.setNotificationRegistration(appParams.getNotificationStatus());
-            }else if(type.equals(TYPE_SET_MESSAGE_STATUS)) {
+            }else if(type.equals(TYPE_SET_NOTIFICATION_FILTER)) {
                 if(V) {
-                    Log.d(TAG,"TYPE_SET_MESSAGE_STATUS: StatusIndicator: "
-                            + appParams.getStatusIndicator()
-                            + ", StatusValue: " + appParams.getStatusValue());
+                    Log.d(TAG,"TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: "
+                            + appParams.getNotificationFilter());
+                }
+                mObserver.setNotificationFilter(appParams.getNotificationFilter());
+                return ResponseCodes.OBEX_HTTP_OK;
+            } else if(type.equals(TYPE_SET_MESSAGE_STATUS)) {
+                if(V) {
+                    Log.d(TAG,"TYPE_SET_MESSAGE_STATUS: " +
+                              "StatusIndicator: " + appParams.getStatusIndicator()
+                            + ", StatusValue: " + appParams.getStatusValue()
+                            + ", ExtentedData: " + "" ); // TODO:   appParams.getExtendedImData());
                 }
                 return setMessageStatus(name, appParams);
             } else if (type.equals(TYPE_MESSAGE)) {
@@ -360,8 +448,19 @@
                             + ", retry: " + appParams.getRetry()
                             + ", charset: " + appParams.getCharset());
                 }
-                return pushMessage(op, name, appParams);
+                return pushMessage(op, name, appParams, mMessageVersion);
+            } else if (type.equals(TYPE_SET_OWNER_STATUS)) {
+                if(V) {
+                    Log.d(TAG,"TYPE_SET_OWNER_STATUS:" +
+                          " PresenceAvailability " + appParams.getPresenceAvailability() +
+                          ", PresenceStatus: " + appParams.getPresenceStatus() +
+                          ", LastActivity: " + appParams.getLastActivityString() +
+                          ", ChatStatus: " + appParams.getChatState() +
+                          ", ChatStatusConvoId: " + appParams.getChatStateConvoIdString());
+                }
+                return setOwnerStatus(name, appParams);
             }
+
         } catch (RemoteException e){
             //reload the providerClient and return error
             try {
@@ -388,18 +487,18 @@
 
     private int updateInbox() throws RemoteException{
         if (mAccount != null) {
-            BluetoothMapFolderElement inboxFolder = mCurrentFolder.getEmailFolderByName(
+            BluetoothMapFolderElement inboxFolder = mCurrentFolder.getFolderByName(
                     BluetoothMapContract.FOLDER_NAME_INBOX);
             if (inboxFolder != null) {
                 long accountId = mAccountId;
                 if (D) Log.d(TAG,"updateInbox inbox=" + inboxFolder.getName() + "id="
-                        + inboxFolder.getEmailFolderId());
+                        + inboxFolder.getFolderId());
 
                 final Bundle extras = new Bundle(2);
                 if (accountId != -1) {
                     if (D) Log.d(TAG,"updateInbox accountId=" + accountId);
                     extras.putLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID,
-                            inboxFolder.getEmailFolderId());
+                            inboxFolder.getFolderId());
                     extras.putLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, accountId);
                 } else {
                     // Only error code allowed on an UpdateInbox is OBEX_HTTP_NOT_IMPLEMENTED,
@@ -408,11 +507,12 @@
                     return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
                 }
 
-                Uri emailUri = Uri.parse(mBaseEmailUriString);
+                Uri emailUri = Uri.parse(mBaseUriString);
                 if (D) Log.d(TAG,"updateInbox in: " + emailUri.toString());
                 try {
                     if (D) Log.d(TAG,"updateInbox call()...");
-                    Bundle myBundle = mProviderClient.call(BluetoothMapContract.METHOD_UPDATE_FOLDER, null, extras);
+                    Bundle myBundle = mProviderClient.call(
+                            BluetoothMapContract.METHOD_UPDATE_FOLDER, null, extras);
                     if (myBundle != null)
                         return ResponseCodes.OBEX_HTTP_OK;
                     else {
@@ -445,13 +545,16 @@
                              + folderElement.getName());
         } else {
             folderElement = mCurrentFolder.getSubFolder(folderName);
-            if(D) Log.d(TAG, "Folder name: " + folderName + " resulted in this element: "
-                    + folderElement.getName());
+            if (folderElement != null) {
+                if(D) Log.d(TAG, "Folder name: " + folderName +
+                                 " resulted in this element: " + folderElement.getName());
+            }
         }
         return folderElement;
     }
 
-    private int pushMessage(final Operation op, String folderName, BluetoothMapAppParams appParams) {
+    private int pushMessage(final Operation op, String folderName,
+            BluetoothMapAppParams appParams, String messageVersion) {
         if(appParams.getCharset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
             if(D) Log.d(TAG, "pushMessage: Missing charset - unable to decode message content. " +
                     "appParams.getCharset() = " + appParams.getCharset());
@@ -466,8 +569,8 @@
             } else {
                 folderName = folderElement.getName();
             }
-            if (!folderName.equals(BluetoothMapContract.FOLDER_NAME_OUTBOX) &&
-                    !folderName.equals(BluetoothMapContract.FOLDER_NAME_DRAFT)) {
+            if (!folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX) &&
+                    !folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) {
                 if(D) Log.d(TAG, "pushMessage: Is only allowed to outbox and draft. " +
                         "folderName=" + folderName);
                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
@@ -481,6 +584,7 @@
             bMsgStream = op.openInputStream();
             // Decode the messageBody
             message = BluetoothMapbMessage.parse(bMsgStream, appParams.getCharset());
+            message.setVersionString(messageVersion);
             // Send message
             if (mObserver == null || message == null) {
                 // Should not happen except at shutdown.
@@ -488,14 +592,16 @@
                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
             }
 
-            if ((message.getType().equals(TYPE.EMAIL) && (folderElement.getEmailFolderId() == -1)) ||
-                ((message.getType().equals(TYPE.SMS_GSM) || message.getType().equals(TYPE.SMS_CDMA) ||
-                  message.getType().equals(TYPE.MMS)) && !folderElement.hasSmsMmsContent()) ) {
+            if ((message.getType().equals(TYPE.EMAIL) && (folderElement.getFolderId() == -1))
+                    || ((message.getType().equals(TYPE.SMS_GSM) ||
+                         message.getType().equals(TYPE.SMS_CDMA) ||
+                         message.getType().equals(TYPE.MMS))
+                         && !folderElement.hasSmsMmsContent()) ) {
                 if(D) Log.w(TAG, "Wrong message type recieved" );
                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
             }
 
-            long handle = mObserver.pushMessage(message, folderElement, appParams, mBaseEmailUriString);
+            long handle = mObserver.pushMessage(message, folderElement, appParams, mBaseUriString);
             if (D) Log.d(TAG, "pushMessage handle: " + handle);
             if (handle < 0) {
                 if(D) Log.w(TAG, "Message  handle not created" );
@@ -503,7 +609,8 @@
             }
             HeaderSet replyHeaders = new HeaderSet();
             String handleStr = BluetoothMapUtils.getMapHandle(handle, message.getType());
-            if (D) Log.d(TAG, "handleStr: " + handleStr + " message.getType(): " + message.getType());
+            if (D) Log.d(TAG, "handleStr: " + handleStr + " message.getType(): "
+                               + message.getType());
             replyHeaders.setHeader(HeaderSet.NAME, handleStr);
             op.sendHeaders(replyHeaders);
 
@@ -513,6 +620,7 @@
                 mProviderClient = acquireUnstableContentProviderOrThrow();
             }catch (RemoteException e2){
                 //should not happen
+                if(D) Log.w(TAG, "acquireUnstableContentProviderOrThrow FAILED");
             }
             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
         } catch (IllegalArgumentException e) {
@@ -542,23 +650,27 @@
     private int setMessageStatus(String msgHandle, BluetoothMapAppParams appParams) {
         int indicator = appParams.getStatusIndicator();
         int value = appParams.getStatusValue();
+        String extendedData = ""; // TODO: appParams.getExtendedImData();
+
         long handle;
         BluetoothMapUtils.TYPE msgType;
 
-        if(indicator == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
-           value == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
-           msgHandle == null) {
+        if (msgHandle == null) {
+            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+        } else if ((indicator == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
+                   value == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) &&
+                   extendedData == null) {
             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
         }
         if (mObserver == null) {
-            if(D) Log.d(TAG, "Error: no mObserver!");
+            if(D) Log.e(TAG, "Error: no mObserver!");
             return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
         }
 
         try {
             handle = BluetoothMapUtils.getCpHandle(msgHandle);
             msgType = BluetoothMapUtils.getMsgTypeFromHandle(msgHandle);
-            if(D)Log.d(TAG,"setMessageStatus. Handle:" + handle+", MsgType: "+ msgType);
+            if(D) Log.d(TAG,"setMessageStatus. Handle:" + handle+", MsgType: "+ msgType);
         } catch (NumberFormatException e) {
             Log.w(TAG, "Wrongly formatted message handle: " + msgHandle);
             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
@@ -566,23 +678,89 @@
 
         if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_DELETED) {
             if (!mObserver.setMessageStatusDeleted(handle, msgType, mCurrentFolder,
-                    mBaseEmailUriString, value)) {
+                    mBaseUriString, value)) {
+                if(D) Log.w(TAG,"setMessageStatusDeleted failed");
                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
             }
-        } else /* BluetoothMapAppParams.STATUS_INDICATOR_READ */ {
-            try{
-            if (!mObserver.setMessageStatusRead(handle, msgType, mBaseEmailUriString, value)) {
-                if(D)Log.d(TAG,"not able to update the message");
-                return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
-            }
-            }catch(RemoteException e) {
-                if(D) Log.e(TAG,"Error in setMessageStatusRead()", e);
+        } else if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_READ) {
+            try {
+                if (!mObserver.setMessageStatusRead(handle, msgType, mBaseUriString, value)) {
+                    if(D) Log.w(TAG,"not able to update the message");
+                    return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+                }
+            } catch (RemoteException e) {
+                if(D) Log.w(TAG,"Error in setMessageStatusRead()", e);
                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
             }
         }
+        if (extendedData != null) {
+
+        }
+
         return ResponseCodes.OBEX_HTTP_OK;
     }
 
+    private int setOwnerStatus(String msgHandle, BluetoothMapAppParams appParams)
+            throws RemoteException{
+        // This does only work for IM
+        if (mAccount != null && mAccount.getType() == BluetoothMapUtils.TYPE.IM) {
+            final Bundle extras = new Bundle(5);
+
+            int presenceState = appParams.getPresenceAvailability();
+            String presenceStatus = appParams.getPresenceStatus();
+            long lastActivity = appParams.getLastActivity();
+            int chatState = appParams.getChatState();
+            String chatStatusConvoId = appParams.getChatStateConvoIdString();
+
+            if(presenceState == BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
+               presenceStatus == null &&
+               lastActivity == BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
+               chatState == BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
+               chatStatusConvoId == null) {
+                return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+            }
+
+            if(presenceState != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
+                extras.putInt(BluetoothMapContract.EXTRA_PRESENCE_STATE, presenceState);
+            }
+            if (presenceStatus != null){
+                extras.putString(BluetoothMapContract.EXTRA_PRESENCE_STATUS, presenceStatus);
+            }
+            if (lastActivity != BluetoothMapAppParams.INVALID_VALUE_PARAMETER){
+                extras.putLong(BluetoothMapContract.EXTRA_LAST_ACTIVE, lastActivity);
+            }
+            if (chatState != BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
+                chatStatusConvoId != null){
+                extras.putInt(BluetoothMapContract.EXTRA_CHAT_STATE, chatState);
+                extras.putString(BluetoothMapContract.EXTRA_CONVERSATION_ID, chatStatusConvoId);
+            }
+
+            Uri uri = Uri.parse(mBaseUriString);
+            if (D) Log.d(TAG,"setOwnerStatus in: " + uri.toString());
+            try {
+                if (D) Log.d(TAG,"setOwnerStatus call()...");
+                Bundle myBundle = mProviderClient.call(
+                        BluetoothMapContract.METHOD_SET_OWNER_STATUS, null, extras);
+                if (myBundle != null)
+                    return ResponseCodes.OBEX_HTTP_OK;
+                else {
+                    if (D) Log.d(TAG,"setOwnerStatus call failed");
+                    return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
+                }
+            } catch (RemoteException e){
+                mProviderClient = acquireUnstableContentProviderOrThrow();
+                return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+            } catch (NullPointerException e) {
+                if(D) Log.e(TAG, "setOwnerStatus - if uri or method is null", e);
+                return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+            } catch (IllegalArgumentException e) {
+                if(D) Log.e(TAG, "setOwnerStatus - if uri is not known", e);
+                return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+            }
+        }
+        return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+    }
+
     @Override
     public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
             final boolean create) {
@@ -601,8 +779,9 @@
         }
 
         if (V) logHeader(request);
-        if (D) Log.d(TAG, "onSetPath name is " + folderName + " backup: " + backup
-                     + " create: " + create);
+        if (D) Log.d(TAG, "onSetPath name is " + folderName +
+                          " backup: " + backup +
+                          " create: " + create);
 
         if(backup == true){
             if(mCurrentFolder.getParent() != null)
@@ -655,13 +834,13 @@
         try {
             request = op.getReceivedHeader();
             type = (String)request.getHeader(HeaderSet.TYPE);
-            name = (String)request.getHeader(HeaderSet.NAME);
+
             appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
             if(appParamRaw != null)
                 appParams = new BluetoothMapAppParams(appParamRaw);
 
             if (V) logHeader(request);
-            if (D) Log.d(TAG, "OnGet type is " + type + " name is " + name);
+            if (D) Log.d(TAG, "OnGet type is " + type );
 
             if (type == null) {
                 if (V) Log.d(TAG, "type is null?" + type);
@@ -670,32 +849,68 @@
 
             if (type.equals(TYPE_GET_FOLDER_LISTING)) {
                 if (V && appParams != null) {
-                    Log.d(TAG,"TYPE_GET_FOLDER_LISTING: MaxListCount = " + appParams.getMaxListCount() +
-                              ", ListStartOffset = " + appParams.getStartOffset());
+                    Log.d(TAG,"TYPE_GET_FOLDER_LISTING: MaxListCount = "
+                            + appParams.getMaxListCount()
+                            + ", ListStartOffset = " + appParams.getStartOffset());
                 }
-                return sendFolderListingRsp(op, appParams); // Block until all packets have been send.
+                // Block until all packets have been send.
+                return sendFolderListingRsp(op, appParams);
             } else if (type.equals(TYPE_GET_MESSAGE_LISTING)){
+                name = (String)request.getHeader(HeaderSet.NAME);
                 if (V && appParams != null) {
-                    Log.d(TAG,"TYPE_GET_MESSAGE_LISTING: MaxListCount = " + appParams.getMaxListCount() +
-                              ", ListStartOffset = " + appParams.getStartOffset());
-                    Log.d(TAG,"SubjectLength = " + appParams.getSubjectLength() + ", ParameterMask = " +
-                              appParams.getParameterMask());
-                    Log.d(TAG,"FilterMessageType = " + appParams.getFilterMessageType() +
-                              ", FilterPeriodBegin = " + appParams.getFilterPeriodBegin());
-                    Log.d(TAG,"FilterPeriodEnd = " + appParams.getFilterPeriodBegin() +
-                              ", FilterReadStatus = " + appParams.getFilterReadStatus());
+                    Log.d(TAG,"TYPE_GET_MESSAGE_LISTING: folder name is: " + name +
+                            ", MaxListCount = " + appParams.getMaxListCount() +
+                            ", ListStartOffset = " + appParams.getStartOffset());
+                    Log.d(TAG,"SubjectLength = " + appParams.getSubjectLength() +
+                            ", ParameterMask = " + appParams.getParameterMask());
+                    Log.d(TAG,"FilterMessageType = " + appParams.getFilterMessageType() );
+                    Log.d(TAG,"FilterPeriodBegin = " + appParams.getFilterPeriodBeginString() +
+                            ", FilterPeriodEnd = " + appParams.getFilterPeriodEndString() +
+                            ", FilterReadStatus = " + appParams.getFilterReadStatus());
                     Log.d(TAG,"FilterRecipient = " + appParams.getFilterRecipient() +
-                              ", FilterOriginator = " + appParams.getFilterOriginator());
+                            ", FilterOriginator = " + appParams.getFilterOriginator());
                     Log.d(TAG,"FilterPriority = " + appParams.getFilterPriority());
+                    long tmpLong = appParams.getFilterMsgHandle();
+                    Log.d(TAG,"FilterMsgHandle = " + (
+                            (tmpLong == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) ? "" :
+                                Long.toHexString(tmpLong)));
+                    SignedLongLong tmpLongLong = appParams.getFilterConvoId();
+                    Log.d(TAG,"FilterConvoId = " + ((tmpLongLong == null) ? "" :
+                        Long.toHexString(tmpLongLong.getLeastSignificantBits()) ) );
                 }
-                return sendMessageListingRsp(op, appParams, name); // Block until all packets have been send.
-            } else if (type.equals(TYPE_MESSAGE)){
+                // Block until all packets have been send.
+                return sendMessageListingRsp(op, appParams, name);
+
+            } else if (type.equals(TYPE_GET_CONVO_LISTING)){
+                name = (String)request.getHeader(HeaderSet.NAME);
+                if (V && appParams != null) {
+                    Log.d(TAG,"TYPE_GET_CONVO_LISTING: name is" + name +
+                            ", MaxListCount = " + appParams.getMaxListCount() +
+                            ", ListStartOffset = " + appParams.getStartOffset());
+                    Log.d(TAG,"FilterLastActivityBegin = "+appParams.getFilterLastActivityBegin());
+                    Log.d(TAG,"FilterLastActivityEnd = " + appParams.getFilterLastActivityEnd());
+                    Log.d(TAG,"FilterReadStatus = " + appParams.getFilterReadStatus());
+                    Log.d(TAG,"FilterRecipient = " + appParams.getFilterRecipient());
+                }
+                // Block until all packets have been send.
+                return sendConvoListingRsp(op, appParams,name);
+            } else if (type.equals(TYPE_GET_MAS_INSTANCE_INFORMATION)) {
                 if(V && appParams != null) {
-                    Log.d(TAG,"TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() +
+                    Log.d(TAG,"TYPE_MESSAGE (GET): MASInstandeId = "
+                            + appParams.getMasInstanceId());
+                }
+                // Block until all packets have been send.
+                return sendMASInstanceInformationRsp(op, appParams);
+            } else if (type.equals(TYPE_MESSAGE)){
+                name = (String)request.getHeader(HeaderSet.NAME);
+                if(V && appParams != null) {
+                    Log.d(TAG,"TYPE_MESSAGE (GET): name is" + name +
+                            ", Attachment = " + appParams.getAttachment() +
                             ", Charset = " + appParams.getCharset() +
                             ", FractionRequest = " + appParams.getFractionRequest());
                 }
-                return sendGetMessageRsp(op, name, appParams); // Block until all packets have been send.
+                // Block until all packets have been send.
+                return sendGetMessageRsp(op, name, appParams, mMessageVersion);
             } else {
                 Log.w(TAG, "unknown type request: " + type);
                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
@@ -735,7 +950,9 @@
      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
      */
-    private int sendMessageListingRsp(Operation op, BluetoothMapAppParams appParams, String folderName){
+    private int sendMessageListingRsp(Operation op,
+                                      BluetoothMapAppParams appParams,
+                                      String folderName){
         OutputStream outStream = null;
         byte[] outBytes = null;
         int maxChunkSize, bytesToWrite, bytesWritten = 0, listSize;
@@ -749,13 +966,34 @@
             appParams.setStartOffset(0);
         }
 
-        BluetoothMapFolderElement folderToList = getFolderElementFromName(folderName);
-        if(folderToList == null) {
-            Log.w(TAG,"sendMessageListingRsp: folderToList == null - sending OBEX_HTTP_BAD_REQUEST");
-            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        /* MAP Spec 1.3 introduces the following
+         * Messagehandle filtering:
+         * msgListing (messageHandle=X) -> other allowed filters: parametereMask, subjectMaxLength
+         * ConversationID filtering:
+         * msgListing (convoId empty) -> should work as normal msgListing in valid folders
+         * msgListing (convoId=0, no other filters) -> should return all messages in all folders
+         * msgListing (convoId=N, other filters) -> should return all messages in conversationID=N
+         *                                          according to filters requested
+         */
+        BluetoothMapFolderElement folderToList = null;
+        if (appParams.getFilterMsgHandle() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER
+            || appParams.getFilterConvoId() != null) {
+            // If messageHandle or convoId filtering ignore folder
+            Log.v(TAG,"sendMessageListingRsp: ignore folder ");
+            folderToList = mCurrentFolder.getRoot();
+            folderToList.setIngore(true);
+        } else {
+            folderToList = getFolderElementFromName(folderName);
+            if(folderToList == null) {
+                Log.w(TAG,"sendMessageListingRsp: folderToList == "+
+                        "null-sending OBEX_HTTP_BAD_REQUEST");
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+            }
+            Log.v(TAG,"sendMessageListingRsp: has sms " + folderToList.hasSmsMmsContent() +
+                    "has email " + folderToList.hasEmailContent() +
+                    "has IM " + folderToList.hasImContent() );
         }
 
-        // Check to see if we only need to send the size - hence no need to encode.
         try {
             // Open the OBEX body stream
             outStream = op.openOutputStream();
@@ -766,29 +1004,46 @@
             if(appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
                 appParams.setStartOffset(0);
 
+            // Check to see if we only need to send the size - hence no need to encode.
             if(appParams.getMaxListCount() != 0) {
                 outList = mOutContent.msgListing(folderToList, appParams);
                 // Generate the byte stream
                 outAppParams.setMessageListingSize(outList.getCount());
-                outBytes = outList.encode(mThreadIdSupport); // Include thread ID for clients that supports it.
+                String version;
+                if(0 < (mRemoteFeatureMask &
+                        BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)) {
+                    version = BluetoothMapUtils.MAP_V11_STR;
+                } else {
+                    version = BluetoothMapUtils.MAP_V10_STR;
+                }
+                /* This will only set the version, the bit must also be checked before adding any
+                 * 1.1 bits to the listing. */
+                outBytes = outList.encode(mThreadIdSupport, version);
                 hasUnread = outList.hasUnread();
-            }
-            else {
+            } else {
                 listSize = mOutContent.msgListingSize(folderToList, appParams);
                 hasUnread = mOutContent.msgListingHasUnread(folderToList, appParams);
                 outAppParams.setMessageListingSize(listSize);
                 op.noBodyHeader();
             }
-
+            folderToList.setIngore(false);
             // Build the application parameter header
-
             // let the peer know if there are unread messages in the list
             if(hasUnread) {
                 outAppParams.setNewMessage(1);
             }else{
                 outAppParams.setNewMessage(0);
             }
-
+            if ((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_DATABASE_INDENTIFIER_BIT)
+                    == BluetoothMapUtils.MAP_FEATURE_DATABASE_INDENTIFIER_BIT ) {
+                outAppParams.setDatabaseIdentifier(0, mMasInstance.getDbIdentifier());
+            }
+            if((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT)
+                    == BluetoothMapUtils.MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT) {
+                // Force update of version counter if needed
+                mObserver.refreshFolderVersionCounter();
+                outAppParams.setFolderVerCounter(mMasInstance.getFolderVersionCounter(), 0);
+            }
             outAppParams.setMseTime(Calendar.getInstance().getTime().getTime());
             replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
             op.sendHeaders(replyHeaders);
@@ -803,7 +1058,8 @@
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
             }
         } catch (IllegalArgumentException e) {
-            Log.w(TAG,"sendMessageListingRsp: IllegalArgumentException - sending OBEX_HTTP_BAD_REQUEST", e);
+            Log.w(TAG,"sendMessageListingRsp: IllegalArgumentException"+
+                                            " - sending OBEX_HTTP_BAD_REQUEST", e);
             if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
         }
@@ -823,7 +1079,167 @@
                 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
             }
             if(bytesWritten != outBytes.length && !mIsAborted) {
-                Log.w(TAG,"sendMessageListingRsp: bytesWritten != outBytes.length - sending OBEX_HTTP_BAD_REQUEST");
+                Log.w(TAG,"sendMessageListingRsp: bytesWritten != outBytes.length" +
+                        " - sending OBEX_HTTP_BAD_REQUEST");
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+            }
+        } else {
+            if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
+        }
+        return ResponseCodes.OBEX_HTTP_OK;
+    }
+
+    /**
+     * Update the {@link BluetoothMapAppParams} object message type filter mask to only contain
+     * message types supported by this mas instance.
+     * Could the folder be used in stead?
+     * @param appParams Reference to the object to update
+     * @param overwrite True: The msgType will be overwritten to match the message types supported
+     * by this MAS instance. False: any unsupported message types will be masked out.
+     */
+    private void setMsgTypeFilterParams(BluetoothMapAppParams appParams, boolean overwrite) {
+        int masFilterMask = 0;
+        if(!mEnableSmsMms) {
+            masFilterMask |= BluetoothMapAppParams.FILTER_NO_SMS_CDMA;
+            masFilterMask |= BluetoothMapAppParams.FILTER_NO_SMS_GSM;
+            masFilterMask |= BluetoothMapAppParams.FILTER_NO_MMS;
+        }
+        if(mAccount==null){
+            masFilterMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
+            masFilterMask |= BluetoothMapAppParams.FILTER_NO_IM;
+        } else {
+            if(!(mAccount.getType() == BluetoothMapUtils.TYPE.EMAIL)) {
+                masFilterMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
+            }
+            if(!(mAccount.getType() == BluetoothMapUtils.TYPE.IM)) {
+                masFilterMask |= BluetoothMapAppParams.FILTER_NO_IM;
+            }
+        }
+        if(overwrite) {
+            appParams.setFilterMessageType(masFilterMask);
+        } else {
+            int newMask = appParams.getFilterMessageType();
+            if(newMask == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
+                appParams.setFilterMessageType(newMask);
+            } else {
+                newMask |= masFilterMask;
+                appParams.setFilterMessageType(newMask);
+            }
+        }
+    }
+
+    /**
+     * Generate and send the Conversation listing response based on an application
+     * parameter header. This function call will block until complete or aborted
+     * by the peer. Fragmentation of packets larger than the obex packet size
+     * will be handled by this function.
+     *
+     * @param op
+     *            The OBEX operation.
+     * @param appParams
+     *            The application parameter header
+     * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
+     *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
+     */
+    private int sendConvoListingRsp(Operation op,
+                                    BluetoothMapAppParams appParams,
+                                    String folderName){
+        OutputStream outStream = null;
+        byte[] outBytes = null;
+        int maxChunkSize, bytesToWrite, bytesWritten = 0;
+        //boolean hasUnread = false;
+        HeaderSet replyHeaders = new HeaderSet();
+        BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
+        BluetoothMapConvoListing outList;
+        if(appParams == null){
+            appParams = new BluetoothMapAppParams();
+            appParams.setMaxListCount(1024);
+            appParams.setStartOffset(0);
+        }
+        // As the app parameters do not carry which message types to list, we set the filter here
+        // to all message types supported by this instance.
+        setMsgTypeFilterParams(appParams, true);
+
+        // Check to see if we only need to send the size - hence no need to encode.
+        try {
+            // Open the OBEX body stream
+            outStream = op.openOutputStream();
+
+            if(appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+                appParams.setMaxListCount(1024);
+
+            if(appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+                appParams.setStartOffset(0);
+
+            if(appParams.getMaxListCount() != 0) {
+                outList = mOutContent.convoListing(appParams, false);
+                outAppParams.setConvoListingSize(outList.getCount());
+                // Generate the byte stream
+                outBytes = outList.encode(); // Include thread ID for clients that supports it.
+      //          hasUnread = outList.hasUnread();
+                if(D) Log.d(TAG, "outBytes size:"+ outBytes.length);
+            } else {
+                outList = mOutContent.convoListing(appParams, true);
+                outAppParams.setConvoListingSize(outList.getCount());
+                if(mEnableSmsMms) {
+                    mOutContent.refreshSmsMmsConvoVersions();
+                }
+                if(mAccount != null) {
+                    mOutContent.refreshImEmailConvoVersions();
+                }
+                // Force update of version counter if needed
+                mObserver.refreshConvoListVersionCounter();
+                if(0 < (mRemoteFeatureMask &
+                        BluetoothMapUtils.MAP_FEATURE_CONVERSATION_VERSION_COUNTER_BIT)) {
+                    outAppParams.setConvoListingVerCounter(
+                            mMasInstance.getCombinedConvoListVersionCounter(), 0);
+                }
+                op.noBodyHeader();
+            }
+            if(D) Log.d(TAG, "outList size:"+ outList.getCount()
+                    + " MaxListCount: "+appParams.getMaxListCount());
+            outList = null; // We don't need it anymore - we might as well give it up for GC
+            outAppParams.setDatabaseIdentifier(0, mMasInstance.getDbIdentifier());
+
+            // Build the application parameter header
+            // The MseTime is not in the CR - but I think it is missing.
+            outAppParams.setMseTime(Calendar.getInstance().getTime().getTime());
+            replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
+            op.sendHeaders(replyHeaders);
+
+        } catch (IOException e) {
+            Log.w(TAG,"sendConvoListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
+            if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
+            if(mIsAborted == true) {
+                if(D) Log.d(TAG, "sendConvoListingRsp Operation Aborted");
+                return ResponseCodes.OBEX_HTTP_OK;
+            } else {
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+            }
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG,"sendConvoListingRsp: IllegalArgumentException" +
+                    " - sending OBEX_HTTP_BAD_REQUEST", e);
+            if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }
+
+        maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
+        if(outBytes != null) {
+            try {
+                while (bytesWritten < outBytes.length && mIsAborted == false) {
+                    bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
+                    outStream.write(outBytes, bytesWritten, bytesToWrite);
+                    bytesWritten += bytesToWrite;
+                }
+            } catch (IOException e) {
+                if(D) Log.w(TAG,e);
+                // We were probably aborted or disconnected
+            } finally {
+                if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
+            }
+            if(bytesWritten != outBytes.length && !mIsAborted) {
+                Log.w(TAG,"sendConvoListingRsp: bytesWritten != outBytes.length" +
+                        " - sending OBEX_HTTP_BAD_REQUEST");
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
             }
         } else {
@@ -882,7 +1298,8 @@
             op.sendHeaders(replyHeaders);
 
         } catch (IOException e1) {
-            Log.w(TAG,"sendFolderListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
+            Log.w(TAG,"sendFolderListingRsp: IOException" +
+                    " - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
             if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
             if(mIsAborted == true) {
                 if(D) Log.d(TAG, "sendFolderListingRsp Operation Aborted");
@@ -891,7 +1308,8 @@
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
             }
         } catch (IllegalArgumentException e1) {
-            Log.w(TAG,"sendFolderListingRsp: IllegalArgumentException - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
+            Log.w(TAG,"sendFolderListingRsp: IllegalArgumentException" +
+                    " - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
             if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
         }
@@ -911,7 +1329,7 @@
                 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
             }
             if(V)
-                Log.v(TAG,"sendFolderList sent " + bytesWritten + " bytes out of "+ outBytes.length);
+                Log.v(TAG,"sendFolderList sent " + bytesWritten+" bytes out of "+ outBytes.length);
             if(bytesWritten == outBytes.length || mIsAborted)
                 return ResponseCodes.OBEX_HTTP_OK;
             else
@@ -922,32 +1340,130 @@
     }
 
     /**
-     * Generate and send the get message response based on an application
-     * parameter header and a handle.
+     * Generate and send the get MAS Instance Information response based on an MAS Instance
      *
      * @param op
      *            The OBEX operation.
      * @param appParams
      *            The application parameter header
-     * @param handle
-     *            The handle of the requested message
      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
      */
-    private int sendGetMessageRsp(Operation op, String handle, BluetoothMapAppParams appParams){
+    private int sendMASInstanceInformationRsp(Operation op, BluetoothMapAppParams appParams){
+
+        OutputStream outStream = null;
+        byte[] outBytes = null;
+        String outString = null;
+        int maxChunkSize, bytesToWrite, bytesWritten = 0;
+
+        try {
+            if(mMasId == appParams.getMasInstanceId()) {
+                if(mAccount != null) {
+                    if(mAccount.getType() == TYPE.EMAIL) {
+                        outString = (mAccount.getName() != null) ? mAccount.getName() :
+                            BluetoothMapMasInstance.TYPE_EMAIL_STR;
+                    } else if(mAccount.getType() == TYPE.IM){
+                        outString = mAccount.getUciFull();
+                        if(outString == null) {
+                            String uci = mAccount.getUci();
+                            // TODO: Do we need to align this with HF/PBAP
+                            StringBuilder sb =
+                                    new StringBuilder(uci == null ? 5 : 5 + uci.length());
+                            sb.append("un");
+                            if(mMasId < 10) {
+                                sb.append("00");
+                            } else if(mMasId < 100) {
+                                sb.append("0");
+                            }
+                            sb.append(mMasId);
+                            if(uci != null) {
+                                sb.append(":").append(uci);
+                            }
+                            outString = sb.toString();
+                        }
+                    }
+                } else {
+                    outString = BluetoothMapMasInstance.TYPE_SMS_MMS_STR;
+                    // TODO: Add phone number if possible
+                }
+            } else {
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+            }
+
+            /* Ensure byte array max length is 200 containing valid UTF-8 characters */
+            outBytes = BluetoothMapUtils.truncateUtf8StringToBytearray(outString,
+                    MAS_INSTANCE_INFORMATION_LENGTH);
+
+            // Open the OBEX body stream
+            outStream = op.openOutputStream();
+
+        } catch (IOException e) {
+            Log.w(TAG,"sendMASInstanceInformationRsp: IOException" +
+                    " - sending OBEX_HTTP_BAD_REQUEST", e);
+            if(mIsAborted == true) {
+                if(D) Log.d(TAG, "sendMASInstanceInformationRsp Operation Aborted");
+                return ResponseCodes.OBEX_HTTP_OK;
+            } else {
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+            }
+        }
+
+        maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
+
+        if(outBytes != null) {
+            try {
+                while (bytesWritten < outBytes.length && mIsAborted == false) {
+                    bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
+                    outStream.write(outBytes, bytesWritten, bytesToWrite);
+                    bytesWritten += bytesToWrite;
+                }
+            } catch (IOException e) {
+                // We were probably aborted or disconnected
+            } finally {
+                if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
+            }
+            if(V)
+                Log.v(TAG,"sendMASInstanceInformationRsp sent " + bytesWritten +
+                        " bytes out of "+ outBytes.length);
+            if(bytesWritten == outBytes.length || mIsAborted)
+                return ResponseCodes.OBEX_HTTP_OK;
+            else
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }
+        return ResponseCodes.OBEX_HTTP_OK;
+    }
+
+    /**
+     * Generate and send the get message response based on an application
+     * parameter header and a handle.
+     *
+     * @param op
+     *            The OBEX operation.
+     * @param handle
+     *            The handle of the requested message
+     * @param appParams
+     *            The application parameter header
+     * @param version
+     *              The string representation of the version number(i.e. "1.0")
+     * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
+     *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
+     */
+    private int sendGetMessageRsp(Operation op, String handle,
+            BluetoothMapAppParams appParams, String version){
         OutputStream outStream = null;
         byte[] outBytes = null;
         int maxChunkSize, bytesToWrite, bytesWritten = 0;
 
         try {
-            outBytes = mOutContent.getMessage(handle, appParams, mCurrentFolder);
+            outBytes = mOutContent.getMessage(handle, appParams, mCurrentFolder, version);
             outStream = op.openOutputStream();
 
             // If it is a fraction request of Email message, set header before responding
-            if ((BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.EMAIL)) &&
+            if ((BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.EMAIL)||
+                    (BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.IM))) &&
                     (appParams.getFractionRequest() ==
                     BluetoothMapAppParams.FRACTION_REQUEST_FIRST)) {
-                BluetoothMapAppParams outAppParams  = new BluetoothMapAppParams();;
+                BluetoothMapAppParams outAppParams  = new BluetoothMapAppParams();
                 HeaderSet replyHeaders = new HeaderSet();
                 outAppParams.setFractionDeliver(BluetoothMapAppParams.FRACTION_DELIVER_LAST);
                 // Build and set the application parameter header
@@ -1000,6 +1516,67 @@
         return ResponseCodes.OBEX_HTTP_OK;
     }
 
+    @Override
+    public int onDelete(HeaderSet request, HeaderSet reply) {
+        if(D) Log.v(TAG, "onDelete() " + request.toString());
+        mIsAborted = false;
+        notifyUpdateWakeLock();
+        String type, name;
+        byte[] appParamRaw;
+        BluetoothMapAppParams appParams = null;
+
+        /* TODO: If this is to be placed here, we need to cleanup - e.g. the exception handling */
+        try {
+            type = (String)request.getHeader(HeaderSet.TYPE);
+
+            name = (String)request.getHeader(HeaderSet.NAME);
+            appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
+            if(appParamRaw != null)
+                appParams = new BluetoothMapAppParams(appParamRaw);
+            if(D) Log.d(TAG,"type = " + type + ", name = " + name);
+            if(type.equals(TYPE_SET_NOTIFICATION_FILTER)) {
+                if(V) {
+                    Log.d(TAG,"TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: "
+                            + appParams.getNotificationFilter());
+                }
+                mObserver.setNotificationFilter(appParams.getNotificationFilter());
+                return ResponseCodes.OBEX_HTTP_OK;
+            }  else if (type.equals(TYPE_SET_OWNER_STATUS)) {
+                if(V) {
+                    Log.d(TAG,"TYPE_SET_OWNER_STATUS:" +
+                          " PresenceAvailability " + appParams.getPresenceAvailability() +
+                          ", PresenceStatus: " + appParams.getPresenceStatus() +
+                          ", LastActivity: " + appParams.getLastActivityString() +
+                          ", ChatStatus: " + appParams.getChatState() +
+                          ", ChatStatusConvoId: " + appParams.getChatStateConvoIdString());
+                }
+                return setOwnerStatus(name, appParams);
+            }
+
+        } catch (RemoteException e){
+            //reload the providerClient and return error
+            try {
+                mProviderClient = acquireUnstableContentProviderOrThrow();
+            }catch (RemoteException e2){
+                //should not happen
+            }
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }catch (Exception e) {
+
+            if(D) {
+                Log.e(TAG, "Exception occured while handling request",e);
+            } else {
+                Log.e(TAG, "Exception occured while handling request");
+            }
+            if(mIsAborted) {
+                return ResponseCodes.OBEX_HTTP_OK;
+            } else {
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+            }
+        }
+        return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+    }
+
     private void notifyUpdateWakeLock() {
         if(mCallback != null) {
             Message msg = Message.obtain(mCallback);
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
index 7e58b5b..29e08a5 100755
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -61,9 +61,9 @@
      * DEBUG log: "setprop log.tag.BluetoothMapService VERBOSE"
      */
 
-    public static final boolean DEBUG = true;
+    public static final boolean DEBUG = true; //FIXME set to false;
 
-    public static final boolean VERBOSE = false;
+    public static final boolean VERBOSE = true; //FIXME set to false; 
 
     /**
      * Intent indicating timeout for user confirmation, which is sent to
@@ -74,8 +74,8 @@
     private static final int USER_CONFIRM_TIMEOUT_VALUE = 25000;
 
     /** Intent indicating that the email settings activity should be opened*/
-    public static final String ACTION_SHOW_MAPS_EMAIL_SETTINGS =
-            "android.btmap.intent.action.SHOW_MAPS_EMAIL_SETTINGS";
+    public static final String ACTION_SHOW_MAPS_SETTINGS =
+            "android.btmap.intent.action.SHOW_MAPS_SETTINGS";
 
     public static final int MSG_SERVERSESSION_CLOSE = 5000;
 
@@ -123,16 +123,16 @@
     private SparseArray<BluetoothMapMasInstance> mMasInstances =
             new SparseArray<BluetoothMapMasInstance>(1);
     /* mMasInstanceMap: A list of the active MasInstances with the key being the account */
-    private HashMap<BluetoothMapEmailSettingsItem, BluetoothMapMasInstance> mMasInstanceMap =
-            new HashMap<BluetoothMapEmailSettingsItem, BluetoothMapMasInstance>(1);
+    private HashMap<BluetoothMapAccountItem, BluetoothMapMasInstance> mMasInstanceMap =
+            new HashMap<BluetoothMapAccountItem, BluetoothMapMasInstance>(1);
 
     private BluetoothDevice mRemoteDevice = null; // The remote connected device - protect access
 
-    private ArrayList<BluetoothMapEmailSettingsItem> mEnabledAccounts = null;
+    private ArrayList<BluetoothMapAccountItem> mEnabledAccounts = null;
     private static String sRemoteDeviceName = null;
 
     private int mState;
-    private BluetoothMapEmailAppObserver mAppObserver = null;
+    private BluetoothMapAppObserver mAppObserver = null;
     private AlarmManager mAlarmManager = null;
 
     private boolean mIsWaitingAuthorization = false;
@@ -157,6 +157,7 @@
 
     }
 
+
     private final void closeService() {
         if (DEBUG) Log.d(TAG, "MAP Service closeService in");
 
@@ -397,7 +398,8 @@
     };
 
     private void onConnectHandler(int masId) {
-        if (mIsWaitingAuthorization == true || mRemoteDevice == null) {
+        if (mIsWaitingAuthorization == true || mRemoteDevice == null
+        		|| mSdpSearchInitiated == true) {
             return;
         }
         BluetoothMapMasInstance masInst = mMasInstances.get(masId);
@@ -546,7 +548,7 @@
         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
         filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
-        filter.addAction(ACTION_SHOW_MAPS_EMAIL_SETTINGS);
+        filter.addAction(ACTION_SHOW_MAPS_SETTINGS);
         filter.addAction(USER_CONFIRM_TIMEOUT_ACTION);
 
         // We need two filters, since Type only applies to the ACTION_MESSAGE_SENT
@@ -565,7 +567,7 @@
             Log.w(TAG,"Unable to register map receiver",e);
         }
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        mAppObserver = new BluetoothMapEmailAppObserver(this, this);
+        mAppObserver = new BluetoothMapAppObserver(this, this);
 
         mEnabledAccounts = mAppObserver.getEnabledAccountItems();
         // Uses mEnabledAccounts, hence getEnabledAccountItems() must be called before this.
@@ -610,14 +612,14 @@
         boolean changed = false;
 
         if(getState() == BluetoothMap.STATE_DISCONNECTED) {
-            ArrayList<BluetoothMapEmailSettingsItem> newAccountList =
+            ArrayList<BluetoothMapAccountItem> newAccountList =
                     mAppObserver.getEnabledAccountItems();
-            ArrayList<BluetoothMapEmailSettingsItem> newAccounts = null;
-            ArrayList<BluetoothMapEmailSettingsItem> removedAccounts = null;
-            newAccounts = new ArrayList<BluetoothMapEmailSettingsItem>();
+            ArrayList<BluetoothMapAccountItem> newAccounts = null;
+            ArrayList<BluetoothMapAccountItem> removedAccounts = null;
+            newAccounts = new ArrayList<BluetoothMapAccountItem>();
             removedAccounts = mEnabledAccounts; // reuse the current enabled list, to track removed
                                                 // accounts
-            for(BluetoothMapEmailSettingsItem account: newAccountList) {
+            for(BluetoothMapAccountItem account: newAccountList) {
                 if(!removedAccounts.remove(account)) {
                     newAccounts.add(account);
                 }
@@ -625,7 +627,7 @@
 
             if(removedAccounts != null) {
                 /* Remove all disabled/removed accounts */
-                for(BluetoothMapEmailSettingsItem account : removedAccounts) {
+                for(BluetoothMapAccountItem account : removedAccounts) {
                     BluetoothMapMasInstance masInst = mMasInstanceMap.remove(account);
                     if(DEBUG)Log.d(TAG,"  Removing account: " + account + " masInst = " + masInst);
                     if(masInst != null) {
@@ -638,7 +640,7 @@
 
             if(newAccounts != null) {
                 /* Add any newly created accounts */
-                for(BluetoothMapEmailSettingsItem account : newAccounts) {
+                for(BluetoothMapAccountItem account : newAccounts) {
                     if(DEBUG)Log.d(TAG,"  Adding account: " + account);
                     int masId = getNextMasId();
                     BluetoothMapMasInstance newInst =
@@ -659,7 +661,7 @@
             mEnabledAccounts = newAccountList;
             if(VERBOSE) {
                 Log.d(TAG,"  Enabled accounts:");
-                for(BluetoothMapEmailSettingsItem account : mEnabledAccounts) {
+                for(BluetoothMapAccountItem account : mEnabledAccounts) {
                     Log.d(TAG, "   " + account);
                 }
                 Log.d(TAG,"  Active MAS instances:");
@@ -718,7 +720,7 @@
         mMasInstanceMap.put(null, smsMmsInst);
 
         // get list of accounts already set to be visible through MAP
-        for(BluetoothMapEmailSettingsItem account : mEnabledAccounts) {
+        for(BluetoothMapAccountItem account : mEnabledAccounts) {
             masId++;  // SMS/MMS is masId=0, increment before adding next
             BluetoothMapMasInstance newInst =
                     new BluetoothMapMasInstance(this,
@@ -781,6 +783,9 @@
                     setUserTimeoutAlarm();
                 } else if (mPermission == BluetoothDevice.ACCESS_REJECTED) {
                     cancelConnection = true;
+                } else if(mPermission == BluetoothDevice.ACCESS_ALLOWED) {
+                    mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
+                    mSdpSearchInitiated = true;
                 }
             } else if (!mRemoteDevice.equals(remoteDevice)) {
                 Log.w(TAG, "Unexpected connection from a second Remote Device received. name: " +
@@ -894,6 +899,7 @@
         public void onReceive(Context context, Intent intent) {
             if (DEBUG) Log.d(TAG, "onReceive");
             String action = intent.getAction();
+            if (DEBUG) Log.d(TAG, "onReceive: " + action);
             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
                                                BluetoothAdapter.ERROR);
@@ -944,6 +950,7 @@
                                     + result);
                         }
                     }
+
                     mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
                     mSdpSearchInitiated = true;
                 } else {
@@ -961,7 +968,7 @@
                     sendConnectCancelMessage();
                 }
             } else if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)){
-                Log.v(TAG, "Received ACTION_SDP_RECORD.");
+//                Log.v(TAG, "Received ACTION_SDP_RECORD.");
                 ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
                 Log.v(TAG, "Received UUID: " + uuid.toString());
                 Log.v(TAG, "expected UUID: " +
@@ -982,10 +989,10 @@
                     }
                     sendConnectMessage(-1); // -1 indicates all MAS instances
                 }
-            } else if (action.equals(ACTION_SHOW_MAPS_EMAIL_SETTINGS)) {
-                Log.v(TAG, "Received ACTION_SHOW_MAPS_EMAIL_SETTINGS.");
+            } else if (action.equals(ACTION_SHOW_MAPS_SETTINGS)) {
+                Log.v(TAG, "Received ACTION_SHOW_MAPS_SETTINGS.");
 
-                Intent in = new Intent(context, BluetoothMapEmailSettings.class);
+                Intent in = new Intent(context, BluetoothMapSettings.class);
                 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                 context.startActivity(in);
             } else if (action.equals(BluetoothMapContentObserver.ACTION_MESSAGE_SENT)) {
@@ -1151,11 +1158,11 @@
         println(sb, "mAccountChanged: " + mAccountChanged);
         println(sb, "mBluetoothMnsObexClient: " + mBluetoothMnsObexClient);
         println(sb, "mMasInstanceMap:");
-        for (BluetoothMapEmailSettingsItem key : mMasInstanceMap.keySet()) {
+        for (BluetoothMapAccountItem key : mMasInstanceMap.keySet()) {
             println(sb, "  " + key + " : " + mMasInstanceMap.get(key));
         }
         println(sb, "mEnabledAccounts:");
-        for (BluetoothMapEmailSettingsItem account : mEnabledAccounts) {
+        for (BluetoothMapAccountItem account : mEnabledAccounts) {
             println(sb, "  " + account);
         }
     }
diff --git a/src/com/android/bluetooth/map/BluetoothMapEmailSettings.java b/src/com/android/bluetooth/map/BluetoothMapSettings.java
similarity index 63%
rename from src/com/android/bluetooth/map/BluetoothMapEmailSettings.java
rename to src/com/android/bluetooth/map/BluetoothMapSettings.java
index ae9cc32..e02552f 100644
--- a/src/com/android/bluetooth/map/BluetoothMapEmailSettings.java
+++ b/src/com/android/bluetooth/map/BluetoothMapSettings.java
@@ -1,5 +1,5 @@
 /*
-* Copyright (C) 2014 Samsung System LSI
+* Copyright (C) 2015 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
@@ -15,7 +15,7 @@
 
 package com.android.bluetooth.map;
 import com.android.bluetooth.R;
-import com.android.bluetooth.map.BluetoothMapEmailSettingsItem;
+import com.android.bluetooth.map.BluetoothMapAccountItem;
 
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
@@ -25,30 +25,32 @@
 import android.widget.ExpandableListView;
 
 
-public class BluetoothMapEmailSettings extends Activity {
+public class BluetoothMapSettings extends Activity {
 
-    private static final String TAG = "BluetoothMapEmailSettings";
+    private static final String TAG = "BluetoothMapSettings";
     private static final boolean D = BluetoothMapService.DEBUG;
     private static final boolean V = BluetoothMapService.VERBOSE;
 
 
 
-    BluetoothMapEmailSettingsLoader mLoader = new BluetoothMapEmailSettingsLoader(this);
-    LinkedHashMap<BluetoothMapEmailSettingsItem,ArrayList<BluetoothMapEmailSettingsItem>> mGroups;
+    BluetoothMapAccountLoader mLoader = new BluetoothMapAccountLoader(this);
+    LinkedHashMap<BluetoothMapAccountItem,ArrayList<BluetoothMapAccountItem>> mGroups;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         /* set UI */
-        setContentView(R.layout.bluetooth_map_email_settings);
+        setContentView(R.layout.bluetooth_map_settings);
         /* create structure for list of groups + items*/
         mGroups = mLoader.parsePackages(true);
 
 
         /* update expandable listview with correct items */
-        ExpandableListView listView = (ExpandableListView) findViewById(R.id.bluetooth_map_email_settings_list_view);
+        ExpandableListView listView = 
+            (ExpandableListView) findViewById(R.id.bluetooth_map_settings_list_view);
 
-        BluetoothMapEmailSettingsAdapter adapter = new BluetoothMapEmailSettingsAdapter(this,listView, mGroups, mLoader.getAccountsEnabledCount());
+        BluetoothMapSettingsAdapter adapter = new BluetoothMapSettingsAdapter(this,
+                listView, mGroups, mLoader.getAccountsEnabledCount());
         listView.setAdapter(adapter);
     }
 
diff --git a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsAdapter.java b/src/com/android/bluetooth/map/BluetoothMapSettingsAdapter.java
similarity index 70%
rename from src/com/android/bluetooth/map/BluetoothMapEmailSettingsAdapter.java
rename to src/com/android/bluetooth/map/BluetoothMapSettingsAdapter.java
index feca9c0..2a751f2 100644
--- a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsAdapter.java
+++ b/src/com/android/bluetooth/map/BluetoothMapSettingsAdapter.java
@@ -45,29 +45,29 @@
 import android.widget.TextView;
 import android.widget.Toast;
 import android.widget.CompoundButton;
-import com.android.bluetooth.map.BluetoothMapEmailSettingsItem;
-import com.android.bluetooth.map.BluetoothMapEmailSettingsLoader;
-public class BluetoothMapEmailSettingsAdapter extends BaseExpandableListAdapter {
+import com.android.bluetooth.map.BluetoothMapAccountItem;
+import com.android.bluetooth.map.BluetoothMapAccountLoader;
+public class BluetoothMapSettingsAdapter extends BaseExpandableListAdapter {
     private static final boolean D = BluetoothMapService.DEBUG;
     private static final boolean V = BluetoothMapService.VERBOSE;
-    private static final String TAG = "BluetoothMapEmailSettingsAdapter";
+    private static final String TAG = "BluetoothMapSettingsAdapter";
     private boolean mCheckAll = true;
     public LayoutInflater mInflater;
     public Activity mActivity;
     /*needed to prevent random checkbox toggles due to item reuse */
     ArrayList<Boolean> mPositionArray;
-    private LinkedHashMap<BluetoothMapEmailSettingsItem,
-                            ArrayList<BluetoothMapEmailSettingsItem>> mProupList;
-    private ArrayList<BluetoothMapEmailSettingsItem> mMainGroup;
+    private LinkedHashMap<BluetoothMapAccountItem,
+                            ArrayList<BluetoothMapAccountItem>> mProupList;
+    private ArrayList<BluetoothMapAccountItem> mMainGroup;
     private int[] mGroupStatus;
     /* number of accounts possible to share */
     private int mSlotsLeft = 10;
 
 
-    public BluetoothMapEmailSettingsAdapter(Activity act,
+    public BluetoothMapSettingsAdapter(Activity act,
                                             ExpandableListView listView,
-                                            LinkedHashMap<BluetoothMapEmailSettingsItem,
-                                            ArrayList<BluetoothMapEmailSettingsItem>> groupsList,
+                                            LinkedHashMap<BluetoothMapAccountItem,
+                                              ArrayList<BluetoothMapAccountItem>> groupsList,
                                             int enabledAccountsCounts) {
         mActivity = act;
         this.mProupList = groupsList;
@@ -78,24 +78,25 @@
         listView.setOnGroupExpandListener(new OnGroupExpandListener() {
 
             public void onGroupExpand(int groupPosition) {
-                BluetoothMapEmailSettingsItem group = mMainGroup.get(groupPosition);
+                BluetoothMapAccountItem group = mMainGroup.get(groupPosition);
                 if (mProupList.get(group).size() > 0)
                     mGroupStatus[groupPosition] = 1;
 
             }
         });
-        mMainGroup = new ArrayList<BluetoothMapEmailSettingsItem>();
-        for (Map.Entry<BluetoothMapEmailSettingsItem, ArrayList<BluetoothMapEmailSettingsItem>> mapEntry : mProupList.entrySet()) {
+        mMainGroup = new ArrayList<BluetoothMapAccountItem>();
+        for (Map.Entry<BluetoothMapAccountItem, 
+                ArrayList<BluetoothMapAccountItem>> mapEntry : mProupList.entrySet()) {
             mMainGroup.add(mapEntry.getKey());
         }
     }
 
     @Override
-    public BluetoothMapEmailSettingsItem getChild(int groupPosition, int childPosition) {
-        BluetoothMapEmailSettingsItem item = mMainGroup.get(groupPosition);
+    public BluetoothMapAccountItem getChild(int groupPosition, int childPosition) {
+        BluetoothMapAccountItem item = mMainGroup.get(groupPosition);
         return mProupList.get(item).get(childPosition);
     }
-    private ArrayList<BluetoothMapEmailSettingsItem> getChild(BluetoothMapEmailSettingsItem group) {
+    private ArrayList<BluetoothMapAccountItem> getChild(BluetoothMapAccountItem group) {
         return mProupList.get(group);
     }
 
@@ -111,49 +112,53 @@
 
         final ChildHolder holder;
         if (convertView == null) {
-            convertView = mInflater.inflate(R.layout.bluetooth_map_email_settings_account_item, null);
+            convertView = mInflater.inflate(R.layout.bluetooth_map_settings_account_item, null);
             holder = new ChildHolder();
-            holder.cb = (CheckBox) convertView.findViewById(R.id.bluetooth_map_email_settings_item_check);
-            holder.title = (TextView) convertView.findViewById(R.id.bluetooth_map_email_settings_item_text_view);
+            holder.cb = (CheckBox) convertView.findViewById(R.id.bluetooth_map_settings_item_check);
+            holder.title = 
+                (TextView) convertView.findViewById(R.id.bluetooth_map_settings_item_text_view);
             convertView.setTag(holder);
         } else {
             holder = (ChildHolder) convertView.getTag();
         }
-            final BluetoothMapEmailSettingsItem child =  getChild(groupPosition, childPosition);
+            final BluetoothMapAccountItem child =  getChild(groupPosition, childPosition);
             holder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
 
                 public void onCheckedChanged(CompoundButton buttonView,
                         boolean isChecked) {
-                    BluetoothMapEmailSettingsItem parentGroup = (BluetoothMapEmailSettingsItem)getGroup(groupPosition);
+                    BluetoothMapAccountItem parentGroup = 
+                          (BluetoothMapAccountItem)getGroup(groupPosition);
                     boolean oldIsChecked = child.mIsChecked; // needed to prevent updates on UI redraw
                     child.mIsChecked = isChecked;
                     if (isChecked) {
-                        ArrayList<BluetoothMapEmailSettingsItem> childList = getChild(parentGroup);
+                        ArrayList<BluetoothMapAccountItem> childList = getChild(parentGroup);
                         int childIndex = childList.indexOf(child);
                         boolean isAllChildClicked = true;
                         if(mSlotsLeft-childList.size() >=0){
 
                             for (int i = 0; i < childList.size(); i++) {
                                 if (i != childIndex) {
-                                    BluetoothMapEmailSettingsItem siblings = childList.get(i);
+                                    BluetoothMapAccountItem siblings = childList.get(i);
                                     if (!siblings.mIsChecked) {
                                         isAllChildClicked = false;
-                                            BluetoothMapEmailSettingsDataHolder.mCheckedChilds.put(child.getName(),
-                                                    parentGroup.getName());
+                                            BluetoothMapSettingsDataHolder.mCheckedChilds.put(
+                                                child.getName(), parentGroup.getName());
                                         break;
 
                                     }
                                 }
                             }
                         }else {
-                            showWarning(mActivity.getString(R.string.bluetooth_map_email_settings_no_account_slots_left));
+                            showWarning(mActivity.getString(
+                                R.string.bluetooth_map_settings_no_account_slots_left));
                             isAllChildClicked = false;
                             child.mIsChecked = false;
                         }
                         if (isAllChildClicked) {
                             parentGroup.mIsChecked = true;
-                            if(!(BluetoothMapEmailSettingsDataHolder.mCheckedChilds.containsKey(child.getName())==true)){
-                                BluetoothMapEmailSettingsDataHolder.mCheckedChilds.put(child.getName(),
+                            if(!(BluetoothMapSettingsDataHolder.mCheckedChilds.containsKey(
+                                child.getName())==true)){
+                                BluetoothMapSettingsDataHolder.mCheckedChilds.put(child.getName(),
                                         parentGroup.getName());
                             }
                             mCheckAll = false;
@@ -164,10 +169,10 @@
                         if (parentGroup.mIsChecked) {
                             parentGroup.mIsChecked = false;
                             mCheckAll = false;
-                            BluetoothMapEmailSettingsDataHolder.mCheckedChilds.remove(child.getName());
+                            BluetoothMapSettingsDataHolder.mCheckedChilds.remove(child.getName());
                         } else {
                             mCheckAll = true;
-                            BluetoothMapEmailSettingsDataHolder.mCheckedChilds.remove(child.getName());
+                            BluetoothMapSettingsDataHolder.mCheckedChilds.remove(child.getName());
                         }
                         // child.isChecked =false;
                     }
@@ -182,7 +187,7 @@
 
             holder.cb.setChecked(child.mIsChecked);
             holder.title.setText(child.getName());
-            if(D)Log.i("childs are", BluetoothMapEmailSettingsDataHolder.mCheckedChilds.toString());
+            if(D)Log.i("childs are", BluetoothMapSettingsDataHolder.mCheckedChilds.toString());
             return convertView;
 
     }
@@ -191,12 +196,12 @@
 
     @Override
     public int getChildrenCount(int groupPosition) {
-        BluetoothMapEmailSettingsItem item = mMainGroup.get(groupPosition);
+        BluetoothMapAccountItem item = mMainGroup.get(groupPosition);
         return mProupList.get(item).size();
     }
 
     @Override
-    public BluetoothMapEmailSettingsItem getGroup(int groupPosition) {
+    public BluetoothMapAccountItem getGroup(int groupPosition) {
         return mMainGroup.get(groupPosition);
     }
 
@@ -227,18 +232,20 @@
         final GroupHolder holder;
 
         if (convertView == null) {
-            convertView = mInflater.inflate(R.layout.bluetooth_map_email_settings_account_group, null);
+            convertView = mInflater.inflate(R.layout.bluetooth_map_settings_account_group, null);
             holder = new GroupHolder();
-            holder.cb = (CheckBox) convertView.findViewById(R.id.bluetooth_map_email_settings_group_checkbox);
+            holder.cb = 
+                (CheckBox) convertView.findViewById(R.id.bluetooth_map_settings_group_checkbox);
             holder.imageView = (ImageView) convertView
-                    .findViewById(R.id.bluetooth_map_email_settings_group_icon);
-            holder.title = (TextView) convertView.findViewById(R.id.bluetooth_map_email_settings_group_text_view);
+                    .findViewById(R.id.bluetooth_map_settings_group_icon);
+            holder.title = 
+                (TextView) convertView.findViewById(R.id.bluetooth_map_settings_group_text_view);
             convertView.setTag(holder);
         } else {
             holder = (GroupHolder) convertView.getTag();
         }
 
-        final BluetoothMapEmailSettingsItem groupItem = getGroup(groupPosition);
+        final BluetoothMapAccountItem groupItem = getGroup(groupPosition);
         holder.imageView.setImageDrawable(groupItem.getIcon());
 
 
@@ -249,8 +256,8 @@
             public void onCheckedChanged(CompoundButton buttonView,
                     boolean isChecked) {
                 if (mCheckAll) {
-                    ArrayList<BluetoothMapEmailSettingsItem> childItem = getChild(groupItem);
-                    for (BluetoothMapEmailSettingsItem children : childItem)
+                    ArrayList<BluetoothMapAccountItem> childItem = getChild(groupItem);
+                    for (BluetoothMapAccountItem children : childItem)
                     {
                         boolean oldIsChecked = children.mIsChecked;
                         if(mSlotsLeft >0){
@@ -259,7 +266,8 @@
                                 updateAccount(children);
                             }
                         }else {
-                            showWarning(mActivity.getString(R.string.bluetooth_map_email_settings_no_account_slots_left));
+                            showWarning(mActivity.getString(
+                                    R.string.bluetooth_map_settings_no_account_slots_left));
                             isChecked = false;
                         }
                     }
@@ -303,13 +311,14 @@
         public TextView title;
         public CheckBox cb;
     }
-    public void updateAccount(BluetoothMapEmailSettingsItem account) {
+    public void updateAccount(BluetoothMapAccountItem account) {
         updateSlotCounter(account.mIsChecked);
-        if(D)Log.d(TAG,"Updating account settings for "+account.getName() +". Value is:"+account.mIsChecked);
+        if(D)Log.d(TAG,"Updating account settings for "
+                +account.getName() +". Value is:"+account.mIsChecked);
         ContentResolver mResolver = mActivity.getContentResolver();
         Uri uri = Uri.parse(account.mBase_uri_no_account+"/"+BluetoothMapContract.TABLE_ACCOUNT);
         ContentValues values = new ContentValues();
-        values.put(BluetoothMapContract.AccountColumns.FLAG_EXPOSE, ((account.mIsChecked)?1:0)); // get title
+        values.put(BluetoothMapContract.AccountColumns.FLAG_EXPOSE, ((account.mIsChecked)?1:0)); 
         values.put(BluetoothMapContract.AccountColumns._ID, account.getId()); // get title
         mResolver.update(uri, values, null ,null);
 
@@ -325,9 +334,10 @@
 
         if (mSlotsLeft <=0)
         {
-            text = mActivity.getString(R.string.bluetooth_map_email_settings_no_account_slots_left);
+            text = mActivity.getString(R.string.bluetooth_map_settings_no_account_slots_left);
         }else {
-            text= mActivity.getString(R.string.bluetooth_map_email_settings_count) + " "+ String.valueOf(mSlotsLeft);
+            text= mActivity.getString(R.string.bluetooth_map_settings_count) 
+                + " "+ String.valueOf(mSlotsLeft);
         }
 
         int duration = Toast.LENGTH_SHORT;
diff --git a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsDataHolder.java b/src/com/android/bluetooth/map/BluetoothMapSettingsDataHolder.java
similarity index 93%
rename from src/com/android/bluetooth/map/BluetoothMapEmailSettingsDataHolder.java
rename to src/com/android/bluetooth/map/BluetoothMapSettingsDataHolder.java
index 104e2af..e33d705 100644
--- a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsDataHolder.java
+++ b/src/com/android/bluetooth/map/BluetoothMapSettingsDataHolder.java
@@ -17,7 +17,7 @@
 
 import java.util.HashMap;
 
-public class BluetoothMapEmailSettingsDataHolder {
+public class BluetoothMapSettingsDataHolder {
         public static HashMap<String, String> mCheckedChilds = new HashMap<String, String>();
 }
 
diff --git a/src/com/android/bluetooth/map/BluetoothMapUtils.java b/src/com/android/bluetooth/map/BluetoothMapUtils.java
index fb1e7e8..3e3ace1 100644
--- a/src/com/android/bluetooth/map/BluetoothMapUtils.java
+++ b/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -14,15 +14,30 @@
 */
 package com.android.bluetooth.map;
 
+import android.database.Cursor;
+import android.util.Base64;
 import android.util.Log;
 
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Date;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 
 /**
  * Various utility methods and generic defines that can be used throughout MAPS
  */
 public class BluetoothMapUtils {
 
-    private static final String TAG = "MapUtils";
+    private static final String TAG = "BluetoothMapUtils";
     private static final boolean D = BluetoothMapService.DEBUG;
     private static final boolean V = BluetoothMapService.VERBOSE;
     /* We use the upper 4 bits for the type mask.
@@ -30,24 +45,96 @@
      *       in stead of a bit to indicate the message type. Then 4
      *       bit can be use for 16 different message types.
      */
-    private static final long HANDLE_TYPE_MASK            = (((long)0xf)<<56);
-    private static final long HANDLE_TYPE_MMS_MASK        = (((long)0x1)<<56);
-    private static final long HANDLE_TYPE_EMAIL_MASK      = (((long)0x2)<<56);
-    private static final long HANDLE_TYPE_SMS_GSM_MASK    = (((long)0x4)<<56);
-    private static final long HANDLE_TYPE_SMS_CDMA_MASK   = (((long)0x8)<<56);
+    private static final long HANDLE_TYPE_MASK            = (((long)0xff)<<56);
+    private static final long HANDLE_TYPE_MMS_MASK        = (((long)0x01)<<56);
+    private static final long HANDLE_TYPE_EMAIL_MASK      = (((long)0x02)<<56);
+    private static final long HANDLE_TYPE_SMS_GSM_MASK    = (((long)0x04)<<56);
+    private static final long HANDLE_TYPE_SMS_CDMA_MASK   = (((long)0x08)<<56);
+    private static final long HANDLE_TYPE_IM_MASK         = (((long)0x10)<<56);
 
+    public static final long CONVO_ID_TYPE_SMS_MMS = 1;
+    public static final long CONVO_ID_TYPE_EMAIL_IM= 2;
 
-    static final int MAP_FEATURE_DEFAULT_BITMASK                = 0x0000001F;
+    // MAP supported feature bit - included from MAP Spec 1.2
+    static final int MAP_FEATURE_DEFAULT_BITMASK                    = 0x0000001F;
+
+    static final int MAP_FEATURE_NOTIFICATION_REGISTRATION_BIT      = 1 << 0;
+    static final int MAP_FEATURE_NOTIFICATION_BIT                   = 1 << 1;
+    static final int MAP_FEATURE_BROWSING_BIT                       = 1 << 2;
+    static final int MAP_FEATURE_UPLOADING_BIT                      = 1 << 3;
+    static final int MAP_FEATURE_DELETE_BIT                         = 1 << 4;
+    static final int MAP_FEATURE_INSTANCE_INFORMATION_BIT           = 1 << 5;
+    static final int MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT       = 1 << 6;
+    static final int MAP_FEATURE_EVENT_REPORT_V12_BIT               = 1 << 7;
+    static final int MAP_FEATURE_MESSAGE_FORMAT_V11_BIT             = 1 << 8;
+    static final int MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT     = 1 << 9;
+    static final int MAP_FEATURE_PERSISTENT_MESSAGE_HANDLE_BIT      = 1 << 10;
+    static final int MAP_FEATURE_DATABASE_INDENTIFIER_BIT           = 1 << 11;
+    static final int MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT         = 1 << 12;
+    static final int MAP_FEATURE_CONVERSATION_VERSION_COUNTER_BIT   = 1 << 13;
+    static final int MAP_FEATURE_PARTICIPANT_PRESENCE_CHANGE_BIT    = 1 << 14;
+    static final int MAP_FEATURE_PARTICIPANT_CHAT_STATE_CHANGE_BIT  = 1 << 15;
+
+    static final int MAP_FEATURE_PBAP_CONTACT_CROSS_REFERENCE_BIT   = 1 << 16;
+    static final int MAP_FEATURE_NOTIFICATION_FILTERING_BIT         = 1 << 17;
+    static final int MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT       = 1 << 18;
+
+    static final String MAP_V10_STR = "1.0";
+    static final String MAP_V11_STR = "1.1";
+    static final String MAP_V12_STR = "1.2";
+
+    // Event Report versions
+    static final int MAP_EVENT_REPORT_V10           = 10; // MAP spec 1.1
+    static final int MAP_EVENT_REPORT_V11           = 11; // MAP spec 1.2
+    static final int MAP_EVENT_REPORT_V12           = 12; // MAP spec 1.3 'to be' incl. IM
+
+    // Message Format versions
+    static final int MAP_MESSAGE_FORMAT_V10         = 10; // MAP spec below 1.3
+    static final int MAP_MESSAGE_FORMAT_V11         = 11; // MAP spec 1.3
+
+    // Message Listing Format versions
+    static final int MAP_MESSAGE_LISTING_FORMAT_V10 = 10; // MAP spec below 1.3
+    static final int MAP_MESSAGE_LISTING_FORMAT_V11 = 11; // MAP spec 1.3
 
     /**
      * This enum is used to convert from the bMessage type property to a type safe
      * type. Hence do not change the names of the enum values.
      */
     public enum TYPE{
+        NONE,
         EMAIL,
         SMS_GSM,
         SMS_CDMA,
-        MMS
+        MMS,
+        IM
+    }
+
+    static public String getDateTimeString(long timestamp) {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Date date = new Date(timestamp);
+        return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+    }
+
+
+    public static void printCursor(Cursor c) {
+        if (D) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("\nprintCursor:\n");
+            for(int i = 0; i < c.getColumnCount(); i++) {
+                if(c.getColumnName(i).equals(BluetoothMapContract.MessageColumns.DATE) ||
+                   c.getColumnName(i).equals(
+                           BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY) ||
+                   c.getColumnName(i).equals(BluetoothMapContract.ChatStatusColumns.LAST_ACTIVE) ||
+                   c.getColumnName(i).equals(BluetoothMapContract.PresenceColumns.LAST_ONLINE) ){
+                    sb.append("  ").append(c.getColumnName(i)).append(" : ").append(
+                            getDateTimeString(c.getLong(i))).append("\n");
+                } else {
+                    sb.append("  ").append(c.getColumnName(i)).append(" : ").append(
+                            c.getString(i)).append("\n");
+                }
+            }
+            Log.d(TAG, sb.toString());
+        }
     }
 
     public static String getLongAsString(long v) {
@@ -69,6 +156,88 @@
     }
 
     /**
+     * Converts a hex-string to a long - please mind that Java has no unsigned data types, hence
+     * any value passed to this function, which has the upper bit set, will return a negative value.
+     * The bitwise content of the variable will however be the same.
+     * Will ignore any white-space characters as well as '-' seperators
+     * @param valueStr a hexstring - NOTE: shall not contain any "0x" prefix.
+     * @return
+     * @throws UnsupportedEncodingException if "US-ASCII" charset is not supported,
+     * NullPointerException if a null pointer is passed to the function,
+     * NumberFormatException if the string contains invalid characters.
+     *
+     */
+    public static long getLongFromString(String valueStr) throws UnsupportedEncodingException {
+        if(valueStr == null) throw new NullPointerException();
+        if(V) Log.i(TAG, "getLongFromString(): converting: " + valueStr);
+        byte[] nibbles;
+        nibbles = valueStr.getBytes("US-ASCII");
+        if(V) Log.i(TAG, "  byte values: " + Arrays.toString(nibbles));
+        byte c;
+        int count = 0;
+        int length = nibbles.length;
+        long value = 0;
+        for(int i = 0; i != length; i++) {
+            c = nibbles[i];
+            if(c >= '0' && c <= '9') {
+                c -= '0';
+            } else if(c >= 'A' && c <= 'F') {
+                c -= ('A'-10);
+            } else if(c >= 'a' && c <= 'f') {
+                c -= ('a'-10);
+            } else if(c <= ' ' || c == '-') {
+                if(V)Log.v(TAG, "Skipping c = '" + new String(new byte[]{ (byte)c }, "US-ASCII")
+                        + "'");
+                continue; // Skip any whitespace and '-' (which is used for UUIDs)
+            } else {
+                throw new NumberFormatException("Invalid character:" + c);
+            }
+            value = value << 4; // The last nibble shall not be shifted
+            value += c;
+            count++;
+            if(count > 16) throw new NullPointerException("String to large - count: " + count);
+        }
+        if(V) Log.i(TAG, "  length: " + count);
+        return value;
+    }
+    private static final int LONG_LONG_LENGTH = 32;
+    public static String getLongLongAsString(long vLow, long vHigh) {
+        char[] result = new char[LONG_LONG_LENGTH];
+        int v1 = (int) (vLow & 0xffffffff);
+        int v2 = (int) ((vLow>>32) & 0xffffffff);
+        int v3 = (int) (vHigh & 0xffffffff);
+        int v4 = (int) ((vHigh>>32) & 0xffffffff);
+        int c,d,i;
+        // Handle the lower bytes
+        for (i = 0; i < 8; i++) {
+            c = v2 & 0x0f;
+            c += (c < 10) ? '0' : ('A'-10);
+            d = v4 & 0x0f;
+            d += (d < 10) ? '0' : ('A'-10);
+            result[23 - i] = (char) c;
+            result[7 - i] = (char) d;
+            v2 >>= 4;
+            v4 >>= 4;
+            c = v1 & 0x0f;
+            c += (c < 10) ? '0' : ('A'-10);
+            d = v3 & 0x0f;
+            d += (d < 10) ? '0' : ('A'-10);
+            result[31 - i] = (char)c;
+            result[15 - i] = (char)d;
+            v1 >>= 4;
+            v3 >>= 4;
+        }
+        // Remove any leading 0's
+        for(i = 0; i < LONG_LONG_LENGTH; i++) {
+            if(result[i] != '0') {
+                break;
+            }
+        }
+        return new String(result, i, LONG_LONG_LENGTH-i);
+    }
+
+
+    /**
      * Convert a Content Provider handle and a Messagetype into a unique handle
      * @param cpHandle content provider handle
      * @param messageType message type (TYPE_MMS/TYPE_SMS_GSM/TYPE_SMS_CDMA/TYPE_EMAIL)
@@ -78,7 +247,6 @@
         String mapHandle = "-1";
         switch(messageType)
         {
-
             case MMS:
                 mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_MMS_MASK);
                 break;
@@ -91,8 +259,37 @@
             case EMAIL:
                 mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_EMAIL_MASK);
                 break;
-                default:
-                    throw new IllegalArgumentException("Message type not supported");
+            case IM:
+                mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_IM_MASK);
+                break;
+            default:
+                throw new IllegalArgumentException("Message type not supported");
+        }
+        return mapHandle;
+
+    }
+
+    /**
+     * Convert a Content Provider handle and a Messagetype into a unique handle
+     * @param cpHandle content provider handle
+     * @param messageType message type (TYPE_MMS/TYPE_SMS_GSM/TYPE_SMS_CDMA/TYPE_EMAIL)
+     * @return String Formatted Map Handle
+     */
+    public static String getMapConvoHandle(long cpHandle, TYPE messageType){
+        String mapHandle = "-1";
+        switch(messageType)
+        {
+            case MMS:
+            case SMS_GSM:
+            case SMS_CDMA:
+                mapHandle = getLongLongAsString(cpHandle, CONVO_ID_TYPE_SMS_MMS);
+                break;
+            case EMAIL:
+            case IM:
+                mapHandle = getLongLongAsString(cpHandle, CONVO_ID_TYPE_EMAIL_IM);
+                break;
+            default:
+                throw new IllegalArgumentException("Message type not supported");
         }
         return mapHandle;
 
@@ -138,8 +335,281 @@
             return TYPE.SMS_GSM;
         if((cpHandle & HANDLE_TYPE_SMS_CDMA_MASK) != 0)
             return TYPE.SMS_CDMA;
+        if((cpHandle & HANDLE_TYPE_IM_MASK) != 0)
+            return TYPE.IM;
 
         throw new IllegalArgumentException("Message type not found in handle string.");
     }
+
+    /**
+     * TODO: Is this still needed after changing to another XML encoder? It should escape illegal
+     *       characters.
+     * Strip away any illegal XML characters, that would otherwise cause the
+     * xml serializer to throw an exception.
+     * Examples of such characters are the emojis used on Android.
+     * @param text The string to validate
+     * @return the same string if valid, otherwise a new String stripped for
+     * any illegal characters. If a null pointer is passed an empty string will be returned.
+     */
+    static public String stripInvalidChars(String text) {
+        if(text == null) {
+            return "";
+        }
+        char out[] = new char[text.length()];
+        int i, o, l;
+        for(i=0, o=0, l=text.length(); i<l; i++){
+            char c = text.charAt(i);
+            if((c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd)) {
+                out[o++] = c;
+            } // Else we skip the character
+        }
+
+        if(i==o) {
+            return text;
+        } else { // We removed some characters, create the new string
+            return new String(out,0,o);
+        }
+    }
+
+    /**
+     * Truncate UTF-8 string encoded byte array to desired length
+     * @param utf8String String to convert to bytes array h
+     * @param length Max length of byte array returned including null termination
+     * @return byte array containing valid utf8 characters with max length
+     * @throws UnsupportedEncodingException
+     */
+    static public byte[] truncateUtf8StringToBytearray(String utf8String, int maxLength)
+            throws UnsupportedEncodingException {
+
+        byte[] utf8Bytes = null;
+        try {
+            utf8Bytes = utf8String.getBytes("UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            Log.e(TAG,"truncateUtf8StringToBytearray: getBytes exception ", e);
+            throw e;
+        }
+
+        if (utf8Bytes.length > (maxLength - 1)) {
+            /* if 'continuation' byte is in place 200,
+             * then strip previous bytes until utf-8 start byte is found */
+            if ( (utf8Bytes[maxLength - 1] & 0xC0) == 0x80 ) {
+                for (int i = maxLength - 2; i >= 0; i--) {
+                    if ((utf8Bytes[i] & 0xC0) == 0xC0) {
+                        /* first byte in utf-8 character found,
+                         * now copy i - 1 bytes to outBytes and add null termination */
+                        utf8Bytes = Arrays.copyOf(utf8Bytes, i+1);
+                        utf8Bytes[i] = 0;
+                        break;
+                    }
+                }
+            } else {
+                /* copy bytes to outBytes and null terminate */
+                utf8Bytes = Arrays.copyOf(utf8Bytes, maxLength);
+                utf8Bytes[maxLength - 1] = 0;
+            }
+        }
+        return utf8Bytes;
+    }
+    private static Pattern p = Pattern.compile("=\\?(.+?)\\?(.)\\?(.+?(?=\\?=))\\?=");
+
+    /**
+     * Method for converting quoted printable og base64 encoded string from headers.
+     * @param in the string with encoding
+     * @return decoded string if success - else the same string as was as input.
+     */
+    static public String stripEncoding(String in){
+        String str = null;
+        if(in.contains("=?") && in.contains("?=")){
+            String encoding;
+            String charset;
+            String encodedText;
+            String match;
+            Matcher m = p.matcher(in);
+            while(m.find()){
+                match = m.group(0);
+                charset = m.group(1);
+                encoding = m.group(2);
+                encodedText = m.group(3);
+                Log.v(TAG, "Matching:" + match +"\nCharset: "+charset +"\nEncoding : " +encoding
+                        + "\nText: " + encodedText);
+                if(encoding.equalsIgnoreCase("Q")){
+                    //quoted printable
+                    Log.d(TAG,"StripEncoding: Quoted Printable string : " + encodedText);
+                    str = new String(quotedPrintableToUtf8(encodedText,charset));
+                    in = in.replace(match, str);
+                }else if(encoding.equalsIgnoreCase("B")){
+                    // base64
+                    try{
+
+                        Log.d(TAG,"StripEncoding: base64 string : " + encodedText);
+                        str = new String(Base64.decode(encodedText.getBytes(charset),
+                                Base64.DEFAULT), charset);
+                        Log.d(TAG,"StripEncoding: decoded string : " + str);
+                        in = in.replace(match, str);
+                    }catch(UnsupportedEncodingException e){
+                        Log.e(TAG, "stripEncoding: Unsupported charset: " + charset);
+                    }catch (IllegalArgumentException e){
+                        Log.e(TAG,"stripEncoding: string not encoded as base64: " +encodedText);
+                    }
+                }else{
+                    Log.e(TAG, "stripEncoding: Hit unknown encoding: "+encoding);
+                }
+            }
+        }
+        return in;
+    }
+
+
+    /**
+     * Convert a quoted-printable encoded string to a UTF-8 string:
+     *  - Remove any soft line breaks: "=<CRLF>"
+     *  - Convert all "=xx" to the corresponding byte
+     * @param text quoted-printable encoded UTF-8 text
+     * @return decoded UTF-8 string
+     */
+    public static byte[] quotedPrintableToUtf8(String text, String charset) {
+        byte[] output = new byte[text.length()]; // We allocate for the worst case memory need
+        byte[] input = null;
+        try {
+            input = text.getBytes("US-ASCII");
+        } catch (UnsupportedEncodingException e) {
+            /* This cannot happen as "US-ASCII" is supported for all Java implementations */ }
+
+        if(input == null){
+            return "".getBytes();
+        }
+
+        int in, out, stopCnt = input.length-2; // Leave room for peaking the next two bytes
+
+        /* Algorithm:
+         *  - Search for token, copying all non token chars
+         * */
+        for(in=0, out=0; in < stopCnt; in++){
+            byte b0 = input[in];
+            if(b0 == '=') {
+                byte b1 = input[++in];
+                byte b2 = input[++in];
+                if(b1 == '\r' && b2 == '\n') {
+                    continue; // soft line break, remove all tree;
+                }
+                if(((b1 >= '0' && b1 <= '9') || (b1 >= 'A' && b1 <= 'F')
+                        || (b1 >= 'a' && b1 <= 'f'))
+                        && ((b2 >= '0' && b2 <= '9') || (b2 >= 'A' && b2 <= 'F')
+                        || (b2 >= 'a' && b2 <= 'f'))) {
+                    if(V)Log.v(TAG, "Found hex number: " + String.format("%c%c", b1, b2));
+                    if(b1 <= '9')       b1 = (byte) (b1 - '0');
+                    else if (b1 <= 'F') b1 = (byte) (b1 - 'A' + 10);
+                    else if (b1 <= 'f') b1 = (byte) (b1 - 'a' + 10);
+
+                    if(b2 <= '9')       b2 = (byte) (b2 - '0');
+                    else if (b2 <= 'F') b2 = (byte) (b2 - 'A' + 10);
+                    else if (b2 <= 'f') b2 = (byte) (b2 - 'a' + 10);
+
+                    if(V)Log.v(TAG, "Resulting nibble values: " +
+                            String.format("b1=%x b2=%x", b1, b2));
+
+                    output[out++] = (byte)(b1<<4 | b2); // valid hex char, append
+                    if(V)Log.v(TAG, "Resulting value: "  + String.format("0x%2x", output[out-1]));
+                    continue;
+                }
+                Log.w(TAG, "Received wrongly quoted printable encoded text. " +
+                        "Continuing at best effort...");
+                /* If we get a '=' without either a hex value or CRLF following, just add it and
+                 * rewind the in counter. */
+                output[out++] = b0;
+                in -= 2;
+                continue;
+            } else {
+                output[out++] = b0;
+                continue;
+            }
+        }
+
+        // Just add any remaining characters. If they contain any encoding, it is invalid,
+        // and best effort would be just to display the characters.
+        while (in < input.length) {
+            output[out++] = input[in++];
+        }
+
+        String result = null;
+        // Figure out if we support the charset, else fall back to UTF-8, as this is what
+        // the MAP specification suggest to use, and is compatible with US-ASCII.
+        if(charset == null){
+            charset = "UTF-8";
+        } else {
+            charset = charset.toUpperCase();
+            try {
+                if(Charset.isSupported(charset) == false) {
+                    charset = "UTF-8";
+                }
+            } catch (IllegalCharsetNameException e) {
+                Log.w(TAG, "Received unknown charset: " + charset + " - using UTF-8.");
+                charset = "UTF-8";
+            }
+        }
+        try{
+            result = new String(output, 0, out, charset);
+        } catch (UnsupportedEncodingException e) {
+            /* This cannot happen unless Charset.isSupported() is out of sync with String */
+            try{
+                result = new String(output, 0, out, "UTF-8");
+            } catch (UnsupportedEncodingException e2) {/* This cannot happen */}
+        }
+        return result.getBytes(); /* return the result as "UTF-8" bytes */
+    }
+
+    /**
+     * Encodes an array of bytes into an array of quoted-printable 7-bit characters.
+     * Unsafe characters are escaped.
+     * Simplified version of encoder from QuetedPrintableCodec.java (Apache external)
+     *
+     * @param bytes
+     *                  array of bytes to be encoded
+     * @return UTF-8 string containing quoted-printable characters
+     */
+
+    private static byte ESCAPE_CHAR = '=';
+    private static byte TAB = 9;
+    private static byte SPACE = 32;
+
+    public static final String encodeQuotedPrintable(byte[] bytes) {
+        if (bytes == null) {
+            return null;
+        }
+
+        BitSet printable = new BitSet(256);
+        // alpha characters
+        for (int i = 33; i <= 60; i++) {
+            printable.set(i);
+        }
+        for (int i = 62; i <= 126; i++) {
+            printable.set(i);
+        }
+        printable.set(TAB);
+        printable.set(SPACE);
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        for (int i = 0; i < bytes.length; i++) {
+            int b = bytes[i];
+            if (b < 0) {
+                b = 256 + b;
+            }
+            if (printable.get(b)) {
+                buffer.write(b);
+            } else {
+                buffer.write(ESCAPE_CHAR);
+                char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
+                char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
+                buffer.write(hex1);
+                buffer.write(hex2);
+            }
+        }
+        try {
+            return buffer.toString("UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            //cannot happen
+            return "";
+        }
+    }
+
 }
 
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessage.java b/src/com/android/bluetooth/map/BluetoothMapbMessage.java
index 54b72cb..9a77568 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessage.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessage.java
@@ -36,7 +36,7 @@
     protected static final boolean D = BluetoothMapService.DEBUG;
     protected static final boolean V = BluetoothMapService.VERBOSE;
 
-    private static final String VERSION = "VERSION:1.0";
+    private String mVersionString = "VERSION:1.0";
 
     public static int INVALID_VALUE = -1;
 
@@ -68,6 +68,8 @@
         private String[] mPhoneNumbers = {};
         private String[] mEmailAddresses = {};
         private int mEnvLevel = 0;
+        private String[] mBtUcis = {};
+        private String[] mBtUids = {};
 
         /**
          * Construct a version 3.0 vCard
@@ -110,15 +112,25 @@
          * @param name Structured name
          * @param formattedName Formatted name
          * @param phoneNumbers a String[] of phone numbers
-         * @param emailAddresses a String[] of email addresses
+         * @param emailAddresses a String[] of email addresses if available, else null
+         * @param btUids a String[] of X-BT-UIDs if available, else null
+         * @param btUcis a String[] of X-BT-UCIs if available, else null
          */
-        public vCard(String name, String formattedName, String[] phoneNumbers, String[] emailAddresses) {
+        public vCard(String name, String formattedName,
+                     String[] phoneNumbers,
+                     String[] emailAddresses,
+                     String[] btUids,
+                     String[] btUcis) {
             this.mVersion = "3.0";
             this.mName = (name != null) ? name : "";
             this.mFormattedName = (formattedName != null) ? formattedName : "";
             setPhoneNumbers(phoneNumbers);
-            if (emailAddresses != null)
+            if (emailAddresses != null) {
                 this.mEmailAddresses = emailAddresses;
+            }
+            if (btUcis != null) {
+                this.mBtUcis = btUcis;
+            }
         }
 
         /**
@@ -140,11 +152,16 @@
                 mPhoneNumbers = new String[numbers.length];
                 for(int i = 0, n = numbers.length; i < n; i++){
                     String networkNumber = PhoneNumberUtils.extractNetworkPortion(numbers[i]);
-                    /* extractNetworkPortion can return N if the number is a service "number" = a string
-                     * with the a name in (i.e. "Some-Tele-company" would return N because of the N in compaNy)
+                    /* extractNetworkPortion can return N if the number is a service
+                     * "number" = a string with the a name in (i.e. "Some-Tele-company" would
+                     * return N because of the N in compaNy)
                      * Hence we need to check if the number is actually a string with alpha chars.
                      * */
-                    Boolean alpha = PhoneNumberUtils.stripSeparators(numbers[i]).matches("[0-9]*[a-zA-Z]+[0-9]*");
+                    String strippedNumber = PhoneNumberUtils.stripSeparators(numbers[i]);
+                    Boolean alpha = false;
+                    if(strippedNumber != null){
+                        alpha = strippedNumber.matches("[0-9]*[a-zA-Z]+[0-9]*");
+                    }
                     if(networkNumber != null && networkNumber.length() > 1 && !alpha) {
                         mPhoneNumbers[i] = networkNumber;
                     } else {
@@ -175,6 +192,19 @@
             } else
                 return null;
         }
+        public String getFirstBtUci() {
+            if(mBtUcis.length > 0) {
+                return mBtUcis[0];
+            } else
+                return null;
+        }
+
+        public String getFirstBtUid() {
+            if(mBtUids.length > 0) {
+                return mBtUids[0];
+            } else
+                return null;
+        }
 
         public void encode(StringBuilder sb)
         {
@@ -194,13 +224,22 @@
             {
                 sb.append("EMAIL:").append(emailAddress).append("\r\n");
             }
+            for(String btUid : mBtUids)
+            {
+                sb.append("X-BT-UID:").append(btUid).append("\r\n");
+            }
+            for(String btUci : mBtUcis)
+            {
+                sb.append("X-BT-UCI:").append(btUci).append("\r\n");
+            }
             sb.append("END:VCARD").append("\r\n");
         }
 
         /**
-         * Parse a vCard from a BMgsReader, where a line containing "BEGIN:VCARD" have just been read.
+         * Parse a vCard from a BMgsReader, where a line containing "BEGIN:VCARD"
+         * have just been read.
          * @param reader
-         * @param mOriginator
+         * @param envLevel
          * @return
          */
         public static vCard parseVcard(BMsgReader reader, int envLevel) {
@@ -208,10 +247,12 @@
             String name = null;
             ArrayList<String> phoneNumbers = null;
             ArrayList<String> emailAddresses = null;
+            ArrayList<String> btUids = null;
+            ArrayList<String> btUcis = null;
             String[] parts;
             String line = reader.getLineEnforce();
 
-            while(!line.contains("END:VCARD")) {
+            while(!line.contains("END:VCARD")){
                 line = line.trim();
                 if(line.startsWith("N:")){
                     parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':'
@@ -233,7 +274,8 @@
                         String[] subParts = parts[1].split("[^\\\\];");
                         if(phoneNumbers == null)
                             phoneNumbers = new ArrayList<String>(1);
-                        phoneNumbers.add(subParts[subParts.length-1]); // only keep actual phone number
+                        // only keep actual phone number
+                        phoneNumbers.add(subParts[subParts.length-1]);
                     } else {}
                         // Empty phone number - ignore
                 }
@@ -243,15 +285,40 @@
                         String[] subParts = parts[1].split("[^\\\\];");
                         if(emailAddresses == null)
                             emailAddresses = new ArrayList<String>(1);
-                        emailAddresses.add(subParts[subParts.length-1]); // only keep actual email address
+                        // only keep actual email address
+                        emailAddresses.add(subParts[subParts.length-1]);
                     } else {}
                         // Empty email address entry - ignore
                 }
+                else if(line.startsWith("X-BT-UCI:")){
+                    parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
+                    if(parts.length == 2) {
+                        String[] subParts = parts[1].split("[^\\\\];");
+                        if(btUcis == null)
+                            btUcis = new ArrayList<String>(1);
+                        btUcis.add(subParts[subParts.length-1]); // only keep actual UCI
+                    } else {}
+                        // Empty UCIentry - ignore
+                }
+                else if(line.startsWith("X-BT-UID:")){
+                    parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
+                    if(parts.length == 2) {
+                        String[] subParts = parts[1].split("[^\\\\];");
+                        if(btUids == null)
+                            btUids = new ArrayList<String>(1);
+                        btUids.add(subParts[subParts.length-1]); // only keep actual UID
+                    } else {}
+                        // Empty UID entry - ignore
+                }
+
+
                 line = reader.getLineEnforce();
             }
             return new vCard(name, formattedName,
-                    phoneNumbers == null? null : phoneNumbers.toArray(new String[phoneNumbers.size()]),
-                    emailAddresses == null ? null : emailAddresses.toArray(new String[emailAddresses.size()]),
+                    phoneNumbers == null?
+                            null : phoneNumbers.toArray(new String[phoneNumbers.size()]),
+                    emailAddresses == null ?
+                            null : emailAddresses.toArray(new String[emailAddresses.size()]),
                     envLevel);
         }
     };
@@ -267,9 +334,9 @@
             int readByte;
 
             /* TODO: Actually the vCard spec. allows to break lines by using a newLine
-             * followed by a white space character(space or tab). Not sure this is a good idea to implement
-             * as the Bluetooth MAP spec. illustrates vCards using tab alignment, hence actually
-             * showing an invalid vCard format...
+             * followed by a white space character(space or tab). Not sure this is a good idea to
+             * implement as the Bluetooth MAP spec. illustrates vCards using tab alignment,
+             * hence actually showing an invalid vCard format...
              * If we read such a folded line, the folded part will be skipped in the parser
              * UPDATE: Check if we actually do unfold before parsing the input stream
              */
@@ -345,7 +412,8 @@
             if(line == null || subString == null){
                 throw new IllegalArgumentException("Line or substring is null");
             }else if(!line.toUpperCase().contains(subString.toUpperCase()))
-                throw new IllegalArgumentException("Expected \"" + subString + "\" in: \"" + line + "\"");
+                throw new IllegalArgumentException("Expected \"" + subString + "\" in: \""
+                                                    + line + "\"");
         }
 
         /**
@@ -358,23 +426,26 @@
         public void expect(String subString, String subString2) throws IllegalArgumentException{
             String line = getLine();
             if(!line.toUpperCase().contains(subString.toUpperCase()))
-                throw new IllegalArgumentException("Expected \"" + subString + "\" in: \"" + line + "\"");
+                throw new IllegalArgumentException("Expected \"" + subString + "\" in: \""
+                                                   + line + "\"");
             if(!line.toUpperCase().contains(subString2.toUpperCase()))
-                throw new IllegalArgumentException("Expected \"" + subString + "\" in: \"" + line + "\"");
+                throw new IllegalArgumentException("Expected \"" + subString + "\" in: \""
+                                                   + line + "\"");
         }
 
         /**
          * Read a part of the bMessage as raw data.
          * @param length the number of bytes to read
-         * @return the byte[] containing the number of bytes or null if an error occurs or EOF is reached
-         * before length bytes have been read.
+         * @return the byte[] containing the number of bytes or null if an error occurs or EOF is
+         * reached before length bytes have been read.
          */
         public byte[] getDataBytes(int length) {
             byte[] data = new byte[length];
             try {
                 int bytesRead;
                 int offset=0;
-                while ((bytesRead = mInStream.read(data, offset, length-offset)) != (length - offset)) {
+                while ((bytesRead = mInStream.read(data, offset, length-offset))
+                                 != (length - offset)) {
                     if(bytesRead == -1)
                         return null;
                     offset += bytesRead;
@@ -391,7 +462,19 @@
 
     }
 
-    public static BluetoothMapbMessage parse(InputStream bMsgStream, int appParamCharset) throws IllegalArgumentException{
+    public String getVersionString() {
+        return mVersionString;
+    }
+    /**
+     * Set the version string for VCARD
+     * @param version the actual number part of the version string i.e. 1.0
+     * */
+    public void setVersionString(String version) {
+        this.mVersionString = "VERSION:"+version;
+    }
+
+    public static BluetoothMapbMessage parse(InputStream bMsgStream,
+                                             int appParamCharset) throws IllegalArgumentException{
         BMsgReader reader;
         String line = "";
         BluetoothMapbMessage newBMsg = null;
@@ -400,10 +483,11 @@
         TYPE type = null;
         String folder = null;
 
-        /* This section is used for debug. It will write the incoming message to a file on the SD-card,
-         * hence should only be used for test/debug.
+        /* This section is used for debug. It will write the incoming message to a file on the
+         * SD-card, hence should only be used for test/debug.
          * If an error occurs, it will result in a OBEX_HTTP_PRECON_FAILED to be send to the client,
-         * even though the message might be formatted correctly, hence only enable this code for test. */
+         * even though the message might be formatted correctly, hence only enable this code for
+         * test. */
         if(V) {
             /* Read the entire stream into a file on the SD card*/
             File sdCard = Environment.getExternalStorageDirectory();
@@ -415,7 +499,8 @@
             int writtenLen = 0;
 
             try {
-                outStream = new FileOutputStream(file, false); /* overwrite if it does already exist */
+                /* overwrite if it does already exist */
+                outStream = new FileOutputStream(file, false);
 
                 byte[] buffer = new byte[4*1024];
                 int len = 0;
@@ -428,7 +513,8 @@
             } catch (IOException e) {
                 Log.e(TAG,"Failed to copy the received message",e);
                 if(writtenLen != 0)
-                    failed = true; /* We failed to write the complete file, hence the received message is lost... */
+                    failed = true; /* We failed to write the complete file,
+                                      hence the received message is lost... */
             } finally {
                 if(outStream != null)
                     try {
@@ -443,7 +529,7 @@
             }
 
             if (outStream == null) {
-                /* We failed to create the the log-file, just continue using the original bMsgStream. */
+                /* We failed to create the log-file, just continue using the original bMsgStream. */
             } else {
                 /* overwrite the bMsgStream using the file written to the SD-Card */
                 try {
@@ -456,7 +542,8 @@
                     bMsgStream = new FileInputStream(file);
                 } catch (FileNotFoundException e) {
                     Log.e(TAG,"Failed to open the bMessage file", e);
-                    throw new IllegalArgumentException(); /* terminate this function with an error. */
+                    /* terminate this function with an error */
+                    throw new IllegalArgumentException();
                 }
             }
             Log.i(TAG, "The incoming bMessage have been dumped to " + file.getAbsolutePath());
@@ -464,7 +551,7 @@
 
         reader = new BMsgReader(bMsgStream);
         reader.expect("BEGIN:BMSG");
-        reader.expect("VERSION","1.0");
+        reader.expect("VERSION");
 
         line = reader.getLineEnforce();
         // Parse the properties - which end with either a VCARD or a BENV
@@ -483,14 +570,24 @@
                     throw new IllegalArgumentException("Missing value for 'STATUS': " + line);
                 }
             }
+            if(line.contains("EXTENDEDDATA")){
+                String arg[] = line.split(":");
+                if (arg != null && arg.length == 2) {
+                    String value = arg[1].trim();
+                    //FIXME what should we do with this
+                    Log.i(TAG,"We got extended data with: "+value);
+                }
+            }
             if(line.contains("TYPE")) {
                 String arg[] = line.split(":");
                 if (arg != null && arg.length == 2) {
                     String value = arg[1].trim();
-                    type = TYPE.valueOf(value); // Will throw IllegalArgumentException if value is wrong
+                    /* Will throw IllegalArgumentException if value is wrong */
+                    type = TYPE.valueOf(value);
                     if(appParamCharset == BluetoothMapAppParams.CHARSET_NATIVE
                             && type != TYPE.SMS_CDMA && type != TYPE.SMS_GSM) {
-                        throw new IllegalArgumentException("Native appParamsCharset only supported for SMS");
+                        throw new IllegalArgumentException("Native appParamsCharset "
+                                                             +"only supported for SMS");
                     }
                     switch(type) {
                     case SMS_CDMA:
@@ -498,11 +595,14 @@
                         newBMsg = new BluetoothMapbMessageSms();
                         break;
                     case MMS:
-                        newBMsg = new BluetoothMapbMessageMms();
+                        newBMsg = new BluetoothMapbMessageMime();
                         break;
                     case EMAIL:
                         newBMsg = new BluetoothMapbMessageEmail();
                         break;
+                    case IM:
+                        newBMsg = new BluetoothMapbMessageMime();
+                        break;
                     default:
                         break;
                     }
@@ -520,7 +620,8 @@
             line = reader.getLineEnforce();
         }
         if(newBMsg == null)
-            throw new IllegalArgumentException("Missing bMessage TYPE: - unable to parse body-content");
+            throw new IllegalArgumentException("Missing bMessage TYPE: "+
+                                                    "- unable to parse body-content");
         newBMsg.setType(type);
         newBMsg.mAppParamCharset = appParamCharset;
         if(folder != null)
@@ -539,10 +640,11 @@
         } else
             throw new IllegalArgumentException("Bmessage has no BEGIN:BENV - line:" + line);
 
-        /* TODO: Do we need to validate the END:* tags? They are only needed if someone puts additional info
-         *        below the END:MSG - in which case we don't handle it.
-         *        We need to parse the message based on the length field, to ensure MAP 1.0 compatibility,
-         *        since this spec. do not suggest to escape the end-tag if it occurs inside the message text.
+        /* TODO: Do we need to validate the END:* tags? They are only needed if someone puts
+         *        additional info below the END:MSG - in which case we don't handle it.
+         *        We need to parse the message based on the length field, to ensure MAP 1.0
+         *        compatibility, since this spec. do not suggest to escape the end-tag if it
+         *        occurs inside the message text.
          */
 
         try {
@@ -640,10 +742,12 @@
                 /* PTS has a bug regarding the message length, and sets it 2 bytes too short, hence
                  * using the length field to determine the amount of data to read, might not be the
                  * best solution.
-                 * Since errata ???(bluetooth.org is down at the moment) introduced escaping of END:MSG
-                 * in the actual message content, it is now safe to use the END:MSG tag as terminator,
-                 * and simply ignore the length field.*/
-                byte[] rawData = reader.getDataBytes(mBMsgLength - (line.getBytes().length + 2)); // 2 added to compensate for the removed \r\n
+                 * Since errata ???(bluetooth.org is down at the moment) introduced escaping of
+                 * END:MSG in the actual message content, it is now safe to use the END:MSG tag
+                 * as terminator, and simply ignore the length field.*/
+
+                /* 2 added to compensate for the removed \r\n */
+                byte[] rawData = reader.getDataBytes(mBMsgLength - (line.getBytes().length + 2));
                 String data;
                 try {
                     data = new String(rawData, "UTF-8");
@@ -665,7 +769,8 @@
                  * 1) split on "\r\nEND:MSG\r\n"
                  * 2) delete "BEGIN:MSG\r\n" for each msg
                  * 3) replace any occurrence of "\END:MSG" with "END:MSG"
-                 * 4) based on charset from application properties either store as String[] or decode to raw PDUs
+                 * 4) based on charset from application properties either store as String[] or
+                 *    decode to raw PDUs
                  * */
                 String messages[] = data.split("\r\nEND:MSG\r\n");
                 parseMsgInit();
@@ -745,12 +850,25 @@
      * @param phoneNumbers
      * @param emailAddresses
      */
-    public void addOriginator(String name, String formattedName, String[] phoneNumbers, String[] emailAddresses) {
+    public void addOriginator(String name, String formattedName,
+                              String[] phoneNumbers,
+                              String[] emailAddresses,
+                              String[] btUids,
+                              String[] btUcis) {
         if(mOriginator == null)
             mOriginator = new ArrayList<vCard>();
-        mOriginator.add(new vCard(name, formattedName, phoneNumbers, emailAddresses));
+        mOriginator.add(new vCard(name, formattedName, phoneNumbers,
+                    emailAddresses, btUids, btUcis));
     }
 
+
+    public void addOriginator(String[] btUcis, String[] btUids) {
+        if(mOriginator == null)
+            mOriginator = new ArrayList<vCard>();
+        mOriginator.add(new vCard(null,null,null,null,btUids, btUcis));
+    }
+
+
     /** Add a version 2.1 vCard with only a name.
      *
      * @param name e.g. Bonde;Casper
@@ -772,11 +890,20 @@
             this.mRecipient = new ArrayList<vCard>();
         this.mRecipient.add(recipient);
     }
-
-    public void addRecipient(String name, String formattedName, String[] phoneNumbers, String[] emailAddresses) {
+    public void addRecipient(String[] btUcis, String[] btUids) {
         if(mRecipient == null)
             mRecipient = new ArrayList<vCard>();
-        mRecipient.add(new vCard(name, formattedName, phoneNumbers, emailAddresses));
+        mRecipient.add(new vCard(null,null,null,null,btUids, btUcis));
+    }
+    public void addRecipient(String name, String formattedName,
+                             String[] phoneNumbers,
+                             String[] emailAddresses,
+                             String[] btUids,
+                             String[] btUcis) {
+        if(mRecipient == null)
+            mRecipient = new ArrayList<vCard>();
+        mRecipient.add(new vCard(name, formattedName, phoneNumbers,
+                    emailAddresses,btUids, btUcis));
     }
 
     public void addRecipient(String name, String[] phoneNumbers, String[] emailAddresses) {
@@ -786,10 +913,10 @@
     }
 
     /**
-     * Convert a byte[] of data to a hex string representation, converting each nibble to the corresponding
-     * hex char.
-     * NOTE: There is not need to escape instances of "\r\nEND:MSG" in the binary data represented as a string
-     *       as only the characters [0-9] and [a-f] is used.
+     * Convert a byte[] of data to a hex string representation, converting each nibble to the
+     * corresponding hex char.
+     * NOTE: There is not need to escape instances of "\r\nEND:MSG" in the binary data represented
+     * as a string as only the characters [0-9] and [a-f] is used.
      * @param pduData the byte-array of data.
      * @param scAddressData the byte-array of the encoded sc-Address.
      * @return the resulting string.
@@ -803,8 +930,10 @@
         for(int i = 0; i < pduData.length; i++) {
             out.append(Integer.toString((pduData[i] >> 4) & 0x0f,16)); // MS-nibble first
             out.append(Integer.toString( pduData[i]       & 0x0f,16));
-            /*out.append(Integer.toHexString(data[i]));*/ /* This is the same as above, but does not include the needed 0's
-                                                           e.g. it converts the value 3 to "3" and not "03" */
+            /*out.append(Integer.toHexString(data[i]));*/ /* This is the same as above, but does not
+                                                           * include the needed 0's
+                                                           * e.g. it converts the value 3 to "3"
+                                                           * and not "03" */
         }
         return out.toString();
     }
@@ -820,7 +949,8 @@
         if(D) Log.d(TAG,"Decoding binary data: START:" + data + ":END");
         for(int i = 0, j = 0, n = out.length; i < n; i++)
         {
-            value = data.substring(j++, ++j); // same as data.substring(2*i, 2*i+1+1) - substring() uses end-1 for last index
+            value = data.substring(j++, ++j);
+            // same as data.substring(2*i, 2*i+1+1) - substring() uses end-1 for last index
             out[i] = (byte)(Integer.valueOf(value, 16) & 0xff);
         }
         if(D) {
@@ -839,13 +969,18 @@
         StringBuilder sb = new StringBuilder(256);
         byte[] msgStart, msgEnd;
         sb.append("BEGIN:BMSG").append("\r\n");
-        sb.append(VERSION).append("\r\n");
+
+        sb.append(mVersionString).append("\r\n");
         sb.append("STATUS:").append(mStatus).append("\r\n");
         sb.append("TYPE:").append(mType.name()).append("\r\n");
         if(mFolder.length() > 512)
-            sb.append("FOLDER:").append(mFolder.substring(mFolder.length()-512, mFolder.length())).append("\r\n");
+            sb.append("FOLDER:").append(
+                    mFolder.substring(mFolder.length()-512, mFolder.length())).append("\r\n");
         else
             sb.append("FOLDER:").append(mFolder).append("\r\n");
+        if(!mVersionString.contains("1.0")){
+            sb.append("EXTENDEDDATA:").append("\r\n");
+        }
         if(mOriginator != null){
             for(vCard element : mOriginator)
                 element.encode(sb);
@@ -887,7 +1022,8 @@
 
         try {
 
-            ByteArrayOutputStream stream = new ByteArrayOutputStream(msgStart.length + msgEnd.length + length);
+            ByteArrayOutputStream stream = new ByteArrayOutputStream(
+                                                       msgStart.length + msgEnd.length + length);
             stream.write(msgStart);
 
             for (byte[] fragment : bodyFragments) {
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageMms.java b/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
similarity index 79%
rename from src/com/android/bluetooth/map/BluetoothMapbMessageMms.java
rename to src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
index 8c9a39d..20147b9 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessageMms.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
@@ -29,19 +29,21 @@
 import android.util.Base64;
 import android.util.Log;
 
-public class BluetoothMapbMessageMms extends BluetoothMapbMessage {
+public class BluetoothMapbMessageMime extends BluetoothMapbMessage {
 
     public static class MimePart {
-        public long mId = INVALID_VALUE;   /* The _id from the content provider, can be used to sort the parts if needed */
-        public String mContentType = null;  /* The mime type, e.g. text/plain */
+        public long mId = INVALID_VALUE;   /* The _id from the content provider, can be used to
+                                            * sort the parts if needed */
+        public String mContentType = null; /* The mime type, e.g. text/plain */
         public String mContentId = null;
         public String mContentLocation = null;
         public String mContentDisposition = null;
-        public String mPartName = null;     /* e.g. text_1.txt*/
-        public String mCharsetName = null;  /* This seems to be a number e.g. 106 for UTF-8 CharacterSets
-                                                holds a method for the mapping. */
+        public String mPartName = null;    /* e.g. text_1.txt*/
+        public String mCharsetName = null; /* This seems to be a number e.g. 106 for UTF-8
+                                              CharacterSets holds a method for the mapping. */
         public String mFileName = null;     /* Do not seem to be used */
-        public byte[] mData = null;        /* The raw un-encoded data e.g. the raw jpeg data or the text.getBytes("utf-8") */
+        public byte[] mData = null;        /* The raw un-encoded data e.g. the raw
+                                            * jpeg data or the text.getBytes("utf-8") */
 
 
         String getDataAsString() {
@@ -73,7 +75,8 @@
             return result;
         }
 
-        public void encode(StringBuilder sb, String boundaryTag, boolean last) throws UnsupportedEncodingException {
+        public void encode(StringBuilder sb, String boundaryTag, boolean last)
+                                                       throws UnsupportedEncodingException {
             sb.append("--").append(boundaryTag).append("\r\n");
             if(mContentType != null)
                 sb.append("Content-Type: ").append(mContentType);
@@ -93,11 +96,20 @@
                 if(mContentType != null &&
                         (mContentType.toUpperCase().contains("TEXT") ||
                          mContentType.toUpperCase().contains("SMIL") )) {
-                    sb.append("Content-Transfer-Encoding: 8BIT\r\n\r\n"); // Add the header split empty line
-                    sb.append(new String(mData,"UTF-8")).append("\r\n");
+                    String text = new String(mData,"UTF-8");
+                    if(text.getBytes().length == text.getBytes("UTF-8").length){
+                        /* Add the header split empty line */
+                        sb.append("Content-Transfer-Encoding: 8BIT\r\n\r\n");
+                    }else {
+                        /* Add the header split empty line */
+                        sb.append("Content-Transfer-Encoding: Quoted-Printable\r\n\r\n");
+                        text = BluetoothMapUtils.encodeQuotedPrintable(mData);
+                    }
+                    sb.append(text).append("\r\n");
                 }
                 else {
-                    sb.append("Content-Transfer-Encoding: Base64\r\n\r\n"); // Add the header split empty line
+                    /* Add the header split empty line */
+                    sb.append("Content-Transfer-Encoding: Base64\r\n\r\n");
                     sb.append(Base64.encodeToString(mData, Base64.DEFAULT)).append("\r\n");
                 }
             }
@@ -108,7 +120,11 @@
 
         public void encodePlainText(StringBuilder sb) throws UnsupportedEncodingException {
             if(mContentType != null && mContentType.toUpperCase().contains("TEXT")) {
-                sb.append(new String(mData,"UTF-8")).append("\r\n");
+                String text = new String(mData, "UTF-8");
+                if(text.getBytes().length != text.getBytes("UTF-8").length){
+                        text = BluetoothMapUtils.encodeQuotedPrintable(mData);
+                }
+                sb.append(text).append("\r\n");
             } else if(mContentType != null && mContentType.toUpperCase().contains("/SMIL")) {
                 /* Skip the smil.xml, as no-one knows what it is. */
             } else {
@@ -168,7 +184,7 @@
     }
     public MimePart addMimePart() {
         if(parts == null)
-            parts = new ArrayList<BluetoothMapbMessageMms.MimePart>();
+            parts = new ArrayList<BluetoothMapbMessageMime.MimePart>();
         MimePart newPart = new MimePart();
         parts.add(newPart);
         return newPart;
@@ -352,8 +368,9 @@
          */
         /* If we are to use US-ASCII anyway, here is the code for it for base64.
           if (subject != null){
-            // Use base64 encoding for the subject, as it may contain non US-ASCII characters or other
-            // illegal (RFC822 header), and android do not seem to have encoders/decoders for quoted-printables
+            // Use base64 encoding for the subject, as it may contain non US-ASCII characters or
+            // other illegal (RFC822 header), and android do not seem to have encoders/decoders
+            // for quoted-printables
             sb.append("Subject:").append("=?utf-8?B?");
             sb.append(Base64.encodeToString(subject.getBytes("utf-8"), Base64.DEFAULT));
             sb.append("?=\r\n");
@@ -384,9 +401,11 @@
             if(messageId != null)
                 sb.append("Message-Id: ").append(messageId).append("\r\n");
             if(contentType != null)
-                sb.append("Content-Type: ").append(contentType).append("; boundary=").append(getBoundary()).append("\r\n");
+                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.
+     // If no headers exists, we still need two CRLF, hence keep it out of the if above.
+        sb.append("\r\n");
     }
 
     /* Notes on MMS
@@ -419,16 +438,16 @@
      * */
 
     /**
-     * Encode the bMessage as a MMS
+     * Encode the bMessage as a Mime message(MMS/IM)
      * @return
      * @throws UnsupportedEncodingException
      */
-    public byte[] encodeMms() throws UnsupportedEncodingException
+    public byte[] encodeMime() throws UnsupportedEncodingException
     {
         ArrayList<byte[]> bodyFragments = new ArrayList<byte[]>();
         StringBuilder sb = new StringBuilder();
         int count = 0;
-        String mmsBody;
+        String mimeBody;
 
         encoding = "8BIT"; // The encoding used
 
@@ -436,7 +455,9 @@
         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. */
+                    /* We call encode on all parts, to include a tag,
+                     * where an attachment is missing. */
+                    part.encodePlainText(sb);
                 }
             } else {
                 for(MimePart part : parts) {
@@ -446,10 +467,11 @@
             }
         }
 
-        mmsBody = sb.toString();
+        mimeBody = sb.toString();
 
-        if(mmsBody != null) {
-            String tmpBody = mmsBody.replaceAll("END:MSG", "/END\\:MSG"); // Replace any occurrences of END:MSG with \END:MSG
+        if(mimeBody != null) {
+           // Replace any occurrences of END:MSG with \END:MSG
+            String tmpBody = mimeBody.replaceAll("END:MSG", "/END\\:MSG");
             bodyFragments.add(tmpBody.getBytes("UTF-8"));
         } else {
             bodyFragments.add(new byte[0]);
@@ -465,7 +487,7 @@
      * @return Null if the entire string were e-mail headers. The part of the string in which
      * no headers were found.
      */
-    private String parseMmsHeaders(String hdrPart) {
+    private String parseMimeHeaders(String hdrPart) {
         String[] headers = hdrPart.split("\r\n");
         if(D) Log.d(TAG,"Header count=" + headers.length);
         String header;
@@ -474,9 +496,10 @@
         for(int i = 0, c = headers.length; i < c; i++) {
             header = headers[i];
             if(D) Log.d(TAG,"Header[" + i + "]: " + header);
-            /* We need to figure out if any headers are present, in cases where devices do not follow the e-mail RFCs.
-             * Skip empty lines, and then parse headers until a non-header line is found, at which point we treat the
-             * remaining as plain text.
+            /* We need to figure out if any headers are present, in cases where devices do
+             * not follow the e-mail RFCs.
+             * Skip empty lines, and then parse headers until a non-header line is found,
+             * at which point we treat the remaining as plain text.
              */
             if(header.trim() == "")
                 continue;
@@ -498,22 +521,27 @@
              * This happens when sending the MMS.
              */
             if(headerType.contains("FROM")) {
+                headerValue = BluetoothMapUtils.stripEncoding(headerValue);
                 Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
                 from = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
             } else if(headerType.contains("TO")) {
+                headerValue = BluetoothMapUtils.stripEncoding(headerValue);
                 Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
                 to = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
             } else if(headerType.contains("CC")) {
+                headerValue = BluetoothMapUtils.stripEncoding(headerValue);
                 Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
                 cc = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
             } else if(headerType.contains("BCC")) {
+                headerValue = BluetoothMapUtils.stripEncoding(headerValue);
                 Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
                 bcc = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
             } else if(headerType.contains("REPLY-TO")) {
+                headerValue = BluetoothMapUtils.stripEncoding(headerValue);
                 Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
                 replyTo = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
             } else if(headerType.contains("SUBJECT")) { // Other headers
-                subject = headerValue;
+                subject = BluetoothMapUtils.stripEncoding(headerValue);
             } else if(headerType.contains("MESSAGE-ID")) {
                 messageId = headerValue;
             } else if(headerType.contains("DATE")) {
@@ -530,7 +558,8 @@
                     if(contentTypeParts[j].contains("boundary")) {
                         boundary = contentTypeParts[j].split("boundary[\\s]*=", 2)[1].trim();
                         // removing quotes from boundary string
-                        if ((boundary.charAt(0) == '\"') && (boundary.charAt(boundary.length()-1) == '\"'))
+                        if ((boundary.charAt(0) == '\"')
+                                && (boundary.charAt(boundary.length()-1) == '\"'))
                             boundary = boundary.substring(1, boundary.length()-1);
                         if(D) Log.d(TAG,"Boundary tag=" + boundary);
                     } else if(contentTypeParts[j].contains("charset")) {
@@ -546,21 +575,23 @@
         return null;
     }
 
-    private void parseMmsMimePart(String partStr) {
+    private void parseMimePart(String partStr) {
         String[] parts = partStr.split("\r\n\r\n", 2); // Split the header from the body
         MimePart newPart = addMimePart();
         String partEncoding = encoding; /* Use the overall encoding as default */
         String body;
 
         String[] headers = parts[0].split("\r\n");
-        if(D) Log.d(TAG, "parseMmsMimePart: headers count=" + headers.length);
+        if(D) Log.d(TAG, "parseMimePart: headers count=" + headers.length);
 
         if(parts.length != 2) {
             body = partStr;
         } else {
             for(String header : headers) {
                 // Skip empty lines(the \r\n after the boundary tag) and endBoundary tags
-                if((header.length() == 0) || (header.trim().isEmpty()) || header.trim().equals("--"))
+                if((header.length() == 0)
+                        || (header.trim().isEmpty())
+                        || header.trim().equals("--"))
                     continue;
 
                 String[] headerParts = header.split(":",2);
@@ -568,7 +599,7 @@
                     if(D) Log.w(TAG, "part-Header not formatted correctly: ");
                     continue;
                 }
-                if(D) Log.d(TAG, "parseMmsMimePart: header=" + header);
+                if(D) Log.d(TAG, "parseMimePart: header=" + header);
                 String headerType = headerParts[0].toUpperCase();
                 String headerValue = headerParts[1].trim();
                 if(headerType.contains("CONTENT-TYPE")) {
@@ -600,7 +631,8 @@
                     newPart.mContentDisposition = headerValue;
                 }
                 else {
-                    if(D) Log.w(TAG,"Skipping unknown part-header: " + headerType + " (" + header + ")");
+                    if(D) Log.w(TAG,"Skipping unknown part-header: " + headerType
+                                                                     + " (" + header + ")");
                 }
             }
             body = parts[1];
@@ -615,7 +647,7 @@
         newPart.mData = decodeBody(body, partEncoding, newPart.mCharsetName);
     }
 
-    private void parseMmsMimeBody(String body) {
+    private void parseMimeBody(String body) {
         MimePart newPart = addMimePart();
         newPart.mCharsetName = mCharset;
         newPart.mData = decodeBody(body, encoding, mCharset);
@@ -625,7 +657,7 @@
         if(encoding != null && encoding.toUpperCase().contains("BASE64")) {
             return Base64.decode(body, Base64.DEFAULT);
         } else if(encoding != null && encoding.toUpperCase().contains("QUOTED-PRINTABLE")) {
-            return quotedPrintableToUtf8(body, charset);
+            return BluetoothMapUtils.quotedPrintableToUtf8(body, charset);
         }else{
             // TODO: handle other encoding types? - here we simply store the string data as bytes
             try {
@@ -638,7 +670,7 @@
         return null;
     }
 
-    private void parseMms(String message) {
+    private void parseMime(String message) {
         /* Overall strategy for decoding:
          * 1) split on first empty line to extract the header
          * 2) unfold and parse headers
@@ -658,11 +690,11 @@
         }
         else
         {
-            remaining = parseMmsHeaders(messageParts[0]);
+            remaining = parseMimeHeaders(messageParts[0]);
             // If we have some text not being a header, add it to the message body.
             if(remaining != null) {
                 messageBody = remaining + messageParts[1];
-                if(D) Log.d(TAG, "parseMms remaining=" + remaining );
+                if(D) Log.d(TAG, "parseMime remaining=" + remaining );
             } else {
                 messageBody = messageParts[1];
             }
@@ -671,7 +703,7 @@
         if(boundary == null)
         {
             // If the boundary is not set, handle as non-multi-part
-            parseMmsMimeBody(messageBody);
+            parseMimeBody(messageBody);
             setTextOnly(true);
             if(contentType == null)
                 contentType = "text/plain";
@@ -685,111 +717,17 @@
             for(int i = 1; i < mimeParts.length - 1; i++) {
                 String part = mimeParts[i];
                 if (part != null && (part.length() > 0))
-                    parseMmsMimePart(part);
+                    parseMimePart(part);
             }
         }
     }
 
-    /**
-     * Convert a quoted-printable encoded string to a UTF-8 string:
-     *  - Remove any soft line breaks: "=<CRLF>"
-     *  - Convert all "=xx" to the corresponding byte
-     * @param text quoted-printable encoded UTF-8 text
-     * @return decoded UTF-8 string
-     */
-    public static byte[] quotedPrintableToUtf8(String text, String charset) {
-        byte[] output = new byte[text.length()]; // We allocate for the worst case memory need
-        byte[] input = null;
-        try {
-            input = text.getBytes("US-ASCII");
-        } catch (UnsupportedEncodingException e) {
-            /* This cannot happen as "US-ASCII" is supported for all Java implementations */ }
-
-        if(input == null){
-            return "".getBytes();
-        }
-
-        int in, out, stopCnt = input.length-2; // Leave room for peaking the next two bytes
-
-        /* Algorithm:
-         *  - Search for token, copying all non token chars
-         * */
-        for(in=0, out=0; in < stopCnt; in++){
-            byte b0 = input[in];
-            if(b0 == '=') {
-                byte b1 = input[++in];
-                byte b2 = input[++in];
-                if(b1 == '\r' && b2 == '\n') {
-                    continue; // soft line break, remove all tree;
-                }
-                if(((b1 >= '0' && b1 <= '9') || (b1 >= 'A' && b1 <= 'F') || (b1 >= 'a' && b1 <= 'f')) &&
-                   ((b2 >= '0' && b2 <= '9') || (b2 >= 'A' && b2 <= 'F') || (b2 >= 'a' && b2 <= 'f'))) {
-                    if(V)Log.v(TAG, "Found hex number: " + String.format("%c%c", b1, b2));
-                    if(b1 <= '9')       b1 = (byte) (b1 - '0');
-                    else if (b1 <= 'F') b1 = (byte) (b1 - 'A' + 10);
-                    else if (b1 <= 'f') b1 = (byte) (b1 - 'a' + 10);
-
-                    if(b2 <= '9')       b2 = (byte) (b2 - '0');
-                    else if (b2 <= 'F') b2 = (byte) (b2 - 'A' + 10);
-                    else if (b2 <= 'f') b2 = (byte) (b2 - 'a' + 10);
-
-                    if(V)Log.v(TAG, "Resulting nibble values: " + String.format("b1=%x b2=%x", b1, b2));
-
-                    output[out++] = (byte)(b1<<4 | b2); // valid hex char, append
-                    if(V)Log.v(TAG, "Resulting value: "  + String.format("0x%2x", output[out-1]));
-                    continue;
-                }
-                Log.w(TAG, "Received wrongly quoted printable encoded text. Continuing at best effort...");
-                /* If we get a '=' without either a hex value or CRLF following, just add it and
-                 * rewind the in counter. */
-                output[out++] = b0;
-                in -= 2;
-                continue;
-            } else {
-                output[out++] = b0;
-                continue;
-            }
-        }
-
-        // Just add any remaining characters. If they contain any encoding, it is invalid,
-        // and best effort would be just to display the characters.
-        while (in < input.length) {
-            output[out++] = input[in++];
-        }
-
-        String result = null;
-        // Figure out if we support the charset, else fall back to UTF-8, as this is what
-        // the MAP specification suggest to use, and is compatible with US-ASCII.
-        if(charset == null){
-            charset = "UTF-8";
-        } else {
-            charset = charset.toUpperCase();
-            try {
-                if(Charset.isSupported(charset) == false) {
-                    charset = "UTF-8";
-                }
-            } catch (IllegalCharsetNameException e) {
-                Log.w(TAG, "Received unknown charset: " + charset + " - using UTF-8.");
-                charset = "UTF-8";
-            }
-        }
-        try{
-            result = new String(output, 0, out, charset);
-        } catch (UnsupportedEncodingException e) {
-            /* This cannot happen unless Charset.isSupported() is out of sync with String */
-            try{
-                result = new String(output, 0, out, "UTF-8");
-            } catch (UnsupportedEncodingException e2) {/* This cannot happen */}
-        }
-        return result.getBytes(); /* return the result as "UTF-8" bytes */
-    }
-
     /* Notes on SMIL decoding (from http://tools.ietf.org/html/rfc2557):
      * src="filename.jpg" refers to a part with Content-Location: filename.jpg
      * src="cid:1234@hest.net" refers to a part with Content-ID:<1234@hest.net>*/
     @Override
     public void parseMsgPart(String msgPart) {
-        parseMms(msgPart);
+        parseMime(msgPart);
 
     }
 
@@ -801,7 +739,7 @@
 
     @Override
     public byte[] encode() throws UnsupportedEncodingException {
-        return encodeMms();
+        return encodeMime();
     }
 
 }
diff --git a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
index 7df31da..6c12018 100644
--- a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
+++ b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
@@ -259,7 +259,7 @@
             }
             mConnected = connected;
         }
-            synchronized (this) {
+        synchronized (this) {
                 mWaitingForRemote = false;
         }
     }
diff --git a/src/com/android/bluetooth/map/MapContact.java b/src/com/android/bluetooth/map/MapContact.java
new file mode 100644
index 0000000..ce2da98
--- /dev/null
+++ b/src/com/android/bluetooth/map/MapContact.java
@@ -0,0 +1,62 @@
+/*
+* Copyright (C) 2015 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 com.android.bluetooth.SignedLongLong;
+
+
+/**
+ * Local representation of an Android contact
+ */
+public class MapContact {
+    private final String mName;
+    private final long mId;
+
+    private MapContact(long id, String name) {
+        mId = id;
+        mName = name;
+    }
+
+    public static MapContact create(long id, String name){
+        return new MapContact(id, name);
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public long getId() {
+        return mId;
+    }
+
+    public String getXBtUidString() {
+        if(mId > 0) {
+            return  BluetoothMapUtils.getLongLongAsString(mId, 0);
+        }
+        return null;
+    }
+
+    public SignedLongLong getXBtUid() {
+        if(mId > 0) {
+            return  new SignedLongLong(mId, 0);
+        }
+        return null;
+    }
+
+    @Override
+    public String toString(){
+        return mName;
+    }
+}
diff --git a/src/com/android/bluetooth/map/SmsMmsContacts.java b/src/com/android/bluetooth/map/SmsMmsContacts.java
new file mode 100644
index 0000000..f43a270
--- /dev/null
+++ b/src/com/android/bluetooth/map/SmsMmsContacts.java
@@ -0,0 +1,195 @@
+/*
+* Copyright (C) 2015 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.util.Arrays;
+import java.util.HashMap;
+import java.util.regex.Pattern;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.Telephony.CanonicalAddressesColumns;
+import android.provider.Telephony.MmsSms;
+import android.util.Log;
+
+/**
+ * Use these functions when extracting data for listings. It caches frequently used data to
+ * speed up building large listings - e.g. before applying filtering.
+ */
+@TargetApi(19)
+public class SmsMmsContacts {
+
+    private static final String TAG = "SmsMmsContacts";
+
+    private HashMap<Long,String> mPhoneNumbers = null;
+    private final HashMap<String,MapContact> mNames = new HashMap<String, MapContact>(10);
+
+    private static final Uri ADDRESS_URI =
+            MmsSms.CONTENT_URI.buildUpon().appendPath("canonical-addresses").build();
+
+    private static final String[] ADDRESS_PROJECTION = { CanonicalAddressesColumns._ID,
+                    CanonicalAddressesColumns.ADDRESS };
+    private static final int COL_ADDR_ID =
+            Arrays.asList(ADDRESS_PROJECTION).indexOf(CanonicalAddressesColumns._ID);
+    private static final int COL_ADDR_ADDR =
+            Arrays.asList(ADDRESS_PROJECTION).indexOf(CanonicalAddressesColumns.ADDRESS);
+
+    private static final String[] CONTACT_PROJECTION = {Contacts._ID, Contacts.DISPLAY_NAME};
+    private static final String CONTACT_SEL_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
+    private static final int COL_CONTACT_ID =
+            Arrays.asList(CONTACT_PROJECTION).indexOf(Contacts._ID);
+    private static final int COL_CONTACT_NAME =
+            Arrays.asList(CONTACT_PROJECTION).indexOf(Contacts.DISPLAY_NAME);
+
+    /**
+     * Get a contacts phone number based on the canonical addresses id of the contact.
+     * (The ID listed in the Threads table.)
+     * @param resolver the ContantResolver to be used.
+     * @param id the id of the contact, as listed in the Threads table
+     * @return the phone number of the contact - or null if id does not exist.
+     */
+    public String getPhoneNumber(ContentResolver resolver, long id) {
+        String number;
+        if(mPhoneNumbers != null && (number = mPhoneNumbers.get(id)) != null) {
+            return number;
+        }
+        fillPhoneCache(resolver);
+        return mPhoneNumbers.get(id);
+    }
+
+    public static String getPhoneNumberUncached(ContentResolver resolver, long id) {
+        String where = CanonicalAddressesColumns._ID + " = " + id;
+        Cursor c = resolver.query(ADDRESS_URI, ADDRESS_PROJECTION, where, null, null);
+        try {
+            if (c != null) {
+                if(c.moveToPosition(0)) {
+                    return c.getString(COL_ADDR_ADDR);
+                }
+            }
+            Log.e(TAG, "query failed");
+        } finally {
+            if(c != null) c.close();
+        }
+        return null;
+    }
+
+    /**
+     * Clears the local cache. Call after a listing is complete, to avoid using invalid data.
+     */
+    public void clearCache() {
+        if(mPhoneNumbers != null) mPhoneNumbers.clear();
+        if(mNames != null) mNames.clear();
+    }
+
+    /**
+     * Refreshes the cache, by clearing all cached values and fill the cache with the result of
+     * a new query.
+     * @param resolver the ContantResolver to be used.
+     */
+    private void fillPhoneCache(ContentResolver resolver){
+        Cursor c = resolver.query(ADDRESS_URI, ADDRESS_PROJECTION, null, null, null);
+        if(mPhoneNumbers == null) {
+            int size = 0;
+            if(c != null)
+            {
+                size = c.getCount();
+            }
+            mPhoneNumbers = new HashMap<Long, String>(size);
+        } else {
+            mPhoneNumbers.clear();
+        }
+        try {
+            if (c != null) {
+                long id;
+                String addr;
+                c.moveToPosition(-1);
+                while (c.moveToNext()) {
+                    id = c.getLong(COL_ADDR_ID);
+                    addr = c.getString(COL_ADDR_ADDR);
+                    mPhoneNumbers.put(id, addr);
+                }
+            } else {
+                Log.e(TAG, "query failed");
+            }
+        } finally {
+            if(c != null) c.close();
+        }
+    }
+
+    public MapContact getContactNameFromPhone(String phone, ContentResolver resolver) {
+        return getContactNameFromPhone(phone, resolver, null);
+    }
+    /**
+     * Lookup a contacts name in the Android Contacts database.
+     * @param phone the phone number of the contact
+     * @param resolver the ContentResolver to use.
+     * @return the name of the contact or null, if no contact was found.
+     */
+    public MapContact getContactNameFromPhone(String phone, ContentResolver resolver,
+            String contactNameFilter) {
+        MapContact contact = mNames.get(phone);
+
+        if(contact != null){
+            if(contact.getId() < 0) {
+                return null;
+            }
+            if(contactNameFilter == null) {
+                return contact;
+            }
+            // Validate filter
+            String searchString = contactNameFilter.replace("*", ".*");
+            searchString = ".*" + searchString + ".*";
+            Pattern p = Pattern.compile(Pattern.quote(searchString), Pattern.CASE_INSENSITIVE);
+            if(p.matcher(contact.getName()).find()) {
+                return contact;
+            }
+            return null;
+        }
+
+        // TODO: Should we change to extract both formatted name, and display name?
+
+        Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,Uri.encode(phone));
+        String selection = CONTACT_SEL_VISIBLE;
+        String[] selectionArgs = null;
+        if(contactNameFilter != null) {
+            selection += "AND " + ContactsContract.Contacts.DISPLAY_NAME + " like ?";
+            selectionArgs = new String[]{"%" + contactNameFilter.replace("*", "%") + "%"};
+        }
+
+        Cursor c = resolver.query(uri, CONTACT_PROJECTION, selection, selectionArgs, null);
+        try {
+            if (c != null && c.getCount() >= 1) {
+                c.moveToFirst();
+                long id = c.getLong(COL_CONTACT_ID);
+                String name = c.getString(COL_CONTACT_NAME);
+                contact = MapContact.create(id, name);
+                mNames.put(phone, contact);
+            } else {
+                contact = MapContact.create(-1, null);
+                mNames.put(phone, contact);
+                contact = null;
+            }
+        } finally {
+            if (c != null) c.close();
+        }
+        return contact;
+    }
+}
diff --git a/tests/Android.mk b/tests/Android.mk
index ca30b08..612cb99 100755
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -5,7 +5,7 @@
 LOCAL_MODULE_TAGS := optional
 LOCAL_CERTIFICATE := platform
 
-LOCAL_JAVA_LIBRARIES := javax.obex android.test.runner
+LOCAL_JAVA_LIBRARIES := javax.obex android.test.runner telephony-common mms-common
 LOCAL_STATIC_JAVA_LIBRARIES := com.android.emailcommon
 
 # Include all test java files.
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 657c858..cf128db 100755
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -11,6 +11,7 @@
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
     <uses-permission android:name="android.permission.READ_CALL_LOG" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
diff --git a/tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java b/tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java
index 9e318c6..15e1bdc 100644
--- a/tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java
+++ b/tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java
@@ -1,102 +1,887 @@
 package com.android.bluetooth.tests;
 
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.ParcelFileDescriptor;
+import android.provider.BaseColumns;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.Threads;
 import android.test.AndroidTestCase;
 import android.util.Log;
-import android.database.Cursor;
-import android.content.Context;
-import android.content.ContentResolver;
 
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
+import com.android.bluetooth.map.BluetoothMapMasInstance;
+import com.android.bluetooth.map.BluetoothMapAccountItem;
+import com.android.bluetooth.map.BluetoothMapAccountLoader;
+import com.android.bluetooth.map.BluetoothMapAppParams;
 import com.android.bluetooth.map.BluetoothMapContent;
-import com.android.bluetooth.map.BluetoothMapContentObserver;
+import com.android.bluetooth.map.BluetoothMapFolderElement;
+import com.android.bluetooth.map.BluetoothMapMessageListing;
+import com.android.bluetooth.map.BluetoothMapUtils;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.bluetooth.map.MapContact;
+import com.android.bluetooth.map.SmsMmsContacts;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
 
-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;
-
+@TargetApi(19)
 public class BluetoothMapContentTest extends AndroidTestCase {
+
     private static final String TAG = "BluetoothMapContentTest";
 
     private static final boolean D = true;
-    private static final boolean V = true;
 
     private Context mContext;
     private ContentResolver mResolver;
+    private SmsMmsContacts mContacts = new SmsMmsContacts();
 
-    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
+    private BluetoothMapFolderElement mCurrentFolder;
+    private BluetoothMapAccountItem mAccount = null;
+
+    private static final int MAS_ID = 0;
+    private static final int REMOTE_FEATURE_MASK = 0x07FFFFFF;
+    private static final BluetoothMapMasInstance mMasInstance =
+            new MockMasInstance(MAS_ID, REMOTE_FEATURE_MASK);
+
+
+    private Uri mEmailUri = null;
+    private Uri mEmailMessagesUri = null;
+    private Uri mEmailFolderUri = null;
+    private Uri mEmailAccountUri = null;
+
+    static final String[] EMAIL_ACCOUNT_PROJECTION = new String[] {
+        BluetoothMapContract.MessageColumns.FOLDER_ID,
+        BluetoothMapContract.MessageColumns.ACCOUNT_ID,
     };
 
+    private void printAccountInfo(Cursor c) {
+        if (D) Log.d(TAG, BluetoothMapContract.MessageColumns.ACCOUNT_ID + " : " +
+                c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.ACCOUNT_ID)) );
+    }
+
+    static final String[] BT_MESSAGE_ID_PROJECTION = new String[] {
+        BluetoothMapContract.MessageColumns._ID,
+        BluetoothMapContract.MessageColumns.DATE,
+    };
+
+    static final String[] BT_MESSAGE_PROJECTION = BluetoothMapContract.BT_MESSAGE_PROJECTION;
+
+    static final String[] BT_ACCOUNT_PROJECTION = BluetoothMapContract.BT_ACCOUNT_PROJECTION;
+
+    static final String[] BT_FOLDER_PROJECTION = BluetoothMapContract.BT_FOLDER_PROJECTION;
+
+    BluetoothMapAccountLoader loader;
+    LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> mFullList;
+
+    public BluetoothMapContentTest() {
+        super();
+    }
+
+    private void initTestSetup(){
+        mContext = this.getContext();
+        mResolver = mContext.getContentResolver();
+
+        // find enabled account
+        loader    = new BluetoothMapAccountLoader(mContext);
+        mFullList = loader.parsePackages(false);
+        String accountId = getEnabledAccount();
+        Uri tmpEmailUri = Uri.parse("content://com.android.email.bluetoothprovider/");
+
+        mEmailUri = Uri.withAppendedPath(tmpEmailUri, accountId + "/");
+        mEmailMessagesUri = Uri.parse(mEmailUri + BluetoothMapContract.TABLE_MESSAGE);
+        mEmailFolderUri = Uri.parse(mEmailUri + BluetoothMapContract.TABLE_FOLDER);
+        mEmailAccountUri = Uri.parse(tmpEmailUri + BluetoothMapContract.TABLE_ACCOUNT);
+
+        buildFolderStructure();
+
+    }
+
+    public String getEnabledAccount(){
+        if(D)Log.d(TAG,"getEnabledAccountItems()\n");
+        String account = null;
+        for(BluetoothMapAccountItem app:mFullList.keySet()){
+            ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
+            for(BluetoothMapAccountItem acc: accountList){
+                mAccount = acc;
+                account = acc.getId();
+                break;
+            }
+        }
+        return account;
+    }
+
+    private void buildFolderStructure(){
+        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
+        if(mEmailFolderUri != null) {
+            addEmailFolders(tmpFolder);
+        }
+    }
+
+    private void addEmailFolders(BluetoothMapFolderElement parentFolder) {
+        BluetoothMapFolderElement newFolder;
+        String where = BluetoothMapContract.FolderColumns.PARENT_FOLDER_ID +
+                        " = " + parentFolder.getFolderId();
+        Cursor c = mContext.getContentResolver().query(mEmailFolderUri,
+                        BluetoothMapContract.BT_FOLDER_PROJECTION, where, null, null);
+        if (c != null) {
+            c.moveToPosition(-1);
+            while (c.moveToNext()) {
+                String name = c.getString(c.getColumnIndex(BluetoothMapContract.FolderColumns.NAME));
+                long id = c.getLong(c.getColumnIndex(BluetoothMapContract.FolderColumns._ID));
+                newFolder = parentFolder.addEmailFolder(name, id);
+                addEmailFolders(newFolder); // Use recursion to add any sub folders
+            }
+            c.close();
+        } else {
+            if (D) Log.d(TAG, "addEmailFolders(): no elements found");
+        }
+    }
+
+    private BluetoothMapFolderElement getInbox() {
+        BluetoothMapFolderElement tmpFolderElement = null;
+
+        tmpFolderElement = mCurrentFolder.getSubFolder("telecom");
+        tmpFolderElement = tmpFolderElement.getSubFolder("msg");
+        tmpFolderElement = tmpFolderElement.getSubFolder("inbox");
+        return tmpFolderElement;
+    }
+
+    private BluetoothMapFolderElement getOutbox() {
+        BluetoothMapFolderElement tmpFolderElement = null;
+
+        tmpFolderElement = mCurrentFolder.getSubFolder("telecom");
+        tmpFolderElement = tmpFolderElement.getSubFolder("msg");
+        tmpFolderElement = tmpFolderElement.getSubFolder("outbox");
+        return tmpFolderElement;
+    }
+
+
     private String getDateTimeString(long timestamp) {
         SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
         Date date = new Date(timestamp);
         return format.format(date); // Format to YYYYMMDDTHHMMSS local time
     }
 
-    private void printEmail(Cursor c) {
-        if (D) Log.d(TAG, "printEmail " +
-            c.getLong(c.getColumnIndex(EmailContent.RECORD_ID)) +
-            "\n   " + MessageColumns.DISPLAY_NAME + " : " + c.getString(c.getColumnIndex(MessageColumns.DISPLAY_NAME)) +
-            "\n   " + MessageColumns.TIMESTAMP + " : " + getDateTimeString(c.getLong(c.getColumnIndex(MessageColumns.TIMESTAMP))) +
-            "\n   " + MessageColumns.SUBJECT + " : " + c.getString(c.getColumnIndex(MessageColumns.SUBJECT)) +
-            "\n   " + MessageColumns.FLAG_READ + " : " + c.getString(c.getColumnIndex(MessageColumns.FLAG_READ)) +
-            "\n   " + MessageColumns.FLAG_ATTACHMENT + " : " + c.getInt(c.getColumnIndex(MessageColumns.FLAG_ATTACHMENT)) +
-            "\n   " + MessageColumns.FLAGS + " : " + c.getInt(c.getColumnIndex(MessageColumns.FLAGS)) +
-            "\n   " + SyncColumns.SERVER_ID + " : " + c.getInt(c.getColumnIndex(SyncColumns.SERVER_ID)) +
-            "\n   " + MessageColumns.DRAFT_INFO + " : " + c.getInt(c.getColumnIndex(MessageColumns.DRAFT_INFO)) +
-            "\n   " + MessageColumns.MESSAGE_ID + " : " + c.getInt(c.getColumnIndex(MessageColumns.MESSAGE_ID)) +
-            "\n   " + MessageColumns.MAILBOX_KEY + " : " + c.getInt(c.getColumnIndex(MessageColumns.MAILBOX_KEY)) +
-            "\n   " + MessageColumns.ACCOUNT_KEY + " : " + c.getInt(c.getColumnIndex(MessageColumns.ACCOUNT_KEY)) +
-            "\n   " + MessageColumns.FROM_LIST + " : " + c.getString(c.getColumnIndex(MessageColumns.FROM_LIST)) +
-            "\n   " + MessageColumns.TO_LIST + " : " + c.getString(c.getColumnIndex(MessageColumns.TO_LIST)) +
-            "\n   " + MessageColumns.CC_LIST + " : " + c.getString(c.getColumnIndex(MessageColumns.CC_LIST)) +
-            "\n   " + MessageColumns.BCC_LIST + " : " + c.getString(c.getColumnIndex(MessageColumns.BCC_LIST)) +
-            "\n   " + MessageColumns.REPLY_TO_LIST + " : " + c.getString(c.getColumnIndex(MessageColumns.REPLY_TO_LIST)) +
-            "\n   " + SyncColumns.SERVER_TIMESTAMP + " : " + getDateTimeString(c.getLong(c.getColumnIndex(SyncColumns.SERVER_TIMESTAMP))) +
-            "\n   " + MessageColumns.MEETING_INFO + " : " + c.getString(c.getColumnIndex(MessageColumns.MEETING_INFO)) +
-            "\n   " + MessageColumns.SNIPPET + " : " + c.getString(c.getColumnIndex(MessageColumns.SNIPPET)) +
-            "\n   " + MessageColumns.PROTOCOL_SEARCH_INFO + " : " + c.getString(c.getColumnIndex(MessageColumns.PROTOCOL_SEARCH_INFO)) +
-            "\n   " + MessageColumns.THREAD_TOPIC + " : " + c.getString(c.getColumnIndex(MessageColumns.THREAD_TOPIC)));
+    private void printCursor(Cursor c) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("\nprintCursor:\n");
+        for(int i = 0; i < c.getColumnCount(); i++) {
+            if(c.getColumnName(i).equals(BluetoothMapContract.MessageColumns.DATE)){
+                sb.append("  ").append(c.getColumnName(i))
+                    .append(" : ").append(getDateTimeString(c.getLong(i))).append("\n");
+            } else {
+                sb.append("  ").append(c.getColumnName(i))
+                    .append(" : ").append(c.getString(i)).append("\n");
+            }
+        }
+        Log.d(TAG, sb.toString());
     }
 
-    public void dumpEmailMessageTable() {
-        Log.d(TAG, "**** Dump of email message table ****");
+    private void dumpMessageContent(Cursor c) {
+        long id = c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns._ID));
+        Uri uri = Uri.parse(mEmailMessagesUri + "/" + id
+                + "/" + BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS);
+        FileInputStream is = null;
+        ParcelFileDescriptor fd = null;
+        int count;
+        try {
+            fd = mResolver.openFileDescriptor(uri, "r");
+            is = new FileInputStream(fd.getFileDescriptor());
+            byte[] buffer = new byte[1024];
 
-        Cursor c = mResolver.query(Message.CONTENT_URI,
-            EMAIL_PROJECTION, null, null, "_id DESC");
+            while((count = is.read(buffer)) != -1) {
+                Log.d(TAG, new String(buffer,0, count));
+            }
+
+
+        } catch (FileNotFoundException e) {
+            Log.w(TAG, e);
+        } catch (IOException e) {
+            Log.w(TAG, e);
+        }
+        finally {
+            try {
+               if(is != null)
+                    is.close();
+            } catch (IOException e) {}
+               try {
+                if(fd != null)
+                    fd.close();
+            } catch (IOException e) {}
+        }
+    }
+
+    /**
+     * Create a new message in the database outbox, based on the content of c.
+     * @param c
+     */
+    private void writeMessageContent(Cursor c) {
+        long id = c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns._ID));
+        Uri uri = Uri.parse(mEmailMessagesUri + "/" + id + "/"
+                + BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS);
+        FileInputStream is = null;
+        ParcelFileDescriptor fd = null;
+        FileOutputStream os = null;
+        ParcelFileDescriptor fdOut = null;
+
+        ContentValues newMessage = new ContentValues();
+        BluetoothMapFolderElement outFolder = getOutbox();
+        newMessage.put(BluetoothMapContract.MessageColumns.FOLDER_ID, outFolder.getFolderId());
+        // Now insert the empty message into outbox (Maybe it should be draft first, and then a move?)
+        // TODO: Examine if we need to set some additional flags, e.g. visable?
+        Uri uriOut = mResolver.insert(mEmailMessagesUri, newMessage);
+        int count;
+        try {
+            fd = mResolver.openFileDescriptor(uri, "r");
+            is = new FileInputStream(fd.getFileDescriptor());
+            fdOut = mResolver.openFileDescriptor(uri, "w");
+            os = new FileOutputStream(fdOut.getFileDescriptor());
+            byte[] buffer = new byte[1024];
+
+            while((count = is.read(buffer)) != -1) {
+                Log.d(TAG, new String(buffer,0, count));
+                os.write(buffer, 0, count);
+            }
+        } catch (FileNotFoundException e) {
+            Log.w(TAG, e);
+        } catch (IOException e) {
+            Log.w(TAG, e);
+        }
+        finally {
+            try {
+               if(is != null)
+                    is.close();
+            } catch (IOException e) {}
+               try {
+                if(fd != null)
+                    fd.close();
+            } catch (IOException e) {}
+               try {
+                   if(os != null)
+                        os.close();
+                } catch (IOException e) {}
+                   try {
+                    if(fdOut != null)
+                        fdOut.close();
+                } catch (IOException e) {}
+        }
+    }
+
+    private void writeMessage(Cursor c) {
+        Log.d(TAG, "c.getCount() = " + c.getCount());
+            c.moveToPosition(-1);
+        if (c.moveToNext()) {
+            writeMessageContent(c);
+        }
+        c.close();
+    }
+
+
+    private void dumpCursor(Cursor c) {
+        Log.d(TAG, "c.getCount() = " + c.getCount());
+        c.moveToPosition(-1);
+        while (c.moveToNext()) {
+            printCursor(c);
+        }
+        c.close();
+    }
+
+    private void callBluetoothProvider() {
+        Log.d(TAG, "**** Test call into email provider ****");
+        int accountId = 0;
+        int mailboxId = 0;
+
+        Log.d(TAG, "contentUri = " + mEmailMessagesUri);
+
+        Cursor c = mResolver.query(mEmailMessagesUri, EMAIL_ACCOUNT_PROJECTION,
+                null, null, "_id DESC");
         if (c != null) {
             Log.d(TAG, "c.getCount() = " + c.getCount());
             c.moveToPosition(-1);
             while (c.moveToNext()) {
-                printEmail(c);
+                printAccountInfo(c);
+                mailboxId = c.getInt(c.getColumnIndex(
+                                BluetoothMapContract.MessageColumns.FOLDER_ID));
+                accountId = c.getInt(c.getColumnIndex(
+                                BluetoothMapContract.MessageColumns.ACCOUNT_ID));
             }
+           c.close();
         } else {
             Log.d(TAG, "query failed");
-            c.close();
+        }
+
+        final Bundle extras = new Bundle(2);
+        /* TODO: find mailbox from DB */
+        extras.putLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID, mailboxId);
+        extras.putLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, accountId);
+        Bundle myBundle = mResolver.call(mEmailUri, BluetoothMapContract.METHOD_UPDATE_FOLDER,
+                                            null, extras);
+    }
+
+
+    public void testMsgListing() {
+        initTestSetup();
+        BluetoothMapContent mBtMapContent = new BluetoothMapContent(mContext, mAccount,
+                mMasInstance);
+        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+        Log.d(TAG, "**** testMsgListing **** ");
+        BluetoothMapFolderElement fe = getInbox();
+
+        if (fe != null) {
+            if (D) Log.d(TAG, "folder name=" + fe.getName());
+
+            appParams.setFilterMessageType(0x0B);
+            appParams.setMaxListCount(1024);
+            appParams.setStartOffset(0);
+
+            BluetoothMapMessageListing msgListing = mBtMapContent.msgListing(fe, appParams);
+            int listCount = msgListing.getCount();
+            int msgListingSize = mBtMapContent.msgListingSize(fe, appParams);
+
+            if (listCount == msgListingSize) {
+                Log.d(TAG, "testMsgListing - " + listCount );
+            }
+            else {
+                Log.d(TAG, "testMsgListing - None");
+            }
+        }
+        else {
+            Log.d(TAG, "testMsgListing - failed ");
+        }
+
+    }
+
+    public void testMsgListingUnread() {
+        initTestSetup();
+        BluetoothMapContent mBtMapContent = new BluetoothMapContent(mContext, mAccount,
+                mMasInstance);
+        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+        Log.d(TAG, "**** testMsgListingUnread **** ");
+        BluetoothMapFolderElement fe = getInbox();
+
+        if (fe != null) {
+
+            appParams.setFilterReadStatus(0x01);
+            appParams.setFilterMessageType(0x0B);
+            appParams.setMaxListCount(1024);
+            appParams.setStartOffset(0);
+
+            BluetoothMapMessageListing msgListing = mBtMapContent.msgListing(fe, appParams);
+
+            int listCount = msgListing.getCount();
+            if (msgListing.getCount() > 0) {
+                Log.d(TAG, "testMsgListingUnread - " + listCount );
+            }
+            else {
+                Log.d(TAG, "testMsgListingUnread - None");
+            }
+        }
+        else {
+            Log.d(TAG, "testMsgListingUnread - getInbox failed ");
         }
     }
 
-    public BluetoothMapContentTest() {
-        super();
+    public void testMsgListingWithOriginator() {
+        initTestSetup();
+        BluetoothMapContent mBtMapContent = new BluetoothMapContent(mContext, mAccount,
+                mMasInstance);
+        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+        Log.d(TAG, "**** testMsgListingUnread **** ");
+        BluetoothMapFolderElement fe = getInbox();
+
+        if (fe != null) {
+
+            appParams.setFilterOriginator("*scsc.*");
+            appParams.setFilterMessageType(0x0B);
+            appParams.setMaxListCount(1024);
+            appParams.setStartOffset(0);
+
+            BluetoothMapMessageListing msgListing = mBtMapContent.msgListing(fe, appParams);
+
+            int listCount = msgListing.getCount();
+            if (msgListing.getCount() > 0) {
+                Log.d(TAG, "testMsgListingWithOriginator - " + listCount );
+            }
+            else {
+                Log.d(TAG, "testMsgListingWithOriginator - None");
+            }
+        } else {
+            Log.d(TAG, "testMsgListingWithOriginator - getInbox failed ");
+        }
+    }
+
+    public void testGetMessages() {
+        initTestSetup();
+        BluetoothMapContent mBtMapContent = new BluetoothMapContent(mContext, mAccount,
+                mMasInstance);
+        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+        Log.d(TAG, "**** testGetMessages **** ");
+        BluetoothMapFolderElement fe = getInbox();
+
+        if (fe != null) {
+            appParams.setAttachment(0);
+            appParams.setCharset(BluetoothMapContent.MAP_MESSAGE_CHARSET_UTF8);
+
+            //get message handles
+            Cursor c = mResolver.query(mEmailMessagesUri, BT_MESSAGE_ID_PROJECTION,
+                    null, null, "_id DESC");
+            if (c != null) {
+                c.moveToPosition(-1);
+                while (c.moveToNext()) {
+                    Long id = c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns._ID));
+                    String handle = BluetoothMapUtils.getMapHandle(id, TYPE.EMAIL);
+                    try {
+                        // getMessage
+                        byte[] bytes = mBtMapContent.getMessage(handle, appParams, fe, "1.1");
+                        Log.d(TAG, "testGetMessages id=" + id + ", handle=" + handle +
+                                ", length=" + bytes.length );
+                        String testPrint = new String(bytes);
+                        Log.d(TAG, "testGetMessage (only dump first part):\n" + testPrint );
+                    } catch (UnsupportedEncodingException e) {
+                        Log.w(TAG, e);
+                    } finally {
+
+                    }
+                }
+            } else {
+                Log.d(TAG, "testGetMessages - no cursor ");
+            }
+        } else {
+            Log.d(TAG, "testGetMessages - getInbox failed ");
+        }
+
+    }
+
+    public void testDumpAccounts() {
+        initTestSetup();
+        Log.d(TAG, "**** testDumpAccounts **** \n from: " + mEmailAccountUri.toString());
+        Cursor c = mResolver.query(mEmailAccountUri, BT_ACCOUNT_PROJECTION, null, null, "_id DESC");
+        if (c != null) {
+            dumpCursor(c);
+        } else {
+            Log.d(TAG, "query failed");
+        }
+        Log.w(TAG, "testDumpAccounts(): ThreadId: " + Thread.currentThread().getId());
+
+    }
+
+    public void testAccountUpdate() {
+        initTestSetup();
+        Log.d(TAG, "**** testAccountUpdate **** \n of: " + mEmailAccountUri.toString());
+        Cursor c = mResolver.query(mEmailAccountUri, BT_ACCOUNT_PROJECTION, null, null, "_id DESC");
+
+        if (c != null) {
+            c.moveToPosition(-1);
+            while (c.moveToNext()) {
+                printCursor(c);
+                Long id = c.getLong(c.getColumnIndex(BluetoothMapContract.AccountColumns._ID));
+                int exposeFlag = c.getInt(
+                        c.getColumnIndex(BluetoothMapContract.AccountColumns.FLAG_EXPOSE));
+                String where = BluetoothMapContract.AccountColumns._ID + " = " + id;
+                ContentValues values = new ContentValues();
+                if(exposeFlag == 1) {
+                    values.put(BluetoothMapContract.AccountColumns.FLAG_EXPOSE, (int) 0);
+                } else {
+                    values.put(BluetoothMapContract.AccountColumns.FLAG_EXPOSE, (int) 1);
+                }
+                Log.i(TAG, "Calling update() with selection: " + where +
+                           "values(exposeFlag): " +
+                            values.getAsInteger(BluetoothMapContract.AccountColumns.FLAG_EXPOSE));
+                mResolver.update(mEmailAccountUri, values, where, null);
+            }
+            c.close();
+        }
+
     }
 
     public void testDumpMessages() {
+        initTestSetup();
+
+        if (D) Log.d(TAG, "**** testDumpMessages **** \n uri=" + mEmailMessagesUri.toString());
+        BluetoothMapFolderElement fe = getInbox();
+        if (fe != null)
+        {
+            String where ="";
+            //where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + fe.getEmailFolderId();
+            Cursor c = mResolver.query(mEmailMessagesUri, BT_MESSAGE_PROJECTION,
+                    where, null, "_id DESC");
+            if (c != null) {
+                dumpCursor(c);
+            } else {
+                if (D) Log.d(TAG, "query failed");
+            }
+            if (D) Log.w(TAG, "dumpMessage(): ThreadId: " + Thread.currentThread().getId());
+        } else {
+            if (D) Log.w(TAG, "dumpMessage(): ThreadId: " + Thread.currentThread().getId());
+        }
+    }
+
+    public void testDumpMessageContent() {
+        initTestSetup();
+
+        Log.d(TAG, "**** testDumpMessageContent **** from: " + mEmailMessagesUri.toString());
+//        BluetoothMapFolderElement fe = getInbox();
+//        String where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + fe.getEmailFolderId();
+//        where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + " = 0";
+
+        Cursor c = mResolver.query(mEmailMessagesUri, BT_MESSAGE_PROJECTION, null, null, "_id DESC");
+        if (c != null && c.moveToNext()) {
+            dumpMessageContent(c);
+        } else {
+            Log.d(TAG, "query failed");
+        }
+        Log.w(TAG, "dumpMessage(): ThreadId: " + Thread.currentThread().getId());
+    }
+
+    public void testWriteMessageContent() {
+        initTestSetup();
+        Log.d(TAG, "**** testWriteMessageContent **** from: " + mEmailMessagesUri.toString());
+        BluetoothMapFolderElement fe = getInbox();
+        String where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + fe.getFolderId();
+//        where += " AND " + BluetoothMapContract.MessageColumns.HIGH_PRIORITY + " = 0";
+        Cursor c = mResolver.query(mEmailMessagesUri, BT_MESSAGE_PROJECTION, where, null, "_id DESC");
+        if (c != null) {
+            writeMessage(c);
+        } else {
+            Log.d(TAG, "query failed");
+        }
+        Log.w(TAG, "writeMessage(): ThreadId: " + Thread.currentThread().getId());
+    }
+
+    /*
+     * Handle test cases
+     */
+    private static final long HANDLE_TYPE_SMS_CDMA_MASK            = (((long)0x1)<<60);
+
+    public void testHandle() {
+        String handleStr = null;
+        Debug.startMethodTracing("str_format");
+        for(long i = 0; i < 10000; i++) {
+            handleStr = String.format("%016X",(i | HANDLE_TYPE_SMS_CDMA_MASK));
+        }
+        Debug.stopMethodTracing();
+        Debug.startMethodTracing("getHandleString");
+        for(long i = 0; i < 10000; i++) {
+            handleStr = BluetoothMapUtils.getLongAsString(i | HANDLE_TYPE_SMS_CDMA_MASK);
+        }
+        Debug.stopMethodTracing();
+    }
+
+    /*
+     * Folder test cases
+     */
+
+    public void testDumpEmailFolders() {
+        initTestSetup();
+        Debug.startMethodTracing();
+        String where = null;
+        Cursor c = mResolver.query(mEmailFolderUri, BT_FOLDER_PROJECTION, where, null, "_id DESC");
+        if (c != null) {
+            dumpCursor(c);
+            c.close();
+        } else {
+            Log.d(TAG, "query failed");
+        }
+        Debug.stopMethodTracing();
+    }
+
+    public void testFolderPath() {
+        initTestSetup();
+        Log.d(TAG, "**** testFolderPath **** ");
+        BluetoothMapFolderElement fe = getInbox();
+        BluetoothMapFolderElement folder = fe.getFolderById(fe.getFolderId());
+        if(folder == null) {
+            Log.d(TAG, "**** testFolderPath unable to find the folder with id: " +
+                    fe.getFolderId());
+        }
+        else {
+            Log.d(TAG, "**** testFolderPath found the folder with id: " +
+                    fe.getFolderId() + "\nFull path: " +
+                    folder.getFullPath());
+        }
+    }
+
+    public void testFolderElement() {
+        Log.d(TAG, "**** testFolderElement **** ");
+        BluetoothMapFolderElement fe = new BluetoothMapFolderElement("root", null);
+        fe = fe.addEmailFolder("MsG", 1);
+        fe.addEmailFolder("Outbox", 100);
+        fe.addEmailFolder("Sent", 200);
+        BluetoothMapFolderElement inbox = fe.addEmailFolder("Inbox", 300);
+        fe.addEmailFolder("Draft", 400);
+        fe.addEmailFolder("Deleted", 500);
+        inbox.addEmailFolder("keep", 301);
+        inbox.addEmailFolder("private", 302);
+        inbox.addEmailFolder("junk", 303);
+
+        BluetoothMapFolderElement folder = fe.getFolderById(400);
+        assertEquals("draft", folder.getName());
+        assertEquals("private", fe.getFolderById(302).getName());
+        assertEquals("junk", fe.getRoot().getFolderById(303).getName());
+        assertEquals("msg/inbox/keep", fe.getFolderById(301).getFullPath());
+    }
+
+    /*
+     * SMS test cases
+     */
+    public void testAddSmsEntries() {
+        int count = 1000;
         mContext = this.getContext();
         mResolver = mContext.getContentResolver();
-        dumpEmailMessageTable();
+        ContentValues values[] = new ContentValues[count];
+        long date = System.currentTimeMillis();
+        Log.i(TAG, "Preparing messages...");
+        for (int x=0;x<count;x++){
+            //if (D) Log.d(TAG, "*** Adding dummy sms #"+x);
+
+            ContentValues item = new ContentValues(4);
+            item.put("address", "1234");
+            item.put("body", "test message "+x);
+            item.put("date", date);
+            item.put("read", "0");
+
+            values[x] = item;
+            // Uri mUri = mResolver.insert(Uri.parse("content://sms"), item);
+        }
+        Log.i(TAG, "Starting bulk insert...");
+        mResolver.bulkInsert(Uri.parse("content://sms"), values);
+        Log.i(TAG, "Bulk insert done.");
     }
+
+    public void testAddSms() {
+        mContext = this.getContext();
+        mResolver = mContext.getContentResolver();
+        if (D) Log.d(TAG, "*** Adding dummy sms #");
+
+        ContentValues item = new ContentValues();
+        item.put("address", "1234");
+        item.put("body", "test message");
+        item.put("date", System.currentTimeMillis());
+        item.put("read", "0");
+
+        Uri mUri = mResolver.insert(Uri.parse("content://sms"), item);
+    }
+
+    public void testServiceSms() {
+        mContext = this.getContext();
+        mResolver = mContext.getContentResolver();
+        if (D) Log.d(TAG, "*** Adding dummy sms #");
+
+        ContentValues item = new ContentValues();
+        item.put("address", "C-Bonde");
+        item.put("body", "test message");
+        item.put("date", System.currentTimeMillis());
+        item.put("read", "0");
+
+        Uri mUri = mResolver.insert(Uri.parse("content://sms"), item);
+    }
+
+    /*
+     * MMS content test cases
+     */
+    public static final int MMS_FROM = 0x89;
+    public static final int MMS_TO = 0x97;
+    public static final int MMS_BCC = 0x81;
+    public static final int MMS_CC = 0x82;
+
+    private void printMmsAddr(long id) {
+        final String[] projection = null;
+        String selection = new String("msg_id=" + id);
+        String uriStr = String.format("content://mms/%d/addr", id);
+        Uri uriAddress = Uri.parse(uriStr);
+        Cursor c = mResolver.query(uriAddress, projection, selection, null, null);
+
+        if (c.moveToFirst()) {
+            do {
+                String add = c.getString(c.getColumnIndex("address"));
+                Integer type = c.getInt(c.getColumnIndex("type"));
+                if (type == MMS_TO) {
+                    if (D) Log.d(TAG, "   recipient: " + add + " (type: " + type + ")");
+                } else if (type == MMS_FROM) {
+                    if (D) Log.d(TAG, "   originator: " + add + " (type: " + type + ")");
+                } else {
+                    if (D) Log.d(TAG, "   address other: " + add + " (type: " + type + ")");
+                }
+                printCursor(c);
+
+            } while(c.moveToNext());
+        }
+    }
+
+    private void printMmsPartImage(long partid) {
+        String uriStr = String.format("content://mms/part/%d", partid);
+        Uri uriAddress = Uri.parse(uriStr);
+        int ch;
+        StringBuffer sb = new StringBuffer("");
+        InputStream is = null;
+
+        try {
+            is = mResolver.openInputStream(uriAddress);
+
+            while ((ch = is.read()) != -1) {
+                sb.append((char)ch);
+            }
+            if (D) Log.d(TAG, sb.toString());
+
+        } catch (IOException e) {
+            // do nothing for now
+            e.printStackTrace();
+        }
+    }
+
+    private void printMmsParts(long id) {
+        final String[] projection = null;
+        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, projection, selection, null, null);
+
+        if (c.moveToFirst()) {
+            int i = 0;
+            do {
+                if (D) Log.d(TAG, "   part " + i++);
+                printCursor(c);
+
+                /* if (ct.equals("image/jpeg")) { */
+                /*     printMmsPartImage(partid); */
+                /* } */
+            } while(c.moveToNext());
+        }
+    }
+
+    public void dumpMmsTable() {
+        mContext = this.getContext();
+        mResolver = mContext.getContentResolver();
+
+        if (D) Log.d(TAG, "**** Dump of mms table ****");
+        Cursor c = mResolver.query(Mms.CONTENT_URI,
+                null, null, null, "_id DESC");
+        if (c != null) {
+            if (D) Log.d(TAG, "c.getCount() = " + c.getCount());
+            c.moveToPosition(-1);
+            while (c.moveToNext()) {
+                Log.d(TAG,"Message:");
+                printCursor(c);
+                long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                Log.d(TAG,"Address:");
+                printMmsAddr(id);
+                Log.d(TAG,"Parts:");
+                printMmsParts(id);
+            }
+            c.close();
+        } else {
+            Log.d(TAG, "query failed");
+        }
+    }
+
+    /**
+     * This dumps the thread database.
+     * Interesting how useful this is.
+     *  - DATE is described to be the creation date of the thread. But it actually
+     *    contains the time-date of the last activity of the thread.
+     *  - RECIPIENTS is a list of the contacts related to the thread. The number can
+     *    be found for both MMS and SMS in the "canonical-addresses" table.
+     *  - The READ column tells if the thread have been read. (read = 1: no unread messages)
+     *  - The snippet is a small piece of text from the last message, and could be used as thread
+     *    name. Please however note that if we do this, the version-counter should change each
+     *    time a message is added to the thread. But since it changes the read attribute and
+     *    last activity, it changes anyway.
+     *  -
+     */
+
+
+    public void dumpThreadsTable() {
+        mContext = this.getContext();
+        mResolver = mContext.getContentResolver();
+        mContacts.clearCache();
+        Uri uri = Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build();
+
+        if (D) Log.d(TAG, "**** Dump of Threads table ****\nUri: " + uri);
+        Cursor c = mResolver.query(uri,
+                null, null, null, "_id DESC");
+        if (c != null) {
+            if (D) Log.d(TAG, "c.getCount() = " + c.getCount());
+            c.moveToPosition(-1);
+            while (c.moveToNext()) {
+                Log.d(TAG,"Threads:");
+                printCursor(c);
+                String ids = c.getString(c.getColumnIndex(Threads.RECIPIENT_IDS));
+                Log.d(TAG,"Address:");
+                printAddresses(ids);
+/*                Log.d(TAG,"Parts:");
+                printMmsParts(id);*/
+            }
+            c.close();
+        } else {
+            Log.d(TAG, "query failed");
+        }
+    }
+
+    /**
+     * This test shows the content of the canonicalAddresses table.
+     * Conclusion:
+     * The _id column matches the id's from the RECIPIENT_IDS column
+     * in the Threads table, hence are to be used to map from an id to
+     * a phone number, which then can be matched to a contact.
+     */
+    public void dumpCanAddrTable() {
+        mContext = this.getContext();
+        mResolver = mContext.getContentResolver();
+        Uri uri = Uri.parse("content://mms-sms/canonical-addresses");
+        uri = MmsSms.CONTENT_URI.buildUpon().appendPath("canonical-addresses").build();
+        dumpUri(uri);
+    }
+
+    public void dumpUri(Uri uri) {
+        if (D) Log.d(TAG, "**** Dump of table ****\nUri: " + uri);
+        Cursor c = mResolver.query(uri, null, null, null, null);
+        if (c != null) {
+            if (D) Log.d(TAG, "c.getCount() = " + c.getCount());
+            c.moveToPosition(-1);
+            while (c.moveToNext()) {
+                Log.d(TAG,"Entry: " + c.getPosition());
+                printCursor(c);
+            }
+            c.close();
+        } else {
+            Log.d(TAG, "query failed");
+        }
+    }
+
+    private void printAddresses(String idsStr) {
+        String[] ids = idsStr.split(" ");
+        for (String id : ids) {
+            long longId;
+            try {
+                longId = Long.parseLong(id);
+                String addr = mContacts.getPhoneNumber(mResolver, longId);
+                MapContact contact = mContacts.getContactNameFromPhone(addr, mResolver);
+                Log.d(TAG, "  id " + id + ": " + addr + " - " + contact.getName()
+                        + "  X-BT-UID: " + contact.getXBtUidString());
+            } catch (NumberFormatException ex) {
+                // skip this id
+                continue;
+            }
+        }
+    }
+
 }
diff --git a/tests/src/com/android/bluetooth/tests/BluetoothMapIMContentTest.java b/tests/src/com/android/bluetooth/tests/BluetoothMapIMContentTest.java
new file mode 100644
index 0000000..2d100e4
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/BluetoothMapIMContentTest.java
@@ -0,0 +1,171 @@
+package com.android.bluetooth.tests;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+import com.android.bluetooth.mapapi.BluetoothMapContract.ConversationColumns;
+
+//import info.guardianproject.otr.app.im.provider.Imps;
+//import info.guardianproject.otr.app.im.provider.ImpsBluetoothProvider;
+
+public class BluetoothMapIMContentTest extends AndroidTestCase {
+    private static final String TAG = "BluetoothMapIMContentTest";
+
+    private static final boolean D = true;
+    private static final boolean V = true;
+
+    private Context mContext;
+    private ContentResolver mResolver;
+
+    private String getDateTimeString(long timestamp) {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Date date = new Date(timestamp);
+        return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+    }
+
+    private void printCursor(Cursor c) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("\nprintCursor:\n");
+        for(int i = 0; i < c.getColumnCount(); i++) {
+            if(c.getColumnName(i).equals(BluetoothMapContract.MessageColumns.DATE) ||
+               c.getColumnName(i).equals(BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY) ||
+               c.getColumnName(i).equals(BluetoothMapContract.ChatStatusColumns.LAST_ACTIVE) ||
+               c.getColumnName(i).equals(BluetoothMapContract.PresenceColumns.LAST_ONLINE) ){
+                sb.append("  ").append(c.getColumnName(i)).append(" : ").append(getDateTimeString(c.getLong(i))).append("\n");
+            } else {
+                sb.append("  ").append(c.getColumnName(i)).append(" : ").append(c.getString(i)).append("\n");
+            }
+        }
+        Log.d(TAG, sb.toString());
+    }
+
+    private void dumpImMessageTable() {
+        Log.d(TAG, "**** Dump of im message table ****");
+
+        Cursor c = mResolver.query(
+                BluetoothMapContract.buildMessageUri("info.guardianproject.otr.app.im.provider.bluetoothprovider"),
+                BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, null, null, "_id DESC");
+        if (c != null) {
+            Log.d(TAG, "c.getCount() = " + c.getCount());
+            c.moveToPosition(-1);
+            while (c.moveToNext()) {
+                printCursor(c);
+            }
+        } else {
+            Log.d(TAG, "query failed");
+            c.close();
+        }
+
+    }
+
+    private void insertImMessage( ) {
+        Log.d(TAG, "**** Insert message in im message table ****");
+        ContentValues cv = new ContentValues();
+        cv.put(BluetoothMapContract.MessageColumns.BODY, "This is a test to insert a message");
+        cv.put(BluetoothMapContract.MessageColumns.DATE, System.currentTimeMillis());
+        cv.put(BluetoothMapContract.MessageColumns.THREAD_ID, 2);
+        Uri uri = BluetoothMapContract.buildMessageUri("info.guardianproject.otr.app.im.provider.bluetoothprovider");
+        Uri uriWithId = mResolver.insert(uri, cv);
+        if (uriWithId != null) {
+            Log.d(TAG, "uriWithId = " + uriWithId.toString());
+        } else {
+            Log.d(TAG, "query failed");
+        }
+
+    }
+
+    private void dumpImConversationTable() {
+        Log.d(TAG, "**** Dump of conversation message table ****");
+
+        Uri uri = BluetoothMapContract.buildConversationUri(
+                "info.guardianproject.otr.app.im.provider.bluetoothprovider", "1");
+        uri = uri.buildUpon().appendQueryParameter(BluetoothMapContract.FILTER_ORIGINATOR_SUBSTRING,
+                "asp").build();
+
+        Cursor convo = mResolver.query(
+                uri,
+                BluetoothMapContract.BT_CONVERSATION_PROJECTION, null, null,
+                null);
+
+        if (convo != null) {
+            Log.d(TAG, "c.getCount() = " + convo.getCount());
+
+            while(convo.moveToNext()) {
+                printCursor(convo);
+            }
+            convo.close();
+        } else {
+            Log.d(TAG, "query failed");
+        }
+    }
+
+
+    private void dumpImContactsTable() {
+        Log.d(TAG, "**** Dump of contacts message table ****");
+        Cursor cContact = mResolver.query(
+                BluetoothMapContract.buildConvoContactsUri("info.guardianproject.otr.app.im.provider.bluetoothprovider","1"),
+                BluetoothMapContract.BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION, null, null, "_id DESC");
+
+        if (cContact != null && cContact.moveToFirst()) {
+            Log.d(TAG, "c.getCount() = " +  cContact.getCount());
+            do {
+                printCursor(cContact);
+            } while(cContact.moveToNext());
+
+        } else {
+            Log.d(TAG, "query failed");
+            cContact.close();
+        }
+    }
+
+    private void dumpImAccountsTable() {
+        Log.d(TAG, "**** Dump of accounts table ****");
+        Cursor cContact = mResolver.query(
+                BluetoothMapContract.buildAccountUri("info.guardianproject.otr.app.im.provider.bluetoothprovider"),
+                BluetoothMapContract.BT_ACCOUNT_PROJECTION, null, null, "_id DESC");
+
+        if (cContact != null && cContact.moveToFirst()) {
+            Log.d(TAG, "c.getCount() = " +  cContact.getCount());
+            do {
+                printCursor(cContact);
+            } while(cContact.moveToNext());
+
+        } else {
+            Log.d(TAG, "query failed");
+            cContact.close();
+        }
+    }
+
+
+    public BluetoothMapIMContentTest() {
+        super();
+    }
+
+    public void testDumpMessages() {
+        mContext = this.getContext();
+        mResolver = mContext.getContentResolver();
+        dumpImMessageTable();
+        dumpImConversationTable();
+        dumpImContactsTable();
+        dumpImAccountsTable();
+
+        insertImMessage();
+
+    }
+
+    public void testDumpConversations() {
+        mContext = this.getContext();
+        mResolver = mContext.getContentResolver();
+        dumpImConversationTable();
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/bluetooth/tests/BluetoothMapUtilsTest.java b/tests/src/com/android/bluetooth/tests/BluetoothMapUtilsTest.java
new file mode 100644
index 0000000..788b36f
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/BluetoothMapUtilsTest.java
@@ -0,0 +1,116 @@
+
+package com.android.bluetooth.tests;
+
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.util.Base64;
+
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import com.android.bluetooth.SignedLongLong;
+import com.android.bluetooth.map.BluetoothMapUtils;
+
+public class BluetoothMapUtilsTest extends AndroidTestCase {
+    private static final String TAG = "BluetoothMapUtilsTest";
+
+    private static final boolean D = true;
+    private static final boolean V = true;
+    private static final String encText1 = "=?UTF-8?b?w6bDuMOlw4bDmMOF?="; //æøåÆØÅ base64
+    private static final String encText2 = "=?UTF-8?B?w6bDuMOlw4bDmMOF?="; //æøåÆØÅ base64
+    private static final String encText3 = "=?UTF-8?q?=C3=A6=C3=B8=C3=A5=C3=86=C3=98=C3=85?="; //æøåÆØÅ QP
+    private static final String encText4 = "=?UTF-8?Q?=C3=A6=C3=B8=C3=A5=C3=86=C3=98=C3=85?="; //æøåÆØÅ QP
+    private static final String encText5 = "=?UTF-8?B?=C3=A6=C3=B8=C3=A5=C3=86=C3=98=C3=85?="; //QP in base64 string - should not compute
+    private static final String encText6 = "=?UTF-8?Q?w6bDuMOlw4bDmMOF?="; //æøåÆØÅ base64 in QP stirng - should not compute
+    private static final String encText7 = "this is a split =?UTF-8?Q?=C3=A6=C3=B8=C3=A5 ###123?= with more =?UTF-8?Q?=C3=A6=C3=B8=C3=A5 ###123?= inside"; // mix of QP and normal
+    private static final String encText8 = "this is a split =?UTF-8?B?w6bDuMOlICMjIzEyMw==?= with more =?UTF-8?Q?=C3=A6=C3=B8=C3=A5 ###123?= inside"; // mix of normal, QP and Base64
+    private static final String encText9 = "=?UTF-8?Q??=";
+    private static final String encText10 = "=?UTF-8?Q??=";
+    private static final String encText11 = "=?UTF-8?Q??=";
+
+    private static final String decText1 = "æøåÆØÅ";
+    private static final String decText2 = "æøåÆØÅ";
+    private static final String decText3 = "æøåÆØÅ";
+    private static final String decText4 = "æøåÆØÅ";
+    private static final String decText5 = encText5;
+    private static final String decText6 = "w6bDuMOlw4bDmMOF";
+    private static final String decText7 = "this is a split æøå ###123 with more æøå ###123 inside";
+    private static final String decText8 = "this is a split æøå ###123 with more æøå ###123 inside";
+
+    public BluetoothMapUtilsTest() {
+        super();
+
+    }
+
+
+    public void testEncoder(){
+        assertTrue(BluetoothMapUtils.stripEncoding(encText1).equals(decText1));
+        assertTrue(BluetoothMapUtils.stripEncoding(encText2).equals(decText2));
+        assertTrue(BluetoothMapUtils.stripEncoding(encText3).equals(decText3));
+        assertTrue(BluetoothMapUtils.stripEncoding(encText4).equals(decText4));
+        assertTrue(BluetoothMapUtils.stripEncoding(encText5).equals(decText5));
+        assertTrue(BluetoothMapUtils.stripEncoding(encText6).equals(decText6));
+        Log.i(TAG,"##############################enc7:" +
+                BluetoothMapUtils.stripEncoding(encText7));
+        assertTrue(BluetoothMapUtils.stripEncoding(encText7).equals(decText7));
+        assertTrue(BluetoothMapUtils.stripEncoding(encText8).equals(decText8));
+    }
+
+    public void testXBtUid() throws UnsupportedEncodingException {
+        {
+            SignedLongLong expected = new SignedLongLong(0x12345678L, 0x90abcdefL);
+            /* this will cause an exception, since the value is too big... */
+            SignedLongLong value;
+            value = SignedLongLong.fromString("90abcdef0000000012345678");
+            assertTrue("expected: " + expected + " value = " + value,
+                    0 == value.compareTo(expected));
+            assertEquals("expected: " + expected + " value = " + value,
+                    expected.toHexString(), value.toHexString());
+            Log.i(TAG,"Succesfully compared : " + value);
+        }
+        {
+            SignedLongLong expected = new SignedLongLong(0x12345678L, 0xfedcba9890abcdefL);
+            /* this will cause an exception, since the value is too big... */
+            SignedLongLong value;
+            value = SignedLongLong.fromString("fedcba9890abcdef0000000012345678");
+            assertTrue("expected: " + expected + " value = " + value,
+                    0 == value.compareTo(expected));
+            assertEquals("expected: " + expected + " value = " + value,
+                    expected.toHexString(), value.toHexString());
+            Log.i(TAG,"Succesfully compared : " + value);
+        }
+        {
+            SignedLongLong expected = new SignedLongLong(0x12345678L, 0);
+            SignedLongLong value = SignedLongLong.fromString("000012345678");
+            assertTrue("expected: " + expected + " value = " + value,
+                    0 == value.compareTo(expected));
+            assertEquals("expected: " + expected + " value = " + value,
+                    expected.toHexString(), value.toHexString());
+            Log.i(TAG,"Succesfully compared : " + value);
+        }
+        {
+            SignedLongLong expected = new SignedLongLong(0x12345678L, 0);
+            SignedLongLong value = SignedLongLong.fromString("12345678");
+            assertTrue("expected: " + expected + " value = " + value,
+                    0 == value.compareTo(expected));
+            assertEquals("expected: " + expected + " value = " + value,
+                    expected.toHexString(), value.toHexString());
+            Log.i(TAG,"Succesfully compared : " + value);
+        }
+        {
+            SignedLongLong expected = new SignedLongLong(0x123456789abcdef1L, 0x9L);
+            SignedLongLong value = SignedLongLong.fromString("0009123456789abcdef1");
+            assertTrue("expected: " + expected + " value = " + value,
+                    0 == value.compareTo(expected));
+            assertEquals("expected: " + expected + " value = " + value,
+                    expected.toHexString(), value.toHexString());
+            Log.i(TAG,"Succesfully compared : " + value);
+        }
+        {
+            long expected = 0x123456789abcdefL;
+            long value = BluetoothMapUtils.getLongFromString(" 1234 5678 9abc-def");
+            assertTrue("expected: " + expected + " value = " + value, value == expected);
+        }
+    }
+}
diff --git a/tests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java b/tests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java
index 1fcec4e..8724e9f 100755
--- a/tests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java
+++ b/tests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java
@@ -8,22 +8,19 @@
 import java.util.Calendar;
 import java.util.Date;
 
+import org.apache.http.message.BasicHeaderElement;
 import org.apache.http.message.BasicHeaderValueFormatter;
 
-import android.preference.PreferenceFragment;
 import android.test.AndroidTestCase;
 import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
 
 import com.android.bluetooth.map.BluetoothMapAppParams;
-import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
 import com.android.bluetooth.map.BluetoothMapSmsPdu;
+import com.android.bluetooth.map.BluetoothMapUtils;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
 import com.android.bluetooth.map.BluetoothMapbMessage;
-import com.android.bluetooth.map.BluetoothMapbMessageMms;
+import com.android.bluetooth.map.BluetoothMapbMessageMime;
 import com.android.bluetooth.map.BluetoothMapbMessageSms;
-import org.apache.http.message.BasicHeaderValueFormatter;
-import org.apache.http.message.BasicHeaderElement;
 
 /***
  *
@@ -58,6 +55,15 @@
                         "EMAIL:casper@email.add\r\n" +
                         "EMAIL:bonde@email.add\r\n" +
                     "END:VCARD\r\n" +
+                    "BEGIN:VCARD\r\n" +
+                    "VERSION:3.0\r\n" +
+                    "FN:Casper Bonde\r\n" +
+                    "N:Bonde,Casper\r\n" +
+                    "TEL:+4512345678\r\n" +
+                    "TEL:+4587654321\r\n" +
+                    "EMAIL:casper@email.add\r\n" +
+                    "EMAIL:bonde@email.add\r\n" +
+                "END:VCARD\r\n" +
                     "BEGIN:BENV\r\n" +
                         "BEGIN:VCARD\r\n" +
                             "VERSION:3.0\r\n" +
@@ -68,6 +74,15 @@
                             "EMAIL:casper@email.add\r\n" +
                             "EMAIL:bonde@email.add\r\n" +
                         "END:VCARD\r\n" +
+                        "BEGIN:VCARD\r\n" +
+                            "VERSION:3.0\r\n" +
+                            "FN:Jens Hansen\r\n" +
+                            "N:\r\n" +
+                            "TEL:+4512345678\r\n" +
+                            "TEL:+4587654321\r\n" +
+                            "EMAIL:casper@email.add\r\n" +
+                            "EMAIL:bonde@email.add\r\n" +
+                        "END:VCARD\r\n" +
                         "BEGIN:BBODY\r\n" +
                             "CHARSET:UTF-8\r\n" +
                             "LENGTH:45\r\n" +
@@ -81,8 +96,10 @@
         String encoded;
         String[] phone = {"+4512345678", "+4587654321"};
         String[] email = {"casper@email.add", "bonde@email.add"};
-        msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email);
-        msg.addRecipient("", "Jens Hansen", phone, email);
+        msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email, null, null);
+        msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email, null, null);
+        msg.addRecipient("", "Jens Hansen", phone, email, null, null);
+        msg.addRecipient("", "Jens Hansen", phone, email, null, null);
         msg.setFolder("inbox");
         msg.setSmsBody("This is a short message");
         msg.setStatus(false);
@@ -182,8 +199,8 @@
         String encoded;
         String[] phone = {"00498912345678", "+4587654321"};
         String[] email = {"casper@email.add", "bonde@email.add"};
-        msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email);
-        msg.addRecipient("", "Jens Hansen", phone, email);
+        msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email, null, null);
+        msg.addRecipient("", "Jens Hansen", phone, email, null, null);
         msg.setFolder("inbox");
         /* TODO: extract current time, and build the expected string */
         msg.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus("Let's go fishing!", "00498912345678", date.getTime()));
@@ -252,8 +269,8 @@
         String encoded;
         String[] phone = {"00498912345678", "+4587654321"};
         String[] email = {"casper@email.add", "bonde@email.add"};
-        msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email);
-        msg.addRecipient("", "Jens Hansen", phone, email);
+        msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email, null, null);
+        msg.addRecipient("", "Jens Hansen", phone, email, null, null);
         msg.setFolder("outbox");
         /* TODO: extract current time, and build the expected string */
         msg.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus("Let's go fishing!", "00498912345678"));
@@ -421,7 +438,7 @@
      * Test encoding of a simple MMS text message (UTF8). This validates most parameters.
      */
     public void testMmsEncodeText() {
-        BluetoothMapbMessageMms msg = new BluetoothMapbMessageMms();
+        BluetoothMapbMessageMime  msg = new BluetoothMapbMessageMime ();
         String str1 =
                  "BEGIN:BMSG\r\n" +
                     "VERSION:1.0\r\n" +
@@ -449,9 +466,16 @@
                         "END:VCARD\r\n" +
                         "BEGIN:BBODY\r\n" +
                             "CHARSET:UTF-8\r\n" +
-                            "LENGTH:45\r\n" +
+                            "LENGTH:184\r\n" +
                             "BEGIN:MSG\r\n" +
-                                "This is a short message\r\n" +
+                            "From: \"Jørn Hansen\" <bonde@email.add>;\r\n" +
+                            "To: \"Jørn Hansen\" <bonde@email.add>;\r\n" +
+                            "Cc: Jens Hansen <bonde@email.add>;\r\n" +
+                            "\r\n" +
+                            "This is a short message\r\n" +
+                            "\r\n" +
+                            "<partNameimage>\r\n" +
+                            "\r\n" +
                             "END:MSG\r\n" +
                         "END:BBODY\r\n" +
                     "END:BENV\r\n" +
@@ -460,14 +484,14 @@
         String encoded;
         String[] phone = {"+4512345678", "+4587654321"};
         String[] email = {"casper@email.add", "bonde@email.add"};
-        msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email);
-        msg.addRecipient("", "Jørn Hansen", phone, email);
+        msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email, null, null);
+        msg.addRecipient("", "Jørn Hansen", phone, email, null, null);
         msg.setFolder("inbox");
         msg.setIncludeAttachments(false);
         msg.addTo("Jørn Hansen", "bonde@email.add");
         msg.addCc("Jens Hansen", "bonde@email.add");
         msg.addFrom("Jørn Hansen", "bonde@email.add");
-        BluetoothMapbMessageMms.MimePart part = msg.addMimePart();
+        BluetoothMapbMessageMime .MimePart part = msg.addMimePart();
         part.mPartName = "partNameText";
         part.mContentType ="dsfajfdlk/text/asdfafda";
         try {
@@ -485,6 +509,8 @@
 
         msg.setStatus(false);
         msg.setType(TYPE.MMS);
+        msg.updateCharset();
+
         try {
             encoded = new String(msg.encode());
             if(D) Log.d(TAG, encoded);
@@ -501,5 +527,56 @@
         if(D) Log.i(TAG, "The encoded header: " + headerStr);
     }
 
+    public void testQuotedPrintable() {
+        testQuotedPrintableIso8859_1();
+        testQuotedPrintableUTF_8();
+    }
+
+    public void testQuotedPrintableIso8859_1() {
+        String charset = "iso-8859-1";
+        String input = "Hello, here are some danish letters: =E6=F8=E5.\r\n" +
+                       "Please check that you are able to remove soft " +
+                       "line breaks and handle '=3D' =\r\ncharacters within the text. \r\n" +
+                       "Just a sequence of non optimal characters to make " +
+                       "it complete: !\"#$@[\\]^{|}=\r\n~\r\n\r\n" +
+                       "Thanks\r\n" +
+                       "Casper";
+        String expected = "Hello, here are some danish letters: æøå.\r\n" +
+                "Please check that you are able to remove soft " +
+                "line breaks and handle '=' characters within the text. \r\n" +
+                "Just a sequence of non optimal characters to make " +
+                "it complete: !\"#$@[\\]^{|}~\r\n\r\n" +
+                "Thanks\r\n" +
+                "Casper";
+        String output;
+        output = new String(BluetoothMapUtils.quotedPrintableToUtf8(input, charset));
+        if(D) Log.d(TAG, "\nExpected: \n" + expected);
+        if(D) Log.d(TAG, "\nOutput: \n" + output);
+        assertTrue(output.equals(expected));
+    }
+
+    public void testQuotedPrintableUTF_8() {
+        String charset = "utf-8";
+        String input = "Hello, here are some danish letters: =C3=A6=C3=B8=C3=A5.\r\n" +
+                       "Please check that you are able to remove soft " +
+                       "line breaks and handle '=3D' =\r\ncharacters within the text. \r\n" +
+                       "Just a sequence of non optimal characters to make " +
+                       "it complete: !\"#$@[\\]^{|}=\r\n~\r\n\r\n" +
+                       "Thanks\r\n" +
+                       "Casper";
+        String expected = "Hello, here are some danish letters: æøå.\r\n" +
+                "Please check that you are able to remove soft " +
+                "line breaks and handle '=' characters within the text. \r\n" +
+                "Just a sequence of non optimal characters to make " +
+                "it complete: !\"#$@[\\]^{|}~\r\n\r\n" +
+                "Thanks\r\n" +
+                "Casper";
+        String output;
+        output = new String(BluetoothMapUtils.quotedPrintableToUtf8(input, charset));
+        if(D) Log.d(TAG, "\nExpected: \n" + expected);
+        if(D) Log.d(TAG, "\nOutput: \n" + output);
+        assertTrue(output.equals(expected));
+    }
+
 }
 
diff --git a/tests/src/com/android/bluetooth/tests/BluetoothTestUtils.java b/tests/src/com/android/bluetooth/tests/BluetoothTestUtils.java
new file mode 100644
index 0000000..4ef89e1
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/BluetoothTestUtils.java
@@ -0,0 +1,54 @@
+package com.android.bluetooth.tests;
+
+import android.annotation.TargetApi;
+import android.bluetooth.BluetoothAdapter;
+import android.os.Build;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+
+@TargetApi(Build.VERSION_CODES.ECLAIR)
+public class BluetoothTestUtils extends AndroidTestCase {
+
+    protected static String TAG = "BluetoothTestUtils";
+    protected static final boolean D = true;
+
+    static final int POLL_TIME = 500;
+    static final int ENABLE_TIMEOUT = 5000;
+
+    /** Helper to turn BT on.
+     * This method will either fail on an assert, or return with BT turned on.
+     * Behavior of getState() and isEnabled() are validated along the way.
+     */
+    public static void enableBt(BluetoothAdapter adapter) {
+        if (adapter.getState() == BluetoothAdapter.STATE_ON) {
+            assertTrue(adapter.isEnabled());
+            return;
+        }
+        assertEquals(BluetoothAdapter.STATE_OFF, adapter.getState());
+        assertFalse(adapter.isEnabled());
+        adapter.enable();
+        for (int i=0; i<ENABLE_TIMEOUT/POLL_TIME; i++) {
+            int state = adapter.getState();
+            switch (state) {
+            case BluetoothAdapter.STATE_ON:
+                assertTrue(adapter.isEnabled());
+                Log.i(TAG, "Bluetooth enabled...");
+                return;
+            case BluetoothAdapter.STATE_OFF:
+                Log.i(TAG, "STATE_OFF: Still waiting for enable to begin...");
+                break;
+            default:
+                Log.i(TAG, "Status is: " + state);
+                assertEquals(BluetoothAdapter.STATE_TURNING_ON, adapter.getState());
+                assertFalse(adapter.isEnabled());
+                break;
+            }
+            try {
+                Thread.sleep(POLL_TIME);
+            } catch (InterruptedException e) {}
+        }
+        fail("enable() timeout");
+    }
+
+}
diff --git a/tests/src/com/android/bluetooth/tests/ISeqStepAction.java b/tests/src/com/android/bluetooth/tests/ISeqStepAction.java
new file mode 100644
index 0000000..db66af2
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/ISeqStepAction.java
@@ -0,0 +1,13 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+
+public interface ISeqStepAction {
+
+    void execute(SeqStep step, HeaderSet request, Operation op)
+            throws IOException;
+
+}
diff --git a/tests/src/com/android/bluetooth/tests/ISeqStepValidator.java b/tests/src/com/android/bluetooth/tests/ISeqStepValidator.java
new file mode 100644
index 0000000..5819f06
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/ISeqStepValidator.java
@@ -0,0 +1,15 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+
+/**
+ * Interface to validate test step result
+ */
+public interface ISeqStepValidator {
+    boolean validate(SeqStep step, HeaderSet response, Operation op)
+            throws IOException;
+
+}
diff --git a/tests/src/com/android/bluetooth/tests/ITestSequenceBuilder.java b/tests/src/com/android/bluetooth/tests/ITestSequenceBuilder.java
new file mode 100644
index 0000000..3314fd6
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/ITestSequenceBuilder.java
@@ -0,0 +1,11 @@
+package com.android.bluetooth.tests;
+
+public interface ITestSequenceBuilder {
+
+    /**
+     * Add steps to a sequencer
+     * @param sequencer The sequencer the steps will be added to.
+     */
+    public void build(TestSequencer sequencer);
+
+}
diff --git a/tests/src/com/android/bluetooth/tests/ITestSequenceConfigurator.java b/tests/src/com/android/bluetooth/tests/ITestSequenceConfigurator.java
new file mode 100644
index 0000000..d260d8b
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/ITestSequenceConfigurator.java
@@ -0,0 +1,17 @@
+package com.android.bluetooth.tests;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+
+import javax.obex.ServerRequestHandler;
+
+public interface ITestSequenceConfigurator {
+
+    /** Use this to customize a serverRequestHandler
+     * @param sequence A reference to the sequence to handle
+     * @param stopLatch a reference to a latch that must be count down, when test completes.
+     * @return Reference to the ServerRequestHandler.
+     */
+    public ServerRequestHandler getObexServer(ArrayList<SeqStep> sequence,
+            CountDownLatch stopLatch);
+}
diff --git a/tests/src/com/android/bluetooth/tests/MapObexLevelTest.java b/tests/src/com/android/bluetooth/tests/MapObexLevelTest.java
new file mode 100644
index 0000000..fb5de24
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/MapObexLevelTest.java
@@ -0,0 +1,286 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+import javax.obex.ServerRequestHandler;
+
+import junit.framework.Assert;
+import android.annotation.TargetApi;
+import android.bluetooth.BluetoothServerSocket;
+import android.bluetooth.BluetoothSocket;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.os.Build;
+import android.os.RemoteException;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.bluetooth.BluetoothObexTransport;
+import com.android.bluetooth.tests.TestSequencer.OPTYPE;
+
+@TargetApi(Build.VERSION_CODES.KITKAT)
+public class MapObexLevelTest extends AndroidTestCase implements ITestSequenceConfigurator {
+    protected static String TAG = "MapObexLevelTest";
+    protected static final boolean D = true;
+    protected static final boolean TRACE = false;
+    protected static final boolean DELAY_PASS_30_SEC = true;
+
+    // 128 bit UUID for MAP MAS
+    static final byte[] MAS_TARGET = new byte[] {
+             (byte)0xBB, (byte)0x58, (byte)0x2B, (byte)0x40,
+             (byte)0x42, (byte)0x0C, (byte)0x11, (byte)0xDB,
+             (byte)0xB0, (byte)0xDE, (byte)0x08, (byte)0x00,
+             (byte)0x20, (byte)0x0C, (byte)0x9A, (byte)0x66
+             };
+
+    // 128 bit UUID for MAP MNS
+    static final byte[] MNS_TARGET = new byte[] {
+             (byte)0xBB, (byte)0x58, (byte)0x2B, (byte)0x41,
+             (byte)0x42, (byte)0x0C, (byte)0x11, (byte)0xDB,
+             (byte)0xB0, (byte)0xDE, (byte)0x08, (byte)0x00,
+             (byte)0x20, (byte)0x0C, (byte)0x9A, (byte)0x66
+             };
+
+    /* Message types */
+    static final String TYPE_GET_FOLDER_LISTING              = "x-obex/folder-listing";
+    static final String TYPE_GET_MESSAGE_LISTING             = "x-bt/MAP-msg-listing";
+    static final String TYPE_GET_CONVO_LISTING               = "x-bt/MAP-convo-listing";
+    static final String TYPE_MESSAGE                         = "x-bt/message";
+    static final String TYPE_SET_MESSAGE_STATUS              = "x-bt/messageStatus";
+    static final String TYPE_SET_NOTIFICATION_REGISTRATION   = "x-bt/MAP-NotificationRegistration";
+    static final String TYPE_MESSAGE_UPDATE                  = "x-bt/MAP-messageUpdate";
+    static final String TYPE_GET_MAS_INSTANCE_INFORMATION    = "x-bt/MASInstanceInformation";
+
+    public void testFolder() {
+        testLocalSockets(new buildFolderTestSeq());
+    }
+
+    public void testFolderServer() {
+        testServer(new buildFolderTestSeq());
+    }
+
+    public void testFolderClient() {
+        testClient(new buildFolderTestSeq());
+    }
+
+    protected class buildFolderTestSeq implements ITestSequenceBuilder {
+        @Override
+        public void build(TestSequencer sequencer) {
+            addConnectStep(sequencer);
+
+            MapStepsFolder.addGoToMsgFolderSteps(sequencer);
+
+            // MAP DISCONNECT Step
+            addDisconnectStep(sequencer);
+        }
+    }
+
+
+    public void testConvo() {
+        testLocalSockets(new buildConvoTestSeq());
+    }
+
+    public void testConvoServer() {
+        testServer(new buildConvoTestSeq());
+    }
+
+    public void testConvoClient() {
+        testClient(new buildConvoTestSeq());
+    }
+
+    class buildConvoTestSeq implements ITestSequenceBuilder {
+        @Override
+        public void build(TestSequencer sequencer) {
+            addConnectStep(sequencer);
+
+            MapStepsFolder.addGoToMsgFolderSteps(sequencer);
+
+            MapStepsConvo.addConvoListingSteps(sequencer);
+
+            // MAP DISCONNECT Step
+            addDisconnectStep(sequencer);
+        }
+    }
+
+    /**
+     * Run the test sequence using a local socket on a single device.
+     * Throughput around 4000 kbyte/s - with a larger OBEX package size.
+     *
+     * Downside: Unable to get a BT-snoop file...
+     */
+    protected void testLocalSockets(ITestSequenceBuilder builder) {
+        mContext = this.getContext();
+        MapTestData.init(mContext);
+        Log.i(TAG,"Setting up sockets...");
+
+        try {
+            /* Create and interconnect local pipes for transport */
+            LocalServerSocket serverSock = new LocalServerSocket("com.android.bluetooth.tests.sock");
+            LocalSocket clientSock = new LocalSocket();
+            LocalSocket acceptSock;
+
+            clientSock.connect(serverSock.getLocalSocketAddress());
+
+            acceptSock = serverSock.accept();
+
+            /* Create the OBEX transport objects to wrap the pipes - enable SRM */
+            ObexPipeTransport clientTransport = new ObexPipeTransport(clientSock.getInputStream(),
+                    clientSock.getOutputStream(), true);
+            ObexPipeTransport serverTransport = new ObexPipeTransport(acceptSock.getInputStream(),
+                    acceptSock.getOutputStream(), true);
+
+            TestSequencer sequencer  = new TestSequencer(clientTransport, serverTransport, this);
+
+            builder.build(sequencer);
+
+            //Debug.startMethodTracing("ObexTrace");
+            assertTrue(sequencer.run(mContext));
+            //Debug.stopMethodTracing();
+
+            clientSock.close();
+            acceptSock.close();
+            serverSock.close();
+        } catch (IOException e) {
+            Log.e(TAG, "IOException", e);
+        }
+    }
+
+    /**
+     * Server side of a dual device test using a Bluetooth Socket.
+     * Enables the possibility to get a BT-snoop file.
+     * If you need the btsnoop from the device which completes the test with success
+     * you need to add a delay after the test ends, and fetch the file before this delay
+     * expires. When the test completes, the Bluetooth subsystem will be restarted, causing
+     * a new bt-snoop to overwrite the one used in test.
+     */
+    public void testServer(ITestSequenceBuilder builder) {
+        mContext = this.getContext();
+        MapTestData.init(mContext);
+        Log.i(TAG,"Setting up sockets...");
+
+        try {
+            /* This will turn on BT and create a server socket on which accept can be called. */
+            BluetoothServerSocket serverSocket=ObexTest.createServerSocket(BluetoothSocket.TYPE_L2CAP, true);
+
+            Log.i(TAG, "Waiting for client to connect...");
+            BluetoothSocket socket = serverSocket.accept();
+            Log.i(TAG, "Client connected...");
+
+            BluetoothObexTransport serverTransport = new BluetoothObexTransport(socket);
+
+            TestSequencer sequencer  = new TestSequencer(null, serverTransport, this);
+
+            builder.build(sequencer);
+
+            //Debug.startMethodTracing("ObexTrace");
+            assertTrue(sequencer.run(mContext));
+            //Debug.stopMethodTracing();
+
+            serverSocket.close();
+            socket.close();
+        } catch (IOException e) {
+            Log.e(TAG, "IOException", e);
+        }
+        if(DELAY_PASS_30_SEC) {
+            Log.i(TAG, "\n\n\nTest done - please fetch logs within 30 seconds...\n\n\n");
+            try {
+                Thread.sleep(30000);
+            } catch (InterruptedException e) {}
+        }
+    }
+
+    /**
+     * Server side of a dual device test using a Bluetooth Socket.
+     * Enables the possibility to get a BT-snoop file.
+     * If you need the btsnoop from the device which completes the test with success
+     * you need to add a delay after the test ends, and fetch the file before this delay
+     * expires. When the test completes, the Bluetooth subsystem will be restarted, causing
+     * a new bt-snoop to overwrite the one used in test.
+     */
+    public void testClient(ITestSequenceBuilder builder) {
+        mContext = this.getContext();
+        MapTestData.init(mContext);
+        Log.i(TAG, "Setting up sockets...");
+
+        try {
+            /* This will turn on BT and connect */
+            BluetoothSocket clientSock =
+                    ObexTest.connectClientSocket(BluetoothSocket.TYPE_L2CAP, true, mContext);
+
+            BluetoothObexTransport clientTransport = new BluetoothObexTransport(clientSock);
+
+            TestSequencer sequencer  = new TestSequencer(clientTransport, null, this);
+
+            builder.build(sequencer);
+
+            //Debug.startMethodTracing("ObexTrace");
+            assertTrue(sequencer.run(mContext));
+            //Debug.stopMethodTracing();
+
+            clientSock.close();
+        } catch (IOException e) {
+            Log.e(TAG, "IOException", e);
+        }
+        if(DELAY_PASS_30_SEC) {
+            Log.i(TAG, "\n\n\nTest done - please fetch logs within 30 seconds...\n\n\n");
+            try {
+                Thread.sleep(30000);
+            } catch (InterruptedException e) {}
+        }
+    }
+
+    protected void addConnectStep(TestSequencer sequencer) {
+        SeqStep step;
+
+        // MAP CONNECT Step
+        step = sequencer.addStep(OPTYPE.CONNECT, null);
+        HeaderSet hs = new HeaderSet();
+        hs.setHeader(HeaderSet.TARGET, MAS_TARGET);
+        step.mReqHeaders = hs;
+        step.mValidator = new MapConnectValidator();
+        //step.mServerPreAction = new MapAddSmsMessages(); // could take in parameters
+    }
+
+    protected void addDisconnectStep(TestSequencer sequencer) {
+        sequencer.addStep(OPTYPE.DISCONNECT, ObexTest.getResponsecodevalidator());
+    }
+
+    /* Functions to validate results */
+
+    private class MapConnectValidator implements ISeqStepValidator {
+        @Override
+        public boolean validate(SeqStep step, HeaderSet response, Operation notUsed)
+                throws IOException {
+            Assert.assertNotNull(response);
+            byte[] who = (byte[])response.getHeader(HeaderSet.WHO);
+            Assert.assertNotNull(who);
+            Assert.assertTrue(Arrays.equals(who, MAS_TARGET));
+            Assert.assertNotNull(response.getHeader(HeaderSet.CONNECTION_ID));
+            return true;
+        }
+    }
+
+    /**
+     * This is the function creating the Obex Server to be used in this class.
+     * Here we use a mocked version of the MapObexServer class
+     */
+    @Override
+    public ServerRequestHandler getObexServer(ArrayList<SeqStep> sequence,
+            CountDownLatch stopLatch) {
+        try {
+            return new MapObexTestServer(mContext, sequence, stopLatch);
+        } catch (RemoteException e) {
+            Log.e(TAG, "exception", e);
+            fail("Unable to create MapObexTestServer");
+        }
+        return null;
+    }
+
+
+}
+
diff --git a/tests/src/com/android/bluetooth/tests/MapObexTestServer.java b/tests/src/com/android/bluetooth/tests/MapObexTestServer.java
new file mode 100644
index 0000000..4fb6ba6
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/MapObexTestServer.java
@@ -0,0 +1,188 @@
+package com.android.bluetooth.tests;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+import javax.obex.ResponseCodes;
+
+import junit.framework.Assert;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.bluetooth.map.BluetoothMapAccountItem;
+import com.android.bluetooth.map.BluetoothMapContentObserver;
+import com.android.bluetooth.map.BluetoothMapMasInstance;
+import com.android.bluetooth.map.BluetoothMapObexServer;
+import com.android.bluetooth.map.BluetoothMapUtils;
+import com.android.bluetooth.map.BluetoothMnsObexClient;
+
+public class MapObexTestServer extends BluetoothMapObexServer {
+
+    private static final String TAG = "MapObexTestServer";
+    private static final boolean V = true;
+
+    ArrayList<SeqStep> mSequence;
+    CountDownLatch mStopLatch;
+
+    ObexTestDataHandler mDataHandler;
+    int mOperationIndex = 0;
+
+    /* This needs to be static, as calling the super-constructor must be the first step.
+     * Alternatively add the account as constructor parameter, and create a builder
+     * function - factory pattern. */
+//    private static BluetoothMapAccountItem mAccountMock = new BluetoothMapAccountItem("1",
+//           "TestAccount",
+//           "do.not.exist.package.name.and.never.used.anyway:-)",
+//           "info.guardianproject.otr.app.im.provider.bluetoothprovider",
+//           null,
+//           BluetoothMapUtils.TYPE.IM,
+//           null,
+//           null);
+    private static BluetoothMapAccountItem mAccountMock = null;
+
+    /* MAP Specific instance variables
+    private final BluetoothMapContentObserver mObserver = null;
+    private final BluetoothMnsObexClient mMnsClient = null;*/
+
+    /* Test values, consider gathering somewhere else */
+    private static final int MAS_ID = 0;
+    private static final int REMOTE_FEATURE_MASK = 0x07FFFFFF;
+    private static final BluetoothMapMasInstance mMasInstance =
+            new MockMasInstance(MAS_ID, REMOTE_FEATURE_MASK);
+
+    public MapObexTestServer(final Context context, ArrayList<SeqStep> sequence,
+            CountDownLatch stopLatch) throws RemoteException {
+
+        super(null, context,
+                new  BluetoothMapContentObserver(context,
+                        new BluetoothMnsObexClient(
+                                BluetoothAdapter.getDefaultAdapter().
+                                getRemoteDevice("12:23:34:45:56:67"), null, null),
+                                /* TODO: this will not work for single device test... */
+                        mMasInstance,
+                        mAccountMock, /* Account */
+                        true) /* Enable SMS/MMS*/,
+                mMasInstance,
+                mAccountMock /* Account */,
+                true /* SMS/MMS enabled*/);
+        mSequence = sequence;
+        mDataHandler = new ObexTestDataHandler("(Server)");
+        mStopLatch = stopLatch;
+    }
+
+    /* OBEX operation handlers */
+    @Override
+    public int onConnect(HeaderSet request, HeaderSet reply) {
+        Log.i(TAG,"onConnect()");
+        int index;
+        int result = ResponseCodes.OBEX_HTTP_OK;
+        try {
+            index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
+            mOperationIndex = index;
+            SeqStep step = mSequence.get(mOperationIndex);
+            Assert.assertNotNull("invalid step index!", step);
+            if(step.mServerPreAction != null) {
+                step.mServerPreAction.execute(step, request, null);
+            }
+            result = super.onConnect(request, reply);
+        } catch (Exception e) {
+            Log.e(TAG, "Exception in onConnect - aborting...", e);
+            result = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+            // A read from null will produce exception to end the test.
+        }
+        return result;
+    }
+
+    @Override
+    public void onDisconnect(HeaderSet request, HeaderSet reply) {
+        Log.i(TAG,"onDisconnect()");
+        /* TODO: validate request headers, and set response headers */
+        int index;
+        int result = ResponseCodes.OBEX_HTTP_OK;
+        try {
+            index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
+            mOperationIndex = index;
+            SeqStep step = mSequence.get(mOperationIndex);
+            Assert.assertNotNull("invalid step index!", step);
+            if(step.mServerPreAction != null) {
+                step.mServerPreAction.execute(step, request, null);
+            }
+            super.onDisconnect(request, reply);
+        } catch (Exception e) {
+            Log.e(TAG, "Exception in onDisconnect - aborting...", e);
+            result = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+            // A read from null will produce exception to end the test.
+        }
+        if(mOperationIndex >= (mSequence.size()-1)) {
+            /* End of test, signal test runner thread */
+            Log.i(TAG, "Sending latch close signal...");
+            mStopLatch.countDown();
+        } else {
+            Log.i(TAG, "Got disconnect with mOperationCounter = " + mOperationIndex);
+        }
+        reply.responseCode = result;
+    }
+
+    @Override
+    public int onPut(Operation operation) {
+        Log.i(TAG,"onPut()");
+        int result = ResponseCodes.OBEX_HTTP_OK;
+        try{
+            HeaderSet reqHeaders = operation.getReceivedHeader();
+            int index = ((Long)reqHeaders.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
+            mOperationIndex = index;
+            SeqStep step = mSequence.get(mOperationIndex);
+            Assert.assertNotNull("invalid step index!", step);
+            if(step.mServerPreAction != null) {
+                step.mServerPreAction.execute(step, reqHeaders, operation);
+            }
+            super.onPut(operation);
+        } catch (Exception e) {
+            Log.e(TAG, "Exception in onPut - aborting...", e);
+            result = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+            // A read from null will produce exception to end the test.
+        }
+        if(result == ResponseCodes.OBEX_HTTP_OK) {
+            Log.i(TAG, "OBEX-HANDLER: operation complete success");
+        } else {
+            Log.e(TAG, "OBEX-HANDLER: operation complete FAILED!");
+        }
+        return result;
+    }
+
+    @Override
+    public int onGet(Operation operation) {
+        Log.i(TAG,"onGet()");
+        int result = ResponseCodes.OBEX_HTTP_OK;
+        try{
+            HeaderSet reqHeaders = operation.getReceivedHeader();
+            int index = ((Long)reqHeaders.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
+            mOperationIndex = index;
+            SeqStep step = mSequence.get(mOperationIndex);
+            Assert.assertNotNull("invalid step index!", step);
+            if(step.mServerPreAction != null) {
+                step.mServerPreAction.execute(step, reqHeaders, operation);
+            }
+            super.onGet(operation);
+        } catch (Exception e) {
+            Log.e(TAG, "Exception in onGet - aborting...", e);
+            result = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+            // A read from null will produce exception to end the test.
+        }
+        if(result == ResponseCodes.OBEX_HTTP_OK) {
+            Log.i(TAG, "OBEX-HANDLER: operation complete success");
+        } else {
+            Log.e(TAG, "OBEX-HANDLER: operation complete FAILED!");
+        }
+        return result;
+    }
+
+
+
+}
+
diff --git a/tests/src/com/android/bluetooth/tests/MapStepsConvo.java b/tests/src/com/android/bluetooth/tests/MapStepsConvo.java
new file mode 100644
index 0000000..f4288bb
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/MapStepsConvo.java
@@ -0,0 +1,240 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+import java.util.Arrays;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+import javax.obex.ResponseCodes;
+
+import junit.framework.Assert;
+import android.util.Log;
+
+import com.android.bluetooth.map.BluetoothMapAppParams;
+import com.android.bluetooth.map.BluetoothMapConvoListing;
+import com.android.bluetooth.map.BluetoothMapConvoListingElement;
+import com.android.bluetooth.map.BluetoothMapFolderElement;
+import com.android.bluetooth.tests.TestSequencer.OPTYPE;
+
+public class MapStepsConvo {
+    private static final String TAG = "MapStepsConvo";
+
+
+    protected static void addConvoListingSteps(TestSequencer sequencer) {
+        SeqStep step;
+        final int count = 5;
+
+        // TODO: As we use the default message database, these tests will fail if the
+        //       database has any content.
+        //       To cope with this for now, the validation is disabled.
+
+        /* Request the number of messages */
+        step = addConvoListingStep(sequencer,
+                0 /*maxListCount*/,
+                -1 /*listStartOffset*/,
+                null /*activityBegin*/,
+                null /*activityEnd*/,
+                -1 /*readStatus*/,
+                null /*recipient*/,
+                new MapConvoListValidator()
+                /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+        /* Add messages and contacts for the entire sequence of tests */
+        step.mServerPreAction = new MapTestData.MapAddSmsMessages(count);
+
+        /* Request the XML for all conversations */
+        step = addConvoListingStep(sequencer,
+                -1 /*maxListCount*/,
+                -1 /*listStartOffset*/,
+                null /*activityBegin*/,
+                null /*activityEnd*/,
+                -1 /*readStatus*/,
+                null /*recipient*/,
+                /*nearly impossible to validate due to auto assigned ID values*/
+                new MapConvoListValidator()
+                /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+
+        step = addConvoListingStep(sequencer,
+                2 /*maxListCount*/,
+                -1 /*listStartOffset*/,
+                null /*activityBegin*/,
+                null /*activityEnd*/,
+                -1 /*readStatus*/,
+                null /*recipient*/,
+                /*nearly impossible to validate due to auto assigned ID values*/
+                new MapConvoListValidator()
+                /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+
+        step = addConvoListingStep(sequencer,
+                2 /*maxListCount*/,
+                1 /*listStartOffset*/,
+                null /*activityBegin*/,
+                null /*activityEnd*/,
+                -1 /*readStatus*/,
+                null /*recipient*/,
+                /*nearly impossible to validate due to auto assigned ID values*/
+                new MapConvoListValidator()
+                /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+
+        step = addConvoListingStep(sequencer,
+                3 /*maxListCount*/,
+                2 /*listStartOffset*/,
+                null /*activityBegin*/,
+                null /*activityEnd*/,
+                -1 /*readStatus*/,
+                null /*recipient*/,
+                /*nearly impossible to validate due to auto assigned ID values*/
+                new MapConvoListValidator()
+                /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+
+        step = addConvoListingStep(sequencer,
+                5 /*maxListCount*/,
+                1 /*listStartOffset*/,
+                MapTestData.TEST_ACTIVITY_BEGIN_STRING /*activityBegin*/,
+                null /*activityEnd*/,
+                -1 /*readStatus*/,
+                null /*recipient*/,
+                /*nearly impossible to validate due to auto assigned ID values*/
+                new MapConvoListValidator()
+                /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+
+        step = addConvoListingStep(sequencer,
+                5 /*maxListCount*/,
+                0 /*listStartOffset*/,
+                MapTestData.TEST_ACTIVITY_BEGIN_STRING /*activityBegin*/,
+                MapTestData.TEST_ACTIVITY_END_STRING /*activityEnd*/,
+                -1 /*readStatus*/,
+                null /*recipient*/,
+                /*nearly impossible to validate due to auto assigned ID values*/
+                new MapConvoListValidator()
+                /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+
+        step = addConvoListingStep(sequencer,
+                5 /*maxListCount*/,
+                1 /*listStartOffset*/,
+                MapTestData.TEST_ACTIVITY_BEGIN_STRING /*activityBegin*/,
+                null /*activityEnd*/,
+                2/* read only */ /*readStatus*/,
+                null /*recipient*/,
+                /*nearly impossible to validate due to auto assigned ID values*/
+                new MapConvoListValidator()
+                /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+
+        /* TODO: Test the different combinations of filtering */
+    }
+
+    /**
+     * Use -1 or null to omit value in request
+     * @param sequencer
+     * @param maxListCount
+     * @param listStartOffset
+     * @param activityBegin
+     * @param activityEnd
+     * @param readStatus -1 omit value, 0 = no filtering, 1 = get unread only, 2 = get read only,
+     *                   3 = 1+2 - hence get none...
+     * @param recipient substring of the recipient name
+     * @param validator
+     * @return a reference to the step added, for further decoration
+     */
+    private static SeqStep addConvoListingStep(TestSequencer sequencer, int maxListCount,
+            int listStartOffset, String activityBegin, String activityEnd,
+            int readStatus, String recipient, ISeqStepValidator validator) {
+        SeqStep step;
+        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+        try {
+            if(activityBegin != null) {
+                appParams.setFilterLastActivityBegin(activityBegin);
+            }
+            if(activityEnd != null) {
+                appParams.setFilterLastActivityEnd(activityEnd);
+            }
+            if(readStatus != -1) {
+                appParams.setFilterReadStatus(readStatus);
+            }
+            if(recipient != null) {
+                appParams.setFilterRecipient(recipient);
+            }
+            if(maxListCount != -1) {
+                appParams.setMaxListCount(maxListCount);
+            }
+            if(listStartOffset != -1) {
+                appParams.setStartOffset(listStartOffset);
+            }
+        } catch (ParseException e) {
+            Log.e(TAG, "unable to build appParams", e);
+        }
+        step = sequencer.addStep(OPTYPE.GET, null);
+        HeaderSet hs = new HeaderSet();
+        hs.setHeader(HeaderSet.TYPE, MapObexLevelTest.TYPE_GET_CONVO_LISTING);
+        try {
+            hs.setHeader(HeaderSet.APPLICATION_PARAMETER, appParams.EncodeParams());
+        } catch (UnsupportedEncodingException e) {
+            Log.e(TAG, "ERROR", e);
+            Assert.fail();
+        }
+        step.mReqHeaders = hs;
+        step.mValidator = validator;
+        return step;
+    }
+
+    /* Functions to validate results */
+    private static class MapConvoListValidator implements ISeqStepValidator {
+
+        final BluetoothMapConvoListing mExpectedListing;
+        final int mExpectedSize;
+
+        public MapConvoListValidator(BluetoothMapConvoListing listing) {
+            this.mExpectedListing = listing;
+            this.mExpectedSize = -1;
+        }
+
+        public MapConvoListValidator(int convoListingSize) {
+            this.mExpectedListing = null;
+            this.mExpectedSize = convoListingSize;
+        }
+
+        public MapConvoListValidator() {
+            this.mExpectedListing = null;
+            this.mExpectedSize = -1;
+        }
+
+        @Override
+        public boolean validate(SeqStep step, HeaderSet response, Operation op)
+                throws IOException {
+            Assert.assertNotNull(op);
+            op.noBodyHeader();
+            try {
+                // For some odd reason, the request will not be send before we start to read the
+                // reply data, hence we need to do this first?
+                BluetoothMapConvoListing receivedListing = new BluetoothMapConvoListing();
+                receivedListing.appendFromXml(op.openInputStream());
+                response = op.getReceivedHeader();
+                byte[] appParamsRaw = (byte[])response.getHeader(HeaderSet.APPLICATION_PARAMETER);
+                Assert.assertNotNull(appParamsRaw);
+                BluetoothMapAppParams appParams;
+                appParams = new BluetoothMapAppParams(appParamsRaw);
+                Assert.assertNotNull(appParams);
+                Assert.assertNotNull(appParams.getDatabaseIdentifier());
+                Assert.assertNotSame(BluetoothMapAppParams.INVALID_VALUE_PARAMETER,
+                        appParams.getConvoListingSize());
+                if(mExpectedSize >= 0) {
+                    Assert.assertSame(mExpectedSize, appParams.getConvoListingSize());
+                }
+                if(mExpectedListing != null) {
+                    // Recursively compare
+                    Assert.assertTrue(mExpectedListing.equals(receivedListing));
+                    Assert.assertSame(mExpectedListing.getList().size(),
+                            appParams.getConvoListingSize());
+                }
+                int responseCode = op.getResponseCode();
+                Assert.assertEquals(ResponseCodes.OBEX_HTTP_OK, responseCode);
+                op.close();
+            } catch (Exception e) {
+                Log.e(TAG,"",e);
+                Assert.fail();
+            }
+            return true;
+        }
+    }
+}
diff --git a/tests/src/com/android/bluetooth/tests/MapStepsFolder.java b/tests/src/com/android/bluetooth/tests/MapStepsFolder.java
new file mode 100644
index 0000000..11d27dc
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/MapStepsFolder.java
@@ -0,0 +1,159 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+import javax.obex.ResponseCodes;
+
+import junit.framework.Assert;
+import android.util.Log;
+
+import com.android.bluetooth.map.BluetoothMapAppParams;
+import com.android.bluetooth.map.BluetoothMapFolderElement;
+import com.android.bluetooth.tests.TestSequencer.OPTYPE;
+
+public class MapStepsFolder {
+    private final static String TAG = "MapStepsFolder";
+    /**
+     * Request and expect the following folder structure:
+     * root
+     *   telecom
+     *     msg
+     *       inbox
+     *       outbox
+     *       draft
+     *       sent
+     *       deleted
+     *
+     * The order in which they occur in the listing will not matter.
+     * @param sequencer
+     */
+    protected static void addGoToMsgFolderSteps(TestSequencer sequencer) {
+        SeqStep step;
+        //BluetoothMapFolderElement rootDir = new BluetoothMapFolderElement("root", null);
+
+        // MAP Get Folder Listing Steps
+        // The telecom folder
+        step = sequencer.addStep(OPTYPE.GET, null);
+        HeaderSet hs = new HeaderSet();
+        hs.setHeader(HeaderSet.TYPE, MapObexLevelTest.TYPE_GET_FOLDER_LISTING);
+        step.mReqHeaders = hs;
+        step.mValidator = new MapBuildFolderStructurValidator(1, null);
+
+        step = sequencer.addStep(OPTYPE.SET_PATH, ObexTest.getResponsecodevalidator());
+        hs = new HeaderSet();
+        hs.setHeader(HeaderSet.NAME, "telecom");
+        step.mReqHeaders = hs;
+        step.mClientPostAction = new MapSetClientFolder("telecom");
+
+
+        // The msg folder
+        step = sequencer.addStep(OPTYPE.GET, null);
+        hs = new HeaderSet();
+        hs.setHeader(HeaderSet.TYPE, MapObexLevelTest.TYPE_GET_FOLDER_LISTING);
+        step.mReqHeaders = hs;
+        step.mValidator = new MapBuildFolderStructurValidator(1, null);
+
+        step = sequencer.addStep(OPTYPE.SET_PATH, ObexTest.getResponsecodevalidator());
+        hs = new HeaderSet();
+        hs.setHeader(HeaderSet.NAME, "msg");
+        step.mReqHeaders = hs;
+        step.mClientPostAction = new MapSetClientFolder("msg");
+
+        // The msg folder
+        step = sequencer.addStep(OPTYPE.GET, null);
+        hs = new HeaderSet();
+        hs.setHeader(HeaderSet.TYPE, MapObexLevelTest.TYPE_GET_FOLDER_LISTING);
+        step.mReqHeaders = hs;
+        step.mValidator = new MapBuildFolderStructurValidator(5, buildDefaultFolderStructure());
+    }
+
+    /**
+     * Sets the current folder on the client, to the folder name specified in the constructor.
+     * TODO: Could be extended to be able to navigate back and forth in the folder structure.
+     */
+    private static class MapSetClientFolder implements ISeqStepAction {
+        final String mFolderName;
+        public MapSetClientFolder(String folderName) {
+            super();
+            this.mFolderName = folderName;
+        }
+        @Override
+        public void execute(SeqStep step, HeaderSet request, Operation op)
+                throws IOException {
+            MapBuildFolderStructurValidator.sCurrentFolder =
+                    MapBuildFolderStructurValidator.sCurrentFolder.getSubFolder(mFolderName);
+            Assert.assertNotNull(MapBuildFolderStructurValidator.sCurrentFolder);
+            Log.i(TAG, "MapSetClientFolder(): Current path: " +
+                    MapBuildFolderStructurValidator.sCurrentFolder.getFullPath());
+        }
+    }
+
+    /* Functions to validate results */
+    private static class MapBuildFolderStructurValidator implements ISeqStepValidator {
+
+        final int mExpectedListingSize;
+        static BluetoothMapFolderElement sCurrentFolder = null;
+        final BluetoothMapFolderElement mExpectedFolderElement;
+
+        public MapBuildFolderStructurValidator(int mExpectedListingSize,
+                BluetoothMapFolderElement folderElement) {
+            super();
+            if(sCurrentFolder == null) {
+                sCurrentFolder = new BluetoothMapFolderElement("root", null);
+            }
+            this.mExpectedListingSize = mExpectedListingSize;
+            this.mExpectedFolderElement = folderElement;
+        }
+
+
+        @Override
+        public boolean validate(SeqStep step, HeaderSet response, Operation op)
+                throws IOException {
+            Assert.assertNotNull(op);
+            op.noBodyHeader();
+            try {
+                // For some odd reason, the request will not be send before we start to read the
+                // reply data, hence we need to do this first?
+                sCurrentFolder.appendSubfolders(op.openInputStream());
+                response = op.getReceivedHeader();
+                byte[] appParamsRaw = (byte[])response.getHeader(HeaderSet.APPLICATION_PARAMETER);
+                Assert.assertNotNull(appParamsRaw);
+                BluetoothMapAppParams appParams;
+                appParams = new BluetoothMapAppParams(appParamsRaw);
+                Assert.assertNotNull(appParams);
+                if(mExpectedFolderElement != null) {
+                    // Recursively compare
+                    Assert.assertTrue(mExpectedFolderElement.compareTo(sCurrentFolder.getRoot())
+                            == 0);
+                }
+                int responseCode = op.getResponseCode();
+                Assert.assertEquals(ResponseCodes.OBEX_HTTP_OK, responseCode);
+                op.close();
+            } catch (Exception e) {
+                Log.e(TAG,"",e);
+                Assert.fail();
+            }
+            return true;
+        }
+
+    }
+
+
+    private static BluetoothMapFolderElement buildDefaultFolderStructure(){
+        BluetoothMapFolderElement root =
+                new BluetoothMapFolderElement("root", null); // This will be the root element
+        BluetoothMapFolderElement tmpFolder;
+        tmpFolder = root.addFolder("telecom"); // root/telecom
+        tmpFolder = tmpFolder.addFolder("msg");         // root/telecom/msg
+        tmpFolder.addFolder("inbox");                   // root/telecom/msg/inbox
+        tmpFolder.addFolder("outbox");                  // root/telecom/msg/outbox
+        tmpFolder.addFolder("sent");                    // root/telecom/msg/sent
+        tmpFolder.addFolder("deleted");                 // root/telecom/msg/deleted
+        tmpFolder.addFolder("draft");                   // root/telecom/msg/draft
+        return root;
+    }
+
+
+}
diff --git a/tests/src/com/android/bluetooth/tests/MapTestData.java b/tests/src/com/android/bluetooth/tests/MapTestData.java
new file mode 100644
index 0000000..7cb723b
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/MapTestData.java
@@ -0,0 +1,286 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+import java.util.Date;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.Telephony.Sms;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.bluetooth.map.BluetoothMapConvoContactElement;
+import com.android.bluetooth.map.BluetoothMapConvoListing;
+import com.android.bluetooth.map.BluetoothMapConvoListingElement;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+
+/**
+ * Class to hold test data - both the server side data to insert into the databases, and the
+ * validation data to validate the result, when reading back the data.
+ *
+ * Should be data only, not operation specific functionality (client).
+ *
+ * Please try to keep useful functionality call-able from a test case, to make it possible
+ * to call a single test case to e.g. inject some contacts or messages into the database.
+ *
+ */
+@TargetApi(20)
+public class MapTestData extends AndroidTestCase {
+    private static final String TAG = "MapTestData";
+
+    /* Test validation variables */
+    static final String TEST_CONTACT_NAME = "Jesus Überboss";
+    static final String TEST_CONTACT_PHONE = "55566688";
+    static final String TEST_CONTACT_EMAIL = "boss@the.skyes";
+    static final int TEST_NUM_CONTACTS = 3;
+
+    static final int TEST_ADD_CONTACT_PER_ITERATIONS = 4;
+    /* I do know this function is deprecated, but I'm unable to find a good alternative
+     * except from taking a copy of the Date.UTC function as suggested. */
+    // NOTE: This will only set the data on the message - not the lastActivity on SMS/MMS threads
+    static final long TEST_ACTIVITY_BEGIN = Date.UTC(
+            2014-1900,
+            8-1, /* month 0-11*/
+            22, /*day 1-31 */
+            22, /*hour*/
+            15, /*minute*/
+            20 /*second*/);
+
+    static final String TEST_ACTIVITY_BEGIN_STRING = "20150102T150047";
+    static final String TEST_ACTIVITY_END_STRING = "20160102T150047";
+
+    static final int TEST_ACTIVITY_INTERVAL = 5*60*1000; /*ms*/
+
+    static Context sContext = null;
+    public static void init(Context context){
+        sContext = context;
+    }
+    /**
+     * Adds messages to the SMS message database.
+     */
+    public static class MapAddSmsMessages implements ISeqStepAction {
+        int mCount;
+        /**
+         *
+         * @param count the number of iterations to execute
+         */
+        public MapAddSmsMessages(int count) {
+            mCount = count;
+        }
+
+        @Override
+        public void execute(SeqStep step, HeaderSet request, Operation op)
+                throws IOException {
+            int count = mCount; // Number of messages in each conversation
+            ContentResolver resolver = sContext.getContentResolver();
+
+            // Insert some messages
+            insertTestMessages(resolver, step.index, count);
+
+            // Cleanup if needed to avoid duplicates
+            deleteTestContacts(resolver);
+
+            // And now add the contacts
+            setupTestContacts(resolver);
+        }
+    }
+
+    /**
+     * TODO: Only works for filter on TEST_CONTACT_NAME
+     * @param maxCount
+     * @param offset
+     * @param filterContact
+     * @param read
+     * @param reportRead
+     * @param msgCount
+     * @return
+     */
+    public static BluetoothMapConvoListing getConvoListingReference(int maxCount, int offset,
+            boolean filterContact, boolean read, boolean reportRead, int msgCount){
+        BluetoothMapConvoListing list = new BluetoothMapConvoListing();
+        BluetoothMapConvoListingElement element;
+        BluetoothMapConvoContactElement contact;
+        element = new BluetoothMapConvoListingElement();
+        element.setRead(read, reportRead);
+        element.setVersionCounter(0);
+        contact = new BluetoothMapConvoContactElement();
+        contact.setName(TEST_CONTACT_NAME);
+        contact.setLastActivity(TEST_ACTIVITY_BEGIN +
+                msgCount*TEST_ADD_CONTACT_PER_ITERATIONS*TEST_ACTIVITY_INTERVAL);
+        element.addContact(contact);
+        list.add(element);
+        return null;
+    }
+
+    public static void insertTestMessages(ContentResolver resolver, int tag, int count) {
+        ContentValues values[] = new ContentValues[count*4]; // 4 messages/iteration
+        long date = TEST_ACTIVITY_BEGIN;
+        Log.i(TAG, "Preparing messages... with data = " + date);
+
+        for (int x = 0;x < count;x++){
+            /* NOTE: Update TEST_ADD_CONTACT_PER_ITERATIONS if more messages are added */
+            ContentValues item = new ContentValues(5);
+            item.put("address", "98765432");
+            item.put("body", "test message " + x + " step index: " + tag);
+            item.put("date", date+=TEST_ACTIVITY_INTERVAL);
+            item.put("read", "0");
+            if(x%2 == 0) {
+                item.put("type", Sms.MESSAGE_TYPE_INBOX);
+            } else {
+                item.put("type", Sms.MESSAGE_TYPE_SENT);
+            }
+            values[x] = item;
+
+            item = new ContentValues(5);
+            item.put("address", "23456780");
+            item.put("body", "test message " + x + " step index: " + tag);
+            item.put("date", date += TEST_ACTIVITY_INTERVAL);
+            item.put("read", "0");
+            if(x%2 == 0) {
+                item.put("type", Sms.MESSAGE_TYPE_INBOX);
+            } else {
+                item.put("type", Sms.MESSAGE_TYPE_SENT);
+            }
+            values[count+x] = item;
+
+            item = new ContentValues(5);
+            item.put("address", "+4523456780");
+            item.put("body", "test message "+x+" step index: " + tag);
+            item.put("date", date += TEST_ACTIVITY_INTERVAL);
+            item.put("read", "0");
+            if(x%2 == 0) {
+                item.put("type", Sms.MESSAGE_TYPE_INBOX);
+            } else {
+                item.put("type", Sms.MESSAGE_TYPE_SENT);
+            }
+            values[2*count+x] = item;
+
+            /* This is the message used for test */
+            item = new ContentValues(5);
+            item.put("address", TEST_CONTACT_PHONE);
+            item.put("body", "test message "+x+" step index: " + tag);
+            item.put("date", date += TEST_ACTIVITY_INTERVAL);
+            item.put("read", "0");
+            if(x%2 == 0) {
+                item.put("type", Sms.MESSAGE_TYPE_INBOX);
+            } else {
+                item.put("type", Sms.MESSAGE_TYPE_SENT);
+            }
+            values[3*count+x] = item;
+        }
+
+        Log.i(TAG, "Starting bulk insert...");
+        resolver.bulkInsert(Uri.parse("content://sms"), values);
+        Log.i(TAG, "Bulk insert done.");
+    }
+
+    /**
+     * Insert a few contacts in the main contact database, using a test account.
+     */
+    public static void setupTestContacts(ContentResolver resolver){
+        /*TEST_NUM_CONTACTS must be updated if this function is changed */
+        insertContact(resolver, "Hans Hansen", "98765432", "hans@hansens.global");
+        insertContact(resolver, "Helle Børgesen", "23456780", "hb@gmail.com");
+        insertContact(resolver, TEST_CONTACT_NAME, TEST_CONTACT_PHONE, TEST_CONTACT_EMAIL);
+    }
+
+    /**
+     * Helper function to insert a contact
+     * @param name
+     * @param phone
+     * @param email
+     */
+    private static void insertContact(ContentResolver resolver, String name, String phone, String email) {
+        // Get the account info
+        //Cursor c = resolver.query(uri, projection, selection, selectionArgs, sortOrder)
+        ContentValues item = new ContentValues(3);
+        item.put(ContactsContract.RawContacts.ACCOUNT_TYPE, "test_account");
+        item.put(ContactsContract.RawContacts.ACCOUNT_NAME, "MAP account");
+        Uri uri = resolver.insert(ContactsContract.RawContacts.CONTENT_URI, item);
+        Log.i(TAG, "Inserted RawContact: " + uri);
+        long rawId = Long.parseLong(uri.getLastPathSegment());
+
+        //Now add contact information
+        item = new ContentValues(3);
+        item.put(ContactsContract.Data.RAW_CONTACT_ID, rawId);
+        item.put(ContactsContract.Data.MIMETYPE,
+                ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
+        item.put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
+                name);
+        resolver.insert(ContactsContract.Data.CONTENT_URI, item);
+
+        if(phone != null) {
+            item = new ContentValues(3);
+            item.put(ContactsContract.Data.RAW_CONTACT_ID, rawId);
+            item.put(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
+            item.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);
+            resolver.insert(ContactsContract.Data.CONTENT_URI, item);
+        }
+
+        if(email != null) {
+            item = new ContentValues(3);
+            item.put(ContactsContract.Data.RAW_CONTACT_ID, rawId);
+            item.put(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
+            item.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);
+            resolver.insert(ContactsContract.Data.CONTENT_URI, item);
+        }
+    }
+
+    /**
+     * Delete all contacts belonging to the test_account.
+     */
+    public static void deleteTestContacts(ContentResolver resolver){
+        resolver.delete(ContactsContract.RawContacts.CONTENT_URI,
+                ContactsContract.RawContacts.ACCOUNT_TYPE + "=\"test_account\"", null);
+    }
+
+    /****************************************************************************
+     * Small test cases to trigger the functionality without running a sequence.
+     ****************************************************************************/
+    /**
+     * Insert a few contacts in the main contact database, using a test account.
+     */
+    public void testInsertMessages() {
+        ContentResolver resolver = mContext.getContentResolver();
+        insertTestMessages(resolver, 1234, 10);
+    }
+
+    public void testInsert1000Messages() {
+        ContentResolver resolver = mContext.getContentResolver();
+        insertTestMessages(resolver, 1234, 1000);
+    }
+
+    /**
+     * Insert a few contacts in the main contact database, using a test account.
+     */
+    public void testSetupContacts() {
+        ContentResolver resolver = mContext.getContentResolver();
+        setupTestContacts(resolver);
+    }
+
+    /**
+     * Delete all contacts belonging to the test_account.
+     */
+    public void testDeleteTestContacts() {
+        ContentResolver resolver = mContext.getContentResolver();
+        deleteTestContacts(resolver);
+    }
+
+    public void testSetup1000Contacts() {
+        ContentResolver resolver = mContext.getContentResolver();
+        for(int i = 0; i < 1000; i++) {
+            insertContact(resolver, "Hans Hansen " + i,
+                    "98765431" + i, "hans" + i + "@hansens.global");
+        }
+    }
+
+}
diff --git a/tests/src/com/android/bluetooth/tests/MockMasInstance.java b/tests/src/com/android/bluetooth/tests/MockMasInstance.java
new file mode 100644
index 0000000..07de8e4
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/MockMasInstance.java
@@ -0,0 +1,30 @@
+package com.android.bluetooth.tests;
+
+import com.android.bluetooth.map.BluetoothMapMasInstance;
+import junit.framework.Assert;
+
+public class MockMasInstance extends BluetoothMapMasInstance {
+
+    private final int mMasId;
+    private final int mRemoteFeatureMask;
+
+    public MockMasInstance(int masId, int remoteFeatureMask) {
+        super();
+        this.mMasId = masId;
+        this.mRemoteFeatureMask = remoteFeatureMask;
+    }
+
+    public int getMasId() {
+        return mMasId;
+    }
+
+   @Override
+    public int getRemoteFeatureMask() {
+        return mRemoteFeatureMask;
+    }
+
+   @Override
+    public void restartObexServerSession() {
+        Assert.fail("restartObexServerSession() should not occur");
+    }
+}
diff --git a/tests/src/com/android/bluetooth/tests/ObexPipeTransport.java b/tests/src/com/android/bluetooth/tests/ObexPipeTransport.java
index 3bb9809..28625d5 100644
--- a/tests/src/com/android/bluetooth/tests/ObexPipeTransport.java
+++ b/tests/src/com/android/bluetooth/tests/ObexPipeTransport.java
@@ -26,12 +26,12 @@
 import javax.obex.ObexTransport;
 
 public class ObexPipeTransport implements ObexTransport {
-    PipedInputStream mInStream;
-    PipedOutputStream mOutStream;
+    InputStream mInStream;
+    OutputStream mOutStream;
     boolean mEnableSrm;
 
-    public ObexPipeTransport(PipedInputStream inStream, 
-            PipedOutputStream outStream, boolean enableSrm) {
+    public ObexPipeTransport(InputStream inStream, 
+            OutputStream outStream, boolean enableSrm) {
         mInStream = inStream;
         mOutStream = outStream;
         mEnableSrm = enableSrm;
@@ -74,12 +74,12 @@
         return true;
     }
 
-    public int getMaxTxPacketSize() {
-        return 15432;
+    public int getMaxTransmitPacketSize() {
+        return 3*15432;
     }
 
-    public int getMaxRxPacketSize() {
-        return 23450;
+    public int getMaxReceivePacketSize() {
+        return 2*23450;
     }
 
     @Override
@@ -88,3 +88,4 @@
     }
 
 }
+
diff --git a/tests/src/com/android/bluetooth/tests/ObexTest.java b/tests/src/com/android/bluetooth/tests/ObexTest.java
index ad45522..b90ea14 100644
--- a/tests/src/com/android/bluetooth/tests/ObexTest.java
+++ b/tests/src/com/android/bluetooth/tests/ObexTest.java
@@ -22,41 +22,35 @@
 import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
 
-import javax.obex.ClientSession;
 import javax.obex.HeaderSet;
-import javax.obex.ObexPacket;
 import javax.obex.ObexTransport;
 import javax.obex.Operation;
 import javax.obex.ResponseCodes;
-import javax.obex.ServerSession;
+import javax.obex.ServerRequestHandler;
 
+import junit.framework.Assert;
 import android.annotation.TargetApi;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothServerSocket;
 import android.bluetooth.BluetoothSocket;
 import android.bluetooth.BluetoothUuid;
+import android.bluetooth.SdpMasRecord;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.nfc.cardemulation.OffHostApduService;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
 import android.os.Build;
 import android.os.Debug;
-import android.os.Handler;
-import android.os.Handler.Callback;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
 import android.os.ParcelUuid;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
 import com.android.bluetooth.BluetoothObexTransport;
 import com.android.bluetooth.sdp.SdpManager;
-import com.android.bluetooth.sdp.SdpMasRecord;
-import com.android.bluetooth.tests.ObexTest.TestSequencer.OPTYPE;
-import com.android.bluetooth.tests.ObexTest.TestSequencer.SeqStep;
+import com.android.bluetooth.tests.TestSequencer.OPTYPE;
 
 /**
  * Test either using the reference ril without a modem, or using a RIL implementing the
@@ -64,42 +58,47 @@
  *
  */
 @TargetApi(Build.VERSION_CODES.KITKAT)
-public class ObexTest extends AndroidTestCase {
+public class ObexTest extends AndroidTestCase implements ITestSequenceConfigurator {
     protected static String TAG = "ObexTest";
     protected static final boolean D = true;
     protected static final boolean TRACE = false;
     protected static final boolean DELAY_PASS_30_SEC = false;
     public static final long PROGRESS_INTERVAL_MS = 1000;
     private static final ObexTestParams defaultParams =
-            new ObexTestParams(2*8092, 0, 2*1024*1024/10);
+            new ObexTestParams(2*8092, 0, 2*1024*1024);
 
     private static final ObexTestParams throttle100Params =
-            new ObexTestParams(2*8092, 100000, 2*1024*1024/10);
+            new ObexTestParams(2*8092, 100000, 1024*1024);
 
     private static final ObexTestParams smallParams =
             new ObexTestParams(2*8092, 0, 2*1024);
 
     private static final ObexTestParams hugeParams =
-            new ObexTestParams(2*8092, 0, 100*1024*1024/1000);
+            new ObexTestParams(2*8092, 0, 100*1024*1024);
 
-    private static final int SMALL_OPERATION_COUNT = 1000/100;
+    private static final int SMALL_OPERATION_COUNT = 1000;
     private static final int CONNECT_OPERATION_COUNT = 4500;
 
     private static final int L2CAP_PSM = 29; /* If SDP is not used */
     private static final int RFCOMM_CHANNEL = 29; /* If SDP is not used */
 
-    public static final String SERVER_ADDRESS = "10:68:3F:5E:F9:2E";
+    //public static final String SERVER_ADDRESS = "10:68:3F:5E:F9:2E";
+    public static final String SERVER_ADDRESS = "F8:CF:C5:A8:70:7E";
 
     private static final String SDP_SERVER_NAME = "Samsung Server";
     private static final String SDP_CLIENT_NAME = "Samsung Client";
 
-    private static final long SDP_FEATURES   = 0x87654321L; /* 32 bit */
+    private static final long SDP_FEATURES  = 0x87654321L; /* 32 bit */
     private static final int SDP_MSG_TYPES  = 0xf1;       /*  8 bit */
     private static final int SDP_MAS_ID     = 0xCA;       /*  8 bit */
     private static final int SDP_VERSION    = 0xF0C0;     /* 16 bit */
     public static final ParcelUuid SDP_UUID_OBEX_MAS = BluetoothUuid.MAS;
 
     private static int sSdpHandle = -1;
+    private static final ObexTestDataHandler sDataHandler = new ObexTestDataHandler("(Client)");
+    private static final ISeqStepValidator sResponseCodeValidator = new ResponseCodeValidator();
+    private static final ISeqStepValidator sDataValidator = new DataValidator();
+
 
     private enum SequencerType {
         SEQ_TYPE_PAYLOAD,
@@ -109,10 +108,6 @@
     private Context mContext = null;
     private int mChannelType = 0;
 
-    public static final int STEP_INDEX_HEADER = 0xF1; /*0xFE*/
-    private static final int ENABLE_TIMEOUT = 5000;
-    private static final int POLL_TIME = 500;
-
     public ObexTest() {
         super();
     }
@@ -121,6 +116,8 @@
      * Test that a connection can be established.
      * WARNING: The performance of the pipe implementation is not good. I'm only able to get a
      * throughput of around 220 kbyte/sec - less that when using Bluetooth :-)
+     * UPDATE: Did a local socket implementation below to replace this...
+     *         This has a throughput of more than 4000 kbyte/s
      */
     public void testLocalPipes() {
         mContext = this.getContext();
@@ -147,58 +144,97 @@
             TestSequencer sequencer = createBtPayloadTestSequence(clientTransport, serverTransport);
 
             //Debug.startMethodTracing("ObexTrace");
-            assertTrue(sequencer.run());
+            assertTrue(sequencer.run(mContext));
             //Debug.stopMethodTracing();
         } catch (IOException e) {
             Log.e(TAG, "IOException", e);
         }
     }
 
+    /**
+     * Run the test sequence using a local socket.
+     * Throughput around 4000 kbyte/s - with a larger OBEX package size.
+     */
+    public void testLocalSockets() {
+        mContext = this.getContext();
+        System.out.println("Setting up sockets...");
+
+        try {
+            /* Create and interconnect local pipes for transport */
+            LocalServerSocket serverSock = new LocalServerSocket("com.android.bluetooth.tests.sock");
+            LocalSocket clientSock = new LocalSocket();
+            LocalSocket acceptSock;
+
+            clientSock.connect(serverSock.getLocalSocketAddress());
+
+            acceptSock = serverSock.accept();
+
+            /* Create the OBEX transport objects to wrap the pipes - enable SRM */
+            ObexPipeTransport clientTransport = new ObexPipeTransport(clientSock.getInputStream(),
+                    clientSock.getOutputStream(), true);
+            ObexPipeTransport serverTransport = new ObexPipeTransport(acceptSock.getInputStream(),
+                    acceptSock.getOutputStream(), true);
+
+            TestSequencer sequencer = createBtPayloadTestSequence(clientTransport, serverTransport);
+
+            //Debug.startMethodTracing("ObexTrace");
+            assertTrue(sequencer.run(mContext));
+            //Debug.stopMethodTracing();
+
+            clientSock.close();
+            acceptSock.close();
+            serverSock.close();
+        } catch (IOException e) {
+            Log.e(TAG, "IOException", e);
+        }
+    }
+
     /* Create a sequence of put/get operations with different payload sizes */
     private TestSequencer createBtPayloadTestSequence(ObexTransport clientTransport,
             ObexTransport serverTransport)
             throws IOException {
-        TestSequencer sequencer = new TestSequencer(clientTransport, serverTransport);
+        TestSequencer sequencer = new TestSequencer(clientTransport, serverTransport, this);
         SeqStep step;
 
-        step = sequencer.addStep(OPTYPE.CONNECT);
+        step = sequencer.addStep(OPTYPE.CONNECT, sResponseCodeValidator);
+        if(false){
 
-        step = sequencer.addStep(OPTYPE.PUT);
+        step = sequencer.addStep(OPTYPE.PUT, sDataValidator);
         step.mParams = defaultParams;
         step.mUseSrm = true;
 
-        step = sequencer.addStep(OPTYPE.GET);
+        step = sequencer.addStep(OPTYPE.GET, sDataValidator);
         step.mParams = defaultParams;
         step.mUseSrm = true;
-if(true){
-        step = sequencer.addStep(OPTYPE.PUT);
+
+        step = sequencer.addStep(OPTYPE.PUT, sDataValidator);
         step.mParams = throttle100Params;
         step.mUseSrm = true;
 
-        step = sequencer.addStep(OPTYPE.GET);
+        step = sequencer.addStep(OPTYPE.GET, sDataValidator);
         step.mParams = throttle100Params;
         step.mUseSrm = true;
 
         for(int i=0; i<SMALL_OPERATION_COUNT; i++){
-            step = sequencer.addStep(OPTYPE.PUT);
+            step = sequencer.addStep(OPTYPE.PUT, sDataValidator);
             step.mParams = smallParams;
             step.mUseSrm = true;
 
-            step = sequencer.addStep(OPTYPE.GET);
+            step = sequencer.addStep(OPTYPE.GET, sDataValidator);
             step.mParams = smallParams;
             step.mUseSrm = true;
 
         }
+}
 
-        step = sequencer.addStep(OPTYPE.PUT);
+        step = sequencer.addStep(OPTYPE.PUT, sDataValidator);
         step.mParams = hugeParams;
         step.mUseSrm = true;
 
-        step = sequencer.addStep(OPTYPE.GET);
+        step = sequencer.addStep(OPTYPE.GET, sDataValidator);
         step.mParams = hugeParams;
         step.mUseSrm = true;
-    }
-        step = sequencer.addStep(OPTYPE.DISCONNECT);
+        step = sequencer.addStep(OPTYPE.DISCONNECT, sResponseCodeValidator);
 
         return sequencer;
     }
@@ -206,24 +242,98 @@
     private TestSequencer createBtConnectTestSequence(ObexTransport clientTransport,
             ObexTransport serverTransport)
             throws IOException {
-        TestSequencer sequencer = new TestSequencer(clientTransport, serverTransport);
+        TestSequencer sequencer = new TestSequencer(clientTransport, serverTransport, this);
         SeqStep step;
 
-            step = sequencer.addStep(OPTYPE.CONNECT);
+            step = sequencer.addStep(OPTYPE.CONNECT, sResponseCodeValidator);
 
-            step = sequencer.addStep(OPTYPE.PUT);
+            step = sequencer.addStep(OPTYPE.PUT, sDataValidator);
             step.mParams = smallParams;
             step.mUseSrm = true;
 
-            step = sequencer.addStep(OPTYPE.GET);
+            step = sequencer.addStep(OPTYPE.GET, sDataValidator);
             step.mParams = smallParams;
             step.mUseSrm = true;
 
-            step = sequencer.addStep(OPTYPE.DISCONNECT);
+            step = sequencer.addStep(OPTYPE.DISCONNECT, sResponseCodeValidator);
 
         return sequencer;
     }
 
+
+    /**
+     * Use this validator to validate operation response codes. E.g. for OBEX CONNECT and
+     * DISCONNECT operations.
+     * Expects HeaderSet to be valid, and Operation to be null.
+     */
+    public static ISeqStepValidator getResponsecodevalidator() {
+        return sResponseCodeValidator;
+    }
+
+    /**
+     * Use this validator to validate (and read/write data) for OBEX PUT and GET operations.
+     * Expects Operation to be valid, and HeaderSet to be null.
+     */
+    public static ISeqStepValidator getDatavalidator() {
+        return sDataValidator;
+    }
+
+    /**
+     * Use this validator to validate operation response codes. E.g. for OBEX CONNECT and
+     * DISCONNECT operations.
+     * Expects HeaderSet to be valid, and Operation to be null.
+     */
+    private static class ResponseCodeValidator implements ISeqStepValidator {
+
+        protected static boolean validateHeaderSet(HeaderSet headers, HeaderSet expected)
+                throws IOException {
+            if(headers.getResponseCode() != ResponseCodes.OBEX_HTTP_OK) {
+                Log.e(TAG,"Wrong ResponseCode: " + headers.getResponseCode());
+                Assert.assertTrue(false);
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public boolean validate(SeqStep step, HeaderSet response, Operation op)
+                throws IOException {
+            if(response == null) {
+                if(op.getResponseCode() != ResponseCodes.OBEX_HTTP_OK) {
+                    Log.e(TAG,"Wrong ResponseCode: " + op.getResponseCode());
+                    Assert.assertTrue(false);
+                    return false;
+                }
+                return true;
+            }
+            return validateHeaderSet(response, step.mResHeaders);
+        }
+    }
+
+    /**
+     * Use this validator to validate (and read/write data) for OBEX PUT and GET operations.
+     * Expects Operation to ve valid, and HeaderSet to be null.
+     */
+    private static class DataValidator implements ISeqStepValidator {
+        @Override
+        public boolean validate(SeqStep step, HeaderSet notUsed, Operation op)
+        throws IOException {
+            Assert.assertNotNull(op);
+            if(step.mType == OPTYPE.GET) {
+                op.noBodyHeader();
+                sDataHandler.readData(op.openDataInputStream(), step.mParams);
+            } else if (step.mType == OPTYPE.PUT) {
+                sDataHandler.writeData(op.openDataOutputStream(), step.mParams);
+            }
+            int responseCode = op.getResponseCode();
+            Log.i(TAG, "response code: " + responseCode);
+            HeaderSet response = op.getReceivedHeader();
+            ResponseCodeValidator.validateHeaderSet(response, step.mResHeaders);
+            op.close();
+            return true;
+        }
+    }
+
     public void testBtServerL2cap() {
         testBtServer(BluetoothSocket.TYPE_L2CAP, false, SequencerType.SEQ_TYPE_PAYLOAD);
     }
@@ -276,7 +386,7 @@
                     SequencerType.SEQ_TYPE_CONNECT_DISCONNECT);
             try {
                 // We give the server 100ms to allow adding SDP record
-                Thread.sleep(100);
+                Thread.sleep(150);
             } catch (InterruptedException e) {
                 Log.e(TAG,"Exception while waiting...",e);
             }
@@ -303,7 +413,7 @@
                     SequencerType.SEQ_TYPE_CONNECT_DISCONNECT);
             try {
                 // We give the server 100ms to allow adding SDP record
-                Thread.sleep(100);
+                Thread.sleep(250);
             } catch (InterruptedException e) {
                 Log.e(TAG,"Exception while waiting...",e);
             }
@@ -327,16 +437,17 @@
             Log.e(TAG,"No Bluetooth Device!");
             assertTrue(false);
         }
-        enableBt(bt);
+        BluetoothTestUtils.enableBt(bt);
         BluetoothServerSocket serverSocket=null;
         if(type == BluetoothSocket.TYPE_L2CAP) {
             if(useSdp == true) {
-                serverSocket = bt.listenUsingL2capOn(L2CAP_PSM);
-            } else {
                 serverSocket = bt.listenUsingL2capOn(
                         BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP);
+            } else {
+                serverSocket = bt.listenUsingL2capOn(L2CAP_PSM);
             }
             l2capPsm = serverSocket.getChannel();
+            Log.d(TAG, "L2CAP createde, PSM: " + l2capPsm);
         } else if(type == BluetoothSocket.TYPE_RFCOMM) {
             if(useSdp == true) {
                 serverSocket = bt.listenUsingInsecureRfcommOn(
@@ -345,19 +456,25 @@
                 serverSocket = bt.listenUsingInsecureRfcommOn(RFCOMM_CHANNEL);
             }
             rfcommChannel = serverSocket.getChannel();
+            Log.d(TAG, "RFCOMM createde, Channel: " + rfcommChannel);
         } else {
             fail("Invalid transport type!");
         }
         if(useSdp == true) {
-            /* We use the MAP service record to be able to  */
+            /* We use the MAP service record to be able to set rfcomm and l2cap channels */
             // TODO: We need to free this
-            if(sSdpHandle < 0) {
-                sSdpHandle = SdpManager.getDefaultManager().createMapMasRecord(SDP_SERVER_NAME,
-                        SDP_MAS_ID, rfcommChannel, l2capPsm,
-                        SDP_VERSION, SDP_MSG_TYPES, (int)(SDP_FEATURES & 0xffffffff));
+            if(sSdpHandle >= 0) {
+                SdpManager.getDefaultManager().removeSdpRecord(sSdpHandle);
             }
+            Log.d(TAG, "Creating record with rfcomm channel: " + rfcommChannel +
+                    " and l2cap channel: " + l2capPsm);
+            sSdpHandle = SdpManager.getDefaultManager().createMapMasRecord(SDP_SERVER_NAME,
+                    SDP_MAS_ID, rfcommChannel, l2capPsm,
+                    SDP_VERSION, SDP_MSG_TYPES, (int)(SDP_FEATURES & 0xffffffff));
+        } else {
+            Log.d(TAG, "SKIP creation of record with rfcomm channel: " + rfcommChannel +
+                    " and l2cap channel: " + l2capPsm);
         }
-
         return serverSocket;
     }
 
@@ -373,7 +490,7 @@
      */
     private void testBtServer(int type, boolean useSdp, SequencerType sequencerType) {
         mContext = this.getContext();
-        System.out.println("Starting BT Server...");
+        Log.d(TAG,"Starting BT Server...");
 
         if(TRACE) Debug.startMethodTracing("ServerSide");
         try {
@@ -398,7 +515,7 @@
 
             }
             //Debug.startMethodTracing("ObexTrace");
-            assertTrue(sequencer.run());
+            assertTrue(sequencer.run(mContext));
             //Debug.stopMethodTracing();
             // Same as below... serverTransport.close();
             // This is done by the obex server socket.close();
@@ -436,7 +553,7 @@
             Log.e(TAG,"No Bluetooth Device!");
             assertTrue(false);
         }
-        enableBt(bt);
+        BluetoothTestUtils.enableBt(bt);
         BluetoothDevice serverDevice = bt.getRemoteDevice(SERVER_ADDRESS);
 
         if(useSdp == true) {
@@ -487,9 +604,9 @@
 
             }
             //Debug.startMethodTracing("ObexTrace");
-            assertTrue(sequencer.run());
+            assertTrue(sequencer.run(mContext));
             //Debug.stopMethodTracing();
-            // socket.close(); shall be closed by the obex client
+            socket.close(); // Only the streams are closed by the obex client
             sequencer.shutdown();
 
         } catch (IOException e) {
@@ -591,336 +708,13 @@
         return broadcastReceiver.getMasRecord();
     }
 
-    /** Helper to turn BT on.
-     * This method will either fail on an assert, or return with BT turned on.
-     * Behavior of getState() and isEnabled() are validated along the way.
-     */
-    public static void enableBt(BluetoothAdapter adapter) {
-        if (adapter.getState() == BluetoothAdapter.STATE_ON) {
-            assertTrue(adapter.isEnabled());
-            return;
-        }
-        assertEquals(BluetoothAdapter.STATE_OFF, adapter.getState());
-        assertFalse(adapter.isEnabled());
-        adapter.enable();
-        for (int i=0; i<ENABLE_TIMEOUT/POLL_TIME; i++) {
-            switch (adapter.getState()) {
-            case BluetoothAdapter.STATE_ON:
-                assertTrue(adapter.isEnabled());
-                return;
-            case BluetoothAdapter.STATE_OFF:
-                Log.i(TAG, "STATE_OFF: Still waiting for enable to begin...");
-                break;
-            default:
-                assertEquals(BluetoothAdapter.STATE_TURNING_ON, adapter.getState());
-                assertFalse(adapter.isEnabled());
-                break;
-            }
-            try {
-                Thread.sleep(POLL_TIME);
-            } catch (InterruptedException e) {}
-        }
-        fail("enable() timeout");
-        Log.i(TAG, "Bluetooth enabled...");
+    @Override
+    public ServerRequestHandler getObexServer(ArrayList<SeqStep> sequence,
+            CountDownLatch stopLatch) {
+        return new ObexTestServer(sequence, stopLatch);
     }
 
-    public static class TestSequencer implements Callback {
-
-        private final static int MSG_ID_TIMEOUT = 0x01;
-        private final static int TIMEOUT_VALUE = 100*2000; // ms
-        private ArrayList<SeqStep> mSequence = null;
-        private HandlerThread mHandlerThread = null;
-        private Handler mMessageHandler = null;
-        private ObexTransport mClientTransport;
-        private ObexTransport mServerTransport;
-
-        private ClientSession mClientSession;
-        private ServerSession mServerSession;
-        ObexTestDataHandler mDataHandler;
-
-        public enum OPTYPE {CONNECT, PUT, GET, DISCONNECT};
 
 
-        public class SeqStep {
-            /**
-             * Test step class to define the operations to be tested.
-             * Some of the data in these test steps will be modified during
-             * test - e.g. the HeaderSets will be modified to enable SRM
-             * and/or carry test information
-             */
-            /* Operation type - Connect, Get, Put etc. */
-            public OPTYPE mType;
-            /* The headers to send in the request - and validate on server side */
-            public HeaderSet mReqHeaders = null;
-            /* The headers to send in the response - and validate on client side */
-            public HeaderSet mResHeaders = null;
-            /* Use SRM */
-            public boolean mUseSrm = false;
-            /* The amount of data to include in the body */
-            public ObexTestParams mParams = null;
-            /* The offset into the data where the un-pause signal is to be sent */
-            public int mUnPauseOffset = -1;
-            /* The offset into the data where the Abort request is to be sent */
-            public int mAbortOffset = -1;
-            /* The side to perform Abort */
-            public boolean mServerSideAbout = false;
-            /* The ID of the test step */
-            private int mId;
+}
 
-            /* Arrays to hold expected sequence of request/response packets. */
-            public ArrayList<ObexPacket> mRequestPackets = null;
-            public ArrayList<ObexPacket> mResponsePackets = null;
-
-            public int index = 0; /* requests with same index are executed in parallel
-                                     (without waiting for a response) */
-
-            public SeqStep(OPTYPE type) {
-                mRequestPackets = new ArrayList<ObexPacket>();
-                mResponsePackets = new ArrayList<ObexPacket>();
-                mType = type;
-            }
-
-            /* TODO: Consider to build these automatically based on the operations
-             *       to be performed. Validate using utility functions - not strict
-             *       binary compare.*/
-            public void addObexPacketSet(ObexPacket request, ObexPacket response) {
-                mRequestPackets.add(request);
-                mResponsePackets.add(response);
-            }
-        }
-
-        public TestSequencer(ObexTransport clientTransport, ObexTransport serverTransport)
-                throws IOException {
-            /* Setup the looper thread to handle messages */
-//            mHandlerThread = new HandlerThread("TestTimeoutHandler",
-//                      android.os.Process.THREAD_PRIORITY_BACKGROUND);
-//            mHandlerThread.start();
-//            Looper testLooper = mHandlerThread.getLooper();
-//            mMessageHandler = new Handler(testLooper, this);
-            mClientTransport = clientTransport;
-            mServerTransport = serverTransport;
-
-            //TODO: fix looper cleanup on server - crash after 464 iterations - related to prepare?
-
-            /* Initialize members */
-            mSequence = new ArrayList<SeqStep>();
-            mDataHandler = new ObexTestDataHandler("(Client)");
-        }
-
-        /**
-         * Add a test step to the sequencer.
-         * @param type the OBEX operation to perform.
-         * @return the created step, which can be decorated before execution.
-         */
-        public SeqStep addStep(OPTYPE type) {
-            SeqStep newStep = new SeqStep(type);
-            mSequence.add(newStep);
-            return newStep;
-        }
-
-        /**
-         * Add a sub-step to a sequencer step. All requests added to the same index will be send to
-         * the SapServer in the order added before listening for the response.
-         * The response order is not validated - hence for each response received the entire list of
-         * responses in the step will be searched for a match.
-         * @param index the index returned from addStep() to which the sub-step is to be added.
-         * @param request The request to send to the SAP server
-         * @param response The response to EXPECT from the SAP server
-
-        public void addSubStep(int index, SapMessage request, SapMessage response) {
-            SeqStep step = sequence.get(index);
-            step.add(request, response);
-        }*/
-
-
-        /**
-         * Run the sequence.
-         * Validate the response is either the expected response or one of the expected events.
-         *
-         * @return true when done - asserts at error/fail
-         */
-        public boolean run() throws IOException {
-            CountDownLatch stopLatch = new CountDownLatch(1);
-
-            /* TODO:
-             * First create sequencer to validate using BT-snoop
-             * 1) Create the transports (this could include a validation sniffer on each side)
-             * 2) Create a server thread with a link to the transport
-             * 3) execute the client operation
-             * 4) validate response
-             *
-             * On server:
-             * 1) validate the request contains the expected content
-             * 2) send response.
-             * */
-
-            /* Create the server */
-            if(mServerTransport != null) {
-                mServerSession = new ServerSession(mServerTransport, new ObexTestServer(mSequence,
-                        stopLatch), null);
-            }
-
-            /* Create the client */
-            if(mClientTransport != null) {
-                mClientSession = new ClientSession(mClientTransport);
-
-                for(SeqStep step : mSequence) {
-                    long stepIndex = mSequence.indexOf(step);
-
-                    Log.i(TAG, "Executing step " + stepIndex + " of type: " + step.mType);
-
-                    switch(step.mType) {
-                    case CONNECT: {
-                        HeaderSet reqHeaders = step.mReqHeaders;
-                        if(reqHeaders == null) {
-                            reqHeaders = new HeaderSet();
-                        }
-                        reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
-                        HeaderSet response = mClientSession.connect(reqHeaders);
-                        validateHeaderSet(response, step.mResHeaders);
-                        break;
-                    }
-                    case GET:{
-                        HeaderSet reqHeaders = step.mReqHeaders;
-                        if(reqHeaders == null) {
-                            reqHeaders = new HeaderSet();
-                        }
-                        reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
-                        Operation op = mClientSession.get(reqHeaders);
-                        if(op != null) {
-                            op.noBodyHeader();
-                            mDataHandler.readData(op.openDataInputStream(), step.mParams);
-                            int responseCode = op.getResponseCode();
-                            Log.i(TAG, "response code: " + responseCode);
-                            HeaderSet response = op.getReceivedHeader();
-                            validateHeaderSet(response, step.mResHeaders);
-                            op.close();
-                        }
-                        break;
-                    }
-                    case PUT: {
-                        HeaderSet reqHeaders = step.mReqHeaders;
-                        if(reqHeaders == null) {
-                            reqHeaders = new HeaderSet();
-                        }
-                        reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
-                        Operation op = mClientSession.put(reqHeaders);
-                        if(op != null) {
-                            mDataHandler.writeData(op.openDataOutputStream(), step.mParams);
-                            int responseCode = op.getResponseCode();
-                            Log.i(TAG, "response code: " + responseCode);
-                            HeaderSet response = op.getReceivedHeader();
-                            validateHeaderSet(response, step.mResHeaders);
-                            op.close();
-                        }
-                        break;
-                    }
-                    case DISCONNECT: {
-                        Log.i(TAG,"Requesting disconnect...");
-                        HeaderSet reqHeaders = step.mReqHeaders;
-                        if(reqHeaders == null) {
-                            reqHeaders = new HeaderSet();
-                        }
-                        reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
-                        try{
-                            HeaderSet response = mClientSession.disconnect(reqHeaders);
-                            Log.i(TAG,"Received disconnect response...");
-                            // For some reason this returns -1 -> EOS
-                            // Maybe increase server timeout.
-                            validateHeaderSet(response, step.mResHeaders);
-                        } catch (IOException e) {
-                            Log.e(TAG, "Error getting response code", e);
-                        }
-                        break;
-                    }
-                    default:
-                        assertTrue("Unknown type: " + step.mType, false);
-                        break;
-
-                    }
-                }
-                mClientSession.close();
-            }
-            /* All done, close down... */
-            if(mServerSession != null) {
-                boolean interrupted = false;
-                do {
-                    try {
-                        interrupted = false;
-                        Log.i(TAG,"Waiting for stopLatch signal...");
-                        stopLatch.await();
-                    } catch (InterruptedException e) {
-                        Log.w(TAG,e);
-                        interrupted = true;
-                    }
-                } while (interrupted == true);
-                Log.i(TAG,"stopLatch signal received closing down...");
-                try {
-                    interrupted = false;
-                    Log.i(TAG,"  Sleep 50ms to allow disconnect signal to be send before closing.");
-                    Thread.sleep(50);
-                } catch (InterruptedException e) {
-                    Log.w(TAG,e);
-                    interrupted = true;
-                }
-                mServerSession.close();
-            }
-            // this will close the I/O streams as well.
-            return true;
-        }
-
-        public void shutdown() {
-//            mMessageHandler.removeCallbacksAndMessages(null);
-//            mMessageHandler.quit();
-//            mMessageHandler = null;
-        }
-
-
-        void validateHeaderSet(HeaderSet headers, HeaderSet expected) throws IOException {
-            /* TODO: Implement and assert if different */
-            if(headers.getResponseCode() != ResponseCodes.OBEX_HTTP_OK) {
-                Log.e(TAG,"Wrong ResponseCode: " + headers.getResponseCode());
-                assertTrue(false);
-            }
-        }
-
-//        private void startTimer() {
-//            Message timeoutMessage = mMessageHandler.obtainMessage(MSG_ID_TIMEOUT);
-//            mMessageHandler.sendMessageDelayed(timeoutMessage, TIMEOUT_VALUE);
-//        }
-//
-//        private void stopTimer() {
-//            mMessageHandler.removeMessages(MSG_ID_TIMEOUT);
-//        }
-
-        @Override
-        public boolean handleMessage(Message msg) {
-
-            Log.i(TAG,"Handling message ID: " + msg.what);
-
-            switch(msg.what) {
-            case MSG_ID_TIMEOUT:
-                Log.w(TAG, "Timeout occured!");
-/*                try {
-                    //inStream.close();
-                } catch (IOException e) {
-                    Log.e(TAG, "failed to close inStream", e);
-                }
-                try {
-                    //outStream.close();
-                } catch (IOException e) {
-                    Log.e(TAG, "failed to close outStream", e);
-                }*/
-                break;
-            default:
-                /* Message not handled */
-                return false;
-            }
-            return true; // Message handles
-        }
-
-
-
-    }
-
-}
\ No newline at end of file
diff --git a/tests/src/com/android/bluetooth/tests/ObexTestServer.java b/tests/src/com/android/bluetooth/tests/ObexTestServer.java
index b7df65c..1efe56d 100644
--- a/tests/src/com/android/bluetooth/tests/ObexTestServer.java
+++ b/tests/src/com/android/bluetooth/tests/ObexTestServer.java
@@ -13,8 +13,6 @@
 
 import android.util.Log;
 
-import com.android.bluetooth.tests.ObexTest.TestSequencer.SeqStep;
-
 public class ObexTestServer extends ServerRequestHandler {
 
     private static final String TAG = "ObexTestServer";
@@ -40,7 +38,7 @@
         int index;
         int result = ResponseCodes.OBEX_HTTP_OK;
         try {
-            index = ((Long)request.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue();
+            index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
             mOperationIndex = index;
         } catch (IOException e) {
             Log.e(TAG, "Exception in onConnect - aborting...");
@@ -58,7 +56,7 @@
         int index;
         int result = ResponseCodes.OBEX_HTTP_OK;
         try {
-            index = ((Long)request.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue();
+            index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
             mOperationIndex = index;
         } catch (IOException e) {
             Log.e(TAG, "Exception in onDisconnect...");
@@ -89,7 +87,7 @@
         try{
             inStream = operation.openInputStream();
             HeaderSet reqHeaders = operation.getReceivedHeader();
-            int index = ((Long)reqHeaders.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue();
+            int index = ((Long)reqHeaders.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
             mOperationIndex = index;
             mDataHandler.readData(inStream, mSequence.get(index).mParams);
         } catch (IOException e) {
@@ -121,7 +119,7 @@
         try{
             outStream = operation.openOutputStream();
             HeaderSet reqHeaders = operation.getReceivedHeader();
-            int index = ((Long)reqHeaders.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue();
+            int index = ((Long)reqHeaders.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
             mOperationIndex = index;
             mDataHandler.writeData(outStream, mSequence.get(index).mParams);
         } catch (IOException e) {
diff --git a/tests/src/com/android/bluetooth/tests/SdpManagerTest.java b/tests/src/com/android/bluetooth/tests/SdpManagerTest.java
index c722072..2b7310d 100644
--- a/tests/src/com/android/bluetooth/tests/SdpManagerTest.java
+++ b/tests/src/com/android/bluetooth/tests/SdpManagerTest.java
@@ -34,8 +34,8 @@
     public static final int SDP_RECORD_COUNT = 12; /* Maximum number of records to create */
     public static final int SDP_ITERATIONS = 2000;
 
-    public static final String SDP_SERVER_NAME = "SDP test Server";
-    public static final String SDP_CLIENT_NAME = "SDP test Client";
+    public static final String SDP_SERVER_NAME = "SDP test server";
+    public static final String SDP_CLIENT_NAME = "SDP test client";
 
     public static final long SDP_FEATURES   = 0x87654321L;  /* 32 bit */
     public static final int  SDP_MSG_TYPES  = 0xf1;         /*  8 bit */
@@ -51,7 +51,7 @@
             Log.e(TAG,"No Bluetooth Device!");
             assertTrue(false);
         }
-        ObexTest.enableBt(bt);
+        BluetoothTestUtils.enableBt(bt);
         mManager = SdpManager.getDefaultManager();
         addRemoveRecords(SDP_RECORD_COUNT);
     }
@@ -62,7 +62,7 @@
             Log.e(TAG,"No Bluetooth Device!");
             assertTrue(false);
         }
-        ObexTest.enableBt(bt);
+        BluetoothTestUtils.enableBt(bt);
         mManager = SdpManager.getDefaultManager();
 
         int handles[] = new int[SDP_RECORD_COUNT];
@@ -175,7 +175,7 @@
             mClientSession = new ClientSession(clientTransport);
             { // Connect
                 HeaderSet reqHeaders = new HeaderSet();
-                reqHeaders.setHeader(ObexTest.STEP_INDEX_HEADER, (long)0);
+                reqHeaders.setHeader(TestSequencer.STEP_INDEX_HEADER, (long)0);
                 HeaderSet response = mClientSession.connect(reqHeaders);
                 assertEquals(response.responseCode, ResponseCodes.OBEX_HTTP_OK);
             }
@@ -186,7 +186,7 @@
 
                 { // get operation to trigger SDP search on peer device
                     HeaderSet reqHeaders = new HeaderSet();
-                    reqHeaders.setHeader(ObexTest.STEP_INDEX_HEADER, (long)iteration);
+                    reqHeaders.setHeader(TestSequencer.STEP_INDEX_HEADER, (long)iteration);
                     reqHeaders.setHeader(HeaderSet.COUNT, (long)count);
                     reqHeaders.setHeader(HeaderSet.NAME, uuids_str);
                     Operation op = mClientSession.get(reqHeaders);
@@ -201,7 +201,7 @@
             }
             { // disconnect to end test
                 HeaderSet reqHeaders = new HeaderSet();
-                reqHeaders.setHeader(ObexTest.STEP_INDEX_HEADER, 0L); // signals end of test
+                reqHeaders.setHeader(TestSequencer.STEP_INDEX_HEADER, 0L); // signals end of test
                 HeaderSet response = mClientSession.disconnect(reqHeaders);
                 assertEquals(response.responseCode, ResponseCodes.OBEX_HTTP_OK);
             }
diff --git a/tests/src/com/android/bluetooth/tests/SdpManagerTestServer.java b/tests/src/com/android/bluetooth/tests/SdpManagerTestServer.java
index 2151e56..19e01be 100644
--- a/tests/src/com/android/bluetooth/tests/SdpManagerTestServer.java
+++ b/tests/src/com/android/bluetooth/tests/SdpManagerTestServer.java
@@ -1,9 +1,6 @@
 package com.android.bluetooth.tests;
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 
@@ -13,9 +10,12 @@
 import javax.obex.ServerRequestHandler;
 
 import junit.framework.Assert;
-
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothUuid;
+import android.bluetooth.SdpMasRecord;
+import android.bluetooth.SdpMnsRecord;
+import android.bluetooth.SdpOppOpsRecord;
+import android.bluetooth.SdpPseRecord;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -25,11 +25,6 @@
 
 import com.android.bluetooth.btservice.AbstractionLayer;
 import com.android.bluetooth.sdp.SdpManager;
-import com.android.bluetooth.sdp.SdpMasRecord;
-import com.android.bluetooth.sdp.SdpMnsRecord;
-import com.android.bluetooth.sdp.SdpOppOpsRecord;
-import com.android.bluetooth.sdp.SdpPseRecord;
-import com.android.bluetooth.tests.ObexTest.TestSequencer.SeqStep;
 
 /**
  * We use an OBEX server to execute SDP search operations, and validate results.
@@ -64,7 +59,7 @@
         int index;
         int result = ResponseCodes.OBEX_HTTP_OK;
         try {
-            index = ((Long)request.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue();
+            index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
             mOperationIndex = index;
         } catch (IOException e) {
             Log.e(TAG, "Exception in onConnect - aborting...");
@@ -80,7 +75,7 @@
         int index;
         int result = ResponseCodes.OBEX_HTTP_OK;
         try {
-            index = ((Long)request.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue();
+            index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
             mOperationIndex = index;
         } catch (IOException e) {
             Log.e(TAG, "Exception in onDisconnect...");
@@ -122,7 +117,7 @@
         mResult = ResponseCodes.OBEX_HTTP_OK;
         try{
             HeaderSet reqHeaders = operation.getReceivedHeader();
-            int index = ((Long)reqHeaders.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue();
+            int index = ((Long)reqHeaders.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
             mOperationIndex = index;
             /* Get the expected number of records to read. */
             int count = ((Long)reqHeaders.getHeader(HeaderSet.COUNT)).intValue();
diff --git a/tests/src/com/android/bluetooth/tests/SeqStep.java b/tests/src/com/android/bluetooth/tests/SeqStep.java
new file mode 100644
index 0000000..3f6772a
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/SeqStep.java
@@ -0,0 +1,88 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import javax.obex.HeaderSet;
+import javax.obex.ObexPacket;
+import javax.obex.Operation;
+
+import junit.framework.Assert;
+
+import com.android.bluetooth.tests.TestSequencer.OPTYPE;
+
+public class SeqStep {
+    /**
+     * Test step class to define the operations to be tested.
+     * Some of the data in these test steps will be modified during
+     * test - e.g. the HeaderSets will be modified to enable SRM
+     * and/or carry test information
+     */
+    /* Operation type - Connect, Get, Put etc. */
+    public OPTYPE mType;
+    /* The headers to send in the request - and validate on server side */
+    public HeaderSet mReqHeaders = null;
+    /* The headers to send in the response - and validate on client side */
+    public HeaderSet mResHeaders = null;
+    /* Use SRM */
+    public boolean mUseSrm = false;
+    /* The amount of data to include in the body */
+    public ObexTestParams mParams = null;
+    /* The offset into the data where the un-pause signal is to be sent */
+    public int mUnPauseOffset = -1;
+    /* The offset into the data where the Abort request is to be sent */
+    public int mAbortOffset = -1;
+    /* The side to perform Abort */
+    public boolean mServerSideAbout = false;
+    /* The ID of the test step */
+    private int mId;
+
+    public boolean mSetPathBackup = false; /* bit 0 in flags */
+    public boolean mSetPathCreate = false; /* Inverse of bit 1 in flags */
+
+
+    public ISeqStepValidator mValidator = null;
+    public ISeqStepAction mServerPreAction = null;
+    public ISeqStepAction mClientPostAction = null;
+
+    /* Arrays to hold expected sequence of request/response packets. */
+    public ArrayList<ObexPacket> mRequestPackets = null;
+    public ArrayList<ObexPacket> mResponsePackets = null;
+
+    public int index = 0; /* requests with same index are executed in parallel
+                             (without waiting for a response) */
+
+    public SeqStep(OPTYPE type) {
+        mRequestPackets = new ArrayList<ObexPacket>();
+        mResponsePackets = new ArrayList<ObexPacket>();
+        mType = type;
+    }
+
+    public boolean validate(HeaderSet response, Operation op) throws IOException {
+        Assert.assertNotNull(mValidator);
+        return mValidator.validate(this, response, op);
+    }
+
+    public void serverPreAction(HeaderSet request, Operation op) throws IOException {
+        if(mServerPreAction != null) {
+            mServerPreAction.execute(this, request, op);
+        }
+    }
+
+    public void clientPostAction(HeaderSet response, Operation op) throws IOException {
+        if(mClientPostAction != null) {
+            mClientPostAction.execute(this, response, op);
+        }
+    }
+
+
+    /* TODO: Consider to build these automatically based on the operations
+     *       to be performed. Validate using utility functions - not strict
+     *       binary compare.
+     *
+     *       OR simply remove!*/
+    public void addObexPacketSet(ObexPacket request, ObexPacket response) {
+        mRequestPackets.add(request);
+        mResponsePackets.add(response);
+    }
+}
diff --git a/tests/src/com/android/bluetooth/tests/TestSequencer.java b/tests/src/com/android/bluetooth/tests/TestSequencer.java
new file mode 100644
index 0000000..fb0dcba
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/TestSequencer.java
@@ -0,0 +1,284 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ObexTransport;
+import javax.obex.Operation;
+import javax.obex.ServerSession;
+
+import junit.framework.Assert;
+
+import android.content.Context;
+import android.hardware.camera2.impl.GetCommand;
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.PowerManager;
+import android.util.Log;
+
+public class TestSequencer implements Callback {
+    protected static String TAG = "TestSequencer";
+    protected static final boolean D = true;
+
+    private final static int MSG_ID_TIMEOUT = 0x01;
+    private final static int TIMEOUT_VALUE = 100*2000; // ms
+    private ArrayList<SeqStep> mSequence = null;
+    private HandlerThread mHandlerThread = null;
+    private Handler mMessageHandler = null;
+    private ObexTransport mClientTransport;
+    private ObexTransport mServerTransport;
+
+    private ClientSession mClientSession = null;
+    private ServerSession mServerSession = null;
+    public static final int STEP_INDEX_HEADER = 0xF1; /*0xFE*/
+
+    public enum OPTYPE {CONNECT, PUT, GET, SET_PATH, DISCONNECT};
+
+    private ITestSequenceConfigurator mConfigurator = null;
+
+    public TestSequencer(ObexTransport clientTransport, ObexTransport serverTransport,
+            ITestSequenceConfigurator configurator)
+            throws IOException {
+        /* Setup the looper thread to handle timeout messages */
+//            mHandlerThread = new HandlerThread("TestTimeoutHandler",
+//                      android.os.Process.THREAD_PRIORITY_BACKGROUND);
+//            mHandlerThread.start();
+//            Looper testLooper = mHandlerThread.getLooper();
+//            mMessageHandler = new Handler(testLooper, this);
+        //TODO: fix looper cleanup on server - crash after 464 iterations - related to prepare?
+
+        mClientTransport = clientTransport;
+        mServerTransport = serverTransport;
+
+        /* Initialize members */
+        mSequence = new ArrayList<SeqStep>();
+        mConfigurator = configurator;
+        Assert.assertNotNull(configurator);
+    }
+
+    /**
+     * Add a test step to the sequencer.
+     * @param type the OBEX operation to perform.
+     * @return the created step, which can be decorated before execution.
+     */
+    public SeqStep addStep(OPTYPE type, ISeqStepValidator validator) {
+        SeqStep newStep = new SeqStep(type);
+        newStep.mValidator = validator;
+        mSequence.add(newStep);
+        return newStep;
+    }
+
+    /**
+     * Add a sub-step to a sequencer step. All requests added to the same index will be send to
+     * the SapServer in the order added before listening for the response.
+     * The response order is not validated - hence for each response received the entire list of
+     * responses in the step will be searched for a match.
+     * @param index the index returned from addStep() to which the sub-step is to be added.
+     * @param request The request to send to the SAP server
+     * @param response The response to EXPECT from the SAP server
+
+    public void addSubStep(int index, SapMessage request, SapMessage response) {
+        SeqStep step = sequence.get(index);
+        step.add(request, response);
+    }*/
+
+
+    /**
+     * Run the sequence.
+     * Validate the response is either the expected response or one of the expected events.
+     *
+     * @return true when done - asserts at error/fail
+     */
+    public boolean run(Context context) throws IOException {
+        CountDownLatch stopLatch = new CountDownLatch(1);
+        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+        //wl.acquire();
+        try {
+            /* TODO:
+             * First create sequencer to validate using BT-snoop
+             * 1) Create the transports (this could include a validation sniffer on each side)
+             * 2) Create a server thread with a link to the transport
+             * 3) execute the client operation
+             * 4) validate response
+             *
+             * On server:
+             * 1) validate the request contains the expected content
+             * 2) send response.
+             * */
+
+            /* Create the server */
+            if(mServerTransport != null) {
+                mServerSession = new ServerSession(mServerTransport,
+                        mConfigurator.getObexServer(mSequence, stopLatch) , null);
+            }
+
+            /* Create the client */
+            if(mClientTransport != null) {
+                mClientSession = new ClientSession(mClientTransport);
+
+                for(SeqStep step : mSequence) {
+                    long stepIndex = mSequence.indexOf(step);
+
+                    Log.i(TAG, "Executing step " + stepIndex + " of type: " + step.mType);
+
+                    switch(step.mType) {
+                    case CONNECT: {
+                        HeaderSet reqHeaders = step.mReqHeaders;
+                        if(reqHeaders == null) {
+                            reqHeaders = new HeaderSet();
+                        }
+                        reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
+                        HeaderSet response = mClientSession.connect(reqHeaders);
+                        step.validate(response, null);
+                        step.clientPostAction(response, null);
+                        break;
+                    }
+                    case GET:{
+                        HeaderSet reqHeaders = step.mReqHeaders;
+                        if(reqHeaders == null) {
+                            reqHeaders = new HeaderSet();
+                        }
+                        reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
+                        Log.i(TAG, "  Starting operation...");
+                        Operation op = mClientSession.get(reqHeaders);
+                        Log.i(TAG, "  Operation done...");
+                        step.validate(null, op);
+                        step.clientPostAction(null, op);
+                        break;
+                    }
+                    case PUT: {
+                        HeaderSet reqHeaders = step.mReqHeaders;
+                        if(reqHeaders == null) {
+                            reqHeaders = new HeaderSet();
+                        }
+                        reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
+                        Operation op = mClientSession.put(reqHeaders);
+                        step.validate(null, op);
+                        step.clientPostAction(null, op);
+                        break;
+                    }
+                    case SET_PATH: {
+                        HeaderSet reqHeaders = step.mReqHeaders;
+                        if(reqHeaders == null) {
+                            reqHeaders = new HeaderSet();
+                        }
+                        reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
+                        try{
+                            HeaderSet response = mClientSession.setPath(reqHeaders,
+                                    step.mSetPathBackup, step.mSetPathCreate);;
+                            Log.i(TAG,"Received setPath response...");
+                            step.validate(response, null);
+                            step.clientPostAction(response, null);
+                        } catch (IOException e) {
+                            Log.e(TAG, "Error getting response code", e);
+                        }
+                        break;
+                    }
+                    case DISCONNECT: {
+                        Log.i(TAG,"Requesting disconnect...");
+                        HeaderSet reqHeaders = step.mReqHeaders;
+                        if(reqHeaders == null) {
+                            reqHeaders = new HeaderSet();
+                        }
+                        reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
+                        try{
+                            HeaderSet response = mClientSession.disconnect(reqHeaders);
+                            Log.i(TAG,"Received disconnect response...");
+                            step.validate(response, null);
+                            step.clientPostAction(response, null);
+                        } catch (IOException e) {
+                            Log.e(TAG, "Error getting response code", e);
+                        }
+                        break;
+                    }
+                    default:
+                        Assert.assertTrue("Unknown type: " + step.mType, false);
+                        break;
+
+                    }
+                }
+                mClientSession.close();
+            }
+            /* All done, close down... */
+            if(mServerSession != null) {
+                boolean interrupted = false;
+                do {
+                    try {
+                        interrupted = false;
+                        Log.i(TAG,"Waiting for stopLatch signal...");
+                        stopLatch.await();
+                    } catch (InterruptedException e) {
+                        Log.w(TAG,e);
+                        interrupted = true;
+                    }
+                } while (interrupted == true);
+                Log.i(TAG,"stopLatch signal received closing down...");
+                try {
+                    interrupted = false;
+                    Log.i(TAG,"  Sleep 50ms to allow disconnect signal to be send before closing.");
+                    Thread.sleep(50);
+                } catch (InterruptedException e) {
+                    Log.w(TAG,e);
+                    interrupted = true;
+                }
+                mServerSession.close();
+            }
+            // this will close the I/O streams as well.
+        } finally {
+            //wl.release();
+        }
+        return true;
+    }
+
+    public void shutdown() {
+//            mMessageHandler.removeCallbacksAndMessages(null);
+//            mMessageHandler.quit();
+//            mMessageHandler = null;
+    }
+
+
+//        private void startTimer() {
+//            Message timeoutMessage = mMessageHandler.obtainMessage(MSG_ID_TIMEOUT);
+//            mMessageHandler.sendMessageDelayed(timeoutMessage, TIMEOUT_VALUE);
+//        }
+//
+//        private void stopTimer() {
+//            mMessageHandler.removeMessages(MSG_ID_TIMEOUT);
+//        }
+
+    @Override
+    public boolean handleMessage(Message msg) {
+
+        Log.i(TAG,"Handling message ID: " + msg.what);
+
+        switch(msg.what) {
+        case MSG_ID_TIMEOUT:
+            Log.w(TAG, "Timeout occured!");
+/*                try {
+                    //inStream.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "failed to close inStream", e);
+                }
+                try {
+                    //outStream.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "failed to close outStream", e);
+                }*/
+            break;
+        default:
+            /* Message not handled */
+            return false;
+        }
+        return true; // Message handles
+    }
+
+
+
+}
+