Bluetooth MAP profile - sms and mms support initial check-in

bug:10116530

Change-Id: If9ce878d71c1e1b12416014c433da03b3033e158
diff --git a/Android.mk b/Android.mk
index b0606df..4c24df9 100644
--- a/Android.mk
+++ b/Android.mk
@@ -10,7 +10,7 @@
 LOCAL_CERTIFICATE := platform
 
 LOCAL_JNI_SHARED_LIBRARIES := libbluetooth_jni
-LOCAL_JAVA_LIBRARIES := javax.obex
+LOCAL_JAVA_LIBRARIES := javax.obex telephony-common mms-common
 LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard
 
 LOCAL_REQUIRED_MODULES := libbluetooth_jni bluetooth.default
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4a363dd..017bd88 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -50,6 +50,13 @@
     <uses-permission android:name="android.permission.MANAGE_USERS"/>
     <uses-permission android:name="com.google.android.gallery3d.permission.GALLERY_PROVIDER"/>
     <uses-permission android:name="com.android.gallery3d.permission.GALLERY_PROVIDER"/>
+    <uses-permission android:name="android.permission.MMS_SEND_OUTBOX_MSG"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.READ_SMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS" />
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
 
     <!-- For PBAP Owner Vcard Info -->
     <uses-permission android:name="android.permission.READ_PROFILE"/>
@@ -220,6 +227,34 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </receiver>
+        <activity android:name=".map.BluetoothMapActivity"
+            android:process="@string/process"
+            android:excludeFromRecents="true"
+            android:theme="@*android:style/Theme.Holo.Dialog.Alert"
+            android:enabled="@bool/profile_supported_map">
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <service
+            android:process="@string/process"
+            android:name=".map.BluetoothMapService"
+            android:enabled="@bool/profile_supported_map" >
+            <intent-filter>
+                <action android:name="android.bluetooth.IBluetoothMap" />
+            </intent-filter>
+        </service>
+        <receiver
+            android:process="@string/process"
+            android:exported="true"
+            android:name=".map.BluetoothMapReceiver"
+            android:enabled="@bool/profile_supported_map">
+            <intent-filter>
+                <action android:name="android.bluetooth.adapter.action.STATE_CHANGED"/>
+                <action android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REPLY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </receiver>
         <service
             android:process="@string/process"
             android:name = ".gatt.GattService"
diff --git a/res/values/config.xml b/res/values/config.xml
index af90926..24eeb2c 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -23,4 +23,5 @@
     <bool name="profile_supported_gatt">true</bool>
     <bool name="pbap_include_photos_in_vcard">false</bool>
     <bool name="pbap_use_profile_for_owner_vcard">true</bool>
+    <bool name="profile_supported_map">true</bool>
 </resources>
diff --git a/res/values/strings_map.xml b/res/values/strings_map.xml
new file mode 100644
index 0000000..ba30b99
--- /dev/null
+++ b/res/values/strings_map.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="map_session_key_dialog_title">Type session key for %1$s</string>
+    <string name="map_session_key_dialog_header">Bluetooth session key required</string>
+    <string name="map_acceptance_timeout_message">There was time out to accept connection with %1$s</string>
+    <string name="map_authentication_timeout_message">There was time out to input session key with %1$s</string>
+    <string name="map_auth_notif_ticker">Obex authentication request</string>
+    <!-- Notification title when a Bluetooth device wants to pair with us -->
+    <string name="map_auth_notif_title">Session Key</string>
+    <!-- Notification message when a Bluetooth device wants to pair with us -->
+    <string name="map_auth_notif_message">Type session key for %1$s</string>
+    <string name="map_defaultname">Carkit</string>
+    <string name="map_unknownName">Unknown name</string>
+    <string name="map_localPhoneName">My name</string>
+    <string name="map_defaultnumber">000000</string>
+</resources>
diff --git a/src/com/android/bluetooth/map/BluetoothMapActivity.java b/src/com/android/bluetooth/map/BluetoothMapActivity.java
new file mode 100644
index 0000000..d415eef
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapActivity.java
@@ -0,0 +1,284 @@
+
+/*
+* 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 com.android.bluetooth.R;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.Preference;
+import android.util.Log;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Button;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.text.InputFilter;
+import android.text.TextWatcher;
+import android.text.InputFilter.LengthFilter;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+/**
+ * MapActivity shows two dialogues: One for accepting incoming map request and
+ * the other prompts the user to enter a session key for authentication with a
+ * remote Bluetooth device.
+ */
+public class BluetoothMapActivity extends AlertActivity implements
+        DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener, TextWatcher {
+    private static final String TAG = "BluetoothMapActivity";
+
+    private static final boolean V = BluetoothMapService.VERBOSE;
+
+    private static final int BLUETOOTH_OBEX_AUTHKEY_MAX_LENGTH = 16;
+
+    private static final int DIALOG_YES_NO_AUTH = 1;
+
+    private static final String KEY_USER_TIMEOUT = "user_timeout";
+
+    private View mView;
+
+    private EditText mKeyView;
+
+    private TextView messageView;
+
+    private String mSessionKey = "";
+
+    private int mCurrentDialog;
+
+    private Button mOkButton;
+
+    private CheckBox mAlwaysAllowed;
+
+    private boolean mTimeout = false;
+
+    private boolean mAlwaysAllowedValue = true;
+
+    private static final int DISMISS_TIMEOUT_DIALOG = 0;
+
+    private static final int DISMISS_TIMEOUT_DIALOG_VALUE = 2000;
+
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!BluetoothMapService.USER_CONFIRM_TIMEOUT_ACTION.equals(intent.getAction())) {
+                return;
+            }
+            onTimeout();
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent i = getIntent();
+        String action = i.getAction();
+        if (action.equals(BluetoothMapService.AUTH_CHALL_ACTION)) {
+            showMapDialog(DIALOG_YES_NO_AUTH);
+            mCurrentDialog = DIALOG_YES_NO_AUTH;
+        } else {
+            Log.e(TAG, "Error: this activity may be started only with intent "
+                    + "MAP_ACCESS_REQUEST or MAP_AUTH_CHALL ");
+            finish();
+        }
+        registerReceiver(mReceiver, new IntentFilter(
+                BluetoothMapService.USER_CONFIRM_TIMEOUT_ACTION));
+    }
+
+    private void showMapDialog(int id) {
+        final AlertController.AlertParams p = mAlertParams;
+        switch (id) {
+            case DIALOG_YES_NO_AUTH:
+                p.mIconId = android.R.drawable.ic_dialog_info;
+                p.mTitle = getString(R.string.map_session_key_dialog_header);
+                p.mView = createView(DIALOG_YES_NO_AUTH);
+                p.mPositiveButtonText = getString(android.R.string.ok);
+                p.mPositiveButtonListener = this;
+                p.mNegativeButtonText = getString(android.R.string.cancel);
+                p.mNegativeButtonListener = this;
+                setupAlert();
+                mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
+                mOkButton.setEnabled(false);
+                break;
+            default:
+                break;
+        }
+    }
+
+    private String createDisplayText(final int id) {
+        String mRemoteName = BluetoothMapService.getRemoteDeviceName();
+        switch (id) {
+            case DIALOG_YES_NO_AUTH:
+                String mMessage2 = getString(R.string.map_session_key_dialog_title, mRemoteName);
+                return mMessage2;
+            default:
+                return null;
+        }
+    }
+
+    private View createView(final int id) {
+        switch (id) {
+            case DIALOG_YES_NO_AUTH:
+                mView = getLayoutInflater().inflate(R.layout.auth, null);
+                messageView = (TextView)mView.findViewById(R.id.message);
+                messageView.setText(createDisplayText(id));
+                mKeyView = (EditText)mView.findViewById(R.id.text);
+                mKeyView.addTextChangedListener(this);
+                mKeyView.setFilters(new InputFilter[] {
+                    new LengthFilter(BLUETOOTH_OBEX_AUTHKEY_MAX_LENGTH)
+                });
+                return mView;
+            default:
+                return null;
+        }
+    }
+
+    private void onPositive() {
+        if (!mTimeout) {
+            if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
+                sendIntentToReceiver(BluetoothMapService.AUTH_RESPONSE_ACTION,
+                        BluetoothMapService.EXTRA_SESSION_KEY, mSessionKey);
+                mKeyView.removeTextChangedListener(this);
+            }
+        }
+        mTimeout = false;
+        finish();
+    }
+
+    private void onNegative() {
+        if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
+            sendIntentToReceiver(BluetoothMapService.AUTH_CANCELLED_ACTION, null, null);
+            mKeyView.removeTextChangedListener(this);
+        }
+        finish();
+    }
+
+    private void sendIntentToReceiver(final String intentName, final String extraName,
+            final String extraValue) {
+        Intent intent = new Intent(intentName);
+        intent.setClassName(BluetoothMapService.THIS_PACKAGE_NAME, BluetoothMapReceiver.class
+                .getName());
+        if (extraName != null) {
+            intent.putExtra(extraName, extraValue);
+        }
+        sendBroadcast(intent);
+    }
+
+    private void sendIntentToReceiver(final String intentName, final String extraName,
+            final boolean extraValue) {
+        Intent intent = new Intent(intentName);
+        intent.setClassName(BluetoothMapService.THIS_PACKAGE_NAME, BluetoothMapReceiver.class
+                .getName());
+        if (extraName != null) {
+            intent.putExtra(extraName, extraValue);
+        }
+        sendBroadcast(intent);
+    }
+
+    public void onClick(DialogInterface dialog, int which) {
+        switch (which) {
+            case DialogInterface.BUTTON_POSITIVE:
+                if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
+                    mSessionKey = mKeyView.getText().toString();
+                }
+                onPositive();
+                break;
+
+            case DialogInterface.BUTTON_NEGATIVE:
+                onNegative();
+                break;
+            default:
+                break;
+        }
+    }
+
+    private void onTimeout() {
+        mTimeout = true;
+        if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
+            messageView.setText(getString(R.string.map_authentication_timeout_message,
+                    BluetoothMapService.getRemoteDeviceName()));
+            mKeyView.setVisibility(View.GONE);
+            mKeyView.clearFocus();
+            mKeyView.removeTextChangedListener(this);
+            mOkButton.setEnabled(true);
+            mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
+        }
+
+        mTimeoutHandler.sendMessageDelayed(mTimeoutHandler.obtainMessage(DISMISS_TIMEOUT_DIALOG),
+                DISMISS_TIMEOUT_DIALOG_VALUE);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        mTimeout = savedInstanceState.getBoolean(KEY_USER_TIMEOUT);
+        if (V) Log.v(TAG, "onRestoreInstanceState() mTimeout: " + mTimeout);
+        if (mTimeout) {
+            onTimeout();
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBoolean(KEY_USER_TIMEOUT, mTimeout);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mReceiver);
+    }
+
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        return true;
+    }
+
+    public void beforeTextChanged(CharSequence s, int start, int before, int after) {
+    }
+
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+    }
+
+    public void afterTextChanged(android.text.Editable s) {
+        if (s.length() > 0) {
+            mOkButton.setEnabled(true);
+        }
+    }
+
+    private final Handler mTimeoutHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case DISMISS_TIMEOUT_DIALOG:
+                    if (V) Log.v(TAG, "Received DISMISS_TIMEOUT_DIALOG msg.");
+                    finish();
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppParams.java b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
new file mode 100644
index 0000000..e55c611
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
@@ -0,0 +1,704 @@
+/*
+* 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.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+import android.util.Log;
+
+/**
+ * This class encapsulates the appParams needed for MAP.
+ */
+public class BluetoothMapAppParams {
+
+    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  = 0x02; //, 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;
+
+    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;
+
+    private int maxListCount        = INVALID_VALUE_PARAMETER;
+    private int startOffset         = INVALID_VALUE_PARAMETER;
+    private int filterMessageType   = INVALID_VALUE_PARAMETER;
+    private long filterPeriodBegin  = INVALID_VALUE_PARAMETER;
+    private long filterPeriodEnd    = INVALID_VALUE_PARAMETER;
+    private int filterReadStatus    = INVALID_VALUE_PARAMETER;
+    private String filterRecipient   = null;
+    private String filterOriginator  = null;
+    private int filterPriority      = INVALID_VALUE_PARAMETER;
+    private int attachment          = INVALID_VALUE_PARAMETER;
+    private int transparent         = INVALID_VALUE_PARAMETER;
+    private int retry               = INVALID_VALUE_PARAMETER;
+    private int newMessage          = INVALID_VALUE_PARAMETER;
+    private int notificationStatus  = INVALID_VALUE_PARAMETER;
+    private int masInstanceId       = INVALID_VALUE_PARAMETER;
+    private long parameterMask      = INVALID_VALUE_PARAMETER;
+    private int folderListingSize   = INVALID_VALUE_PARAMETER;
+    private int messageListingSize  = INVALID_VALUE_PARAMETER;
+    private int subjectLength       = INVALID_VALUE_PARAMETER;
+    private int charset             = INVALID_VALUE_PARAMETER;
+    private int fractionRequest     = INVALID_VALUE_PARAMETER;
+    private int fractionDeliver     = INVALID_VALUE_PARAMETER;
+    private int statusIndicator     = INVALID_VALUE_PARAMETER;
+    private int statusValue         = INVALID_VALUE_PARAMETER;
+    private long mseTime            = INVALID_VALUE_PARAMETER;
+
+    /**
+     * Default constructor, used to build an application parameter object to be
+     * encoded. By default the member variables will be initialized to
+     * {@link INVALID_VALUE_PARAMETER} for values, and empty strings for String
+     * typed members.
+     */
+    public BluetoothMapAppParams() {
+    }
+
+    /**
+     * Creates an application parameter object based on a application parameter
+     * OBEX header. The content of the {@link appParam} byte array will be
+     * parsed, and its content will be stored in the member variables.
+     * {@link INVALID_VALUE_PARAMETER} can be used to determine if a value is
+     * set or not, where strings will be empty, if {@link appParam} did not
+     * contain the parameter.
+     *
+     * @param appParams
+     *            the byte array containing the application parameters OBEX
+     *            header
+     * @throws IllegalArgumentException
+     *             when a parameter does not respect the valid ranges specified
+     *             in the MAP spec.
+     * @throws ParseException
+     *             if a parameter string if formated incorrectly.
+     */
+    public BluetoothMapAppParams(final byte[] appParams)
+                 throws IllegalArgumentException, ParseException {
+        ParseParams(appParams);
+    }
+
+    /**
+     * Parse an application parameter OBEX header stored in a ByteArray.
+     *
+     * @param appParams
+     *            the byte array containing the application parameters OBEX
+     *            header
+     * @throws IllegalArgumentException
+     *             when a parameter does not respect the valid ranges specified
+     *             in the MAP spec.
+     * @throws ParseException
+     *             if a parameter string if formated incorrectly.
+     */
+    private void ParseParams(final byte[] appParams) throws ParseException,
+              IllegalArgumentException {
+        int i = 0;
+        int tagId, tagLength;
+        ByteBuffer appParamBuf = ByteBuffer.wrap(appParams);
+        appParamBuf.order(ByteOrder.BIG_ENDIAN);
+        while (i < appParams.length) {
+            tagId = appParams[i++] & 0xff;     // Convert to unsigned to support values above 127
+            tagLength = appParams[i++] & 0xff; // Convert to unsigned to support values above 127
+            switch (tagId) {
+            case MAX_LIST_COUNT:
+                if (tagLength != MAX_LIST_COUNT_LEN) {
+                    Log.w(TAG, "MAX_LIST_COUNT: Wrong length received: " + tagLength
+                               + " expected: " + MAX_LIST_COUNT_LEN);
+                    break;
+                }
+                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;
+                }
+                setStartOffset(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
+                break;
+            case FILTER_MESSAGE_TYPE:
+                setFilterMessageType(appParams[i] & 0x0f);
+                break;
+            case FILTER_PERIOD_BEGIN:
+                setFilterPeriodBegin(new String(appParams, i, tagLength));
+                break;
+            case FILTER_PERIOD_END:
+                setFilterPeriodEnd(new String(appParams, i, tagLength));
+                break;
+            case FILTER_READ_STATUS:
+                setFilterReadStatus(appParams[i] & 0x03); // Lower two bits
+                break;
+            case FILTER_RECIPIENT:
+                setFilterRecipient(new String(appParams, i, tagLength));
+                break;
+            case FILTER_ORIGINATOR:
+                setFilterOriginator(new String(appParams, i, tagLength));
+                break;
+            case FILTER_PRIORITY:
+                setFilterPriority(appParams[i] & 0x03); // Lower two bits
+                break;
+            case ATTACHMENT:
+                setAttachment(appParams[i] & 0x01); // Lower bit
+                break;
+            case TRANSPARENT:
+                setTransparent(appParams[i] & 0x01); // Lower bit
+                break;
+            case RETRY:
+                setRetry(appParams[i] & 0x01); // Lower bit
+                break;
+            case NEW_MESSAGE:
+                setNewMessage(appParams[i] & 0x01); // Lower bit
+                break;
+            case NOTIFICATION_STATUS:
+                setNotificationStatus(appParams[i] & 0x01); // Lower bit
+                break;
+            case MAS_INSTANCE_ID:
+                setMasInstanceId(appParams[i] & 0xff);
+                break;
+            case PARAMETER_MASK:
+                setParameterMask(appParamBuf.getInt(i) & 0xffffffffL); // Make it unsigned
+                break;
+            case FOLDER_LISTING_SIZE:
+                setFolderListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
+                break;
+            case MESSAGE_LISTING_SIZE:
+                setMessageListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
+                break;
+            case SUBJECT_LENGTH:
+                setSubjectLength(appParams[i] & 0xff);
+                break;
+            case CHARSET:
+                setCharset(appParams[i] & 0x01); // Lower bit
+                break;
+            case FRACTION_REQUEST:
+                setFractionRequest(appParams[i] & 0x01); // Lower bit
+                break;
+            case FRACTION_DELIVER:
+                setFractionDeliver(appParams[i] & 0x01); // Lower bit
+                break;
+            case STATUS_INDICATOR:
+                setStatusIndicator(appParams[i] & 0x01); // Lower bit
+                break;
+            case STATUS_VALUE:
+                setStatusValue(appParams[i] & 0x01); // Lower bit
+                break;
+            case MSE_TIME:
+                setMseTime(new String(appParams, i, tagLength));
+                break;
+            default:
+                // Just skip unknown Tags, no need to report error
+                Log.w(TAG, "Unknown TagId received ( 0x" + Integer.toString(tagId, 16)
+                           + "), skipping...");
+                break;
+            }
+            i += tagLength; // Offset to next TagId
+        }
+    }
+
+    /**
+     * Get the approximate length needed to store the appParameters in a byte
+     * array.
+     *
+     * @return the length in bytes
+     * @throws UnsupportedEncodingException
+     *             if the platform does not support UTF-8 encoding.
+     */
+    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;
+        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;
+        return length;
+    }
+
+    /**
+     * Encode the application parameter object to a byte array.
+     *
+     * @return a byte Array representation of the application parameter object.
+     * @throws UnsupportedEncodingException
+     *             if the platform does not support UTF-8 encoding.
+     */
+    public byte[] EncodeParams() throws UnsupportedEncodingException {
+        ByteBuffer appParamBuf = ByteBuffer.allocate(getParamMaxLength());
+        appParamBuf.order(ByteOrder.BIG_ENDIAN);
+        byte[] retBuf;
+
+        if (getMaxListCount() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) MAX_LIST_COUNT);
+            appParamBuf.put((byte) MAX_LIST_COUNT_LEN);
+            appParamBuf.putShort((short) getMaxListCount());
+        }
+        if (getStartOffset() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) START_OFFSET);
+            appParamBuf.put((byte) START_OFFSET_LEN);
+            appParamBuf.putShort((short) getStartOffset());
+        }
+        if (getFilterMessageType() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) FILTER_MESSAGE_TYPE);
+            appParamBuf.put((byte) FILTER_MESSAGE_TYPE_LEN);
+            appParamBuf.put((byte) getFilterMessageType());
+        }
+        if (getFilterPeriodBegin() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) FILTER_PERIOD_BEGIN);
+            appParamBuf.put((byte) getFilterPeriodBeginString().getBytes("UTF-8").length);
+            appParamBuf.put(getFilterPeriodBeginString().getBytes("UTF-8"));
+        }
+        if (getFilterPeriodEnd() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) FILTER_PERIOD_END);
+            appParamBuf.put((byte) getFilterPeriodEndString().getBytes("UTF-8").length);
+            appParamBuf.put(getFilterPeriodEndString().getBytes("UTF-8"));
+        }
+        if (getFilterReadStatus() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) FILTER_READ_STATUS);
+            appParamBuf.put((byte) FILTER_READ_STATUS_LEN);
+            appParamBuf.put((byte) getFilterReadStatus());
+        }
+        if (getFilterRecipient() != null) {
+            appParamBuf.put((byte) FILTER_RECIPIENT);
+            appParamBuf.put((byte) getFilterRecipient().getBytes("UTF-8").length);
+            appParamBuf.put(getFilterRecipient().getBytes("UTF-8"));
+        }
+        if (getFilterOriginator() != null) {
+            appParamBuf.put((byte) FILTER_ORIGINATOR);
+            appParamBuf.put((byte) getFilterOriginator().getBytes("UTF-8").length);
+            appParamBuf.put(getFilterOriginator().getBytes("UTF-8"));
+        }
+        if (getFilterPriority() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) FILTER_PRIORITY);
+            appParamBuf.put((byte) FILTER_PRIORITY_LEN);
+            appParamBuf.put((byte) getFilterPriority());
+        }
+        if (getAttachment() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) ATTACHMENT);
+            appParamBuf.put((byte) ATTACHMENT_LEN);
+            appParamBuf.put((byte) getAttachment());
+        }
+        if (getTransparent() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) TRANSPARENT);
+            appParamBuf.put((byte) TRANSPARENT_LEN);
+            appParamBuf.put((byte) getTransparent());
+        }
+        if (getRetry() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) RETRY);
+            appParamBuf.put((byte) RETRY_LEN);
+            appParamBuf.put((byte) getRetry());
+        }
+        if (getNewMessage() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) NEW_MESSAGE);
+            appParamBuf.put((byte) NEW_MESSAGE_LEN);
+            appParamBuf.put((byte) getNewMessage());
+        }
+        if (getNotificationStatus() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) NOTIFICATION_STATUS);
+            appParamBuf.put((byte) NOTIFICATION_STATUS_LEN);
+            appParamBuf.putShort((short) getNotificationStatus());
+        }
+        if (getMasInstanceId() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) MAS_INSTANCE_ID);
+            appParamBuf.put((byte) MAS_INSTANCE_ID_LEN);
+            appParamBuf.put((byte) getMasInstanceId());
+        }
+        if (getParameterMask() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) PARAMETER_MASK);
+            appParamBuf.put((byte) PARAMETER_MASK_LEN);
+            appParamBuf.putInt((int) getParameterMask());
+        }
+        if (getFolderListingSize() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) FOLDER_LISTING_SIZE);
+            appParamBuf.put((byte) FOLDER_LISTING_SIZE_LEN);
+            appParamBuf.putShort((short) getFolderListingSize());
+        }
+        if (getMessageListingSize() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) MESSAGE_LISTING_SIZE);
+            appParamBuf.put((byte) MESSAGE_LISTING_SIZE_LEN);
+            appParamBuf.putShort((short) getMessageListingSize());
+        }
+        if (getSubjectLength() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) SUBJECT_LENGTH);
+            appParamBuf.put((byte) SUBJECT_LENGTH_LEN);
+            appParamBuf.put((byte) getSubjectLength());
+        }
+        if (getCharset() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) CHARSET);
+            appParamBuf.put((byte) CHARSET_LEN);
+            appParamBuf.put((byte) getCharset());
+        }
+        if (getFractionRequest() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) FRACTION_REQUEST);
+            appParamBuf.put((byte) FRACTION_REQUEST_LEN);
+            appParamBuf.put((byte) getFractionRequest());
+        }
+        if (getFractionDeliver() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) FRACTION_DELIVER);
+            appParamBuf.put((byte) FRACTION_DELIVER_LEN);
+            appParamBuf.put((byte) getFractionDeliver());
+        }
+        if (getStatusIndicator() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) STATUS_INDICATOR);
+            appParamBuf.put((byte) STATUS_INDICATOR_LEN);
+            appParamBuf.put((byte) getStatusIndicator());
+        }
+        if (getStatusValue() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) STATUS_VALUE);
+            appParamBuf.put((byte) STATUS_VALUE_LEN);
+            appParamBuf.put((byte) getStatusValue());
+        }
+        if (getMseTime() != INVALID_VALUE_PARAMETER) {
+            appParamBuf.put((byte) MSE_TIME);
+            appParamBuf.put((byte) getMseTimeString().getBytes("UTF-8").length);
+            appParamBuf.put(getMseTimeString().getBytes("UTF-8"));
+        }
+        // We need to reduce the length of the array to match the content
+        retBuf = Arrays.copyOfRange(appParamBuf.array(), appParamBuf.arrayOffset(),
+                                    appParamBuf.arrayOffset() + appParamBuf.position());
+        return retBuf;
+    }
+
+    public int getMaxListCount() {
+        return maxListCount;
+    }
+
+    public void setMaxListCount(int maxListCount) throws IllegalArgumentException {
+        if (maxListCount < 0 || maxListCount > 0xFFFF)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFF");
+        this.maxListCount = maxListCount;
+    }
+
+    public int getStartOffset() {
+        return startOffset;
+    }
+
+    public void setStartOffset(int startOffset) throws IllegalArgumentException {
+        if (startOffset < 0 || startOffset > 0xFFFF)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFF");
+        this.startOffset = startOffset;
+    }
+
+    public int getFilterMessageType() {
+        return filterMessageType;
+    }
+
+    public void setFilterMessageType(int filterMessageType) throws IllegalArgumentException {
+        if (filterMessageType < 0 || filterMessageType > 0x000F)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x000F");
+        this.filterMessageType = filterMessageType;
+    }
+
+    public long getFilterPeriodBegin() {
+        return filterPeriodBegin;
+    }
+
+    public String getFilterPeriodBeginString() {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Date date = new Date(filterPeriodBegin);
+        return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+    }
+
+    public void setFilterPeriodBegin(long filterPeriodBegin) {
+        this.filterPeriodBegin = filterPeriodBegin;
+    }
+
+    public void setFilterPeriodBegin(String filterPeriodBegin) throws ParseException {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Date date = format.parse(filterPeriodBegin);
+        this.filterPeriodBegin = date.getTime();
+    }
+
+    public long getFilterPeriodEnd() {
+        return filterPeriodEnd;
+    }
+
+    public String getFilterPeriodEndString() {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Date date = new Date(filterPeriodEnd);
+        return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+    }
+
+    public void setFilterPeriodEnd(long filterPeriodEnd) {
+        this.filterPeriodEnd = filterPeriodEnd;
+    }
+
+    public void setFilterPeriodEnd(String filterPeriodEnd) throws ParseException {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Date date = format.parse(filterPeriodEnd);
+        this.filterPeriodEnd = date.getTime();
+    }
+
+    public int getFilterReadStatus() {
+        return filterReadStatus;
+    }
+
+    public void setFilterReadStatus(int filterReadStatus) throws IllegalArgumentException {
+        if (filterReadStatus < 0 || filterReadStatus > 0x0002)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0002");
+        this.filterReadStatus = filterReadStatus;
+    }
+
+    public String getFilterRecipient() {
+        return filterRecipient;
+    }
+
+    public void setFilterRecipient(String filterRecipient) {
+        this.filterRecipient = filterRecipient;
+    }
+
+    public String getFilterOriginator() {
+        return filterOriginator;
+    }
+
+    public void setFilterOriginator(String filterOriginator) {
+        this.filterOriginator = filterOriginator;
+    }
+
+    public int getFilterPriority() {
+        return filterPriority;
+    }
+
+    public void setFilterPriority(int filterPriority) throws IllegalArgumentException {
+        if (filterPriority < 0 || filterPriority > 0x0002)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0002");
+        this.filterPriority = filterPriority;
+    }
+
+    public int getAttachment() {
+        return attachment;
+    }
+
+    public void setAttachment(int attachment) throws IllegalArgumentException {
+        if (attachment < 0 || attachment > 0x0001)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        this.attachment = attachment;
+    }
+
+    public int getTransparent() {
+        return transparent;
+    }
+
+    public void setTransparent(int transparent) throws IllegalArgumentException {
+        if (transparent < 0 || transparent > 0x0001)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        this.transparent = transparent;
+    }
+
+    public int getRetry() {
+        return retry;
+    }
+
+    public void setRetry(int retry) throws IllegalArgumentException {
+        if (retry < 0 || retry > 0x0001)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        this.retry = retry;
+    }
+
+    public int getNewMessage() {
+        return newMessage;
+    }
+
+    public void setNewMessage(int newMessage) throws IllegalArgumentException {
+        if (newMessage < 0 || newMessage > 0x0001)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        this.newMessage = newMessage;
+    }
+
+    public int getNotificationStatus() {
+        return notificationStatus;
+    }
+
+    public void setNotificationStatus(int notificationStatus) throws IllegalArgumentException {
+        if (notificationStatus < 0 || notificationStatus > 0x0001)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        this.notificationStatus = notificationStatus;
+    }
+
+    public int getMasInstanceId() {
+        return masInstanceId;
+    }
+
+    public void setMasInstanceId(int masInstanceId) {
+        if (masInstanceId < 0 || masInstanceId > 0x00FF)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x00FF");
+        this.masInstanceId = masInstanceId;
+    }
+
+    public long getParameterMask() {
+        return parameterMask;
+    }
+
+    public void setParameterMask(long parameterMask) {
+        if (parameterMask < 0 || parameterMask > 0xFFFFFFFFL)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFFFFFF");
+        this.parameterMask = parameterMask;
+    }
+
+    public int getFolderListingSize() {
+        return folderListingSize;
+    }
+
+    public void setFolderListingSize(int folderListingSize) {
+        if (folderListingSize < 0 || folderListingSize > 0xFFFF)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFF");
+        this.folderListingSize = folderListingSize;
+    }
+
+    public int getMessageListingSize() {
+        return messageListingSize;
+    }
+
+    public void setMessageListingSize(int messageListingSize) {
+        if (messageListingSize < 0 || messageListingSize > 0xFFFF)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFF");
+        this.messageListingSize = messageListingSize;
+    }
+
+    public int getSubjectLength() {
+        return subjectLength;
+    }
+
+    public void setSubjectLength(int subjectLength) {
+        if (subjectLength < 0 || subjectLength > 0xFF)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x00FF");
+        this.subjectLength = subjectLength;
+    }
+
+    public int getCharset() {
+        return charset;
+    }
+
+    public void setCharset(int charset) {
+        if (charset < 0 || charset > 0x1)
+            throw new IllegalArgumentException("Out of range: " + charset + ", valid range is 0x0000 to 0x0001");
+        this.charset = charset;
+    }
+
+    public int getFractionRequest() {
+        return fractionRequest;
+    }
+
+    public void setFractionRequest(int fractionRequest) {
+        if (fractionRequest < 0 || fractionRequest > 0x1)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        this.fractionRequest = fractionRequest;
+    }
+
+    public int getFractionDeliver() {
+        return fractionDeliver;
+    }
+
+    public void setFractionDeliver(int fractionDeliver) {
+        if (fractionDeliver < 0 || fractionDeliver > 0x1)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        this.fractionDeliver = fractionDeliver;
+    }
+
+    public int getStatusIndicator() {
+        return statusIndicator;
+    }
+
+    public void setStatusIndicator(int statusIndicator) {
+        if (statusIndicator < 0 || statusIndicator > 0x1)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        this.statusIndicator = statusIndicator;
+    }
+
+    public int getStatusValue() {
+        return statusValue;
+    }
+
+    public void setStatusValue(int statusValue) {
+        if (statusValue < 0 || statusValue > 0x1)
+            throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        this.statusValue = statusValue;
+    }
+
+    public long getMseTime() {
+        return mseTime;
+    }
+
+    public String getMseTimeString() {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmssZ");
+        Date date = new Date(getMseTime());
+        return format.format(date); // Format to YYYYMMDDTHHMMSS±hhmm UTC time ± offset
+    }
+
+    public void setMseTime(long mseTime) {
+        this.mseTime = mseTime;
+    }
+
+    public void setMseTime(String mseTime) throws ParseException {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmssZ");
+        Date date = format.parse(mseTime);
+        this.mseTime = date.getTime();
+    }
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapAuthenticator.java b/src/com/android/bluetooth/map/BluetoothMapAuthenticator.java
new file mode 100644
index 0000000..12f64e0
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapAuthenticator.java
@@ -0,0 +1,88 @@
+/*
+* 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 android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import javax.obex.Authenticator;
+import javax.obex.PasswordAuthentication;
+
+/**
+ * BluetoothMapAuthenticator is a used by BluetoothObexServer for obex
+ * authentication procedure.
+ */
+public class BluetoothMapAuthenticator implements Authenticator {
+    private static final String TAG = "BluetoothMapAuthenticator";
+
+    private boolean mChallenged;
+
+    private boolean mAuthCancelled;
+
+    private String mSessionKey;
+
+    private Handler mCallback;
+
+    public BluetoothMapAuthenticator(final Handler callback) {
+        mCallback = callback;
+        mChallenged = false;
+        mAuthCancelled = false;
+        mSessionKey = null;
+    }
+
+    public final synchronized void setChallenged(final boolean bool) {
+        mChallenged = bool;
+    }
+
+    public final synchronized void setCancelled(final boolean bool) {
+        mAuthCancelled = bool;
+    }
+
+    public final synchronized void setSessionKey(final String string) {
+        mSessionKey = string;
+    }
+
+    private void waitUserConfirmation() {
+        Message msg = Message.obtain(mCallback);
+        msg.what = BluetoothMapService.MSG_OBEX_AUTH_CHALL;
+        msg.sendToTarget();
+        synchronized (this) {
+            while (!mChallenged && !mAuthCancelled) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Interrupted while waiting on isChalled");
+                }
+            }
+        }
+    }
+
+    public PasswordAuthentication onAuthenticationChallenge(final String description,
+            final boolean isUserIdRequired, final boolean isFullAccess) {
+        waitUserConfirmation();
+        if (mSessionKey.trim().length() != 0) {
+            PasswordAuthentication pa = new PasswordAuthentication(null, mSessionKey.getBytes());
+            return pa;
+        }
+        return null;
+    }
+
+    // TODO: Reserved for future use only, in case MSE challenge MCE
+    public byte[] onAuthenticationResponse(final byte[] userName) {
+        byte[] b = null;
+        return b;
+    }
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapContent.java b/src/com/android/bluetooth/map/BluetoothMapContent.java
new file mode 100644
index 0000000..531e90b
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -0,0 +1,1536 @@
+/*
+* Copyright (C) 2013 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.android.bluetooth.map;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+
+import org.apache.http.util.ByteArrayBuffer;
+
+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.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.Sms;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.google.android.mms.pdu.CharacterSets;
+
+public class BluetoothMapContent {
+    private static final String TAG = "BluetoothMapContent";
+
+    private static final boolean D = true;
+    private static final boolean V = true;
+
+    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;
+
+    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 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;
+
+    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;
+
+    /* 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;
+
+    private Context mContext;
+    private ContentResolver mResolver;
+
+    static final String[] SMS_PROJECTION = new String[] {
+        BaseColumns._ID,
+        Sms.THREAD_ID,
+        Sms.ADDRESS,
+        Sms.BODY,
+        Sms.DATE,
+        Sms.READ,
+        Sms.TYPE,
+        Sms.STATUS,
+        Sms.LOCKED,
+        Sms.ERROR_CODE,
+    };
+
+    static final String[] MMS_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,
+    };
+
+    private class FilterInfo {
+        public static final int TYPE_SMS = 0;
+        public static final int TYPE_MMS = 1;
+
+        int msgType = TYPE_SMS;
+        int phoneType = 0;
+        String phoneNum = null;
+        String phoneAlphaTag = null;
+    }
+
+    public BluetoothMapContent(final Context context) {
+        mContext = context;
+        mResolver = mContext.getContentResolver();
+        if (mResolver == null) {
+            Log.d(TAG, "getContentResolver failed");
+        }
+    }
+
+    private void addSmsEntry() {
+        Log.d(TAG, "*** Adding dummy sms ***");
+
+        ContentValues mVal = new ContentValues();
+        mVal.put(Sms.ADDRESS, "1234");
+        mVal.put(Sms.BODY, "Hello!!!");
+        mVal.put(Sms.DATE, System.currentTimeMillis());
+        mVal.put(Sms.READ, "0");
+
+        Uri mUri = mResolver.insert(Sms.CONTENT_URI, mVal);
+    }
+
+    private BluetoothMapAppParams buildAppParams() {
+        BluetoothMapAppParams ap = new BluetoothMapAppParams();
+        try {
+            int paramMask = (MASK_SUBJECT
+                | MASK_DATETIME
+                | MASK_SENDER_NAME
+                | MASK_SENDER_ADDRESSING
+                | MASK_RECIPIENT_NAME
+                | MASK_RECIPIENT_ADDRESSING
+                | MASK_TYPE
+                | MASK_SIZE
+                | MASK_RECEPTION_STATUS
+                | MASK_TEXT
+                | MASK_ATTACHMENT_SIZE
+                | MASK_PRIORITY
+                | MASK_READ
+                | MASK_SENT
+                | MASK_PROTECTED
+                );
+            ap.setMaxListCount(5);
+            ap.setStartOffset(0);
+            ap.setFilterMessageType(0);
+            ap.setFilterPeriodBegin("20130101T000000");
+            ap.setFilterPeriodEnd("20131230T000000");
+            ap.setFilterReadStatus(0);
+            ap.setParameterMask(paramMask);
+            ap.setSubjectLength(10);
+            /* ap.setFilterOriginator("Sms*"); */
+            /* ap.setFilterRecipient("41*"); */
+        } catch (ParseException e) {
+            return null;
+        }
+        return ap;
+    }
+
+    private void printSms(Cursor c) {
+        String body = c.getString(c.getColumnIndex(Sms.BODY));
+        if (D) Log.d(TAG, "printSms " + BaseColumns._ID + ": " + c.getLong(c.getColumnIndex(BaseColumns._ID)) +
+                " " + Sms.THREAD_ID + " : " + c.getLong(c.getColumnIndex(Sms.THREAD_ID)) +
+                " " + Sms.ADDRESS + " : " + c.getString(c.getColumnIndex(Sms.ADDRESS)) +
+                " " + Sms.BODY + " : " + body.substring(0, Math.min(body.length(), 8)) +
+                " " + Sms.DATE + " : " + c.getLong(c.getColumnIndex(Sms.DATE)) +
+                " " + Sms.TYPE + " : " + c.getInt(c.getColumnIndex(Sms.TYPE)));
+    }
+
+    private void printMms(Cursor c) {
+        if (D) Log.d(TAG, "printMms " + BaseColumns._ID + ": " + c.getLong(c.getColumnIndex(BaseColumns._ID)) +
+                "\n   " + Mms.THREAD_ID + " : " + c.getLong(c.getColumnIndex(Mms.THREAD_ID)) +
+                "\n   " + Mms.MESSAGE_ID + " : " + c.getString(c.getColumnIndex(Mms.MESSAGE_ID)) +
+                "\n   " + Mms.SUBJECT + " : " + c.getString(c.getColumnIndex(Mms.SUBJECT)) +
+                "\n   " + Mms.CONTENT_TYPE + " : " + c.getString(c.getColumnIndex(Mms.CONTENT_TYPE)) +
+                "\n   " + Mms.TEXT_ONLY + " : " + c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) +
+                "\n   " + Mms.DATE + " : " + c.getLong(c.getColumnIndex(Mms.DATE)) +
+                "\n   " + Mms.DATE_SENT + " : " + c.getLong(c.getColumnIndex(Mms.DATE_SENT)) +
+                "\n   " + Mms.READ + " : " + c.getInt(c.getColumnIndex(Mms.READ)) +
+                "\n   " + Mms.MESSAGE_BOX + " : " + c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)) +
+                "\n   " + Mms.STATUS + " : " + c.getInt(c.getColumnIndex(Mms.STATUS)));
+    }
+
+    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) {
+                    Log.d(TAG, "   recipient: " + add + " (type: " + type + ")");
+                } else if (type == MMS_FROM) {
+                    Log.d(TAG, "   originator: " + add + " (type: " + type + ")");
+                } else {
+                    Log.d(TAG, "   address other: " + add + " (type: " + type + ")");
+                }
+
+            } 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);
+            }
+            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);
+
+        Log.d(TAG, "   parts:");
+        if (c.moveToFirst()) {
+            do {
+                Long partid = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                String ct = c.getString(c.getColumnIndex("ct"));
+                String name = c.getString(c.getColumnIndex("name"));
+                String charset = c.getString(c.getColumnIndex("chset"));
+                String filename = c.getString(c.getColumnIndex("fn"));
+                String text = c.getString(c.getColumnIndex("text"));
+                Integer fd = c.getInt(c.getColumnIndex("_data"));
+
+                Log.d(TAG, "     _id : " + partid +
+                    "\n     ct : " + ct +
+                    "\n     partname : " + name +
+                    "\n     charset : " + charset +
+                    "\n     filename : " + filename +
+                    "\n     text : " + text +
+                    "\n     fd : " + fd);
+
+                /* if (ct.equals("image/jpeg")) { */
+                /*     printMmsPartImage(partid); */
+                /* } */
+            } while(c.moveToNext());
+        }
+    }
+
+    public void dumpMmsTable() {
+        Log.d(TAG, "**** Dump of mms table ****");
+        Cursor c = mResolver.query(Mms.CONTENT_URI,
+                MMS_PROJECTION, null, null, "_id DESC");
+        if (c != null) {
+            Log.d(TAG, "c.getCount() = " + c.getCount());
+            c.moveToPosition(-1);
+            while (c.moveToNext()) {
+                printMms(c);
+                long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                printMmsAddr(id);
+                printMmsParts(id);
+            }
+        } else {
+            Log.d(TAG, "query failed");
+            c.close();
+        }
+    }
+
+    public void dumpSmsTable() {
+        addSmsEntry();
+        Log.d(TAG, "**** Dump of sms table ****");
+        Cursor c = mResolver.query(Sms.CONTENT_URI,
+                SMS_PROJECTION, null, null, "_id DESC");
+        if (c != null) {
+            Log.d(TAG, "c.getCount() = " + c.getCount());
+            c.moveToPosition(-1);
+            while (c.moveToNext()) {
+                printSms(c);
+            }
+        } else {
+            Log.d(TAG, "query failed");
+            c.close();
+        }
+
+    }
+
+    public void dumpMessages() {
+        dumpSmsTable();
+        dumpMmsTable();
+
+        BluetoothMapAppParams ap = buildAppParams();
+        Log.d(TAG, "message listing size = " + msgListingSize("inbox", ap));
+        BluetoothMapMessageListing mList = msgListing("inbox", ap);
+        try {
+            mList.encode();
+        } catch (UnsupportedEncodingException ex) {
+            /* do nothing */
+        }
+        mList = msgListing("sent", ap);
+        try {
+            mList.encode();
+        } catch (UnsupportedEncodingException ex) {
+            /* do nothing */
+        }
+    }
+
+    private void setProtected(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        if ((ap.getParameterMask() & MASK_PROTECTED) != 0) {
+            String protect = "no";
+            Log.d(TAG, "setProtected: " + protect);
+            e.setProtect(protect);
+        }
+    }
+
+    private void setSent(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        if ((ap.getParameterMask() & MASK_SENT) != 0) {
+            int msgType = 0;
+            if (fi.msgType == FilterInfo.TYPE_SMS) {
+                msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
+            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+                msgType = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
+            }
+            String sent = null;
+            if (msgType == 2) {
+                sent = "yes";
+            } else {
+                sent = "no";
+            }
+            Log.d(TAG, "setSent: " + sent);
+            e.setSent(sent);
+        }
+    }
+
+    private void setRead(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        if ((ap.getParameterMask() & MASK_READ) != 0) {
+            int read = 0;
+            if (fi.msgType == FilterInfo.TYPE_SMS) {
+                read = c.getInt(c.getColumnIndex(Sms.READ));
+            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+                read = c.getInt(c.getColumnIndex(Mms.READ));
+            }
+            String setread = null;
+            if (read == 1) {
+                setread = "yes";
+            } else {
+                setread = "no";
+            }
+            Log.d(TAG, "setRead: " + setread);
+            e.setRead(setread);
+        }
+    }
+
+    private void setPriority(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        if ((ap.getParameterMask() & MASK_PRIORITY) != 0) {
+            String priority = "no";
+            Log.d(TAG, "setPriority: " + priority);
+            e.setPriority(priority);
+        }
+    }
+
+    private void setAttachmentSize(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        if ((ap.getParameterMask() & MASK_ATTACHMENT_SIZE) != 0) {
+            int size = 0;
+            Log.d(TAG, "setAttachmentSize: " + size);
+            e.setAttachmentSize(size);
+        }
+    }
+
+    private void setText(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        if ((ap.getParameterMask() & MASK_TEXT) != 0) {
+            String hasText = "";
+            if (fi.msgType == FilterInfo.TYPE_SMS) {
+                hasText = "yes";
+            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+                int textOnly = c.getInt(c.getColumnIndex(Mms.TEXT_ONLY));
+                if (textOnly == 1) {
+                    hasText = "yes";
+                } else {
+                    long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                    String text = getTextPartsMms(id);
+                    if (text != null && text.length() > 0) {
+                        hasText = "yes";
+                    } else {
+                        hasText = "no";
+                    }
+                }
+            }
+            Log.d(TAG, "setText: " + hasText);
+            e.setText(hasText);
+        }
+    }
+
+    private void setReceptionStatus(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        if ((ap.getParameterMask() & MASK_RECEPTION_STATUS) != 0) {
+            String status = "complete";
+            Log.d(TAG, "setReceptionStatus: " + status);
+            e.setReceptionStatus(status);
+        }
+    }
+
+    private void setSize(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        if ((ap.getParameterMask() & MASK_SIZE) != 0) {
+            int size = 0;
+            if (fi.msgType == FilterInfo.TYPE_SMS) {
+                String subject = c.getString(c.getColumnIndex(Sms.BODY));
+                size = subject.length();
+            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+                size = c.getInt(c.getColumnIndex(Mms.MESSAGE_SIZE));
+            }
+            Log.d(TAG, "setSize: " + size);
+            e.setSize(size);
+        }
+    }
+
+    private void setType(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        if ((ap.getParameterMask() & MASK_TYPE) != 0) {
+            TYPE type = null;
+            if (fi.msgType == FilterInfo.TYPE_SMS) {
+                if (fi.phoneType == TelephonyManager.PHONE_TYPE_GSM) {
+                    type = TYPE.SMS_GSM;
+                } else if (fi.phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
+                    type = TYPE.SMS_CDMA;
+                }
+            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+                type = TYPE.MMS;
+            }
+            Log.d(TAG, "setType: " + type);
+            e.setType(type);
+        }
+    }
+
+    private void setRecipientAddressing(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        if ((ap.getParameterMask() & MASK_RECIPIENT_ADDRESSING) != 0) {
+            String address = null;
+            if (fi.msgType == FilterInfo.TYPE_SMS) {
+                int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
+                if (msgType == 1) {
+                    address = fi.phoneNum;
+                } else {
+                    address = c.getString(c.getColumnIndex(Sms.ADDRESS));
+                }
+            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+                long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                address = getAddressMms(mResolver, id, MMS_TO);
+            }
+            Log.d(TAG, "setRecipientAddressing: " + address);
+            e.setRecipientAddressing(address);
+        }
+    }
+
+    private void setRecipientName(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        if ((ap.getParameterMask() & MASK_RECIPIENT_NAME) != 0) {
+            String name = null;
+            if (fi.msgType == FilterInfo.TYPE_SMS) {
+                int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
+                if (msgType != 1) {
+                    String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
+                    name = getContactNameFromPhone(phone);
+                } else {
+                    name = fi.phoneAlphaTag;
+                }
+            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+                long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                String phone = getAddressMms(mResolver, id, MMS_TO);
+                name = getContactNameFromPhone(phone);
+            }
+            Log.d(TAG, "setRecipientName: " + name);
+            e.setRecipientName(name);
+        }
+    }
+
+    private void setSenderAddressing(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        if ((ap.getParameterMask() & MASK_SENDER_ADDRESSING) != 0) {
+            String address = null;
+            if (fi.msgType == FilterInfo.TYPE_SMS) {
+                int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
+                if (msgType == 1) {
+                    address = c.getString(c.getColumnIndex(Sms.ADDRESS));
+                } else {
+                    address = fi.phoneNum;
+                }
+            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+                long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                address = getAddressMms(mResolver, id, MMS_FROM);
+            }
+            Log.d(TAG, "setSenderAddressing: " + address);
+            e.setSenderAddressing(address);
+        }
+    }
+
+    private void setSenderName(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        if ((ap.getParameterMask() & MASK_SENDER_NAME) != 0) {
+            String name = null;
+            if (fi.msgType == FilterInfo.TYPE_SMS) {
+                int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
+                if (msgType == 1) {
+                    String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
+                    name = getContactNameFromPhone(phone);
+                } else {
+                    name = fi.phoneAlphaTag;
+                }
+            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+                long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                String phone = getAddressMms(mResolver, id, MMS_FROM);
+                name = getContactNameFromPhone(phone);
+            }
+            Log.d(TAG, "setSenderName: " + name);
+            e.setSenderName(name);
+        }
+    }
+
+    private void setDateTime(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        long date = 0;
+
+        if (fi.msgType == FilterInfo.TYPE_SMS) {
+            date = c.getLong(c.getColumnIndex(Sms.DATE));
+        } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+            /* Use Mms.DATE for all messages. Although contract class states */
+            /* Mms.DATE_SENT are for outgoing messages. But that is not working. */
+            date = c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L;
+
+            /* int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); */
+            /* if (msgBox == Mms.MESSAGE_BOX_INBOX) { */
+            /*     date = c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L; */
+            /* } else { */
+            /*     date = c.getLong(c.getColumnIndex(Mms.DATE_SENT)) * 1000L; */
+            /* } */
+        }
+        e.setDateTime(date);
+    }
+
+    private String getTextPartsMms(long id) {
+        String text = "";
+        String selection = new String("mid=" + id);
+        String uriStr = String.format("content://mms/%d/part", id);
+        Uri uriAddress = Uri.parse(uriStr);
+        Cursor c = mResolver.query(uriAddress, null, selection,
+            null, null);
+
+        if (c != null && c.moveToFirst()) {
+            do {
+                String ct = c.getString(c.getColumnIndex("ct"));
+                if (ct.equals("text/plain")) {
+                    text += c.getString(c.getColumnIndex("text"));
+                }
+            } while(c.moveToNext());
+        }
+        if (c != null) {
+            c.close();
+        }
+        return text;
+    }
+
+    private void setSubject(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        String subject = "";
+        int subLength = ap.getSubjectLength();
+        if(subLength == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+            subLength = 256;
+
+        if ((ap.getParameterMask() & MASK_SUBJECT) != 0) {
+            if (fi.msgType == FilterInfo.TYPE_SMS) {
+                subject = c.getString(c.getColumnIndex(Sms.BODY));
+            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+                subject = c.getString(c.getColumnIndex(Mms.SUBJECT));
+                if (subject == null || subject.length() == 0) {
+                    /* Get subject from mms text body parts - if any exists */
+                    long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                    subject = getTextPartsMms(id);
+                }
+            }
+            if (subject != null) {
+                subject = subject.substring(0, Math.min(subject.length(),
+                    subLength));
+            }
+            Log.d(TAG, "setSubject: " + subject);
+            e.setSubject(subject);
+        }
+    }
+
+    private void setHandle(BluetoothMapMessageListingElement e, Cursor c,
+        FilterInfo fi, BluetoothMapAppParams ap) {
+        long handle = c.getLong(c.getColumnIndex(BaseColumns._ID));
+        Log.d(TAG, "setHandle: " + handle);
+        e.setHandle(handle);
+    }
+
+    private BluetoothMapMessageListingElement element(Cursor c, FilterInfo fi,
+        BluetoothMapAppParams ap) {
+        BluetoothMapMessageListingElement e = new BluetoothMapMessageListingElement();
+
+        setHandle(e, c, fi, ap);
+        setSubject(e, c, fi, ap);
+        setDateTime(e, c, fi, ap);
+        setSenderName(e, c, fi, ap);
+        setSenderAddressing(e, c, fi, ap);
+        setRecipientName(e, c, fi, ap);
+        setRecipientAddressing(e, c, fi, ap);
+        setType(e, c, fi, ap);
+        setSize(e, c, fi, ap);
+        setReceptionStatus(e, c, fi, ap);
+        setText(e, c, fi, ap);
+        setAttachmentSize(e, c, fi, ap);
+        setPriority(e, c, fi, ap);
+        setRead(e, c, fi, ap);
+        setSent(e, c, fi, ap);
+        setProtected(e, c, fi, ap);
+        return e;
+    }
+
+    private String getContactNameFromPhone(String phone) {
+        String name = null;
+
+        Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
+            Uri.encode(phone));
+
+        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);
+
+        if (c != null && c.getCount() >= 1) {
+            c.moveToFirst();
+            name = c.getString(c.getColumnIndex(Contacts.DISPLAY_NAME));
+        }
+
+        c.close();
+        return name;
+    }
+
+    static public String getAddressMms(ContentResolver r, long id, int type) {
+        String selection = new String("msg_id=" + id + " AND type=" + type);
+        String uriStr = String.format("content://mms/%d/addr", id);
+        Uri uriAddress = Uri.parse(uriStr);
+        String addr = null;
+        Cursor c = r.query(uriAddress, null, selection, null, null);
+
+        if (c != null && c.moveToFirst()) {
+            addr = c.getString(c.getColumnIndex("address"));
+        }
+
+        if (c != null) {
+            c.close();
+        }
+        return addr;
+    }
+
+    private boolean matchRecipientMms(Cursor c, FilterInfo fi, String recip) {
+        boolean res;
+        long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+        String phone = getAddressMms(mResolver, id, MMS_TO);
+        if (phone != null && phone.length() > 0) {
+            if (phone.matches(recip)) {
+                Log.d(TAG, "match recipient phone = " + phone);
+                res = true;
+            } else {
+                String name = getContactNameFromPhone(phone);
+                if (name != null && name.length() > 0 && name.matches(recip)) {
+                    Log.d(TAG, "match recipient name = " + name);
+                    res = true;
+                } else {
+                    res = false;
+                }
+            }
+        } else {
+            res = false;
+        }
+        return res;
+    }
+
+    private boolean matchRecipientSms(Cursor c, FilterInfo fi, String recip) {
+        boolean res;
+        int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
+        if (msgType == 1) {
+            String phone = fi.phoneNum;
+            String name = fi.phoneAlphaTag;
+            if (phone != null && phone.length() > 0 && phone.matches(recip)) {
+                Log.d(TAG, "match recipient phone = " + phone);
+                res = true;
+            } else if (name != null && name.length() > 0 && name.matches(recip)) {
+                Log.d(TAG, "match recipient name = " + name);
+                res = true;
+            } else {
+                res = false;
+            }
+        }
+        else {
+            String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
+            if (phone != null && phone.length() > 0) {
+                if (phone.matches(recip)) {
+                    Log.d(TAG, "match recipient phone = " + phone);
+                    res = true;
+                } else {
+                    String name = getContactNameFromPhone(phone);
+                    if (name != null && name.length() > 0 && name.matches(recip)) {
+                        Log.d(TAG, "match recipient name = " + name);
+                        res = true;
+                    } else {
+                        res = false;
+                    }
+                }
+            } else {
+                res = false;
+            }
+        }
+        return res;
+    }
+
+    private boolean matchRecipient(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) {
+        boolean res;
+        String recip = ap.getFilterRecipient();
+        if (recip != null && recip.length() > 0) {
+            recip = recip.replace("*", ".*");
+            recip = ".*" + recip + ".*";
+            if (fi.msgType == FilterInfo.TYPE_SMS) {
+                res = matchRecipientSms(c, fi, recip);
+            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+                res = matchRecipientMms(c, fi, recip);
+            } else {
+                Log.d(TAG, "Unknown msg type: " + fi.msgType);
+                res = false;
+            }
+        } else {
+            res = true;
+        }
+        return res;
+    }
+
+    private boolean matchOriginatorMms(Cursor c, FilterInfo fi, String orig) {
+        boolean res;
+        long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+        String phone = getAddressMms(mResolver, id, MMS_FROM);
+        if (phone != null && phone.length() > 0) {
+            if (phone.matches(orig)) {
+                Log.d(TAG, "match originator phone = " + phone);
+                res = true;
+            } else {
+                String name = getContactNameFromPhone(phone);
+                if (name != null && name.length() > 0 && name.matches(orig)) {
+                    Log.d(TAG, "match originator name = " + name);
+                    res = true;
+                } else {
+                    res = false;
+                }
+            }
+        } else {
+            res = false;
+        }
+        return res;
+    }
+
+    private boolean matchOriginatorSms(Cursor c, FilterInfo fi, String orig) {
+        boolean res;
+        int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
+        if (msgType == 1) {
+            String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
+            if (phone !=null && phone.length() > 0) {
+                if (phone.matches(orig)) {
+                    Log.d(TAG, "match originator phone = " + phone);
+                    res = true;
+                } else {
+                    String name = getContactNameFromPhone(phone);
+                    if (name != null && name.length() > 0 && name.matches(orig)) {
+                        Log.d(TAG, "match originator name = " + name);
+                        res = true;
+                    } else {
+                        res = false;
+                    }
+                }
+            } else {
+                res = false;
+            }
+        }
+        else {
+            String phone = fi.phoneNum;
+            String name = fi.phoneAlphaTag;
+            if (phone != null && phone.length() > 0 && phone.matches(orig)) {
+                Log.d(TAG, "match originator phone = " + phone);
+                res = true;
+            } else if (name != null && name.length() > 0 && name.matches(orig)) {
+                Log.d(TAG, "match originator name = " + name);
+                res = true;
+            } else {
+                res = false;
+            }
+        }
+        return res;
+    }
+
+   private boolean matchOriginator(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) {
+        boolean res;
+        String orig = ap.getFilterOriginator();
+        if (orig != null && orig.length() > 0) {
+            orig = orig.replace("*", ".*");
+            orig = ".*" + orig + ".*";
+            if (fi.msgType == FilterInfo.TYPE_SMS) {
+                res = matchOriginatorSms(c, fi, orig);
+            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+                res = matchOriginatorMms(c, fi, orig);
+            } else {
+                Log.d(TAG, "Unknown msg type: " + fi.msgType);
+                res = false;
+            }
+        } else {
+            res = true;
+        }
+        return res;
+    }
+
+    private boolean matchAddresses(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) {
+        if (matchOriginator(c, fi, ap) && matchRecipient(c, fi, ap)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private String setWhereFilterFolderTypeSms(String folder) {
+        String where = "";
+        if ("inbox".equalsIgnoreCase(folder)) {
+            where = "type = 1 AND thread_id <> -1";
+        }
+        else if ("outbox".equalsIgnoreCase(folder)) {
+            where = "(type = 4 OR type = 5 OR type = 6) AND thread_id <> -1";
+        }
+        else if ("sent".equalsIgnoreCase(folder)) {
+            where = "type = 2 AND thread_id <> -1";
+        }
+        else if ("draft".equalsIgnoreCase(folder)) {
+            where = "type = 3 AND thread_id <> -1";
+        }
+        else if ("deleted".equalsIgnoreCase(folder)) {
+            where = "thread_id = -1";
+        }
+
+        return where;
+    }
+
+    private String setWhereFilterFolderTypeMms(String folder) {
+        String where = "";
+        if ("inbox".equalsIgnoreCase(folder)) {
+            where = "msg_box = 1 AND thread_id <> -1";
+        }
+        else if ("outbox".equalsIgnoreCase(folder)) {
+            where = "msg_box = 4 AND thread_id <> -1";
+        }
+        else if ("sent".equalsIgnoreCase(folder)) {
+            where = "msg_box = 2 AND thread_id <> -1";
+        }
+        else if ("draft".equalsIgnoreCase(folder)) {
+            where = "msg_box = 3 AND thread_id <> -1";
+        }
+        else if ("deleted".equalsIgnoreCase(folder)) {
+            where = "thread_id = -1";
+        }
+
+        return where;
+    }
+
+    private String setWhereFilterFolderType(String folder, FilterInfo fi) {
+        String where = "";
+        if (fi.msgType == FilterInfo.TYPE_SMS) {
+            where = setWhereFilterFolderTypeSms(folder);
+        } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+            where = setWhereFilterFolderTypeMms(folder);
+        }
+
+        return where;
+    }
+
+    private String setWhereFilterReadStatus(BluetoothMapAppParams ap) {
+        String where = "";
+        if (ap.getFilterReadStatus() != -1) {
+            if ((ap.getFilterReadStatus() & 0x01) != 0) {
+                where = " AND read=0 ";
+            }
+
+            if ((ap.getFilterReadStatus() & 0x02) != 0) {
+                where = " AND read=1 ";
+            }
+        }
+
+        return where;
+    }
+
+    private String setWhereFilterPeriod(BluetoothMapAppParams ap, FilterInfo fi) {
+        String where = "";
+        if ((ap.getFilterPeriodBegin() != -1)) {
+            if (fi.msgType == FilterInfo.TYPE_SMS) {
+            where = " AND date >= " + ap.getFilterPeriodBegin();
+            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+                where = " AND date >= " + (ap.getFilterPeriodBegin() / 1000L);
+            }
+        }
+
+        if ((ap.getFilterPeriodEnd() != -1)) {
+            if (fi.msgType == FilterInfo.TYPE_SMS) {
+            where += " AND date < " + ap.getFilterPeriodEnd();
+            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
+                where += " AND date < " + (ap.getFilterPeriodEnd() / 1000L);
+            }
+        }
+
+        return where;
+    }
+
+    private String setWhereFilterPhones(String str) {
+        String where = "";
+        str = str.replace("*", "%");
+
+        Cursor c = mResolver.query(ContactsContract.Contacts.CONTENT_URI, null,
+            ContactsContract.Contacts.DISPLAY_NAME + " like ?",
+            new String[]{str},
+            ContactsContract.Contacts.DISPLAY_NAME + " ASC");
+
+        while (c != null && c.moveToNext()) {
+            String contactId = c.getString(c.getColumnIndex(ContactsContract.Contacts._ID));
+
+            Cursor p = mResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
+                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
+                new String[]{contactId},
+                null);
+
+            while (p != null && p.moveToNext()) {
+                String number = p.getString(
+                    p.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
+
+                where += " address = " + "'" + number + "'";
+                if (!p.isLast()) {
+                    where += " OR ";
+                }
+            }
+            if (!c.isLast()) {
+                where += " OR ";
+            }
+            p.close();
+        }
+        c.close();
+
+        if (str != null && str.length() > 0) {
+            if (where.length() > 0) {
+                where += " OR ";
+            }
+            where += " address like " + "'" + str + "'";
+        }
+
+        return where;
+    }
+
+    private String setWhereFilterOriginator(BluetoothMapAppParams ap,
+        FilterInfo fi) {
+        String where = "";
+        String orig = ap.getFilterOriginator();
+
+        if (orig != null && orig.length() > 0) {
+            String phones = setWhereFilterPhones(orig);
+
+            if (phones.length() > 0) {
+                where = " AND ((type <> 1) OR ( " + phones + " ))";
+            } else {
+                where = " AND (type <> 1)";
+            }
+
+            orig = orig.replace("*", ".*");
+            orig = ".*" + orig + ".*";
+
+            boolean localPhoneMatchOrig = false;
+            if (fi.phoneNum != null && fi.phoneNum.length() > 0
+                && fi.phoneNum.matches(orig)) {
+                localPhoneMatchOrig = true;
+            }
+
+            if (fi.phoneAlphaTag != null && fi.phoneAlphaTag.length() > 0
+                && fi.phoneAlphaTag.matches(orig)) {
+                localPhoneMatchOrig = true;
+            }
+
+            if (!localPhoneMatchOrig) {
+                where += " AND (type = 1)";
+            }
+        }
+
+        return where;
+    }
+
+    private String setWhereFilterRecipient(BluetoothMapAppParams ap,
+        FilterInfo fi) {
+        String where = "";
+        String recip = ap.getFilterRecipient();
+
+        if (recip != null && recip.length() > 0) {
+            String phones = setWhereFilterPhones(recip);
+
+            if (phones.length() > 0) {
+                where = " AND ((type = 1) OR ( " + phones + " ))";
+            } else {
+                where = " AND (type = 1)";
+            }
+
+            recip = recip.replace("*", ".*");
+            recip = ".*" + recip + ".*";
+
+            boolean localPhoneMatchOrig = false;
+            if (fi.phoneNum != null && fi.phoneNum.length() > 0
+                && fi.phoneNum.matches(recip)) {
+                localPhoneMatchOrig = true;
+            }
+
+            if (fi.phoneAlphaTag != null && fi.phoneAlphaTag.length() > 0
+                && fi.phoneAlphaTag.matches(recip)) {
+                localPhoneMatchOrig = true;
+            }
+
+            if (!localPhoneMatchOrig) {
+                where += " AND (type <> 1)";
+            }
+        }
+
+        return where;
+    }
+
+    private String setWhereFilter(String folder, FilterInfo fi, BluetoothMapAppParams ap) {
+        String where = "";
+
+        where += setWhereFilterFolderType(folder, fi);
+        where += setWhereFilterReadStatus(ap);
+        where += setWhereFilterPeriod(ap, fi);
+        /* where += setWhereFilterOriginator(ap, fi); */
+        /* where += setWhereFilterRecipient(ap, fi); */
+
+        Log.d(TAG, "where: " + where);
+
+        return where;
+    }
+
+    private boolean smsSelected(FilterInfo fi, BluetoothMapAppParams ap) {
+        int msgType = ap.getFilterMessageType();
+        int phoneType = fi.phoneType;
+
+        if (msgType == -1)
+            return true;
+        if ((msgType & 0x03) == 0)
+            return true;
+
+        if (((msgType & 0x01) == 0) && (phoneType == TelephonyManager.PHONE_TYPE_GSM))
+            return true;
+
+        if (((msgType & 0x02) == 0) && (phoneType == TelephonyManager.PHONE_TYPE_CDMA))
+            return true;
+
+        return false;
+    }
+
+    private boolean mmsSelected(FilterInfo fi, BluetoothMapAppParams ap) {
+        int msgType = ap.getFilterMessageType();
+
+        if (msgType == -1)
+            return true;
+
+        if ((msgType & 0x08) == 0)
+            return true;
+
+        return false;
+    }
+
+    private void setFilterInfo(FilterInfo fi) {
+        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        if (tm != null) {
+            fi.phoneType = tm.getPhoneType();
+            fi.phoneNum = tm.getLine1Number();
+            fi.phoneAlphaTag = tm.getLine1AlphaTag();
+            Log.d(TAG, "phone type = " + fi.phoneType +
+                " phone num = " + fi.phoneNum +
+                " phone alpha tag = " + fi.phoneAlphaTag);
+        }
+    }
+
+    public BluetoothMapMessageListing msgListing(String folder, BluetoothMapAppParams ap) {
+        Log.d(TAG, "msgListing: folder = " + folder);
+        BluetoothMapMessageListing bmList = new BluetoothMapMessageListing();
+        BluetoothMapMessageListingElement e = null;
+
+        /* Cache some info used throughout filtering */
+        FilterInfo fi = new FilterInfo();
+        setFilterInfo(fi);
+
+        if (smsSelected(fi, ap)) {
+            fi.msgType = FilterInfo.TYPE_SMS;
+
+            String where = setWhereFilter(folder, fi, ap);
+
+            Cursor c = mResolver.query(Sms.CONTENT_URI,
+                SMS_PROJECTION, where, null, "date DESC");
+
+            if (c != null) {
+                while (c.moveToNext()) {
+                    if (matchAddresses(c, fi, ap)) {
+                        printSms(c);
+                        e = element(c, fi, ap);
+                        bmList.add(e);
+                    }
+                }
+                c.close();
+            }
+        }
+
+        if (mmsSelected(fi, ap)) {
+            fi.msgType = FilterInfo.TYPE_MMS;
+
+            String where = setWhereFilter(folder, fi, ap);
+
+            Cursor c = mResolver.query(Mms.CONTENT_URI,
+                MMS_PROJECTION, where, null, "date DESC");
+
+            if (c != null) {
+                int cnt = 0;
+                while (c.moveToNext()) {
+                    if (matchAddresses(c, fi, ap)) {
+                        printMms(c);
+                        e = element(c, fi, ap);
+                        bmList.add(e);
+                    }
+                }
+                c.close();
+            }
+        }
+
+        /* Enable this if post sorting and segmenting needed */
+        bmList.sort();
+        bmList.segment(ap.getMaxListCount(), ap.getStartOffset());
+
+        return bmList;
+    }
+
+    public int msgListingSize(String folder, BluetoothMapAppParams ap) {
+        Log.d(TAG, "msgListingSize: folder = " + folder);
+        int cnt = 0;
+
+        /* Cache some info used throughout filtering */
+        FilterInfo fi = new FilterInfo();
+        setFilterInfo(fi);
+
+        if (smsSelected(fi, ap)) {
+            String where = setWhereFilter(folder, fi, ap);
+
+            Cursor c = mResolver.query(Sms.CONTENT_URI,
+                SMS_PROJECTION, where, null, "date DESC");
+
+            if (c != null) {
+                cnt = c.getCount();
+                c.close();
+            }
+        }
+        Log.d(TAG, "msgListingSize: size = " + cnt);
+        return cnt;
+    }
+
+    /**
+     * Get the folder name of an SMS message or MMS message.
+     * @param c the cursor pointing at the message
+     * @return the folder name.
+     */
+    private String getFolderName(int type, int threadId) {
+
+        if(threadId == -1)
+            return "deleted";
+
+        switch(type) {
+        case 1:
+            return "inbox";
+        case 2:
+            return "send";
+        case 3:
+            return "draft";
+        case 4: // Just name outbox, failed and queued "outbox"
+        case 5:
+        case 6:
+            return "outbox";
+        }
+        return "";
+    }
+
+    public byte[] getMessage(String handle, int charset) throws UnsupportedEncodingException{
+        TYPE type = BluetoothMapUtils.getMsgTypeFromHandle(handle);
+        long id = BluetoothMapUtils.getCpHandle(handle);
+        switch(type) {
+        case SMS_GSM:
+        case SMS_CDMA:
+            return getSmsMessage(id, charset);
+        case MMS:
+            return getMmsMessage(id);
+        case EMAIL:
+            throw new IllegalArgumentException("Email not implemented - invalid message handle.");
+        }
+        throw new IllegalArgumentException("Invalid message handle.");
+    }
+
+    private void 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));
+
+        String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME};
+        String selection = Contacts.IN_VISIBLE_GROUP + "=1";
+        String orderBy = Contacts._ID + " ASC";
+
+        // Get the contact _ID and name
+        p = mResolver.query(uri, projection, selection, null, orderBy);
+        if (p != null && p.getCount() >= 1) {
+            p.moveToFirst();
+            contactId = p.getString(p.getColumnIndex(Contacts._ID));
+            contactName = p.getString(p.getColumnIndex(Contacts.DISPLAY_NAME));
+        }
+        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 {
+            // Fetch all contact phone numbers
+            p = mResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
+                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
+                new String[]{contactId},
+                null);
+            if(p != null) {
+                int i = 0;
+                phoneNumbers = new String[p.getCount()];
+                while (p != null && p.moveToNext()) {
+                    String number = p.getString(
+                        p.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
+                    phoneNumbers[i++] = number;
+                }
+                p.close();
+            }
+
+            // Fetch contact e-mail addresses
+            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;
+                }
+                p.close();
+            }
+        }
+        if(incoming == true)
+            message.addOriginator(contactName, contactName, phoneNumbers, emailAddresses); // Use version 3.0 as we only have a formatted name
+        else
+            message.addRecipient(contactName, contactName, phoneNumbers, emailAddresses); // Use version 3.0 as we only have a formatted name
+    }
+
+    public static final int MAP_MESSAGE_CHARSET_NATIVE = 0;
+    public static final int MAP_MESSAGE_CHARSET_UTF8 = 1;
+
+    public byte[] getSmsMessage(long id, int charset) throws UnsupportedEncodingException{
+        int type, threadId;
+        long time = -1;
+        String msgBody;
+        BluetoothMapbMessageSms message = new BluetoothMapbMessageSms();
+        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, "_ID = " + id, null, null);
+
+        if(c != null && c.moveToFirst())
+        {
+
+            if(V) Log.d(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));
+
+            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)
+                    message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(msgBody, phone));
+                else
+                    message.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus(msgBody, phone, time)); // TODO: No support for GSM
+            } else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ {
+                message.setSmsBody(msgBody);
+            }
+
+            c.close();
+
+            return message.encode();
+        }
+        throw new IllegalArgumentException("SMS handle not found");
+    }
+
+    private void extractMmsAddresses(long id, BluetoothMapbMessageMmsEmail message) {
+        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);
+        /* TODO: Change the setVCard...() to return the vCard, and use the name in message.addXxx() */
+        if (c.moveToFirst()) {
+            do {
+                String address = c.getString(c.getColumnIndex("address"));
+                Integer type = c.getInt(c.getColumnIndex("type"));
+                switch(type) {
+                case MMS_FROM:
+                    setVCardFromPhoneNumber(message, address, false);
+                    message.addFrom(null, address);
+                    break;
+                case MMS_TO:
+                    setVCardFromPhoneNumber(message, address, true);
+                    message.addTo(null, address);
+                    break;
+                case MMS_CC:
+                    setVCardFromPhoneNumber(message, address, true);
+                    message.addCc(null, address);
+                    break;
+                case MMS_BCC:
+                    setVCardFromPhoneNumber(message, address, true);
+                    message.addBcc(null, address);
+                default:
+                    break;
+                }
+            } while(c.moveToNext());
+        }
+    }
+
+    private byte[] readMmsDataPart(long partid) {
+        String uriStr = String.format("content://mms/part/%d", partid);
+        Uri uriAddress = Uri.parse(uriStr);
+        InputStream is = null;
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        int bufferSize = 8192;
+        byte[] buffer = new byte[bufferSize];
+        byte[] retVal = null;
+
+        try {
+            is = mResolver.openInputStream(uriAddress);
+            int len = 0;
+            while ((len = is.read(buffer)) != -1) {
+              os.write(buffer, 0, len); // We need to specify the len, as it can be != bufferSize
+            }
+            retVal = os.toByteArray();
+        } catch (IOException e) {
+            // do nothing for now
+            Log.w(TAG,"Error reading part data",e);
+        } finally {
+            try {
+                os.close();
+                is.close();
+            } catch (IOException e) {
+            }
+        }
+        return retVal;
+    }
+
+    private void extractMmsParts(long id, BluetoothMapbMessageMmsEmail message)
+    {
+        final String[] projection = null;
+        String selection = new String("mid=" + id);
+        String uriStr = String.format("content://mms/%d/part", id);
+        Uri uriAddress = Uri.parse(uriStr);
+        BluetoothMapbMessageMmsEmail.MimePart part;
+        Cursor c = mResolver.query(
+            uriAddress,
+            projection,
+            selection,
+            null, null);
+
+        if (c.moveToFirst()) {
+            do {
+                Long partId = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                String contentType = c.getString(c.getColumnIndex("ct"));
+                String name = c.getString(c.getColumnIndex("name"));
+                String charset = c.getString(c.getColumnIndex("chset"));
+                String filename = c.getString(c.getColumnIndex("fn"));
+                String text = c.getString(c.getColumnIndex("text"));
+                Integer fd = c.getInt(c.getColumnIndex("_data"));
+
+                if(D)Log.d(TAG, "     _id : " + partId +
+                    "\n     ct : " + contentType +
+                    "\n     partname : " + name +
+                    "\n     charset : " + charset +
+                    "\n     filename : " + filename +
+                    "\n     text : " + text +
+                    "\n     fd : " + fd);
+                part = message.addMimePart();
+                part.contentType = contentType;
+                part.partName = name;
+                try {
+                    if(text != null) {
+                        part.data = text.getBytes("UTF-8");
+                        part.charsetName = "utf-8";
+                    }
+                    else {
+                        part.data = readMmsDataPart(partId);
+                        if(charset != null)
+                            part.charsetName = CharacterSets.getMimeName(Integer.parseInt(charset));
+                    }
+                } catch (NumberFormatException e) {
+                    Log.d(TAG,"extractMmsParts",e);
+                    part.data = null;
+                    part.charsetName = null;
+                } catch (UnsupportedEncodingException e) {
+                    Log.d(TAG,"extractMmsParts",e);
+                    part.data = null;
+                    part.charsetName = null;
+                } finally {
+                }
+                part.fileName = filename;
+            } while(c.moveToNext());
+        }
+        message.updateCharset();
+    }
+
+    public byte[] getMmsMessage(long id) throws UnsupportedEncodingException {
+        int msgBox, threadId;
+        BluetoothMapbMessageMmsEmail message = new BluetoothMapbMessageMmsEmail();
+        Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, "_ID = " + id, null, null);
+        if(c != null && c.moveToFirst())
+        {
+            message.setType(TYPE.MMS);
+
+            // 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);
+            // c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)); - TODO: Do we need this
+            // 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
+            extractMmsParts(id, message);
+
+            // The addresses
+            extractMmsAddresses(id, message);
+
+            c.close();
+
+            return message.encode();
+        }
+        else if(c != null) {
+            c.close();
+        }
+
+        throw new IllegalArgumentException("MMS handle not found");
+    }
+
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
new file mode 100644
index 0000000..ed090ea
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -0,0 +1,1168 @@
+/*
+* Copyright (C) 2013 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.android.bluetooth.map;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.BaseColumns;
+import android.provider.Telephony;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.Sms;
+import android.provider.Telephony.Sms.Inbox;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SmsManager;
+import android.telephony.SmsMessage;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.bluetooth.map.BluetoothMapbMessageMmsEmail.MimePart;
+import com.google.android.mms.pdu.PduHeaders;
+
+public class BluetoothMapContentObserver {
+    private static final String TAG = "BluetoothMapContentObserver";
+
+    private static final boolean D = true;
+    private static final boolean V = true;
+
+    private Context mContext;
+    private ContentResolver mResolver;
+    private BluetoothMnsObexClient mMnsClient;
+    private int mMasId;
+
+    public static final int DELETED_THREAD_ID = -1;
+
+    /* X-Mms-Message-Type field types. These are from PduHeaders.java */
+    public static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84;
+
+    private TYPE mSmsType;
+
+    static final String[] SMS_PROJECTION = new String[] {
+        BaseColumns._ID,
+        Sms.THREAD_ID,
+        Sms.ADDRESS,
+        Sms.BODY,
+        Sms.DATE,
+        Sms.READ,
+        Sms.TYPE,
+        Sms.STATUS,
+        Sms.LOCKED,
+        Sms.ERROR_CODE,
+    };
+
+    static final String[] MMS_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.MESSAGE_TYPE,
+        Mms.STATUS,
+    };
+
+    public BluetoothMapContentObserver(final Context context) {
+        mContext = context;
+        mResolver = mContext.getContentResolver();
+
+        mSmsType = getSmsType();
+    }
+
+    private TYPE getSmsType() {
+        TYPE smsType = null;
+        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+
+        if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
+            smsType = TYPE.SMS_GSM;
+        } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+            smsType = TYPE.SMS_CDMA;
+        }
+
+        return smsType;
+    }
+
+    private final ContentObserver mObserver = 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.toString() + " selfchange: " + selfChange);
+
+            handleMsgListChanges();
+        }
+    };
+
+    private static final String folderSms[] = {
+        "",
+        "inbox",
+        "sent",
+        "draft",
+        "outbox",
+        "outbox",
+        "outbox",
+        "inbox",
+        "inbox",
+    };
+
+    private static final String folderMms[] = {
+        "",
+        "inbox",
+        "sent",
+        "draft",
+        "outbox",
+    };
+
+    private class Event {
+        String eventType;
+        long handle;
+        String folder;
+        String oldFolder;
+        TYPE msgType;
+
+        public Event(String eventType, long handle, String folder,
+            String oldFolder, TYPE msgType) {
+            String PATH = "telecom/msg/";
+            this.eventType = eventType;
+            this.handle = handle;
+            if (folder != null) {
+                this.folder = PATH + folder;
+            } else {
+                this.folder = null;
+            }
+            if (oldFolder != null) {
+                this.oldFolder = PATH + oldFolder;
+            } else {
+                this.oldFolder = null;
+            }
+            this.msgType = msgType;
+        }
+
+        public byte[] encode() throws UnsupportedEncodingException {
+            StringWriter sw = new StringWriter();
+            XmlSerializer xmlEvtReport = Xml.newSerializer();
+            try {
+                xmlEvtReport.setOutput(sw);
+                xmlEvtReport.startDocument(null, null);
+                xmlEvtReport.text("\n");
+                xmlEvtReport.startTag("", "Map-event-report");
+                xmlEvtReport.attribute("", "version", "1.0");
+
+                xmlEvtReport.startTag("", "event");
+                xmlEvtReport.attribute("", "type", eventType);
+                xmlEvtReport.attribute("", "handle", BluetoothMapUtils.getMapHandle(handle, msgType));
+                if (folder != null) {
+                    xmlEvtReport.attribute("", "folder", folder);
+                }
+                if (oldFolder != null) {
+                    xmlEvtReport.attribute("", "old_folder", oldFolder);
+                }
+                xmlEvtReport.attribute("", "msg_type", msgType.name());
+                xmlEvtReport.endTag("", "event");
+
+                xmlEvtReport.endTag("", "Map-event-report");
+                xmlEvtReport.endDocument();
+            } catch (IllegalArgumentException e) {
+                e.printStackTrace();
+            } catch (IllegalStateException e) {
+                e.printStackTrace();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+
+            if (V) System.out.println(sw.toString());
+
+            return sw.toString().getBytes("UTF-8");
+        }
+    }
+
+    private class Msg {
+        long id;
+        int type;
+
+        public Msg(long id, int type) {
+            this.id = id;
+            this.type = type;
+        }
+    }
+
+    private Map<Long, Msg> mMsgListSms =
+        Collections.synchronizedMap(new HashMap<Long, Msg>());
+
+    private Map<Long, Msg> mMsgListMms =
+        Collections.synchronizedMap(new HashMap<Long, Msg>());
+
+    public void registerObserver(BluetoothMnsObexClient mns, int masId) {
+        if (V) Log.d(TAG, "registerObserver");
+        /* Use MmsSms Uri since the Sms Uri is not notified on deletes */
+        mMasId = masId;
+        mMnsClient = mns;
+        mResolver.registerContentObserver(MmsSms.CONTENT_URI, false, mObserver);
+        initMsgList();
+    }
+
+    public void unregisterObserver() {
+        if (V) Log.d(TAG, "unregisterObserver");
+        mResolver.unregisterContentObserver(mObserver);
+    }
+
+    private void sendEvent(Event evt) {
+        Log.d(TAG, "sendEvent: " + evt.eventType + " " + evt.handle + " "
+        + evt.folder + " " + evt.oldFolder + " " + evt.msgType.name());
+
+        if (mMnsClient == null) {
+            Log.d(TAG, "sendEvent: No MNS client registered - don't send event");
+            return;
+        }
+
+        try {
+            mMnsClient.sendEvent(evt.encode(), mMasId);
+        } catch (UnsupportedEncodingException ex) {
+            /* do nothing */
+        }
+    }
+
+    private void initMsgList() {
+        if (V) Log.d(TAG, "initMsgList");
+
+        mMsgListSms.clear();
+        mMsgListMms.clear();
+
+        HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>();
+
+        Cursor c = mResolver.query(Sms.CONTENT_URI,
+            SMS_PROJECTION, null, null, null);
+
+        if (c != null && c.moveToFirst()) {
+            do {
+                long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                int type = c.getInt(c.getColumnIndex(Sms.TYPE));
+
+                Msg msg = new Msg(id, type);
+                msgListSms.put(id, msg);
+            } while (c.moveToNext());
+            c.close();
+        }
+
+        mMsgListSms = msgListSms;
+
+        HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>();
+
+        c = mResolver.query(Mms.CONTENT_URI,
+            MMS_PROJECTION, null, null, null);
+
+        if (c != null && c.moveToFirst()) {
+            do {
+                long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
+
+                Msg msg = new Msg(id, type);
+                msgListMms.put(id, msg);
+            } while (c.moveToNext());
+            c.close();
+        }
+
+        mMsgListMms = msgListMms;
+    }
+
+    private void handleMsgListChangesSms() {
+        if (V) Log.d(TAG, "handleMsgListChangesSms");
+
+        HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>();
+
+        Cursor c = mResolver.query(Sms.CONTENT_URI,
+            SMS_PROJECTION, null, null, null);
+
+        synchronized(mMsgListSms) {
+            if (c != null && c.moveToFirst()) {
+                do {
+                    long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                    int type = c.getInt(c.getColumnIndex(Sms.TYPE));
+
+                    Msg msg = mMsgListSms.remove(id);
+
+                    if (msg == null) {
+                        /* New message */
+                        msg = new Msg(id, type);
+                        msgListSms.put(id, msg);
+
+                        if (folderSms[type].equals("inbox")) {
+                            Event evt = new Event("NewMessage", id, folderSms[type],
+                                null, mSmsType);
+                            sendEvent(evt);
+                        }
+                    } else {
+                        /* Existing message */
+                        if (type != msg.type) {
+                            Log.d(TAG, "new type: " + type + " old type: " + msg.type);
+                            Event evt = new Event("MessageShift", id, folderSms[type],
+                                folderSms[msg.type], mSmsType);
+                            sendEvent(evt);
+                            msg.type = type;
+                        }
+                        msgListSms.put(id, msg);
+                    }
+                } while (c.moveToNext());
+                c.close();
+            }
+
+            for (Msg msg : mMsgListSms.values()) {
+                Event evt = new Event("MessageDeleted", msg.id, "deleted",
+                    folderSms[msg.type], mSmsType);
+                sendEvent(evt);
+            }
+
+            mMsgListSms = msgListSms;
+        }
+    }
+
+    private void handleMsgListChangesMms() {
+        if (V) Log.d(TAG, "handleMsgListChangesMms");
+
+        HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>();
+
+        Cursor c = mResolver.query(Mms.CONTENT_URI,
+            MMS_PROJECTION, null, null, null);
+
+        synchronized(mMsgListMms) {
+            if (c != null && c.moveToFirst()) {
+                do {
+                    long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                    int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
+                    int mtype = c.getInt(c.getColumnIndex(Mms.MESSAGE_TYPE));
+
+                    Msg msg = mMsgListMms.remove(id);
+
+                    if (msg == null) {
+                        /* New message - only notify on retrieve conf */
+                        if (folderMms[type].equals("inbox") &&
+                            mtype != MESSAGE_TYPE_RETRIEVE_CONF) {
+                                continue;
+                        }
+
+                        msg = new Msg(id, type);
+                        msgListMms.put(id, msg);
+
+                        if (folderMms[type].equals("inbox")) {
+                            Event evt = new Event("NewMessage", 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);
+                            Event evt = new Event("MessageShift", id, folderMms[type],
+                                folderMms[msg.type], TYPE.MMS);
+                            sendEvent(evt);
+                            msg.type = type;
+
+                            if (folderMms[type].equals("sent")) {
+                                evt = new Event("SendingSuccess", id,
+                                    folderSms[type], null, TYPE.MMS);
+                                sendEvent(evt);
+                            }
+                        }
+                        msgListMms.put(id, msg);
+                    }
+                } while (c.moveToNext());
+                c.close();
+            }
+
+            for (Msg msg : mMsgListMms.values()) {
+                Event evt = new Event("MessageDeleted", msg.id, "deleted",
+                    folderMms[msg.type], TYPE.MMS);
+                sendEvent(evt);
+            }
+
+            mMsgListMms = msgListMms;
+        }
+    }
+
+    private void handleMsgListChanges() {
+        handleMsgListChangesSms();
+        handleMsgListChangesMms();
+    }
+
+    private boolean deleteMessageMms(long handle) {
+        boolean res = false;
+        Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
+        Cursor c = mResolver.query(uri, null, null, null, null);
+        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 */
+                ContentValues contentValues = new ContentValues();
+                contentValues.put(Mms.THREAD_ID, DELETED_THREAD_ID);
+                mResolver.update(uri, contentValues, null, null);
+            } else {
+                /* Delete from observer message list to avoid delete notifications */
+                mMsgListMms.remove(handle);
+                /* Delete message */
+                mResolver.delete(uri, null, null);
+            }
+            res = true;
+        }
+        if (c != null) {
+            c.close();
+        }
+        return res;
+    }
+
+    private void updateThreadIdMms(Uri uri, long threadId) {
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(Mms.THREAD_ID, threadId);
+        mResolver.update(uri, contentValues, null, null);
+    }
+
+    private boolean unDeleteMessageMms(long handle) {
+        boolean res = false;
+        Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
+        Cursor c = mResolver.query(uri, null, null, null, null);
+
+        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 */
+                String address;
+                long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
+                if (msgBox == Mms.MESSAGE_BOX_INBOX) {
+                    address = BluetoothMapContent.getAddressMms(mResolver, id,
+                        BluetoothMapContent.MMS_FROM);
+                } else {
+                    address = BluetoothMapContent.getAddressMms(mResolver, id,
+                        BluetoothMapContent.MMS_TO);
+                }
+                Set<String> recipients = new HashSet<String>();
+                recipients.addAll(Arrays.asList(address));
+                updateThreadIdMms(uri, Telephony.Threads.getOrCreateThreadId(mContext, recipients));
+            } else {
+                Log.d(TAG, "Message not in deleted folder: handle " + handle
+                    + " threadId " + threadId);
+            }
+            res = true;
+        }
+        if (c != null) {
+            c.close();
+        }
+        return res;
+    }
+
+    private boolean deleteMessageSms(long handle) {
+        boolean res = false;
+        Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
+        Cursor c = mResolver.query(uri, null, null, null, null);
+
+        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) {
+                /* Set deleted thread id */
+                ContentValues contentValues = new ContentValues();
+                contentValues.put(Sms.THREAD_ID, DELETED_THREAD_ID);
+                mResolver.update(uri, contentValues, null, null);
+            } else {
+                /* Delete from observer message list to avoid delete notifications */
+                mMsgListSms.remove(handle);
+                /* Delete message */
+                mResolver.delete(uri, null, null);
+            }
+            res = true;
+        }
+        if (c != null) {
+            c.close();
+        }
+        return res;
+    }
+
+    private void updateThreadIdSms(Uri uri, long threadId) {
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(Sms.THREAD_ID, threadId);
+        mResolver.update(uri, contentValues, null, null);
+    }
+
+    private boolean unDeleteMessageSms(long handle) {
+        boolean res = false;
+        Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
+        Cursor c = mResolver.query(uri, null, null, null, null);
+
+        if (c != null && c.moveToFirst()) {
+            int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
+            if (threadId == DELETED_THREAD_ID) {
+                String address = c.getString(c.getColumnIndex(Sms.ADDRESS));
+                Set<String> recipients = new HashSet<String>();
+                recipients.addAll(Arrays.asList(address));
+                updateThreadIdSms(uri, Telephony.Threads.getOrCreateThreadId(mContext, recipients));
+            } else {
+                Log.d(TAG, "Message not in deleted folder: handle " + handle
+                    + " threadId " + threadId);
+            }
+            res = true;
+        }
+        if (c != null) {
+            c.close();
+        }
+        return res;
+    }
+
+    public boolean setMessageStatusDeleted(long handle, TYPE type, int statusValue) {
+        boolean res = false;
+        if (D) Log.d(TAG, "setMessageStatusDeleted: handle " + handle
+            + " type " + type + " value " + statusValue);
+
+        if (statusValue == BluetoothMapAppParams.STATUS_VALUE_YES) {
+            if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
+                res = deleteMessageSms(handle);
+            } else if (type == TYPE.MMS) {
+                res = deleteMessageMms(handle);
+            }
+        } else if (statusValue == BluetoothMapAppParams.STATUS_VALUE_NO) {
+            if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
+                res = unDeleteMessageSms(handle);
+            } else if (type == TYPE.MMS) {
+                res = unDeleteMessageMms(handle);
+            }
+        }
+        return res;
+    }
+
+    public boolean setMessageStatusRead(long handle, TYPE type, int statusValue) {
+        boolean res = true;
+
+        if (D) Log.d(TAG, "setMessageStatusRead: handle " + handle
+            + " type " + type + " value " + statusValue);
+
+        /* 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 = ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
+            Cursor c = mResolver.query(uri, null, null, null, null);
+
+            ContentValues contentValues = new ContentValues();
+            contentValues.put(Sms.READ, statusValue);
+            mResolver.update(uri, contentValues, null, null);
+        } else if (type == TYPE.MMS) {
+            Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
+            Cursor c = mResolver.query(uri, null, null, null, null);
+
+            ContentValues contentValues = new ContentValues();
+            contentValues.put(Mms.READ, statusValue);
+            mResolver.update(uri, contentValues, null, null);
+        }
+
+        return res;
+    }
+
+    private class PushMsgInfo {
+        long id;
+        int transparent;
+        int retry;
+        String phone;
+        Uri uri;
+        int parts;
+        int partsSent;
+        int partsDelivered;
+        boolean resend;
+
+        public PushMsgInfo(long id, int transparent,
+            int retry, String phone, Uri uri) {
+            this.id = id;
+            this.transparent = transparent;
+            this.retry = retry;
+            this.phone = phone;
+            this.uri = uri;
+            this.resend = false;
+        };
+    }
+
+    private Map<Long, PushMsgInfo> mPushMsgList =
+        Collections.synchronizedMap(new HashMap<Long, PushMsgInfo>());
+
+    public long pushMessage(BluetoothMapbMessage msg, String folder,
+        BluetoothMapAppParams ap) throws IllegalArgumentException {
+        if (D) Log.d(TAG, "pushMessage");
+        ArrayList<BluetoothMapbMessage.vCard> recipientList = msg.getRecipients();
+        int transparent = (ap.getTransparent() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) ?
+                0 : ap.getTransparent();
+        int retry = ap.getRetry();
+        int charset = ap.getCharset();
+        long handle = -1;
+
+        if (recipientList == null) {
+            Log.d(TAG, "empty recipient list");
+            return -1;
+        }
+
+        for (BluetoothMapbMessage.vCard recipient : recipientList) {
+            if(recipient.getEnvLevel() == 0) // Only send the message to the top level recipient
+            {
+                /* Only send to first address */
+                String phone = recipient.getFirstPhoneNumber();
+                boolean read = false;
+                boolean deliveryReport = true;
+
+                switch(msg.getType()){
+                    case MMS:
+                    {
+                        /* Send message if folder is outbox */
+                        if (folder.equals("outbox")) {
+                           handle = sendMmsMessage(folder, phone, (BluetoothMapbMessageMmsEmail)msg);
+                        }
+                        break;
+                    }
+                    case SMS_GSM: //fall-through
+                    case SMS_CDMA:
+                    {
+                        /* Add the message to the database */
+                        String msgBody = ((BluetoothMapbMessageSms) msg).getSmsBody();
+                        Uri contentUri = Uri.parse("content://sms/" + folder);
+                        Uri uri = Sms.addMessageToUri(mResolver, contentUri, phone, msgBody,
+                            "", System.currentTimeMillis(), read, deliveryReport);
+
+                        if (uri == null) {
+                            Log.d(TAG, "pushMessage - failure on add to uri " + contentUri);
+                            return -1;
+                        }
+
+                        handle = Long.parseLong(uri.getLastPathSegment());
+
+                        /* Send message if folder is outbox */
+                        if (folder.equals("outbox")) {
+                            PushMsgInfo msgInfo = new PushMsgInfo(handle, transparent,
+                                retry, phone, uri);
+                            mPushMsgList.put(handle, msgInfo);
+                            sendMessage(msgInfo, msgBody);
+                        }
+                        break;
+                    }
+                    case EMAIL:
+                    {
+                        break;
+                    }
+                }
+
+            }
+        }
+
+        /* If multiple recipients return handle of last */
+        return handle;
+    }
+
+
+
+    public long sendMmsMessage(String folder,String to_address, BluetoothMapbMessageMmsEmail msg) {
+        /*
+         *strategy:
+         *1) parse message into parts
+         *if folder is outbox/drafts:
+         *2) push message to draft
+         *if folder is outbox:
+         *3) move message to outbox (to trigger the mms app to add msg to pending_messages list)
+         *4) send intent to mms app in order to wake it up.
+         *else if folder !outbox:
+         *1) push message to folder
+         * */
+        if (folder != null && (folder.equalsIgnoreCase("outbox")||  folder.equalsIgnoreCase("drafts"))) {
+            long handle = pushMmsToFolder(Mms.MESSAGE_BOX_DRAFTS, to_address, msg);
+            /* if invalid handle (-1) then just return the handle - else continue sending (if folder is outbox) */
+            if (BluetoothMapAppParams.INVALID_VALUE_PARAMETER != handle && folder.equalsIgnoreCase("outbox")) {
+                moveDraftToOutbox(handle);
+
+                Intent sendIntent = new Intent("android.intent.action.MMS_SEND_OUTBOX_MSG");
+                Log.d(TAG, "broadcasting intent: "+sendIntent.toString());
+                mContext.sendBroadcast(sendIntent);
+            }
+            return handle;
+        } else {
+            /* not allowed to push mms to anything but outbox/drafts */
+            throw  new IllegalArgumentException("Cannot push message to other folders than outbox/drafts");
+        }
+
+    }
+
+
+    private void moveDraftToOutbox(long handle) {
+        ContentResolver contentResolver = mContext.getContentResolver();
+        /*Move message by changing the msg_box value in the content provider database */
+        if (handle != -1) {
+            String whereClause = " _id= " + handle;
+            Uri uri = Uri.parse("content://mms");
+            Cursor queryResult = contentResolver.query(uri, null, whereClause, null, null);
+            if (queryResult != null) {
+                if (queryResult.getCount() > 0) {
+                    queryResult.moveToFirst();
+                    ContentValues data = new ContentValues();
+                    /* set folder to be outbox */
+                    data.put("msg_box", Mms.MESSAGE_BOX_OUTBOX);
+                    contentResolver.update(uri, data, whereClause, null);
+                    Log.d(TAG, "moved draft MMS to outbox");
+                }
+                queryResult.close();
+            }else {
+                Log.d(TAG, "Could not move draft to outbox ");
+            }
+        }
+    }
+    private long pushMmsToFolder(int folder, String to_address, BluetoothMapbMessageMmsEmail msg) {
+        /**
+         * strategy:
+         * 1) parse msg into parts + header
+         * 2) create thread id (abuse the ease of adding an SMS to get id for thread)
+         * 3) push parts into content://mms/parts/ table
+         * 3)
+         */
+
+        ContentValues values = new ContentValues();
+        values.put("msg_box", folder);
+
+        values.put("read", 0);
+        values.put("seen", 0);
+        values.put("sub_cs", 106);
+        values.put("ct_t", "application/vnd.wap.multipart.related");
+        values.put("exp", 604800);
+        values.put("m_cls", PduHeaders.MESSAGE_CLASS_PERSONAL_STR);
+        values.put("m_type", PduHeaders.MESSAGE_TYPE_SEND_REQ);
+        values.put("v", PduHeaders.CURRENT_MMS_VERSION);
+        values.put("pri", PduHeaders.PRIORITY_NORMAL);
+        values.put("rr", PduHeaders.VALUE_NO);
+        values.put("tr_id", "T"+ Long.toHexString(System.currentTimeMillis()));
+        values.put("d_rpt", PduHeaders.VALUE_NO);
+        values.put("locked", 0);
+     // Get thread id
+        Set<String> recipients = new HashSet<String>();
+        recipients.addAll(Arrays.asList(to_address));
+        values.put("thread_id", Telephony.Threads.getOrCreateThreadId(mContext, recipients));
+        Uri uri = Uri.parse("content://mms");
+
+        ContentResolver cr = mContext.getContentResolver();
+        uri = cr.insert(uri, values);
+
+        if (uri == null) {
+            // unable to insert MMS
+            Log.e(TAG, "Unabled to insert MMS " + values + "Uri: " + uri);
+            return -1;
+        }
+
+        long handle = Long.parseLong(uri.getLastPathSegment());
+        if (V){
+            Log.v(TAG, " NEW URI " + uri.toString());
+        }
+        try {
+        for(MimePart part : msg.getMimeParts()) {
+            values.clear();
+            if(part.contentType != null &&  part.contentType.toUpperCase().contains("TEXT")) {
+                values.put("seq", 0);
+                values.put("ct", "text/plain");
+                values.put("name", "null");
+                values.put("chset", 106);
+                values.put("cd", "null");
+                values.put("fn", part.partName);
+                values.put("name", part.partName);
+                values.put("cid", "<smil>");
+                values.put("cl", part.partName);
+                values.put("ctt_s", "null");
+                values.put("ctt_t", "null");
+                values.put("_data", "null");
+                values.put("text", new String(part.data, "UTF-8"));
+                uri = Uri.parse("content://mms/" + handle + "/part");
+                uri = cr.insert(uri, values);
+
+            } else if (part.contentType != null &&  part.contentType.toUpperCase().contains("SMIL")){
+
+                values.put("seq", -1);
+                values.put("ct", "application/smil");
+                values.put("cid", "<smil>");
+                values.put("cl", "smil.xml");
+                values.put("fn", "smil.xml");
+                values.put("name", "smil.xml");
+                values.put("text", new String(part.data, "UTF-8"));
+
+                uri = Uri.parse("content://mms/" + handle + "/part");
+                uri = cr.insert(uri, values);
+
+            }else /*VIDEO/AUDIO/IMAGE*/ {
+                writeMmsDataPart(handle, part.contentType, part.partName, part.data);
+            }
+            if (uri != null && V){
+                Log.v(TAG, "Added part with content-type: "+ part.contentType + " to Uri: " + uri.toString());
+            }
+        }
+        } catch (UnsupportedEncodingException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+
+        values.clear();
+        values.put("contact_id", "null");
+        values.put("address", "insert-address-token");
+        values.put("type", BluetoothMapContent.MMS_FROM);
+        values.put("charset", 106);
+
+        uri = Uri.parse("content://mms/" + handle + "/addr");
+        uri = cr.insert(uri, values);
+        if (uri != null && V){
+            Log.v(TAG, " NEW URI " + uri.toString());
+        }
+
+        values.clear();
+        values.put("contact_id", "null");
+        values.put("address", to_address);
+        values.put("type", BluetoothMapContent.MMS_TO);
+        values.put("charset", 106);
+
+        uri = Uri.parse("content://mms/" + handle + "/addr");
+        uri = cr.insert(uri, values);
+        if (uri != null && V){
+            Log.v(TAG, " NEW URI " + uri.toString());
+        }
+        return handle;
+    }
+
+
+    private void writeMmsDataPart(long handle, String contentType, String name, byte[] data) throws IOException{
+        ContentValues values = new ContentValues();
+        values.put("mid", handle);
+        values.put("ct", contentType);
+        values.put("cid", "<smil>");
+        values.put("cl", name);
+        values.put("fn", name);
+        values.put("name", name);
+        Uri partUri = Uri.parse("content://mms/" + handle + "/part");
+        Uri res = mResolver.insert(partUri, values);
+
+        // Add data to part
+        OutputStream os = mResolver.openOutputStream(res);
+        ByteArrayInputStream is = new ByteArrayInputStream(data);
+        byte[] buffer = new byte[256];
+        for (int len=0; (len=is.read(buffer)) != -1;)
+        {
+            os.write(buffer, 0, len);
+        }
+        os.close();
+        is.close();
+    }
+
+
+    public void sendMessage(PushMsgInfo msgInfo, String msgBody) {
+
+        SmsManager smsMng = SmsManager.getDefault();
+        ArrayList<String> parts = smsMng.divideMessage(msgBody);
+        msgInfo.parts = parts.size();
+
+        ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(msgInfo.parts);
+        ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(msgInfo.parts);
+
+        for (int i = 0; i < msgInfo.parts; i++) {
+            Intent intent;
+            intent = new Intent(ACTION_MESSAGE_DELIVERY, null);
+            intent.putExtra("HANDLE", msgInfo.id);
+            deliveryIntents.add(PendingIntent.getBroadcast(mContext, 0, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT));
+
+            intent = new Intent(ACTION_MESSAGE_SENT, null);
+            intent.putExtra("HANDLE", msgInfo.id);
+            sentIntents.add(PendingIntent.getBroadcast(mContext, 0, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT));
+        }
+
+        Log.d(TAG, "sendMessage to " + msgInfo.phone);
+
+        smsMng.sendMultipartTextMessage(msgInfo.phone, null, parts, sentIntents,
+            deliveryIntents);
+    }
+
+    private static final String ACTION_MESSAGE_DELIVERY =
+        "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY";
+    private static final String ACTION_MESSAGE_SENT =
+        "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT";
+
+    private SmsBroadcastReceiver mSmsBroadcastReceiver = new SmsBroadcastReceiver();
+
+    private class SmsBroadcastReceiver extends BroadcastReceiver {
+        private final String[] ID_PROJECTION = new String[] { Sms._ID };
+        private final Uri UPDATE_STATUS_URI = Uri.parse("content://sms/status");
+
+        public void register() {
+            Handler handler = new Handler();
+
+            IntentFilter intentFilter = new IntentFilter();
+            intentFilter.addAction(ACTION_MESSAGE_DELIVERY);
+            intentFilter.addAction(ACTION_MESSAGE_SENT);
+            mContext.registerReceiver(this, intentFilter, null, handler);
+        }
+
+        public void unregister() {
+            try {
+                mContext.unregisterReceiver(this);
+            } catch (IllegalArgumentException e) {
+                /* do nothing */
+            }
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            long handle = intent.getLongExtra("HANDLE", -1);
+            PushMsgInfo msgInfo = mPushMsgList.get(handle);
+
+            Log.d(TAG, "onReceive: action"  + action);
+
+            if (msgInfo == null) {
+                Log.d(TAG, "onReceive: no msgInfo found for handle " + handle);
+                return;
+            }
+
+            if (action.equals(ACTION_MESSAGE_SENT)) {
+                msgInfo.partsSent++;
+                if (msgInfo.partsSent == msgInfo.parts) {
+                    actionMessageSent(context, intent, msgInfo);
+                }
+            } else if (action.equals(ACTION_MESSAGE_DELIVERY)) {
+                msgInfo.partsDelivered++;
+                if (msgInfo.partsDelivered == msgInfo.parts) {
+                    actionMessageDelivery(context, intent, msgInfo);
+                }
+            } else {
+                Log.d(TAG, "onReceive: Unknown action " + action);
+            }
+        }
+
+        private void actionMessageSent(Context context, Intent intent,
+            PushMsgInfo msgInfo) {
+            int result = getResultCode();
+            boolean delete = false;
+
+            if (result == Activity.RESULT_OK) {
+                Log.d(TAG, "actionMessageSent: result OK");
+                if (msgInfo.transparent == 0) {
+                    if (!Sms.moveMessageToFolder(context, msgInfo.uri,
+                            Sms.MESSAGE_TYPE_SENT, 0)) {
+                        Log.d(TAG, "Failed to move " + msgInfo.uri + " to SENT");
+                    }
+                } else {
+                    delete = true;
+                }
+
+                Event evt = new Event("SendingSuccess", msgInfo.id,
+                    folderSms[Sms.MESSAGE_TYPE_SENT], null, mSmsType);
+                sendEvent(evt);
+
+            } else {
+                if (msgInfo.retry == 1) {
+                    /* Notify failure, but keep message in outbox for resending */
+                    msgInfo.resend = true;
+                    Event evt = new Event("SendingFailure", msgInfo.id,
+                        folderSms[Sms.MESSAGE_TYPE_OUTBOX], null, mSmsType);
+                    sendEvent(evt);
+                } else {
+                    if (msgInfo.transparent == 0) {
+                        if (!Sms.moveMessageToFolder(context, msgInfo.uri,
+                                Sms.MESSAGE_TYPE_FAILED, 0)) {
+                            Log.d(TAG, "Failed to move " + msgInfo.uri + " to FAILED");
+                        }
+                    } else {
+                        delete = true;
+                    }
+
+                    Event evt = new Event("SendingFailure", msgInfo.id,
+                        folderSms[Sms.MESSAGE_TYPE_FAILED], null, mSmsType);
+                    sendEvent(evt);
+                }
+            }
+
+            if (delete == true) {
+                /* Delete from Observer message list to avoid delete notifications */
+                mMsgListSms.remove(msgInfo.id);
+
+                /* Delete from DB */
+                mResolver.delete(msgInfo.uri, null, null);
+            }
+        }
+
+        private void actionMessageDelivery(Context context, Intent intent,
+            PushMsgInfo msgInfo) {
+            Uri messageUri = intent.getData();
+            byte[] pdu = intent.getByteArrayExtra("pdu");
+            String format = intent.getStringExtra("format");
+
+            SmsMessage message = SmsMessage.createFromPdu(pdu, format);
+            if (message == null) {
+                Log.d(TAG, "actionMessageDelivery: Can't get message from pdu");
+                return;
+            }
+            int status = message.getStatus();
+
+            Cursor cursor = mResolver.query(msgInfo.uri, ID_PROJECTION, null, null, null);
+
+            try {
+                if (cursor.moveToFirst()) {
+                    int messageId = cursor.getInt(0);
+
+                    Uri updateUri = ContentUris.withAppendedId(UPDATE_STATUS_URI, messageId);
+                    boolean isStatusReport = message.isStatusReportMessage();
+
+                    Log.d(TAG, "actionMessageDelivery: uri=" + messageUri + ", status=" + status +
+                                ", isStatusReport=" + isStatusReport);
+
+                    ContentValues contentValues = new ContentValues(2);
+
+                    contentValues.put(Sms.STATUS, status);
+                    contentValues.put(Inbox.DATE_SENT, System.currentTimeMillis());
+                    mResolver.update(updateUri, contentValues, null, null);
+                } else {
+                    Log.d(TAG, "Can't find message for status update: " + messageUri);
+                }
+            } finally {
+                cursor.close();
+            }
+
+            if (status == 0) {
+                Event evt = new Event("DeliverySuccess", msgInfo.id,
+                    folderSms[Sms.MESSAGE_TYPE_SENT], null, mSmsType);
+                sendEvent(evt);
+            } else {
+                Event evt = new Event("DeliveryFailure", msgInfo.id,
+                    folderSms[Sms.MESSAGE_TYPE_SENT], null, mSmsType);
+                sendEvent(evt);
+            }
+
+            mPushMsgList.remove(msgInfo.id);
+        }
+    }
+
+    private void registerPhoneServiceStateListener() {
+        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);
+        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);
+
+        if (c != null && c.moveToFirst()) {
+            do {
+                long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+                String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
+                PushMsgInfo msgInfo = mPushMsgList.get(id);
+                if (msgInfo == null || msgInfo.resend == false) {
+                    continue;
+                }
+                sendMessage(msgInfo, msgBody);
+            } while (c.moveToNext());
+            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 && c.moveToFirst()) {
+            do {
+                long id = c.getLong(c.getColumnIndex(BaseColumns._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());
+            c.close();
+        }
+    }
+
+    private void removeDeletedMessages() {
+        /* Remove messages from virtual "deleted" folder (thread_id -1) */
+        mResolver.delete(Uri.parse("content://sms/"),
+                "thread_id = " + DELETED_THREAD_ID, null);
+    }
+
+    private PhoneStateListener mPhoneListener = new PhoneStateListener() {
+        @Override
+        public void onServiceStateChanged(ServiceState serviceState) {
+            Log.d(TAG, "Phone service state change: " + serviceState.getState());
+            if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) {
+                resendPendingMessages();
+            }
+        }
+    };
+
+    public void init() {
+        mSmsBroadcastReceiver.register();
+        registerPhoneServiceStateListener();
+    }
+
+    public void deinit() {
+        mSmsBroadcastReceiver.unregister();
+        unRegisterPhoneServiceStateListener();
+        failPendingMessages();
+        removeDeletedMessages();
+    }
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
new file mode 100644
index 0000000..d3909dd
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
@@ -0,0 +1,133 @@
+/*
+* 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.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import android.util.Xml;
+
+/**
+ * @author cbonde
+ *
+ */
+public class BluetoothMapFolderElement {
+    private String name;
+    private BluetoothMapFolderElement parent = null;
+    private ArrayList<BluetoothMapFolderElement> subFolders;
+
+    public BluetoothMapFolderElement( String name, BluetoothMapFolderElement parrent ){
+        this.name = name;
+        this.parent = parrent;
+        subFolders = new ArrayList<BluetoothMapFolderElement>();
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Fetch the parent folder.
+     * @return the parent folder or null if we are at the root folder.
+     */
+    public BluetoothMapFolderElement getParent() {
+        return parent;
+    }
+
+    /**
+     * Fetch the root folder.
+     * @return the parent folder or null if we are at the root folder.
+     */
+    public BluetoothMapFolderElement getRoot() {
+        BluetoothMapFolderElement rootFolder = this;
+        while(rootFolder.getParent() != null)
+            rootFolder = rootFolder.getParent();
+        return rootFolder;
+    }
+
+    /**
+     * Add a folder.
+     * @param name the name of the folder to add.
+     * @return the added folder element.
+     */
+    public BluetoothMapFolderElement addFolder(String name){
+        BluetoothMapFolderElement newFolder = new BluetoothMapFolderElement(name, this);
+        subFolders.add(newFolder);
+        return newFolder;
+    }
+
+    /**
+     * Fetch the number of sub folders.
+     * @return returns the number of sub folders.
+     */
+    public int getSubFolderCount(){
+        return subFolders.size();
+    }
+
+    /**
+     * Returns the subFolder element matching the supplied folder name.
+     * @param folderName the name of the subFolder to find.
+     * @return the subFolder element if found {@code null} otherwise.
+     */
+    public BluetoothMapFolderElement getSubFolder(String folderName){
+        for(BluetoothMapFolderElement subFolder : subFolders){
+            if(subFolder.getName().equals(folderName))
+                return subFolder;
+        }
+        return null;
+    }
+
+    public byte[] encode(int offset, int count) throws UnsupportedEncodingException {
+        StringWriter sw = new StringWriter();
+        XmlSerializer xmlMsgElement = Xml.newSerializer();
+        int i, stopIndex;
+        if(offset > subFolders.size())
+            throw new IllegalArgumentException("FolderListingEncode: offset > subFolders.size()");
+
+        stopIndex = offset + count;
+        if(stopIndex > subFolders.size())
+            stopIndex = subFolders.size();
+
+        try {
+            xmlMsgElement.setOutput(sw);
+            xmlMsgElement.startDocument(null, null);
+            xmlMsgElement.text("\n");
+            xmlMsgElement.startTag("", "folder-listing");
+            xmlMsgElement.attribute("", "version", "1.0");
+            for(i = offset; i<stopIndex; i++)
+            {
+                xmlMsgElement.startTag("", "folder");
+                xmlMsgElement.attribute("", "name", subFolders.get(i).getName());
+                xmlMsgElement.endTag("", "folder");
+            }
+            xmlMsgElement.endTag("", "folder-listing");
+            xmlMsgElement.endDocument();
+        } catch (IllegalArgumentException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (IllegalStateException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        return sw.toString().getBytes("UTF-8");
+    }
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
new file mode 100644
index 0000000..0e5ba97
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
@@ -0,0 +1,92 @@
+/*
+* 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.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import android.util.Log;
+import android.util.Xml;
+
+public class BluetoothMapMessageListing {
+
+    private static final String TAG = "BluetoothMapMessageListing";
+    private List<BluetoothMapMessageListingElement> list;
+
+    public BluetoothMapMessageListing(){
+     list = new ArrayList<BluetoothMapMessageListingElement>();
+    }
+    public void add(BluetoothMapMessageListingElement element) {
+        list.add(element);
+    }
+
+    /**
+     * Used to fetch the number of BluetoothMapMessageListingElement elements in the list.
+     * @return the number of elements in the list.
+     */
+    public int getCount() {
+        return list.size();
+    }
+    /**
+     * 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 xmlMsgElement = Xml.newSerializer();
+        try {
+            xmlMsgElement.setOutput(sw);
+            xmlMsgElement.startDocument(null, null);
+            xmlMsgElement.startTag("", "MAP-msg-listing");
+            xmlMsgElement.attribute("", "version", "1.0");
+            // Do the XML encoding of list
+            for (BluetoothMapMessageListingElement element : list) {
+                element.encode(xmlMsgElement); // Append the list element
+            }
+            xmlMsgElement.endTag("", "MAP-msg-listing");
+            xmlMsgElement.endDocument();
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, e.toString());
+        } catch (IllegalStateException e) {
+            Log.w(TAG, e.toString());
+        } catch (IOException e) {
+            Log.w(TAG, e.toString());
+        }
+        return sw.toString().getBytes("UTF-8");
+    }
+
+    public void sort() {
+        Collections.sort(list);
+    }
+
+    public void segment(int count, int offset) {
+        count = Math.min(count, list.size());
+        if (offset + count <= list.size()) {
+            list = list.subList(offset, offset + count);
+        } else {
+            list = null;
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
new file mode 100644
index 0000000..1870486
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
@@ -0,0 +1,256 @@
+/*
+* 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.StringWriter;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+
+public class BluetoothMapMessageListingElement
+    implements Comparable<BluetoothMapMessageListingElement> {
+
+    private static final String TAG = "BluetoothMapMessageListingElement";
+    private static final boolean D = true;
+    private static final boolean V = true;
+
+    private long handle = 0;
+    private String subject = null;
+    private long dateTime = 0;
+    private String senderName = null;
+    private String senderAddressing = null;
+    private String replytoAddressing = null;
+    private String recipientName = null;
+    private String recipientAddressing = null;
+    private TYPE type = null;
+    private int size = -1;
+    private String text = null;
+    private String receptionStatus = null;
+    private int attachmentSize = -1;
+    private String priority = null;
+    private String read = null;
+    private String sent = null;
+    private String protect = null;
+
+    public long getHandle() {
+        return handle;
+    }
+
+    public void setHandle(long handle) {
+        this.handle = handle;
+    }
+
+    public long getDateTime() {
+        return dateTime;
+    }
+
+    public String getDateTimeString() {
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Date date = new Date(dateTime);
+        return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+    }
+
+    public void setDateTime(long dateTime) {
+        this.dateTime = dateTime;
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public void setSubject(String subject) {
+        this.subject = subject;
+    }
+
+    public String getSenderName() {
+        return senderName;
+    }
+
+    public void setSenderName(String senderName) {
+        this.senderName = senderName;
+    }
+
+    public String getSenderAddressing() {
+        return senderAddressing;
+    }
+
+    public void setSenderAddressing(String senderAddressing) {
+        /* TODO: This should depend on the type - for email, the addressing is an email address
+         * Consider removing this again - to allow strings.
+         */
+        this.senderAddressing = PhoneNumberUtils.extractNetworkPortion(senderAddressing);
+        if(this.senderAddressing == null || this.senderAddressing.length() < 2){
+            this.senderAddressing = "11"; // Ensure we have at least two digits to
+        }
+    }
+
+    public String getReplyToAddressing() {
+        return replytoAddressing;
+    }
+
+    public void setReplytoAddressing(String replytoAddressing) {
+        this.replytoAddressing = replytoAddressing;
+    }
+
+    public String getRecipientName() {
+        return recipientName;
+    }
+
+    public void setRecipientName(String recipientName) {
+        this.recipientName = recipientName;
+    }
+
+    public String getRecipientAddressing() {
+        return recipientAddressing;
+    }
+
+    public void setRecipientAddressing(String recipientAddressing) {
+        this.recipientAddressing = recipientAddressing;
+    }
+
+    public TYPE getType() {
+        return type;
+    }
+
+    public void setType(TYPE type) {
+        this.type = type;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public void setSize(int size) {
+        this.size = size;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    public String getReceptionStatus() {
+        return receptionStatus;
+    }
+
+    public void setReceptionStatus(String receptionStatus) {
+        this.receptionStatus = receptionStatus;
+    }
+
+    public int getAttachmentSize() {
+        return attachmentSize;
+    }
+
+    public void setAttachmentSize(int attachmentSize) {
+        this.attachmentSize = attachmentSize;
+    }
+
+    public String getPriority() {
+        return priority;
+    }
+
+    public void setPriority(String priority) {
+        this.priority = priority;
+    }
+
+    public String getRead() {
+        return read;
+    }
+
+    public void setRead(String read) {
+        this.read = read;
+    }
+
+    public String getSent() {
+        return sent;
+    }
+
+    public void setSent(String sent) {
+        this.sent = sent;
+    }
+
+    public String getProtect() {
+        return protect;
+    }
+
+    public void setProtect(String protect) {
+        this.protect = protect;
+    }
+
+    public int compareTo(BluetoothMapMessageListingElement e) {
+        if (this.dateTime < e.dateTime) {
+            return 1;
+        } else if (this.dateTime > e.dateTime) {
+            return -1;
+        } else {
+            return 0;
+        }
+    }
+
+    /* Encode the MapMessageListingElement into the StringBuilder reference.
+     * */
+    public void encode(XmlSerializer xmlMsgElement) throws IllegalArgumentException,
+                         IllegalStateException, IOException
+    {
+        // contruct the XML tag for a single msg in the msglisting
+        xmlMsgElement.startTag("", "msg");
+        xmlMsgElement.attribute("", "handle", BluetoothMapUtils.getMapHandle(handle, type));
+        xmlMsgElement.attribute("", "subject", subject);
+        xmlMsgElement.attribute("", "datetime", this.getDateTimeString());
+        if (senderName != null)
+            xmlMsgElement.attribute("", "sender_name", senderName);
+        if (senderAddressing != null)
+            xmlMsgElement.attribute("", "sender_addressing", senderAddressing);
+        if (replytoAddressing != null)
+            xmlMsgElement.attribute("", "replyto_addressing",replytoAddressing);
+        if (recipientName != null)
+            xmlMsgElement.attribute("", "recipient_name",recipientName);
+        if (recipientAddressing != null)
+            xmlMsgElement.attribute("", "recipient_addressing", recipientAddressing);
+        if (type != null)
+            xmlMsgElement.attribute("", "type", type.name());
+        if (size != -1)
+            xmlMsgElement.attribute("", "size", Integer.toString(size));
+        if (text != null)
+            xmlMsgElement.attribute("", "text", text);
+        if (receptionStatus != null)
+            xmlMsgElement.attribute("", "reception_status", receptionStatus);
+        if (attachmentSize != -1)
+            xmlMsgElement.attribute("", "attachment_size", Integer.toString(attachmentSize));
+        if (priority != null)
+            xmlMsgElement.attribute("", "priority", priority);
+        if (read != null)
+            xmlMsgElement.attribute("", "read", read);
+        if (sent != null)
+            xmlMsgElement.attribute("", "sent", sent);
+        if (protect != null)
+            xmlMsgElement.attribute("", "protect", protect);
+        xmlMsgElement.endTag("", "msg");
+    }
+}
+
+
diff --git a/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
new file mode 100644
index 0000000..c1a82c2
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -0,0 +1,695 @@
+/*
+* 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.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Calendar;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+import javax.obex.ResponseCodes;
+import javax.obex.ServerRequestHandler;
+
+import com.android.bluetooth.map.BluetoothMapUtils;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+public class BluetoothMapObexServer extends ServerRequestHandler {
+
+    private static final String TAG = "BluetoothMapObexServer";
+
+    private static final boolean D = BluetoothMapService.DEBUG;
+    private static final boolean V = BluetoothMapService.VERBOSE;
+
+    private static final int UUID_LENGTH = 16;
+
+    // 128 bit UUID for MAP
+    private static final byte[] MAP_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
+                 };
+
+    /* 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_SEND_EVENT          = "x-bt/MAP-event-report";
+    private static final String TYPE_MESSAGE_UPDATE      = "x-bt/MAP-messageUpdate";
+
+    private BluetoothMapFolderElement mCurrentFolder;
+
+    private Handler mCallback = null;
+
+    private Context mContext;
+
+    public static boolean sIsAborted = false;
+
+    BluetoothMapContent mOutContent;
+
+    public BluetoothMapObexServer(Handler callback, Context context) {
+        super();
+        mCallback = callback;
+        mContext = context;
+        mOutContent = new BluetoothMapContent(mContext);
+
+        buildFolderStructure(); /* Build the default folder structure, and set
+                                   mCurrentFolder to root folder */
+    }
+
+    /**
+     * Build the default minimal folder structure, as defined in the MAP specification.
+     */
+    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
+        tmpFolder.addFolder("inbox");                    // root/telecom/msg/inbox
+        tmpFolder.addFolder("outbox");
+        tmpFolder.addFolder("sent");
+        tmpFolder.addFolder("deleted");
+        tmpFolder.addFolder("draft");
+    }
+
+    @Override
+    public int onConnect(final HeaderSet request, HeaderSet reply) {
+        if (D) Log.d(TAG, "onConnect():");
+        if (V) logHeader(request);
+        try {
+            byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
+            if (uuid == null) {
+                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+            }
+            if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid));
+
+            if (uuid.length != UUID_LENGTH) {
+                Log.w(TAG, "Wrong UUID length");
+                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+            }
+            for (int i = 0; i < UUID_LENGTH; i++) {
+                if (uuid[i] != MAP_TARGET[i]) {
+                    Log.w(TAG, "Wrong UUID");
+                    return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+                }
+            }
+            reply.setHeader(HeaderSet.WHO, uuid);
+        } catch (IOException e) {
+            Log.e(TAG, e.toString());
+            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+        }
+
+        try {
+            byte[] remote = (byte[])request.getHeader(HeaderSet.WHO);
+            if (remote != null) {
+                if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote));
+                reply.setHeader(HeaderSet.TARGET, remote);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, e.toString());
+            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+        }
+
+        if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " +
+                "MSG_SESSION_ESTABLISHED msg.");
+
+        Message msg = Message.obtain(mCallback);
+        msg.what = BluetoothMapService.MSG_SESSION_ESTABLISHED;
+        msg.sendToTarget();
+
+        return ResponseCodes.OBEX_HTTP_OK;
+    }
+
+    @Override
+    public void onDisconnect(final HeaderSet req, final HeaderSet resp) {
+        if (D) Log.d(TAG, "onDisconnect(): enter");
+        if (V) logHeader(req);
+
+        resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
+        if (mCallback != null) {
+            Message msg = Message.obtain(mCallback);
+            msg.what = BluetoothMapService.MSG_SESSION_DISCONNECTED;
+            msg.sendToTarget();
+            if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out.");
+        }
+    }
+
+    @Override
+    public int onAbort(HeaderSet request, HeaderSet reply) {
+        if (D) Log.d(TAG, "onAbort(): enter.");
+        sIsAborted = true;
+        return ResponseCodes.OBEX_HTTP_OK;
+    }
+
+    @Override
+    public int onPut(final Operation op) {
+        if (D) Log.d(TAG, "onPut(): enter");
+        HeaderSet request = null;
+        String type, name;
+        byte[] appParamRaw;
+        BluetoothMapAppParams appParams = null;
+
+        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);
+        } catch (Exception e) {
+            Log.e(TAG, "request headers error");
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }
+
+        if(D) Log.d(TAG,"type = " + type + ", name = " + name);
+        if (type.equals(TYPE_MESSAGE_UPDATE)) {
+            if(V) {
+                Log.d(TAG,"TYPE_MESSAGE_UPDATE:");
+            }
+            return ResponseCodes.OBEX_HTTP_OK;
+        }else if(type.equals(TYPE_SET_NOTIFICATION_REGISTRATION)) {
+            if(V) {
+                Log.d(TAG,"TYPE_SET_NOTIFICATION_REGISTRATION: NotificationStatus: " + appParams.getNotificationStatus());
+            }
+            return setNotificationRegistration(appParams);
+        }else if(type.equals(TYPE_SET_MESSAGE_STATUS)) {
+            if(V) {
+                Log.d(TAG,"TYPE_SET_MESSAGE_STATUS: StatusIndicator: " + appParams.getStatusIndicator() + ", StatusValue: " + appParams.getStatusValue());
+            }
+            return setMessageStatus(name, appParams);
+        } else if (type.equals(TYPE_MESSAGE)) {
+            if(V) {
+                Log.d(TAG,"TYPE_MESSAGE: Transparet: " + appParams.getTransparent() +  ", Retry: " + appParams.getRetry());
+                Log.d(TAG,"              charset: " + appParams.getCharset());
+            }
+            return pushMessage(op, name, appParams);
+
+        }
+
+        return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+    }
+
+    private int setNotificationRegistration(BluetoothMapAppParams appParams) {
+        // Forward the request to the MNS thread as a message - including the MAS instance ID.
+        Handler mns = BluetoothMnsObexClient.getMessageHandler();
+        if(mns != null) {
+            Message msg = Message.obtain(mns);
+            msg.what = BluetoothMnsObexClient.MSG_MNS_NOTIFICATION_REGISTRATION;
+            msg.arg1 = 0; // TODO: Add correct MAS ID, as specified in the SDP record.
+            msg.arg2 = appParams.getNotificationStatus();
+            msg.sendToTarget();
+            return ResponseCodes.OBEX_HTTP_OK;
+        } else {
+            return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // This should not happen.
+        }
+    }
+
+    private int pushMessage(final Operation op, String folderName, BluetoothMapAppParams appParams) {
+        if(appParams.getCharset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
+            if(D) Log.d(TAG, "Missing charset - unable to decode message content. appParams.getCharset() = " + appParams.getCharset());
+            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+        }
+        try {
+            if(folderName == null || folderName.equals("")) {
+                folderName = mCurrentFolder.getName();
+            }
+            if(!folderName.equals("outbox") && !folderName.equals("draft")) {
+                if(D) Log.d(TAG, "Push message only allowed to outbox and draft. folderName: " + folderName);
+                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+            }
+            /* TODO:
+             *  - Read out the message - OK
+             *  - Decode into a bMessage - OK
+             *  - push to draft or send.
+             */
+            InputStream bMsgStream;
+            BluetoothMapbMessage message;
+            bMsgStream = op.openInputStream();
+            message = BluetoothMapbMessage.parse(bMsgStream, appParams.getCharset()); // Decode the messageBody
+            // Send message
+            BluetoothMapContentObserver observer = BluetoothMnsObexClient.getContentObserver();
+            if (observer == null) {
+                return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
+            }
+
+            long handle = observer.pushMessage(message, folderName, appParams);
+            if (D) Log.d(TAG, "pushMessage handle: " + handle);
+            if (handle < 0) {
+                return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
+            }
+            HeaderSet replyHeaders = new HeaderSet();
+            String handleStr = BluetoothMapUtils.getMapHandle(handle, message.getType());
+            if (D) Log.d(TAG, "handleStr: " + handleStr + " message.getType(): " + message.getType());
+            replyHeaders.setHeader(HeaderSet.NAME, handleStr);
+            op.sendHeaders(replyHeaders);
+        } catch (IllegalArgumentException e) {
+            if(D) Log.w(TAG, "Wrongly formatted bMessage received", e);
+            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+        } catch (Exception e) {
+            // TODO: Change to IOException after debug
+            Log.e(TAG, "Exception occured: ", e);
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }
+        return ResponseCodes.OBEX_HTTP_OK;
+    }
+
+    private int setMessageStatus(String msgHandle, BluetoothMapAppParams appParams) {
+        int indicator = appParams.getStatusIndicator();
+        int value = appParams.getStatusValue();
+        if(indicator == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
+           value == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
+           msgHandle == null) {
+            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+        }
+        BluetoothMapContentObserver observer = BluetoothMnsObexClient.getContentObserver();
+        if (observer == null) {
+            return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
+        }
+
+        long handle = BluetoothMapUtils.getCpHandle(msgHandle);
+        BluetoothMapUtils.TYPE msgType = BluetoothMapUtils.getMsgTypeFromHandle(msgHandle);
+        if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_DELETED) {
+            if (!observer.setMessageStatusDeleted(handle, msgType, value)) {
+                return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+            }
+        } else /* BluetoothMapAppParams.STATUS_INDICATOR_READE */ {
+            if (!observer.setMessageStatusRead(handle, msgType, value)) {
+                return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+            }
+        }
+        return ResponseCodes.OBEX_HTTP_OK;
+    }
+
+    @Override
+    public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
+            final boolean create) {
+        String folderName;
+        BluetoothMapFolderElement folder;
+        try {
+            folderName = (String)request.getHeader(HeaderSet.NAME);
+        } catch (Exception e) {
+            Log.e(TAG, "request headers error");
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }
+
+        if (V) logHeader(request);
+        if (D) Log.d(TAG, "onSetPath name is " + folderName + " backup: " + backup
+                     + "create: " + create);
+
+        if(backup == true){
+            if(mCurrentFolder.getParent() != null)
+                mCurrentFolder = mCurrentFolder.getParent();
+            else
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }
+
+        if (folderName == null || folderName == "") {
+            if(backup == false)
+                mCurrentFolder = mCurrentFolder.getRoot();
+        }
+        else {
+            folder = mCurrentFolder.getSubFolder(folderName);
+            if(folder != null)
+                mCurrentFolder = folder;
+            else
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }
+        if (V) Log.d(TAG, "Current Folder: " + mCurrentFolder.getName());
+        return ResponseCodes.OBEX_HTTP_OK;
+    }
+
+    @Override
+    public void onClose() {
+        if (mCallback != null) {
+            Message msg = Message.obtain(mCallback);
+            msg.what = BluetoothMapService.MSG_SERVERSESSION_CLOSE;
+            msg.sendToTarget();
+            if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
+        }
+    }
+
+    @Override
+    public int onGet(Operation op) {
+        sIsAborted = false;
+        HeaderSet request;
+        String type;
+        String name;
+        byte[] appParamRaw = null;
+        BluetoothMapAppParams appParams = null;
+        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 (type == null) {
+                if (V) Log.d(TAG, "type is null?" + type);
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+            }
+
+            if (type.equals(TYPE_GET_FOLDER_LISTING)) {
+                if (V && appParams != null) {
+                    Log.d(TAG,"TYPE_GET_FOLDER_LISTING: MaxListCount = " + appParams.getMaxListCount() +
+                              ", ListStartOffset = " + appParams.getStartOffset());
+                }
+                return sendFolderListingRsp(op, appParams); // Block until all packets have been send.
+            }
+            else if (type.equals(TYPE_GET_MESSAGE_LISTING)){
+                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,"FilterRecipient = " + appParams.getFilterRecipient() +
+                              ", FilterOriginator = " + appParams.getFilterOriginator());
+                    Log.d(TAG,"FilterPriority = " + appParams.getFilterPriority());
+                }
+                return sendMessageListingRsp(op, appParams, name); // Block until all packets have been send.
+            }
+            else if (type.equals(TYPE_MESSAGE)){
+                if (V && appParams != null) {
+                    Log.d(TAG,"TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() +
+                              ", Charset = " + appParams.getCharset() +
+                              ", FractionRequest = " + appParams.getFractionRequest());
+                }
+                return sendGetMessageRsp(op, name, appParams); // Block until all packets have been send.
+            }
+            else {
+                Log.w(TAG, "unknown type request: " + type);
+                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+            }
+        } catch (Exception e) {
+            // TODO: Move to the part that actually throws exceptions, and change to the correat exception type
+            Log.e(TAG, "request headers error, Exception:", e);
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }
+    }
+
+    /**
+     * Generate and send the message 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 sendMessageListingRsp(Operation op, BluetoothMapAppParams appParams, String folderName){
+        OutputStream outStream = null;
+        byte[] outBytes = null;
+        int maxChunkSize, bytesToWrite, bytesWritten = 0, listSize;
+        HeaderSet replyHeaders = new HeaderSet();
+        BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
+        BluetoothMapMessageListing outList;
+        if (folderName == null) {
+            folderName = mCurrentFolder.getName();
+        }
+        if (appParams == null){
+            appParams = new BluetoothMapAppParams();
+            appParams.setMaxListCount(1024);
+            appParams.setStartOffset(0);
+        }
+
+        // TODO: 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.msgListing(folderName, appParams);
+                // Generate the byte stream
+                outAppParams.setMessageListingSize(outList.getCount());
+//                if(outList.getCount() != 0) {
+                    outBytes = outList.encode();
+//                } else {
+//                    op.noBodyHeader();
+                    // TODO: Remove after test
+                    //Log.w(TAG,"sendMessageListingRsp: Empty list - sending OBEX_HTTP_BAD_REQUEST");
+                    //return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+//                }
+            }
+            else {
+                listSize = mOutContent.msgListingSize(folderName, appParams);
+                outAppParams.setMessageListingSize(listSize);
+                op.noBodyHeader();
+            }
+
+            // Build the application parameter header
+            outAppParams.setNewMessage(0); // TODO: set depending on new messages
+            outAppParams.setMseTime(Calendar.getInstance().getTime().getTime());
+
+            replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
+
+            op.sendHeaders(replyHeaders);
+
+        } catch (IOException e) {
+            Log.w(TAG,"sendMessageListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG,"sendMessageListingRsp: IllegalArgumentException - sending OBEX_HTTP_BAD_REQUEST", e);
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        } catch (Exception e){
+            Log.w(TAG, "Exception:", e);
+            // TODO: REMOVE AFTER TEST!!
+        }
+
+        maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
+        if (outBytes != null) {
+            try {
+                while (bytesWritten < outBytes.length && sIsAborted == false) {
+                    bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
+                    outStream.write(outBytes, bytesWritten, bytesToWrite);
+                    bytesWritten += bytesToWrite;
+                }
+            } catch (IOException e) {
+                if(V) Log.w(TAG,e);
+                // We were probably aborted or disconnected
+            } catch (Exception e){
+                if(V) Log.w(TAG,e);
+                // TODO: REMOVE AFTER TEST!!
+            } finally {
+                if (outStream != null) {
+                    try {
+                        outStream.close();
+                    } catch (IOException e) {
+                        // If an error occurs during close, there is no more cleanup to do
+                    }
+                }
+            }
+            if (bytesWritten != outBytes.length)
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        } else {
+            try {
+                outStream.close();
+            } catch (IOException e) {
+                // If an error occurs during close, there is no more cleanup to do
+            }
+        }
+        return ResponseCodes.OBEX_HTTP_OK;
+    }
+
+    /**
+     * Generate and send the Folder 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 sendFolderListingRsp(Operation op, BluetoothMapAppParams appParams){
+        OutputStream outStream = null;
+        byte[] outBytes = null;
+        BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
+        int maxChunkSize, bytesWritten = 0;
+        HeaderSet replyHeaders = new HeaderSet();
+        int bytesToWrite, maxListCount, listStartOffset;
+        if(appParams == null){
+            appParams = new BluetoothMapAppParams();
+            appParams.setMaxListCount(1024);
+        }
+
+        if(V)
+            Log.v(TAG,"sendFolderList for " + mCurrentFolder.getName());
+
+        try {
+            maxListCount = appParams.getMaxListCount();
+            listStartOffset = appParams.getStartOffset();
+
+            if(listStartOffset == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+                listStartOffset = 0;
+
+            if(maxListCount == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+                maxListCount = 1024;
+
+            if(maxListCount != 0)
+            {
+                outBytes = mCurrentFolder.encode(listStartOffset, maxListCount);
+                outStream = op.openOutputStream();
+            }
+
+            // Build and set the application parameter header
+            outAppParams.setFolderListingSize(mCurrentFolder.getSubFolderCount());
+            replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
+            op.sendHeaders(replyHeaders);
+
+        } catch (IOException e1) {
+            Log.w(TAG,"sendFolderListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        } catch (IllegalArgumentException e1) {
+            Log.w(TAG,"sendFolderListingRsp: IllegalArgumentException - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
+            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 && sIsAborted == 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 an error occurs during close, there is no more cleanup to do
+                    }
+                }
+            }
+            if(V)
+                Log.v(TAG,"sendFolderList sent " + bytesWritten + " bytes out of "+ outBytes.length);
+            if(bytesWritten == outBytes.length)
+                return ResponseCodes.OBEX_HTTP_OK;
+            else
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }
+
+        return ResponseCodes.OBEX_HTTP_OK;
+    }
+
+    /**
+     * Generate and send the Folder 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 sendGetMessageRsp(Operation op, String name, BluetoothMapAppParams appParams){
+        OutputStream outStream ;
+        byte[] outBytes;
+        int maxChunkSize, bytesToWrite, bytesWritten = 0;
+        long msgHandle;
+
+        try {
+            outBytes = mOutContent.getMessage(name, appParams.getCharset());
+            outStream = op.openOutputStream();
+
+        } catch (IOException e) {
+            Log.w(TAG,"sendGetMessageRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG,"sendGetMessageRsp: IllegalArgumentException (e.g. invalid handle) - sending OBEX_HTTP_BAD_REQUEST", e);
+            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 && sIsAborted == 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 an error occurs during close, there is no more cleanup to do
+                    }
+                }
+            }
+            if(bytesWritten == outBytes.length)
+                return ResponseCodes.OBEX_HTTP_OK;
+            else
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }
+
+        return ResponseCodes.OBEX_HTTP_OK;
+    }
+
+
+    public static final void logHeader(HeaderSet hs) {
+        Log.v(TAG, "Dumping HeaderSet " + hs.toString());
+        try {
+            Log.v(TAG, "CONNECTION_ID : " + hs.getHeader(HeaderSet.CONNECTION_ID));
+            Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
+            Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
+            Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
+            Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
+            Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
+        } catch (IOException e) {
+            Log.e(TAG, "dump HeaderSet error " + e);
+        }
+        Log.v(TAG, "NEW!!! Dumping HeaderSet END");
+    }
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapReceiver.java b/src/com/android/bluetooth/map/BluetoothMapReceiver.java
new file mode 100644
index 0000000..7363e00
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapReceiver.java
@@ -0,0 +1,64 @@
+/*
+* 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 android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class BluetoothMapReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "BluetoothMapReceiver";
+
+    private static final boolean V = BluetoothMapService.VERBOSE;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (V) Log.v(TAG, "MapReceiver onReceive ");
+
+        Intent in = new Intent();
+        in.putExtras(intent);
+        in.setClass(context, BluetoothMapService.class);
+        String action = intent.getAction();
+        in.putExtra("action", action);
+        if (V) Log.v(TAG,"***********action = " + action);
+
+        boolean startService = true;
+        if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
+            in.putExtra(BluetoothAdapter.EXTRA_STATE, state);
+            if (V) Log.v(TAG,"***********state = " + state);
+            if ((state == BluetoothAdapter.STATE_TURNING_ON)
+                    || (state == BluetoothAdapter.STATE_OFF)) {
+                //FIX: We turn on MAP after BluetoothAdapter.STATE_ON,
+                //but we turn off MAP right after BluetoothAdapter.STATE_TURNING_OFF
+                startService = false;
+            }
+        } else {
+            // Don't forward intent unless device has bluetooth and bluetooth is enabled.
+            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+            if (adapter == null || !adapter.isEnabled()) {
+                startService = false;
+            }
+        }
+        if (startService) {
+            if (V) Log.v(TAG,"***********Calling start service!!!! with action = " + in.getAction());
+            context.startService(in);
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapRfcommTransport.java b/src/com/android/bluetooth/map/BluetoothMapRfcommTransport.java
new file mode 100644
index 0000000..90437d8
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapRfcommTransport.java
@@ -0,0 +1,72 @@
+/*
+* 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 android.bluetooth.BluetoothSocket;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.obex.ObexTransport;
+
+public class BluetoothMapRfcommTransport implements ObexTransport {
+    private BluetoothSocket mSocket = null;
+
+    public BluetoothMapRfcommTransport(BluetoothSocket rfs) {
+        super();
+        this.mSocket = rfs;
+    }
+
+    public void close() throws IOException {
+        mSocket.close();
+    }
+
+    public DataInputStream openDataInputStream() throws IOException {
+        return new DataInputStream(openInputStream());
+    }
+
+    public DataOutputStream openDataOutputStream() throws IOException {
+        return new DataOutputStream(openOutputStream());
+    }
+
+    public InputStream openInputStream() throws IOException {
+        return mSocket.getInputStream();
+    }
+
+    public OutputStream openOutputStream() throws IOException {
+        return mSocket.getOutputStream();
+    }
+
+    public void connect() throws IOException {
+    }
+
+    public void create() throws IOException {
+    }
+
+    public void disconnect() throws IOException {
+    }
+
+    public void listen() throws IOException {
+    }
+
+    public boolean isConnected() throws IOException {
+        return true;
+    }
+
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
new file mode 100644
index 0000000..e4da10f
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -0,0 +1,778 @@
+/*
+* 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 javax.obex.ServerSession;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothServerSocket;
+import android.bluetooth.IBluetooth;
+import android.bluetooth.IBluetoothMap;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.BluetoothMap;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.bluetooth.R;
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
+
+
+public class BluetoothMapService extends Service {
+    private static final String TAG = "BluetoothMapService";
+
+    /**
+     * To enable MAP DEBUG/VERBOSE logging - run below cmd in adb shell, and
+     * restart com.android.bluetooth process. only enable DEBUG log:
+     * "setprop log.tag.BluetoothMapService DEBUG"; enable both VERBOSE and
+     * DEBUG log: "setprop log.tag.BluetoothMapService VERBOSE"
+     */
+
+    public static final boolean DEBUG = true;
+
+    public static final boolean VERBOSE = true;
+
+    /**
+     * Intent indicating incoming obex authentication request which is from
+     * PCE(Carkit)
+     */
+    public static final String AUTH_CHALL_ACTION = "com.android.bluetooth.map.authchall";
+
+    /**
+     * Intent indicating obex session key input complete by user which is sent
+     * from BluetoothMapActivity
+     */
+    public static final String AUTH_RESPONSE_ACTION = "com.android.bluetooth.map.authresponse";
+
+    /**
+     * Intent indicating user canceled obex authentication session key input
+     * which is sent from BluetoothMapActivity
+     */
+    public static final String AUTH_CANCELLED_ACTION = "com.android.bluetooth.map.authcancelled";
+
+    /**
+     * Intent indicating timeout for user confirmation, which is sent to
+     * BluetoothMapActivity
+     */
+    public static final String USER_CONFIRM_TIMEOUT_ACTION =
+            "com.android.bluetooth.map.userconfirmtimeout";
+
+    /**
+     * Intent Extra name indicating session key which is sent from
+     * BluetoothMapActivity
+     */
+    public static final String EXTRA_SESSION_KEY = "com.android.bluetooth.map.sessionkey";
+
+    public static final String THIS_PACKAGE_NAME = "com.android.bluetooth";
+
+    public static final int MSG_SERVERSESSION_CLOSE = 5000;
+
+    public static final int MSG_SESSION_ESTABLISHED = 5001;
+
+    public static final int MSG_SESSION_DISCONNECTED = 5002;
+
+    public static final int MSG_OBEX_AUTH_CHALL = 5003;
+
+    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+
+    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
+
+    private static final int START_LISTENER = 1;
+
+    private static final int USER_TIMEOUT = 2;
+
+    private static final int AUTH_TIMEOUT = 3;
+
+
+    private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
+
+
+    // Ensure not conflict with Opp notification ID
+    private static final int NOTIFICATION_ID_ACCESS = -1000001;
+
+    private static final int NOTIFICATION_ID_AUTH = -1000002;
+
+    private PowerManager.WakeLock mWakeLock = null;
+
+    private BluetoothAdapter mAdapter;
+
+    private SocketAcceptThread mAcceptThread = null;
+
+    private BluetoothMapAuthenticator mAuth = null;
+
+    private BluetoothMapObexServer mMapServer;
+
+    private ServerSession mServerSession = null;
+
+    private BluetoothMnsObexClient mBluetoothMnsObexClient = null;
+
+    private BluetoothServerSocket mServerSocket = null;
+
+    private BluetoothSocket mConnSocket = null;
+
+    private BluetoothDevice mRemoteDevice = null;
+
+    private static String sLocalPhoneNum = null;
+
+    private static String sLocalPhoneName = null;
+
+    private static String sRemoteDeviceName = null;
+
+    private boolean mHasStarted = false;
+
+    private volatile boolean mInterrupted;
+
+    private int mState;
+
+    private int mStartId = -1;
+
+    //private IBluetooth mBluetoothService;
+
+    private boolean isWaitingAuthorization = false;
+
+    // package and class name to which we send intent to check phone book access permission
+    private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
+    private static final String ACCESS_AUTHORITY_CLASS =
+        "com.android.settings.bluetooth.BluetoothPermissionRequest";
+
+    public BluetoothMapService() {
+        mState = BluetoothMap.STATE_DISCONNECTED;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        if (VERBOSE) Log.v(TAG, "Map Service onCreate");
+
+        mInterrupted = false;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        if (!mHasStarted) {
+            mHasStarted = true;
+            if (VERBOSE) Log.v(TAG, "Starting MAP service");
+
+            int state = mAdapter.getState();
+            if (state == BluetoothAdapter.STATE_ON) {
+                // start RFCOMM listener
+                mSessionStatusHandler.sendMessage(mSessionStatusHandler
+                        .obtainMessage(START_LISTENER));
+            }
+        }
+    }
+    // incoming Start intent handler
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+            mStartId = startId;
+            if (mAdapter == null) {
+                Log.w(TAG, "Stopping BluetoothMapService: "
+                        + "device does not have BT or device is not ready");
+                // Release all resources
+                closeService();
+            } else {
+                // No need to handle the null intent case, because we have
+                // all restart work done in onCreate()
+                if (intent != null) {
+                    parseIntent(intent);
+                }
+            }
+        return START_NOT_STICKY;
+    }
+
+    // process the intent from receiver
+    private void parseIntent(final Intent intent) {
+        String action = intent.getStringExtra("action");
+        if (VERBOSE) Log.v(TAG, "action: " + action);
+
+        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
+        if (VERBOSE) Log.v(TAG, "state: " + state);
+
+        boolean removeTimeoutMsg = true;
+        // BT status have been changed check new state
+        if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+            if (state == BluetoothAdapter.STATE_TURNING_OFF) {
+                // Send any pending timeout now, as this service will be destroyed.
+                if (mSessionStatusHandler.hasMessages(USER_TIMEOUT)) {
+                    Intent timeoutIntent =
+                        new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
+                    timeoutIntent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
+                    sendBroadcast(timeoutIntent, BLUETOOTH_ADMIN_PERM);
+                }
+                // Release all resources
+                closeService();
+            } else {
+                removeTimeoutMsg = false;
+            }
+        // Authorization answer intent
+        } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
+            if (!isWaitingAuthorization) {
+                // this reply is not for us
+                return;
+            }
+
+            isWaitingAuthorization = false;
+
+            if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
+                                   BluetoothDevice.CONNECTION_ACCESS_NO) ==
+                BluetoothDevice.CONNECTION_ACCESS_YES) {
+                //bluetooth connection accepted by user
+                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
+                    boolean result = mRemoteDevice.setTrust(true);
+                    if (VERBOSE) Log.v(TAG, "setTrust() result=" + result);
+                }
+                try {
+                    if (mConnSocket != null) {
+                        // start obex server and rfcomm connection
+                        startObexServerSession();
+                    } else {
+                        stopObexServerSession();
+                    }
+                } catch (IOException ex) {
+                    Log.e(TAG, "Caught the error: " + ex.toString());
+                }
+            } else {
+                stopObexServerSession();
+            }
+        } else if (action.equals(AUTH_RESPONSE_ACTION)) {
+            String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY);
+            //send auth request
+            notifyAuthKeyInput(sessionkey);
+        } else if (action.equals(AUTH_CANCELLED_ACTION)) {
+            //user cancelled auth request
+            notifyAuthCancelled();
+        } else {
+            removeTimeoutMsg = false;
+        }
+
+        if (removeTimeoutMsg) {
+            mSessionStatusHandler.removeMessages(USER_TIMEOUT);
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        if (VERBOSE) Log.v(TAG, "Map Service onDestroy");
+
+        super.onDestroy();
+        setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
+        if (mWakeLock != null) {
+            mWakeLock.release();
+            mWakeLock = null;
+        }
+        closeService();
+        if(mSessionStatusHandler != null) {
+            mSessionStatusHandler.removeCallbacksAndMessages(null);
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (VERBOSE) Log.v(TAG, "Map Service onBind");
+        return mBinder;
+    }
+
+    private void startRfcommSocketListener() {
+        if (VERBOSE) Log.v(TAG, "Map Service startRfcommSocketListener");
+
+        if (mAcceptThread == null) {
+            mAcceptThread = new SocketAcceptThread();
+            mAcceptThread.setName("BluetoothMapAcceptThread");
+            mAcceptThread.start();
+        }
+    }
+
+    private final boolean initSocket() {
+        if (VERBOSE) Log.v(TAG, "Map Service initSocket");
+
+        boolean initSocketOK = true;
+        final int CREATE_RETRY_TIME = 10;
+
+        // It's possible that create will fail in some cases. retry for 10 times
+        for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {
+            try {
+                // It is mandatory for PSE to support initiation of bonding and
+                // encryption.
+                mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord
+                    ("OBEX  Message Access Server", BluetoothUuid.MAP.getUuid());
+
+            } catch (IOException e) {
+                Log.e(TAG, "Error create RfcommServerSocket " + e.toString());
+                initSocketOK = false;
+            }
+            if (!initSocketOK) {
+                // Need to break out of this loop if BT is being turned off.
+                if (mAdapter == null) break;
+                int state = mAdapter.getState();
+                if ((state != BluetoothAdapter.STATE_TURNING_ON) &&
+                    (state != BluetoothAdapter.STATE_ON)) {
+                    Log.w(TAG, "initServerSocket failed as BT is (being) turned off");
+                    break;
+                }
+                synchronized (this) {
+                    try {
+                        if (VERBOSE) Log.v(TAG, "wait 300 ms");
+                        Thread.sleep(300);
+                    } catch (InterruptedException e) {
+                        Log.e(TAG, "socketAcceptThread thread was interrupted (3)");
+                        mInterrupted = true;
+                    }
+                }
+            } else {
+                break;
+            }
+        }
+
+        if (initSocketOK) {
+            if (VERBOSE) Log.v(TAG, "Succeed to create listening socket ");
+
+        } else {
+            Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
+        }
+        return initSocketOK;
+    }
+
+    private final void closeSocket(boolean server, boolean accept) throws IOException {
+        if (server == true) {
+            // Stop the possible trying to init serverSocket
+            mInterrupted = true;
+
+            if (mServerSocket != null) {
+                mServerSocket.close();
+                mServerSocket = null;
+            }
+        }
+
+        if (accept == true) {
+            if (mConnSocket != null) {
+                mConnSocket.close();
+                mConnSocket = null;
+            }
+        }
+    }
+
+    private final void closeService() {
+        if (VERBOSE) Log.v(TAG, "Map Service closeService in");
+
+        try {
+            closeSocket(true, true);
+        } catch (IOException ex) {
+            Log.e(TAG, "CloseSocket error: " + ex);
+        }
+
+        if (mAcceptThread != null) {
+            try {
+                mAcceptThread.shutdown();
+                mAcceptThread.join();
+                mAcceptThread = null;
+            } catch (InterruptedException ex) {
+                Log.w(TAG, "mAcceptThread close error", ex);
+            }
+        }
+        if (mServerSession != null) {
+            mServerSession.close();
+            mServerSession = null;
+        }
+        if (mBluetoothMnsObexClient != null) {
+            try {
+                mBluetoothMnsObexClient.interrupt();
+                mBluetoothMnsObexClient.join();
+                mBluetoothMnsObexClient = null;
+            } catch (InterruptedException ex) {
+                Log.w(TAG, "mBluetoothMnsObexClient close error", ex);
+            }
+        }
+//      mBluetoothMnsObexClient.shutdown
+
+        mHasStarted = false;
+        if (mStartId != -1 && stopSelfResult(mStartId)) {
+            if (VERBOSE) Log.v(TAG, "successfully stopped map service");
+            mStartId = -1;
+        }
+        if (VERBOSE) Log.v(TAG, "Map Service closeService out");
+    }
+
+    private final void startObexServerSession() throws IOException {
+        if (VERBOSE) Log.v(TAG, "Map Service startObexServerSession");
+
+        // acquire the wakeLock before start Obex transaction thread
+        if (mWakeLock == null) {
+            PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
+            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                    "StartingObexMapTransaction");
+            mWakeLock.setReferenceCounted(false);
+            mWakeLock.acquire();
+        }
+
+
+        mMapServer = new BluetoothMapObexServer(mSessionStatusHandler, this);
+        synchronized (this) {
+            // We need to get authentication now that obex server is up
+            mAuth = new BluetoothMapAuthenticator(mSessionStatusHandler);
+            mAuth.setChallenged(false);
+            mAuth.setCancelled(false);
+        }
+        // setup RFCOMM transport
+        BluetoothMapRfcommTransport transport = new BluetoothMapRfcommTransport(mConnSocket);
+        mServerSession = new ServerSession(transport, mMapServer, mAuth);
+        mBluetoothMnsObexClient = new BluetoothMnsObexClient(this, mRemoteDevice);
+        mBluetoothMnsObexClient.start(); // Initiate the MNS message loop.
+        setState(BluetoothMap.STATE_CONNECTED);
+        if (VERBOSE) {
+            Log.v(TAG, "startObexServerSession() success!");
+        }
+    }
+
+    private void stopObexServerSession() {
+        if (VERBOSE) Log.v(TAG, "Map Service stopObexServerSession");
+
+        // Release the wake lock if obex transaction is over
+        if (mWakeLock != null) {
+            mWakeLock.release();
+            mWakeLock = null;
+        }
+
+        if (mServerSession != null) {
+            mServerSession.close();
+            mServerSession = null;
+        }
+
+        mAcceptThread = null;
+
+        if(mBluetoothMnsObexClient != null) {
+            mBluetoothMnsObexClient.disconnect();
+            mBluetoothMnsObexClient = null;
+        }
+
+        try {
+            closeSocket(false, true);
+            mConnSocket = null;
+        } catch (IOException e) {
+            Log.e(TAG, "closeSocket error: " + e.toString());
+        }
+        // Last obex transaction is finished, we start to listen for incoming
+        // connection again
+        if (mAdapter.isEnabled()) {
+            startRfcommSocketListener();
+        }
+        setState(BluetoothMap.STATE_DISCONNECTED);
+    }
+
+    private void notifyAuthKeyInput(final String key) {
+        synchronized (mAuth) {
+            if (key != null) {
+                mAuth.setSessionKey(key);
+            }
+            mAuth.setChallenged(true);
+            mAuth.notify();
+        }
+    }
+
+    private void notifyAuthCancelled() {
+        synchronized (mAuth) {
+            mAuth.setCancelled(true);
+            mAuth.notify();
+        }
+    }
+
+    /**
+     * A thread that runs in the background waiting for remote rfcomm
+     * connect.Once a remote socket connected, this thread shall be
+     * shutdown.When the remote disconnect,this thread shall run again waiting
+     * for next request.
+     */
+    private class SocketAcceptThread extends Thread {
+
+        private boolean stopped = false;
+
+        @Override
+        public void run() {
+            if (mServerSocket == null) {
+                if (!initSocket()) {
+                    closeService();
+                    return;
+                }
+            }
+
+            while (!stopped) {
+                try {
+                    if (VERBOSE) Log.v(TAG, "Accepting socket connection...");
+                    mConnSocket = mServerSocket.accept();
+                    if (VERBOSE) Log.v(TAG, "Accepted socket connection...");
+
+                    mRemoteDevice = mConnSocket.getRemoteDevice();
+                    if (mRemoteDevice == null) {
+                        Log.i(TAG, "getRemoteDevice() = null");
+                        break;
+                    }
+                    sRemoteDeviceName = mRemoteDevice.getName();
+                    // In case getRemoteName failed and return null
+                    if (TextUtils.isEmpty(sRemoteDeviceName)) {
+                        sRemoteDeviceName = getString(R.string.defaultname);
+                    }
+                    boolean trust = mRemoteDevice.getTrustState();
+                    if (VERBOSE) Log.v(TAG, "GetTrustState() = " + trust);
+
+                    if (trust) {
+                        try {
+                            if (VERBOSE) Log.v(TAG, "incoming connection accepted from: "
+                                + sRemoteDeviceName + " automatically as trusted device");
+                            startObexServerSession();
+                        } catch (IOException ex) {
+                            Log.e(TAG, "catch exception starting obex server session"
+                                    + ex.toString());
+                        }
+                    } else {
+                        Intent intent = new
+                            Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
+                        intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
+                        intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
+                                        BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
+                        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
+                        intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName());
+                        intent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME,
+                                        BluetoothMapReceiver.class.getName());
+                        sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+                        isWaitingAuthorization = true;
+
+                        if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: "
+                                + sRemoteDeviceName);
+
+                    }
+                    stopped = true; // job done ,close this thread;
+                } catch (IOException ex) {
+                    stopped=true;
+                    if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
+                }
+            }
+        }
+
+        void shutdown() {
+            stopped = true;
+            interrupt();
+        }
+    }
+
+    private final Handler mSessionStatusHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
+
+            switch (msg.what) {
+                case START_LISTENER:
+                    if (mAdapter.isEnabled()) {
+                        startRfcommSocketListener();
+                    } else {
+                        closeService();// release all resources
+                    }
+                    break;
+                case USER_TIMEOUT:
+
+                    Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
+                    intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
+                    sendBroadcast(intent);
+                    isWaitingAuthorization = false;
+                    stopObexServerSession();
+                    break;
+                case AUTH_TIMEOUT:
+                    Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
+                    sendBroadcast(i);
+                    removeMapNotification(NOTIFICATION_ID_AUTH);
+                    notifyAuthCancelled();
+                    break;
+                case MSG_SERVERSESSION_CLOSE:
+                    stopObexServerSession();
+                    break;
+                case MSG_SESSION_ESTABLISHED:
+                    break;
+                case MSG_SESSION_DISCONNECTED:
+                    // handled elsewhere
+                    break;
+                case MSG_OBEX_AUTH_CHALL:
+                    createMapNotification(AUTH_CHALL_ACTION);
+                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
+                            .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    private void setState(int state) {
+        setState(state, BluetoothMap.RESULT_SUCCESS);
+    }
+
+    private synchronized void setState(int state, int result) {
+        if (state != mState) {
+            if (DEBUG) Log.d(TAG, "Map state " + mState + " -> " + state + ", result = "
+                    + result);
+            int prevState = mState;
+            mState = state;
+            Intent intent = new Intent(BluetoothMap.MAP_STATE_CHANGED_ACTION);
+            intent.putExtra(BluetoothMap.MAP_PREVIOUS_STATE, prevState);
+            intent.putExtra(BluetoothMap.MAP_STATE, mState);
+            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
+            sendBroadcast(intent, BLUETOOTH_PERM);
+            AdapterService s = AdapterService.getAdapterService();
+            if (s != null) {
+                s.onProfileConnectionStateChanged(mRemoteDevice, BluetoothProfile.MAP,
+                        mState, prevState);
+            }
+        }
+    }
+
+    private void createMapNotification(String action) {
+
+        NotificationManager nm = (NotificationManager)
+            getSystemService(Context.NOTIFICATION_SERVICE);
+
+        // Create an intent triggered by clicking on the status icon.
+        Intent clickIntent = new Intent();
+        clickIntent.setClass(this, BluetoothMapActivity.class);
+        clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        clickIntent.setAction(action);
+
+        // Create an intent triggered by clicking on the
+        // "Clear All Notifications" button
+        Intent deleteIntent = new Intent();
+        deleteIntent.setClass(this, BluetoothMapReceiver.class);
+
+        Notification notification = null;
+        String name = getRemoteDeviceName();
+
+        if (action.equals(AUTH_CHALL_ACTION)) {
+            deleteIntent.setAction(AUTH_CANCELLED_ACTION);
+            notification = new Notification.Builder(this)
+                               .setContentTitle(getString(R.string.auth_notif_title))
+                               .setContentText(getString(R.string.auth_notif_message,name))
+                               .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
+                               .build();
+
+            notification.flags |= Notification.FLAG_AUTO_CANCEL;
+            notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
+            notification.defaults = Notification.DEFAULT_SOUND;
+            notification.deleteIntent = PendingIntent.getBroadcast(this, 0, deleteIntent, 0);
+            nm.notify(NOTIFICATION_ID_AUTH, notification);
+        }
+    }
+
+    private void removeMapNotification(int id) {
+        NotificationManager nm = (NotificationManager)
+            getSystemService(Context.NOTIFICATION_SERVICE);
+        nm.cancel(id);
+    }
+
+    public static String getRemoteDeviceName() {
+        return sRemoteDeviceName;
+    }
+
+    /**
+     * Handlers for incoming service calls
+     */
+    private final IBluetoothMap.Stub mBinder = new IBluetoothMap.Stub() {
+        public int getState() {
+            if (DEBUG) Log.d(TAG, "getState " + mState);
+
+            if (!Utils.checkCaller()) {
+                Log.w(TAG,"getState(): not allowed for non-active user");
+                return BluetoothMap.STATE_DISCONNECTED;
+            }
+
+            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            return mState;
+        }
+
+        public BluetoothDevice getClient() {
+            if (DEBUG) Log.d(TAG, "getClient" + mRemoteDevice);
+
+            if (!Utils.checkCaller()) {
+                Log.w(TAG,"getClient(): not allowed for non-active user");
+                return null;
+            }
+
+            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            if (mState == BluetoothMap.STATE_DISCONNECTED) {
+                return null;
+            }
+            return mRemoteDevice;
+        }
+
+        public boolean isConnected(BluetoothDevice device) {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG,"isConnected(): not allowed for non-active user");
+                return false;
+            }
+
+            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            return mState == BluetoothMap.STATE_CONNECTED && mRemoteDevice.equals(device);
+        }
+
+        public boolean connect(BluetoothDevice device) {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG,"connect(): not allowed for non-active user");
+                return false;
+            }
+
+            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                    "Need BLUETOOTH_ADMIN permission");
+            return false;
+        }
+
+        public void disconnect() {
+            if (DEBUG) Log.d(TAG, "disconnect");
+
+            if (!Utils.checkCaller()) {
+                Log.w(TAG,"disconnect(): not allowed for non-active user");
+                return;
+            }
+
+            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                    "Need BLUETOOTH_ADMIN permission");
+            synchronized (BluetoothMapService.this) {
+                switch (mState) {
+                    case BluetoothMap.STATE_CONNECTED:
+                        if (mServerSession != null) {
+                            mServerSession.close();
+                            mServerSession = null;
+                        }
+                        try {
+                            closeSocket(false, true);
+                            mConnSocket = null;
+                        } catch (IOException ex) {
+                            Log.e(TAG, "Caught the error: " + ex);
+                        }
+                        setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+    };
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java b/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java
new file mode 100644
index 0000000..7f87cd7
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java
@@ -0,0 +1,721 @@
+/*
+* 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 static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
+import static com.android.internal.telephony.SmsConstants.ENCODING_7BIT;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Random;
+
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsMessage;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.telephony.*;
+/*import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsConstants;*/
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
+import com.android.internal.telephony.cdma.sms.*;
+import com.android.internal.telephony.gsm.SmsMessage.SubmitPdu;
+
+public class BluetoothMapSmsPdu {
+
+    private static final String TAG = "BluetoothMapSmsPdu";
+    private static final boolean V = true;
+    private static int INVALID_VALUE = -1;
+    public static int SMS_TYPE_GSM = 1;
+    public static int SMS_TYPE_CDMA = 2;
+
+
+    /* TODO: We need to handle the SC-address mentioned in errata 4335.
+     * Since the definition could be read in three different ways, I have asked
+     * the car working group for clarification, and are awaiting confirmation that
+     * this clarification will go into the MAP spec:
+     *  "The native format should be <sc_addr><tpdu> where <sc_addr> is <length><ton><1..10 octet of address>
+     *   coded according to 24.011. The IEI is not to be used, as the fixed order of the data makes a type 4 LV
+     *   information element sufficient. <length> is a single octet which value is the length of the value-field
+     *   in octets including both the <ton> and the <address>."
+     * */
+
+
+    public static class SmsPdu {
+        private byte[] data;
+        private byte[] scAddress = {0}; // At the moment we do not use the scAddress, hence set the length to 0.
+        private int userDataMsgOffset = 0;
+        private int encoding;
+        private int languageTable;
+        private int languageShiftTable;
+        private int type;
+
+        /* Members used for pdu decoding */
+        private int userDataSeptetPadding = INVALID_VALUE;
+        private int msgSeptetCount = 0;
+
+        SmsPdu(byte[] data, int type){
+            this.data = data;
+            this.encoding = INVALID_VALUE;
+            this.type = type;
+            this.languageTable = INVALID_VALUE;
+            this.languageShiftTable = INVALID_VALUE;
+            this.userDataMsgOffset = gsmSubmitGetTpUdOffset(); // Assume no user data header
+        }
+
+        /**
+         * Create a pdu instance based on the data generated on this device.
+         * @param data
+         * @param encoding
+         * @param type
+         * @param languageTable
+         */
+        SmsPdu(byte[]data, int encoding, int type, int languageTable){
+            this.data = data;
+            this.encoding = encoding;
+            this.type = type;
+            this.languageTable = languageTable;
+        }
+        public byte[] getData(){
+            return data;
+        }
+        public byte[] getScAddress(){
+            return scAddress;
+        }
+        public void setEncoding(int encoding) {
+            this.encoding = encoding;
+        }
+        public int getEncoding(){
+            return encoding;
+        }
+        public int getType(){
+            return type;
+        }
+        public int getUserDataMsgOffset() {
+            return userDataMsgOffset;
+        }
+        /** The user data message payload size in bytes - excluding the user data header. */
+        public int getUserDataMsgSize() {
+            return data.length - userDataMsgOffset;
+        }
+
+        public int getLanguageShiftTable() {
+            return languageShiftTable;
+        }
+
+        public int getLanguageTable() {
+            return languageTable;
+        }
+
+        public int getUserDataSeptetPadding() {
+            return userDataSeptetPadding;
+        }
+
+        public int getMsgSeptetCount() {
+            return msgSeptetCount;
+        }
+
+
+        /* PDU parsing/modification functionality */
+        private final static byte TELESERVICE_IDENTIFIER                    = 0x00;
+        private final static byte SERVICE_CATEGORY                          = 0x01;
+        private final static byte ORIGINATING_ADDRESS                       = 0x02;
+        private final static byte ORIGINATING_SUB_ADDRESS                   = 0x03;
+        private final static byte DESTINATION_ADDRESS                       = 0x04;
+        private final static byte DESTINATION_SUB_ADDRESS                   = 0x05;
+        private final static byte BEARER_REPLY_OPTION                       = 0x06;
+        private final static byte CAUSE_CODES                               = 0x07;
+        private final static byte BEARER_DATA                               = 0x08;
+
+        /**
+         * Find and return the offset to the specified parameter ID
+         * @param parameterId The parameter ID to find
+         * @return the offset in number of bytes to the parameterID entry in the pdu data.
+         * The byte at the offset contains the parameter ID, the byte following contains the
+         * parameter length, and offset + 2 is the first byte of the parameter data.
+         */
+        private int cdmaGetParameterOffset(byte parameterId) {
+            ByteArrayInputStream pdu = new ByteArrayInputStream(data);
+            int offset = 0;
+            boolean found = false;
+
+            try {
+                pdu.skip(1); // Skip the message type
+
+                while (pdu.available() > 0) {
+                    int currentId = pdu.read();
+                    int currentLen = pdu.read();
+
+                    if(currentId == parameterId) {
+                        found = true;
+                        break;
+                    }
+                    else {
+                        pdu.skip(currentLen);
+                        offset += 2 + currentLen;
+                    }
+                }
+                pdu.close();
+            } catch (Exception e) {
+                Log.e(TAG, "cdmaGetParameterOffset: ", e);
+            }
+
+            if(found)
+                return offset;
+            else
+                return 0;
+        }
+
+        private final static byte BEARER_DATA_MSG_ID = 0x00;
+
+        private int cdmaGetSubParameterOffset(byte subParameterId) {
+            ByteArrayInputStream pdu = new ByteArrayInputStream(data);
+            int offset = 0;
+            boolean found = false;
+            offset = cdmaGetParameterOffset(BEARER_DATA) + 2; // Add to offset the BEARER_DATA parameter id and length bytes
+            pdu.skip(offset);
+            try {
+
+                while (pdu.available() > 0) {
+                    int currentId = pdu.read();
+                    int currentLen = pdu.read();
+
+                    if(currentId == subParameterId) {
+                        found = true;
+                        break;
+                    }
+                    else {
+                        pdu.skip(currentLen);
+                        offset += 2 + currentLen;
+                    }
+                }
+                pdu.close();
+            } catch (Exception e) {
+                Log.e(TAG, "cdmaGetParameterOffset: ", e);
+            }
+
+            if(found)
+                return offset;
+            else
+                return 0;
+        }
+
+
+        public void cdmaChangeToDeliverPdu(long date){
+            /* Things to change:
+             *  - Message Type in bearer data (Not the overall point-to-point type)
+             *  - Change address ID from destination to originating (sub addresses are not used)
+             *  - A time stamp is not mandatory.
+             */
+            int offset;
+            offset = cdmaGetParameterOffset(DESTINATION_ADDRESS);
+            data[offset] = ORIGINATING_ADDRESS;
+            offset = cdmaGetParameterOffset(DESTINATION_SUB_ADDRESS);
+            data[offset] = ORIGINATING_SUB_ADDRESS;
+
+            offset = cdmaGetSubParameterOffset(BEARER_DATA_MSG_ID);
+
+//            if(data != null && data.length > 2) {
+                int tmp = data[offset+2] & 0xff; // Skip the subParam ID and length, and read the first byte.
+                // Mask out the type
+                tmp &= 0x0f;
+                // Set the new type
+                tmp |= ((BearerData.MESSAGE_TYPE_DELIVER << 4) & 0xf0);
+                // Store the result
+                data[offset+2] = (byte) tmp;
+
+//            }
+                //TODO: Error handling.
+                /* TODO: Do we need to change anything in the user data? Not sure if the user data is
+                 *        just encoded using GSM encoding, or it is an actual GSM submit PDU embedded
+                 *        in the user data?
+                 */
+
+        }
+
+        private static final byte TP_MIT_DELIVER       = 0x00; // bit 0 and 1
+        private static final byte TP_MMS_NO_MORE       = 0x04; // bit 2
+        private static final byte TP_RP_NO_REPLY_PATH  = 0x00; // bit 7
+        private static final byte TP_UDHI_MASK         = 0x20; // bit 6
+        private static final byte TP_SRI_NO_REPORT     = 0x00; // bit 5
+
+        private int gsmSubmitGetTpPidOffset() {
+            /* calculate the offset to TP_PID and return the TP_PID byte.
+             * The TP-DA has variable length, and the length excludes the 2 byte length and type headers.
+             * The TP-DA is two bytes within the PDU */
+            int offset = 2 + (data[2] & 0xff) + 2; //
+            if((offset > data.length) || (offset > (2 + 12))) // max length of TP_DA is 12 bytes + two byte offset
+                throw new IllegalArgumentException("wrongly formatted gsm submit PDU");
+            return offset;
+        }
+
+        public int gsmSubmitGetTpDcs() {
+            return data[gsmSubmitGetTpDcsOffset()] & 0xff;
+        }
+
+        public boolean gsmSubmitHasUserDataHeader() {
+            return ((data[0] & 0xff) & TP_UDHI_MASK) == TP_UDHI_MASK;
+        }
+
+        private int gsmSubmitGetTpDcsOffset() {
+            return gsmSubmitGetTpPidOffset() + 1;
+        }
+
+        private int gsmSubmitGetTpUdlOffset() {
+            switch(((data[0]  & 0xff) & (0x08 | 0x04))>>2) {
+            case 0: // Not TP-VP present
+                return gsmSubmitGetTpPidOffset() + 2;
+            case 1: // TP-VP relative format
+                return gsmSubmitGetTpPidOffset() + 2 + 1;
+            case 2: // TP-VP enhanced format
+            case 3: // TP-VP absolute format
+                break;
+            }
+            return gsmSubmitGetTpPidOffset() + 2 + 7;
+        }
+        private int gsmSubmitGetTpUdOffset() {
+            return gsmSubmitGetTpUdlOffset() + 1;
+        }
+
+        public void gsmDecodeUserDataHeader() {
+            ByteArrayInputStream pdu = new ByteArrayInputStream(data);
+
+            pdu.skip(gsmSubmitGetTpUdlOffset());
+            int userDataLength = pdu.read();
+            int userDataHeaderLength = pdu.read();
+
+            // This part is only needed to extract the language info, hence only needed for 7 bit encoding
+            if(encoding == SmsConstants.ENCODING_7BIT)
+            {
+                byte[] udh = new byte[userDataHeaderLength];
+                try {
+                    pdu.read(udh);
+                } catch (IOException e) {
+                    Log.w(TAG, "unable to read userDataHeader", e);
+                }
+                SmsHeader userDataHeader = SmsHeader.fromByteArray(udh);
+                languageTable = userDataHeader.languageTable;
+                languageShiftTable = userDataHeader.languageShiftTable;
+
+                int headerBits = (userDataHeaderLength + 1) * 8;
+                int headerSeptets = headerBits / 7;
+                headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
+                userDataSeptetPadding = (headerSeptets * 7) - headerBits;
+                msgSeptetCount = userDataLength - headerSeptets;
+            }
+            userDataMsgOffset = gsmSubmitGetTpUdOffset() + userDataHeaderLength + 1; // Add the byte containing the length
+        }
+
+        private void gsmWriteDate(ByteArrayOutputStream header, long time) throws UnsupportedEncodingException {
+            SimpleDateFormat format = new SimpleDateFormat("yyMMddHHmmss");
+            Date date = new Date(time);
+            String timeStr = format.format(date); // Format to YYMMDDTHHMMSS UTC time
+            byte[] timeChars = timeStr.getBytes("US-ASCII");
+
+            for(int i = 0, n = timeStr.length()/2; i < n; i++) {
+                header.write((timeChars[i+1]-0x30) << 4 | (timeChars[i]-0x30)); // Offset from ascii char to decimal value
+            }
+
+            Calendar cal = Calendar.getInstance();
+            int offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / (15 * 60 * 1000); /* offset in quarters of an hour */
+            String offsetString;
+            if(offset < 0) {
+                offsetString = String.format("%1$02d", -(offset));
+                char[] offsetChars = offsetString.toCharArray();
+                header.write((offsetChars[1]-0x30) << 4 | 0x40 | (offsetChars[0]-0x30));
+            }
+            else {
+                offsetString = String.format("%1$02d", offset);
+                char[] offsetChars = offsetString.toCharArray();
+                header.write((offsetChars[1]-0x30) << 4 | (offsetChars[0]-0x30));
+            }
+        }
+
+/*        private void gsmSubmitExtractUserData() {
+            int userDataLength = data[gsmSubmitGetTpUdlOffset()];
+            userData = new byte[userDataLength];
+            System.arraycopy(userData, 0, data, gsmSubmitGetTpUdOffset(), userDataLength);
+
+        }*/
+
+        /**
+         * Change the GSM Submit Pdu data in this object to a deliver PDU:
+         *  - Build the new header with deliver PDU type, originator and time stamp.
+         *  - Extract encoding details from the submit PDU
+         *  - Extract user data length and user data from the submitPdu
+         *  - Build the new PDU
+         * @param date the time stamp to include (The value is the number of milliseconds since Jan. 1, 1970 GMT.)
+         * @param originator the phone number to include in the deliver PDU header. Any undesired characters,
+         *                    such as '-' will be striped from this string.
+         */
+        public void gsmChangeToDeliverPdu(long date, String originator)
+        {
+            ByteArrayOutputStream newPdu = new ByteArrayOutputStream(22); // 22 is the max length of the deliver pdu header
+            byte[] encodedAddress;
+            int userDataLength = 0;
+            try {
+                newPdu.write(TP_MIT_DELIVER | TP_MMS_NO_MORE | TP_RP_NO_REPLY_PATH | TP_SRI_NO_REPORT
+                             | (data[0] & 0xff)  & TP_UDHI_MASK);
+                encodedAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(originator);
+                // Insert originator address into the header - this includes the length
+                newPdu.write(encodedAddress);
+                newPdu.write(data[gsmSubmitGetTpPidOffset()]);
+                newPdu.write(data[gsmSubmitGetTpDcsOffset()]);
+                // Generate service center time stamp
+                gsmWriteDate(newPdu, date);
+                userDataLength = (data[gsmSubmitGetTpUdlOffset()] & 0xff);
+                newPdu.write(userDataLength);
+                // Copy the pdu user data - keep in mind that the userDataLength is not the length in bytes for 7-bit encoding.
+                newPdu.write(data, gsmSubmitGetTpUdOffset(), data.length - gsmSubmitGetTpUdOffset());
+            } catch (IOException e) {
+                Log.e(TAG, "", e);
+                throw new IllegalArgumentException("Failed to change type to deliver PDU."); // TODO: Is this the best way to handle this error? - which cannot occur...
+            }
+            data = newPdu.toByteArray();
+        }
+
+        /* SMS encoding to bmessage strings */
+        /** get the encoding type as a bMessage string */
+        public String getEncodingString(){
+            if(type == SMS_TYPE_GSM)
+            {
+                switch(encoding){
+                case SmsMessage.ENCODING_7BIT:
+                    if(languageTable == 0)
+                        return "G-7BIT";
+                    else
+                        return "G-7BITEXT";
+                case SmsMessage.ENCODING_8BIT:
+                    return "G-8BIT";
+                case SmsMessage.ENCODING_16BIT:
+                    return "G-16BIT";
+                case SmsMessage.ENCODING_UNKNOWN:
+                    default:
+                    return "";
+                }
+            } else /* SMS_TYPE_CDMA */ {
+                switch(encoding){
+                case SmsMessage.ENCODING_7BIT:
+                    return "C-7ASCII";
+                case SmsMessage.ENCODING_8BIT:
+                    return "C-8BIT";
+                case SmsMessage.ENCODING_16BIT:
+                    return "C-UNICODE";
+                case SmsMessage.ENCODING_KSC5601:
+                    return "C-KOREAN";
+                case SmsMessage.ENCODING_UNKNOWN:
+                    default:
+                    return "";
+                }
+            }
+        }
+    }
+
+    private static int sConcatenatedRef = new Random().nextInt(256);
+
+    protected static int getNextConcatenatedRef() {
+        sConcatenatedRef += 1;
+        return sConcatenatedRef;
+    }
+    public static ArrayList<SmsPdu> getSubmitPdus(String messageText, String address){
+        /* Use the generic GSM/CDMA SMS Message functionality within Android to generate the
+         * SMS PDU's as once generated to send the SMS message.
+         */
+
+        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); // TODO: Change to use: ((TelephonyManager)myContext.getSystemService(Context.TELEPHONY_SERVICE))
+        int phoneType;
+        GsmAlphabet.TextEncodingDetails ted = (PHONE_TYPE_CDMA == activePhone) ?
+            com.android.internal.telephony.cdma.SmsMessage.calculateLength((CharSequence)messageText, false) :
+            com.android.internal.telephony.gsm.SmsMessage.calculateLength((CharSequence)messageText, false);
+
+        SmsPdu newPdu;
+        String destinationAddress;
+        int msgCount = ted.msgCount;
+        int encoding;
+        int languageTable;
+        int languageShiftTable;
+        int refNumber = getNextConcatenatedRef() & 0x00FF;
+        ArrayList<String> smsFragments = SmsMessage.fragmentText(messageText);
+        ArrayList<SmsPdu> pdus = new ArrayList<SmsPdu>(msgCount);
+        byte[] data;
+
+        // Default to GSM, as this code should not be used, if we neither have CDMA not GSM.
+        phoneType = (activePhone == PHONE_TYPE_CDMA) ? SMS_TYPE_CDMA : SMS_TYPE_GSM;
+        encoding = ted.codeUnitSize;
+        languageTable = ted.languageTable;
+        languageShiftTable = ted.languageShiftTable;
+        destinationAddress = PhoneNumberUtils.stripSeparators(address);
+        if(destinationAddress == null || destinationAddress.length() < 2) {
+            destinationAddress = "12"; // Ensure we add a number at least 2 digits as specified in the GSM spec.
+        }
+
+        if(msgCount == 1){
+            data = SmsMessage.getSubmitPdu(null, destinationAddress, smsFragments.get(0), false).encodedMessage;
+            newPdu = new SmsPdu(data, encoding, phoneType, languageTable);
+            pdus.add(newPdu);
+        }
+
+        /* This code is a reduced copy of the actual code used in the Android SMS sub system,
+         * hence the comments have been left untouched. */
+        for(int i = 0; i < msgCount; i++){
+            SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
+            concatRef.refNumber = refNumber;
+            concatRef.seqNumber = i + 1;  // 1-based sequence
+            concatRef.msgCount = msgCount;
+            // TODO: We currently set this to true since our messaging app will never
+            // send more than 255 parts (it converts the message to MMS well before that).
+            // However, we should support 3rd party messaging apps that might need 16-bit
+            // references
+            // Note:  It's not sufficient to just flip this bit to true; it will have
+            // ripple effects (several calculations assume 8-bit ref).
+            concatRef.isEightBits = true;
+            SmsHeader smsHeader = new SmsHeader();
+            smsHeader.concatRef = concatRef;
+
+            /* Depending on the type, call either GSM or CDMA getSubmitPdu(). The encoding
+             * will be determined(again) by getSubmitPdu().
+             * All packets need to be encoded using the same encoding, as the bMessage
+             * only have one filed to describe the encoding for all messages in a concatenated
+             * SMS... */
+            if (encoding == SmsConstants.ENCODING_7BIT) {
+                smsHeader.languageTable = languageTable;
+                smsHeader.languageShiftTable = languageShiftTable;
+            }
+
+            if(phoneType == SMS_TYPE_GSM){
+                data = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null, destinationAddress,
+                        smsFragments.get(i), false, SmsHeader.toByteArray(smsHeader),
+                        encoding, languageTable, languageShiftTable).encodedMessage;
+            } else { // SMS_TYPE_CDMA
+                UserData uData = new UserData();
+                uData.payloadStr = smsFragments.get(i);
+                uData.userDataHeader = smsHeader;
+                if (encoding == SmsConstants.ENCODING_7BIT) {
+                    uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
+                } else { // assume UTF-16
+                    uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+                }
+                uData.msgEncodingSet = true;
+                data = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(destinationAddress,
+                        uData, false).encodedMessage;
+            }
+            newPdu = new SmsPdu(data, encoding, phoneType, languageTable);
+            pdus.add(newPdu);
+        }
+
+        return pdus;
+    }
+
+    /**
+     * Generate a list of deliver PDUs. The messageText and address parameters must be different from null,
+     * for CDMA the date can be omitted (and will be ignored if supplied)
+     * @param messageText The text to include.
+     * @param address The originator address.
+     * @param date The delivery time stamp.
+     * @return
+     */
+    public static ArrayList<SmsPdu> getDeliverPdus(String messageText, String address, long date){
+        ArrayList<SmsPdu> deliverPdus = getSubmitPdus(messageText, address);
+
+        /*
+         * For CDMA the only difference between deliver and submit pdus are the messageType,
+         * which is set in encodeMessageId, (the higher 4 bits of the 1st byte
+         * of the Message identification sub parameter data.) and the address type.
+         *
+         * For GSM, a larger part of the header needs to be generated.
+         */
+        for(SmsPdu currentPdu : deliverPdus){
+            if(currentPdu.getType() == SMS_TYPE_CDMA){
+                currentPdu.cdmaChangeToDeliverPdu(date);
+            } else { /* SMS_TYPE_GSM */
+                currentPdu.gsmChangeToDeliverPdu(date, address);
+            }
+        }
+
+        return deliverPdus;
+    }
+
+    public static void testSendRawPdu(SmsPdu pdu){
+        if(pdu.getType() == SMS_TYPE_CDMA){
+            /* TODO: Try to send the message using SmsManager.sendData()?*/
+        }else {
+
+        }
+    }
+
+    /**
+     * The decoding only supports decoding the actual textual content of the PDU received
+     * from the MAP client. (As the Android system has no interface to send pre encoded PDUs)
+     * The destination address must be extracted from the bmessage vCard(s).
+     */
+    public static String decodePdu(byte[] data, int type) {
+        String ret;
+        if(type == SMS_TYPE_CDMA) {
+            /* This is able to handle both submit and deliver PDUs */
+            ret = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(0, data).getMessageBody();
+        } else {
+            /* For GSM, there is no submit pdu decoder, and most parser utils are private, and only minded for submit pdus */
+            ret = gsmParseSubmitPdu(data);
+        }
+        return ret;
+    }
+
+    /* At the moment we do not support using a SC-address. Use this function to strip off
+     * the SC-address before parsing it to the SmsPdu. (this was added in errata 4335)
+     */
+    private static byte[] gsmStripOffScAddress(byte[] data) {
+        /* The format of a native GSM SMS is: <sc-address><pdu> where sc-address is:
+         * <length-byte><type-byte><number-bytes> */
+        int addressLength = data[0] & 0xff; // Treat the byte value as an unsigned value
+        if(addressLength >= data.length) // TODO: We could verify that the address-length is no longer than 11 bytes
+            throw new IllegalArgumentException("Length of address exeeds the length of the PDU data.");
+        int pduLength = data.length-(1+addressLength);
+        byte[] newData = new byte[pduLength];
+        System.arraycopy(data, 1+addressLength, newData, 0, pduLength);
+        return newData;
+    }
+
+    private static String gsmParseSubmitPdu(byte[] data) {
+        /* Things to do:
+         *  - extract hasUsrData bit
+         *  - extract TP-DCS -> Character set, compressed etc.
+         *  - extract user data header to get the language properties
+         *  - extract user data
+         *  - decode the string */
+        //Strip off the SC-address before parsing
+        SmsPdu pdu = new SmsPdu(gsmStripOffScAddress(data), SMS_TYPE_GSM);
+        boolean userDataCompressed = false;
+        int dataCodingScheme = pdu.gsmSubmitGetTpDcs();
+        int encodingType =  SmsConstants.ENCODING_UNKNOWN;
+        String messageBody = null;
+
+        // Look up the data encoding scheme
+        if ((dataCodingScheme & 0x80) == 0) {
+            // Bits 7..4 == 0xxx
+            userDataCompressed = (0 != (dataCodingScheme & 0x20));
+
+            if (userDataCompressed) {
+                Log.w(TAG, "4 - Unsupported SMS data coding scheme "
+                        + "(compression) " + (dataCodingScheme & 0xff));
+            } else {
+                switch ((dataCodingScheme >> 2) & 0x3) {
+                case 0: // GSM 7 bit default alphabet
+                    encodingType =  SmsConstants.ENCODING_7BIT;
+                    break;
+
+                case 2: // UCS 2 (16bit)
+                    encodingType =  SmsConstants.ENCODING_16BIT;
+                    break;
+
+                case 1: // 8 bit data
+                case 3: // reserved
+                    Log.w(TAG, "1 - Unsupported SMS data coding scheme "
+                            + (dataCodingScheme & 0xff));
+                    encodingType =  SmsConstants.ENCODING_8BIT;
+                    break;
+                }
+            }
+        } else if ((dataCodingScheme & 0xf0) == 0xf0) {
+            userDataCompressed = false;
+
+            if (0 == (dataCodingScheme & 0x04)) {
+                // GSM 7 bit default alphabet
+                encodingType =  SmsConstants.ENCODING_7BIT;
+            } else {
+                // 8 bit data
+                encodingType =  SmsConstants.ENCODING_8BIT;
+            }
+        } else if ((dataCodingScheme & 0xF0) == 0xC0
+                || (dataCodingScheme & 0xF0) == 0xD0
+                || (dataCodingScheme & 0xF0) == 0xE0) {
+            // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+
+            // 0xC0 == 7 bit, don't store
+            // 0xD0 == 7 bit, store
+            // 0xE0 == UCS-2, store
+
+            if ((dataCodingScheme & 0xF0) == 0xE0) {
+                encodingType =  SmsConstants.ENCODING_16BIT;
+            } else {
+                encodingType =  SmsConstants.ENCODING_7BIT;
+            }
+
+            userDataCompressed = false;
+
+            // bit 0x04 reserved
+        } else if ((dataCodingScheme & 0xC0) == 0x80) {
+            // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+            // 0x80..0xBF == Reserved coding groups
+            if (dataCodingScheme == 0x84) {
+                // This value used for KSC5601 by carriers in Korea.
+                encodingType =  SmsConstants.ENCODING_KSC5601;
+            } else {
+                Log.w(TAG, "5 - Unsupported SMS data coding scheme "
+                        + (dataCodingScheme & 0xff));
+            }
+        } else {
+            Log.w(TAG, "3 - Unsupported SMS data coding scheme "
+                    + (dataCodingScheme & 0xff));
+        }
+
+        /* TODO: This is NOT good design - to have the pdu class being depending on these two function calls.
+         *        - move the encoding extraction into the pdu class */
+        pdu.setEncoding(encodingType);
+        if(pdu.gsmSubmitHasUserDataHeader()) {
+            pdu.gsmDecodeUserDataHeader();
+        }
+
+        try {
+            switch (encodingType) {
+            case  SmsConstants.ENCODING_UNKNOWN:
+            case  SmsConstants.ENCODING_8BIT:
+                messageBody = null;
+                break;
+
+            case  SmsConstants.ENCODING_7BIT:
+                messageBody = GsmAlphabet.gsm7BitPackedToString(pdu.getData(), pdu.getUserDataMsgOffset(),
+                                pdu.getMsgSeptetCount(), pdu.getUserDataSeptetPadding(), pdu.getLanguageTable(),
+                                pdu.getLanguageShiftTable());
+
+                break;
+
+            case  SmsConstants.ENCODING_16BIT:
+                messageBody = new String(pdu.getData(), pdu.getUserDataMsgOffset(), pdu.getUserDataMsgSize(), "utf-16");
+                break;
+
+            case SmsConstants.ENCODING_KSC5601:
+                messageBody = new String(pdu.getData(), pdu.getUserDataMsgOffset(), pdu.getUserDataMsgSize(), "KSC5601");
+
+                break;
+            }
+        } catch (UnsupportedEncodingException e) {
+            Log.e(TAG, "Unsupported encoding type???", e); // This should never happen.
+            return null;
+        }
+
+        return messageBody;
+    }
+
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapUtils.java b/src/com/android/bluetooth/map/BluetoothMapUtils.java
new file mode 100644
index 0000000..e57cf16
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -0,0 +1,116 @@
+/*
+* 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;
+
+
+/**
+ * Various utility methods and generic defines that can be used throughout MAPS
+ */
+public class BluetoothMapUtils {
+
+    private static final String TAG = "MapUtils";
+    private static final boolean V = BluetoothMapService.VERBOSE;
+    /* We use the upper 5 bits for the type mask - avoid using the top bit, since it
+     * indicates a negative value, hence corrupting the formatter when converting to
+     * type String. (I really miss the unsigned type in Java:))
+     */
+    private static final long HANDLE_TYPE_MASK            = 0xf<<59;
+    private static final long HANDLE_TYPE_MMS_MASK        = 0x1<<59;
+    private static final long HANDLE_TYPE_EMAIL_MASK      = 0x2<<59;
+    private static final long HANDLE_TYPE_SMS_GSM_MASK    = 0x4<<59;
+    private static final long HANDLE_TYPE_SMS_CDMA_MASK   = 0x8<<59;
+
+    /**
+     * 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{
+        EMAIL,
+        SMS_GSM,
+        SMS_CDMA,
+        MMS
+    }
+
+    /**
+     * 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
+     */
+    static public String getMapHandle(long cpHandle, TYPE messageType){
+        String mapHandle = "-1";
+        switch(messageType)
+        {
+            case MMS:
+                mapHandle = String.format("%016X",(cpHandle | HANDLE_TYPE_MMS_MASK));
+                break;
+            case SMS_GSM:
+                mapHandle = String.format("%016X",cpHandle | HANDLE_TYPE_SMS_GSM_MASK);
+                break;
+            case SMS_CDMA:
+                mapHandle = String.format("%016X",cpHandle | HANDLE_TYPE_SMS_CDMA_MASK);
+                break;
+            case EMAIL:
+                mapHandle = String.format("%016X",(cpHandle | HANDLE_TYPE_EMAIL_MASK)); //TODO correct when email support is implemented
+                break;
+                default:
+                    throw new IllegalArgumentException("Message type not supported");
+        }
+        return mapHandle;
+
+    }
+
+    /**
+     * Convert a handle string the the raw long representation, including the type bit.
+     * @param mapHandle the handle string
+     * @return the handle value
+     */
+    static public long getMsgHandleAsLong(String mapHandle){
+        return Long.parseLong(mapHandle, 16);
+    }
+    /**
+     * Convert a Map Handle into a content provider Handle
+     * @param mapHandle handle to convert from
+     * @return content provider handle without message type mask
+     */
+    static public long getCpHandle(String mapHandle)
+    {
+        long cpHandle = getMsgHandleAsLong(mapHandle);
+        /* remove masks as the call should already know what type of message this handle is for */
+        cpHandle &= ~HANDLE_TYPE_MASK;
+        return cpHandle;
+    }
+
+    /**
+     * Extract the message type from the handle.
+     * @param mapHandle
+     * @return
+     */
+    static public TYPE getMsgTypeFromHandle(String mapHandle) {
+        long cpHandle = getMsgHandleAsLong(mapHandle);
+
+        if((cpHandle & HANDLE_TYPE_MMS_MASK) != 0)
+            return TYPE.MMS;
+        if((cpHandle & HANDLE_TYPE_EMAIL_MASK) != 0)
+            return TYPE.EMAIL;
+        if((cpHandle & HANDLE_TYPE_SMS_GSM_MASK) != 0)
+            return TYPE.SMS_GSM;
+        if((cpHandle & HANDLE_TYPE_SMS_CDMA_MASK) != 0)
+            return TYPE.SMS_CDMA;
+
+        throw new IllegalArgumentException("Message type not found in handle string.");
+    }
+}
+
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessage.java b/src/com/android/bluetooth/map/BluetoothMapbMessage.java
new file mode 100644
index 0000000..084ebed
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessage.java
@@ -0,0 +1,778 @@
+/*
+* Copyright (C) 2013 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.android.bluetooth.map;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+
+public abstract class BluetoothMapbMessage {
+
+    protected static String TAG = "BluetoothMapbMessage";
+    protected static final boolean D = true;
+    protected static final boolean V = true;
+    private static final String VERSION = "VERSION:1.0";
+
+    public static int INVALID_VALUE = -1;
+
+    protected int appParamCharset = BluetoothMapAppParams.INVALID_VALUE_PARAMETER;
+
+    // TODO: Reevaluate if strings are the best types for the members.
+
+    /* BMSG attributes */
+    private String status = null; // READ/UNREAD
+    protected TYPE type = null;   // SMS/MMS/EMAIL
+
+    private String folder = null;
+
+    /* BBODY attributes */
+    private long partId = INVALID_VALUE;
+    protected String encoding = null;
+    protected String charset = null;
+    private String language = null;
+
+    private int bMsgLength = INVALID_VALUE;
+
+    private ArrayList<vCard> originator = null;
+    private ArrayList<vCard> recipient = null;
+
+
+    public static class vCard {
+        /* VCARD attributes */
+        private String version;
+        private String name = null;
+        private String formattedName = null;
+        private String[] phoneNumbers = {};
+        private String[] emailAddresses = {};
+        private int envLevel = 0;
+
+        /**
+         * Construct a version 3.0 vCard
+         * @param name Structured
+         * @param formattedName Formatted name
+         * @param phoneNumbers a String[] of phone numbers
+         * @param emailAddresses a String[] of email addresses
+         * @param the bmessage envelope level (0 is the top/most outer level)
+         */
+        public vCard(String name, String formattedName, String[] phoneNumbers,
+                String[] emailAddresses, int envLevel) {
+            this.envLevel = envLevel;
+            this.version = "3.0";
+            this.name = name != null ? name : "";
+            this.formattedName = formattedName != null ? formattedName : "";
+            setPhoneNumbers(phoneNumbers);
+            if (emailAddresses != null)
+                this.emailAddresses = emailAddresses;
+        }
+
+        /**
+         * Construct a version 2.1 vCard
+         * @param name Structured name
+         * @param phoneNumbers a String[] of phone numbers
+         * @param emailAddresses a String[] of email addresses
+         * @param the bmessage envelope level (0 is the top/most outer level)
+         */
+        public vCard(String name, String[] phoneNumbers,
+                String[] emailAddresses, int envLevel) {
+            this.envLevel = envLevel;
+            this.version = "2.1";
+            this.name = name != null ? name : "";
+            setPhoneNumbers(phoneNumbers);
+            if (emailAddresses != null)
+                this.emailAddresses = emailAddresses;
+        }
+
+        /**
+         * Construct a version 3.0 vCard
+         * @param name Structured name
+         * @param formattedName Formatted name
+         * @param phoneNumbers a String[] of phone numbers
+         * @param emailAddresses a String[] of email addresses
+         */
+        public vCard(String name, String formattedName, String[] phoneNumbers, String[] emailAddresses) {
+            this.version = "3.0";
+            this.name = name != null ? name : "";
+            this.formattedName = formattedName != null ? formattedName : "";
+            setPhoneNumbers(phoneNumbers);
+            if (emailAddresses != null)
+                this.emailAddresses = emailAddresses;
+        }
+
+        /**
+         * Construct a version 2.1 vCard
+         * @param name Structured Name
+         * @param phoneNumbers a String[] of phone numbers
+         * @param emailAddresses a String[] of email addresses
+         */
+        public vCard(String name, String[] phoneNumbers, String[] emailAddresses) {
+            this.version = "2.1";
+            this.name = name != null ? name : "";
+            setPhoneNumbers(phoneNumbers);
+            if (emailAddresses != null)
+                this.emailAddresses = emailAddresses;
+        }
+
+        private void setPhoneNumbers(String[] numbers) {
+            if(numbers != null && numbers.length > 0)
+            {
+                phoneNumbers = new String[numbers.length];
+                for(int i = 0, n = numbers.length; i < n; i++){
+                    phoneNumbers[i] = PhoneNumberUtils.extractNetworkPortion(numbers[i]);
+                }
+            }
+        }
+
+        public String getFirstPhoneNumber() {
+            if(phoneNumbers.length > 0) {
+                return phoneNumbers[0];
+            } else
+                throw new IllegalArgumentException("No Phone number");
+        }
+
+        public int getEnvLevel() {
+            return envLevel;
+        }
+
+        public void encode(StringBuilder sb)
+        {
+            sb.append("BEGIN:VCARD").append("\r\n");
+            sb.append("VERSION:").append(version).append("\r\n");
+            if (version.equals("3.0") && formattedName != null)
+            {
+                sb.append("FN:").append(formattedName).append("\r\n");
+            }
+            if (name != null)
+                sb.append("N:").append(name).append("\r\n");
+            for (String phoneNumber : phoneNumbers)
+            {
+                sb.append("TEL:").append(phoneNumber).append("\r\n");
+            }
+            for (String emailAddress : emailAddresses)
+            {
+                sb.append("EMAIL:").append(emailAddress).append("\r\n");
+            }
+            sb.append("END:VCARD").append("\r\n");
+        }
+
+        /**
+         * Parse a vCard from a BMgsReader, where a line containing "BEGIN:VCARD" have just been read.
+         * @param reader
+         * @param originator
+         * @return
+         */
+        public static vCard parseVcard(BMsgReader reader, int envLevel) {
+            String formattedName = null;
+            String name = null;
+            ArrayList<String> phoneNumbers = null;
+            ArrayList<String> emailAddresses = null;
+            String[] parts;
+            String line = reader.getLineEnforce();
+
+            while(!line.contains("END:VCARD")) {
+                line = line.trim();
+                if(line.startsWith("N:")){
+                    parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':'
+                    if(parts.length == 2) {
+                        name = parts[1];
+                    } else
+                        name = "";
+                }
+                else if(line.startsWith("FN:")){
+                    parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':'
+                    if(parts.length == 2) {
+                        formattedName = parts[1];
+                    } else
+                        formattedName = "";
+                }
+                else if(line.startsWith("TEL:")){
+                    parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':'
+                    if(parts.length == 2) {
+                        String[] subParts = parts[1].split("[^\\\\];");
+                        if(phoneNumbers == null)
+                            phoneNumbers = new ArrayList<String>(1);
+                        phoneNumbers.add(subParts[subParts.length-1]); // only keep actual phone number
+                    } else {}
+                        // Empty phone number - ignore
+                }
+                else if(line.startsWith("EMAIL:")){
+                    parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
+                    if(parts.length == 2) {
+                        String[] subParts = parts[1].split("[^\\\\];");
+                        if(emailAddresses == null)
+                            emailAddresses = new ArrayList<String>(1);
+                        emailAddresses.add(subParts[subParts.length-1]); // only keep actual email address
+                    } else {}
+                        // Empty email address entry - ignore
+                }
+                line = reader.getLineEnforce();
+            }
+            return new vCard(name, formattedName,
+                    phoneNumbers == null? null : phoneNumbers.toArray(new String[phoneNumbers.size()]),
+                    emailAddresses == null ? null : emailAddresses.toArray(new String[emailAddresses.size()]),
+                    envLevel);
+        }
+    };
+
+    private static class BMsgReader {
+        InputStream mInStream;
+        public BMsgReader(InputStream is)
+        {
+            this.mInStream = is;
+        }
+
+        private byte[] getLineAsBytes() {
+            int readByte;
+
+            /* TODO: Actually the vCard spec. allows to break lines by using a newLine
+             * followed by a white space character(space or tab). Not sure this is a good idea to implement
+             * as the Bluetooth MAP spec. illustrates vCards using tab alignment, hence actually
+             * showing an invalid vCard format...
+             * If we read such a folded line, the folded part will be skipped in the parser
+             */
+
+            ByteArrayOutputStream output = new ByteArrayOutputStream();
+            try {
+                while ((readByte = mInStream.read()) != -1) {
+                    if (readByte == '\r') {
+                        if ((readByte = mInStream.read()) != -1 && readByte == '\n') {
+                            if(output.size() == 0)
+                                continue; /* Skip empty lines */
+                            else
+                                break;
+                        } else {
+                            output.write('\r');
+                        }
+                    } else if (readByte == '\n' && output.size() == 0) {
+                        /* Empty line - skip */
+                        continue;
+                    }
+
+                    output.write(readByte);
+                }
+            } catch (IOException e) {
+                Log.w(TAG, e);
+                return null;
+            }
+            return output.toByteArray();
+        }
+
+        /**
+         * Read a line of text from the BMessage.
+         * @return the next line of text, or null at end of file, or if UTF-8 is not supported.
+         */
+        public String getLine() {
+            try {
+                byte[] line = getLineAsBytes();
+                if (line.length == 0)
+                    return null;
+                else
+                    return new String(line, "UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                Log.w(TAG, e);
+                return null;
+            }
+        }
+
+        /**
+         * same as getLine(), but throws an exception, if we run out of lines.
+         * Use this function when ever more lines are needed for the bMessage to be complete.
+         * @return the next line
+         */
+        public String getLineEnforce() {
+            String line = getLine();
+            if (line == null)
+                throw new IllegalArgumentException("Bmessage too short");
+            return line;
+        }
+
+
+        /**
+         * Reads a line from the InputStream, and examines if the subString
+         * matches the line read.
+         * @param subString
+         * The string to match against the line.
+         * @throws IllegalArgumentException
+         * If the expected substring is not found.
+         *
+         */
+        public void expect(String subString) throws IllegalArgumentException{
+            String line = getLine();
+            if (!line.contains(subString))
+                // TODO: Should this be case insensitive? (Either use toUpper() or matches())
+                throw new IllegalArgumentException("Expected \"" + subString + "\" in: \"" + line + "\"");
+        }
+
+        /**
+         * Same as expect(String), but with two strings.
+         * @param subString
+         * @param subString2
+         * @throws IllegalArgumentException
+         * If one or all of the strings are not found.
+         */
+        public void expect(String subString, String subString2) throws IllegalArgumentException{
+            String line = getLine();
+            if(!line.contains(subString)) // TODO: Should this be case insensitive? (Either use toUpper() or matches())
+                throw new IllegalArgumentException("Expected \"" + subString + "\" in: \"" + line + "\"");
+            if(!line.contains(subString2)) // TODO: Should this be case insensitive? (Either use toUpper() or matches())
+                throw new IllegalArgumentException("Expected \"" + subString + "\" in: \"" + line + "\"");
+        }
+
+        /**
+         * Read a part of the bMessage as raw data.
+         * @param length the number of bytes to read
+         * @return the byte[] containing the number of bytes or null if an error occurs or EOF is reached
+         * before length bytes have been read.
+         */
+        public byte[] getDataBytes(int length) {
+            byte[] data = new byte[length];
+            try {
+                int bytesRead;
+                int offset=0;
+                while ((bytesRead = mInStream.read(data, offset, length-offset)) != length) {
+                    if(bytesRead == -1)
+                        return null;
+                    offset += bytesRead;
+                }
+            } catch (IOException e) {
+                Log.w(TAG, e);
+                return null;
+            }
+            return data;
+        }
+    };
+
+    public BluetoothMapbMessage() {
+
+    }
+
+    public static BluetoothMapbMessage parse(InputStream bMsgStream, int appParamCharset) throws IllegalArgumentException{
+        BMsgReader reader = new BMsgReader(bMsgStream);
+        String line = "";
+        BluetoothMapbMessage newBMsg = null;
+        reader.expect("BEGIN:BMSG");
+        reader.expect("VERSION","1.0");
+        boolean status = false;
+        boolean statusFound = false;
+        TYPE type = null;
+        String folder = null;
+
+        line = reader.getLineEnforce();
+        // Parse the properties - which end with either a VCARD or a BENV
+        while(!line.contains("BEGIN:VCARD") && !line.contains("BEGIN:BENV")) {
+            if(line.contains("STATUS")){
+                String arg[] = line.split(":");
+                if (arg != null && arg.length == 2) {
+                    if (arg[1].trim().equals("READ")) {
+                        status = true;
+                    } else if (arg[1].trim().equals("UNREAD")) {
+                        status =false;
+                    } else {
+                        throw new IllegalArgumentException("Wrong value in 'STATUS': " + arg[1]);
+                    }
+                } else {
+                    throw new IllegalArgumentException("Missing value for 'STATUS': " + line);
+                }
+            }
+            if(line.contains("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
+                    if(appParamCharset == BluetoothMapAppParams.CHARSET_NATIVE
+                            && type != TYPE.SMS_CDMA && type != TYPE.SMS_GSM) {
+                        throw new IllegalArgumentException("Native appParamsCharset only supported for SMS");
+                    }
+                    switch(type) {
+                    case SMS_CDMA:
+                    case SMS_GSM:
+                        newBMsg = new BluetoothMapbMessageSms();
+                        break;
+                    case MMS:
+                    case EMAIL:
+                        newBMsg = new BluetoothMapbMessageMmsEmail();
+                        break;
+                    default:
+                        break;
+                    }
+                } else {
+                    throw new IllegalArgumentException("Missing value for 'TYPE':" + line);
+                }
+            }
+            if(line.contains("FOLDER")) {
+                String[] arg = line.split(":");
+                if (arg != null && arg.length == 2) {
+                    folder = arg[1].trim();
+                } else {
+                    throw new IllegalArgumentException("Missing value for 'FOLDER':" + line);
+                }
+            }
+            line = reader.getLineEnforce();
+        }
+        if(newBMsg == null)
+            throw new IllegalArgumentException("Missing bMessage TYPE: - unable to parse body-content");
+        newBMsg.setType(type);
+        newBMsg.appParamCharset = appParamCharset;
+        if(folder != null)
+            newBMsg.setFolder(folder);
+        if(statusFound)
+            newBMsg.setStatus(status);
+
+        // Now check for originator VCARDs
+        while(line.contains("BEGIN:VCARD")){
+            if(D) Log.d(TAG,"Decoding vCard");
+            newBMsg.addOriginator(vCard.parseVcard(reader,0));
+            line = reader.getLineEnforce();
+        }
+        if(line.contains("BEGIN:BENV")) {
+            newBMsg.parseEnvelope(reader, 0);
+        } else
+            throw new IllegalArgumentException("Bmessage has no BEGIN:BENV - line:" + line);
+
+        /* TODO: Do we need to validate the END:* tags? They are only needed if someone puts additional info
+         *        below the END:MSG - in which case we don't handle it.
+         */
+        return newBMsg;
+    }
+
+    private void parseEnvelope(BMsgReader reader, int level) {
+        String line;
+        line = reader.getLineEnforce();
+        if(D) Log.d(TAG,"Decoding envelope level " + level);
+
+       while(line.contains("BEGIN:VCARD")){
+           if(D) Log.d(TAG,"Decoding recipient vCard level " + level);
+            if(recipient == null)
+                recipient = new ArrayList<vCard>(1);
+            recipient.add(vCard.parseVcard(reader, level));
+            line = reader.getLineEnforce();
+        }
+        if(line.contains("BEGIN:BENV")) {
+            if(D) Log.d(TAG,"Decoding nested envelope");
+            parseEnvelope(reader, ++level); // Nested BENV
+        }
+        if(line.contains("BEGIN:BBODY")){
+            if(D) Log.d(TAG,"Decoding bbody");
+            parseBody(reader);
+        }
+    }
+
+    private void parseBody(BMsgReader reader) {
+        String line;
+        line = reader.getLineEnforce();
+        while(!line.contains("END:")) {
+            if(line.contains("PARTID:")) {
+                String arg[] = line.split(":");
+                if (arg != null && arg.length == 2) {
+                    try {
+                    partId = Long.parseLong(arg[1].trim());
+                    } catch (NumberFormatException e) {
+                        throw new IllegalArgumentException("Wrong value in 'PARTID': " + arg[1]);
+                    }
+                } else {
+                    throw new IllegalArgumentException("Missing value for 'PARTID': " + line);
+                }
+            }
+            else if(line.contains("ENCODING:")) {
+                String arg[] = line.split(":");
+                if (arg != null && arg.length == 2) {
+                    encoding = arg[1].trim(); // TODO: Validate ?
+                } else {
+                    throw new IllegalArgumentException("Missing value for 'ENCODING': " + line);
+                }
+            }
+            else if(line.contains("CHARSET:")) {
+                String arg[] = line.split(":");
+                if (arg != null && arg.length == 2) {
+                    charset = arg[1].trim(); // TODO: Validate ?
+                } else {
+                    throw new IllegalArgumentException("Missing value for 'CHARSET': " + line);
+                }
+            }
+            else if(line.contains("LANGUAGE:")) {
+                String arg[] = line.split(":");
+                if (arg != null && arg.length == 2) {
+                    language = arg[1].trim(); // TODO: Validate ?
+                } else {
+                    throw new IllegalArgumentException("Missing value for 'LANGUAGE': " + line);
+                }
+            }
+            else if(line.contains("LENGTH:")) {
+                String arg[] = line.split(":");
+                if (arg != null && arg.length == 2) {
+                    try {
+                        bMsgLength = Integer.parseInt(arg[1].trim());
+                    } catch (NumberFormatException e) {
+                        throw new IllegalArgumentException("Wrong value in 'LENGTH': " + arg[1]);
+                    }
+                } else {
+                    throw new IllegalArgumentException("Missing value for 'LENGTH': " + line);
+                }
+            }
+            else if(line.contains("BEGIN:MSG")) {
+                if(bMsgLength == INVALID_VALUE)
+                    throw new IllegalArgumentException("Missing value for 'LENGTH'. Unable to read remaining part of the message");
+                // For SMS: Encoding of MSG is always UTF-8 compliant, regardless of any properties, since PDUs are encodes as hex-strings
+                /* PTS has a bug regarding the message length, and sets it 2 bytes too short, hence
+                 * using the length field to determine the amount of data to read, might not be the
+                 * best solution.
+                 * Since errata ???(bluetooth.org is down at the moment) introduced escaping of END:MSG
+                 * in the actual message content, it is now safe to use the END:MSG tag as terminator,
+                 * and simply ignore the length field.*/
+                byte[] rawData = reader.getDataBytes(bMsgLength - (line.getBytes().length + 2)); // 2 added to compensate for the removed \r\n
+                String data;
+                try {
+                    data = new String(rawData, "UTF-8");
+                    if(V) {
+                        Log.v(TAG,"MsgLength: " + bMsgLength);
+                        Log.v(TAG,"line.getBytes().length: " + line.getBytes().length);
+                        String debug = line.replaceAll("\\n", "<LF>\n");
+                        debug = debug.replaceAll("\\r", "<CR>");
+                        Log.v(TAG,"The line: \"" + debug + "\"");
+                        debug = data.replaceAll("\\n", "<LF>\n");
+                        debug = debug.replaceAll("\\r", "<CR>");
+                        Log.v(TAG,"The msgString: \"" + debug + "\"");
+                    }
+                } catch (UnsupportedEncodingException e) {
+                    Log.w(TAG,e);
+                    throw new IllegalArgumentException("Unable to convert to UTF-8");
+                }
+                /* Decoding of MSG:
+                 * 1) split on "\r\nEND:MSG\r\n"
+                 * 2) delete "BEGIN:MSG\r\n" for each msg
+                 * 3) replace any occurrence of "\END:MSG" with "END:MSG"
+                 * 4) based on charset from application properties either store as String[] or decode to raw PDUs
+                 * */
+                String messages[] = data.split("\r\nEND:MSG\r\n");
+                parseMsgInit();
+                for(int i = 0; i < messages.length; i++) {
+                    messages[i] = messages[i].replaceFirst("^BEGIN:MGS\r\n", "");
+                    messages[i] = messages[i].replaceAll("\r\n([/]*)/END\\:MSG", "\r\n$1END:MSG");
+                    messages[i] = messages[i].trim();
+                    parseMsgPart(messages[i]);
+                }
+            }
+            line = reader.getLineEnforce();
+        }
+    }
+
+    /**
+     * Parse the 'message' part of <bmessage-body-content>"
+     * @param msgPart
+     */
+    public abstract void parseMsgPart(String msgPart);
+    /**
+     * Set initial values before parsing - will be called is a message body is found
+     * during parsing.
+     */
+    public abstract void parseMsgInit();
+
+    public abstract byte[] encode() throws UnsupportedEncodingException;
+
+    public void setStatus(boolean read) {
+        if(read)
+            this.status = "READ";
+        else
+            this.status = "UNREAD";
+    }
+
+    public void setType(TYPE type) {
+        this.type = type;
+    }
+
+    /**
+     * @return the type
+     */
+    public TYPE getType() {
+        return type;
+    }
+
+    public void setFolder(String folder) {
+        this.folder = "telecom/msg/" + folder;
+    }
+
+    public void setEncoding(String encoding) {
+        this.encoding = encoding;
+    }
+
+    public ArrayList<vCard> getOriginators() {
+        return originator;
+    }
+
+    public void addOriginator(vCard originator) {
+        if(this.originator == null)
+            this.originator = new ArrayList<vCard>();
+        this.originator.add(originator);
+    }
+
+    /**
+     * Add a version 3.0 vCard with a formatted name
+     * @param name e.g. Bonde;Casper
+     * @param formattedName e.g. "Casper Bonde"
+     * @param phoneNumbers
+     * @param emailAddresses
+     */
+    public void addOriginator(String name, String formattedName, String[] phoneNumbers, String[] emailAddresses) {
+        if(originator == null)
+            originator = new ArrayList<vCard>();
+        originator.add(new vCard(name, formattedName, phoneNumbers, emailAddresses));
+    }
+
+    /** Add a version 2.1 vCard with only a name.
+     *
+     * @param name e.g. Bonde;Casper
+     * @param phoneNumbers
+     * @param emailAddresses
+     */
+    public void addOriginator(String name, String[] phoneNumbers, String[] emailAddresses) {
+        if(originator == null)
+            originator = new ArrayList<vCard>();
+        originator.add(new vCard(name, phoneNumbers, emailAddresses));
+    }
+
+    public ArrayList<vCard> getRecipients() {
+        return recipient;
+    }
+
+    public void setRecipient(vCard recipient) {
+        if(this.recipient == null)
+            this.recipient = new ArrayList<vCard>();
+        this.recipient.add(recipient);
+    }
+
+    public void addRecipient(String name, String formattedName, String[] phoneNumbers, String[] emailAddresses) {
+        if(recipient == null)
+            recipient = new ArrayList<vCard>();
+        recipient.add(new vCard(name, formattedName, phoneNumbers, emailAddresses));
+    }
+
+    public void addRecipient(String name, String[] phoneNumbers, String[] emailAddresses) {
+        if(recipient == null)
+            recipient = new ArrayList<vCard>();
+        recipient.add(new vCard(name, phoneNumbers, emailAddresses));
+    }
+
+    /**
+     * Convert a byte[] of data to a hex string representation, converting each nibble to the corresponding
+     * hex char.
+     * NOTE: There is not need to escape instances of "\r\nEND:MSG" in the binary data represented as a string
+     *       as only the characters [0-9] and [a-f] is used.
+     * @param pduData the byte-array of data.
+     * @param scAddressData the byte-array of the encoded sc-Address.
+     * @return the resulting string.
+     */
+    protected String encodeBinary(byte[] pduData, byte[] scAddressData) {
+        StringBuilder out = new StringBuilder((pduData.length + scAddressData.length)*2);
+        for(int i = 0; i < scAddressData.length; i++) {
+            out.append(Integer.toString((scAddressData[i] >> 4) & 0x0f,16)); // MS-nibble first
+            out.append(Integer.toString( scAddressData[i]       & 0x0f,16));
+        }
+        for(int i = 0; i < pduData.length; i++) {
+            out.append(Integer.toString((pduData[i] >> 4) & 0x0f,16)); // MS-nibble first
+            out.append(Integer.toString( pduData[i]       & 0x0f,16));
+            /*out.append(Integer.toHexString(data[i]));*/ /* This is the same as above, but does not include the needed 0's
+                                                           e.g. it converts the value 3 to "3" and not "03" */
+        }
+        return out.toString();
+    }
+
+    /**
+     * Decodes a binary hex-string encoded UTF-8 string to the represented binary data set.
+     * @param data The string representation of the data - must have an even number of characters.
+     * @return the byte[] represented in the data.
+     */
+    protected byte[] decodeBinary(String data) {
+        byte[] out = new byte[data.length()/2];
+        String value;
+        if(D) Log.d(TAG,"Decoding binary data: START:" + data + ":END");
+        for(int i = 0, j = 0, n = out.length; i < n; i++)
+        {
+            value = data.substring(j++, j++); // same as data.substring(2*i, 2*i+1)
+            out[i] = Byte.valueOf(value, 16);
+        }
+        return out;
+    }
+
+    public byte[] encodeGeneric(ArrayList<byte[]> bodyFragments) throws UnsupportedEncodingException
+    {
+        StringBuilder sb = new StringBuilder(256);
+        byte[] msgStart, msgEnd;
+        sb.append("BEGIN:BMSG").append("\r\n");
+        sb.append(VERSION).append("\r\n");
+        sb.append("STATUS:").append(status).append("\r\n");
+        sb.append("TYPE:").append(type.name()).append("\r\n");
+        sb.append("FOLDER:").append(folder).append("\r\n");
+        if(originator != null){
+            for(vCard element : originator)
+                element.encode(sb);
+        }
+        /* TODO: Do we need the three levels of env? - e.g. for e-mail. - we do have a level in the
+         *  vCards that could be used to determine the the levels of the envelope.
+         */
+
+        sb.append("BEGIN:BENV").append("\r\n");
+        if(recipient != null){
+            for(vCard element : recipient)
+                element.encode(sb);
+        }
+        sb.append("BEGIN:BBODY").append("\r\n");
+        if(encoding != null && encoding != "")
+            sb.append("ENCODING:").append(encoding).append("\r\n");
+        if(charset != null && charset != "")
+            sb.append("CHARSET:").append(charset).append("\r\n");
+
+
+        int length = 0;
+        /* 22 is the length of the 'BEGIN:MSG' and 'END:MSG' + 3*CRLF */
+        for (byte[] fragment : bodyFragments) {
+            length += fragment.length + 22;
+        }
+        sb.append("LENGTH:").append(length).append("\r\n");
+
+        // Extract the initial part of the bMessage string
+        msgStart = sb.toString().getBytes("UTF-8");
+
+        sb = new StringBuilder(31);
+        sb.append("END:BBODY").append("\r\n");
+        sb.append("END:BENV").append("\r\n");
+        sb.append("END:BMSG").append("\r\n");
+
+        msgEnd = sb.toString().getBytes("UTF-8");
+
+        try {
+
+            ByteArrayOutputStream stream = new ByteArrayOutputStream(msgStart.length + msgEnd.length + length);
+            stream.write(msgStart);
+
+            for (byte[] fragment : bodyFragments) {
+                stream.write("BEGIN:MSG\r\n".getBytes("UTF-8"));
+                stream.write(fragment);
+                stream.write("\r\nEND:MSG\r\n".getBytes("UTF-8"));
+            }
+            stream.write(msgEnd);
+
+            if(V) Log.v(TAG,stream.toString("UTF-8"));
+            return stream.toByteArray();
+        } catch (IOException e) {
+            Log.w(TAG,e);
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java b/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java
new file mode 100644
index 0000000..169c42d
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java
@@ -0,0 +1,538 @@
+/*
+* 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.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Locale;
+import java.util.UUID;
+
+import android.text.util.Rfc822Token;
+import android.text.util.Rfc822Tokenizer;
+import android.util.Base64;
+import android.util.Log;
+
+public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage {
+
+    private String mmsBody;
+    /**
+     * TODO: Determine the best way to store the MMS message content.
+     * @param mmsBody
+     */
+
+    public static class MimePart {
+        public long _id = INVALID_VALUE;   /* The _id from the content provider, can be used to sort the parts if needed */
+        public String contentType = null;  /* The mime type, e.g. text/plain */
+        public String contentId = null;
+        public String partName = null;     /* e.g. text_1.txt*/
+        public String charsetName = null;  /* This seems to be a number e.g. 106 for UTF-8 CharacterSets
+                                                holds a method for the mapping. */
+        public String fileName = null;     /* Do not seem to be used */
+        public byte[] data = null;        /* The raw un-encoded data e.g. the raw jpeg data or the text.getBytes("utf-8") */
+
+        public void encode(StringBuilder sb, String boundaryTag, boolean last) throws UnsupportedEncodingException {
+            sb.append("--").append(boundaryTag);
+            if(last)
+                sb.append("--");
+            sb.append("\r\n");
+            if(contentType != null)
+                sb.append("Content-Type: ").append(contentType);
+            if(charsetName != null)
+                sb.append("; ").append("charset=\"").append(charsetName).append("\"");
+            sb.append("\r\n");
+            if(partName != null)
+                sb.append("Content-Location: ").append(partName).append("\r\n");
+            if(data != null) {
+                // If errata 4176 is adopted in the current form, the below is not allowed, Base64 should be used for text
+                if(contentType.toUpperCase().contains("TEXT")) {
+                    sb.append("Content-Transfer-Encoding: 8BIT\r\n\r\n"); // Add the header split empty line
+                    sb.append(new String(data,"UTF-8")).append("\r\n");
+                }
+                else {
+                    sb.append("Content-Transfer-Encoding: Base64\r\n\r\n"); // Add the header split empty line
+                    sb.append(Base64.encodeToString(data, Base64.DEFAULT)).append("\r\n");
+                }
+            }
+        }
+    }
+    private long date = INVALID_VALUE;
+    private String subject = null;
+    private ArrayList<Rfc822Token> from = null;   // Shall not be empty
+    private ArrayList<Rfc822Token> sender = null;   // Shall not be empty
+    private ArrayList<Rfc822Token> to = null;     // Shall not be empty
+    private ArrayList<Rfc822Token> cc = null;     // Can be empty
+    private ArrayList<Rfc822Token> bcc = null;    // Can be empty
+    private ArrayList<Rfc822Token> replyTo = null;// Can be empty
+    private String messageId = null;
+    private ArrayList<MimePart> parts = null;
+    private String contentType = null;
+    private String boundary = null;
+
+    /* TODO:
+     *  - create an encoder for the parts e.g. embedded in the mimePart class
+     *  */
+    private String getBoundary() {
+        if(boundary == null)
+            boundary = "----" + UUID.randomUUID();
+        return boundary;
+    }
+
+    /**
+     * @return the parts
+     */
+    public ArrayList<MimePart> getMimeParts() {
+        return parts;
+    }
+
+    public MimePart addMimePart() {
+        if(parts == null)
+            parts = new ArrayList<BluetoothMapbMessageMmsEmail.MimePart>();
+        MimePart newPart = new MimePart();
+        parts.add(newPart);
+        return newPart;
+    }
+    public String getDateString() {
+        SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
+        Date dateObj = new Date(date);
+        return format.format(dateObj); // Format according to RFC 2822 page 14
+    }
+    public long getDate() {
+        return date;
+    }
+    public void setDate(long date) {
+        this.date = date;
+    }
+    public String getSubject() {
+        return subject;
+    }
+    public void setSubject(String subject) {
+        this.subject = subject;
+    }
+    public ArrayList<Rfc822Token> getFrom() {
+        return from;
+    }
+    public void setFrom(ArrayList<Rfc822Token> from) {
+        this.from = from;
+    }
+    public void addFrom(String name, String address) {
+        if(this.from == null)
+            this.from = new ArrayList<Rfc822Token>(1);
+        //this.from.add(formatAddress(name, address));
+        this.from.add(new Rfc822Token(name, address, null));
+    }
+    public ArrayList<Rfc822Token> getSender() {
+        return from;
+    }
+    public void setSender(ArrayList<Rfc822Token> sender) {
+        this.sender = sender;
+    }
+    public void addSender(String name, String address) {
+        if(this.sender == null)
+            this.sender = new ArrayList<Rfc822Token>(1);
+        //this.sender.add(formatAddress(name, address));
+        this.sender.add(new Rfc822Token(name,address,null));
+    }
+    public ArrayList<Rfc822Token> getTo() {
+        return to;
+    }
+    public void setTo(ArrayList<Rfc822Token> to) {
+        this.to = to;
+    }
+    public void addTo(String name, String address) {
+        if(this.to == null)
+            this.to = new ArrayList<Rfc822Token>(1);
+        //this.to.add(formatAddress(name, address));
+        this.to.add(new Rfc822Token(name, address, null));
+    }
+    public ArrayList<Rfc822Token> getCc() {
+        return cc;
+    }
+    public void setCc(ArrayList<Rfc822Token> cc) {
+        this.cc = cc;
+    }
+    public void addCc(String name, String address) {
+        if(this.cc == null)
+            this.cc = new ArrayList<Rfc822Token>(1);
+        //this.cc.add(formatAddress(name, address));
+        this.cc.add(new Rfc822Token(name, address, null));
+    }
+    public ArrayList<Rfc822Token> getBcc() {
+        return bcc;
+    }
+    public void setBcc(ArrayList<Rfc822Token> bcc) {
+        this.bcc = bcc;
+    }
+    public void addBcc(String name, String address) {
+        if(this.bcc == null)
+            this.bcc = new ArrayList<Rfc822Token>(1);
+        //this.bcc.add(formatAddress(name, address));
+        this.bcc.add(new Rfc822Token(name, address, null));
+    }
+    public ArrayList<Rfc822Token> getReplyTo() {
+        return replyTo;
+    }
+    public void setReplyTo(ArrayList<Rfc822Token> replyTo) {
+        this.replyTo = replyTo;
+    }
+    public void addReplyTo(String name, String address) {
+        if(this.replyTo == null)
+            this.replyTo = new ArrayList<Rfc822Token>(1);
+        //this.replyTo.add(formatAddress(name, address));
+        this.replyTo.add(new Rfc822Token(name, address, null));
+    }
+    public void setMessageId(String messageId) {
+        this.messageId = messageId;
+    }
+    public String getMessageId() {
+        return messageId;
+    }
+    public void setContentType(String contentType) {
+        this.contentType = contentType;
+    }
+    public String getContentType() {
+        return contentType;
+    }
+    public void updateCharset() {
+        charset = null;
+        for(MimePart part : parts) {
+            if(part.contentType != null &&
+               part.contentType.toUpperCase().contains("TEXT")) {
+                charset = "UTF-8";
+            }
+        }
+    }
+    /**
+     * Use this to format an address according to RFC 2822.
+     * @param name
+     * @param address
+     * @return
+     */
+    public static String formatAddress(String name, String address) {
+        StringBuilder sb = new StringBuilder();
+        Boolean nameSet = false;
+        if(name != null && !(name = name.trim()).equals("")) {
+            sb.append(name.trim());
+            nameSet = true;
+        }
+        if(address != null && !(address = address.trim()).equals(""))
+        {
+            if(nameSet == true)
+                sb.append(":");
+            sb.append("<").append(address).append(">");
+        }
+        // TODO: Throw exception of the string is larger than 996
+        return sb.toString();
+    }
+
+
+    /**
+     * Encode an address header, and perform folding if needed.
+     * @param sb The stringBuilder to write to
+     * @param headerName The RFC 2822 header name
+     * @param addresses the reformatted address substrings to encode. Create
+     * these using {@link formatAddress}
+     */
+    public void encodeHeaderAddresses(StringBuilder sb, String headerName,
+            ArrayList<Rfc822Token> addresses) {
+        /* TODO: Do we need to encode the addresses if they contain illegal characters */
+        int partLength, lineLength = 0;
+        lineLength += headerName.getBytes().length;
+        sb.append(headerName);
+        for(Rfc822Token address : addresses) {
+            partLength = address.toString().getBytes().length+1;
+            // Add folding if needed
+            if(lineLength + partLength >= 998) // max line length in RFC2822
+            {
+                sb.append("\r\n "); // Append a FWS (folding whitespace)
+                lineLength = 0;
+            }
+            sb.append(address.toString()).append(";");
+            lineLength += partLength;
+        }
+        sb.append("\r\n");
+    }
+
+    public void encodeHeaders(StringBuilder sb) throws UnsupportedEncodingException
+    {
+        /* TODO: From RFC-4356 - about the RFC-(2)822 headers:
+         *    "Current Internet Message format requires that only 7-bit US-ASCII
+         *     characters be present in headers.  Non-7-bit characters in an address
+         *     domain must be encoded with [IDN].  If there are any non-7-bit
+         *     characters in the local part of an address, the message MUST be
+         *     rejected.  Non-7-bit characters elsewhere in a header MUST be encoded
+         *     according to [Hdr-Enc]."
+         *    We need to add the address encoding in encodeHeaderAddresses, but it is not
+         *    straight forward, as it is unclear how to do this.  */
+        if (date != INVALID_VALUE)
+            sb.append("Date: ").append(getDateString()).append("\r\n");
+        /* According to RFC-2822 headers must use US-ASCII, where the MAP specification states
+         * UTF-8 should be used for the entire <bmessage-body-content>. We let the MAP specification
+         * take precedence above the RFC-2822. The code to
+         */
+/*        If we are to use US-ASCII anyway, here are the code for it.
+          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
+            sb.append("Subject:").append("=?utf-8?B?");
+            sb.append(Base64.encodeToString(subject.getBytes("utf-8"), Base64.DEFAULT));
+            sb.append("?=\r\n");
+        }*/
+        if (subject != null)
+            sb.append("Subject: ").append(subject).append("\r\n");
+        if(from != null)
+            encodeHeaderAddresses(sb, "From: ", from); // This includes folding if needed.
+        if(sender != null)
+            encodeHeaderAddresses(sb, "Sender: ", sender); // This includes folding if needed.
+        /* For MMS one recipient(to, cc or bcc) must exists, if none: 'To:  undisclosed-
+         * recipients:;' could be used.
+         * TODO: Is this a valid solution for E-Mail?
+         */
+        if(to == null && cc == null && bcc == null)
+            sb.append("To:  undisclosed-recipients:;\r\n");
+        if(to != null)
+            encodeHeaderAddresses(sb, "To: ", to); // This includes folding if needed.
+        if(cc != null)
+            encodeHeaderAddresses(sb, "Cc: ", cc); // This includes folding if needed.
+        if(bcc != null)
+            encodeHeaderAddresses(sb, "Bcc: ", bcc); // This includes folding if needed.
+        if(replyTo != null)
+            encodeHeaderAddresses(sb, "Reply-To: ", replyTo); // This includes folding if needed.
+        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());
+        sb.append("\r\n\r\n"); // If no headers exists, we still need two CRLF, hence keep it out of the if above.
+    }
+
+    /* Notes on MMS
+     * ------------
+     * According to rfc4356 all headers of a MMS converted to an E-mail must use
+     * 7-bit encoding. According the the MAP specification only 8-bit encoding is
+     * allowed - hence the bMessage-body should contain no SMTP headers. (Which makes
+     * sense, since the info is already present in the bMessage properties.)
+     * The result is that no information from RFC4356 is needed, since it does not
+     * describe any mapping between MMS content and E-mail content.
+     * Suggestion:
+     * Clearly state in the MAP specification that
+     * only the actual message content should be included in the <bmessage-body-content>.
+     * Correct the Example to not include the E-mail headers, and in stead show how to
+     * include a picture or another binary attachment.
+     *
+     * If the headers should be included, clearly state which, as the example clearly shows
+     * that some of the headers should be excluded.
+     * Additionally it is not clear how to handle attachments. There is a parameter in the
+     * get message to include attachments, but since only 8-bit encoding is allowed,
+     * (hence neither base64 nor binary) there is not mechanism to embed the attachment in
+     * the <bmessage-body-content>.
+     *
+     * UPDATE: Errata xxx allows the needed encoding typed inside the <bmessage-body-content>
+     * including Base64 and Quoted Printables - hence it is possible to encode non-us-ascii
+     * messages - e.g. pictures and utf-8 strings with non-us-ascii content.
+     * */
+
+    /**
+     * Encode the bMessage as a MMS
+     * @return
+     * @throws UnsupportedEncodingException
+     */
+    public byte[] encodeMms() throws UnsupportedEncodingException
+    {
+        ArrayList<byte[]> bodyFragments = new ArrayList<byte[]>();
+        StringBuilder sb = new StringBuilder();
+        int count = 0;
+        String mmsBody;
+
+        encoding = "8BIT"; // The encoding used
+
+        encodeHeaders(sb);
+        for(MimePart part : parts) {
+            count++;
+            part.encode(sb, getBoundary(), (count == parts.size()));
+        }
+
+        mmsBody = sb.toString();
+
+        if(mmsBody != null) {
+            String tmpBody = mmsBody.replaceAll("END:MSG", "/END\\:MSG"); // Replace any occurrences of END:MSG with \END:MSG
+            bodyFragments.add(tmpBody.getBytes("UTF-8"));
+        } else {
+            bodyFragments.add(new byte[0]); // TODO: Is this allowed? (An empty message)
+        }
+
+        return encodeGeneric(bodyFragments);
+    }
+
+    private void parseMmsHeaders(String hdrPart) {
+        String[] headers = hdrPart.split("\r\n");
+
+        for(String header : headers) {
+            if(header.trim() == "")
+                continue;
+            String[] headerParts = header.split(":",2);
+            if(headerParts.length != 2)
+                throw new IllegalArgumentException("Header not formatted correctly: " + header);
+            String headerType = headerParts[0].toUpperCase();
+            String headerValue = headerParts[1].trim();
+
+            // Address headers
+            // TODO: If this is empty, the MSE needs to fill it in
+            if(headerType.contains("FROM")) {
+                Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
+                from = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
+            }
+            else if(headerType.contains("TO")) {
+                Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
+                to = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
+            }
+            else if(headerType.contains("CC")) {
+                Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
+                cc = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
+            }
+            else if(headerType.contains("BCC")) {
+                Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
+                bcc = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
+            }
+            else if(headerType.contains("REPLY-TO")) {
+                Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
+                replyTo = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
+            }// Other headers
+            else if(headerType.contains("SUBJECT")) {
+                subject = headerValue;
+            }
+            else if(headerType.contains("MESSAGE-ID")) {
+                messageId = headerValue;
+            }
+            else if(headerType.contains("DATE")) {
+                /* XXX: Set date */
+            }
+            else if(headerType.contains("CONTENT-TYPE")) {
+                String[] contentTypeParts = headerValue.split(";");
+                contentType = contentTypeParts[0];
+                // Extract the boundary if it exists
+                for(int i=1, n=contentTypeParts.length; i<n; i++)
+                {
+                    if(contentTypeParts[i].contains("boundary")) {
+                        boundary = contentTypeParts[i].split("boundary[\\s]*=", 2)[1].trim();
+                    }
+                }
+            }
+            else {
+                if(D) Log.w(TAG,"Skipping unknown header: " + headerType + " (" + header + ")");
+            }
+        }
+    }
+
+    private void parseMmsMimePart(String partStr) {
+        /**/
+        String[] parts = partStr.split("\r\n\r\n", 2); // Split the header from the body
+        if(parts.length != 2) {
+            throw new IllegalArgumentException("Wrongly formatted email part - unable to locate header section");
+        }
+        String[] headers = parts[0].split("\r\n");
+        MimePart newPart = addMimePart();
+        String encoding = "";
+
+        for(String header : headers) {
+            if(header.length() == 0)
+                continue;
+
+            if(header.trim() == "" || header.trim().equals("--")) // Skip empty lines(the \r\n after the boundary tag) and endBoundary tags
+                continue;
+            String[] headerParts = header.split(":",2);
+            if(headerParts.length != 2)
+                throw new IllegalArgumentException("part-Header not formatted correctly: " + header);
+            String headerType = headerParts[0].toUpperCase();
+            String headerValue = headerParts[1].trim();
+            if(headerType.contains("CONTENT-TYPE")) {
+                // TODO: extract charset - as for
+                newPart.contentType = headerValue;
+                Log.d(TAG, "*** CONTENT-TYPE: " + newPart.contentType);
+            }
+            else if(headerType.contains("CONTENT-LOCATION")) {
+                // This is used if the smil refers to a file name in its src=
+                newPart.partName = headerValue;
+            }
+            else if(headerType.contains("CONTENT-TRANSFER-ENCODING")) {
+                encoding = headerValue;
+            }
+            else if(headerType.contains("CONTENT-ID")) {
+                // This is used if the smil refers to a cid:<xxx> in it's src=
+                newPart.contentId = headerValue;
+            }
+            else {
+                if(D) Log.w(TAG,"Skipping unknown part-header: " + headerType + " (" + header + ")");
+            }
+        }
+        // Now for the body
+        if(encoding.toUpperCase().contains("BASE64")) {
+            newPart.data = Base64.decode(parts[1], Base64.DEFAULT);
+        } else {
+            // TODO: handle other encoding types? - here we simply store the string data as bytes
+            try {
+                newPart.data = parts[1].getBytes("UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                // This will never happen, as UTF-8 is mandatory on Android platforms
+            }
+        }
+    }
+    private void parseMms(String message) {
+        /* Overall strategy for decoding:
+         * 1) split on first empty line to extract the header
+         * 2) unfold and parse headers
+         * 3) split on boundary to split into parts (or use the remaining as a part,
+         *    if part is not found)
+         * 4) parse each part
+         * */
+        String[] messageParts;
+        String[] mimeParts;
+        message = message.replaceAll("\\r\\n[ \\\t]+", ""); // Unfold
+        messageParts = message.split("\r\n\r\n", 2); // Split the header from the body
+        if(messageParts.length != 2) {
+            throw new IllegalArgumentException("Wrongly formatted email message - unable to locate header section");
+        }
+        parseMmsHeaders(messageParts[0]);
+        mimeParts = messageParts[1].split("--" + boundary);
+        for(String part : mimeParts) {
+            if (part != null && (part.length() > 0))
+                parseMmsMimePart(part);
+        }
+    }
+
+    /* 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) {
+        // TODO Auto-generated method stub
+        parseMms(msgPart);
+
+    }
+
+    @Override
+    public void parseMsgInit() {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public byte[] encode() throws UnsupportedEncodingException {
+        return encodeMms();
+    }
+
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java b/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java
new file mode 100644
index 0000000..07a2a67
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java
@@ -0,0 +1,82 @@
+/*
+* 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.UnsupportedEncodingException;
+import java.util.ArrayList;
+
+import com.android.bluetooth.map.BluetoothMapSmsPdu.SmsPdu;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+
+public class BluetoothMapbMessageSms extends BluetoothMapbMessage {
+
+    private ArrayList<SmsPdu> smsBodyPdus = null;
+    private String smsBody = null;
+
+    public void setSmsBodyPdus(ArrayList<SmsPdu> smsBodyPdus) {
+        this.smsBodyPdus = smsBodyPdus;
+        this.charset = null;
+        if(smsBodyPdus.size() > 0)
+            this.encoding = smsBodyPdus.get(0).getEncodingString();
+    }
+
+    public String getSmsBody() {
+        return smsBody;
+    }
+
+    public void setSmsBody(String smsBody) {
+        this.smsBody = smsBody;
+        this.charset = "UTF-8";
+        this.encoding = null;
+    }
+
+    @Override
+    public void parseMsgPart(String msgPart) {
+        if(appParamCharset == BluetoothMapAppParams.CHARSET_NATIVE) {
+            smsBody += BluetoothMapSmsPdu.decodePdu(decodeBinary(msgPart),
+                    type == TYPE.SMS_CDMA ? BluetoothMapSmsPdu.SMS_TYPE_CDMA
+                                          : BluetoothMapSmsPdu.SMS_TYPE_GSM);
+        } else {
+            smsBody += msgPart;
+        }
+    }
+    @Override
+    public void parseMsgInit() {
+        smsBody = "";
+    }
+
+    public byte[] encode() throws UnsupportedEncodingException
+    {
+        ArrayList<byte[]> bodyFragments = new ArrayList<byte[]>();
+
+        /* Store the messages in an ArrayList to be able to handle the different message types in a generic way.
+         * We use byte[] since we need to extract the length in bytes.
+         */
+        if(smsBody != null) {
+            String tmpBody = smsBody.replaceAll("END:MSG", "/END\\:MSG"); // Replace any occurrences of END:MSG with \END:MSG
+            bodyFragments.add(tmpBody.getBytes("UTF-8"));
+        }else if (smsBodyPdus.size() > 0) {
+            for (SmsPdu pdu : smsBodyPdus) {
+                // This cannot(must not) contain END:MSG
+                bodyFragments.add(encodeBinary(pdu.getData(),pdu.getScAddress()).getBytes("UTF-8"));
+            }
+        } else {
+            bodyFragments.add(new byte[0]); // TODO: Is this allowed? (An empty message)
+        }
+
+        return encodeGeneric(bodyFragments);
+    }
+
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
new file mode 100644
index 0000000..c6283f1
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
@@ -0,0 +1,346 @@
+/*
+* 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 android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.obex.ApplicationParameter;
+import javax.obex.ClientOperation;
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ObexTransport;
+import javax.obex.ResponseCodes;
+
+/**
+ *
+ * @author Casper Bonde, Henrik Ejersbo and Kim Schulz
+ *
+ * The Message Notification Service class runs its own message handler thread,
+ * to avoid executing long operations on the MAP service Thread.
+ * This handler context is passed to the content observers,
+ * hence all call-backs (and thereby transmission of data) is executed
+ * from this thread.
+ */
+public class BluetoothMnsObexClient extends Thread{
+
+    private static final String TAG = "BluetoothMnsObexClient";
+    private static final boolean D = true;
+    private static final boolean V = true;
+
+    public final static int MSG_SESSION_ERROR = 1;
+    public final static int MSG_CONNECT_TIMEOUT = 2;
+
+    private ObexTransport mTransport;
+    private Context mContext;
+    public static Handler mHandler = null;
+    private volatile boolean mWaitingForRemote;
+    private static final String TYPE_EVENT = "x-bt/MAP-event-report";
+    private ClientSession mClientSession;
+    private boolean mConnected = false;
+    BluetoothDevice mRemoteDevice;
+    private static BluetoothMapContentObserver mObserver;
+    private boolean mObserverRegistered = false;
+
+    private Looper mLooper = null;
+    // Used by the MAS to forward notification registrations
+    public static final int MSG_MNS_NOTIFICATION_REGISTRATION = 1;
+    public static final int MSG_MNS_SHUTDOWN = 2;
+
+    public static final ParcelUuid BluetoothUuid_ObexMns =
+            ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
+
+
+    public BluetoothMnsObexClient(Context context, BluetoothDevice remoteDevice) {
+        if (remoteDevice == null) {
+            throw new NullPointerException("Obex transport is null");
+        }
+        mContext = context;
+        mRemoteDevice = remoteDevice;
+    }
+
+    public static Handler getMessageHandler() {
+        // TODO: if mHandle is null, we should wait for it to be created.
+        return mHandler;
+    }
+
+    public static BluetoothMapContentObserver getContentObserver() {
+        return mObserver;
+    }
+
+    @Override
+    public void run() {
+        Looper.prepare();
+        mLooper = Looper.myLooper();
+
+
+        /* Create the context observer from within the thread to ensure the "content changed"
+         * events are handled in this thread. */
+        mObserver = new BluetoothMapContentObserver(mContext);
+        mObserver.init();
+
+        mHandler = new Handler() {
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                case MSG_MNS_NOTIFICATION_REGISTRATION:
+                    handleRegistration(msg.arg1 /*masId*/, msg.arg2 /*status*/);
+                    break;
+                case MSG_MNS_SHUTDOWN:
+                    if(mObserverRegistered)
+                        mObserver.unregisterObserver();
+                    break;
+                default:
+                    break;
+                }
+            }
+        };
+        Looper.loop();
+    }
+
+    public boolean isConnected() {
+        return mConnected;
+    }
+
+    public void disconnect() {
+        try {
+            if (mClientSession != null) {
+                mClientSession.disconnect(null);
+                if (D) Log.d(TAG, "OBEX session disconnected");
+            }
+            mClientSession = null;
+        } catch (IOException e) {
+            Log.w(TAG, "OBEX session disconnect error " + e.getMessage());
+        }
+        try {
+            if (mClientSession != null) {
+                if (D) Log.d(TAG, "OBEX session close mClientSession");
+                mClientSession.close();
+                if (D) Log.d(TAG, "OBEX session closed");
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "OBEX session close error:" + e.getMessage());
+        }
+        if (mTransport != null) {
+            try {
+                if (D) Log.d(TAG, "Close Obex Transport");
+                mTransport.close();
+                if (D) Log.d(TAG, "Obex Transport Closed");
+            } catch (IOException e) {
+                Log.e(TAG, "mTransport.close error: " + e.getMessage());
+            }
+        }
+        if(mObserverRegistered) {
+            mObserver.unregisterObserver();
+            mObserverRegistered = false;
+        }
+
+        mObserver.deinit();
+        // Shut down the thread
+        if(mLooper != null)
+            mLooper.quit();
+        interrupt();
+        try {
+            join();
+        } catch (InterruptedException e) {
+            if(V) Log.w(TAG, e);
+        }
+    }
+
+    private HeaderSet hsConnect = null;
+
+    public void handleRegistration(int masId, int notificationStatus){
+        Log.d(TAG, "handleRegistration( " + masId + ", " + notificationStatus + ")");
+
+        if(isConnected() == false) {
+            Log.d(TAG, "handleRegistration: connect");
+            connect();
+        }
+
+        if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_NO) {
+            // Unregister - should we disconnect, or keep the connection? - the spec. says nothing about this.
+            if(mObserverRegistered == true) {
+                mObserver.unregisterObserver();
+                mObserverRegistered = false;
+                disconnect();
+            }
+        } else if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) {
+            /* Connect if we do not have a connection, and start the content observers providing
+             * this thread as Handler.
+             */
+            if(mObserverRegistered == false) {
+                mObserver.registerObserver(this, masId);
+                mObserverRegistered = true;
+            }
+        }
+    }
+
+    public void connect() {
+        Log.d(TAG, "handleRegistration: connect 2");
+
+        BluetoothSocket btSocket = null;
+        try {
+            btSocket = mRemoteDevice.createInsecureRfcommSocketToServiceRecord(
+                    BluetoothUuid_ObexMns.getUuid());
+            btSocket.connect();
+        } catch (IOException e) {
+            Log.e(TAG, "BtSocket Connect error " + e.getMessage(), e);
+            // TODO: do we need to report error somewhere?
+            return;
+        }
+
+        mTransport = new BluetoothMnsRfcommTransport(btSocket);
+
+        try {
+            mClientSession = new ClientSession(mTransport);
+            mConnected = true;
+        } catch (IOException e1) {
+            Log.e(TAG, "OBEX session create error " + e1.getMessage());
+        }
+        if (mConnected && mClientSession != null) {
+            mConnected = false;
+            HeaderSet hs = new HeaderSet();
+            // bb582b41-420c-11db-b0de-0800200c9a66
+            byte[] mnsTarget = { (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 };
+            hs.setHeader(HeaderSet.TARGET, mnsTarget);
+
+            synchronized (this) {
+                mWaitingForRemote = true;
+            }
+            try {
+                hsConnect = mClientSession.connect(hs);
+                if (D) Log.d(TAG, "OBEX session created");
+                mConnected = true;
+            } catch (IOException e) {
+                Log.e(TAG, "OBEX session connect error " + e.getMessage());
+            }
+        }
+            synchronized (this) {
+                mWaitingForRemote = false;
+        }
+    }
+
+    public int sendEvent(byte[] eventBytes, int masInstanceId) {
+
+        boolean error = false;
+        int responseCode = -1;
+        HeaderSet request;
+        int maxChunkSize, bytesToWrite, bytesWritten = 0;
+        request = new HeaderSet();
+        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+        appParams.setMasInstanceId(masInstanceId);
+
+        ClientOperation putOperation = null;
+        OutputStream outputStream = null;
+
+        try {
+            request.setHeader(HeaderSet.TYPE, TYPE_EVENT);
+            request.setHeader(HeaderSet.APPLICATION_PARAMETER, appParams.EncodeParams());
+
+            request.mConnectionID = new byte[4];
+            System.arraycopy(hsConnect.mConnectionID, 0, request.mConnectionID, 0, 4);
+
+            synchronized (this) {
+                mWaitingForRemote = true;
+            }
+            // Send the header first and then the body
+            try {
+                if (V) Log.v(TAG, "Send headerset Event ");
+                putOperation = (ClientOperation)mClientSession.put(request);
+                // TODO - Should this be kept or Removed
+
+            } catch (IOException e) {
+                Log.e(TAG, "Error when put HeaderSet " + e.getMessage());
+                error = true;
+            }
+            synchronized (this) {
+                mWaitingForRemote = false;
+            }
+            if (!error) {
+                try {
+                    if (V) Log.v(TAG, "Send headerset Event ");
+                    outputStream = putOperation.openOutputStream();
+                } catch (IOException e) {
+                    Log.e(TAG, "Error when opening OutputStream " + e.getMessage());
+                    error = true;
+                }
+            }
+
+            if (!error) {
+
+                maxChunkSize = putOperation.getMaxPacketSize();
+
+                while (bytesWritten < eventBytes.length) {
+                    bytesToWrite = Math.min(maxChunkSize, eventBytes.length - bytesWritten);
+                    outputStream.write(eventBytes, bytesWritten, bytesToWrite);
+                    bytesWritten += bytesToWrite;
+                }
+
+                if (bytesWritten == eventBytes.length) {
+                    Log.i(TAG, "SendEvent finished send length" + eventBytes.length);
+                    outputStream.close();
+                } else {
+                    error = true;
+                    // TBD - Is Output stream close needed here
+                    putOperation.abort();
+                    Log.i(TAG, "SendEvent interrupted");
+                }
+            }
+        } catch (IOException e) {
+            handleSendException(e.toString());
+            error = true;
+        } catch (IndexOutOfBoundsException e) {
+            handleSendException(e.toString());
+            error = true;
+        } finally {
+            try {
+                if (!error) {
+                    responseCode = putOperation.getResponseCode();
+                    if (responseCode != -1) {
+                        if (V) Log.v(TAG, "Put response code " + responseCode);
+                        if (responseCode != ResponseCodes.OBEX_HTTP_OK) {
+                            Log.i(TAG, "Response error code is " + responseCode);
+                        }
+                    }
+                }
+                if (putOperation != null) {
+                   putOperation.close();
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Error when closing stream after send " + e.getMessage());
+            }
+        }
+
+        return responseCode;
+    }
+
+    private void handleSendException(String exception) {
+        Log.e(TAG, "Error when sending event: " + exception);
+    }
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMnsRfcommTransport.java b/src/com/android/bluetooth/map/BluetoothMnsRfcommTransport.java
new file mode 100644
index 0000000..fc5d54a
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMnsRfcommTransport.java
@@ -0,0 +1,79 @@
+/*
+* 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 android.bluetooth.BluetoothSocket;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.obex.ObexTransport;
+
+public class BluetoothMnsRfcommTransport implements ObexTransport {
+
+    private final BluetoothSocket mSocket;
+
+    public BluetoothMnsRfcommTransport(BluetoothSocket socket) {
+        super();
+        this.mSocket = socket;
+    }
+
+    public void close() throws IOException {
+        mSocket.close();
+    }
+
+    public DataInputStream openDataInputStream() throws IOException {
+        return new DataInputStream(openInputStream());
+    }
+
+    public DataOutputStream openDataOutputStream() throws IOException {
+        return new DataOutputStream(openOutputStream());
+    }
+
+    public InputStream openInputStream() throws IOException {
+        return mSocket.getInputStream();
+    }
+
+    public OutputStream openOutputStream() throws IOException {
+        return mSocket.getOutputStream();
+    }
+
+    public void connect() throws IOException {
+    }
+
+    public void create() throws IOException {
+    }
+
+    public void disconnect() throws IOException {
+    }
+
+    public void listen() throws IOException {
+    }
+
+    public boolean isConnected() throws IOException {
+        // TODO: add implementation
+        return true;
+    }
+
+    public String getRemoteAddress() {
+        if (mSocket == null)
+            return null;
+        return mSocket.getRemoteDevice().getAddress();
+    }
+
+}