summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAshwini Munigala <AshwiniM@codeaurora.org>2013-06-12 17:05:14 +0530
committerAshwini Munigala <AshwiniM@codeaurora.org>2013-06-27 17:41:21 +0530
commite38b5663e574d3ac86b44a1b8d9e64c9541a142a (patch)
tree5ca818e90c34df0dfe2b9f15e23845ee1a3f4466
parentc6b48c69121f9985d2a8e292671a52ea4ecdda09 (diff)
downloadandroid_packages_apps_BluetoothExt-e38b5663e574d3ac86b44a1b8d9e64c9541a142a.tar.gz
android_packages_apps_BluetoothExt-e38b5663e574d3ac86b44a1b8d9e64c9541a142a.tar.bz2
android_packages_apps_BluetoothExt-e38b5663e574d3ac86b44a1b8d9e64c9541a142a.zip
Bluetooth: Support OBEX MAP profile on Bluedroid.
Porting changes for MAP on Bluedroid as a part of BluetoothExt APK. Support MAP 1.0 version features. Handle MAP Authorization Settings from BluetoothExt APK instead from AOSP Settings proj. CRs-fixed: 504042 Change-Id: If12233e8c2861fec3105076b8fcdc58448405e6a
-rw-r--r--Android.mk6
-rw-r--r--AndroidManifest.xml33
-rw-r--r--res/layout/auth.xml50
-rw-r--r--res/layout/bluetooth_access.xml48
-rw-r--r--res/values/config.xml17
-rw-r--r--res/values/strings.xml24
-rw-r--r--res/values/strings_map.xml49
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMapAuthenticator.java102
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMapRfcommTransport.java86
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMasActivity.java313
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMasAppEmail.java800
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMasAppIf.java615
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMasAppParams.java53
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMasAppSmsMms.java2569
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMasMsg.java38
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMasObexServer.java1320
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMasReceiver.java87
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMasService.java982
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMasSpecParams.java127
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMasTestActivity.java306
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMns.java784
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMnsEmail.java315
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMnsObexSession.java312
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMnsPreference.java167
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMnsRfcommTransport.java93
-rw-r--r--src/org/codeaurora/bluetooth/map/BluetoothMnsSmsMms.java366
-rw-r--r--src/org/codeaurora/bluetooth/map/IBluetoothMasApp.java144
-rw-r--r--src/org/codeaurora/bluetooth/map/MapUtils/BmessageConsts.java210
-rw-r--r--src/org/codeaurora/bluetooth/map/MapUtils/CommonUtils.java156
-rw-r--r--src/org/codeaurora/bluetooth/map/MapUtils/EmailUtils.java1054
-rw-r--r--src/org/codeaurora/bluetooth/map/MapUtils/MapUtils.java1986
-rw-r--r--src/org/codeaurora/bluetooth/map/MapUtils/MapUtilsConsts.java53
-rw-r--r--src/org/codeaurora/bluetooth/map/MapUtils/MsgListingConsts.java212
-rw-r--r--src/org/codeaurora/bluetooth/map/MapUtils/SmsMmsUtils.java237
-rw-r--r--src/org/codeaurora/bluetooth/map/MapUtils/SortMsgListByDate.java41
-rw-r--r--src/org/codeaurora/bluetooth/map/MapUtils/SqlHelper.java147
36 files changed, 13902 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
index 147141b..7ae59ea 100644
--- a/Android.mk
+++ b/Android.mk
@@ -8,6 +8,12 @@ LOCAL_SRC_FILES := \
LOCAL_PACKAGE_NAME := BluetoothExt
LOCAL_CERTIFICATE := platform
+LOCAL_JAVA_LIBRARIES := javax.obex
+LOCAL_JAVA_LIBRARIES += mms-common
+LOCAL_JAVA_LIBRARIES += telephony-common
+LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard
+
+LOCAL_REQUIRED_MODULES := libbluetooth_jni bluetooth.default
LOCAL_PROGUARD_ENABLED := disabled
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7519746..63229db 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -40,7 +40,16 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
<uses-permission android:name="android.permission.BLUETOOTH_STACK" />
+ <uses-permission android:name="android.permission.READ_SMS"></uses-permission>
+ <uses-permission android:name="android.permission.WRITE_SMS"></uses-permission>
+ <uses-permission android:name="android.permission.BROADCAST_SMS"></uses-permission>
+ <uses-permission android:name="android.permission.RECEIVE_SMS"></uses-permission>
+ <uses-permission android:name="android.permission.SEND_SMS"></uses-permission>
+ <uses-permission android:name="android.permission.MMS_PUSH"></uses-permission>
+ <uses-permission android:name="com.android.email.permission.ACCESS_PROVIDER"/>
+ <uses-permission android:name="com.android.email.permission.READ_ATTACHMENT"/>
<application>
+ <uses-library android:name="javax.obex" />
<service
android:name = ".btcservice.BTCService">
</service>
@@ -60,5 +69,29 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<action android:name="android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED" />
</intent-filter>
</receiver>
+ <activity android:name=".map.BluetoothMasActivity"
+ android:process="@string/process"
+ android:excludeFromRecents="true"
+ android:enabled="@bool/profile_supported_map"
+ android:theme="@*android:style/Theme.Holo.Dialog.Alert">
+ <intent-filter>
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <service
+ android:process="@string/process"
+ android:enabled="@bool/profile_supported_map"
+ android:name=".map.BluetoothMasService" >
+ </service>
+ <receiver
+ android:process="@string/process"
+ android:enabled="@bool/profile_supported_map"
+ android:name=".map.BluetoothMasReceiver">
+ <intent-filter>
+ <action android:name="android.bluetooth.adapter.action.STATE_CHANGED"/>
+ <action android:name="android.bluetooth.device.action.BOND_STATE_CHANGED"/>
+ <action android:name="android.bluetooth.device.action.ACL_DISCONNECTED"/>
+ </intent-filter>
+ </receiver>
</application>
</manifest>
diff --git a/res/layout/auth.xml b/res/layout/auth.xml
new file mode 100644
index 0000000..7d1a200
--- /dev/null
+++ b/res/layout/auth.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, The Android Open Source Project
+**
+** 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.
+*/
+-->
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent">
+
+ <LinearLayout
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="20dip"
+ android:gravity="center_horizontal"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <EditText
+ android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dip"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="20dip"
+ android:singleLine="true" />
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/res/layout/bluetooth_access.xml b/res/layout/bluetooth_access.xml
new file mode 100644
index 0000000..d81305b
--- /dev/null
+++ b/res/layout/bluetooth_access.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, The Android Open Source Project
+**
+** 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.
+*/
+-->
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent">
+
+ <LinearLayout
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="20dip"
+ android:layout_marginEnd="20dip"
+ android:gravity="center_horizontal"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <CheckBox android:id="@+id/alwaysallowed"
+ style="?android:attr/textAppearanceMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="2dip"
+ android:text="@string/bluetooth_remember_choice" />
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..247f362
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009-2012 Broadcom Corporation
+ 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.
+-->
+<resources>
+ <bool name="profile_supported_map">true</bool>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..7c01009
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ 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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="defaultname">Carkit</string>
+ <!-- Do not translate. android:process of this application. -->
+ <string name="process" translate="false"><xliff:g id="x" /></string>
+
+ <!-- Bluetooth FTP or MAP permission Alert Activity checkbox text [CHAR LIMIT=none] -->
+ <string name="bluetooth_remember_choice">Always Allow</string>
+
+</resources>
diff --git a/res/values/strings_map.xml b/res/values/strings_map.xml
new file mode 100644
index 0000000..5ed01c7
--- /dev/null
+++ b/res/values/strings_map.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="map_acceptance_dialog_title">%1$s would like to access your phone folders. Give access to %2$s?</string>
+ <string name="map_acceptance_dialog_header">Phone Folder Access</string>
+ <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_notif_ticker">Bluetooth connection request</string>
+ <!-- Notification title when a Bluetooth device wants to pair with us -->
+ <string name="map_notif_title">Session Key Request for MAP</string>
+ <!-- Notification message when a Bluetooth device wants to pair with us -->
+ <string name="map_notif_message">"Touch to connect to \u0022<xliff:g id="device_name">%1$s</xliff:g>\u0022."</string>
+ <string name="map_alert_conn_failed_message">Bluetooth MAP connection failed.</string>
+ <!-- Activity label of BluetoothMasPermissionActivity, also used as Strings in the permission dialog [CHAR LIMIT=none] -->
+ <string name="bluetooth_mas_request">"Message Access request"</string>
+ <!-- Bluetooth MAS permission Alert Activity text [CHAR LIMIT=none] -->
+ <string name="bluetooth_mas_acceptance_dialog_text">%1$s would like to access your messages. Give access to %2$s?</string>
+
+</resources>
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMapAuthenticator.java b/src/org/codeaurora/bluetooth/map/BluetoothMapAuthenticator.java
new file mode 100644
index 0000000..314536e
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMapAuthenticator.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2008-2009, Motorola, Inc.
+ * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.codeaurora.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 = BluetoothMasService.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 PSE challenge PCE
+ public byte[] onAuthenticationResponse(final byte[] userName) {
+ return null;
+ }
+}
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMapRfcommTransport.java b/src/org/codeaurora/bluetooth/map/BluetoothMapRfcommTransport.java
new file mode 100644
index 0000000..154ecb0
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMapRfcommTransport.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2008-2009, Motorola, Inc.
+ * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.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/org/codeaurora/bluetooth/map/BluetoothMasActivity.java b/src/org/codeaurora/bluetooth/map/BluetoothMasActivity.java
new file mode 100644
index 0000000..011641c
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMasActivity.java
@@ -0,0 +1,313 @@
+ /*
+ * Copyright (c) 2008-2009, Motorola, Inc.
+ * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+import android.bluetooth.BluetoothDevice;
+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.text.TextWatcher;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+
+import org.codeaurora.bluetooth.R;
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+/**
+ * MapActivity shows dialogues for accepting incoming map request
+ * with a remote Bluetooth device.
+ */
+public class BluetoothMasActivity extends AlertActivity implements
+ DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener, TextWatcher {
+ private static final String TAG = "BluetoothMasActivity";
+
+ private static final boolean V = BluetoothMasService.VERBOSE;
+
+ private static final int BLUETOOTH_OBEX_AUTHKEY_MAX_LENGTH = 16;
+
+ private static final int DIALOG_YES_NO_CONNECT = 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 = false;
+
+ 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 (!BluetoothMasService.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 (BluetoothMasService.ACCESS_REQUEST_ACTION.equals(action)) {
+ showMapDialog(DIALOG_YES_NO_CONNECT);
+ mCurrentDialog = DIALOG_YES_NO_CONNECT;
+ }
+ else {
+ Log.e(TAG, "Error: this activity may be started only with intent "
+ + "MAP_ACCESS_REQUEST");
+ finish();
+ }
+ registerReceiver(mReceiver, new IntentFilter(
+ BluetoothMasService.USER_CONFIRM_TIMEOUT_ACTION));
+ }
+
+ private void showMapDialog(int id) {
+ final AlertController.AlertParams p = mAlertParams;
+ switch (id) {
+ case DIALOG_YES_NO_CONNECT:
+ p.mIconId = android.R.drawable.ic_dialog_info;
+ p.mTitle = getString(R.string.bluetooth_mas_request);
+ p.mView = createView(DIALOG_YES_NO_CONNECT);
+ p.mPositiveButtonText = getString(android.R.string.yes);
+ p.mPositiveButtonListener = this;
+ p.mNegativeButtonText = getString(android.R.string.no);
+ p.mNegativeButtonListener = this;
+ mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
+ setupAlert();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private String getRemoteDeviceName() {
+ String remoteDeviceName = null;
+ Intent intent = getIntent();
+ if (intent.hasExtra(BluetoothMasService.EXTRA_BLUETOOTH_DEVICE)) {
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothMasService.EXTRA_BLUETOOTH_DEVICE);
+ if (device != null) {
+ remoteDeviceName = device.getName();
+ }
+ }
+
+ return (remoteDeviceName != null) ? remoteDeviceName : getString(R.string.defaultname);
+ }
+
+ private String createDisplayText(final int id) {
+ String mRemoteName = getRemoteDeviceName();
+ switch (id) {
+ case DIALOG_YES_NO_CONNECT:
+ String mMessage1 = getString(R.string.bluetooth_mas_acceptance_dialog_text, mRemoteName,
+ mRemoteName);
+ return mMessage1;
+ default:
+ return null;
+ }
+ }
+
+ private View createView(final int id) {
+ switch (id) {
+ case DIALOG_YES_NO_CONNECT:
+ mView = getLayoutInflater().inflate(R.layout.bluetooth_access, null);
+ messageView = (TextView)mView.findViewById(R.id.message);
+ messageView.setText(createDisplayText(id));
+ mAlwaysAllowed = (CheckBox)mView.findViewById(R.id.alwaysallowed);
+ mAlwaysAllowed.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ mAlwaysAllowedValue = true;
+ } else {
+ mAlwaysAllowedValue = false;
+ }
+ }
+ });
+ return mView;
+ default:
+ return null;
+ }
+ }
+
+ private void onPositive() {
+ if (!mTimeout) {
+ if (mCurrentDialog == DIALOG_YES_NO_CONNECT) {
+ sendIntentToReceiver(BluetoothMasService.ACCESS_ALLOWED_ACTION,
+ BluetoothMasService.EXTRA_ALWAYS_ALLOWED, mAlwaysAllowedValue);
+ }
+ }
+ mTimeout = false;
+ finish();
+ }
+
+ private void onNegative() {
+ if (mCurrentDialog == DIALOG_YES_NO_CONNECT) {
+ sendIntentToReceiver(BluetoothMasService.ACCESS_DISALLOWED_ACTION, null, null);
+ }
+ finish();
+ }
+
+ private void sendIntentToReceiver(final String intentName, final String extraName,
+ final String extraValue) {
+ Intent intent = new Intent(intentName);
+ intent.setClassName(BluetoothMasService.THIS_PACKAGE_NAME, BluetoothMasReceiver.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(BluetoothMasService.THIS_PACKAGE_NAME, BluetoothMasReceiver.class
+ .getName());
+ if (extraName != null) {
+ intent.putExtra(extraName, extraValue);
+ }
+ Intent i = getIntent();
+ if (i.hasExtra(BluetoothMasService.EXTRA_BLUETOOTH_DEVICE)) {
+ intent.putExtra(BluetoothMasService.EXTRA_BLUETOOTH_DEVICE, i.getParcelableExtra(BluetoothMasService.EXTRA_BLUETOOTH_DEVICE));
+ }
+ sendBroadcast(intent);
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ onPositive();
+ break;
+
+ case DialogInterface.BUTTON_NEGATIVE:
+ onNegative();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void onTimeout() {
+ mTimeout = true;
+ if (mCurrentDialog == DIALOG_YES_NO_CONNECT) {
+ if(mView != null) {
+ messageView.setText(getString(R.string.map_acceptance_timeout_message,
+ getRemoteDeviceName()));
+ mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
+ mAlwaysAllowed.setVisibility(View.GONE);
+ mAlwaysAllowed.clearFocus();
+ }
+ }
+
+ 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/org/codeaurora/bluetooth/map/BluetoothMasAppEmail.java b/src/org/codeaurora/bluetooth/map/BluetoothMasAppEmail.java
new file mode 100644
index 0000000..f827205
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMasAppEmail.java
@@ -0,0 +1,800 @@
+/*
+ * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.text.format.Time;
+import android.util.Log;
+
+import org.codeaurora.bluetooth.map.MapUtils.BmessageConsts;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils;
+import org.codeaurora.bluetooth.map.MapUtils.EmailUtils;
+import org.codeaurora.bluetooth.map.MapUtils.MapUtils;
+import org.codeaurora.bluetooth.map.MapUtils.MsgListingConsts;
+import org.codeaurora.bluetooth.map.MapUtils.SqlHelper;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMasMessageListingRsp;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMasMessageRsp;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMasPushMsgRsp;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMsgListRsp;
+import org.codeaurora.bluetooth.map.MapUtils.MapUtils.BadRequestException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import javax.obex.ResponseCodes;
+
+import static org.codeaurora.bluetooth.map.BluetoothMasService.MSG_SERVERSESSION_CLOSE;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.TYPE_DELETED;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.TYPE_DRAFT;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.TYPE_INBOX;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.TYPE_OUTBOX;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.TYPE_SENT;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.DELETED;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.DRAFT;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.DRAFTS;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.INBOX;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.OUTBOX;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.SENT;
+
+public class BluetoothMasAppEmail extends BluetoothMasAppIf {
+ public final String TAG = "BluetoothMasAppEmail";
+ public final boolean V = BluetoothMasService.VERBOSE;
+
+ private ContentObserver mObserver;
+ private static final int[] SPECIAL_MAILBOX_TYPES
+ = {TYPE_DELETED, TYPE_DRAFT, TYPE_INBOX, TYPE_OUTBOX, TYPE_SENT};
+ private static final String[] SPECIAL_MAILBOX_MAP_NAME
+ = {DELETED, DRAFT, INBOX, OUTBOX, SENT};
+ private HashMap<Integer, String> mSpecialMailboxName = new HashMap<Integer, String>();
+
+ public BluetoothMasAppEmail(Context context, Handler handler, BluetoothMns mnsClient,
+ int masId, String remoteDeviceName) {
+ super(context, handler, MESSAGE_TYPE_EMAIL, mnsClient, masId, remoteDeviceName);
+
+ mObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ long id = EmailUtils.getAccountId(mMasId);
+ if (!EmailUtils.hasEmailAccount(mContext, id)) {
+ // Email account removed, disconnect
+ // TODO: inform the user
+ disconnect();
+ }
+ super.onChange(selfChange);
+ }
+ };
+
+ loadSpecialMailboxName();
+ if (V) Log.v(TAG, "BluetoothMasAppEmail Constructor called");
+ }
+
+ private void loadSpecialMailboxName() {
+ mSpecialMailboxName.clear();
+ long id = EmailUtils.getAccountId(mMasId);
+ final String where = EmailUtils.ACCOUNT_KEY + "=" + id + " AND " + EmailUtils.TYPE + "=";
+ String name;
+ for (int i = 0; i < SPECIAL_MAILBOX_TYPES.length; i ++) {
+ name = SqlHelper.getFirstValueForColumn(mContext, EmailUtils.EMAIL_BOX_URI,
+ EmailUtils.DISPLAY_NAME, where + SPECIAL_MAILBOX_TYPES[i], null);
+ if (name.length() > 0) {
+ mSpecialMailboxName.put(i, name);
+ }
+ }
+ }
+
+ /**
+ * Start an MNS obex client session and push notification whenever available
+ */
+ public void startMnsSession(BluetoothDevice remoteDevice) {
+ if (V) Log.v(TAG, "Start MNS Client");
+ mMnsClient.getHandler().obtainMessage(BluetoothMns.MNS_CONNECT, mMasId,
+ -1, remoteDevice).sendToTarget();
+ }
+
+ /**
+ * Stop pushing notifications and disconnect MNS obex session
+ */
+ public void stopMnsSession(BluetoothDevice remoteDevice) {
+ if (V) Log.v(TAG, "Stop MNS Client");
+ mMnsClient.getHandler().obtainMessage(BluetoothMns.MNS_DISCONNECT, mMasId,
+ -1, remoteDevice).sendToTarget();
+ }
+
+ @Override
+ protected List<String> getCompleteFolderList() {
+ if (V) Log.v(TAG, "getCompleteFolderList mCurrentPath: " + mCurrentPath);
+ long id = EmailUtils.getAccountId(mMasId);
+ List<String> list;
+ String splitStrings[] = mCurrentPath.split("/");
+ ArrayList<String> finalList = new ArrayList<String>();
+ String name;
+ int type;
+ int curType;
+ int len =splitStrings.length;
+ if (V) Log.v(TAG, "getCompleteFolderList splitStrings.len = " + splitStrings.length);
+ //Get Default List at "/telecom/msg/"
+ if(len < 3 && (mCurrentPath.equalsIgnoreCase("telecom") ||
+ mCurrentPath.equalsIgnoreCase("telecom/msg"))) {
+ list = EmailUtils.getEmailFolderListAtPath(mContext, id, "");
+ for (int i = 0; i < SPECIAL_MAILBOX_TYPES.length; i ++) {
+ curType = SPECIAL_MAILBOX_TYPES[i];
+ if (V) Log.v(TAG, " getCompleteFolderList: Current Type: " + curType);
+ for (String str : list) {
+ type = EmailUtils.getTypeForFolder(mContext, id, str);
+ if (V) Log.v(TAG, " getCompleteFolderList: type: " + type);
+ if (type == curType) {
+ if (V) Log.v(TAG, " getCompleteFolderList: removing folder : " + str);
+ list.remove(str);
+ break;
+ }
+ }
+ if (!list.contains(SPECIAL_MAILBOX_MAP_NAME[i])) {
+ if (V) Log.v(TAG, " getCompleteFolderList: adding default folder : "
+ + SPECIAL_MAILBOX_MAP_NAME[i]);
+ list.add(SPECIAL_MAILBOX_MAP_NAME[i]);
+ }
+ }
+ for (String str : list) {
+ type = EmailUtils.getTypeForFolder(mContext, id, str);
+ if (type <= ((EmailUtils.TYPE_DELETED) + 1)) {
+ if (V) Log.v(TAG, " getCompleteFolderList: Adding a valid folder:" + str);
+ finalList.add(str);
+ }
+ }
+ }
+ else {
+ //Remove length of "telecom/msg" -> 11 from mCurrentPath , Get folders and subfodlers
+ String path = mCurrentPath.substring(12);
+ list = EmailUtils.getEmailFolderListAtPath(mContext, id, path);
+ for (String str : list) {
+ if (V) Log.v(TAG, " getCompleteFolderList: Processing SerId: " + str);
+ String folderStr = str.substring(path.length()+ 1);
+ String folder[] = folderStr.split("/");
+ if(folder.length == 1){
+ type = EmailUtils.getTypeForFolder(mContext, id, folder[0]);
+ if (V) Log.v(TAG, " getCompleteFolderList: Add Folder:" + folder[0]);
+ finalList.add(folder[0]);
+ }
+ }
+ }
+ if (V) Log.v(TAG, "Returning from CompleteFolderList");
+ return finalList;
+ }
+
+ public boolean checkPrecondition() {
+ long id = EmailUtils.getAccountId(mMasId);
+ if (id == -1) {
+ return false;
+ }
+ return true;
+ }
+
+ public void onConnect() {
+ if (V) Log.v(TAG, "onConnect() registering email account content observer");
+ mContext.getContentResolver().registerContentObserver(
+ EmailUtils.EMAIL_ACCOUNT_URI, true, mObserver);
+ }
+
+ public void onDisconnect() {
+ if (V) Log.v(TAG, "onDisconnect() unregistering email account content observer");
+ mContext.getContentResolver().unregisterContentObserver(mObserver);
+ }
+
+ private void disconnect() {
+ if (V) Log.v(TAG, "disconnect() sending serversession close.");
+ mHandler.obtainMessage(MSG_SERVERSESSION_CLOSE, mMasId, -1).sendToTarget();
+ }
+
+ /*
+ * Email specific methods
+ */
+ @Override
+ protected BluetoothMsgListRsp msgListingSpecific(List<MsgListingConsts> msgList, String name,
+ BluetoothMasMessageListingRsp rsp, BluetoothMasAppParams appParams) {
+ BluetoothMsgListRsp bmlr = new BluetoothMsgListRsp();
+ String fullPath = (name == null || name.length() == 0) ? mCurrentPath :
+ CommonUtils.getFullPath(name, mContext, getCompleteFolderList(), mCurrentPath);
+ if (fullPath == null) {
+ // Child folder not present
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ bmlr.rsp = rsp;
+ return bmlr;
+ }
+
+ if (V) {
+ Log.v(TAG, "appParams.FilterMessageType ::"+ appParams.FilterMessageType);
+ Log.v(TAG, "Condition result:"+ (appParams.FilterMessageType & 0x04));
+ }
+ String splitStrings[] = fullPath.split("/");
+ int len = splitStrings.length;
+ //Add folders and subfolders
+ if (len >= 3) {
+ if (CommonUtils.validateFilterPeriods(appParams) == 0) {
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ bmlr.rsp = rsp;
+ return bmlr;
+ }
+ if (appParams.FilterReadStatus > 0x02) {
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ bmlr.rsp = rsp;
+ return bmlr;
+ }
+
+ if (appParams.FilterPriority == 0 || appParams.FilterPriority == 0x02) {
+ if((appParams.FilterMessageType & 0x04) == 0) {
+ String folderName;
+ if (splitStrings.length < 3) {
+ Log.e(TAG, "The folder path is invalid.");
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;;
+ bmlr.rsp = rsp;
+ return bmlr;
+ }
+ if (V) Log.v(TAG, "splitStrings[len-1] = " + splitStrings[len-1]);
+
+ folderName = EmailUtils.getFolderName(splitStrings);
+ int index = 0;
+ long accountId = EmailUtils.getAccountId(mMasId);
+ for (; index < SPECIAL_MAILBOX_MAP_NAME.length; index ++) {
+ if (SPECIAL_MAILBOX_MAP_NAME[index].equalsIgnoreCase(folderName)) {
+ List<String> folders = EmailUtils.getFoldersForType(mContext,
+ accountId, SPECIAL_MAILBOX_TYPES[index]);
+ List<MsgListingConsts> list = null;
+ for (String folder : folders) {
+ list = getListEmailFromFolder(folder, rsp, appParams);
+ if (list.size() > 0) {
+ msgList.addAll(list);
+ }
+ }
+ break;
+ }
+ }
+ //Add NON SPECIAL FOLDERS
+ if (index >= SPECIAL_MAILBOX_MAP_NAME.length) {
+ msgList = getListEmailFromFolder(folderName, rsp, appParams);
+ }
+ rsp.rsp = ResponseCodes.OBEX_HTTP_OK;
+ bmlr.messageListingSize = rsp.msgListingSize;
+ bmlr.rsp = rsp;
+ bmlr.msgList = msgList;
+ return bmlr;
+ }
+ else {
+ if (V) Log.v(TAG, "Invalid Message Filter, returning empty list");
+ rsp.rsp = ResponseCodes.OBEX_HTTP_OK;
+ bmlr.rsp = rsp;
+ return bmlr;
+ }
+ } else {
+ if (appParams.FilterPriority > 0x02) {
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ bmlr.rsp = rsp;
+ return bmlr;
+ }
+ }
+ }
+ rsp.rsp = ResponseCodes.OBEX_HTTP_OK;
+ bmlr.rsp = rsp;
+ return bmlr;
+ }
+
+ @Override
+ protected BluetoothMasMessageRsp getMessageSpecific(long msgHandle,
+ BluetoothMasMessageRsp rsp, BluetoothMasAppParams bluetoothMasAppParams) {
+ /*
+ * Spec 5.6.4 says MSE shall reject request with value native
+ * for MMS and Email
+ */
+ if ((int)bluetoothMasAppParams.Charset == 0) {
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ return rsp;
+ }
+
+ long emailMsgID = msgHandle - OFFSET_START;
+ String str = EmailUtils.bldEmailBmsg(emailMsgID, rsp, mContext, mRemoteDeviceName);
+ if (V) Log.v(TAG, "\n" + str + "\n");
+ if (str != null && (str.length() > 0)) {
+ final String FILENAME = "message" + getMasId();
+ FileOutputStream bos = null;
+ File file = new File(mContext.getFilesDir() + "/" + FILENAME);
+ file.delete();
+
+ try {
+ bos = mContext.openFileOutput(FILENAME, Context.MODE_PRIVATE);
+ bos.write(str.getBytes());
+ bos.flush();
+ bos.close();
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Unable to write " + FILENAME, e);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to write " + FILENAME, e);
+ }
+
+ File fileR = new File(mContext.getFilesDir() + "/" + FILENAME);
+ if (fileR.exists() == true) {
+ rsp.file = fileR;
+ rsp.fractionDeliver = 1;
+ } else {
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ }
+ return rsp;
+ }
+
+ /**
+ * Push a outgoing message from MAS Client to the network
+ *
+ * @return Response to push command
+ */
+ public BluetoothMasPushMsgRsp pushMsg(String name, File file,
+ BluetoothMasAppParams bluetoothMasAppParams) throws BadRequestException {
+ BluetoothMasPushMsgRsp rsp = new BluetoothMasPushMsgRsp();
+ rsp.response = ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+ rsp.msgHandle = null;
+ if((int)bluetoothMasAppParams.Charset == 0) {
+ rsp.response = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ return rsp;
+ }
+ if(!checkPath(false, name, false) ||
+ mCurrentPath == null ||
+ mCurrentPath.equals("telecom") ||
+ (mCurrentPath.equals("telecom/msg") && (name == null || name.length() == 0))) {
+ rsp.response = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ return rsp;
+ }
+ byte[] readBytes = null;
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(file);
+ if(file.length() > EMAIL_MAX_PUSHMSG_SIZE){
+ rsp.response = ResponseCodes.OBEX_HTTP_ENTITY_TOO_LARGE;
+ rsp.msgHandle = null;
+ Log.d(TAG,"Message body is larger than the max length allowed");
+ return rsp;
+ } else {
+ readBytes = new byte[(int) file.length()];
+ fis.read(readBytes);
+ }
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, e.getMessage());
+ return rsp;
+ } catch (IOException e) {
+ Log.e(TAG, e.getMessage());
+ return rsp;
+ } catch (SecurityException e) {
+ Log.e(TAG, e.getMessage());
+ return rsp;
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException ei) {
+ Log.e(TAG, "Error while closing stream"+ ei.toString());
+ }
+ }
+ }
+
+ String readStr = "";
+ String type = "";
+ try {
+ readStr = new String(readBytes);
+ type = MapUtils.fetchType(readStr);
+ } catch (Exception e) {
+ throw new BadRequestException(e.getMessage());
+ }
+ if (type != null && type.equalsIgnoreCase("EMAIL")) {
+ rsp = pushMessageEmail(rsp, readStr, name);
+ return rsp;
+ }
+ rsp.response = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ return rsp;
+ }
+
+ /**
+ * Sets the message status (read/unread, delete)
+ *
+ * @return Obex response code
+ */
+ public int msgStatus(String msgHandle, BluetoothMasAppParams bluetoothMasAppParams) {
+ if ((bluetoothMasAppParams.StatusIndicator != 0)
+ && (bluetoothMasAppParams.StatusIndicator != 1)) {
+ return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+ }
+ if ((bluetoothMasAppParams.StatusValue != 0)
+ && (bluetoothMasAppParams.StatusValue != 1)) {
+ return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+ }
+ final long handle = Long.valueOf(msgHandle);
+ if (handle < OFFSET_START && handle > OFFSET_END) {
+ return ResponseCodes.OBEX_HTTP_NOT_FOUND;
+ }
+ return setMsgStatusEmail(handle, bluetoothMasAppParams);
+ }
+
+ /**
+ * Sets the message update
+ *
+ * @return Obex response code
+ */
+ public int msgUpdate() {
+ if (V) Log.v(TAG, "Message Update");
+ long accountId = EmailUtils.getAccountId(mMasId);
+ if (V) Log.v(TAG, " Account id for Inbox Update: " +accountId);
+
+ Intent emailIn = new Intent();
+
+ emailIn.setAction("com.android.email.intent.action.MAIL_SERVICE_WAKEUP");
+ emailIn.putExtra("com.android.email.intent.extra.ACCOUNT", accountId);
+ mContext.startService(emailIn);
+
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ /**
+ * Adds an Email to the Email ContentProvider
+ */
+ private String addToEmailFolder(String folder, String address, String text, String subject,
+ String OrigEmail, String OrigName) {
+ if (V) {
+ Log.v(TAG, "-------------");
+ Log.v(TAG, "address " + address);
+ Log.v(TAG, "TEXT " + text);
+ }
+ // TODO: need to insert a row in the body table and update the mailbox table
+ // with the no of messages unread
+ Cursor cr;
+ int folderId = -1;
+ long accountId = -1;
+ Time timeObj = new Time();
+ timeObj.setToNow();
+
+ Cursor cr1;
+ String whereClause1 = "UPPER(emailAddress) LIKE '"+OrigEmail.toUpperCase().trim()+"'";
+ cr1 = mContext.getContentResolver().query(
+ Uri.parse("content://com.android.email.provider/account"),
+ null, whereClause1, null, null);
+ if (cr1 != null) {
+ if (cr1.getCount() > 0) {
+ cr1.moveToFirst();
+ accountId = cr1.getInt(cr1.getColumnIndex("_id"));
+ }
+ cr1.close();
+ }
+ if (accountId == -1) {
+ accountId = EmailUtils.getAccountId(mMasId);
+ }
+ if (DRAFT.equalsIgnoreCase(folder)) {
+ List<String> folders = EmailUtils.getFoldersForType(mContext, accountId,
+ EmailUtils.TYPE_DRAFT);
+ if (V) Log.v(TAG, "DRAFT folders: " + folders.toString());
+ if (folders.size() == 0) {
+ // no draft folder
+ return INTERNAL_ERROR;
+ }
+ if (folders.contains(DRAFTS)) {
+ folder = DRAFTS;
+ } else {
+ folder = folders.get(0);
+ }
+ }
+
+ String whereClause = "UPPER(displayName) = '"+folder.toUpperCase().trim()+"'";
+ cr = mContext.getContentResolver().query(
+ Uri.parse("content://com.android.email.provider/mailbox"),
+ null, whereClause, null, null);
+ if (cr != null) {
+ if (cr.getCount() > 0) {
+ cr.moveToFirst();
+ folderId = cr.getInt(cr.getColumnIndex("_id"));
+ }
+ cr.close();
+ }
+ if (folderId == -1) {
+ return INTERNAL_ERROR;
+ }
+
+ if (V){
+ Log.v(TAG, "-------------");
+ Log.v(TAG, "To address " + address);
+ Log.v(TAG, "Text " + text);
+ Log.v(TAG, "Originator email address:: " + OrigEmail);
+ Log.v(TAG, "Originator email name:: " + OrigName);
+ Log.v(TAG, "Time Stamp:: " + timeObj.toMillis(false));
+ Log.v(TAG, "Account Key:: " + accountId);
+ Log.v(TAG, "Folder Id:: " + folderId);
+ Log.v(TAG, "Folder Name:: " + folder);
+ Log.v(TAG, "Subject" + subject);
+ }
+ ContentValues values = new ContentValues();
+ values.put("syncServerTimeStamp", 0);
+ values.put("syncServerId", "5:65");
+ values.put("displayName", OrigName.trim());
+ values.put("timeStamp", timeObj.toMillis(false));
+ values.put("subject", subject.trim());
+ values.put("flagLoaded", "1");
+ values.put("flagFavorite", "0");
+ values.put("flagAttachment", "0");
+ values.put("flags", "0");
+
+ values.put("accountKey", accountId);
+ values.put("fromList", OrigEmail.trim());
+
+ values.put("mailboxKey", folderId);
+ values.put("toList", address.trim());
+ values.put("flagRead", 0);
+
+ Uri uri = mContext.getContentResolver().insert(
+ Uri.parse("content://com.android.email.provider/message"), values);
+ if (V){
+ Log.v(TAG, " NEW URI " + (uri == null ? "null" : uri.toString()));
+ }
+
+ if (uri == null) {
+ return INTERNAL_ERROR;
+ }
+ String str = uri.toString();
+ String[] splitStr = str.split("/");
+ if (splitStr.length < 5) {
+ return INTERNAL_ERROR;
+ }
+ if (V){
+ Log.v(TAG, " NEW HANDLE " + splitStr[4]);
+ }
+
+ // TODO: need to insert into the body table
+ // --seems like body table gets updated automatically
+ ContentValues valuesBody = new ContentValues();
+ valuesBody.put("messageKey", splitStr[4]);
+ valuesBody.put("textContent", text);
+
+ mContext.getContentResolver().insert(
+ Uri.parse("content://com.android.email.provider/body"), valuesBody);
+ long virtualMsgId;
+ virtualMsgId = Long.valueOf(splitStr[4]) + OFFSET_START;
+ return Long.toString(virtualMsgId);
+ }
+
+ private List<MsgListingConsts> getListEmailFromFolder(String folderName,
+ BluetoothMasMessageListingRsp rsp, BluetoothMasAppParams appParams) {
+ List<MsgListingConsts> msgList = new ArrayList<MsgListingConsts>();
+ String urlEmail = "content://com.android.email.provider/message";
+ Uri uriEmail = Uri.parse(urlEmail);
+ ContentResolver crEmail = mContext.getContentResolver();
+
+ String whereClauseEmail = EmailUtils.getConditionString(folderName, mContext, appParams,
+ mMasId);
+
+ if (V){
+ Log.v(TAG, "## whereClauseEmail ##:" + whereClauseEmail);
+ }
+ Cursor cursor = crEmail.query(uriEmail, null, whereClauseEmail, null, "timeStamp desc");
+
+ if (cursor != null && V){
+ Log.v(TAG, "move to First" + cursor.moveToFirst());
+ }
+ if (cursor != null && V){
+ Log.v(TAG, "move to Liststartoffset"
+ + cursor.moveToPosition(appParams.ListStartOffset));
+ }
+ if (cursor != null && cursor.moveToFirst()) {
+ int idInd = cursor.getColumnIndex("_id");
+ int displayNameIndex = cursor.getColumnIndex("displayName");
+ int fromIndex = cursor.getColumnIndex("fromList");
+ int toIndex = cursor.getColumnIndex("toList");
+ int dateInd = cursor.getColumnIndex("timeStamp");
+ int readInd = cursor.getColumnIndex("flagRead");
+ int subjectInd = cursor.getColumnIndex("subject");
+ int replyToInd = cursor.getColumnIndex("replyToList");
+
+ do {
+ /*
+ * Apply remaining filters
+ */
+
+ if (V) Log.v(TAG, " msgListSize " + rsp.msgListingSize);
+ rsp.msgListingSize++;
+
+ String subject = cursor.getString(subjectInd);
+ String timestamp = cursor.getString(dateInd);
+ String senderName = cursor.getString(displayNameIndex);
+ String senderAddressing = cursor.getString(fromIndex);
+ String recipientName = cursor.getString(toIndex);
+ String recipientAddressing = cursor.getString(toIndex);
+ String msgId = cursor.getString(idInd);
+ String readStatus = cursor.getString(readInd);
+ String replyToStr = cursor.getString(replyToInd);
+
+ /*
+ * Don't want the listing; just send the listing size after
+ * applying all the filters.
+ */
+
+ /*
+ * TODO: Skip the first ListStartOffset record(s). Don't write
+ * more than MaxListCount record(s).
+ */
+
+ MsgListingConsts emailMsg = new MsgListingConsts();
+ emailMsg = EmailUtils.bldEmailMsgLstItem(mContext, folderName, appParams,
+ subject, timestamp, senderName, senderAddressing,
+ recipientName, recipientAddressing,
+ msgId, readStatus, replyToStr, OFFSET_START);
+
+ // New Message?
+ if ((rsp.newMessage == 0) && (cursor.getInt(readInd) == 0)) {
+ rsp.newMessage = 1;
+ }
+ msgList.add(emailMsg);
+ } while (cursor.moveToNext());
+ }
+ if (cursor != null) {
+ cursor.close();
+ }
+ return msgList;
+ }
+
+ private boolean isAllowedEmailFolderForPush(String folderName) {
+ if (DRAFT.equalsIgnoreCase(folderName) || OUTBOX.equalsIgnoreCase(folderName)) {
+ return true;
+ }
+ long id = EmailUtils.getAccountId(mMasId);
+ int type = EmailUtils.getTypeForFolder(mContext, id, folderName);
+ if (type == EmailUtils.TYPE_DRAFT || type == EmailUtils.TYPE_OUTBOX) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private BluetoothMasPushMsgRsp pushMessageEmail(BluetoothMasPushMsgRsp rsp,
+ String readStr, String name) throws BadRequestException {
+ if (V) Log.v(TAG, " Before fromBmessageemail method:: "+readStr);
+
+ BmessageConsts bMsg = MapUtils.fromBmessageEmail(mContext,readStr,mMasId);
+ String address = bMsg.getRecipientVcard_email();
+ String text = bMsg.getBody_msg();
+ String subject = bMsg.getSubject();
+ String originator = bMsg.getOriginatorVcard_email();
+ String origName = bMsg.getOriginatorVcard_name();
+
+ String fullPath = (name == null || name.length() == 0)
+ ? mCurrentPath : mCurrentPath + "/" + name;
+ String splitStrings[] = fullPath.split("/");
+ mMnsClient.addMceInitiatedOperation("+");
+ int tmp = splitStrings.length;
+ String folderName;
+ if (name != null) {
+ if (name.length() == 0) {
+ folderName = splitStrings[tmp - 1];
+ } else {
+ folderName = name;
+ }
+ } else {
+ folderName = splitStrings[tmp - 1];
+ }
+ if (!isAllowedEmailFolderForPush(folderName)) {
+ rsp.msgHandle = null;
+ rsp.response = ResponseCodes.OBEX_HTTP_FORBIDDEN;
+ return rsp;
+ }
+ String handle = addToEmailFolder(folderName, address, text, subject, originator, origName);
+ if (INTERNAL_ERROR == handle) { // == comparison valid here
+ rsp.msgHandle = null;
+ rsp.response = ResponseCodes.OBEX_HTTP_NOT_FOUND;
+ return rsp;
+ }
+ rsp.msgHandle = handle;
+ rsp.response = ResponseCodes.OBEX_HTTP_OK;
+
+ long accountId = EmailUtils.getAccountId(mMasId);
+ if (V) Log.v(TAG, " Account id before Mail service:: " + accountId);
+
+ Intent emailIn = new Intent();
+ emailIn.setAction("com.android.email.intent.action.MAIL_SERVICE_SEND_PENDING");
+ emailIn.putExtra("com.android.email.intent.extra.ACCOUNT", accountId);
+ mContext.startService(emailIn);
+ return rsp;
+ }
+
+ private int setMsgStatusEmail(long handle,
+ BluetoothMasAppParams bluetoothMasAppParams){
+ //Query the mailbox table to get the id values for Inbox and Trash folder
+ Uri uri1 = Uri.parse("content://com.android.email.provider/mailbox");
+ Cursor cr1 = mContext.getContentResolver().query(uri1, null,
+ "(UPPER(displayName) = 'INBOX' OR UPPER(displayName) LIKE '%TRASH%')", null, null);
+ int inboxFolderId = 0;
+ int deletedFolderId = 0;
+ int msgFolderId = 0;
+ String folderName;
+ if (cr1 != null && cr1.moveToFirst()) {
+ do {
+ folderName = cr1.getString(cr1.getColumnIndex("displayName"));
+ if(folderName.equalsIgnoreCase("INBOX")){
+ inboxFolderId = cr1.getInt(cr1.getColumnIndex("_id"));
+ } else {
+ deletedFolderId = cr1.getInt(cr1.getColumnIndex("_id"));
+ }
+ } while (cr1.moveToNext());
+ }
+ if (cr1 != null) {
+ cr1.close();
+ }
+
+ //Query the message table for the given message id
+ long emailMsgId = handle - 0;
+ emailMsgId = handle - OFFSET_START;
+ Uri uri2 = Uri.parse("content://com.android.email.provider/message/"+emailMsgId);
+ Cursor crEmail = mContext.getContentResolver().query(uri2, null, null, null, null);
+ if (crEmail != null && crEmail.moveToFirst()) {
+ if (bluetoothMasAppParams.StatusIndicator == 0) {
+ /* Read Status */
+ ContentValues values = new ContentValues();
+ values.put("flagRead", bluetoothMasAppParams.StatusValue);
+ mContext.getContentResolver().update(uri2, values, null, null);
+ } else {
+ if (bluetoothMasAppParams.StatusValue == 1) { //if the email is deleted
+ msgFolderId = crEmail.getInt(crEmail.getColumnIndex("mailboxKey"));
+ if(msgFolderId == deletedFolderId){
+ // TODO: need to add notification for deleted email here
+ mMnsClient.addMceInitiatedOperation(Long.toString(handle));
+ mContext.getContentResolver().delete(
+ Uri.parse("content://com.android.email.provider/message/"
+ + emailMsgId), null, null);
+ } else {
+ ContentValues values = new ContentValues();
+ values.put("mailboxKey", deletedFolderId);
+ mContext.getContentResolver().update(uri2, values, null, null);
+ }
+ } else { // if the email is undeleted
+ // TODO: restore it to original folder
+ ContentValues values = new ContentValues();
+ values.put("mailboxKey", inboxFolderId);
+ mContext.getContentResolver().update(uri2, values, null, null);
+ }
+ }
+ }
+ if (crEmail != null) {
+ crEmail.close();
+ }
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+}
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMasAppIf.java b/src/org/codeaurora/bluetooth/map/BluetoothMasAppIf.java
new file mode 100644
index 0000000..90afe68
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMasAppIf.java
@@ -0,0 +1,615 @@
+/*
+ * Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.codeaurora.bluetooth.map.MapUtils.MapUtils;
+import org.codeaurora.bluetooth.map.MapUtils.MsgListingConsts;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMasMessageListingRsp;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMasMessageRsp;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMsgListRsp;
+import org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.VcardContent;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.obex.ResponseCodes;
+
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.DELETED;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.DRAFT;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.INBOX;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.OUTBOX;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.SENT;
+
+/**
+ * This class provides the application interface for MAS Server It interacts
+ * with the SMS repository using Sms Content Provider to service the MAS
+ * requests. It also initializes BluetoothMns thread which is used for MNS
+ * connection.
+ */
+
+public abstract class BluetoothMasAppIf implements IBluetoothMasApp {
+ public static final String TAG = "BluetoothMasAppIf";
+ public static final boolean D = BluetoothMasService.DEBUG;
+ public static final boolean V = BluetoothMasService.VERBOSE;
+
+ protected static final String INTERNAL_ERROR = "ERROR";
+
+ // IOP work around for BMW carkit
+ // The connection is dropped by the carkit When GetMessagesListing results empty list
+ // So, we ignore improper filtering request by messageType.
+ protected static final String BMW = "BMW";
+
+ protected Context mContext;
+ protected int mSupportedMessageTypes;
+ protected int mMasId;
+ protected String mRemoteDeviceName;
+
+ protected String mCurrentPath = null;
+ protected BluetoothMns mMnsClient;
+ protected Handler mHandler = null;
+
+ protected final long OFFSET_START;
+ protected final long OFFSET_END;
+
+ public BluetoothMasAppIf(Context context, Handler handler, int supportedMessageTypes,
+ BluetoothMns mnsClient, int masId, String remoteDeviceName) {
+ mContext = context;
+ mSupportedMessageTypes = supportedMessageTypes;
+ mMasId = masId;
+ mHandler = handler;
+ mMnsClient = mnsClient;
+ mRemoteDeviceName = remoteDeviceName;
+
+ OFFSET_START = HANDLE_OFFSET[masId];
+ OFFSET_END = HANDLE_OFFSET[masId + 1] - 1;
+
+ if (V) Log.v(TAG, "Constructor called");
+ }
+
+ /**
+ * This must be overridden
+ * @return folder list for corresponding MAS
+ */
+ protected abstract List<String> getCompleteFolderList();
+
+ /**
+ * Check the path to a given folder. If setPathFlag is set,
+ * set the path to the new value. Else, just check if the path
+ * exists and don't change the current path.
+ *
+ * @return true if the path exists, and could be accessed.
+ */
+ public boolean checkPath(boolean up, String name, boolean setPathFlag) {
+ if (V) Log.v(TAG, "setPath called");
+ if (V) {
+ Log.v(TAG, "mCurrentPath::"+mCurrentPath);
+ Log.v(TAG, "name::"+name);
+ }
+ if (up == false) {
+ if (name == null || name.length() == 0) {
+ mCurrentPath = (setPathFlag) ? null : mCurrentPath;
+ return true;
+ }
+ } else {
+ if (mCurrentPath == null) {
+ // Can't go above root
+ return false;
+ } else {
+ int LastIndex;
+ if (mCurrentPath.toUpperCase().contains("GMAIL")) {
+ LastIndex = mCurrentPath.lastIndexOf('/');
+ mCurrentPath = mCurrentPath.substring(0, LastIndex);
+ LastIndex = mCurrentPath.lastIndexOf('/');
+ } else {
+ LastIndex = mCurrentPath.lastIndexOf('/');
+ }
+ if (LastIndex < 0) {
+ // Reaches root
+ mCurrentPath = null;
+ } else {
+ mCurrentPath = mCurrentPath.substring(0, LastIndex);
+ }
+ }
+ if (name == null || name.length() == 0) {
+ // Only going up by one
+ return true;
+ }
+ }
+
+ if (mCurrentPath == null) {
+ if (TELECOM.equals(name)) {
+ mCurrentPath = (setPathFlag) ? TELECOM : mCurrentPath;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ String splitStrings[] = mCurrentPath.split("/");
+
+ boolean Result = false;
+ switch (splitStrings.length) {
+ case 0:
+ Result=false;
+ break;
+ case 1:
+ if (name.equals(MSG)) {
+ mCurrentPath += (setPathFlag) ? ("/" + name) : "";
+ Result = true;
+ }
+ break;
+ //Handle folders and subfolders
+ default:
+ List<String> completeFolderList = getCompleteFolderList();
+ for (String FolderName : completeFolderList) {
+ //added second condition for gmail sent folder
+ if (FolderName.equalsIgnoreCase(name)) {
+ mCurrentPath += (setPathFlag) ? ("/" + name) : "";
+ Result = true;
+ break;
+ }
+ }
+ break;
+ }
+ return Result;
+ }
+
+ /**
+ * Set the path to a given folder.
+ *
+ * @return true if the path exists, and could be accessed.
+ */
+ public boolean setPath(boolean up, String name) {
+ return checkPath(up, name, true);
+ }
+
+ /**
+ * Get the number of messages in the folder
+ *
+ * @return number of messages; -1 if error
+ */
+ public int folderListingSize() {
+ if (V) Log.v(TAG, "folderListingSize called, current path " + mCurrentPath);
+
+ if (mCurrentPath == null) {
+ // at root, only telecom folder should be present
+ return 1;
+ }
+
+ if (mCurrentPath.equals(TELECOM)) {
+ // at root -> telecom, only msg folder should be present
+ return 1;
+ }
+ //Add folders and subfolders
+ List<String> completeFolderList = getCompleteFolderList();
+ return completeFolderList.size();
+
+ }
+
+ /**
+ * Get the XML listing of the folders at CurrenthPath
+ *
+ * @return XML listing of the folders
+ */
+ public String folderListing(BluetoothMasAppParams appParam) {
+ if (V) Log.v(TAG, "folderListing called, current path " + mCurrentPath);
+
+ List<String> list = new ArrayList<String>();
+
+ if (mCurrentPath == null) {
+ // at root, only telecom folder should be present
+ if (appParam.ListStartOffset == 0) {
+ list.add(TELECOM);
+ }
+ }else if (mCurrentPath.equals(TELECOM)) {
+ // at root -> telecom, only msg folder should be present
+ if (appParam.ListStartOffset == 0) {
+ list.add(MSG);
+ }
+ } else if (!(mCurrentPath.equals(TELECOM + "/" + MSG + "/" + INBOX) ||
+ mCurrentPath.equals(TELECOM + "/" + MSG + "/" + OUTBOX) ||
+ mCurrentPath.equals(TELECOM + "/" + MSG + "/" + DRAFT) ||
+ mCurrentPath.equals(TELECOM + "/" + MSG + "/" + DELETED) ||
+ mCurrentPath.equals(TELECOM + "/" + MSG + "/" + SENT))) {
+ //Add folders and subfolders NOT for SPECIAL FOLDERS
+ int offset = 0;
+ int added = 0;
+ List<String> completeFolderList = getCompleteFolderList();
+ for (String Folder : completeFolderList) {
+ offset++;
+ if ((offset > appParam.ListStartOffset)
+ && (added < appParam.MaxListCount)) {
+ list.add(Folder);
+ if (V) Log.v(TAG, "folderListing AddFolder " + Folder);
+ added++;
+ }
+ }
+ }
+
+ return MapUtils.folderListingXML(list);
+
+ }
+
+ static final int PHONELOOKUP_ID_COLUMN_INDEX = 0;
+ static final int PHONELOOKUP_LOOKUP_KEY_COLUMN_INDEX = 1;
+ static final int PHONELOOKUP_DISPLAY_NAME_COLUMN_INDEX = 2;
+
+ static final int EMAIL_DATA_COLUMN_INDEX = 0;
+
+ private List<VcardContent> mVcardList = new ArrayList<VcardContent>();;
+ protected VcardListContentObserver mVcardListObserver = new VcardListContentObserver();
+ protected VcardContent getVcardContent(String phoneAddress) {
+ VcardContent vCard = new VcardContent();
+ vCard.tel = phoneAddress;
+
+ Uri uriContacts = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(phoneAddress));
+ Cursor cursorContacts = mContext.getContentResolver().query(
+ uriContacts,
+ new String[] { PhoneLookup._ID, PhoneLookup.LOOKUP_KEY,
+ PhoneLookup.DISPLAY_NAME }, null, null, null);
+ if (cursorContacts == null) {
+ return vCard;
+ }
+ cursorContacts.moveToFirst();
+ if (cursorContacts.getCount() > 0) {
+ long contactId = cursorContacts
+ .getLong(PHONELOOKUP_ID_COLUMN_INDEX);
+ String lookupKey = cursorContacts
+ .getString(PHONELOOKUP_LOOKUP_KEY_COLUMN_INDEX);
+ vCard.name = cursorContacts
+ .getString(PHONELOOKUP_DISPLAY_NAME_COLUMN_INDEX);
+
+ Uri lookUpUri = Contacts.getLookupUri(contactId, lookupKey);
+ String Id = lookUpUri.getLastPathSegment();
+
+ Cursor crEm = mContext.getContentResolver().query(Email.CONTENT_URI,
+ new String[] { Email.DATA }, Email.CONTACT_ID + "=?",
+ new String[] { Id }, null);
+ if (crEm != null) {
+ if (crEm.moveToFirst()) {
+ vCard.email = "";
+ if (crEm.moveToFirst()) {
+ do {
+ vCard.email += crEm.getString(EMAIL_DATA_COLUMN_INDEX) + ";";
+ } while (crEm.moveToNext());
+ }
+ }
+ crEm.close();
+ }
+ }
+ cursorContacts.close();
+ return vCard;
+ }
+ public final class VcardListContentObserver extends ContentObserver {
+ public VcardListContentObserver() {
+ super(null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ Log.v(TAG, "Clear VcardList");
+ if(!mVcardList.isEmpty())
+ mVcardList.clear();
+ }
+ }
+
+
+ /**
+ * Check if the entry is not to be filtered out (allowed)
+ */
+ protected boolean allowEntry(String phoneAddress, String filterString) {
+ boolean found = false;
+ VcardContent foundEntry = null;
+ for (VcardContent elem : mVcardList) {
+ if (elem.tel.contains(phoneAddress)) {
+ found = true;
+ foundEntry = elem;
+ }
+ }
+ if (found == false) {
+ VcardContent vCard = getVcardContent(phoneAddress);
+ if (vCard != null) {
+ mVcardList.add(vCard);
+ found = true;
+ foundEntry = vCard;
+ if (V) {
+ Log.v(TAG, " NEW VCARD ADDED " + vCard.tel + vCard.name
+ + vCard.email);
+ }
+ } else {
+ if (V) Log.v(TAG, "VCARD NOT FOUND ERROR");
+ }
+ }
+
+ if (found == true) {
+ String regExp = filterString.replace("*", ".*[0-9A-Za-z].*");
+ if ((foundEntry.tel.matches(".*"+regExp+".*"))
+ || (foundEntry.name.matches(".*"+regExp+".*"))
+ || (foundEntry.email.matches(".*"+regExp+".*"))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get the contact name for the given phone number
+ */
+ protected String getContactName(String phoneNumber) {
+ boolean found = false;
+ VcardContent foundEntry = null;
+ if(phoneNumber == null){
+ return null;
+ }
+ if(V) Log.v(TAG, "getContactName " + phoneNumber);
+ for (VcardContent elem : mVcardList) {
+ if (elem.tel == null){
+ continue;
+ }
+ if (elem.tel.contains(phoneNumber)) {
+ found = true;
+ foundEntry = elem;
+ break;
+ }
+ }
+ if (found == false) {
+ foundEntry = getVcardContent(phoneNumber);
+ if (foundEntry != null) {
+ mVcardList.add(foundEntry);
+ found = true;
+ }
+ }
+ if (found == true) {
+ return foundEntry.name;
+ }
+
+ return null;
+ }
+
+ protected class OwnerInfo {
+ public String Name;
+ public String Number;
+ }
+
+ protected OwnerInfo getOwnerInfo() {
+ OwnerInfo info = new OwnerInfo();
+ TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ if (tm != null) {
+ String localPhoneNum = tm.getLine1Number();
+ String localPhoneName = tm.getLine1AlphaTag();
+
+ if (TextUtils.isEmpty(localPhoneNum)) {
+ localPhoneNum = "";
+ }
+ if (TextUtils.isEmpty(localPhoneName)) {
+ localPhoneName = mContext.getString(android.R.string.unknownName);
+ }
+ info.Name = localPhoneName;
+ info.Number = localPhoneNum;
+ }
+ return info;
+ }
+
+ private OwnerInfo ownerInfo = null;
+
+ /**
+ * Get the owners name
+ */
+ protected String getOwnerName() {
+ if (ownerInfo == null) {
+ ownerInfo = getOwnerInfo();
+ }
+ return ownerInfo.Name;
+ }
+
+ /**
+ * Get the owners phone number
+ */
+ protected String getOwnerNumber() {
+ if (ownerInfo == null) {
+ ownerInfo = getOwnerInfo();
+ }
+ return ownerInfo.Number;
+ }
+
+ /**
+ * Get the list of message in the given folder.
+ * It must be implemented for MessageType specific
+ * @param msgList
+ * @param name
+ * @param rsp
+ * @param appParams
+ * @return
+ */
+ protected abstract BluetoothMsgListRsp msgListingSpecific(List<MsgListingConsts> msgList,
+ String name, BluetoothMasMessageListingRsp rsp, BluetoothMasAppParams appParams);
+
+ /**
+ * Get the list of messages in the given folder
+ *
+ * @return Listing of messages in MAP-msg-listing format
+ */
+ public BluetoothMasMessageListingRsp msgListing(String name, BluetoothMasAppParams appParams) {
+ BluetoothMasMessageListingRsp rsp = new BluetoothMasMessageListingRsp();
+ boolean fileGenerated = false;
+ final String FILENAME = "msglist" + getMasId();
+
+ List<MsgListingConsts> msgList = new ArrayList<MsgListingConsts>();
+
+ if (appParams == null) {
+ return null;
+ }
+
+ BluetoothMsgListRsp specificRsp = msgListingSpecific(msgList, name, rsp, appParams);
+ rsp = specificRsp.rsp;
+
+ if (rsp.rsp != ResponseCodes.OBEX_HTTP_OK) {
+ return rsp;
+ }
+ msgList = specificRsp.msgList;
+ // Process the list based on MaxListCount and list offset
+ String str = null;
+ int numOfItems = msgList.size();
+ int msgDelta = numOfItems - appParams.ListStartOffset;
+ int startIdx = appParams.ListStartOffset;
+ int stopIdx = 0;
+ if (msgDelta <= 0) {
+ List<MsgListingConsts> msgSubList = new ArrayList<MsgListingConsts>();;
+ str = MapUtils.messageListingXML(msgSubList);
+ } else {
+ if (msgDelta <= appParams.MaxListCount) {
+ stopIdx = startIdx + msgDelta;
+ } else {
+ stopIdx = startIdx + appParams.MaxListCount;
+ }
+ List<MsgListingConsts> msgSubList = msgList.subList(startIdx,
+ stopIdx);
+ str = MapUtils.messageListingXML(msgSubList);
+ }
+ if (str == null) {
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ return rsp;
+ }
+
+ // String str = "this is a test for the data file";
+ try {
+ FileOutputStream bos = mContext.openFileOutput(FILENAME, Context.MODE_PRIVATE);
+ bos.write(str.getBytes());
+ bos.flush();
+ bos.close();
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ msgList.clear();
+
+ if (V) {
+ Log.v(TAG, "");
+ Log.v(TAG, " MESSAGE LISTING FULL ( total length)" + str.length());
+ Log.v(TAG, str);
+ }
+
+ try {
+ FileInputStream fis = new FileInputStream(mContext.getFilesDir()
+ + "/" + FILENAME);
+ fis.close();
+ fileGenerated = true;
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ if (fileGenerated == true) {
+ File file = new File(mContext.getFilesDir() + "/" + FILENAME);
+ rsp.file = file;
+ }
+ rsp.rsp = ResponseCodes.OBEX_HTTP_OK;
+ return rsp;
+ }
+
+ protected abstract BluetoothMasMessageRsp getMessageSpecific(long msgHandle,
+ BluetoothMasMessageRsp rsp, BluetoothMasAppParams bluetoothMasAppParams);
+
+ /**
+ * Get the message for the given message handle
+ *
+ * @return BMSG object
+ */
+ public BluetoothMasMessageRsp msg(String msgHandle,
+ BluetoothMasAppParams bluetoothMasAppParams) {
+ BluetoothMasMessageRsp rsp = new BluetoothMasMessageRsp();
+ if (msgHandle == null || msgHandle.length() == 0) {
+ rsp.file = null;
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ return rsp;
+ }
+ final long handle = Long.valueOf(msgHandle);
+ if (handle < OFFSET_START && handle > OFFSET_END) {
+ rsp.rsp = ResponseCodes.OBEX_HTTP_NOT_FOUND;
+ return rsp;
+ }
+
+ return getMessageSpecific(handle, rsp, bluetoothMasAppParams);
+ }
+
+ /**
+ * Enable/disable notification
+ *
+ * @return Obex response code
+ */
+ public int notification(BluetoothDevice remoteDevice,
+ BluetoothMasAppParams bluetoothMasAppParams) {
+ if (bluetoothMasAppParams.Notification == 1) {
+ startMnsSession(remoteDevice);
+ return ResponseCodes.OBEX_HTTP_OK;
+ } else if (bluetoothMasAppParams.Notification == 0) {
+ stopMnsSession(remoteDevice);
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+ }
+
+ /**
+ * Start an MNS obex client session and push notification whenever available
+ */
+ public abstract void startMnsSession(BluetoothDevice remoteDevice);
+
+ /**
+ * Stop pushing notifications and disconnect MNS obex session
+ */
+ public abstract void stopMnsSession(BluetoothDevice remoteDevice);
+
+ public int getMasId() {
+ return mMasId;
+ }
+}
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMasAppParams.java b/src/org/codeaurora/bluetooth/map/BluetoothMasAppParams.java
new file mode 100644
index 0000000..4514d4b
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMasAppParams.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+public final class BluetoothMasAppParams {
+
+ public int MaxListCount;
+ public int ListStartOffset;
+ public short SubjectLength;
+ public long ParameterMask;
+ public byte FilterMessageType;
+ public byte FilterReadStatus;
+ public byte FilterPriority;
+ public String FilterPeriodBegin;
+ public String FilterPeriodEnd;
+ public String FilterRecipient;
+ public String FilterOriginator;
+ public byte FractionRequest;
+ public byte Charset;
+ public byte Attachment;
+ public byte Retry;
+ public byte Transparent;
+ public byte StatusIndicator;
+ public byte StatusValue;
+ public byte Notification;
+
+};
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMasAppSmsMms.java b/src/org/codeaurora/bluetooth/map/BluetoothMasAppSmsMms.java
new file mode 100644
index 0000000..69fe818
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMasAppSmsMms.java
@@ -0,0 +1,2569 @@
+/*
+ * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.codeaurora.bluetooth.map;
+
+import android.app.ActivityManager;
+import android.bluetooth.BluetoothDevice;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.telephony.SmsManager;
+import android.telephony.SmsMessage;
+import android.telephony.TelephonyManager;
+import android.text.format.Time;
+import android.util.Log;
+import android.util.TimeFormatException;
+
+import org.codeaurora.bluetooth.map.MapUtils.BmessageConsts;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils;
+import org.codeaurora.bluetooth.map.MapUtils.MapUtils;
+import org.codeaurora.bluetooth.map.MapUtils.MsgListingConsts;
+import org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils;
+import org.codeaurora.bluetooth.map.MapUtils.SortMsgListByDate;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMasMessageListingRsp;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMasMessageRsp;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMasPushMsgRsp;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMsgListRsp;
+import org.codeaurora.bluetooth.map.MapUtils.MapUtils.BadRequestException;
+import org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.VcardContent;
+import android.provider.ContactsContract.PhoneLookup;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+
+import javax.obex.ResponseCodes;
+
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.DELETED;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.DRAFT;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.DRAFTS;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.INBOX;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.OUTBOX;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.QUEUED;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.SENT;
+
+public class BluetoothMasAppSmsMms extends BluetoothMasAppIf {
+ public final String TAG = "BluetoothMasAppSmsMms";
+
+ private final long SMS_OFFSET_START;
+ private final long MMS_OFFSET_START;
+
+ private static final String SMS_GSM = "SMS_GSM";
+ private static final String SMS_CDMA = "SMS_CDMA";
+ private static final String MMS = "MMS";
+ // OMA-TS-MMS-ENC defined many types in X-Mms-Message-Type.
+ // Only m-send-req (128) m-retrieve-conf (132), m-notification-ind (130)
+ // are interested by user
+ private static final String INTERESTED_MESSAGE_TYPE_CLAUSE =
+ "(m_type = 128 OR m_type = 132 OR m_type = 130)";
+
+ public BluetoothMasAppSmsMms(Context context, Handler handler, BluetoothMns mnsClient,
+ int masId, String remoteDeviceName) {
+ super(context, handler, MESSAGE_TYPE_SMS_MMS, mnsClient, masId, remoteDeviceName);
+ SMS_OFFSET_START = OFFSET_START;
+ MMS_OFFSET_START = OFFSET_START + ((OFFSET_END - OFFSET_START) / 2);
+
+ // Clear out deleted items from database
+ cleanUp();
+
+ if (V) Log.v(TAG, "BluetoothMasAppSmsMms Constructor called");
+ }
+
+ /**
+ * Start an MNS obex client session and push notification whenever available
+ */
+ public void startMnsSession(BluetoothDevice remoteDevice) {
+ if (V) Log.v(TAG, "Start MNS Client");
+ mMnsClient.getHandler()
+ .obtainMessage(BluetoothMns.MNS_CONNECT, 0, -1, remoteDevice)
+ .sendToTarget();
+ }
+
+ /**
+ * Stop pushing notifications and disconnect MNS obex session
+ */
+ public void stopMnsSession(BluetoothDevice remoteDevice) {
+ if (V) Log.v(TAG, "Stop MNS Client");
+ mMnsClient.getHandler()
+ .obtainMessage(BluetoothMns.MNS_DISCONNECT, 0, -1,
+ remoteDevice).sendToTarget();
+ }
+
+ @Override
+ protected List<String> getCompleteFolderList() {
+ return SmsMmsUtils.FORLDER_LIST_SMS_MMS;
+ }
+
+ private void cleanUp() {
+ // Remove the deleted item entries
+ mContext.getContentResolver().delete(Uri.parse("content://mms-sms/conversations/"),
+ "thread_id = " + DELETED_THREAD_ID, null);
+ if(V) Log.v(TAG, "Unregister PhoneLookUP observer");
+ mContext.getContentResolver().unregisterContentObserver(mVcardListObserver);
+ }
+
+ public boolean checkPrecondition() {
+ // TODO: Add any precondition check routine for this MAS instance
+ return true;
+ }
+
+ public void onConnect() {
+ // TODO: Add any routine to be run when OBEX connection established
+ if(V) Log.v(TAG, "Register PhoneLookUP observer");
+ mContext.getContentResolver().registerContentObserver(PhoneLookup.CONTENT_FILTER_URI, true,
+ mVcardListObserver);
+ }
+
+ public void onDisconnect() {
+ cleanUp();
+ }
+
+ @Override
+ protected BluetoothMsgListRsp msgListingSpecific(List<MsgListingConsts> msgList, String name,
+ BluetoothMasMessageListingRsp rsp, BluetoothMasAppParams appParams) {
+ BluetoothMsgListRsp bmlr = new BluetoothMsgListRsp();
+ boolean validFilter = false;
+ String fullPath = (name == null || name.length() == 0) ? mCurrentPath :
+ CommonUtils.getFullPath(name, mContext, getCompleteFolderList(), mCurrentPath);
+ if (fullPath == null) {
+ // Child folder not present
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ bmlr.rsp = rsp;
+ return bmlr;
+ }
+
+ if (V) {
+ Log.v(TAG, "appParams.FilterMessageType ::"+ appParams.FilterMessageType);
+ Log.v(TAG, "Condition result::"+ (appParams.FilterMessageType & 0x0B));
+ }
+ String splitStrings[] = fullPath.split("/");
+ if (splitStrings.length == 3) {
+ String folderName = splitStrings[2];
+ if (V) Log.v(TAG, "folderName: " + folderName);
+ if (CommonUtils.validateFilterPeriods(appParams) == 0) {
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ bmlr.rsp = rsp;
+ return bmlr;
+ }
+ if (appParams.FilterReadStatus > 0x02) {
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ bmlr.rsp = rsp;
+ return bmlr;
+ }
+
+ // TODO: Filter priority?
+ /*
+ * There is no support for FilterPriority in SMS/MMS. So, we will
+ * assume Filter Priority is always non-high which makes sense for
+ * SMS/MMS.We check the content provider only if the Filter Priority
+ * is "unfiltered" or "non-high". Else, simply return an empty
+ * string. If the Filter priority is greater than 2, return a bad
+ * request.
+ */
+
+ if (appParams.FilterPriority == 0 || appParams.FilterPriority == 0x02) {
+ final int phoneType = TelephonyManager.getDefault().getPhoneType();
+ if ((appParams.FilterMessageType & 0x03) == 0 ||
+ ((appParams.FilterMessageType & 0x01) == 0 &&
+ phoneType == TelephonyManager.PHONE_TYPE_GSM) ||
+ ((appParams.FilterMessageType & 0x02) == 0 &&
+ phoneType == TelephonyManager.PHONE_TYPE_CDMA)) {
+ validFilter = true;
+ BluetoothMsgListRsp bmlrSms = msgListSms(msgList, folderName,
+ rsp, appParams);
+ bmlr.msgList = bmlrSms.msgList;
+ bmlr.rsp = bmlrSms.rsp;
+ }
+ // Now that all of the SMS messages have been listed. Look for
+ // any
+ // MMS messages and provide them
+ if((appParams.FilterMessageType & 0x08) == 0) {
+ Log.v(TAG, "About to retrieve msgListMms ");
+ // MMS draft folder is called //mms/drafts not //mms/draft like
+ // SMS
+ validFilter = true;
+ if (DRAFT.equalsIgnoreCase(folderName)) {
+ folderName = DRAFTS;
+ }
+ BluetoothMsgListRsp bmlrMms = msgListMms(msgList, folderName, rsp, appParams);
+ bmlr.msgList = bmlrMms.msgList;
+ bmlr.rsp = bmlrMms.rsp;
+ }
+ if (validFilter != true) {
+ if (V) Log.v(TAG, "Invalid message filter, returning empty-list");
+ rsp.rsp = ResponseCodes.OBEX_HTTP_OK;
+ bmlr.rsp = rsp;
+ return bmlr;
+ }
+ } else {
+ if (appParams.FilterPriority > 0x02) {
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ bmlr.rsp = rsp;
+ return bmlr;
+ }
+ }
+ }
+
+ // Now that the message list exists, we can sort the list by date
+ Collections.sort(bmlr.msgList, new SortMsgListByDate());
+ rsp.rsp = ResponseCodes.OBEX_HTTP_OK;
+ bmlr.rsp = rsp;
+ return bmlr;
+ }
+
+ @Override
+ protected BluetoothMasMessageRsp getMessageSpecific(long msgHandle, BluetoothMasMessageRsp rsp,
+ BluetoothMasAppParams bluetoothMasAppParams) {
+ final long handle = Long.valueOf(msgHandle);
+
+ if (handle >= MMS_OFFSET_START) { // MMS
+ /*
+ * Spec 5.6.4 says MSE shall reject request with value native
+ * for MMS and Email
+ */
+ if ((int)bluetoothMasAppParams.Charset == 0) {
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ return rsp;
+ }
+ return getMessageMms(msgHandle, rsp);
+ } else { // SMS
+ return getMessageSms(msgHandle, mContext, rsp, bluetoothMasAppParams);
+ }
+ }
+
+ /**
+ * Push a outgoing message from MAS Client to the network
+ *
+ * @return Response to push command
+ */
+ public BluetoothMasPushMsgRsp pushMsg(String name, File file,
+ BluetoothMasAppParams bluetoothMasAppParams) throws BadRequestException {
+ BluetoothMasPushMsgRsp rsp = new BluetoothMasPushMsgRsp();
+ rsp.response = ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+ rsp.msgHandle = null;
+
+ if(!checkPath(false, name, false) ||
+ mCurrentPath == null ||
+ mCurrentPath.equals("telecom") ||
+ (mCurrentPath.equals("telecom/msg") && (name == null || name.length() == 0))) {
+ rsp.response = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ return rsp;
+ }
+
+ ActivityManager am = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo();
+ am.getMemoryInfo(outInfo);
+ final long allowedMem = outInfo.availMem - outInfo.threshold;
+
+ byte[] readBytes = null;
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(file);
+ if(file.length() > allowedMem){
+ rsp.response = ResponseCodes.OBEX_HTTP_ENTITY_TOO_LARGE;
+ rsp.msgHandle = null;
+ Log.d(TAG,"Message body is larger than the max length allowed");
+ return rsp;
+ } else {
+ readBytes = new byte[(int) file.length()];
+ fis.read(readBytes);
+ }
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, e.getMessage());
+ return rsp;
+ } catch (IOException e) {
+ Log.e(TAG, e.getMessage());
+ return rsp;
+ } catch (SecurityException e) {
+ Log.e(TAG, e.getMessage());
+ return rsp;
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Error while closing stream"+ e.toString());
+ }
+ }
+ }
+
+ String readStr = "";
+ String type = "";
+ try {
+ readStr = new String(readBytes);
+ type = MapUtils.fetchType(readStr);
+ } catch (Exception e) {
+ throw new BadRequestException(e.getMessage());
+ }
+ if (type == null) {
+ rsp.response = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ return rsp;
+ }
+ if (SMS_GSM.equalsIgnoreCase(type) || SMS_CDMA.equalsIgnoreCase(type)) {
+ return pushMessageSms(rsp, readStr, name, bluetoothMasAppParams);
+ } else if (MMS.equals(type) && ((int)bluetoothMasAppParams.Charset != 0)) {
+ // If the message to be pushed is an MMS message, extract any text,
+ // discard
+ // any attachments and convert the message to an SMS
+ if (type.equalsIgnoreCase("MMS")) {
+ /*
+ * The pair of calls below is used to send the MMS message out to
+ * the network.You need to first move the message to the drafts
+ * folder and then move the message from drafts to the outbox
+ * folder. This action causes the message to also be added to the
+ * pending_msgs table in the database. The transaction service will
+ * then send the message out to the network the next time it is
+ * scheduled to run
+ */
+ rsp = pushMessageMms(rsp, readStr, name);
+ return rsp;
+ }
+ }
+ rsp.response = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ return rsp;
+ }
+
+ /**
+ * Sets the message status (read/unread, delete)
+ *
+ * @return Obex response code
+ */
+ public int msgStatus(String msgHandle, BluetoothMasAppParams bluetoothMasAppParams) {
+ if ((bluetoothMasAppParams.StatusIndicator != 0)
+ && (bluetoothMasAppParams.StatusIndicator != 1)) {
+ return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+ }
+ if ((bluetoothMasAppParams.StatusValue != 0)
+ && (bluetoothMasAppParams.StatusValue != 1)) {
+ return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+ }
+ final long handle = Long.valueOf(msgHandle);
+ if (handle < OFFSET_START && handle > OFFSET_END) {
+ return ResponseCodes.OBEX_HTTP_NOT_FOUND;
+ }
+ if (handle >= MMS_OFFSET_START) { // MMS
+ return setMsgStatusMms(handle, bluetoothMasAppParams);
+ } else { // SMS
+ return setMsgStatusSms(handle, bluetoothMasAppParams);
+ }
+ }
+
+ /**
+ * Sets the message update
+ *
+ * @return Obex response code
+ */
+ public int msgUpdate() {
+ if (V) Log.v(TAG, "Message Update");
+ // UpdateInbox for MMS/SMS is not supported
+ return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
+ }
+
+ private boolean isOutgoingSMSMessage(int type) {
+ if (type == 1) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Get the folder name (MAP representation) based on the folder type value
+ * in SMS database
+ */
+ private String getMAPFolder(String type, String threadId) {
+ String folder = null;
+ if (type == null || threadId == null){
+ if (V) Log.v(TAG, "getMapFolder cannot parse folder type");
+ return null;
+ }
+
+ if (Integer.valueOf(threadId) == DELETED_THREAD_ID) {
+ folder = DELETED;
+ } else {
+ switch (Integer.valueOf(type)) {
+ case 1:
+ folder = INBOX;
+ break;
+ case 2:
+ folder = SENT;
+ break;
+ case 3:
+ folder = DRAFT;
+ break;
+ case 4:
+ case 5:
+ case 6:
+ folder = OUTBOX;
+ break;
+ default:
+ break;
+ }
+ }
+ return folder;
+ }
+
+ /**
+ * Get the folder name (MAP representation) based on the message Handle
+ */
+ private String getContainingFolder(long msgHandle) {
+ String folder = null;
+ Cursor cr = mContext.getContentResolver().query(
+ Uri.parse("content://sms/" + msgHandle),
+ new String[] { "_id", "type", "thread_id"}, null, null, null);
+ if (cr != null) {
+ if (cr.getCount() > 0) {
+ cr.moveToFirst();
+ folder = getMAPFolder(cr.getString(cr.getColumnIndex("type")),
+ cr.getString(cr.getColumnIndex("thread_id")));
+ }
+ cr.close();
+ }
+ return folder;
+ }
+
+ /**
+ * Get the SMS Deliver PDU for the given SMS
+ */
+ private String getSMSDeliverPdu(String smsBody, String dateTime, String address) {
+ Time time = new Time();
+ time.set(Long.valueOf(dateTime));
+
+ String timeStr = time.format3339(false);
+
+ // Extract the YY, MM, DD, HH, MM, SS from time
+ String tempTimeStr = timeStr.substring(2,4) + timeStr.substring(5, 7)
+ + timeStr.substring(8, 10) + timeStr.substring(11, 13) +
+ timeStr.substring(14, 16) + timeStr.substring(17, 19);
+
+ /* Calculate the time zone offset
+ * An offset of 1 indicates 15 min difference between local
+ * time and GMT. MSB of 1 in offset indicates it is negative
+ */
+ String tZoneStr = timeStr.substring(timeStr.length()- 6);
+ int tempInt = Integer.valueOf(tZoneStr.substring(tZoneStr.length()-2));
+ int tZone15offset = tempInt / 15;
+
+ tZone15offset += (Integer.valueOf(tZoneStr.substring(tZoneStr.length()-5,
+ tZoneStr.length()-3)) * 4);
+ if (timeStr.charAt(timeStr.length()-6) == '-'){
+ tZone15offset = tZone15offset | 0x80;
+ }
+
+ String tZone15OffsetHexStr = "";
+
+ // Add 0 as prefix for single digit offset
+ if(((int) tZone15offset & 0xff) < 0x10){
+ tZone15OffsetHexStr += "0";
+ }
+ tZone15OffsetHexStr += Integer.toHexString(tZone15offset);
+
+ tempTimeStr += tZone15OffsetHexStr;
+
+ // Swap the nibble
+ String encodedTimeStr = "";
+ for (int i=0; i<tempTimeStr.length(); i=i+2){
+ encodedTimeStr += tempTimeStr.substring(i+1, i+2);
+ encodedTimeStr += tempTimeStr.substring(i, i+1);
+ }
+
+ byte[] byteAddress = address.getBytes();
+
+ // Let the service center number be 0000000000
+ String smsPdu = "0681000000000004";
+
+ // Extract only digits out of the phone address
+ StringBuffer strbufAddress = new StringBuffer(address.length() + 1);
+ for (int i=0; i<address.length(); i++){
+ if (V){
+ Log.v(TAG, " VAL " + address.substring(i, i+1));
+ }
+ if (byteAddress[i] >= 48 && byteAddress[i] <= 57){
+ strbufAddress.append(Integer.parseInt(address.substring(i, i+1)));
+ }
+ }
+
+ int addressLength = strbufAddress.length();
+
+ String addressLengthStr = "";
+
+ if(((int) addressLength & 0xff) < 0x10)
+ addressLengthStr += "0";
+ addressLengthStr += Integer.toHexString(addressLength);
+
+ smsPdu = smsPdu + addressLengthStr;
+ smsPdu = smsPdu + "81";
+
+ String strAddress = new String(strbufAddress);
+
+ // Use getSubmitPdu only to obtain the encoded msg and encoded address
+ byte[] msg = SmsMessage.getSubmitPdu(null, strAddress, smsBody, false).encodedMessage;
+
+ int addLength = Integer.valueOf(msg[2]);
+ if (addLength %2 != 0){
+ addLength++;
+ }
+ addLength = addLength / 2;
+
+ // Extract the message from the SubmitPdu
+ int msgOffset = 7 + addLength;
+ int msgLength = msg.length - msgOffset;
+
+ StringBuffer strbufMessage = new StringBuffer(msgLength * 2);
+
+ // Convert from byte to Hex String
+ for (int i=msgOffset; i<msgLength + msgOffset; i++) {
+ if (((int) msg[i] & 0xff) < 0x10) {
+ strbufMessage.append("0");
+ }
+ strbufMessage.append((Long.toString((int) msg[i] & 0xff, 16)));
+ }
+
+ int encodedAddressLength = strAddress.length() / 2;
+ if (strAddress.length() % 2 != 0) {
+ encodedAddressLength++;
+ }
+
+ StringBuffer strbufAddress1 = new StringBuffer(msgLength * 2);
+
+ // Convert from byte to Hex String
+ for(int i=4; i<encodedAddressLength + 4; i++)
+ {
+ if(((int) msg[i] & 0xff) < 0x10)
+ strbufAddress1.append("0");
+ strbufAddress1.append((Long.toString((int) msg[i] & 0xff, 16)));
+ }
+
+ smsPdu += strbufAddress1;
+ smsPdu += "0000";
+ smsPdu += encodedTimeStr;
+
+ int smsBodyLength = smsBody.length();
+ String smsMessageTextLengthStr = "";
+
+ if(((int) smsBodyLength & 0xff) < 0x10){
+ smsMessageTextLengthStr += "0";
+ }
+ smsMessageTextLengthStr += Integer.toHexString(smsBodyLength);
+
+ smsPdu += smsMessageTextLengthStr;
+ smsPdu += strbufMessage;
+ smsPdu = smsPdu.toUpperCase();
+ return smsPdu;
+ }
+
+ /**
+ * Adds a SMS to the Sms ContentProvider
+ */
+ private String addToSmsFolder(String folder, String address, String text) {
+ ContentValues values = new ContentValues();
+ // thread_id is handled by SmsProvider
+ values.put("body", text);
+ values.put("address", address);
+ values.put("read", 0);
+ values.put("seen", 0);
+ /*
+ * status none -1 complete 0 pending 64 failed 128
+ */
+ values.put("status", -1);
+ /*
+ * outbox 4 queued 6
+ */
+ values.put("locked", 0);
+ values.put("error_code", 0);
+ Uri uri = mContext.getContentResolver().insert(
+ Uri.parse("content://sms/" + folder), values);
+ if (V){
+ Log.v(TAG, " NEW URI " + ((uri == null) ? "null" : uri.toString()));
+ }
+
+ if (uri == null) {
+ return INTERNAL_ERROR;
+ }
+ String str = uri.toString();
+ String[] splitStr = str.split("/");
+ if (splitStr.length < 4) {
+ return INTERNAL_ERROR;
+ }
+ if (V){
+ Log.v(TAG, " NEW HANDLE " + splitStr[3]);
+ }
+ return splitStr[3];
+ }
+
+ private void updateMMSThreadId(long handle, int threadId) {
+ ContentValues values = new ContentValues();
+ values.put("thread_id", threadId);
+ mContext.getContentResolver().update(Uri.parse("content://mms/" + handle),
+ values, null, null);
+ }
+
+ private void deleteMMS(long handle) {
+ Cursor cr = mContext.getContentResolver().query(Uri.parse("content://mms/" + handle),
+ null, null, null, null);
+ if (cr != null && cr.moveToFirst()){
+ int threadId = cr.getInt(cr.getColumnIndex(("thread_id")));
+ if (threadId != DELETED_THREAD_ID){
+ // Move to deleted folder
+ updateMMSThreadId(handle, Integer.valueOf(DELETED_THREAD_ID));
+ } else {
+ // Delete the message permanently
+ long msgId = handle + MMS_OFFSET_START;
+ mMnsClient.addMceInitiatedOperation(Long.toString(msgId));
+ mContext.getContentResolver().delete(Uri.parse("content://mms/" + handle),
+ null, null);
+ }
+ }
+ if (cr != null) {
+ cr.close();
+ }
+ }
+
+ private void unDeleteMMS(long msgHandle) {
+ Cursor cr = mContext.getContentResolver().query(Uri.parse("content://mms/" + msgHandle),
+ null, null, null, null );
+ if (cr == null) {
+ if (V){
+ Log.v(TAG, "unable to query content://mms/" + msgHandle);
+ }
+ return;
+ }
+ if (cr.moveToFirst()){
+ // Make sure that the message is in delete folder
+ String currentThreadId = cr.getString(cr.getColumnIndex("thread_id"));
+ if (currentThreadId != null && Integer.valueOf(currentThreadId) != -1){
+ if (V){
+ Log.v(TAG, " Not in delete folder");
+ }
+ return;
+ }
+
+ // Fetch the address of the deleted message
+ String address = getMmsMsgAddress(msgHandle);
+
+ // Search the database for the given message ID
+ Cursor crThreadId = mContext.getContentResolver().query(Uri.parse("content://mms/"),
+ null,"_id = " + msgHandle + " AND thread_id != -1", null, null);
+ if (crThreadId != null && crThreadId.moveToFirst()) {
+ // A thread for the given message ID exists in the database
+ String threadIdStr = crThreadId.getString(crThreadId.getColumnIndex("thread_id"));
+ if (V){
+ Log.v(TAG, " THREAD ID " + threadIdStr);
+ }
+ updateMMSThreadId(msgHandle, Integer.valueOf(threadIdStr));
+ } else {
+ /* No thread for the given address
+ * Create a fake message to obtain the thread, use that thread_id
+ * and then delete the fake message
+ */
+ ContentValues tempValue = new ContentValues();
+ tempValue.put("address", address);
+ tempValue.put("type", "20");
+ Uri tempUri = mContext.getContentResolver().insert( Uri.parse("content://sms/"),
+ tempValue);
+
+ if (tempUri != null) {
+ Cursor tempCr = mContext.getContentResolver().query(tempUri, null, null, null,
+ null);
+ if (tempCr != null) {
+ tempCr.moveToFirst();
+ String newThreadIdStr = tempCr.getString(
+ tempCr.getColumnIndex("thread_id"));
+ tempCr.close();
+ updateMMSThreadId(msgHandle, Integer.valueOf(newThreadIdStr));
+ }
+ mContext.getContentResolver().delete(tempUri, null, null);
+ } else {
+ Log.e(TAG, "Error in undelete");
+ }
+ }
+ if (crThreadId != null) {
+ crThreadId.close();
+ }
+ } else {
+ if (V) {
+ Log.v(TAG, "msgHandle not found");
+ }
+ }
+ cr.close();
+ }
+
+ private void updateSMSThreadId(long msgHandle, int threadId) {
+ ContentValues values = new ContentValues();
+ values.put("thread_id", threadId);
+ mContext.getContentResolver().update(Uri.parse("content://sms/" + msgHandle),
+ values, null, null);
+ }
+
+ private void deleteSMS(long handle) {
+ Cursor cr = mContext.getContentResolver().query(Uri.parse("content://sms/" + handle),
+ null, null, null, null);
+ if (cr != null && cr.moveToFirst()){
+ int threadId = cr.getInt(cr.getColumnIndex(("thread_id")));
+ if (threadId != DELETED_THREAD_ID){
+ // Move to deleted folder
+ updateSMSThreadId(handle, Integer.valueOf(DELETED_THREAD_ID));
+ } else {
+ // Delete the message permanently
+ long msgHandle = handle + SMS_OFFSET_START;
+ mMnsClient.addMceInitiatedOperation(Long.toString(msgHandle));
+ mContext.getContentResolver().delete(Uri.parse("content://sms/" + handle),
+ null, null);
+ }
+ }
+ if (cr != null) {
+ cr.close();
+ }
+ }
+
+ private void unDeleteSMS(long msgHandle){
+ Cursor cr = mContext.getContentResolver().query(Uri.parse("content://sms/" + msgHandle),
+ null, null, null, null );
+ if (cr == null) {
+ return;
+ }
+
+ if (cr.moveToFirst()){
+ // Make sure that the message is in delete folder
+ String currentThreadId = cr.getString(cr.getColumnIndex("thread_id"));
+ if (currentThreadId != null && Integer.valueOf(currentThreadId) != -1){
+ if (V){
+ Log.v(TAG, " Not in delete folder");
+ }
+ return;
+ }
+
+ // Fetch the address of the deleted message
+ String address = cr.getString(cr.getColumnIndex("address"));
+
+ // Search the database for the given address
+ Cursor crThreadId = mContext.getContentResolver().query(Uri.parse("content://sms/"),
+ null, "address = " + address + " AND thread_id != -1", null, null);
+ if (crThreadId != null && crThreadId.moveToFirst()) {
+ // A thread for the given address exists in the database
+ String threadIdStr = crThreadId.getString(crThreadId.getColumnIndex("thread_id"));
+ if (V){
+ Log.v(TAG, " THREAD ID " + threadIdStr);
+ }
+ updateSMSThreadId(msgHandle, Integer.valueOf(threadIdStr));
+ } else {
+ /* No thread for the given address
+ * Create a fake message to obtain the thread, use that thread_id
+ * and then delete the fake message
+ */
+ ContentValues tempValue = new ContentValues();
+ tempValue.put("address", address);
+ tempValue.put("type", "20");
+ Uri tempUri = mContext.getContentResolver().insert( Uri.parse("content://sms/"),
+ tempValue);
+
+ if (tempUri != null) {
+ Cursor tempCr = mContext.getContentResolver().query(tempUri, null, null, null,
+ null);
+ if (tempCr != null) {
+ tempCr.moveToFirst();
+ String newThreadIdStr = tempCr.getString(
+ tempCr.getColumnIndex("thread_id"));
+ tempCr.close();
+ updateSMSThreadId(msgHandle, Integer.valueOf(newThreadIdStr));
+ }
+
+ mContext.getContentResolver().delete(tempUri, null, null);
+ }
+ }
+ if (crThreadId != null) {
+ crThreadId.close();
+ }
+ } else {
+ if (V) {
+ Log.v(TAG, "msgHandle not found");
+ }
+ }
+ cr.close();
+ }
+
+ /**
+ * Obtain the number of MMS messages
+ */
+ private int getNumMmsMsgs(String name) {
+ int msgCount = 0;
+
+ if ( name.equalsIgnoreCase(DELETED)){
+ Uri uri = Uri.parse("content://mms/");
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(uri, null, "thread_id = " + DELETED_THREAD_ID
+ + " AND " + INTERESTED_MESSAGE_TYPE_CLAUSE, null, null);
+ if(cursor != null){
+ msgCount = cursor.getCount();
+ cursor.close();
+ }
+ } else {
+ Uri uri = Uri.parse("content://mms/" + name);
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(uri, null, "thread_id <> " + DELETED_THREAD_ID
+ + " AND " + INTERESTED_MESSAGE_TYPE_CLAUSE, null, null);
+ if(cursor != null){
+ msgCount = cursor.getCount();
+ cursor.close();
+ }
+ }
+ return msgCount;
+ }
+
+ /**
+ * Obtain the MMS message ID from Handle
+ */
+ private Long getMmsMsgHndToID(long msgHandle) {
+ long msgID = -1;
+ String whereClause = " mid= " + (msgHandle - MMS_OFFSET_START);
+ Uri uri = Uri.parse("content://mms/part");
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(uri, null, whereClause, null, null);
+ if (cursor != null) {
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ int handleInd = cursor.getColumnIndex("mid");
+ msgID = cursor.getLong(handleInd);
+ }
+ cursor.close();
+ }
+ return msgID;
+ }
+
+ /**
+ * Obtain the MMS message MID list
+ */
+ private List<Integer> getMmsMsgMIDs(String whereClause) {
+ List<Integer> idList = new ArrayList<Integer>();
+ Uri uri = Uri.parse("content://mms");
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor crID = cr.query(uri, null, whereClause, null, null);
+ if (crID != null) {
+ int idInd = crID.getColumnIndex("_id");
+ if (crID.getCount() != 0) {
+ crID.moveToFirst();
+ do {
+ idList.add(Integer.valueOf(crID.getInt(idInd)));
+ } while (crID.moveToNext());
+ }
+ crID.close();
+ }
+ return idList;
+ }
+
+ /**
+ * Build a whereclause for MMS filtering
+ */
+ private String bldMmsWhereClause(BluetoothMasAppParams appParams, int foldertype) {
+ String whereClause = "";
+ if ( foldertype != -1) {
+ // Inbox, Outbox, Sent, Draft folders
+ whereClause = "msg_box=" + foldertype + " AND thread_id <> " + DELETED_THREAD_ID
+ + " AND " + INTERESTED_MESSAGE_TYPE_CLAUSE;
+ } else {
+ // Deleted folder
+ whereClause = "thread_id = " + DELETED_THREAD_ID + " AND "
+ + INTERESTED_MESSAGE_TYPE_CLAUSE;
+ }
+
+ /* Filter readstatus: 0 no filtering, 0x01 get unread, 0x10 get read */
+ if (appParams.FilterReadStatus != 0) {
+ if ((appParams.FilterReadStatus & 0x1) != 0) {
+ if (whereClause.length() != 0) {
+ whereClause += " AND ";
+ }
+ whereClause += " read=0 ";
+ }
+ if ((appParams.FilterReadStatus & 0x02) != 0) {
+ if (whereClause.length() != 0) {
+ whereClause += " AND ";
+ }
+ whereClause += " read=1 ";
+ }
+ }
+
+ /* Filter Period Begin */
+ if ((appParams.FilterPeriodBegin != null)
+ && (appParams.FilterPeriodBegin.length() > 0)) {
+ Time time = new Time();
+ try {
+ time.parse(appParams.FilterPeriodBegin.trim());
+ if (whereClause.length() != 0) {
+ whereClause += " AND ";
+ }
+ whereClause += "date >= " + (time.toMillis(false))/1000;
+ } catch (TimeFormatException e) {
+ Log.d(TAG, "Bad formatted FilterPeriodBegin "
+ + appParams.FilterPeriodBegin);
+ }
+ }
+
+ /* Filter Period End */
+ if ((appParams.FilterPeriodEnd != null)
+ && (appParams.FilterPeriodEnd.length() > 0)) {
+ Time time = new Time();
+ try {
+ time.parse(appParams.FilterPeriodEnd.trim());
+ if (whereClause.length() != 0) {
+ whereClause += " AND ";
+ }
+ whereClause += "date < " + (time.toMillis(false))/1000;
+ } catch (TimeFormatException e) {
+ Log.d(TAG, "Bad formatted FilterPeriodEnd "
+ + appParams.FilterPeriodEnd);
+ }
+ }
+ //Delivery report check
+ if (whereClause.length() != 0) {
+ whereClause += " AND ";
+ }
+ whereClause += "d_rpt > 0";
+
+ return whereClause;
+ }
+
+ /**
+ * Obtain the MMS msg_box id
+ */
+ private int getMmsMsgBox(long msgID) {
+ int val = -1;
+ String whereClause = " _id= " + msgID;
+ Uri uri = Uri.parse("content://mms/");
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(uri, null, whereClause, null, null);
+ if (cursor != null) {
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ int msgBoxInd = cursor.getColumnIndex("msg_box");
+ val = cursor.getInt(msgBoxInd);
+ }
+ cursor.close();
+ }
+ return val;
+ }
+
+ /**
+ * Obtain MMS message text
+ */
+ private String getMmsMsgTxt(long msgID) {
+ String text = null;
+ String whereClause = " mid= " + msgID + " AND ct=\"text/plain\"";
+ Uri uri = Uri.parse("content://mms/part");
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(uri, null, whereClause, null, null);
+ if (cursor != null) {
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ int textInd = cursor.getColumnIndex("text");
+ text = cursor.getString(textInd);
+ }
+ cursor.close();
+ }
+ return text;
+ }
+
+ /**
+ * Obtain the MMS message Subject
+ */
+ private String getMmsMsgSubject(long msgID) {
+ String text = null;
+ String whereClause = " _id= " + msgID;
+ Uri uri = Uri.parse("content://mms/");
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(uri, null, whereClause, null, null);
+ if (cursor != null) {
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ int subjectInd = cursor.getColumnIndex("sub");
+ text = cursor.getString(subjectInd);
+ }
+ cursor.close();
+ }
+ return text;
+ }
+
+ /**
+ * Obtain the MMS message Date
+ */
+ private Date getMmsMsgDate(long msgID) {
+ long date = 0;
+ String whereClause = " _id= " + msgID;
+ Uri uri = Uri.parse("content://mms/");
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(uri, null, whereClause, null, null);
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ int dateInd = cursor.getColumnIndex("date");
+ date = 1000 * cursor.getLong(dateInd);
+ }
+ cursor.close();
+ }
+ if (V) Log.v(TAG, "DATE:millisces "+ date);
+ return new Date(date);
+
+ }
+
+ /**
+ * Obtain the MMS attachment size
+ */
+ private int getMmsMsgAttachSize(long msgID) {
+ int attachSize = 0;
+ String whereClause = " _id= " + msgID;
+ Uri uri = Uri.parse("content://mms/");
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(uri, null, whereClause, null, null);
+ if (cursor != null) {
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ int sizeInd = cursor.getColumnIndex("m_size");
+ attachSize = cursor.getInt(sizeInd);
+ }
+ cursor.close();
+ }
+ return attachSize;
+
+ }
+
+ /**
+ * Obtain the MMS message read status
+ */
+ private String getMmsMsgReadStatus(long msgID) {
+ String text = null;
+ String whereClause = " _id= " + msgID;
+ Uri uri = Uri.parse("content://mms/");
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(uri, null, whereClause, null, null);
+ if (cursor != null) {
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ int readInd = cursor.getColumnIndex("read");
+ if (cursor.getInt(readInd) == 0) {
+ text = "no";
+ } else {
+ text = "yes";
+ }
+ }
+ cursor.close();
+ }
+ return text;
+ }
+
+ /**
+ * Obtain the MMS message read sent
+ */
+ private String getMmsMsgReadSent(long msgID) {
+ String text = null;
+ if ( getMmsMsgBox(msgID) == 2 ) {
+ // Sent folder
+ text = "yes";
+ } else {
+ text = "no";
+ }
+ return text;
+ }
+
+ /**
+ * Obtain the MMS message priority
+ */
+ private String getMmsMsgPriority(long msgID) {
+ final int PRIORITY_LOW = 0X80;
+ final int PRIORITY_NORMAL = 0X81;
+ final int PRIORITY_HIGH = 0X82;
+
+ String text = null;
+ String whereClause = " _id= " + msgID;
+ Uri uri = Uri.parse("content://mms/");
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(uri, null, whereClause, null, null);
+ if (cursor != null && cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ int priInd = cursor.getColumnIndex("pri");
+
+ switch (cursor.getInt(priInd)) {
+ case PRIORITY_LOW:
+ text = "no";
+ break;
+ case PRIORITY_NORMAL:
+ text = "no";
+ break;
+ case PRIORITY_HIGH:
+ text = "yes";
+ break;
+
+ default:
+ text = "no";
+ break;
+ }
+ }
+ if (cursor != null) {
+ cursor.close();
+ }
+ return text;
+ }
+
+ /**
+ * Obtain the MMS message read protected
+ */
+ private String getMmsMsgProtected(long msgID) {
+ String text = null;
+ String whereClause = " _id= " + msgID;
+ Uri uri = Uri.parse("content://mms/");
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(uri, null, whereClause, null, null);
+ if (cursor != null) {
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ int readInd = cursor.getColumnIndex("locked");
+ if (cursor.getInt(readInd) == 0) {
+ text = "no";
+ } else {
+ text = "yes";
+ }
+ }
+ cursor.close();
+ }
+ return text;
+
+ }
+
+ /**
+ * Obtain MMS message address
+ * When Multiple addresses are present return the addresses separated by semicolon
+ */
+ private String getMmsMsgAddress(long msgID) {
+ String text = "";
+ String whereClause = " address != \"insert-address-token\"";
+ Uri uri = Uri.parse("content://mms/" + msgID + "/addr");
+ //FOR INCOMING MMS ONLY "FROM" ADDRESS IS REQUIRED
+ if (isOutgoingMMSMessage(msgID) == false)
+ whereClause = " address != \"insert-address-token\" AND type=137";
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(uri, null, whereClause, null, null);
+ if (cursor != null) {
+ if (V) Log.v(TAG, "cursor.getCount(): " + cursor.getCount());
+ if (cursor.getCount() == 1) {
+ cursor.moveToFirst();
+ int addressInd = cursor.getColumnIndex("address");
+ text = cursor.getString(addressInd);
+ }
+ else if (cursor.getCount() > 1) {
+ cursor.moveToFirst();
+ int addressInd = 0;
+ do {
+ addressInd = cursor.getColumnIndex("address");
+ text = text.concat(cursor.getString(addressInd) + ";");
+ if (V) Log.v(TAG, "address: " + cursor.getString(cursor.getColumnIndex("address")));
+ } while (cursor.moveToNext());
+
+ }
+ cursor.close();
+ }
+ if (V) Log.v(TAG, "final MMS address: " + text);
+ return text;
+ }
+
+ /**
+ * Get the folder name (MAP representation) based on the message Handle
+ */
+ private int getMmsContainingFolder(long msgID) {
+ int folderNum = 0;
+ String whereClause = " _id= " + msgID;
+ Uri uri = Uri.parse("content://mms/");
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(uri, null, whereClause, null, null);
+ if (cursor != null) {
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ int msgboxInd = cursor.getColumnIndex("msg_box");
+ String thread_id = cursor.getString(cursor.getColumnIndex("thread_id"));
+ if ( Integer.valueOf(thread_id) == DELETED_THREAD_ID) {
+ // Deleted folder
+ folderNum = 0;
+ } else {
+ folderNum = cursor.getInt(msgboxInd);
+ }
+ }
+ cursor.close();
+ }
+ return folderNum;
+ }
+
+ /**
+ * Get MMS folder name based on value Inbox = 1 Sent = 2 Drafts = 3 Outbox =
+ * 4 Queued = 6
+ *
+ */
+ private String getMmsMapVirtualFolderName(int type) {
+ String folderName = null;
+
+ switch (type) {
+ case 0:
+ folderName = DELETED;
+ break;
+ case 1:
+ folderName = INBOX;
+ break;
+ case 2:
+ folderName = SENT;
+ break;
+ case 3:
+ folderName = DRAFT;
+ break;
+ case 4: // outbox
+ case 5: // failed
+ case 6: // queued
+ folderName = OUTBOX;
+ break;
+
+ default:
+ break;
+ }
+ return folderName;
+ }
+
+ /**
+ * Build an MMS bMessage when given a message handle
+ */
+ private BluetoothMasMessageRsp bldMmsBmsg(long msgID, BluetoothMasMessageRsp rsp) {
+ Cursor cr = null;
+ Uri uri = Uri.parse("content://mms/");
+ String whereClause = " _id = " + msgID;
+ String address = null;
+ String addressTokens[] = null;
+ cr = mContext.getContentResolver().query(uri, null, whereClause, null,
+ null);
+ if (cr == null) {
+ rsp.rsp = ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+ return rsp;
+ }
+ if (cr.getCount() > 0) {
+ cr.moveToFirst();
+ String containingFolder = getMmsMapVirtualFolderName((getMmsContainingFolder(msgID)));
+ BmessageConsts bmsg = new BmessageConsts();
+
+ // Create a bMessage
+ bmsg.setType("MMS");
+
+ bmsg.setBmsg_version("1.0");
+ if (cr.getString(cr.getColumnIndex("read")).equalsIgnoreCase("1")) {
+ bmsg.setStatus("READ");
+ } else {
+ bmsg.setStatus("UNREAD");
+ }
+
+ bmsg.setFolder(TELECOM + "/" + MSG + "/" + containingFolder);
+
+ bmsg.setVcard_version("2.1");
+ address = getMmsMsgAddress(msgID);
+ if ((address != null) && address.contains(";")) {
+ addressTokens = address.split(";");
+ String name = "";
+ String tel = "";
+ VcardContent vcardInternal;
+ for (int i=0; i < addressTokens.length; i++) {
+ vcardInternal = getVcardContent(addressTokens[i]);
+ if ((vcardInternal.name == null) || (vcardInternal.name.length() == 0)) {
+ name = name.concat(" " + ";");
+ if(V) Log.v (TAG, "name not present: ");
+ } else {
+ if(V) Log.v (TAG, "name present");
+ name = name.concat(vcardInternal.name + ";");
+ }
+ tel = tel.concat(vcardInternal.tel + ";");
+ }
+ String type = cr.getString(cr.getColumnIndex("msg_box"));
+ // Inbox is type 1.
+ if (type.equalsIgnoreCase("1")) {
+ bmsg.setOriginatorVcard_name(name);
+ bmsg.setOriginatorVcard_phone_number(tel);
+ bmsg.setRecipientVcard_name(getOwnerName());
+ bmsg.setRecipientVcard_phone_number(getOwnerNumber());
+ } else {
+ bmsg.setRecipientVcard_name(name);
+ bmsg.setRecipientVcard_phone_number(tel);
+ bmsg.setOriginatorVcard_name(getOwnerName());
+ bmsg.setOriginatorVcard_phone_number(getOwnerNumber());
+ }
+ }
+ else {
+ VcardContent vcard = getVcardContent(address);
+ String type = cr.getString(cr.getColumnIndex("msg_box"));
+ // Inbox is type 1.
+ if (type.equalsIgnoreCase("1")) {
+ bmsg.setOriginatorVcard_name(vcard.name);
+ bmsg.setOriginatorVcard_phone_number(vcard.tel);
+ bmsg.setRecipientVcard_name(getOwnerName());
+ bmsg.setRecipientVcard_phone_number(getOwnerNumber());
+ } else {
+ bmsg.setRecipientVcard_name(vcard.name);
+ bmsg.setRecipientVcard_phone_number(vcard.tel);
+ bmsg.setOriginatorVcard_name(getOwnerName());
+ bmsg.setOriginatorVcard_phone_number(getOwnerNumber());
+ }
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append("Date: ").append(getMmsMsgDate(msgID).toString()).append("\r\n");
+
+ boolean MIME = true;
+ boolean msgFormat = MIME;
+ sb.append(bldMMSBody(bmsg, msgFormat, msgID));
+ bmsg.setBody_msg(sb.toString());
+ bmsg.setBody_length(sb.length() + 22);
+ bmsg.setBody_encoding("8BIT");
+ // Send a bMessage
+ String str = MapUtils.toBmessageMMS(bmsg);
+ if (V) Log.v(TAG, str);
+ if (str != null && (str.length() > 0)) {
+ final String FILENAME = "message" + getMasId();
+ FileOutputStream bos = null;
+ File file = new File(mContext.getFilesDir() + "/" + FILENAME);
+ file.delete();
+
+ try {
+ bos = mContext.openFileOutput(FILENAME, Context.MODE_PRIVATE);
+ bos.write(str.getBytes());
+ bos.flush();
+ bos.close();
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Unable to write " + FILENAME, e);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to write " + FILENAME, e);
+ }
+
+ File fileR = new File(mContext.getFilesDir() + "/" + FILENAME);
+ if (fileR.exists() == true) {
+ rsp.file = fileR;
+ rsp.fractionDeliver = 1;
+ }
+ }
+ }
+ cr.close();
+ return rsp;
+ }
+
+ private boolean isOutgoingMMSMessage(long mmsMsgID) {
+ if (getMmsMsgBox(mmsMsgID) == 1) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * This method constructs an MMS message that is added to the message list
+ * which is used to construct a message listing
+ */
+ private MsgListingConsts bldMmsMsgLstItem(long mmsMsgID, BluetoothMasAppParams appParams,
+ String folderName, String datetimeStr) {
+
+ MsgListingConsts ml = new MsgListingConsts();
+ String address = getMmsMsgAddress(mmsMsgID);
+ boolean isMultipleAddress = false;
+ String addressTokens[] = null;
+ if ((address != null) && address.contains(";")) {
+ isMultipleAddress = true;
+ addressTokens = address.split(";");
+ for (int i=0; i < addressTokens.length; i++) {
+ if (V) Log.v ( TAG, "addressTokens[" + i + "] = " + addressTokens[i]);
+ }
+ if (V) Log.v (TAG, "addressTokens.length: " + addressTokens.length);
+ }
+ if (V) Log.v (TAG, "isMultipleAddress: " + isMultipleAddress);
+
+
+ // Set the message handle
+ ml.setMsg_handle(mmsMsgID + MMS_OFFSET_START);
+
+ // Set the message subject
+ if ((appParams.ParameterMask & BIT_SUBJECT) != 0) {
+ ml.setSubject(getMmsMsgSubject(mmsMsgID));
+ ml.sendSubject = true;
+ }
+
+ // Construct datetime value
+ if ((appParams.ParameterMask & BIT_DATETIME) != 0) {
+ ml.setDatetime(datetimeStr);
+ }
+
+ // Construct msg body
+ if ((appParams.ParameterMask & BIT_TEXT) != 0) {
+ if ((getMmsMsgTxt(mmsMsgID) != null)) {
+ ml.setContains_text("yes");
+ } else {
+ ml.setContains_text("no");
+ }
+
+ }
+
+ // Set text size
+ if ((appParams.ParameterMask & BIT_SIZE) != 0) {
+ final String mmsMsgTxt = getMmsMsgTxt(mmsMsgID);
+ ml.setSize(mmsMsgTxt == null ? 0 : mmsMsgTxt.length());
+ }
+
+ // Set message type
+ if ((appParams.ParameterMask & BIT_TYPE) != 0) {
+ ml.setType("MMS");
+ }
+
+ if ((appParams.ParameterMask & BIT_RECIPIENT_NAME) != 0) {
+ String recipientName = "";
+ if (isOutgoingMMSMessage(mmsMsgID) == false) {
+ recipientName = getOwnerName();
+ } else {
+ if(!isMultipleAddress) {
+ recipientName = getContactName(getMmsMsgAddress(mmsMsgID));
+ } else {
+ for (int i=0; i < addressTokens.length; i++) {
+ recipientName = recipientName.concat(getContactName(addressTokens[i]) + ";");
+ if (V) Log.v ( TAG, "recipientName: " + recipientName);
+ }
+ }
+ }
+ ml.setRecepient_name(recipientName);
+ }
+
+ if ((appParams.ParameterMask & BIT_RECIPIENT_ADDRESSING) != 0) {
+ // TODO: In case of a SMS this is the recipient's phone number
+ // in canonical form (chapter 2.4.1 of [5])
+ String recipientAddressing = null;
+ if (isOutgoingMMSMessage(mmsMsgID) == false) {
+ recipientAddressing = getOwnerNumber();
+ } else {
+ recipientAddressing = getMmsMsgAddress(mmsMsgID);
+ }
+ ml.setRecepient_addressing(recipientAddressing);
+ ml.setSendRecipient_addressing(true);
+ }
+
+ if ((appParams.ParameterMask & BIT_SENDER_NAME) != 0) {
+ String senderName = "";
+ if (isOutgoingMMSMessage(mmsMsgID) == true) {
+ senderName = getOwnerName();
+ } else {
+ if(!isMultipleAddress) {
+ senderName = getContactName(getMmsMsgAddress(mmsMsgID));
+ } else {
+ for (int i=0; i < addressTokens.length; i++) {
+ senderName = senderName.concat(getContactName(addressTokens[i]) + ";");
+ if (V) Log.v ( TAG, "senderName: " + senderName);
+ }
+ }
+ }
+ ml.setSender_name(senderName);
+ }
+
+ if ((appParams.ParameterMask & BIT_SENDER_ADDRESSING) != 0) {
+ String senderAddressing = null;
+ if (isOutgoingMMSMessage(mmsMsgID) == true) {
+ senderAddressing = getOwnerNumber();
+ } else {
+ senderAddressing = getMmsMsgAddress(mmsMsgID);
+ }
+ ml.setSender_addressing(senderAddressing);
+ }
+
+ // Set read status
+ if ((appParams.ParameterMask & BIT_READ) != 0) {
+ final String mmsMsgStatus = getMmsMsgReadStatus(mmsMsgID);
+ if (mmsMsgStatus != null && mmsMsgStatus.equalsIgnoreCase("yes")) {
+ ml.setRead("yes");
+ } else {
+ ml.setRead("no");
+ }
+ }
+
+ // Set priority
+ if ((appParams.ParameterMask & BIT_PRIORITY) != 0) {
+ ml.setPriority(getMmsMsgPriority(mmsMsgID));
+ }
+
+ // Set Protected
+ if ((appParams.ParameterMask & BIT_PROTECTED) != 0) {
+ ml.setMsg_protected(getMmsMsgProtected(mmsMsgID));
+ }
+
+ // Set sent
+ if ((appParams.ParameterMask & BIT_SENT) != 0) {
+ ml.setSent(getMmsMsgReadSent(mmsMsgID));
+ }
+
+ // Set reception status
+ if ((appParams.ParameterMask & BIT_RECEPTION_STATUS) != 0) {
+ ml.setReception_status("complete");
+ }
+
+ // Set attachment size
+ if ((appParams.ParameterMask & BIT_ATTACHMENT_SIZE) != 0) {
+ ml.setAttachment_size(getMmsMsgAttachSize(mmsMsgID));
+ }
+
+ return ml;
+ }
+
+ /**
+ * This method is used to take an MMS in the drafts folder and move it to
+ * the outbox This action is required to add the MMS to the pending_msgs
+ * table which is used to send the MMS out to the network
+ */
+ private void moveMMSfromDraftstoOutbox() {
+
+ String handle = null;
+
+ // scan drafts folder for an MMS to send
+ // fetch the message handle
+ Uri uri = Uri.parse("content://mms/drafts");
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor crID = cr.query(uri, null, null, null, null);
+ if (crID != null) {
+ if (crID.getCount() > 0) {
+ crID.moveToFirst();
+ int msgIDInd = crID.getColumnIndex("_id");
+ handle = crID.getString(msgIDInd);
+ }
+ crID.close();
+ }
+
+ if (handle != null) {
+ String whereClause = " _id= " + handle;
+ uri = Uri.parse("content://mms");
+ crID = cr.query(uri, null, whereClause, null, null);
+ if (crID != null) {
+ if (crID.getCount() > 0) {
+ crID.moveToFirst();
+ ContentValues values = new ContentValues();
+ values.put("msg_box", 4);
+ cr.update(uri, values, whereClause, null);
+ }
+ crID.close();
+ }
+ }
+ }
+
+ /**
+ * This method is used to take a Bmessage that was pushed and move it to the
+ * folder
+ */
+ private String addToMmsFolder(String folderName, String mmsMsg) throws BadRequestException {
+ if (folderName == null) {
+ return null;
+ }
+ if (folderName.equalsIgnoreCase(DRAFT)) {
+ folderName = DRAFTS;
+ }
+ int folderType = SmsMmsUtils.getFolderTypeMms(folderName);
+ BmessageConsts bMsg = MapUtils.fromBmessageMMS(mmsMsg);
+ String address = bMsg.getRecipientVcard_phone_number();
+ String mmsText = bMsg.getBody_msg();
+
+ /**
+ * The PTS tester does not contain the same message format as CE4A This
+ * code /* looks at the pushed message and checks for the message boundary. If
+ * it does not /* find it then it then it assumes PTS tester format
+ */
+ mmsText = MapUtils.fetchBodyEmail(mmsText);
+ ContentValues values = new ContentValues();
+ values.put("msg_box", folderType);
+
+ if (folderName.equalsIgnoreCase(DELETED)) {
+ values.put("thread_id", -1);
+ } else {
+ values.put("thread_id", createMMSThread(address));
+ }
+
+ // function that creates a thread ID
+ 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", "personal");
+ values.put("m_type", 128);
+ values.put("v", 18);
+ values.put("pri", 129);
+ values.put("rr", 129);
+ values.put("tr_id", "T12dc2e87182");
+ values.put("d_rpt", 129);
+ values.put("locked", 0);
+
+ Uri uri;
+ if (folderName.equalsIgnoreCase(DELETED)) {
+ uri = Uri.parse("content://mms/inbox");
+ } else {
+ uri = Uri.parse("content://mms/" + folderName);
+ }
+ ContentResolver cr = mContext.getContentResolver();
+ uri = cr.insert(uri, values);
+
+ if (uri == null) {
+ // unable to insert MMS
+ Log.e(TAG, "Unabled to insert MMS " + values);
+ return INTERNAL_ERROR;
+ }
+ String msgNum = uri.getLastPathSegment();
+ long msgID = Long.parseLong(msgNum);
+ if (V){
+ Log.v(TAG, " NEW URI " + uri.toString());
+ }
+ long virtualMsgId = (msgID + MMS_OFFSET_START);
+
+ // Build the \mms\part portion
+ values.clear();
+
+ values.put("seq", -1);
+ values.put("ct", "application/smil");
+ values.put("cid", "<smil>");
+ values.put("cl", "smil.xml");
+ values.put("text", "<smil><head><layout><root-layout width=\"320px\" height=\"480px\"/>" +
+ "<region id=\"Text\" left=\"0\" top=\"320\" width=\"320px\" height=\"160px\"" +
+ " fit=\"meet\"/></layout></head><body><par dur=\"5000ms\">" +
+ "<text src=\"text_0.txt\" region=\"Text\"/></par></body></smil>");
+
+ uri = Uri.parse("content://mms/" + msgID + "/part");
+ uri = cr.insert(uri, values);
+ if (uri != null && V){
+ Log.v(TAG, " NEW URI " + uri.toString());
+ }
+
+ values.clear();
+ values.put("seq", 0);
+ values.put("ct", "text/plain");
+ values.put("name", "null");
+ values.put("chset", 106);
+ values.put("cd", "null");
+ values.put("fn", "null");
+ values.put("cid", "<smil>");
+ values.put("cl", "text_0.txt");
+ values.put("ctt_s", "null");
+ values.put("ctt_t", "null");
+ values.put("_data", "null");
+ values.put("text", mmsText);
+
+ uri = Uri.parse("content://mms/" + msgID + "/part");
+ 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", "insert-address-token");
+ values.put("type", 137);
+ values.put("charset", 106);
+
+ uri = Uri.parse("content://mms/" + msgID + "/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", address);
+ values.put("type", 151);
+ values.put("charset", 106);
+
+ uri = Uri.parse("content://mms/" + msgID + "/addr");
+ uri = cr.insert(uri, values);
+ if (uri != null && V){
+ Log.v(TAG, " NEW URI " + uri.toString());
+ }
+
+ String virtualMsgIdStr = String.valueOf(virtualMsgId);
+ String whereClause = "address LIKE '" + address + "' AND type = 125";
+ if (!folderName.equalsIgnoreCase("deleted")) {
+ mContext.getContentResolver().delete(Uri.parse("content://sms/"), whereClause, null);
+ }
+ return virtualMsgIdStr;
+ }
+
+ /**
+ * Method to construct body of bmessage using either MIME or no MIME
+ *
+ */
+ private String bldMMSBody(BmessageConsts bMsg, boolean msgType, long msgID) {
+ boolean MIME = true;
+ StringBuilder sb = new StringBuilder();
+
+ if (msgType == MIME) {
+ Random randomGenerator = new Random();
+ int randomInt = randomGenerator.nextInt(1000);
+ String boundary = "MessageBoundary."+randomInt;
+ final String mmsMsgTxt = getMmsMsgTxt(msgID);
+ if(mmsMsgTxt != null){
+ while(mmsMsgTxt.contains(boundary)){
+ randomInt = randomGenerator.nextInt(1000);
+ boundary = "MessageBoundary."+randomInt;
+ }
+ }
+ sb.append("To:").append(bMsg.recipient_vcard_phone_number)
+ .append("\r\n");
+ sb.append("Mime-Version: 1.0").append("\r\n");
+ sb.append(
+ "Content-Type: multipart/mixed; boundary=\""+boundary+"\"")
+ .append("\r\n");
+ sb.append("Content-Transfer-Encoding: 7bit").append("\r\n")
+ .append("\r\n");
+ sb.append("MIME Message").append("\r\n");
+ sb.append("--"+boundary).append("\r\n");
+ sb.append("Content-Type: text/plain").append("\r\n");
+ sb.append("Content-Transfer-Encoding: 8bit").append("\r\n");
+ sb.append("Content-Disposition:inline").append("\r\n")
+ .append("\r\n");
+ sb.append(getMmsMsgTxt(msgID)).append("\r\n");
+ sb.append("--"+boundary+"--").append("\r\n")
+ .append("\r\n");
+ } else {
+ sb.append("Subject:").append("Not Implemented").append("\r\n");
+ sb.append("From:").append(bMsg.originator_vcard_phone_number)
+ .append("\r\n");
+ sb.append(getMmsMsgTxt(msgID)).append("\r\n").append("\r\n");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Method to create a thread for a pushed MMS message
+ *
+ */
+ private int createMMSThread(String address) {
+ int returnValue = 0;
+ if (address != null) {
+ if (V){
+ Log.v(TAG, "Inside adress not null");
+ }
+ ContentValues tempValue = new ContentValues();
+ tempValue.put("address", address);
+ tempValue.put("type", 125);
+ Uri tempUri = mContext.getContentResolver().insert(
+ Uri.parse("content://sms/"), tempValue);
+
+ if (tempUri != null) {
+ Cursor tempCr = mContext.getContentResolver().query(tempUri, null,
+ null, null, null);
+ if (tempCr != null) {
+ tempCr.moveToFirst();
+ String newThreadIdStr = tempCr.getString(tempCr
+ .getColumnIndex("thread_id"));
+ tempCr.close();
+ returnValue = Integer.valueOf(newThreadIdStr);
+ }
+ if (V){
+ Log.v(TAG, "Thread ID::"+returnValue);
+ }
+ }
+ }
+
+ return returnValue;
+ }
+
+ private String bldSmsBmsg(long msgHandle, Context context, Cursor cr,
+ BluetoothMasAppParams bluetoothMasAppParams) {
+ String str = null;
+ int body_length = 0;
+ if (cr.getCount() > 0) {
+ cr.moveToFirst();
+ String containingFolder = getContainingFolder(msgHandle);
+ BmessageConsts bmsg = new BmessageConsts();
+
+ // Create a bMessage
+
+ if (TelephonyManager.getDefault().getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+ bmsg.setType("SMS_CDMA");
+ } else {
+ bmsg.setType("SMS_GSM");
+ }
+
+ bmsg.setBmsg_version("1.0");
+ if (cr.getString(cr.getColumnIndex("read")).equalsIgnoreCase("1")) {
+ bmsg.setStatus("READ");
+ } else {
+ bmsg.setStatus("UNREAD");
+ }
+
+ bmsg.setFolder(TELECOM + "/" + MSG + "/" + containingFolder);
+
+ bmsg.setVcard_version("2.1");
+ VcardContent vcard = getVcardContent(cr.getString(cr
+ .getColumnIndex("address")));
+
+ String type = cr.getString(cr.getColumnIndex("type"));
+ if (type.equalsIgnoreCase("1")) {
+ // The address in database is of originator
+ bmsg.setOriginatorVcard_name(vcard.name);
+ bmsg.setOriginatorVcard_phone_number(vcard.tel);
+ bmsg.setRecipientVcard_name(getOwnerName());
+ bmsg.setRecipientVcard_phone_number(getOwnerNumber());
+ } else {
+ bmsg.setRecipientVcard_name(vcard.name);
+ bmsg.setRecipientVcard_phone_number(vcard.tel);
+ bmsg.setOriginatorVcard_name(getOwnerName());
+ bmsg.setOriginatorVcard_phone_number(getOwnerNumber());
+ }
+
+ String smsBody = " ";
+
+ if ( (int)bluetoothMasAppParams.Charset == 1){
+ bmsg.setBody_charset("UTF-8");
+ smsBody = cr.getString(cr.getColumnIndex("body"));
+ }
+
+ if ( (int)bluetoothMasAppParams.Charset == 0){
+ bmsg.setBody_encoding("G-7BIT");
+ String smsBodyUnicode = cr.getString(cr.getColumnIndex("body"));
+ smsBody = getSMSDeliverPdu(smsBodyUnicode, cr.getString(cr.getColumnIndex("date")),
+ vcard.tel);
+ }
+ if (V) {
+ Log.v(TAG, "Unicode String Length :: " + smsBody.length());
+ }
+ try {
+ switch ((int)bluetoothMasAppParams.Charset) {
+ case 1:
+ byte[] b = smsBody.getBytes("UTF-8");
+ body_length = b.length;
+ break;
+ case 0:
+ default:
+ body_length = smsBody.length();
+ break;
+ }
+ }catch (Exception e) {
+ if (V) {
+ Log.v(TAG, "Exception::Getting String UTF-8 Length:: " + smsBody.length());
+ }
+ body_length = smsBody.length();
+ }
+ if (V) {
+ Log.v(TAG, "BT SMS Total length :: 22 + " + body_length);
+ }
+ bmsg.setBody_length(22 + body_length);
+ bmsg.setBody_msg(smsBody);
+ cr.close();
+ // Send a bMessage
+ if (V){
+ Log.v(TAG, "bMessageSMS test\n");
+ Log.v(TAG, "=======================\n\n");
+ }
+ str = MapUtils.toBmessageSMS(bmsg);
+ }
+ return str;
+ }
+
+ private MsgListingConsts bldSmsMsgLstItem(BluetoothMasAppParams appParams,
+ String subject, String timestamp, String address, String msgId,
+ String readStatus, int msgType) {
+ MsgListingConsts ml = new MsgListingConsts();
+ ml.setMsg_handle(Integer.valueOf(msgId));
+
+ Time time = new Time();
+ time.set(Long.valueOf(timestamp));
+
+ String datetimeStr = time.toString().substring(0, 15);
+
+ ml.msgInfo.setDateTime(datetimeStr);
+
+ if ((appParams.ParameterMask & BIT_SUBJECT) != 0) {
+ /* SMS doesn't have subject. Append Body
+ * so that remote client doesn't have to do
+ * GetMessage
+ */
+ ml.setSendSubject(true);
+ if (subject == null) {
+ subject = "";
+ } else if (subject != null && subject.length() > appParams.SubjectLength ) {
+ subject = subject.substring(0,
+ appParams.SubjectLength);
+ }
+ ml.setSubject(subject);
+ }
+
+ if ((appParams.ParameterMask & BIT_DATETIME) != 0) {
+ ml.setDatetime(datetimeStr);
+ }
+
+ if ((appParams.ParameterMask & BIT_SENDER_NAME) != 0) {
+ // TODO: Query the Contacts database
+ String senderName = null;
+ if (isOutgoingSMSMessage(msgType) == true) {
+ senderName = getOwnerName();
+ } else {
+ senderName = getContactName(address);
+ }
+ ml.setSender_name(senderName);
+ }
+
+ if ((appParams.ParameterMask & BIT_SENDER_ADDRESSING) != 0) {
+ // TODO: In case of a SMS this is the sender's phone number in canonical form
+ // (chapter 2.4.1 of [5]).
+ String senderAddressing = null;
+ if (isOutgoingSMSMessage(msgType) == true) {
+ senderAddressing = getOwnerNumber();
+ } else {
+ senderAddressing = address;
+ }
+ ml.setSender_addressing(senderAddressing);
+ }
+
+ if ((appParams.ParameterMask & BIT_RECIPIENT_NAME) != 0) {
+ // TODO: "recipient_name" is the name of the recipient of
+ // the message, when it is known by the MSE device.
+ String recipientName = null;
+ if (isOutgoingSMSMessage(msgType) == false) {
+ recipientName = getOwnerName();
+ } else {
+ recipientName = getContactName(address);
+ }
+ ml.setRecepient_name(recipientName);
+ }
+
+ if ((appParams.ParameterMask & BIT_RECIPIENT_ADDRESSING) != 0) {
+ // TODO: In case of a SMS this is the recipient's phone
+ // number in canonical form (chapter 2.4.1 of [5])
+ String recipientAddressing = null;
+ if (isOutgoingSMSMessage(msgType) == false) {
+ recipientAddressing = getOwnerNumber();
+ } else {
+ recipientAddressing = address;
+ }
+ ml.setRecepient_addressing(recipientAddressing);
+ ml.setSendRecipient_addressing(true);
+ }
+
+ if ((appParams.ParameterMask & BIT_TYPE) != 0) {
+ final int phoneType = TelephonyManager.getDefault().getPhoneType();
+ if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
+ ml.setType("SMS_CDMA");
+ } else {
+ ml.setType("SMS_GSM");
+ }
+ }
+
+ if ((appParams.ParameterMask & BIT_SIZE) != 0) {
+ ml.setSize(subject.length());
+ }
+
+ if ((appParams.ParameterMask & BIT_RECEPTION_STATUS) != 0) {
+ ml.setReception_status("complete");
+ }
+
+ if ((appParams.ParameterMask & BIT_TEXT) != 0) {
+ // TODO: Set text to "yes"
+ ml.setContains_text("yes");
+ }
+
+ if ((appParams.ParameterMask & BIT_ATTACHMENT_SIZE) != 0) {
+ ml.setAttachment_size(0);
+ }
+
+ if ((appParams.ParameterMask & BIT_PRIORITY) != 0) {
+ // TODO: Get correct priority
+ ml.setPriority("no");
+ }
+
+ if ((appParams.ParameterMask & BIT_READ) != 0) {
+ if (readStatus.equalsIgnoreCase("1")) {
+ ml.setRead("yes");
+ } else {
+ ml.setRead("no");
+ }
+ }
+
+ if ((appParams.ParameterMask & BIT_SENT) != 0) {
+ // TODO: Get sent status?
+ if (msgType == 2) {
+ ml.setSent("yes");
+ } else {
+ ml.setSent("no");
+ }
+ }
+
+ if ((appParams.ParameterMask & BIT_PROTECTED) != 0) {
+ ml.setMsg_protected("no");
+ }
+
+ return ml;
+ }
+ private BluetoothMsgListRsp msgListSms(List<MsgListingConsts> msgList, String folder,
+ BluetoothMasMessageListingRsp rsp, BluetoothMasAppParams appParams) {
+ BluetoothMsgListRsp bmlr = new BluetoothMsgListRsp();
+ String url = "content://sms/";
+ Uri uri = Uri.parse(url);
+ ContentResolver cr = mContext.getContentResolver();
+ String whereClause = SmsMmsUtils.getConditionStringSms(folder, appParams);
+
+ Cursor cursor = cr.query(uri, null, whereClause, null,
+ "date desc");
+
+ if (cursor != null && V) {
+ Log.v(TAG, "move to First" + cursor.moveToFirst());
+ }
+ if (cursor != null && V) {
+ Log.v(TAG, "move to Liststartoffset"
+ + cursor.moveToPosition(appParams.ListStartOffset));
+ }
+ if (cursor != null && cursor.moveToFirst()) {
+ int idInd = cursor.getColumnIndex("_id");
+ int addressInd = cursor.getColumnIndex("address");
+ int personInd = cursor.getColumnIndex("person");
+ int dateInd = cursor.getColumnIndex("date");
+ int readInd = cursor.getColumnIndex("read");
+ int statusInd = cursor.getColumnIndex("status");
+ int subjectInd = cursor.getColumnIndex("subject");
+ int typeInd = cursor.getColumnIndex("type");
+ int bodyInd = cursor.getColumnIndex("body");
+
+ do {
+ /*
+ * Apply remaining filters
+ */
+
+ /*
+ * For incoming message, originator is the remote
+ * contact For outgoing message, originator is the
+ * owner.
+ */
+ String filterString = null;
+ String oname = getOwnerName();
+ if (oname == null) {
+ oname = "";
+ }
+ String onumber = getOwnerNumber();
+ if (onumber == null) {
+ onumber = "";
+ }
+
+ int msgType = cursor.getInt(typeInd);
+ String regExpOrig = null;
+ String regExpRecipient = null;
+ if (appParams.FilterOriginator != null) {
+ regExpOrig = appParams.FilterOriginator.replace("*", ".*[0-9A-Za-z].*");
+ }
+ if (appParams.FilterRecipient != null) {
+ regExpRecipient = appParams.FilterRecipient.replace("*", ".*[0-9A-Za-z].*");
+ }
+ if (isOutgoingSMSMessage(msgType) == true) {
+ if ((appParams.FilterOriginator != null)
+ && (appParams.FilterOriginator.length() != 0)
+ && !(oname.matches(".*"+regExpOrig+".*"))
+ && !(onumber.matches(".*"+regExpOrig+".*"))) {
+ continue;
+ }
+ if ((appParams.FilterRecipient != null)
+ && (appParams.FilterRecipient.length() != 0)) {
+ filterString = appParams.FilterRecipient.trim();
+ if (V){
+ Log.v(TAG, "appParams.FilterRecipient"
+ + appParams.FilterRecipient);
+ }
+ }
+ }
+ if (isOutgoingSMSMessage(msgType) == false) {
+ if ((appParams.FilterRecipient != null)
+ && (appParams.FilterRecipient.length() != 0)
+ && !(oname.matches(".*"+regExpRecipient+".*"))
+ && !(onumber.matches(".*"+regExpRecipient+".*"))) {
+ continue;
+ }
+ if ((appParams.FilterOriginator != null)
+ && (appParams.FilterOriginator.length() != 0)) {
+ filterString = appParams.FilterOriginator.trim();
+ if (V){
+ Log.v(TAG, "appParams.FilterOriginator"
+ + appParams.FilterOriginator);
+ }
+ }
+ }
+
+ if (filterString != null) {
+ if (V){
+ Log.v(TAG, "filterString = " + filterString);
+ }
+ if (allowEntry(cursor.getString(addressInd),
+ filterString) == true) {
+ if (V){
+ Log.v(TAG,
+ " ALLOWED : "
+ + cursor.getString(addressInd)
+ + " - " + cursor.getPosition());
+ }
+ } else {
+ if (V){
+ Log.v(TAG,
+ " DENIED : "
+ + cursor.getString(addressInd)
+ + " - " + cursor.getPosition());
+ }
+ continue;
+ }
+ }
+ if (V) Log.v(TAG, " msgListSize " + rsp.msgListingSize);
+ rsp.msgListingSize++;
+
+ /*
+ * Don't want the listing; just send the listing size
+ * after applying all the filters.
+ */
+ if (appParams.MaxListCount == 0) {
+ continue;
+ }
+ String msgIdSms = cursor.getString(idInd);
+ String subjectSms = cursor.getString(bodyInd);
+ String timestampSms = cursor.getString(dateInd);
+ String addressSms = cursor.getString(addressInd);
+ String readStatusSms = cursor.getString(readInd);
+
+ MsgListingConsts ml = bldSmsMsgLstItem(appParams, subjectSms,
+ timestampSms, addressSms, msgIdSms,
+ readStatusSms, msgType);
+
+ // New Message?
+ if ((rsp.newMessage == 0)
+ && (cursor.getInt(readInd) == 0)) {
+ rsp.newMessage = 1;
+ }
+
+ msgList.add(ml);
+ } while (cursor.moveToNext());
+ }
+ if (cursor != null) {
+ cursor.close();
+ }
+ rsp.rsp = ResponseCodes.OBEX_HTTP_OK;
+ bmlr.messageListingSize = rsp.msgListingSize;
+ bmlr.rsp = rsp;
+ bmlr.msgList = msgList;
+ return bmlr;
+ }
+
+ private BluetoothMsgListRsp msgListMms(List<MsgListingConsts> msgList, String name,
+ BluetoothMasMessageListingRsp rsp, BluetoothMasAppParams appParams) {
+ BluetoothMsgListRsp bmlr = new BluetoothMsgListRsp();
+ String filterString = null;
+
+ String oname = getOwnerName();
+ if (oname == null) {
+ oname = "";
+ }
+
+ String onumber = getOwnerNumber();
+ if (onumber == null) {
+ onumber = "";
+ }
+
+ Log.v(TAG, "oname = " + oname + "onumber = " + onumber);
+
+ String regExpOrig = null;
+ String regExpRecipient = null;
+
+ if (appParams.FilterOriginator != null) {
+ regExpOrig = appParams.FilterOriginator.replace("*", ".*[0-9A-Za-z].*");
+ }
+
+ if (appParams.FilterRecipient != null) {
+ regExpRecipient = appParams.FilterRecipient.replace("*", ".*[0-9A-Za-z].*");
+ }
+
+ Log.v(TAG, " regExpOrig = " + regExpOrig + " regExpRecipient = " + regExpRecipient);
+
+ if (getNumMmsMsgs(name) != 0) {
+ List<Integer> list = getMmsMsgMIDs(bldMmsWhereClause(
+ appParams, SmsMmsUtils.getFolderTypeMms(name)));
+ for (int msgId : list) {
+ if (V){
+ Log.v(TAG, "\n MMS Text message ==> "
+ + getMmsMsgTxt(msgId));
+ }
+ if (V){
+ Log.v(TAG, "\n MMS message subject ==> "
+ + getMmsMsgSubject(msgId));
+ }
+ if (isOutgoingMMSMessage(msgId) == false) {
+ if ((appParams.FilterRecipient != null)
+ && (appParams.FilterRecipient.length() != 0)
+ && !(oname.matches(".*"+regExpRecipient+".*"))
+ && !(onumber.matches(".*"+regExpRecipient+".*"))) {
+ continue;
+ }
+ if ((appParams.FilterOriginator != null)
+ && (appParams.FilterOriginator.length() != 0)) {
+ filterString = appParams.FilterOriginator.trim();
+ if (V){
+ Log.v(TAG, " appParams.FilterOriginator"
+ + appParams.FilterOriginator);
+ }
+ }
+ }
+
+ if (isOutgoingMMSMessage(msgId) == true) {
+ if ((appParams.FilterOriginator != null)
+ && (appParams.FilterOriginator.length() != 0)
+ && !(oname.matches(".*"+regExpOrig+".*"))
+ && !(onumber.matches(".*"+regExpOrig+".*"))) {
+ continue;
+ }
+
+ if ((appParams.FilterRecipient != null)
+ && (appParams.FilterRecipient.length() != 0)) {
+ filterString = appParams.FilterRecipient.trim();
+ if (V){
+ Log.v(TAG, " appParams.FilterRecipient"
+ + appParams.FilterRecipient);
+ }
+ }
+ }
+
+ if (filterString != null) {
+ if (V){
+ Log.v(TAG, " filterString = " + filterString);
+ }
+ String ContactName = null;
+ String ContactNum = null;
+
+ ContactName = getContactName(getMmsMsgAddress(msgId));
+ ContactNum = getMmsMsgAddress(msgId);
+
+ if (ContactName.matches(filterString) || ContactNum.matches(filterString)) {
+ if (V){
+ Log.v(TAG, " ALLOWED : "
+ + ContactName + " - " + ContactNum );
+ }
+ } else {
+ if (V){
+ Log.v(TAG, " DENIED : "
+ + ContactName + " - " + ContactNum );
+ }
+ continue;
+ }
+ }
+
+ Time time = new Time();
+ time.set(getMmsMsgDate(msgId).getTime());
+
+ String datetimeStr = time.toString().substring(0, 15);
+
+ MsgListingConsts mmsl = bldMmsMsgLstItem(msgId, appParams, name, datetimeStr);
+ mmsl.msgInfo.setDateTime(datetimeStr);
+
+ if ((rsp.newMessage == 0)
+ && "no".equalsIgnoreCase(getMmsMsgReadStatus(msgId))) {
+ rsp.newMessage = 1;
+ }
+
+ msgList.add(mmsl);
+ rsp.msgListingSize++;
+ }
+ }
+ rsp.rsp = ResponseCodes.OBEX_HTTP_OK;
+ bmlr.messageListingSize = rsp.msgListingSize;
+ bmlr.rsp = rsp;
+ bmlr.msgList = msgList;
+ return bmlr;
+ }
+
+ private BluetoothMasMessageRsp getMessageSms(long msgHandle, Context context,
+ BluetoothMasMessageRsp rsp, BluetoothMasAppParams bluetoothMasAppParams) {
+ long smsHandle = msgHandle - SMS_OFFSET_START;
+ Cursor cr = null;
+ Uri uri = Uri.parse("content://sms/");
+ String whereClause = " _id = " + smsHandle;
+ try {
+ cr = context.getContentResolver().query(uri, null, whereClause, null,
+ null);
+ } catch (Exception e){
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ return rsp;
+ }
+ if (cr == null) {
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ return rsp;
+ }
+ String strSms = bldSmsBmsg(smsHandle, context, cr, bluetoothMasAppParams);
+ cr.close();
+ if (V) Log.v(TAG, strSms);
+
+ if (strSms != null && (strSms.length() > 0)) {
+ final String FILENAME = "message" + getMasId();
+ FileOutputStream bos = null;
+ File file = new File(context.getFilesDir() + "/" + FILENAME);
+ file.delete();
+
+ try {
+ bos = context.openFileOutput(FILENAME, Context.MODE_PRIVATE);
+ bos.write(strSms.getBytes());
+ bos.flush();
+ bos.close();
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Unable to write " + FILENAME, e);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to write " + FILENAME, e);
+ }
+
+ File fileR = new File(context.getFilesDir() + "/" + FILENAME);
+ if (fileR.exists() == true) {
+ rsp.file = fileR;
+ rsp.fractionDeliver = 1;
+ }
+ }
+ return rsp;
+ }
+
+ private BluetoothMasMessageRsp getMessageMms(long msgHandle, BluetoothMasMessageRsp rsp) {
+ long mmsMsgID = 0;
+ try {
+ mmsMsgID = getMmsMsgHndToID(msgHandle);
+ } catch (Exception e) {
+ rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ return rsp;
+ }
+ if (mmsMsgID > 0) {
+ rsp = bldMmsBmsg(mmsMsgID, rsp);
+ }
+ return rsp;
+ }
+
+ private BluetoothMasPushMsgRsp pushMessageMms(BluetoothMasPushMsgRsp rsp,
+ String readStr, String name) throws BadRequestException {
+ String fullPath = (name == null || name.length() == 0) ? mCurrentPath : mCurrentPath + "/" + name;
+ if (fullPath.equalsIgnoreCase("telecom/msg/outbox")) {
+ String handle = addToMmsFolder(DRAFTS, readStr);
+ if (INTERNAL_ERROR == handle) { // == comparison valid here
+ rsp.response = ResponseCodes.OBEX_HTTP_NOT_FOUND;
+ return rsp;
+ }
+ moveMMSfromDraftstoOutbox();
+ if (V) Log.v(TAG, "\nBroadcasting Intent to MmsSystemEventReceiver\n ");
+ Intent sendIntent = new Intent("android.intent.action.MMS_PUSH");
+ mContext.sendBroadcast(sendIntent);
+ rsp.msgHandle = handle;
+ rsp.response = ResponseCodes.OBEX_HTTP_OK;
+ return rsp;
+ } else {
+ String splitStrings[] = mCurrentPath.split("/");
+ mMnsClient.addMceInitiatedOperation("+");
+ int tmp = splitStrings.length;
+ String folderName;
+ if (name != null) {
+ if (name.length() == 0) {
+ folderName = splitStrings[tmp - 1];
+ } else {
+ folderName = name;
+ }
+ } else {
+ folderName = splitStrings[tmp - 1];
+ }
+ if (folderName != null && folderName.equalsIgnoreCase(DRAFT)){
+ String handle = addToMmsFolder(folderName, readStr);
+ if (INTERNAL_ERROR == handle) { // == comparison valid here
+ rsp.msgHandle = null;
+ rsp.response = ResponseCodes.OBEX_HTTP_NOT_FOUND;
+ return rsp;
+ }
+ rsp.msgHandle = handle;
+ rsp.response = ResponseCodes.OBEX_HTTP_OK;
+ return rsp;
+ } else {
+ rsp.msgHandle = null;
+ rsp.response = ResponseCodes.OBEX_HTTP_FORBIDDEN;
+ return rsp;
+ }
+ }
+ }
+
+ private static int toByte(char c) throws BadRequestException{
+ if (c >= '0' && c <= '9') return (c - '0');
+ if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
+ if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
+ throw new BadRequestException ("Invalid hex char '" + c + "'");
+ }
+
+ private static byte[] hexStringToByteArray(String hexString) throws BadRequestException{
+ int length = hexString.length();
+ if((length % 2) != 0)
+ throw new BadRequestException ("HexString must be even number of characters");
+
+ byte[] buffer = new byte[length / 2];
+
+ for (int i = 0 ; i < length ; i += 2) {
+ buffer[i / 2] = (byte)((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i+1)));
+ }
+ return buffer;
+ }
+
+ private String parseSMSDeliverPdu(String pdu)throws BadRequestException{
+ SmsMessage sms = SmsMessage.createFromPdu(hexStringToByteArray(pdu));
+ return sms.getMessageBody();
+ }
+
+ private BluetoothMasPushMsgRsp pushMessageSms(BluetoothMasPushMsgRsp rsp, String readStr,
+ String name, BluetoothMasAppParams bluetoothMasAppParams) throws BadRequestException {
+ BmessageConsts bMsg = MapUtils.fromBmessageSMS(readStr);
+ String address = bMsg.getRecipientVcard_phone_number();
+ String smsText;
+ if((int)bluetoothMasAppParams.Charset == 0)
+ smsText = parseSMSDeliverPdu(bMsg.getBody_msg());
+ else
+ smsText = bMsg.getBody_msg();
+ String fullPath = (name == null || name.length() == 0) ? mCurrentPath : mCurrentPath + "/" + name;
+ if(!"telecom/msg/outbox".equalsIgnoreCase(fullPath)) {
+ String splitStrings[] = mCurrentPath.split("/");
+ mMnsClient.addMceInitiatedOperation("+");
+ int tmp = splitStrings.length;
+ String folderName;
+ if (name != null) {
+ if (name.length() == 0){
+ folderName = splitStrings[tmp - 1];
+ } else {
+ folderName = name;
+ }
+ } else {
+ folderName = splitStrings[tmp - 1];
+ }
+ if (folderName != null && folderName.equalsIgnoreCase(DRAFT)) {
+ String handle = addToSmsFolder(folderName, address, smsText);
+ if (INTERNAL_ERROR == handle) { // == comparison valid here
+ rsp.msgHandle = null;
+ rsp.response = ResponseCodes.OBEX_HTTP_NOT_FOUND;
+ return rsp;
+ }
+ rsp.msgHandle = handle;
+ rsp.response = ResponseCodes.OBEX_HTTP_OK;
+ return rsp;
+ } else {
+ rsp.msgHandle = null;
+ rsp.response = ResponseCodes.OBEX_HTTP_FORBIDDEN;
+ return rsp;
+ }
+ }
+ rsp.msgHandle = "";
+ if (bluetoothMasAppParams.Transparent == 0) {
+ mMnsClient.addMceInitiatedOperation("+");
+ String handle = addToSmsFolder(QUEUED, address, smsText);
+ if (INTERNAL_ERROR == handle) { // == comparison valid here
+ rsp.msgHandle = null;
+ rsp.response = ResponseCodes.OBEX_HTTP_NOT_FOUND;
+ return rsp;
+ }
+ rsp.msgHandle = handle;
+ rsp.response = ResponseCodes.OBEX_HTTP_OK;
+ } else if (bluetoothMasAppParams.Transparent == 1) {
+ ArrayList<String> parts = new ArrayList<String>();
+ SmsManager sms = SmsManager.getDefault();
+ parts = sms.divideMessage(smsText);
+
+ mMnsClient.addMceInitiatedOperation("+");
+ sms.sendMultipartTextMessage(address, null, parts, null, null);
+ rsp.msgHandle = "-1";
+ rsp.response = ResponseCodes.OBEX_HTTP_OK;
+ return rsp;
+ }
+
+ if (V) {
+ Log.v(TAG, " Trying to send SMS ");
+ Log.v(TAG, " Text " + smsText + " address " + address);
+ }
+
+ try {
+ Intent sendIntentSms = new Intent("com.android.mms.transaction.SEND_MESSAGE");
+ sendIntentSms.putExtra(android.content.Intent.EXTRA_PHONE_NUMBER, address);
+ sendIntentSms.putExtra(android.content.Intent.EXTRA_TEXT, smsText);
+ mContext.sendBroadcast(sendIntentSms);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return rsp;
+ }
+
+ private int setMsgStatusSms(long msgHandle, BluetoothMasAppParams bluetoothMasAppParams){
+ long handle = msgHandle - SMS_OFFSET_START;
+ Uri uri = Uri.parse("content://sms/" + handle);
+ Cursor cr = mContext.getContentResolver().query(uri, null, null, null, null);
+ if (cr != null && cr.moveToFirst()) {
+ if (bluetoothMasAppParams.StatusIndicator == 0) {
+ /* Read Status */
+ ContentValues values = new ContentValues();
+ values.put("read", bluetoothMasAppParams.StatusValue);
+ mContext.getContentResolver().update(uri, values, null, null);
+ } else {
+ if (bluetoothMasAppParams.StatusValue == 1) {
+ deleteSMS(handle);
+ } else if (bluetoothMasAppParams.StatusValue == 0) {
+ unDeleteSMS(handle);
+ }
+ }
+ }
+ if (cr != null) {
+ cr.close();
+ }
+ // Do we need to return ResponseCodes.OBEX_HTTP_BAD_REQUEST when cr == null?
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ private int setMsgStatusMms(long msgHandle, BluetoothMasAppParams bluetoothMasAppParams){
+ long handle = getMmsMsgHndToID(msgHandle);
+ String whereClause = " _id= " + handle;
+ Uri uri = Uri.parse("content://mms/");
+ if(handle > 0){
+ Cursor cr = mContext.getContentResolver().query(uri, null, null, null, null);
+ if (cr != null) {
+ if (cr.getCount() > 0) {
+ cr.moveToFirst();
+ if (bluetoothMasAppParams.StatusIndicator == 0) {
+ /* Read Status */
+ ContentValues values = new ContentValues();
+ values.put("read", bluetoothMasAppParams.StatusValue);
+ int rowUpdate = mContext.getContentResolver().update(uri,
+ values, whereClause, null);
+ if (V){
+ Log.v(TAG, "\nRows updated => " + Integer.toString(rowUpdate));
+ }
+ return ResponseCodes.OBEX_HTTP_OK;
+ } else {
+ if (bluetoothMasAppParams.StatusValue == 1) {
+ deleteMMS(handle);
+ } else if (bluetoothMasAppParams.StatusValue == 0) {
+ unDeleteMMS(handle);
+ }
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+ }
+ cr.close();
+ }
+ }
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+}
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMasMsg.java b/src/org/codeaurora/bluetooth/map/BluetoothMasMsg.java
new file mode 100644
index 0000000..81248f5
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMasMsg.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+import java.io.File;
+
+public class BluetoothMasMsg {
+
+ byte fractionDeliver;
+ File file;
+
+}
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMasObexServer.java b/src/org/codeaurora/bluetooth/map/BluetoothMasObexServer.java
new file mode 100644
index 0000000..c2accc3
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMasObexServer.java
@@ -0,0 +1,1320 @@
+/*
+ * Copyright (c) 2008-2009, Motorola, Inc.
+ * Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.text.format.Time;
+import android.util.Log;
+
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMasMessageListingRsp;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMasMessageRsp;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMasPushMsgRsp;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+import javax.obex.ApplicationParameter;
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+import javax.obex.ResponseCodes;
+import javax.obex.ServerOperation;
+import javax.obex.ServerRequestHandler;
+
+import org.codeaurora.bluetooth.map.MapUtils.MapUtils.BadRequestException;
+
+public class BluetoothMasObexServer extends ServerRequestHandler {
+
+ private static final String TAG = "BluetoothMasObexServer";
+
+ private static final boolean D = BluetoothMasService.DEBUG;
+
+ private static final boolean V = BluetoothMasService.VERBOSE;
+
+ private static final int UUID_LENGTH = 16;
+
+ // type for list folder contents
+ private static final String TYPE_LISTING = "x-obex/folder-listing";
+ private static final String TYPE_MESSAGE_LISTING = "x-bt/MAP-msg-listing";
+ private static final String TYPE_MESSAGE = "x-bt/message";
+ private static final String TYPE_MESSAGE_STATUS = "x-bt/messageStatus";
+ private static final String TYPE_MESSAGE_UPDATE = "x-bt/MAP-messageUpdate";
+ private static final String TYPE_MESSAGE_NOTIFICATION = "x-bt/MAP-NotificationRegistration";
+
+ public long mConnectionId;
+
+ private Handler mCallback = null;
+
+ public Context mContext;
+
+ public static boolean sIsAborted = false;
+
+ private PowerManager.WakeLock mWakeLock = null;
+
+ public enum MasState {
+ MAS_SERVER_CONNECTING,
+ MAS_SERVER_DISCONNECTING,
+ MAS_SERVER_CONNECTED,
+ MAS_SERVER_DISCONNECTED,
+ MAS_SERVER_SET_FOLDER,
+ MAS_SERVER_GET_FILE_PENDING,
+ MAS_SERVER_BROWSE_FOLDER_PENDING,
+ MAS_SERVER_BROWSE_FOLDER,
+ MAS_SERVER_GET_MSG_LIST_PENDING,
+ MAS_SERVER_GET_MSG_LIST,
+ MAS_SERVER_GET_MSG_PENDING,
+ MAS_SERVER_GET_MSG,
+ MAS_SERVER_SET_MSG_STATUS,
+ MAS_SERVER_SET_NOTIFICATION_REG,
+ MAS_SERVER_UPDATE_INBOX,
+ MAS_SERVER_PUSH_MESSAGE
+ };
+ private MasState mState = MasState.MAS_SERVER_DISCONNECTED;
+
+ // 128 bit UUID for MAS
+ private static final byte[] MAS_TARGET = new byte[] {
+ (byte)0xbb, (byte)0x58, (byte)0x2b, (byte)0x40, (byte)0x42, (byte)0x0c, (byte)0x11, (byte)0xdb,
+ (byte)0xb0, (byte)0xde, (byte)0x08, (byte)0x00, (byte)0x20, (byte)0x0c, (byte)0x9a, (byte)0x66
+ };
+
+ private IBluetoothMasApp mAppIf;
+
+ private BluetoothDevice mRemoteDevice;
+
+ private class MasAppParamsStore {
+
+ private BluetoothMasAppParams appParams = null;
+
+ public final void clear() {
+ if (D) Log.d(TAG, "Clear AppParams : Enter");
+ appParams.MaxListCount = BluetoothMasSpecParams.MAS_DEFAULT_MAX_LIST_COUNT;
+ appParams.ListStartOffset = 0;
+ appParams.SubjectLength = BluetoothMasSpecParams.MAS_DEFAULT_SUBJECT_LENGTH;
+ appParams.ParameterMask = BluetoothMasSpecParams.MAS_DEFAULT_PARAMETER_MASK;
+ appParams.FilterMessageType = 0;
+ appParams.FilterReadStatus = 0;
+ appParams.FilterPriority = 0;
+ appParams.FilterPeriodBegin = null;
+ appParams.FilterPeriodEnd = null;
+ appParams.FilterRecipient = null;
+ appParams.FilterOriginator = null;
+ appParams.Retry = 1;
+ appParams.Transparent = 0;
+ appParams.FractionRequest = BluetoothMasSpecParams.MAS_FRACTION_REQUEST_NOT_SET;
+ appParams.Charset = 0x01;
+ }
+
+ public MasAppParamsStore() {
+ super();
+ appParams = new BluetoothMasAppParams();
+ clear();
+ }
+
+ public final BluetoothMasAppParams get() {
+ if (D) Log.d(TAG, "Create MasAppParams struct for service : Enter");
+ BluetoothMasAppParams tmp = new BluetoothMasAppParams();
+
+ tmp.MaxListCount = appParams.MaxListCount;
+ tmp.ListStartOffset = appParams.ListStartOffset;
+ tmp.SubjectLength = appParams.SubjectLength;
+ tmp.ParameterMask = appParams.ParameterMask;
+
+ tmp.Attachment = appParams.Attachment;
+ tmp.Charset = appParams.Charset;
+
+ tmp.StatusIndicator = appParams.StatusIndicator;
+ tmp.StatusValue = appParams.StatusValue;
+ tmp.Retry = appParams.Retry;
+
+ tmp.FilterMessageType = appParams.FilterMessageType;
+ tmp.FilterReadStatus = appParams.FilterReadStatus;
+ tmp.FilterPriority = appParams.FilterPriority;
+
+ tmp.FilterPeriodBegin = (appParams.FilterPeriodBegin == null) ? null : new String(appParams.FilterPeriodBegin);
+ tmp.FilterPeriodEnd = (appParams.FilterPeriodEnd == null) ? null : new String(appParams.FilterPeriodEnd);
+ tmp.FilterRecipient = (appParams.FilterRecipient == null) ? null : new String(appParams.FilterRecipient);
+ tmp.FilterOriginator = (appParams.FilterOriginator == null) ? null : new String(appParams.FilterOriginator);
+ tmp.Retry = appParams.Retry;
+ tmp.Transparent = appParams.Transparent;
+ tmp.FractionRequest = appParams.FractionRequest;
+ tmp.Notification = appParams.Notification;
+
+ return tmp;
+ }
+
+ public final boolean isMaxListCountZero() {
+
+ return (appParams.MaxListCount == 0) ? true : false;
+
+ }
+
+ private final int getUint16BigEndian(byte b1, byte b2) {
+ int retVal;
+ retVal = (int) ((0x0000FF00 & (int) (b1 << 0x8)) | (0x000000FF & (int) b2));
+ return retVal;
+ }
+
+ private final long getUint32BigEndian(byte b1, byte b2, byte b3, byte b4) {
+ long retVal;
+ retVal = (long) ((0xFF000000 & (long) (b1 << 0x24))
+ | (0x00FF0000 & (long) (b2 << 0x16))
+ | (0x0000FF00 & (long) (b3 << 0x8)) | (0x000000FF & (long) b4));
+ return retVal;
+ }
+
+ private final boolean validateTag(long tagVal, long tagLen, long tagMinVal, long tagMaxVal, long tagActualLen) {
+
+ if (tagLen != tagActualLen) {
+ return false;
+ }
+
+ if ( tagVal < tagMinVal || tagVal > tagMaxVal){
+ return false;
+ }
+ return true;
+ }
+
+ public final boolean parse(byte[] params) {
+ int i = 0;
+
+
+ if (D) Log.d(TAG, "Parse App. Params: Enter");
+
+ if (params == null){
+ if (D) Log.d(TAG, "No App. Params to parse: Exit");
+ return true;
+ }
+
+ while (i < params.length) {
+ switch (params[i]) {
+ case BluetoothMasSpecParams.MAS_TAG_MAX_LIST_COUNT:
+ i += 2;
+ appParams.MaxListCount = getUint16BigEndian(params[i], params[i + 1]);
+ if (V){
+ Log.v(TAG, " params i " + params[i] + " params i+1"
+ + params[i + 1] + " maxlistcount "
+ + appParams.MaxListCount);
+ }
+ if(validateTag((long)appParams.MaxListCount, (long) params[i-1],
+ (long) BluetoothMasSpecParams.MAS_TAG_MAX_LIST_COUNT_MIN_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_MAX_LIST_COUNT_MAX_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_MAX_LIST_COUNT_LEN ) == false){
+ return false;
+ }
+ i += BluetoothMasSpecParams.MAS_TAG_MAX_LIST_COUNT_LEN;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_LIST_START_OFFSET:
+ i += 2;
+ appParams.ListStartOffset = getUint16BigEndian(params[i],
+ params[i + 1]);
+ if (V){
+ Log.v(TAG, " params i " + params[i] + " params i+1"
+ + params[i + 1] + " maxlistcount "
+ + appParams.ListStartOffset);
+ }
+ if(validateTag((long)appParams.ListStartOffset, (long) params[i-1],
+ (long) BluetoothMasSpecParams.MAS_TAG_LIST_START_OFFSET_MIN_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_LIST_START_OFFSET_MAX_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_LIST_START_OFFSET_LEN ) == false){
+ return false;
+ }
+ i += BluetoothMasSpecParams.MAS_TAG_LIST_START_OFFSET_LEN;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_FILTER_PERIOD_BEGIN:
+ i += 1;
+ appParams.FilterPeriodBegin = new String("");
+ for (int j = 1; j <= params[i]; j++) {
+ appParams.FilterPeriodBegin += (char) params[i + j];
+ }
+ if (V){
+ Log.v(TAG, "FilterPeriodBegin "
+ + appParams.FilterPeriodBegin);
+ }
+ i += params[i];
+ i += 1;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_FILTER_PERIOD_END:
+ i += 1;
+ appParams.FilterPeriodEnd = new String("");
+ for (int j = 1; j <= params[i]; j++) {
+ appParams.FilterPeriodEnd += (char) params[i + j];
+ }
+ if (V){
+ Log.v(TAG, "FilterPeriodEnd " + appParams.FilterPeriodEnd);
+ }
+ i += params[i];
+ i += 1;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_FILTER_RECIPIENT:
+ i += 1;
+ appParams.FilterRecipient = new String("");
+ for (int j = 1; j <= params[i]; j++) {
+ appParams.FilterRecipient += (char)params[i + j];
+
+ }
+ if (V){
+ Log.v(TAG, "FilterPeriodRecipient "
+ + appParams.FilterRecipient);
+ }
+ i += params[i];
+ i += 1;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_FILTER_ORIGINATOR:
+ i += 1;
+ appParams.FilterOriginator = new String("");
+ for (int j = 1; j <= params[i]; j++) {
+ appParams.FilterOriginator += (char) params[i+ j];
+ }
+ if (V){
+ Log.v(TAG, "FilterPeriodOriginator "
+ + appParams.FilterOriginator);
+ }
+ i += params[i];
+ i += 1;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_FILTER_MESSAGE_TYPE:
+ i += 2;
+ appParams.FilterMessageType = params[i];
+ if (V){
+ Log.v(TAG, " params i " + params[i] + " FilterMessageType "
+ + appParams.FilterMessageType);
+ }
+ if(validateTag((long)appParams.FilterMessageType, (long) params[i-1],
+ (long) BluetoothMasSpecParams.MAS_TAG_FILTER_MESSAGE_TYPE_MIN_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_FILTER_MESSAGE_TYPE_MAX_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_FILTER_MESSAGE_TYPE_LEN ) == false){
+ return false;
+ }
+ i += BluetoothMasSpecParams.MAS_TAG_FILTER_MESSAGE_TYPE_LEN;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_FILTER_READ_STATUS:
+ i += 2;
+ appParams.FilterReadStatus = params[i];
+ if (V){
+ Log.v(TAG, " params i " + params[i] + " FilterReadStatus "
+ + appParams.FilterReadStatus);
+ }
+ if(validateTag((long)appParams.FilterReadStatus, (long) params[i-1],
+ (long) BluetoothMasSpecParams.MAS_TAG_FILTER_READ_STATUS_MIN_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_FILTER_READ_STATUS_MAX_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_FILTER_READ_STATUS_LEN ) == false){
+ return false;
+ }
+ i += BluetoothMasSpecParams.MAS_TAG_FILTER_READ_STATUS_LEN;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_FILTER_PRIORITY:
+ i += 2;
+ appParams.FilterPriority = params[i];
+ if (V){
+ Log.v(TAG, " params i " + params[i] + " FilterPriority "
+ + appParams.FilterPriority);
+ }
+ if(validateTag((long)appParams.FilterPriority, (long) params[i-1],
+ (long) BluetoothMasSpecParams.MAS_TAG_FILTER_PRIORITY_MIN_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_FILTER_PRIORITY_MAX_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_FILTER_PRIORITY_LEN ) == false){
+ return false;
+ }
+ i += BluetoothMasSpecParams.MAS_TAG_FILTER_PRIORITY_LEN;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_STATUS_INDICATOR:
+ i += 2;
+ appParams.StatusIndicator = params[i];
+ if (V){
+ Log.v(TAG, " params i " + params[i] + " StatusIndicator "
+ + appParams.StatusIndicator);
+ }
+ if(validateTag((long)appParams.StatusIndicator, (long) params[i-1],
+ (long) BluetoothMasSpecParams.MAS_TAG_STATUS_INDICATOR_MIN_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_STATUS_INDICATOR_MAX_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_STATUS_INDICATOR_LEN ) == false){
+ return false;
+ }
+ i += BluetoothMasSpecParams.MAS_TAG_STATUS_INDICATOR_LEN;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_STATUS_VALUE:
+ i += 2;
+ appParams.StatusValue = params[i];
+ if (V){
+ Log.v(TAG, " params i " + params[i] + " StatusValue "
+ + appParams.StatusValue);
+ }
+ if(validateTag((long)appParams.StatusValue, (long) params[i-1],
+ (long) BluetoothMasSpecParams.MAS_TAG_STATUS_VALUE_MIN_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_STATUS_VALUE_MAX_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_STATUS_VALUE_LEN ) == false){
+ return false;
+ }
+ i += BluetoothMasSpecParams.MAS_TAG_STATUS_VALUE_LEN;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_SUBJECT_LENGTH:
+ i += 2;
+ appParams.SubjectLength = (short)(params[i] & 0x00FF);
+ if (V){
+ Log.v(TAG, " params i " + params[i] + " SubjectLen "
+ + appParams.SubjectLength);
+ }
+ if(validateTag((long)appParams.SubjectLength, (long) params[i-1],
+ (long) BluetoothMasSpecParams.MAS_TAG_SUBJECT_LENGTH_MIN_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_SUBJECT_LENGTH_MAX_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_SUBJECT_LENGTH_LEN ) == false){
+ return false;
+ }
+ i += BluetoothMasSpecParams.MAS_TAG_SUBJECT_LENGTH_LEN;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_PARAMETER_MASK:
+ i += 2;
+ appParams.ParameterMask = getUint32BigEndian(params[i],
+ params[i + 1], params[i + 2], params[i + 3]);
+ if ( appParams.ParameterMask == 0 ){
+ // If it is 0, send all parameters
+ appParams.ParameterMask = BluetoothMasSpecParams.MAS_DEFAULT_PARAMETER_MASK;
+ }
+ if (V){
+ Log.v(TAG, " params i " + params[i] + " params i+1"
+ + params[i + 1] + "params[i+2]" + params[i + 2]
+ + "params[i+3" + params[i + 3] + " ParameterMask "
+ + appParams.ParameterMask);
+ }
+ if(validateTag((long)appParams.ParameterMask, (long) params[i-1],
+ (long) BluetoothMasSpecParams.MAS_TAG_PARAMETER_MASK_MIN_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_PARAMETER_MASK_MAX_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_PARAMETER_MASK_LEN ) == false){
+ return false;
+ }
+ i += BluetoothMasSpecParams.MAS_TAG_PARAMETER_MASK_LEN;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_CHARSET:
+ i += 2;
+ appParams.Charset = params[i];
+ if (V){
+ Log.v(TAG, " params i " + params[i] + " Charset "
+ + appParams.Charset);
+ }
+ if(validateTag((long)appParams.Charset, (long) params[i-1],
+ (long) BluetoothMasSpecParams.MAS_TAG_CHARSET_MIN_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_CHARSET_MAX_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_CHARSET_LEN ) == false){
+ return false;
+ }
+ i += BluetoothMasSpecParams.MAS_TAG_CHARSET_LEN;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_TRANSPARENT:
+ i += 2;
+ appParams.Transparent = params[i];
+ if (V){
+ Log.v(TAG, " params i " + params[i] + " Transparent "
+ + appParams.Transparent);
+ }
+ if(validateTag((long)appParams.Transparent, (long) params[i-1],
+ (long) BluetoothMasSpecParams.MAS_TAG_TRANSPARENT_MIN_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_TRANSPARENT_MAX_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_TRANSPARENT_LEN ) == false){
+ return false;
+ }
+ i += BluetoothMasSpecParams.MAS_TAG_TRANSPARENT_LEN;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_RETRY:
+ i += 2;
+ appParams.Retry = params[i];
+ if (V){
+ Log.v(TAG, " params i " + params[i] + " Retry "
+ + appParams.Retry);
+ }
+ if(validateTag((long)appParams.Retry, (long) params[i-1],
+ (long) BluetoothMasSpecParams.MAS_TAG_RETRY_MIN_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_RETRY_MAX_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_RETRY_LEN ) == false){
+ return false;
+ }
+ i += BluetoothMasSpecParams.MAS_TAG_RETRY_LEN;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_ATTACHMENT:
+ i += 2;
+ appParams.Attachment = params[i];
+ if (V){
+ Log.v(TAG, " params i " + params[i] + " Attachment "
+ + appParams.Attachment);
+ }
+ if(validateTag((long)appParams.Attachment, (long) params[i-1],
+ (long) BluetoothMasSpecParams.MAS_TAG_ATTACHMENT_MIN_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_ATTACHMENT_MAX_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_ATTACHMENT_LEN ) == false){
+ return false;
+ }
+ i += BluetoothMasSpecParams.MAS_TAG_ATTACHMENT_LEN;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_FRACTION_REQUEST:
+ i += 2;
+ appParams.FractionRequest = params[i];
+ if (V){
+ Log.v(TAG, " params i " + params[i] + " Fraction Request "
+ + appParams.FractionRequest);
+ }
+ if(validateTag((long)appParams.FractionRequest, (long) params[i-1],
+ (long) BluetoothMasSpecParams.MAS_TAG_FRACTION_REQUEST_MIN_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_FRACTION_REQUEST_MAX_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_FRACTION_REQUEST_LEN ) == false){
+ return false;
+ }
+ i += BluetoothMasSpecParams.MAS_TAG_FRACTION_REQUEST_LEN;
+ break;
+
+ case BluetoothMasSpecParams.MAS_TAG_NOTIFICATION_STATUS:
+ i += 2;
+ appParams.Notification = params[i];
+ if(validateTag((long)appParams.MaxListCount, (long) params[i-1],
+ (long) BluetoothMasSpecParams.MAS_TAG_NOTIFICATION_STATUS_MIN_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_NOTIFICATION_STATUS_MAX_VAL,
+ (long) BluetoothMasSpecParams.MAS_TAG_NOTIFICATION_STATUS_LEN ) == false){
+ return false;
+
+ }
+ i += BluetoothMasSpecParams.MAS_TAG_NOTIFICATION_STATUS_LEN;
+ break;
+ default:
+ break;
+
+ }
+ }
+ return true;
+ }
+ }
+
+ private MasAppParamsStore masAppParams = new MasAppParamsStore();
+
+ public BluetoothMasObexServer(Handler callback, BluetoothDevice remoteDevice,
+ Context context, IBluetoothMasApp appIf) {
+ super();
+ mAppIf = appIf;
+
+ mConnectionId = -1;
+ mCallback = callback;
+ mContext = context;
+ mRemoteDevice = remoteDevice;
+ if (V){
+ Log.v(TAG, "BlueoothMasObexServer const called");
+ }
+ // set initial value when ObexServer created
+ if (D) Log.d(TAG, "Initialize MasObexServer");
+ }
+
+ @Override
+ public int onConnect(final HeaderSet request, HeaderSet reply) {
+ if (V) Log.v(TAG, "BluetoothMasObexServer: onConnect");
+ acquireMasLock();
+ int retVal = onConnectInternal(request, reply);
+ if (V) Log.v(TAG, "BluetoothMasObexServer: exiting from onConnect");
+ releaseMasLock();
+ return retVal;
+ }
+
+ private int onConnectInternal(final HeaderSet request, HeaderSet reply) {
+ if (D) Log.d(TAG, "onConnect()");
+ try {
+ byte[] uuid = (byte[]) request.getHeader(HeaderSet.TARGET);
+ if (uuid == null) {
+ Log.w(TAG, "Null UUID ");
+ 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] != MAS_TARGET[i]) {
+ Log.w(TAG, "Wrong UUID");
+ return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+ }
+ }
+ if (!mAppIf.checkPrecondition()) {
+ return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+ }
+ reply.setHeader(HeaderSet.WHO, uuid);
+ } catch (IOException e) {
+ Log.e(TAG, e.toString());
+ return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, e.toString());
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+
+ 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 = BluetoothMasService.MSG_SESSION_ESTABLISHED;
+ msg.sendToTarget();
+
+ mState = MasState.MAS_SERVER_CONNECTED;
+ if (D) Log.d(TAG, "Connect(): Success");
+ mAppIf.onConnect();
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ @Override
+ public void onDisconnect(final HeaderSet req, final HeaderSet resp) {
+ if (V) Log.v(TAG, "BluetoothMasObexServer: onDisconnect");
+ acquireMasLock();
+ onDisconnectInternal(req, resp);
+ if (V) Log.v(TAG, "BluetoothMasObexServer: exiting from onDisconnect");
+ releaseMasLock();
+ }
+
+ private void onDisconnectInternal(final HeaderSet req, final HeaderSet resp) {
+ if (D) Log.d(TAG, "onDisconnect(): enter");
+ mAppIf.onDisconnect();
+
+ resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
+ if (mCallback != null) {
+ Message msg = Message.obtain(mCallback);
+ msg.what = BluetoothMasService.MSG_SESSION_DISCONNECTED;
+ msg.sendToTarget();
+ if (V) Log.v(TAG,"onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out.");
+ }
+
+ // MNS Service
+ mAppIf.stopMnsSession(mRemoteDevice);
+ mState = MasState.MAS_SERVER_DISCONNECTED;
+ }
+
+ @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 onSetPath(final HeaderSet request, final HeaderSet reply,
+ final boolean backup, final boolean create) {
+ if (V) Log.v(TAG, "BluetoothMasObexServer: onSetPath");
+ acquireMasLock();
+ int retVal = onSetPathInternal(request, reply, backup, create);
+ if (V) Log.v(TAG, "BluetoothMasObexServer: exiting from onSetPath");
+ releaseMasLock();
+ return retVal;
+ }
+
+ private int onSetPathInternal(final HeaderSet request, final HeaderSet reply,
+ final boolean backup, final boolean create) {
+
+ if (D) Log.d(TAG, "onSetPath(): supports SetPath request.");
+
+ String tmpPath = null;
+ boolean retVal = false;
+ boolean tmpBackup = backup;
+
+ if (tmpBackup && create) {
+ tmpBackup = true;
+ } else {
+ tmpBackup = false;
+ }
+ if (mState != MasState.MAS_SERVER_CONNECTED) {
+ if (D)
+ Log.e(TAG, "onSetPath() Failed: Mas Server not connected");
+ return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+ }
+ mState = MasState.MAS_SERVER_SET_FOLDER;
+
+ try {
+ tmpPath = (String) request.getHeader(HeaderSet.NAME);
+ } catch (IOException e) {
+ Log.e(TAG, "Get name header fail: " + e);
+ return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Get name header fail: " + e);
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ if (D)
+ Log.d(TAG, "backup=" + backup + " create=" + create + " name="
+ + tmpPath);
+
+ retVal = mAppIf.setPath(backup, tmpPath);
+ mState = MasState.MAS_SERVER_CONNECTED;
+ if (retVal == true) {
+ if (V)
+ Log.v(TAG, "SetPath to" + tmpPath + "SUCCESS");
+ return ResponseCodes.OBEX_HTTP_OK;
+ } else {
+ Log.e(TAG, "Path not found");
+ return ResponseCodes.OBEX_HTTP_NOT_FOUND;
+ }
+ }
+
+ @Override
+ public void onClose() {
+ if (V) Log.v(TAG, "BluetoothMasObexServer: onClose");
+ acquireMasLock();
+ onCloseInternal();
+ if (V) Log.v(TAG, "BluetoothMasObexServer: exiting from onClose");
+ releaseMasLock();
+ }
+
+ public void onCloseInternal() {
+ mAppIf.stopMnsSession(mRemoteDevice);
+
+ if (mCallback != null) {
+ Message msg = Message.obtain(mCallback);
+ msg.what = BluetoothMasService.MSG_SERVERSESSION_CLOSE;
+ msg.arg1 = mAppIf.getMasId();
+ msg.sendToTarget();
+ if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
+ }
+ }
+
+ @Override
+ public int onGet(Operation op) {
+ if (V) Log.v(TAG, "BluetoothMasObexServer: onGet");
+ acquireMasLock();
+ int retVal = onGetInternal(op);
+ if (V) Log.v(TAG, "BluetoothMasObexServer: exiting from onGet");
+ releaseMasLock();
+ return retVal;
+ }
+
+ private int onGetInternal(Operation op) {
+
+ byte[] appParams = null;
+ boolean retVal = true;
+
+ if (D) Log.d(TAG, "onGet(): support GET request.");
+
+ sIsAborted = false;
+ HeaderSet request = null;
+ String type = "";
+ String name = "";
+
+ // TBD - IncompleteGet handling
+ try {
+ request = op.getReceivedHeader();
+ type = (String) request.getHeader(HeaderSet.TYPE);
+ name = (String) request.getHeader(HeaderSet.NAME);
+ appParams = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
+ } catch (IOException e) {
+ Log.e(TAG, "request headers error: " + e);
+ return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "request headers error: " + e);
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+
+ masAppParams.clear();
+ retVal = masAppParams.parse(appParams);
+
+ if (type == null || (retVal == false) ) {
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+
+ if (V) Log.v(TAG, "type = " + type);
+
+ if (type.equals(TYPE_LISTING)) {
+ return sendFolderListing(op);
+ }
+ if (type.equals(TYPE_MESSAGE_LISTING)) {
+ return sendMsgListing(op, name);
+ }
+ if (type.equals(TYPE_MESSAGE)) {
+ return sendMsg(op, name);
+ }
+
+ if (V) Log.v(TAG, "get returns HTTP_BAD_REQUEST");
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+
+ }
+
+ private final int pushMsg(Operation op, String name) {
+ // TBD - Need to do this on a per masinstance basis
+ String fileName = "PushMsg" + mAppIf.getMasId();
+ int outputBufferSize = op.getMaxPacketSize();
+ int readLength = 0;
+ long timestamp = 0;
+ int position = 0;
+ byte[] b = new byte[outputBufferSize];
+ BufferedOutputStream bos = null;
+ InputStream is = null;
+ boolean error = false;
+ File file = null;
+ BluetoothMasPushMsgRsp pMsg = new BluetoothMasPushMsgRsp();;
+
+ file = new File(mContext.getFilesDir() + "/" + fileName);
+
+ try {
+ is = op.openInputStream();
+ } catch (IOException e1) {
+ Log.e(TAG, "Error while opening InputStream");
+ error = true;
+ }
+
+ if (error != true) {
+ try {
+
+ FileOutputStream fos = mContext.openFileOutput(fileName,
+ Context.MODE_PRIVATE);
+
+ bos = new BufferedOutputStream(fos);
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ if (error != true) {
+ try {
+ while (true) {
+ if (V) {
+ timestamp = System.currentTimeMillis();
+ }
+ readLength = is.read(b);
+ if (readLength == -1) {
+ if (D) {
+ Log.d(TAG, "Receive file reached stream end at position" + position);
+ }
+ break;
+ }
+ bos.write(b, 0, readLength);
+ position += readLength;
+ if (V) {
+ Log.v(TAG, "Receive file position = " + position
+ + " readLength " + readLength + " bytes took "
+ + (System.currentTimeMillis() - timestamp)
+ + " ms");
+ }
+ }
+ } catch (IOException e1) {
+ Log.e(TAG, "Error when receiving file");
+ error = true;
+ }
+ }
+
+ if (bos != null) {
+ try {
+ bos.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Error when closing stream after send");
+ error = true;
+ }
+ }
+
+ if (error != true) {
+ try {
+ pMsg = mAppIf.pushMsg(name, file, masAppParams.get());
+ } catch (BadRequestException e) {
+ if (V) Log.v(TAG, "BadRequestException:" + e.getMessage(), e);
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+
+ if ((pMsg.msgHandle != null)
+ && (pMsg.response == ResponseCodes.OBEX_HTTP_OK)) {
+ HeaderSet reply;
+ reply = new HeaderSet();
+ reply.setHeader(HeaderSet.NAME, pMsg.msgHandle);
+ return pushHeader(op, reply);
+ } else {
+ return pMsg.response;
+ }
+ } else {
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ }
+
+ private final int msgStatus(Operation op, String name) {
+ if (D) Log.d(TAG, "msgStatus: Enter");
+ if (name == null || name.length() == 0) {
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ return mAppIf.msgStatus(name, masAppParams.get());
+ }
+
+ private final int msgUpdate(Operation op) {
+ if (D) Log.d(TAG, "msgUpdate: Enter");
+ return mAppIf.msgUpdate();
+ }
+
+ private final int notification(Operation op) {
+ return mAppIf.notification(mRemoteDevice, masAppParams.get());
+ }
+
+ @Override
+ public int onPut(Operation op) {
+ if (V) Log.v(TAG, "BluetoothMasObexServer: onPut");
+ acquireMasLock();
+ int retVal = onPutInternal(op);
+ if (V) Log.v(TAG, "BluetoothMasObexServer: exiting from onPut");
+ releaseMasLock();
+ return retVal;
+ }
+
+ private int onPutInternal(Operation op) {
+
+ byte[] appParams = null;
+ boolean retVal = true;
+ BluetoothMasAppParams tmp;
+ InputStream inputStream = null;
+ byte[] readByte = new byte[10];
+
+ if (D) Log.d(TAG, "onPut(): support PUT request.");
+
+ sIsAborted = false;
+ HeaderSet request = null;
+ String type = "";
+ String name = "";
+
+ // TBD - IncompleteGet handling
+ try {
+ request = op.getReceivedHeader();
+ type = (String) request.getHeader(HeaderSet.TYPE);
+ name = (String) request.getHeader(HeaderSet.NAME);
+ appParams = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
+ } catch (IOException e) {
+ Log.e(TAG, "request headers error: " + e);
+ return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "request headers error: " + e);
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ masAppParams.clear();
+ if ( appParams != null ){
+ masAppParams.parse(appParams);
+ }
+ if(type == null || retVal == false) {
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+
+ tmp = masAppParams.get();
+
+ if (V) Log.v(TAG, "type = " + type);
+
+ if (type.equals(TYPE_MESSAGE)) {
+ return pushMsg(op, name);
+ }
+ if (type.equals(TYPE_MESSAGE_STATUS)) {
+ return msgStatus(op, name);
+ }
+ if (type.equals(TYPE_MESSAGE_UPDATE)) {
+ return msgUpdate(op);
+ }
+ if (type.equals(TYPE_MESSAGE_NOTIFICATION)) {
+ if (V) Log.v(TAG, "entered TYPE_MESSAGE_NOTIFICATION");
+ // Following section of code ensures if the Body/EOB
+ // payload is not present in the same Obex packet
+ // Containing headers, but is pushed in the continuation
+ // packet, then we take the corresponding action only
+ // after reading the complete obex packet.
+ // And as the Body / EOB payload contains dummy body as
+ // '0' [0x30 (48)], hence we discard the same.
+ if (!(((ServerOperation) op).finalBitSet)) {
+ if (V) Log.v(TAG, "Not the final Obex packet");
+ try {
+ inputStream = op.openInputStream();
+ int readLength = -1;
+ while(true) {
+ if (V) Log.v(TAG, "Inside while loop: TYPE_MESSAGE_NOTIFICATION");
+ readLength = inputStream.read(readByte);
+ if (readLength == -1) {
+ if (V) Log.v(TAG, "Complete Obex packet read, Proceeding");
+ break;
+ } else {
+ if (V) Log.v(TAG, "readLength: " + readLength);
+ if (V) Log.v(TAG, "readByte[0]: " + readByte[0]);
+ // Compare first byte to check if '0' is received as Body/ EOB
+ // And Length of the Body Payload is 1, If not, print Error
+ if ((readByte[0] == 0x30) && (readLength == 1)) {
+ if (V) Log.v(TAG, "Body / EOB contains '0'");
+ } else {
+ Log.e(TAG, "Body / EOB does not contain '0'");
+ }
+ }
+ }
+ } catch (IOException ioException) {
+ Log.e(TAG, "Error while opening InputStream");
+ } finally {
+ try {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } catch (IOException ioException) {
+ Log.e(TAG, "Error when closing stream");
+ }
+ }
+ }
+ return notification(op);
+ }
+ if (V) Log.v(TAG, "put returns HTTP_BAD_REQUEST");
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+
+ }
+
+ /**
+ */
+ private final int pushHeader(final Operation op, final HeaderSet reply) {
+
+ if (D) Log.d(TAG, "Push Header");
+ if (D) Log.d(TAG, reply.toString());
+
+ int pushResult = ResponseCodes.OBEX_HTTP_OK;
+ try {
+ op.sendHeaders(reply);
+ } catch (IOException e) {
+ Log.e(TAG, e.toString());
+ pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, e.toString());
+ pushResult = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ if (D) Log.d(TAG, "Push Header: Exit : RetVal " + pushResult);
+ return pushResult;
+ }
+
+ /** Function to send folder data to client */
+ private final int sendFolderListingBody(Operation op,
+ final String folderlistString) {
+
+ if (folderlistString == null) {
+ Log.e(TAG, "folderlistString is null!");
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ int folderlistStringLen = folderlistString.length();
+ if (D) Log.d(TAG, "Send Folder Listing Body: len=" + folderlistStringLen);
+
+ OutputStream outputStream = null;
+ int pushResult = ResponseCodes.OBEX_HTTP_OK;
+ try {
+ outputStream = op.openOutputStream();
+ } catch (IOException e) {
+ Log.e(TAG, "open outputstrem failed" + e.toString());
+ return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+ }
+
+ int position = 0;
+ long timestamp = 0;
+ int outputBufferSize = op.getMaxPacketSize();
+ if (V) Log.v(TAG, "outputBufferSize = " + outputBufferSize);
+ while (position != folderlistStringLen) {
+ if (sIsAborted) {
+ ((ServerOperation) op).isAborted = true;
+ sIsAborted = false;
+ break;
+ }
+ if (V) timestamp = System.currentTimeMillis();
+ int readLength = outputBufferSize;
+ if (folderlistStringLen - position < outputBufferSize) {
+ readLength = folderlistStringLen - position;
+ }
+ String subStr = folderlistString.substring(position, position + readLength);
+ try {
+ outputStream.write(subStr.getBytes(), 0, readLength);
+ } catch (IOException e) {
+ Log.e(TAG, "write outputstream failed" + e.toString());
+ pushResult = ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+ break;
+ }
+ if (V) {
+ Log.d(TAG, "Sending folderlist String position = " + position
+ + " readLength " + readLength + " bytes took "
+ + (System.currentTimeMillis() - timestamp) + " ms");
+ }
+ position += readLength;
+ }
+
+ if (V) Log.v(TAG, "Send Data complete!");
+
+ if (!closeStream(outputStream, op)) {
+ Log.e(TAG,"Send Folder Listing Body - Close output stream error! ");
+ pushResult = ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+ }
+ if (V) Log.v(TAG, "Send Folder Listing Body complete! result = " + pushResult);
+ return pushResult;
+ }
+
+ private final int sendBody(Operation op, File fileinfo) {
+ if (V) Log.v(TAG, "sendFile = " + fileinfo.getName());
+ int position = 0;
+ int readLength = 0;
+ int outputBufferSize = op.getMaxPacketSize();
+ long timestamp = 0;
+ FileInputStream fileInputStream = null;
+ OutputStream outputStream;
+ BufferedInputStream bis;
+
+ if (D) Log.d(TAG, "Send Body: Enter");
+ try {
+ byte[] buffer = new byte[outputBufferSize];
+ fileInputStream = new FileInputStream(fileinfo);
+ outputStream = op.openOutputStream();
+ bis = new BufferedInputStream(fileInputStream, 0x4000);
+ while ((position != fileinfo.length())) {
+ timestamp = System.currentTimeMillis();
+ if (position != fileinfo.length()) {
+ readLength = bis.read(buffer, 0, outputBufferSize);
+ }
+ outputStream.write(buffer, 0, readLength);
+ position += readLength;
+ if (V) {
+ Log.v(TAG, "Sending file position = " + position
+ + " readLength " + readLength + " bytes took "
+ + (System.currentTimeMillis() - timestamp) + " ms");
+ }
+ }
+ } catch (IOException e) {
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ } finally {
+ if (fileInputStream != null) {
+ try {
+ fileInputStream.close();
+ } catch (IOException ei) {
+ Log.e(TAG, "Error while closing stream"+ ei.toString());
+ }
+ }
+ }
+ if (position == fileinfo.length()) {
+ if (D) Log.d(TAG, "SendBody : Exit: OK");
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+ else {
+ if (D) Log.d(TAG, "SendBody : Exit: CONTINUE");
+ return ResponseCodes.OBEX_HTTP_CONTINUE;
+ }
+ }
+
+ /** Send a bMessage to client */
+ private final int sendMsg(Operation op, String name) {
+ BluetoothMasMessageRsp msg = new BluetoothMasMessageRsp();
+ byte[] val = new byte[1];
+
+ if (D) Log.d(TAG, "SendMsg : Enter");
+ msg = mAppIf.msg(name, masAppParams.get());
+ if(msg == null || msg.rsp != ResponseCodes.OBEX_HTTP_OK) {
+ return msg.rsp;
+ }
+
+ if(masAppParams.get().FractionRequest == 1){
+ HeaderSet reply;
+ val[0] = msg.fractionDeliver;
+ ApplicationParameter ap = new ApplicationParameter();
+ ap.addAPPHeader(
+ (byte) BluetoothMasSpecParams.MAS_TAG_FRACTION_DELIVER,
+ (byte) BluetoothMasSpecParams.MAS_TAG_FRACTION_DELIVER_LEN,
+ val);
+
+ reply = new HeaderSet();
+ reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
+
+ int retVal;
+ retVal = pushHeader(op, reply);
+ if (retVal != ResponseCodes.OBEX_HTTP_OK) {
+ if (D) Log.d(TAG, "SendMsg : FAILED: RetVal " + retVal);
+ return retVal;
+ }
+ }
+ if (D) Log.d(TAG, "SendMsg : SUCCESS");
+ return sendBody(op, msg.file);
+ }
+
+ /** Send an XML format String to client for Folder listing */
+ private final int sendFolderListing(Operation op) {
+ int folderListSize = 0;
+ if (D) Log.d(TAG, "SendFolderListing : Enter");
+ folderListSize = mAppIf.folderListingSize();
+ byte[] size = new byte[2];
+ size[0] = (byte) ((folderListSize / 0x100) & 0xff);
+ size[1] = (byte) ((folderListSize % 0x100) & 0xff);
+
+ HeaderSet reply;
+ ApplicationParameter ap = new ApplicationParameter();
+ ap.addAPPHeader(
+ (byte) BluetoothMasSpecParams.MAS_TAG_FOLDER_LISTING_SIZE,
+ (byte) BluetoothMasSpecParams.MAS_TAG_FOLDER_LISTING_SIZE_LEN,
+ size);
+ reply = new HeaderSet();
+ reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
+
+ if (!masAppParams.isMaxListCountZero()) {
+ int retVal;
+ retVal = pushHeader(op, reply);
+ if (retVal != ResponseCodes.OBEX_HTTP_OK) {
+ if (D) Log.d(TAG, "SendFolderListing : FAILED : RetVal" + retVal);
+ return retVal;
+ }
+ return sendFolderListingBody(op, mAppIf.folderListing(masAppParams.get()));
+ } else {
+ op.noEndofBody();
+ return pushHeader(op, reply);
+ }
+ }
+
+ /** Send an XML format String to client for Message listing */
+ private final int sendMsgListing(Operation op, String name) {
+
+ byte[] val = new byte[2];
+ BluetoothMasMessageListingRsp appIfMsgListRsp = new BluetoothMasMessageListingRsp();
+ if (D) Log.d(TAG, "SendMsgListing : Enter");
+ appIfMsgListRsp = mAppIf.msgListing(name, masAppParams.get());
+
+ if(appIfMsgListRsp == null || appIfMsgListRsp.rsp != ResponseCodes.OBEX_HTTP_OK) {
+ return appIfMsgListRsp.rsp;
+ }
+
+ Time time = new Time();
+ time.setToNow();
+
+ String time3339 = time.format3339(false);
+ int timeStrLength = time3339.length();
+
+ String datetimeStr = time.toString().substring(0, 15) +
+ time3339.substring(timeStrLength - 6, timeStrLength - 3) +
+ time3339.substring(timeStrLength - 2, timeStrLength);
+
+ byte[] MSETime = datetimeStr.getBytes();
+
+ HeaderSet reply;
+ ApplicationParameter ap = new ApplicationParameter();
+ ap.addAPPHeader((byte) BluetoothMasSpecParams.MAS_TAG_MSE_TIME,
+ (byte) BluetoothMasSpecParams.MAS_TAG_MSE_TIME_LEN, MSETime);
+ val[0] = appIfMsgListRsp.newMessage;
+ ap.addAPPHeader((byte) BluetoothMasSpecParams.MAS_TAG_NEW_MESSAGE,
+ (byte) BluetoothMasSpecParams.MAS_TAG_NEW_MESSAGE_LEN, val);
+
+ val[0] = (byte) ((appIfMsgListRsp.msgListingSize / 0x100) & 0xff);
+ val[1] = (byte) ((appIfMsgListRsp.msgListingSize % 0x100) & 0xff);
+
+ ap.addAPPHeader(
+ (byte) BluetoothMasSpecParams.MAS_TAG_MESSAGE_LISTING_SIZE,
+ (byte) BluetoothMasSpecParams.MAS_TAG_MESSAGE_LISTING_SIZE_LEN,
+ val);
+
+ reply = new HeaderSet();
+ reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
+
+ if (!masAppParams.isMaxListCountZero()) {
+ int retVal;
+ retVal = pushHeader(op, reply);
+ if (retVal != ResponseCodes.OBEX_HTTP_OK) {
+ if (D) Log.d(TAG, "SendMsgListing : Failed : RetVal " + retVal);
+ return retVal;
+ }
+ return sendBody(op, appIfMsgListRsp.file);
+ } else {
+ return pushHeader(op, reply);
+ }
+ }
+
+ public static boolean closeStream(final OutputStream out, final Operation op) {
+ boolean returnvalue = true;
+ try {
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "outputStream close failed" + e.toString());
+ returnvalue = false;
+ }
+ try {
+ if (op != null) {
+ op.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "operation close failed" + e.toString());
+ returnvalue = false;
+ }
+ return returnvalue;
+ }
+
+ private void acquireMasLock() {
+ if (V) Log.v(TAG, "About to acquire Mas:mWakeLock");
+ if (mWakeLock == null) {
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MasPartialWakeLock");
+ mWakeLock.setReferenceCounted(false);
+ mWakeLock.acquire();
+ if (V) Log.v(TAG, "Mas:mWakeLock acquired");
+ }
+ else
+ {
+ Log.e(TAG, "Mas:mWakeLock already acquired");
+ }
+ }
+
+ private void releaseMasLock() {
+ if (V) Log.v(TAG, "About to release Mas:mWakeLock");
+ if (mWakeLock != null) {
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ if (V) Log.v(TAG, "Mas:mWakeLock released");
+ } else {
+ if (V) Log.v(TAG, "Mas:mWakeLock already released");
+ }
+ mWakeLock = null;
+ }
+ }
+};
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMasReceiver.java b/src/org/codeaurora/bluetooth/map/BluetoothMasReceiver.java
new file mode 100644
index 0000000..ce29570
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMasReceiver.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2008-2009, Motorola, Inc.
+ * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class BluetoothMasReceiver extends BroadcastReceiver {
+ private static final String TAG = "BluetoothMasReceiver";
+ private static final boolean V = BluetoothMasService.VERBOSE;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (V) Log.v(TAG, "BluetoothMasReceiver onReceive :" + intent.getAction());
+
+ Intent in = new Intent();
+ in.putExtras(intent);
+ in.setClass(context, BluetoothMasService.class);
+ String action = intent.getAction();
+ in.putExtra("action", action);
+ boolean startService = true;
+ if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
+ int state = in.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+ BluetoothAdapter.ERROR);
+ in.putExtra(BluetoothAdapter.EXTRA_STATE, state);
+ /*
+ * Other than Tranistioning state, start the MAP service whenever BT
+ * transitioned to OFF/ON, or Adapter returns error
+ */
+ Log.d(TAG, "Bluetooth STATE CHANGED to " + state);
+
+ if ((state == BluetoothAdapter.STATE_TURNING_ON)
+ || (state == BluetoothAdapter.STATE_TURNING_OFF)) {
+ startService = false;
+ }
+ if (state == BluetoothAdapter.ERROR) {
+ Log.d(TAG, " BluetoothAdapter returns ERROR");
+ }
+
+ if ((state == BluetoothAdapter.STATE_OFF)) {
+
+ startService = false;
+ // Stop MAS service
+ context.stopService(in);
+
+ // TODO - Stop MNS service?
+ }
+
+ } else if (action.equals(Intent.ACTION_MEDIA_EJECT)
+ || action.equals(Intent.ACTION_MEDIA_MOUNTED))
+ startService = true;
+
+ if (startService) {
+ context.startService(in);
+ }
+ }
+}
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMasService.java b/src/org/codeaurora/bluetooth/map/BluetoothMasService.java
new file mode 100644
index 0000000..e53c2f3
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMasService.java
@@ -0,0 +1,982 @@
+/*
+ * Copyright (c) 2008-2009, Motorola, Inc. All rights reserved.
+ * Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+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.BluetoothServerSocket;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.text.TextUtils;
+import android.os.ParcelUuid;
+import android.util.Log;
+import android.bluetooth.BluetoothUuid;
+
+import org.codeaurora.bluetooth.R;
+import org.codeaurora.bluetooth.map.BluetoothMns.MnsClient;
+import org.codeaurora.bluetooth.map.MapUtils.EmailUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.HashSet;
+import java.io.FileInputStream;
+import java.io.DataInputStream;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.FileNotFoundException;
+import javax.obex.ServerSession;
+
+import static org.codeaurora.bluetooth.map.IBluetoothMasApp.MESSAGE_TYPE_EMAIL;
+import static org.codeaurora.bluetooth.map.IBluetoothMasApp.MESSAGE_TYPE_MMS;
+import static org.codeaurora.bluetooth.map.IBluetoothMasApp.MESSAGE_TYPE_SMS;
+import static org.codeaurora.bluetooth.map.IBluetoothMasApp.MESSAGE_TYPE_SMS_MMS;
+
+public class BluetoothMasService extends Service {
+ private static final String TAG = "BluetoothMasService";
+
+ /**
+ * 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;
+ // public static final boolean DEBUG = false;
+ //public static final boolean VERBOSE = false;
+
+ /**
+ * Intent indicating incoming connection request which is sent to
+ * BluetoothMasActivity
+ */
+ public static final String ACCESS_REQUEST_ACTION = "org.codeaurora.bluetooth.map.accessrequest";
+
+ /**
+ * Intent indicating incoming connection request accepted by user which is
+ * sent from BluetoothMasActivity
+ */
+ public static final String ACCESS_ALLOWED_ACTION = "org.codeaurora.bluetooth.map.accessallowed";
+
+ /**
+ * Intent indicating incoming connection request denied by user which is
+ * sent from BluetoothMasActivity
+ */
+ public static final String ACCESS_DISALLOWED_ACTION = "org.codeaurora.bluetooth.map.accessdisallowed";
+
+ /**
+ * Intent indicating incoming obex authentication request which is from
+ * PCE(Carkit)
+ */
+ public static final String AUTH_CHALL_ACTION = "org.codeaurora.bluetooth.map.authchall";
+
+ /**
+ * Intent indicating obex session key input complete by user which is sent
+ * from BluetoothMasActivity
+ */
+ public static final String AUTH_RESPONSE_ACTION = "org.codeaurora.bluetooth.map.authresponse";
+
+ /**
+ * Intent indicating user canceled obex authentication session key input
+ * which is sent from BluetoothMasActivity
+ */
+ public static final String AUTH_CANCELLED_ACTION = "org.codeaurora.bluetooth.map.authcancelled";
+
+ /**
+ * Intent indicating timeout for user confirmation, which is sent to
+ * BluetoothMasActivity
+ */
+ public static final String USER_CONFIRM_TIMEOUT_ACTION = "org.codeaurora.bluetooth.map.userconfirmtimeout";
+
+ public static final String THIS_PACKAGE_NAME = "org.codeaurora.bluetooth";
+
+ /**
+ * Intent Extra name indicating always allowed which is sent from
+ * BluetoothMasActivity
+ */
+ public static final String EXTRA_ALWAYS_ALLOWED = "org.codeaurora.bluetooth.map.alwaysallowed";
+
+ /**
+ * Intent Extra name indicating session key which is sent from
+ * BluetoothMasActivity
+ */
+ public static final String EXTRA_SESSION_KEY = "org.codeaurora.bluetooth.map.sessionkey";
+
+ /**
+ * Intent Extra name indicating BluetoothDevice which is sent to
+ * BluetoothMasActivity
+ */
+ public static final String EXTRA_BLUETOOTH_DEVICE = "org.codeaurora.bluetooth.map.bluetoothdevice";
+
+ private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+
+ private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
+
+ public static final int MSG_SERVERSESSION_CLOSE = 5004;
+
+ public static final int MSG_SESSION_ESTABLISHED = 5005;
+
+ public static final int MSG_SESSION_DISCONNECTED = 5006;
+
+ public static final int MSG_OBEX_AUTH_CHALL = 5007;
+
+ private static final int MSG_INTERNAL_START_LISTENER = 1;
+
+ private static final int MSG_INTERNAL_USER_TIMEOUT = 2;
+
+ private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
+
+ public static final int MAS0_PORT_NUM = 16;
+
+ public static final int MAS1_PORT_NUM = 17;
+
+ public static final ParcelUuid MessageAccessServer =
+ ParcelUuid.fromString("00001132-0000-1000-8000-00805f9b34fb");
+
+ public static final ParcelUuid MessageNotificationServer =
+ ParcelUuid.fromString("00001133-0000-1000-8000-00805f9b34fb");
+
+ // Ensure not conflict with Opp notification ID
+ private static final int NOTIFICATION_ID_ACCESS = -1000005;
+
+ private BluetoothAdapter mAdapter;
+
+ private Object mAuthSync = new Object();
+
+ private BluetoothMapAuthenticator mAuth = null;
+
+ BluetoothMasObexConnectionManager mConnectionManager = null;
+
+ BluetoothMns mnsClient;
+ private static BluetoothDevice mRemoteDevice = null;
+ private static HashSet<BluetoothDevice> trustDevices = new HashSet<BluetoothDevice>();
+
+ private boolean mHasStarted = false;
+ private int mStartId = -1;
+
+ private boolean mIsEmailEnabled = true;
+
+ /**
+ * The flag indicating MAP request has been notified.
+ * This is set on when initiate notification and set off after accept/time out
+ */
+ private volatile boolean mIsRequestBeingNotified = false;
+
+
+ public static class MasInstanceInfo {
+ int mSupportedMessageTypes;
+ Class<? extends MnsClient> mMnsClientClass;
+ int mRfcommPort;
+
+ public MasInstanceInfo(int smt, Class<? extends MnsClient> _class, int port) {
+ mSupportedMessageTypes = smt;
+ mMnsClientClass = _class;
+ mRfcommPort = port;
+ }
+ }
+
+ public static final int MAX_INSTANCES = 2;
+ public static final int EMAIL_MAS_START = 1;
+ public static final int EMAIL_MAS_END = 1;
+ public static final MasInstanceInfo MAS_INS_INFO[] = new MasInstanceInfo[MAX_INSTANCES];
+
+ // The following information must match with corresponding
+ // SDP records supported message types and port number
+ // Please refer sdptool.c, BluetoothService.java, & init.qcom.rc
+ static {
+ MAS_INS_INFO[0] = new MasInstanceInfo(MESSAGE_TYPE_SMS_MMS, BluetoothMnsSmsMms.class, MAS0_PORT_NUM);
+ MAS_INS_INFO[1] = new MasInstanceInfo(MESSAGE_TYPE_EMAIL, BluetoothMnsEmail.class, MAS1_PORT_NUM);
+ }
+
+ private ContentObserver mEmailAccountObserver;
+
+ private static final String CONF_FILE_PATH =
+ "/etc/bluetooth/main.conf";
+
+ public void CheckEmailEnabled() {
+ if (VERBOSE)
+ Log.v(TAG, " CheckEmailEnabled: Loading from conf");
+ FileInputStream fstream = null;
+ try {
+ fstream = new FileInputStream(CONF_FILE_PATH);
+ DataInputStream in = new DataInputStream(fstream);
+ BufferedReader file = new BufferedReader(new InputStreamReader(in));
+ String line;
+ while((line = file.readLine()) != null) {
+ line = line.trim();
+ if (line.length() == 0 || line.startsWith("#")) continue;
+ String[] value = line.split(" = ");
+ if (value != null && value.length == 2) {
+ if (value[0].equalsIgnoreCase("BluetoothMapEmailEnabled")) {
+ if (value[1].equalsIgnoreCase("false")) {
+ mIsEmailEnabled = false;
+ }
+ else {
+ mIsEmailEnabled = true;
+ }
+ Log.v(TAG, "CheckEmailEnabled: IsEmailEnabled: " + mIsEmailEnabled);
+ }
+ }
+ }
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Main.conf File Not found");
+ } catch (IOException e) {
+ Log.e(TAG, "IOException: read Main.conf File " + e);
+ } finally {
+ if (fstream != null) {
+ try {
+ fstream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ private void updateEmailAccount() {
+ if (VERBOSE) Log.v(TAG, "updateEmailAccount()");
+ List<Long> list = EmailUtils.getEmailAccountIdList(this);
+ ArrayList<Long> notAssigned = new ArrayList<Long>();
+ EmailUtils.removeMasIdIfNotPresent(list);
+ for (Long id : list) {
+ int masId = EmailUtils.getMasId(id);
+ if (masId == -1) {
+ notAssigned.add(id);
+ }
+ }
+ for (int i = EMAIL_MAS_START; i <= EMAIL_MAS_END; i ++) {
+ long accountId = EmailUtils.getAccountId(i);
+ if (accountId == -1 && notAssigned.size() > 0) {
+ EmailUtils.updateMapTable(notAssigned.remove(0), i);
+ }
+ }
+ }
+
+ public BluetoothMasService() {
+ CheckEmailEnabled();
+ mConnectionManager = new BluetoothMasObexConnectionManager();
+ if (VERBOSE)
+ Log.v(TAG, "BluetoothMasService: mIsEmailEnabled: " + mIsEmailEnabled);
+ if(mIsEmailEnabled) {
+ mEmailAccountObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateEmailAccount();
+ }
+ };
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (VERBOSE)
+ Log.v(TAG, "Map Service onCreate");
+
+ mConnectionManager.init();
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ if (!mHasStarted) {
+ mHasStarted = true;
+ if (VERBOSE) Log.v(TAG, "Starting MAS instances");
+
+ int state = mAdapter.getState();
+ if (state == BluetoothAdapter.STATE_ON) {
+ mSessionStatusHandler.sendEmptyMessage(MSG_INTERNAL_START_LISTENER);
+ } else if (VERBOSE) {
+ Log.v(TAG, "BT is not ON, no start");
+ }
+ }
+ Log.v(TAG, "onCreate: mIsEmailEnabled: " + mIsEmailEnabled);
+ if(mIsEmailEnabled) {
+ getContentResolver().registerContentObserver(
+ EmailUtils.EMAIL_ACCOUNT_URI, true, mEmailAccountObserver);
+ }
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (VERBOSE)
+ Log.v(TAG, "Map Service onStartCommand");
+ int retCode = super.onStartCommand(intent, flags, startId);
+ if (retCode == START_STICKY) {
+ mStartId = startId;
+ if (mAdapter == null) {
+ Log.w(TAG, "Stopping BluetoothMasService: "
+ + "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 retCode;
+ }
+
+ // 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);
+ boolean removeTimeoutMsg = true;
+ if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
+ if (state == BluetoothAdapter.STATE_TURNING_OFF) {
+ // Release all resources
+ closeService();
+ } else {
+ removeTimeoutMsg = false;
+ Log.v(TAG, "parseIntent 1: mIsEmailEnabled: " + mIsEmailEnabled);
+ if(mIsEmailEnabled) {
+ if (state == BluetoothAdapter.STATE_ON) {
+ updateEmailAccount();
+ }
+ }
+ }
+ } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) &&
+ mIsRequestBeingNotified) {
+ if (mRemoteDevice == null) {
+ Log.e(TAG, "Unexpected error!");
+ return;
+ }
+ if (mSessionStatusHandler != null) {
+ /* Let the user timeout handle this case as well */
+ mSessionStatusHandler.sendMessage(mSessionStatusHandler
+ .obtainMessage(MSG_INTERNAL_USER_TIMEOUT));
+ removeTimeoutMsg = false;
+ }
+ } else if (action.equals(ACCESS_ALLOWED_ACTION)) {
+ if (mRemoteDevice == null) {
+ Log.e(TAG, "Unexpected error!");
+ return;
+ }
+ if (intent.getBooleanExtra(BluetoothMasService.EXTRA_ALWAYS_ALLOWED, false) == true) {
+ trustDevices.add(mRemoteDevice);
+ Log.v(TAG, "setTrust() TRUE " + mRemoteDevice.getName());
+ }
+ Log.v(TAG, "parseIntent 2: mIsEmailEnabled: " + mIsEmailEnabled);
+ if(mIsEmailEnabled) {
+ updateEmailAccount();
+ }
+ mConnectionManager.initiateObexServerSession(mRemoteDevice);
+ } else if (action.equals(ACCESS_DISALLOWED_ACTION)) {
+ mIsRequestBeingNotified = false;
+ mConnectionManager.stopObexServerSessionWaiting();
+ } else if (AUTH_RESPONSE_ACTION.equals(action)) {
+ String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY);
+ notifyAuthKeyInput(sessionkey);
+ } else if (AUTH_CANCELLED_ACTION.equals(action)) {
+ notifyAuthCancelled();
+ } else if ( BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
+
+ if (intent.hasExtra(BluetoothDevice.EXTRA_DEVICE)) {
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if(device != null)
+ Log.d(TAG,"device: "+ device.getName());
+ if(mRemoteDevice != null)
+ Log.d(TAG," Remtedevie: "+mRemoteDevice.getName());
+ if (device != null && trustDevices.contains(device) &&
+ intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE) == BluetoothDevice.BOND_NONE) {
+ Log.d(TAG,"BOND_STATE_CHANGED REFRESH trustDevices"+ device.getName());
+ trustDevices.remove(device);
+ }
+ }
+
+ } else {
+ removeTimeoutMsg = false;
+ }
+
+ if (removeTimeoutMsg) {
+ mSessionStatusHandler.removeMessages(MSG_INTERNAL_USER_TIMEOUT);
+ if (VERBOSE) Log.v(TAG, "MAS access request notification flag off");
+ mIsRequestBeingNotified = false;
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (VERBOSE)
+ Log.v(TAG, "Map Service onDestroy");
+
+ super.onDestroy();
+ Log.v(TAG, "onDestroy: mIsEmailEnabled: " + mIsEmailEnabled);
+ if(mIsEmailEnabled) {
+ getContentResolver().unregisterContentObserver(mEmailAccountObserver);
+ EmailUtils.clearMapTable();
+ }
+ closeService();
+ }
+
+ private final void closeService() {
+ if (VERBOSE) Log.v(TAG, "MNS_BT: inside closeService");
+ try {
+ if(mnsClient!=null) {
+ if (VERBOSE) Log.v(TAG, "MNS_BT: about to send MNS_BLUETOOTH_OFF");
+ mnsClient.getHandler().sendEmptyMessage(BluetoothMns.MNS_BLUETOOTH_OFF);
+ mnsClient = null;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "MNS_BT: exception while sending MNS_BLUETOOTH_OFF");
+ } finally {
+ if (VERBOSE) Log.v(TAG, "MNS_BT: successfully sent MNS_BLUETOOTH_OFF");
+ mConnectionManager.closeAll();
+ mHasStarted = false;
+ if (stopSelfResult(mStartId)) {
+ if (VERBOSE) Log.v(TAG, "successfully stopped map service");
+ }
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (VERBOSE) Log.v(TAG, "Map Service onBind");
+ return null;
+ }
+
+ private final Handler mSessionStatusHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
+ Context context = getApplicationContext();
+ if (mnsClient == null) {
+ Log.v(TAG, "handleMessage: mIsEmailEnabled" + mIsEmailEnabled);
+ mnsClient = new BluetoothMns(context, mIsEmailEnabled);
+ }
+
+ switch (msg.what) {
+ case MSG_INTERNAL_START_LISTENER:
+ if (mAdapter.isEnabled()) {
+ mConnectionManager.startAll();
+ } else {
+ closeService();
+ }
+ break;
+ case MSG_INTERNAL_USER_TIMEOUT:
+ Intent intent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
+ sendBroadcast(intent);
+ removeMapNotification(NOTIFICATION_ID_ACCESS);
+ if (VERBOSE) Log.v(TAG, "MAS access request notification flag off");
+ mIsRequestBeingNotified = false;
+ mConnectionManager.stopObexServerSessionWaiting();
+ break;
+ case MSG_SERVERSESSION_CLOSE:
+ {
+ final int masId = msg.arg1;
+ mConnectionManager.stopObexServerSession(masId);
+ break;
+ }
+ case MSG_SESSION_ESTABLISHED:
+ break;
+ case MSG_SESSION_DISCONNECTED:
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ private void createMapNotification(BluetoothDevice device) {
+ if (VERBOSE) Log.v(TAG, "Creating MAS access notification");
+ mIsRequestBeingNotified = true;
+
+ NotificationManager nm = (NotificationManager)
+ getSystemService(Context.NOTIFICATION_SERVICE);
+
+ // Create an intent triggered by clicking on the status icon.
+ Intent clickIntent = new Intent();
+ clickIntent.setClass(this, BluetoothMasActivity.class);
+ clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ clickIntent.setAction(ACCESS_REQUEST_ACTION);
+ clickIntent.putExtra(EXTRA_BLUETOOTH_DEVICE, device);
+
+ // Create an intent triggered by clicking on the
+ // "Clear All Notifications" button
+ Intent deleteIntent = new Intent();
+ deleteIntent.setClass(this, BluetoothMasReceiver.class);
+
+ Notification notification = null;
+ String name = device.getName();
+ if (TextUtils.isEmpty(name)) {
+ name = getString(R.string.defaultname);
+ }
+
+ deleteIntent.setAction(ACCESS_DISALLOWED_ACTION);
+ notification = new Notification(android.R.drawable.stat_sys_data_bluetooth,
+ getString(R.string.map_notif_ticker), System.currentTimeMillis());
+ notification.setLatestEventInfo(this, getString(R.string.map_notif_ticker),
+ getString(R.string.map_notif_message, name), PendingIntent
+ .getActivity(this, 0, clickIntent, 0));
+
+ 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_ACCESS, notification);
+
+
+ if (VERBOSE) Log.v(TAG, "Awaiting Authorization : MAS Connection : " + device.getName());
+
+ }
+
+ private void removeMapNotification(int id) {
+ Context context = getApplicationContext();
+ NotificationManager nm = (NotificationManager) context
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.cancel(id);
+ }
+
+ private void notifyAuthKeyInput(final String key) {
+ synchronized (mAuthSync) {
+ if (key != null) {
+ mAuth.setSessionKey(key);
+ }
+ mAuth.setChallenged(true);
+ mAuth.notify();
+ }
+ }
+
+ private void notifyAuthCancelled() {
+ synchronized (mAuthSync) {
+ mAuth.setCancelled(true);
+ mAuth.notify();
+ }
+ }
+
+ class BluetoothMasObexConnectionManager {
+ private ArrayList<BluetoothMasObexConnection> mConnections =
+ new ArrayList<BluetoothMasObexConnection>();
+
+ public BluetoothMasObexConnectionManager() {
+ int numberOfSupportedInstances = MAX_INSTANCES;
+ Log.e(TAG, "BluetoothMasObexConnectionManager: mIsEmailEnabled: " + mIsEmailEnabled);
+ if(!mIsEmailEnabled) {
+ numberOfSupportedInstances = 1; /*Email instance not supported*/
+ }
+ for (int i = 0; i < numberOfSupportedInstances; i ++) {
+ mConnections.add(new BluetoothMasObexConnection(
+ MAS_INS_INFO[i].mSupportedMessageTypes, i, MAS_INS_INFO[i].mRfcommPort));
+ }
+ }
+
+ public void initiateObexServerSession(BluetoothDevice device) {
+ try {
+ for (BluetoothMasObexConnection connection : mConnections) {
+ if (connection.mConnSocket != null && connection.mWaitingForConfirmation) {
+ connection.mWaitingForConfirmation = false;
+ connection.startObexServerSession(device, mnsClient);
+ }
+ }
+ } catch (IOException ex) {
+ Log.e(TAG, "Caught the error: " + ex.toString());
+ }
+ }
+
+ public void setWaitingForConfirmation(int masId) {
+ if (masId < mConnections.size()) {
+ final BluetoothMasObexConnection connect = mConnections.get(masId);
+ connect.mWaitingForConfirmation = true;
+ } else {
+ Log.e(TAG, "Attempt to set waiting for user confirmation for MAS id: " + masId);
+ Log.e(TAG, "out of index");
+ }
+ }
+
+ public void stopObexServerSession(int masId) {
+ if (masId < mConnections.size()) {
+ final BluetoothMasObexConnection connect = mConnections.get(masId);
+ if (connect.mConnSocket != null) {
+ connect.stopObexServerSession();
+ } else {
+ Log.w(TAG, "Attempt to stop OBEX Server session for MAS id: " + masId);
+ Log.w(TAG, "when there is no connected socket");
+ }
+ } else {
+ Log.e(TAG, "Attempt to stop OBEX Server session for MAS id: " + masId);
+ Log.e(TAG, "out of index");
+ }
+ }
+
+ public void stopObexServerSessionWaiting() {
+ for (BluetoothMasObexConnection connection : mConnections) {
+ if (connection.mConnSocket != null && connection.mWaitingForConfirmation) {
+ connection.mWaitingForConfirmation = false;
+ connection.stopObexServerSession();
+ }
+ }
+ }
+
+ public void stopObexServerSessionAll() {
+ for (BluetoothMasObexConnection connection : mConnections) {
+ if (connection.mConnSocket != null) {
+ connection.stopObexServerSession();
+ }
+ }
+ }
+
+ public void closeAll() {
+ for (BluetoothMasObexConnection connection : mConnections) {
+ // Stop the possible trying to init serverSocket
+ connection.mInterrupted = true;
+ connection.closeConnection();
+ }
+ }
+
+ public void startAll() {
+ for (BluetoothMasObexConnection connection : mConnections) {
+ connection.startRfcommSocketListener(mnsClient);
+ }
+ }
+
+ public void init() {
+ for (BluetoothMasObexConnection connection: mConnections) {
+ connection.mInterrupted = false;
+ }
+ }
+
+ public boolean isAllowedConnection(BluetoothDevice remoteDevice) {
+ String remoteAddress = remoteDevice.getAddress();
+ if (remoteAddress == null) {
+ if (VERBOSE) Log.v(TAG, "Connection request from unknown device");
+ return false;
+ }
+ final int size = mConnections.size();
+ for (int i = 0; i < size; i ++) {
+ final BluetoothMasObexConnection connection = mConnections.get(i);
+ BluetoothSocket socket = connection.mConnSocket;
+ if (socket != null) {
+ BluetoothDevice device = socket.getRemoteDevice();
+ if (device != null) {
+ String address = device.getAddress();
+ if (address != null) {
+ if (remoteAddress.equalsIgnoreCase(address)) {
+ if (VERBOSE) {
+ Log.v(TAG, "Connection request from " + remoteAddress);
+ Log.v(TAG, "when MAS id:" + i + " is connected to " + address);
+ }
+ return true;
+ } else {
+ if (VERBOSE) {
+ Log.v(TAG, "Connection request from " + remoteAddress);
+ Log.v(TAG, "when MAS id:" + i + " is connected to " + address);
+ }
+ return false;
+ }
+ } else {
+ // shall not happen, connected device must has address
+ // just for null pointer dereference
+ Log.w(TAG, "Connected device has no address!");
+ }
+ }
+ }
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG, "Connection request from " + remoteAddress);
+ Log.v(TAG, "when no MAS instance is connected.");
+ }
+ return true;
+ }
+ }
+
+ private class BluetoothMasObexConnection {
+ private volatile boolean mInterrupted;
+ private BluetoothServerSocket mServerSocket = null;
+ private SocketAcceptThread mAcceptThread = null;
+ private BluetoothSocket mConnSocket = null;
+ private ServerSession mServerSession = null;
+ private BluetoothMasObexServer mMapServer = null;
+
+ private int mSupportedMessageTypes;
+ private int mPortNum;
+ private int mMasId;
+ boolean mWaitingForConfirmation = false;
+
+ public BluetoothMasObexConnection(int supportedMessageTypes, int masId, int portNumber) {
+ mSupportedMessageTypes = supportedMessageTypes;
+ mMasId = masId;
+ mPortNum = portNumber;
+ }
+
+ private void startRfcommSocketListener(BluetoothMns mnsClient) {
+ if (VERBOSE)
+ Log.v(TAG, "Map Service startRfcommSocketListener");
+
+ if (mServerSocket == null) {
+ if (!initSocket()) {
+ closeService();
+ return;
+ }
+ }
+ if (mAcceptThread == null) {
+ mAcceptThread = new SocketAcceptThread(mnsClient, mMasId);
+ mAcceptThread.setName("BluetoothMapAcceptThread " + mPortNum);
+ mAcceptThread.start();
+ }
+ }
+
+ private final boolean initSocket() {
+ if (VERBOSE)
+ Log.v(TAG, "Map Service initSocket");
+
+ boolean initSocketOK = false;
+ final int CREATE_RETRY_TIME = 10;
+
+ // It's possible that create will fail in some cases. retry for 10 times
+ for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {
+ try {
+ //mServerSocket = mAdapter.listenUsingRfcommOn(mPortNum);
+ if(mPortNum == MAS1_PORT_NUM)
+ mServerSocket = mAdapter.listenUsingRfcommWithServiceRecord("Email Message Access", MessageAccessServer.getUuid());
+ else
+ mServerSocket = mAdapter.listenUsingRfcommWithServiceRecord("SMS/MMS Message Access", MessageAccessServer.getUuid());
+ initSocketOK = true;
+ } catch (IOException e) {
+ Log.e(TAG, "Error create RfcommServerSocket " + e.toString());
+ initSocketOK = false;
+ }
+
+ if (!initSocketOK) {
+ synchronized (this) {
+ try {
+ if (VERBOSE) Log.v(TAG, "wait 3 seconds");
+ Thread.sleep(3000);
+ } 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 on channel "
+ + mPortNum);
+ } else {
+ Log.e(TAG, "Error to create listening socket after "
+ + CREATE_RETRY_TIME + " try");
+ }
+ return initSocketOK;
+ }
+
+ private final void closeSocket() throws IOException {
+ if (mConnSocket != null) {
+ mConnSocket.close();
+ mConnSocket = null;
+ }
+ }
+
+ public void closeConnection() {
+ if (VERBOSE) Log.v(TAG, "Mas connection closing");
+
+ if (mAcceptThread != null) {
+ try {
+ mAcceptThread.shutdown();
+ mAcceptThread.join();
+ } catch (InterruptedException ex) {
+ Log.w(TAG, "mAcceptThread close error" + ex);
+ } finally {
+ mAcceptThread = null;
+ }
+ }
+
+ if (mServerSession != null) {
+ mServerSession.close();
+ mServerSession = null;
+ }
+
+ try {
+ closeSocket();
+ } catch (IOException ex) {
+ Log.e(TAG, "CloseSocket error: " + ex);
+ }
+ if (VERBOSE) Log.v(TAG, "Mas connection closed");
+ }
+
+ private final void startObexServerSession(BluetoothDevice device, BluetoothMns mnsClient)
+ throws IOException {
+ if (VERBOSE)
+ Log.v(TAG, "Map Service startObexServerSession ");
+
+ Context context = getApplicationContext();
+
+ IBluetoothMasApp appIf = null;
+ if (((mSupportedMessageTypes & ~MESSAGE_TYPE_SMS_MMS) == 0x00) &&
+ ((mSupportedMessageTypes & MESSAGE_TYPE_SMS) != 0x00) &&
+ ((mSupportedMessageTypes & MESSAGE_TYPE_MMS) != 0x00)) {
+ // BluetoothMasAppZero if and only if both SMS and MMS
+ appIf = new BluetoothMasAppSmsMms(context, mSessionStatusHandler, mnsClient,
+ mMasId, device.getName());
+ } else if (((mSupportedMessageTypes & ~MESSAGE_TYPE_EMAIL) == 0x0) &&
+ ((mSupportedMessageTypes & MESSAGE_TYPE_EMAIL) != 0x0)) {
+ // BluetoothMasAppOne if and only if email
+ appIf = new BluetoothMasAppEmail(context, mSessionStatusHandler, mnsClient,
+ mMasId, device.getName());
+ }
+
+ mMapServer = new BluetoothMasObexServer(mSessionStatusHandler,
+ device, context, appIf);
+ synchronized (mAuthSync) {
+ mAuth = new BluetoothMapAuthenticator(mSessionStatusHandler);
+ mAuth.setChallenged(false);
+ mAuth.setCancelled(false);
+ }
+ BluetoothMapRfcommTransport transport = new BluetoothMapRfcommTransport(
+ mConnSocket);
+ mServerSession = new ServerSession(transport, mMapServer, mAuth);
+
+ if (VERBOSE) Log.v(TAG, "startObexServerSession() success!");
+ }
+
+ private void stopObexServerSession() {
+ if (VERBOSE) Log.v(TAG, "Map Service stopObexServerSession ");
+
+ closeConnection();
+
+ // Last obex transaction is finished, we start to listen for incoming
+ // connection again
+ if (mAdapter.isEnabled()) {
+ startRfcommSocketListener(mnsClient);
+ }
+ }
+
+ /**
+ * A thread that runs in the background waiting for remote rfcomm
+ * connect.Once a remote socket connected, this thread shall be
+ * shutdown.When the remote disconnect,this thread shall run again waiting
+ * for next request.
+ */
+ private class SocketAcceptThread extends Thread {
+ private boolean stopped = false;
+ private BluetoothMns mnsObj;
+ private int mMasId;
+
+ public SocketAcceptThread(BluetoothMns mnsClient, int masId) {
+ mnsObj = mnsClient;
+ mMasId = masId;
+ }
+
+ @Override
+ public void run() {
+ while (!stopped) {
+ try {
+ BluetoothSocket connSocket = mServerSocket.accept();
+
+ BluetoothDevice device = connSocket.getRemoteDevice();
+ mRemoteDevice = device;
+ if (device == null) {
+ Log.i(TAG, "getRemoteDevice() = null");
+ break;
+ }
+ String remoteDeviceName = device.getName();
+ // In case getRemoteName failed and return null
+ if (TextUtils.isEmpty(remoteDeviceName)) {
+ remoteDeviceName = getString(R.string.defaultname);
+ }
+
+ if (!mConnectionManager.isAllowedConnection(device)) {
+ connSocket.close();
+ continue;
+ }
+ mConnSocket = connSocket;
+ boolean trust = false;
+ if (trustDevices != null)
+ trust = trustDevices.contains(device);
+ if (VERBOSE) Log.v(TAG, "GetTrustState() = " + trust);
+ if (mIsRequestBeingNotified) {
+ if (VERBOSE) Log.v(TAG, "Request notification is still on going.");
+ mConnectionManager.setWaitingForConfirmation(mMasId);
+ break;
+ } else if (trust) {
+ if (VERBOSE) {
+ Log.v(TAG, "trust is true::");
+ Log.v(TAG, "incomming connection accepted from: "
+ + remoteDeviceName + " automatically as trusted device");
+ }
+ try {
+ startObexServerSession(device, mnsObj);
+ } catch (IOException ex) {
+ Log.e(TAG, "catch exception starting obex server session"
+ + ex.toString());
+ }
+ } else {
+ if (VERBOSE) Log.v(TAG, "trust is false.");
+ mConnectionManager.setWaitingForConfirmation(mMasId);
+ createMapNotification(device);
+ if (VERBOSE) Log.v(TAG, "incomming connection accepted from: "
+ + remoteDeviceName);
+ mSessionStatusHandler.sendMessageDelayed(
+ mSessionStatusHandler.obtainMessage(MSG_INTERNAL_USER_TIMEOUT),
+ USER_CONFIRM_TIMEOUT_VALUE);
+ }
+ stopped = true; // job done ,close this thread;
+ } catch (IOException ex) {
+ if (stopped) {
+ break;
+ }
+ if (VERBOSE)
+ Log.v(TAG, "Accept exception: " + ex.toString());
+ }
+ }
+ }
+
+ void shutdown() {
+ if (VERBOSE) Log.v(TAG, "AcceptThread shutdown for MAS id: " + mMasId);
+ stopped = true;
+ interrupt();
+ if (mServerSocket != null) {
+ try {
+ mServerSocket.close();
+ mServerSocket = null;
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close socket", e);
+ }
+ }
+ }
+ }
+ }
+};
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMasSpecParams.java b/src/org/codeaurora/bluetooth/map/BluetoothMasSpecParams.java
new file mode 100644
index 0000000..d6b874b
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMasSpecParams.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+public final class BluetoothMasSpecParams {
+
+ public static final int MAS_TAG_MAX_LIST_COUNT = 0x01;
+ public static final int MAS_TAG_LIST_START_OFFSET = 0x02;
+ public static final int MAS_TAG_FILTER_MESSAGE_TYPE = 0x03;
+ public static final int MAS_TAG_FILTER_PERIOD_BEGIN = 0x04;
+ public static final int MAS_TAG_FILTER_PERIOD_END = 0x05;
+ public static final int MAS_TAG_FILTER_READ_STATUS = 0x06;
+ public static final int MAS_TAG_FILTER_RECIPIENT = 0x07;
+ public static final int MAS_TAG_FILTER_ORIGINATOR = 0x08;
+ public static final int MAS_TAG_FILTER_PRIORITY = 0x09;
+ public static final int MAS_TAG_ATTACHMENT = 0x0A;
+ public static final int MAS_TAG_TRANSPARENT = 0x0B;
+ public static final int MAS_TAG_RETRY = 0x0C;
+ public static final int MAS_TAG_NEW_MESSAGE = 0x0D;
+ public static final int MAS_TAG_NOTIFICATION_STATUS = 0x0E;
+ public static final int MAS_TAG_MAS_INSTANCE_ID = 0x0F;
+ public static final int MAS_TAG_PARAMETER_MASK = 0x10;
+ public static final int MAS_TAG_FOLDER_LISTING_SIZE = 0x11;
+ public static final int MAS_TAG_MESSAGE_LISTING_SIZE = 0x12;
+ public static final int MAS_TAG_SUBJECT_LENGTH = 0x13;
+ public static final int MAS_TAG_CHARSET = 0x14;
+ public static final int MAS_TAG_FRACTION_REQUEST = 0x15;
+ public static final int MAS_TAG_FRACTION_DELIVER = 0x16;
+ public static final int MAS_TAG_STATUS_INDICATOR = 0x17;
+ public static final int MAS_TAG_STATUS_VALUE = 0x18;
+ public static final int MAS_TAG_MSE_TIME = 0x19;
+
+ public static final int MAS_TAG_MAX_LIST_COUNT_LEN = 0x02;
+ public static final int MAS_TAG_LIST_START_OFFSET_LEN = 0x02;
+ public static final int MAS_TAG_SUBJECT_LENGTH_LEN = 0x01;
+ public static final int MAS_TAG_FILTER_MESSAGE_TYPE_LEN = 0x01;
+ public static final int MAS_TAG_FILTER_READ_STATUS_LEN = 0x01;
+ public static final int MAS_TAG_FILTER_PRIORITY_LEN = 0x01;
+ public static final int MAS_TAG_PARAMETER_MASK_LEN = 0x04;
+ public static final int MAS_TAG_ATTACHMENT_LEN = 0x01;
+ public static final int MAS_TAG_TRANSPARENT_LEN = 0x01;
+ public static final int MAS_TAG_RETRY_LEN = 0x01;
+ public static final int MAS_TAG_NEW_MESSAGE_LEN = 0x01;
+ public static final int MAS_TAG_NOTIFICATION_STATUS_LEN = 0x01;
+ public static final int MAS_TAG_MAS_INSTANCE_ID_LEN = 0x01;
+ public static final int MAS_TAG_FOLDER_LISTING_SIZE_LEN = 0x02;
+ public static final int MAS_TAG_MESSAGE_LISTING_SIZE_LEN = 0x02;
+ public static final int MAS_TAG_CHARSET_LEN = 0x01;
+ public static final int MAS_TAG_FRACTION_REQUEST_LEN = 0x01;
+ public static final int MAS_TAG_FRACTION_DELIVER_LEN = 0x01;
+ public static final int MAS_TAG_STATUS_INDICATOR_LEN = 0x01;
+ public static final int MAS_TAG_STATUS_VALUE_LEN = 0x01;
+ public static final int MAS_TAG_MSE_TIME_LEN = 0x14;
+
+ public static final int MAS_DEFAULT_MAX_LIST_COUNT = 1024;
+ public static final int MAS_DEFAULT_SUBJECT_LENGTH = 255;
+ public static final int MAS_DEFAULT_PARAMETER_MASK = 0xFFFF;
+
+ public static final int MAS_FRACTION_REQUEST_NOT_SET = 0x02;
+
+ public static final int MAS_TAG_MAX_LIST_COUNT_MIN_VAL = 0x0;
+ public static final int MAS_TAG_MAX_LIST_COUNT_MAX_VAL = 0xFFFF;
+ public static final int MAS_TAG_LIST_START_OFFSET_MIN_VAL = 0x00;
+ public static final int MAS_TAG_LIST_START_OFFSET_MAX_VAL = 0xFFFF;
+ public static final int MAS_TAG_SUBJECT_LENGTH_MIN_VAL = 0x01;
+ public static final int MAS_TAG_SUBJECT_LENGTH_MAX_VAL = 0xFF;
+ public static final int MAS_TAG_FILTER_MESSAGE_TYPE_MIN_VAL = 0x00;
+ public static final int MAS_TAG_FILTER_MESSAGE_TYPE_MAX_VAL = 0x0F;
+ public static final int MAS_TAG_FILTER_READ_STATUS_MIN_VAL = 0x00;
+ public static final int MAS_TAG_FILTER_READ_STATUS_MAX_VAL = 0x02;
+ public static final int MAS_TAG_FILTER_PRIORITY_MIN_VAL = 0x00;
+ public static final int MAS_TAG_FILTER_PRIORITY_MAX_VAL = 0x02;
+ public static final int MAS_TAG_PARAMETER_MASK_MIN_VAL = 0x0;
+ public static final int MAS_TAG_PARAMETER_MASK_MAX_VAL = 0xFFFF;
+ public static final int MAS_TAG_ATTACHMENT_MIN_VAL = 0x00;
+ public static final int MAS_TAG_ATTACHMENT_MAX_VAL = 0x01;
+ public static final int MAS_TAG_TRANSPARENT_MIN_VAL = 0x00;
+ public static final int MAS_TAG_TRANSPARENT_MAX_VAL = 0x01;
+ public static final int MAS_TAG_RETRY_MIN_VAL = 0x00;
+ public static final int MAS_TAG_RETRY_MAX_VAL = 0x01;
+ public static final int MAS_TAG_NEW_MESSAGE_MIN_VAL = 0x00;
+ public static final int MAS_TAG_NEW_MESSAGE_MAX_VAL = 0x01;
+ public static final int MAS_TAG_NOTIFICATION_STATUS_MIN_VAL = 0x00;
+ public static final int MAS_TAG_NOTIFICATION_STATUS_MAX_VAL = 0x01;
+ public static final int MAS_TAG_MAS_INSTANCE_ID_MIN_VAL = 0x00;
+ public static final int MAS_TAG_MAS_INSTANCE_ID_MAX_VAL = 0xFF;
+ public static final int MAS_TAG_FOLDER_LISTING_SIZE_MIN_VAL = 0x00;
+ public static final int MAS_TAG_FOLDER_LISTING_SIZE_MAX_VAL = 0xFFFF;
+ public static final int MAS_TAG_MESSAGE_LISTING_SIZE_MIN_VAL = 0x00;
+ public static final int MAS_TAG_MESSAGE_LISTING_SIZE_MAX_VAL = 0xFFFF;
+ public static final int MAS_TAG_CHARSET_MIN_VAL = 0x00;
+ public static final int MAS_TAG_CHARSET_MAX_VAL = 0x01;
+ public static final int MAS_TAG_FRACTION_REQUEST_MIN_VAL = 0x00;
+ public static final int MAS_TAG_FRACTION_REQUEST_MAX_VAL = 0x01;
+ public static final int MAS_TAG_FRACTION_DELIVER_MIN_VAL = 0x00;
+ public static final int MAS_TAG_FRACTION_DELIVER_MAX_VAL = 0x01;
+ public static final int MAS_TAG_STATUS_INDICATOR_MIN_VAL = 0x00;
+ public static final int MAS_TAG_STATUS_INDICATOR_MAX_VAL = 0x01;
+ public static final int MAS_TAG_STATUS_VALUE_MIN_VAL = 0x00;
+ public static final int MAS_TAG_STATUS_VALUE_MAX_VAL = 0x01;
+};
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMasTestActivity.java b/src/org/codeaurora/bluetooth/map/BluetoothMasTestActivity.java
new file mode 100644
index 0000000..708e5be
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMasTestActivity.java
@@ -0,0 +1,306 @@
+ /*
+ * Copyright (c) 2008-2009, Motorola, Inc.
+ * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothDevice;
+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.text.InputFilter;
+import android.text.TextWatcher;
+import android.text.InputFilter.LengthFilter;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+
+import org.codeaurora.bluetooth.R;
+
+import static org.codeaurora.bluetooth.map.BluetoothMasService.EXTRA_BLUETOOTH_DEVICE;
+
+/**
+ * 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 BluetoothMasTestActivity extends Activity implements
+ DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener, TextWatcher {
+ private static final String TAG = "BluetoothMasActivity";
+
+ private static final boolean V = BluetoothMasService.VERBOSE;
+
+ private static final int BLUETOOTH_OBEX_AUTHKEY_MAX_LENGTH = 16;
+
+ private static final int DIALOG_YES_NO_CONNECT = 1;
+
+ private static final int DIALOG_YES_NO_AUTH = 2;
+
+ 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 (!BluetoothMasService.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(BluetoothMasService.ACCESS_REQUEST_ACTION)) {
+ showMapDialog(DIALOG_YES_NO_CONNECT);
+ mCurrentDialog = DIALOG_YES_NO_CONNECT;
+ } else if (action.equals(BluetoothMasService.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");
+ finish();
+ }
+ registerReceiver(mReceiver, new IntentFilter(
+ BluetoothMasService.USER_CONFIRM_TIMEOUT_ACTION));
+ }
+
+ private void showMapDialog(int id) {
+ }
+
+ private String getRemoteDeviceName() {
+ String remoteDeviceName = null;
+ Intent intent = getIntent();
+ if (intent.hasExtra(EXTRA_BLUETOOTH_DEVICE)) {
+ BluetoothDevice device = intent.getParcelableExtra(EXTRA_BLUETOOTH_DEVICE);
+ if (device != null) {
+ remoteDeviceName = device.getName();
+ }
+ }
+
+ return (remoteDeviceName != null) ? remoteDeviceName : getString(R.string.defaultname);
+ }
+
+ private String createDisplayText(final int id) {
+ String mRemoteName = getRemoteDeviceName();
+ return null;
+ }
+
+ private View createView(final int id) {
+ switch (id) {
+ case DIALOG_YES_NO_CONNECT:
+ //mView = getLayoutInflater().inflate(R.layout.access, null);
+ messageView = (TextView)mView.findViewById(R.id.message);
+ messageView.setText(createDisplayText(id));
+ //mAlwaysAllowed = (CheckBox)mView.findViewById(R.id.alwaysallowed);
+ mAlwaysAllowed.setChecked(true);
+ mAlwaysAllowed.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ mAlwaysAllowedValue = true;
+ } else {
+ mAlwaysAllowedValue = false;
+ }
+ }
+ });
+ return mView;
+ 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_CONNECT) {
+ sendIntentToReceiver(BluetoothMasService.ACCESS_ALLOWED_ACTION,
+ BluetoothMasService.EXTRA_ALWAYS_ALLOWED, mAlwaysAllowedValue);
+ } else if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
+ sendIntentToReceiver(BluetoothMasService.AUTH_RESPONSE_ACTION,
+ BluetoothMasService.EXTRA_SESSION_KEY, mSessionKey);
+ mKeyView.removeTextChangedListener(this);
+ }
+ }
+ mTimeout = false;
+ finish();
+ }
+
+ private void onNegative() {
+ if (mCurrentDialog == DIALOG_YES_NO_CONNECT) {
+ sendIntentToReceiver(BluetoothMasService.ACCESS_DISALLOWED_ACTION, null, null);
+ } else if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
+ sendIntentToReceiver(BluetoothMasService.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(BluetoothMasService.THIS_PACKAGE_NAME, BluetoothMasReceiver.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(BluetoothMasService.THIS_PACKAGE_NAME, BluetoothMasReceiver.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() {
+ // TODO - Implement appropriate timeout function
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ mTimeout = savedInstanceState.getBoolean(KEY_USER_TIMEOUT);
+ if (V) Log.e(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.e(TAG, "Received DISMISS_TIMEOUT_DIALOG msg.");
+ finish();
+ break;
+ default:
+ break;
+ }
+ }
+ };
+}
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMns.java b/src/org/codeaurora/bluetooth/map/BluetoothMns.java
new file mode 100644
index 0000000..41c794b
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMns.java
@@ -0,0 +1,784 @@
+/*
+ * Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.text.format.Time;
+import android.util.Log;
+import android.util.Pair;
+
+import org.codeaurora.bluetooth.map.IBluetoothMasApp.MessageNotificationListener;
+import org.codeaurora.bluetooth.map.IBluetoothMasApp.MnsRegister;
+import org.codeaurora.bluetooth.map.MapUtils.MapUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.obex.ObexTransport;
+
+import static org.codeaurora.bluetooth.map.BluetoothMasService.MAS_INS_INFO;
+import static org.codeaurora.bluetooth.map.BluetoothMasService.MAX_INSTANCES;
+import static org.codeaurora.bluetooth.map.IBluetoothMasApp.HANDLE_OFFSET;
+import static org.codeaurora.bluetooth.map.IBluetoothMasApp.MSG;
+import static org.codeaurora.bluetooth.map.IBluetoothMasApp.TELECOM;
+
+/**
+ * This class run an MNS session.
+ */
+public class BluetoothMns implements MessageNotificationListener {
+ private static final String TAG = "BtMns";
+
+ private static final boolean V = BluetoothMasService.VERBOSE;
+
+ public static final int RFCOMM_ERROR = 10;
+
+ public static final int RFCOMM_CONNECTED = 11;
+
+ public static final int MNS_CONNECT = 13;
+
+ public static final int MNS_DISCONNECT = 14;
+
+ public static final int MNS_SEND_EVENT = 15;
+
+ public static final int MNS_SEND_EVENT_DONE = 16;
+
+ public static final int MNS_SEND_TIMEOUT = 17;
+
+ public static final int MNS_BLUETOOTH_OFF = 18;
+
+ public static final int MNS_SEND_TIMEOUT_DURATION = 30000; // 30 secs
+
+ private static final short MNS_UUID16 = 0x1133;
+
+ public static final String NEW_MESSAGE = "NewMessage";
+
+ public static final String DELIVERY_SUCCESS = "DeliverySuccess";
+
+ public static final String SENDING_SUCCESS = "SendingSuccess";
+
+ public static final String DELIVERY_FAILURE = "DeliveryFailure";
+
+ public static final String SENDING_FAILURE = "SendingFailure";
+
+ public static final String MEMORY_FULL = "MemoryFull";
+
+ public static final String MEMORY_AVAILABLE = "MemoryAvailable";
+
+ public static final String MESSAGE_DELETED = "MessageDeleted";
+
+ public static final String MESSAGE_SHIFT = "MessageShift";
+
+ private Context mContext;
+
+ private BluetoothAdapter mAdapter;
+
+ private BluetoothMnsObexSession mSession;
+
+ private EventHandler mSessionHandler;
+
+ private List<MnsClient> mMnsClients = new ArrayList<MnsClient>();
+ public static final ParcelUuid BluetoothUuid_ObexMns = ParcelUuid
+ .fromString("00001133-0000-1000-8000-00805F9B34FB");
+
+ private HashSet<Integer> mWaitingMasId = new HashSet<Integer>();
+ private final Queue<Pair<Integer, String>> mEventQueue = new ConcurrentLinkedQueue<Pair<Integer, String>>();
+ private boolean mSendingEvent = false;
+
+
+ public BluetoothMns(Context context, boolean isEmailEnabled) {
+ /* check Bluetooth enable status */
+ /*
+ * normally it's impossible to reach here if BT is disabled. Just check
+ * for safety
+ */
+
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mContext = context;
+ int numOfSupportedInstances = MAX_INSTANCES;
+ Log.v(TAG, "BluetoothMns: isEmailEnabled: " + isEmailEnabled);
+ if(!isEmailEnabled) {
+ numOfSupportedInstances = 1; /*Email is not supported*/
+ }
+ for (int i = 0; i < numOfSupportedInstances; i ++) {
+ try {
+ // TODO: must be updated when Class<? extends MnsClient>'s constructor is changed
+ Constructor<? extends MnsClient> constructor;
+ constructor = MAS_INS_INFO[i].mMnsClientClass.getConstructor(Context.class,
+ Integer.class);
+ mMnsClients.add(constructor.newInstance(mContext, i));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "The " + MAS_INS_INFO[i].mMnsClientClass.getName()
+ + "'s constructor arguments mismatch", e);
+ } catch (InstantiationException e) {
+ Log.e(TAG, "The " + MAS_INS_INFO[i].mMnsClientClass.getName()
+ + " cannot be instantiated", e);
+ } catch (IllegalAccessException e) {
+ Log.e(TAG, "The " + MAS_INS_INFO[i].mMnsClientClass.getName()
+ + " cannot be instantiated", e);
+ } catch (InvocationTargetException e) {
+ Log.e(TAG, "Exception during " + MAS_INS_INFO[i].mMnsClientClass.getName()
+ + "'s constructor invocation", e);
+ } catch (SecurityException e) {
+ Log.e(TAG, MAS_INS_INFO[i].mMnsClientClass.getName()
+ + "'s constructor is not accessible", e);
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, MAS_INS_INFO[i].mMnsClientClass.getName()
+ + " has no matched constructor", e);
+ }
+ }
+
+ if (!mAdapter.isEnabled()) {
+ Log.e(TAG, "Can't send event when Bluetooth is disabled ");
+ return;
+ }
+
+ mSessionHandler = new EventHandler();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
+ filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+ mContext.registerReceiver(mStorageStatusReceiver, filter);
+ }
+
+ public Handler getHandler() {
+ return mSessionHandler;
+ }
+
+ /**
+ * Asserting masId
+ * @param masId
+ * @return true if MnsClient is created for masId; otherwise false.
+ */
+ private boolean assertMasid(final int masId) {
+ final int size = mMnsClients.size();
+ if (masId < 0 || masId >= size) {
+ Log.e(TAG, "MAS id: " + masId + " is out of maximum number of MAS instances: " + size);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean register(final int masId) {
+ if (!assertMasid(masId)) {
+ Log.e(TAG, "Attempt to register MAS id: " + masId);
+ return false;
+ }
+ final MnsClient client = mMnsClients.get(masId);
+ if (!client.isRegistered()) {
+ try {
+ client.register(BluetoothMns.this);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception occured while register MNS for MAS id: " + masId, e);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private synchronized boolean canDisconnect() {
+ for (MnsClient client : mMnsClients) {
+ if (client.isRegistered()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void deregister(final int masId) {
+ if (!assertMasid(masId)) {
+ Log.e(TAG, "Attempt to register MAS id: " + masId);
+ return;
+ }
+ final MnsClient client = mMnsClients.get(masId);
+ if (client.isRegistered()) {
+ client.register(null);
+ }
+ }
+
+ private void deregisterAll() {
+ for (MnsClient client : mMnsClients) {
+ if (client.isRegistered()) {
+ client.register(null);
+ }
+ }
+ }
+
+ private void mnsCleanupInstances() {
+ if (V) Log.v(TAG, "MNS_BT: entered mnsCleanupInstances");
+ if(mStorageStatusReceiver != null) {
+ mContext.unregisterReceiver(mStorageStatusReceiver);
+ mStorageStatusReceiver = null;
+ }
+ for (MnsClient client : mMnsClients) {
+ if (V) Log.v(TAG, "MNS_BT: mnsCleanupInstances: inside for loop");
+ if (client.isRegistered()) {
+ if (V) Log.v(TAG, "MNS_BT: mnsCleanupInstances: Attempt to deregister MnsClient");
+ client.register(null);
+ client = null;
+ if (V) Log.v(TAG, "MNS_BT: mnsCleanupInstances: made client = null");
+ }
+ }
+ if (V) Log.v(TAG, "Relase MNS lock during MNS close service");
+ if(mSession != null) {
+ mSession.releaseMnsLock();
+ }
+ }
+
+ /*
+ * Receives events from mConnectThread & mSession back in the main thread.
+ */
+ private class EventHandler extends Handler {
+ public EventHandler() {
+ super();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (V){
+ Log.v(TAG, " Handle Message " + msg.what);
+ }
+ switch (msg.what) {
+ case MNS_CONNECT:
+ {
+ final int masId = msg.arg1;
+ final BluetoothDevice device = (BluetoothDevice)msg.obj;
+ if (mSession != null) {
+ if (V) Log.v(TAG, "is MNS session connected? " + mSession.isConnected());
+ if (mSession.isConnected()) {
+ if (!register(masId)) {
+ // failed to register, disconnect
+ obtainMessage(MNS_DISCONNECT, masId, -1).sendToTarget();
+ }
+ break;
+ }
+ }
+ if (mWaitingMasId.isEmpty()) {
+ mWaitingMasId.add(masId);
+ mConnectThread = new SocketConnectThread(device);
+ mConnectThread.start();
+ } else {
+ mWaitingMasId.add(masId);
+ }
+ break;
+ }
+ case MNS_DISCONNECT:
+ {
+ final int masId = msg.arg1;
+ new Thread(new Runnable() {
+ public void run() {
+ deregister(masId);
+ if (canDisconnect()) {
+ stop();
+ }
+ }
+ }).start();
+ break;
+ }
+ case MNS_BLUETOOTH_OFF:
+ if (V) Log.v(TAG, "MNS_BT: receive MNS_BLUETOOTH_OFF msg");
+ new Thread(new Runnable() {
+ public void run() {
+ if (V) Log.v(TAG, "MNS_BT: Started Deregister Thread");
+ if (canDisconnect()) {
+ stop();
+ }
+ mnsCleanupInstances();
+ }
+ }).start();
+ break;
+ /*
+ * RFCOMM connect fail is for outbound share only! Mark batch
+ * failed, and all shares in batch failed
+ */
+ case RFCOMM_ERROR:
+ if (V) Log.v(TAG, "receive RFCOMM_ERROR msg");
+ deregisterAll();
+ if (canDisconnect()) {
+ stop();
+ }
+ break;
+ /*
+ * RFCOMM connected. Do an OBEX connect by starting the session
+ */
+ case RFCOMM_CONNECTED:
+ {
+ if (V) Log.v(TAG, "Transfer receive RFCOMM_CONNECTED msg");
+ ObexTransport transport = (ObexTransport) msg.obj;
+ try {
+ startObexSession(transport);
+ } catch (NullPointerException ne) {
+ sendEmptyMessage(RFCOMM_ERROR);
+ return;
+ }
+ for (int masId : mWaitingMasId) {
+ register(masId);
+ }
+ mWaitingMasId.clear();
+ break;
+ }
+ /* Handle the error state of an Obex session */
+ case BluetoothMnsObexSession.MSG_SESSION_ERROR:
+ if (V) Log.v(TAG, "receive MSG_SESSION_ERROR");
+ deregisterAll();
+ stop();
+ break;
+ case MNS_SEND_EVENT:
+ {
+ final String xml = (String)msg.obj;
+ final int masId = msg.arg1;
+ if (mSendingEvent) {
+ mEventQueue.add(new Pair<Integer, String>(masId, xml));
+ } else {
+ mSendingEvent = true;
+ new Thread(new SendEventTask(xml, masId)).start();
+ }
+ break;
+ }
+ case MNS_SEND_EVENT_DONE:
+ if (mEventQueue.isEmpty()) {
+ mSendingEvent = false;
+ } else {
+ final Pair<Integer, String> p = mEventQueue.remove();
+ final int masId = p.first;
+ final String xml = p.second;
+ new Thread(new SendEventTask(xml, masId)).start();
+ }
+ break;
+ case MNS_SEND_TIMEOUT:
+ {
+ if (V) Log.v(TAG, "MNS_SEND_TIMEOUT disconnecting.");
+ deregisterAll();
+ stop();
+ break;
+ }
+ }
+ }
+
+ private void setTimeout(int masId) {
+ if (V) Log.v(TAG, "setTimeout MNS_SEND_TIMEOUT for instance " + masId);
+ sendMessageDelayed(obtainMessage(MNS_SEND_TIMEOUT, masId, -1),
+ MNS_SEND_TIMEOUT_DURATION);
+ }
+
+ private void removeTimeout() {
+ if (hasMessages(MNS_SEND_TIMEOUT)) {
+ removeMessages(MNS_SEND_TIMEOUT);
+ sendEventDone();
+ }
+ }
+
+ private void sendEventDone() {
+ if (V) Log.v(TAG, "post MNS_SEND_EVENT_DONE");
+ obtainMessage(MNS_SEND_EVENT_DONE).sendToTarget();
+ }
+
+ class SendEventTask implements Runnable {
+ final String mXml;
+ final int mMasId;
+ SendEventTask (String xml, int masId) {
+ mXml = xml;
+ mMasId = masId;
+ }
+
+ public void run() {
+ if (V) Log.v(TAG, "MNS_SEND_EVENT started");
+ setTimeout(mMasId);
+ sendEvent(mXml, mMasId);
+ removeTimeout();
+ if (V) Log.v(TAG, "MNS_SEND_EVENT finished");
+ }
+ }
+ }
+
+ /*
+ * Class to hold message handle for MCE Initiated operation
+ */
+ public class BluetoothMnsMsgHndlMceInitOp {
+ public String msgHandle;
+ Time time;
+ }
+
+ /*
+ * Keep track of Message Handles on which the operation was
+ * initiated by MCE
+ */
+ List<BluetoothMnsMsgHndlMceInitOp> opList = new ArrayList<BluetoothMnsMsgHndlMceInitOp>();
+
+ /*
+ * Adds the Message Handle to the list for tracking
+ * MCE initiated operation
+ */
+ public void addMceInitiatedOperation(String msgHandle) {
+ BluetoothMnsMsgHndlMceInitOp op = new BluetoothMnsMsgHndlMceInitOp();
+ op.msgHandle = msgHandle;
+ op.time = new Time();
+ op.time.setToNow();
+ opList.add(op);
+ }
+ /*
+ * Removes the Message Handle from the list for tracking
+ * MCE initiated operation
+ */
+ public void removeMceInitiatedOperation(int location) {
+ opList.remove(location);
+ }
+
+ /*
+ * Finds the location in the list of the given msgHandle, if
+ * available. "+" indicates the next (any) operation
+ */
+ public int findLocationMceInitiatedOperation( String msgHandle) {
+ int location = -1;
+
+ Time currentTime = new Time();
+ currentTime.setToNow();
+
+ if (V) Log.v(TAG, "findLocationMceInitiatedOperation " + msgHandle);
+
+ List<BluetoothMnsMsgHndlMceInitOp> staleOpList = new ArrayList<BluetoothMnsMsgHndlMceInitOp>();
+ for (BluetoothMnsMsgHndlMceInitOp op: opList) {
+ if (currentTime.toMillis(false) - op.time.toMillis(false) > 10000) {
+ // add stale entries
+ staleOpList.add(op);
+ }
+ }
+ if (!staleOpList.isEmpty()) {
+ for (BluetoothMnsMsgHndlMceInitOp op: staleOpList) {
+ // Remove stale entries
+ opList.remove(op);
+ }
+ }
+
+ for (BluetoothMnsMsgHndlMceInitOp op: opList) {
+ if (op.msgHandle.equalsIgnoreCase(msgHandle)){
+ location = opList.indexOf(op);
+ break;
+ }
+ }
+
+ if (location == -1) {
+ for (BluetoothMnsMsgHndlMceInitOp op: opList) {
+ if (op.msgHandle.equalsIgnoreCase("+")) {
+ location = opList.indexOf(op);
+ break;
+ }
+ }
+ }
+ if (V) Log.v(TAG, "findLocationMce loc" + location);
+ return location;
+ }
+
+
+ /**
+ * Post a MNS Event to the MNS thread
+ */
+ public void sendMnsEvent(int masId, String msg, String handle, String folder,
+ String old_folder, String msgType) {
+ if (V) {
+ Log.v(TAG, "sendMnsEvent()");
+ Log.v(TAG, "msg: " + msg);
+ Log.v(TAG, "handle: " + handle);
+ Log.v(TAG, "folder: " + folder);
+ Log.v(TAG, "old_folder: " + old_folder);
+ Log.v(TAG, "msgType: " + msgType);
+ }
+ int location = -1;
+
+ /* Send the notification, only if it was not initiated
+ * by MCE. MEMORY_FULL and MEMORY_AVAILABLE cannot be
+ * MCE initiated
+ */
+ if (msg.equals(MEMORY_AVAILABLE) || msg.equals(MEMORY_FULL)) {
+ location = -1;
+ } else {
+ /* Consider SENDING_SUCESS as non MCE Initiated operation and remove
+ * message handle from MCE Initiated OpList when MmsContentObserver
+ * is not triggered for both OUTBOX and SENT folder even though
+ * message is pushed sucessfully.
+ */
+ location = findLocationMceInitiatedOperation(handle);
+ if (location != -1 && msg.equals(SENDING_SUCCESS)) {
+ if (V) Log.v(TAG, "Handle Pending MCE Initiated list " + location);
+ removeMceInitiatedOperation(location);
+ location = -1;
+ }
+ }
+
+ if (location == -1) {
+ String str = MapUtils.mapEventReportXML(msg, handle, folder, old_folder, msgType);
+ if (V) Log.v(TAG, "Notification to MAS " + masId + ", msgType = " + msgType);
+ mSessionHandler.obtainMessage(MNS_SEND_EVENT, masId, -1, str).sendToTarget();
+ } else {
+ if (V) Log.v(TAG, "REMOVE location" + location);
+ removeMceInitiatedOperation(location);
+ }
+ }
+
+ /**
+ * Push the message over Obex client session
+ */
+ private void sendEvent(String str, int masId) {
+ if (str != null && (str.length() > 0)) {
+ if (V){
+ Log.v(TAG, "--------------");
+ Log.v(TAG, " CONTENT OF EVENT REPORT FILE: " + str);
+ }
+
+ final String FILENAME = "EventReport" + masId;
+ FileOutputStream fos = null;
+ File file = new File(mContext.getFilesDir() + "/" + FILENAME);
+ file.delete();
+ try {
+ fos = mContext.openFileOutput(FILENAME, Context.MODE_PRIVATE);
+ fos.write(str.getBytes());
+ fos.flush();
+ fos.close();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ File fileR = new File(mContext.getFilesDir() + "/" + FILENAME);
+ if (fileR.exists() == true) {
+ if (V) {
+ Log.v(TAG, " Sending event report file for Mas " + masId);
+ }
+ try {
+ if (mSession != null) {
+ mSession.sendEvent(fileR, (byte) masId);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else {
+ if (V){
+ Log.v(TAG, " ERROR IN CREATING SEND EVENT OBJ FILE");
+ }
+ }
+ } else if (V) {
+ Log.v(TAG, "sendEvent(null, " + masId + ")");
+ }
+ }
+
+ private BroadcastReceiver mStorageStatusReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent != null && mSession != null) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
+ Log.d(TAG, " Memory Full ");
+ sendMnsEventMemory(MEMORY_FULL);
+ } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
+ Log.d(TAG, " Memory Available ");
+ sendMnsEventMemory(MEMORY_AVAILABLE);
+ }
+ }
+ }
+ };
+
+ /**
+ * Stop the transfer
+ */
+ public synchronized void stop() {
+ if (V) Log.v(TAG, "stop");
+ if (mSession != null) {
+ if (V) Log.v(TAG, "Stop mSession");
+ mSession.disconnect();
+ mSession = null;
+ }
+ }
+
+ /**
+ * Connect the MNS Obex client to remote server
+ */
+ private void startObexSession(ObexTransport transport) throws NullPointerException {
+ if (V) Log.v(TAG, "Create Client session with transport " + transport.toString());
+ mSession = new BluetoothMnsObexSession(mContext, transport);
+ mSession.connect();
+ }
+
+ private SocketConnectThread mConnectThread;
+ /**
+ * This thread is used to establish rfcomm connection to
+ * remote device
+ */
+ private class SocketConnectThread extends Thread {
+ private final BluetoothDevice device;
+
+ private long timestamp;
+
+ /* create a Rfcomm Socket */
+ public SocketConnectThread(BluetoothDevice device) {
+ super("Socket Connect Thread");
+ this.device = device;
+ }
+
+ public void interrupt() {
+ }
+
+ @Override
+ public void run() {
+ timestamp = System.currentTimeMillis();
+
+ BluetoothSocket btSocket = null;
+ try {
+ btSocket = device.createInsecureRfcommSocketToServiceRecord(
+ BluetoothUuid_ObexMns.getUuid());
+ btSocket.connect();
+ } catch (IOException e) {
+ Log.e(TAG, "BtSocket Connect error " + e.getMessage(), e);
+ markConnectionFailed(btSocket);
+ return;
+ }
+
+ if (V) Log.v(TAG, "Rfcomm socket connection attempt took "
+ + (System.currentTimeMillis() - timestamp) + " ms");
+ ObexTransport transport;
+ transport = new BluetoothMnsRfcommTransport(btSocket);
+ if (V) Log.v(TAG, "Send transport message " + transport.toString());
+
+ mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
+ }
+
+ /**
+ * RFCOMM connection failed
+ */
+ private void markConnectionFailed(BluetoothSocket s) {
+ try {
+ if (s != null) {
+ s.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error when close socket");
+ }
+ mSessionHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
+ return;
+ }
+ }
+
+ public void sendMnsEventMemory(String msg) {
+ // Sending "MemoryFull" or "MemoryAvailable" to all registered Mas Instances
+ for (MnsClient client : mMnsClients) {
+ if (client.isRegistered()) {
+ sendMnsEvent(client.getMasId(), msg, null, null, null, null);
+ }
+ }
+ }
+
+ public void onDeliveryFailure(int masId, String handle, String folder, String msgType) {
+ sendMnsEvent(masId, DELIVERY_FAILURE, handle, folder, null, msgType);
+ }
+
+ public void onDeliverySuccess(int masId, String handle, String folder, String msgType) {
+ sendMnsEvent(masId, DELIVERY_SUCCESS, handle, folder, null, msgType);
+ }
+
+ public void onMessageShift(int masId, String handle, String toFolder,
+ String fromFolder, String msgType) {
+ sendMnsEvent(masId, MESSAGE_SHIFT, handle, toFolder, fromFolder, msgType);
+ }
+
+ public void onNewMessage(int masId, String handle, String folder, String msgType) {
+ sendMnsEvent(masId, NEW_MESSAGE, handle, folder, null, msgType);
+ }
+
+ public void onSendingFailure(int masId, String handle, String folder, String msgType) {
+ sendMnsEvent(masId, SENDING_FAILURE, handle, folder, null, msgType);
+ }
+
+ public void onSendingSuccess(int masId, String handle, String folder, String msgType) {
+ sendMnsEvent(masId, SENDING_SUCCESS, handle, folder, null, msgType);
+ }
+
+ public void onMessageDeleted(int masId, String handle, String folder, String msgType) {
+ sendMnsEvent(masId, MESSAGE_DELETED, handle, folder, null, msgType);
+ }
+
+ public static abstract class MnsClient implements MnsRegister {
+ public static final String TAG = "MnsClient";
+ public static final boolean V = BluetoothMasService.VERBOSE;
+ protected static final String PRE_PATH = TELECOM + "/" + MSG + "/";
+
+ protected Context mContext;
+ protected MessageNotificationListener mListener = null;
+ protected int mMasId;
+
+ protected final long OFFSET_START;
+ protected final long OFFSET_END;
+
+ public MnsClient(Context context, int masId) {
+ mContext = context;
+ mMasId = masId;
+ OFFSET_START = HANDLE_OFFSET[masId];
+ OFFSET_END = HANDLE_OFFSET[masId + 1] - 1;
+ }
+
+ public synchronized void register(MessageNotificationListener listener) {
+ if (V) Log.v(TAG, "MNS_BT: register entered");
+ if (listener != null) {
+ mListener = listener;
+ registerContentObserver();
+ } else {
+ if (V) Log.v(TAG, "MNS_BT: register(null)");
+ unregisterContentObserver();
+ mListener = null;
+ }
+ }
+
+ public boolean isRegistered() {
+ return mListener != null;
+ }
+
+ public int getMasId() {
+ return mMasId;
+ }
+
+ protected abstract void registerContentObserver();
+ protected abstract void unregisterContentObserver();
+ }
+}
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMnsEmail.java b/src/org/codeaurora/bluetooth/map/BluetoothMnsEmail.java
new file mode 100644
index 0000000..c348685
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMnsEmail.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (c) 2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.os.Handler;
+import android.util.Log;
+
+import org.codeaurora.bluetooth.map.BluetoothMns.MnsClient;
+import org.codeaurora.bluetooth.map.MapUtils.EmailUtils;
+
+import java.util.Collection;
+import java.util.HashMap;
+
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.EMAIL_BOX_COLUMN_ACCOUNT_KEY;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.EMAIL_BOX_COLUMN_DISPLAY_NAME;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.EMAIL_BOX_COLUMN_RECORD_ID;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.EMAIL_BOX_COLUMN_TYPE;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.EMAIL_BOX_PROJECTION;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.EMAIL_BOX_URI;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.EMAIL_MESSAGE_PROJECTION;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.EMAIL_MESSAGE_URI;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.EMAIL_URI;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.MSG_COL_ACCOUNT_KEY;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.MSG_COL_MAILBOX_KEY;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.MSG_COL_RECORD_ID;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.TYPE_DELETED;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.TYPE_DRAFT;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.TYPE_INBOX;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.TYPE_OUTBOX;
+import static org.codeaurora.bluetooth.map.MapUtils.EmailUtils.TYPE_SENT;
+
+/**
+ * This class run an MNS session.
+ */
+public class BluetoothMnsEmail extends MnsClient {
+ private static final String TAG = "BluetoothMnsEmail";
+ private static final boolean V = BluetoothMasService.VERBOSE;
+ private static final String EMAIL_TO_MAP[] = {
+ "inbox", // TYPE_INBOX = 0;
+ "", // TYPE_MAIL = 1;
+ "", // TYPE_PARENT = 2;
+ "draft", // TYPE_DRAFTS = 3;
+ "outbox", // TYPE_OUTBOX = 4;
+ "sent", // TYPE_SENT = 5;
+ "deleted", // TYPE_TRASH = 6;
+ "" // TYPE_JUNK = 7;
+ };
+ private static final String EMAIL = "EMAIL";
+ private EmailContentObserver mEmailObserver = new EmailContentObserver();
+ private long mAccountKey;
+
+ public BluetoothMnsEmail(Context context, Integer masId) {
+ super(context, masId);
+ }
+
+ @Override
+ protected void registerContentObserver() {
+ if (V) Log.v(TAG, "REGISTERING EMAIL MNS");
+ mAccountKey = EmailUtils.getAccountId(mMasId);
+ mEmailObserver.updateEmailBox();
+ mEmailObserver.update(true);
+ mContext.getContentResolver().registerContentObserver(EMAIL_URI, true, mEmailObserver);
+ if (V) Log.v(TAG, "REGISTERING EMAIL MNS DONE");
+ }
+
+ @Override
+ protected void unregisterContentObserver() {
+ if (V) Log.v(TAG, "UNREGISTERING MNS EMAIL");
+ mContext.getContentResolver().unregisterContentObserver(mEmailObserver);
+ if (V) Log.v(TAG, "UNREGISTERED MNS EMAIL");
+ }
+
+ static class EmailBox {
+ long mId;
+ String mDisplayName;
+ long mAccountKey;
+ int mType;
+
+ public EmailBox(long id, String displayName, long accountKey, int type) {
+ mId = id;
+ mDisplayName = displayName;
+ mAccountKey = accountKey;
+ mType = type;
+ }
+
+ @Override
+ public String toString() {
+ return "[id:" + mId + ", display name:" + mDisplayName + ", account key:" +
+ mAccountKey + ", type:" + mType + "]";
+ }
+ }
+
+ static class EmailMessage {
+ long mId;
+ long mAccountKey;
+ String mFolderName;
+ int mType;
+
+ public EmailMessage(long id, long accountKey, String folderName, int type) {
+ mId = id;
+ mAccountKey = accountKey;
+ mFolderName = folderName;
+ mType = type;
+ }
+
+ @Override
+ public String toString() {
+ return "[id:" + mId + ", folder name:" + mFolderName + ", account key:" + mAccountKey +
+ ", type:" + mType + "]";
+ }
+ }
+
+ private class EmailContentObserver extends ContentObserver {
+ private static final String TAG = "EmailContentObserver";
+ private HashMap<Long, EmailBox> mEmailBoxList = new HashMap<Long, EmailBox>();
+ private HashMap<Long, EmailMessage> mEmailList = new HashMap<Long, EmailMessage>();
+ /** List of deleted message, do not notify */
+ private HashMap<Long, EmailMessage> mDeletedList = new HashMap<Long, EmailMessage>();
+ private HashMap<Long, EmailMessage> mEmailAddedList = new HashMap<Long, EmailMessage>();
+ /** List of newly deleted message, notify */
+ private HashMap<Long, EmailMessage> mEmailDeletedList = new HashMap<Long, EmailMessage>();
+
+ private static final int UPDATE = 0;
+ private static final int THRESHOLD = 3000; // 3 sec
+
+ public EmailContentObserver() {
+ super(null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ if (V) Log.v(TAG, "onChange(" + selfChange + ")");
+ if (mHandler.hasMessages(UPDATE)) {
+ mHandler.removeMessages(UPDATE);
+ }
+ mHandler.sendEmptyMessageDelayed(UPDATE, THRESHOLD);
+ }
+
+ private Handler mHandler = new Handler() {
+ private static final String TAG = "EmailContentObserver.Hanlder";
+ @Override
+ public void handleMessage(android.os.Message msg) {
+ if (V) Log.v(TAG, "handleMessage(" + msg.what + ") mas Id: " + mMasId);
+ switch (msg.what) {
+ case UPDATE:
+ new Thread(new Runnable() {
+ public void run() {
+ updateEmailBox();
+ update(false);
+ sendEvents();
+ }
+ }, "Email Content Observer Thread").start();
+ break;
+ }
+ }
+ };
+
+ void updateEmailBox() {
+ mEmailBoxList.clear();
+ final ContentResolver resolver = mContext.getContentResolver();
+ Cursor crBox = resolver.query(EMAIL_BOX_URI, EMAIL_BOX_PROJECTION, null, null, null);
+ if (crBox != null) {
+ if (crBox.moveToFirst()) {
+ do {
+ final long id = crBox.getLong(EMAIL_BOX_COLUMN_RECORD_ID);
+ final String displayName = crBox.getString(EMAIL_BOX_COLUMN_DISPLAY_NAME);
+ final long accountKey = crBox.getLong(EMAIL_BOX_COLUMN_ACCOUNT_KEY);
+ final int type = crBox.getInt(EMAIL_BOX_COLUMN_TYPE);
+ final EmailBox box = new EmailBox(id, displayName, accountKey, type);
+ mEmailBoxList.put(id, box);
+ if (V) Log.v(TAG, box.toString());
+ } while (crBox.moveToNext());
+ }
+ crBox.close();
+ }
+ }
+
+ void update(boolean init) {
+ if (init) {
+ clear();
+ }
+ final ContentResolver resolver = mContext.getContentResolver();
+ Cursor crEmail = resolver.query(EMAIL_MESSAGE_URI, EMAIL_MESSAGE_PROJECTION,
+ null, null, null);
+ if (crEmail != null) {
+ if (crEmail.moveToFirst()) {
+ final HashMap<Long, EmailBox> boxList = mEmailBoxList;
+ HashMap<Long, EmailMessage> oldEmailList = mEmailList;
+ HashMap<Long, EmailMessage> emailList = new HashMap<Long, EmailMessage>();
+ do {
+ final long accountKey = crEmail.getLong(MSG_COL_ACCOUNT_KEY);
+ if (accountKey != mAccountKey) {
+ continue;
+ }
+ final long id = crEmail.getLong(MSG_COL_RECORD_ID);
+ final long mailboxKey = crEmail.getLong(MSG_COL_MAILBOX_KEY);
+ if (boxList.containsKey(mailboxKey)) {
+ final EmailBox box = boxList.get(mailboxKey);
+ if (box == null) {
+ continue;
+ }
+ final String folderName = isMapFolder(box.mType)
+ ? EMAIL_TO_MAP[box.mType] : box.mDisplayName;
+ final EmailMessage msg = new EmailMessage(id, accountKey,
+ folderName, box.mType);
+ if (box.mType == EmailUtils.TYPE_DELETED) {
+ if (init) {
+ mDeletedList.put(id, msg);
+ } else if (!mDeletedList.containsKey(id) &&
+ !mEmailDeletedList.containsKey(id)) {
+ mEmailDeletedList.put(id, msg);
+ }
+ } else {
+ emailList.put(id, msg);
+ if (!oldEmailList.containsKey(id) && !init &&
+ !mEmailAddedList.containsKey(id)) {
+ mEmailAddedList.put(id, msg);
+ }
+ }
+ } else {
+ Log.e(TAG, "Mailbox is not updated");
+ }
+ } while (crEmail.moveToNext());
+ mEmailList = emailList;
+ }
+ crEmail.close();
+ }
+ }
+
+ private void sendEvents() {
+ if (mEmailAddedList.size() > 0) {
+ newEmail();
+ mEmailAddedList.clear();
+ }
+ if (mEmailDeletedList.size() > 0) {
+ mDeletedList.putAll(mEmailDeletedList);
+ deletedEmail();
+ mEmailDeletedList.clear();
+ }
+ }
+
+ private void clear() {
+ mEmailList.clear();
+ mDeletedList.clear();
+ mEmailAddedList.clear();
+ mEmailDeletedList.clear();
+ }
+
+ private boolean isMapFolder(int type) {
+ if (type == TYPE_INBOX || type == TYPE_OUTBOX || type == TYPE_SENT ||
+ type == TYPE_DRAFT || type == TYPE_DELETED) {
+ return true;
+ }
+ return false;
+ }
+
+ private void newEmail() {
+ if (V) Log.v(TAG, "newEmail()");
+ if (mListener != null) {
+ Collection<EmailMessage> values = mEmailAddedList.values();
+ for (EmailMessage email : values) {
+ if (V) Log.v(TAG, email.toString());
+ mListener.onNewMessage(mMasId, String.valueOf(email.mId + OFFSET_START),
+ PRE_PATH + email.mFolderName, EMAIL);
+ if (email.mType == TYPE_SENT) {
+ mListener.onSendingSuccess(mMasId, String.valueOf(email.mId + OFFSET_START),
+ PRE_PATH + email.mFolderName, EMAIL);
+ }
+ }
+ }
+ }
+
+ private void deletedEmail() {
+ if (V) Log.v(TAG, "deletedEmail()");
+ if (mListener != null) {
+ Collection<EmailMessage> values = mEmailDeletedList.values();
+ for (EmailMessage email : values) {
+ if (V) Log.v(TAG, email.toString());
+ mListener.onMessageDeleted(mMasId, String.valueOf(email.mId + OFFSET_START),
+ PRE_PATH + email.mFolderName, EMAIL);
+ }
+ }
+ }
+ };
+}
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMnsObexSession.java b/src/org/codeaurora/bluetooth/map/BluetoothMnsObexSession.java
new file mode 100644
index 0000000..6430ecc
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMnsObexSession.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) 2008-2009, Motorola, Inc.
+ * Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+import android.os.PowerManager;
+
+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;
+
+/**
+ * This class runs as an OBEX client
+ */
+
+// TBD - Do applications need to do anything for power management?
+
+public class BluetoothMnsObexSession {
+
+ private static final String TAG = "BtMns ObexClient";
+ private static final boolean D = BluetoothMasService.DEBUG;
+ private static final boolean V = BluetoothMasService.VERBOSE;
+
+ public final static int MSG_SESSION_ERROR = 1;
+ public final static int MSG_CONNECT_TIMEOUT = 2;
+
+ private ObexTransport mTransport;
+
+ private Context mContext;
+
+ private volatile boolean mWaitingForRemote;
+
+ private PowerManager.WakeLock mWakeLock = null;
+
+ private static final String TYPE_EVENT = "x-bt/MAP-event-report";
+
+ public BluetoothMnsObexSession(Context context, ObexTransport transport) {
+ if (transport == null) {
+ throw new NullPointerException("transport is null");
+ }
+ mContext = context;
+ mTransport = transport;
+ }
+
+ private ClientSession mCs;
+
+ private boolean mConnected = false;
+
+ public boolean isConnected() {
+ return mConnected;
+ }
+
+ public void disconnect() {
+ if(D) Log.d(TAG, "BluetoothMnsObexSession: disconnect");
+ acquireMnsLock();
+ try {
+ if (mCs != null) {
+ mCs.disconnect(null);
+ }
+ mCs = null;
+ if (D) Log.d(TAG, "OBEX session disconnected");
+ } catch (IOException e) {
+ Log.w(TAG, "OBEX session disconnect error " + e.getMessage());
+ }
+ try {
+ if (mCs != null) {
+ if (D) Log.d(TAG, "OBEX session close mCs");
+ mCs.close();
+ if (D) Log.d(TAG, "OBEX session closed");
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "OBEX session close error" + e.getMessage());
+ }
+ try {
+ if (mTransport != null) {
+ mTransport.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "mTransport.close error " + e.getMessage());
+ }
+ if(D) Log.d(TAG, "BluetoothMnsObexSession: exiting from disconnect");
+ releaseMnsLock();
+ }
+
+ private HeaderSet hsConnect = null;
+
+ public void connect() {
+ if(D) Log.d(TAG, "BluetoothMnsObexSession: Create ClientSession with transport " + mTransport.toString());
+ acquireMnsLock();
+ try {
+ mCs = new ClientSession(mTransport);
+ mConnected = true;
+ } catch (IOException e1) {
+ Log.e(TAG, "OBEX session create error " + e1.getMessage());
+ }
+ if (mConnected && mCs != 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 = mCs.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;
+ }
+ if(D) Log.d(TAG, "BluetoothMnsObexSession: exiting from connect");
+ releaseMnsLock();
+ }
+
+ public int sendEvent(File file, byte masInstanceId) {
+ Log.d(TAG, "BluetoothMnsObexSession: sendEvent");
+ acquireMnsLock();
+ boolean error = false;
+ int responseCode = -1;
+ HeaderSet request;
+ byte[] val = new byte[1];
+ val[0] = masInstanceId;
+ request = new HeaderSet();
+ ApplicationParameter ap = new ApplicationParameter();
+ ap.addAPPHeader((byte)BluetoothMasSpecParams.MAS_TAG_MAS_INSTANCE_ID,
+ (byte)BluetoothMasSpecParams.MAS_TAG_MAS_INSTANCE_ID_LEN,
+ val);
+ request.setHeader(HeaderSet.TYPE, TYPE_EVENT);
+ request.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
+
+ request.mConnectionID = new byte[4];
+ System.arraycopy(hsConnect.mConnectionID, 0, request.mConnectionID, 0, 4);
+
+ ClientOperation putOperation = null;
+ OutputStream outputStream = null;
+ FileInputStream fileInputStream = null;
+ try {
+ synchronized (this) {
+ mWaitingForRemote = true;
+ }
+ // Send the header first and then the body
+ try {
+ if (V) Log.v(TAG, "Send headerset Event ");
+ putOperation = (ClientOperation)mCs.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) {
+ int position = 0;
+ int readLength = 0;
+ long timestamp = 0;
+ int outputBufferSize = putOperation.getMaxPacketSize();
+ byte[] buffer = new byte[outputBufferSize];
+
+ fileInputStream = new FileInputStream(file);
+ BufferedInputStream a = new BufferedInputStream(fileInputStream, 0x4000);
+
+ while ((position != file.length())) {
+ if (V) timestamp = System.currentTimeMillis();
+
+ readLength = a.read(buffer, 0, outputBufferSize);
+ outputStream.write(buffer, 0, readLength);
+
+ position += readLength;
+ if (V) {
+ Log.v(TAG, "Sending file position = " + position
+ + " readLength " + readLength + " bytes took "
+ + (System.currentTimeMillis() - timestamp) + " ms");
+ }
+ }
+ if (position == file.length()) {
+ Log.i(TAG, "SendFile finished send out file " + file.length()
+ + " length " + file.length());
+ outputStream.close();
+ } else {
+ error = true;
+ // TBD - Is Output stream close needed here
+ putOperation.abort();
+ Log.i(TAG, "SendFile interrupted when send out file "
+ + " at " + position + " of " + file.length());
+ }
+ }
+ } catch (IOException e) {
+ handleSendException(e.toString());
+ error = true;
+ } catch (IndexOutOfBoundsException e) {
+ handleSendException(e.toString());
+ error = true;
+ } finally {
+ if (fileInputStream != null) {
+ try {
+ fileInputStream.close();
+ } catch (IOException ei) {
+ Log.e(TAG, "Error while closing stream"+ ei.toString());
+ }
+ }
+ try {
+ if (!error) {
+ responseCode = putOperation.getResponseCode();
+ if (responseCode != -1) {
+ if (V) Log.v(TAG, "Get 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());
+ }
+ }
+ if(D) Log.d(TAG, "BluetoothMnsObexSession: exiting from sendEvent");
+ releaseMnsLock();
+ return responseCode;
+ }
+
+ private void handleSendException(String exception) {
+ Log.e(TAG, "Error when sending event: " + exception);
+ }
+
+ private void acquireMnsLock() {
+ if (V) Log.v(TAG, "About to acquire Mns:mWakeLock");
+ if (mWakeLock == null) {
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MnsPartialWakeLock");
+ mWakeLock.setReferenceCounted(false);
+ mWakeLock.acquire();
+ if (V) Log.v(TAG, "Mns:mWakeLock acquired");
+ }
+ else {
+ Log.e(TAG, "Mns:mWakeLock already acquired");
+ }
+ }
+
+ public void releaseMnsLock() {
+ if (V) Log.v(TAG, "About to release Mns:mWakeLock");
+ if (mWakeLock != null) {
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ if (V) Log.v(TAG, "Mns:mWakeLock released");
+ } else {
+ if (V) Log.v(TAG, "Mns:mWakeLock already released");
+ }
+ mWakeLock = null;
+ }
+ }
+}
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMnsPreference.java b/src/org/codeaurora/bluetooth/map/BluetoothMnsPreference.java
new file mode 100644
index 0000000..4d4d673
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMnsPreference.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2008-2009, Motorola, Inc.
+ * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * This class cache Bluetooth device name and channel locally. Its a temp
+ * solution which should be replaced by bluetooth_devices in SettingsProvider
+ */
+public class BluetoothMnsPreference {
+
+ private static final String TAG = "BluetoothMnsPreference";
+ private static final boolean D = BluetoothMasService.DEBUG;
+ private static final boolean V = BluetoothMasService.VERBOSE;
+
+ private static BluetoothMnsPreference INSTANCE;
+
+ /* Used when obtaining a reference to the singleton instance. */
+ private static Object INSTANCE_LOCK = new Object();
+
+ private boolean mInitialized;
+
+ private Context mContext;
+
+ private SharedPreferences mNamePreference;
+
+ private SharedPreferences mChannelPreference;
+
+ private HashMap<String, Integer> mChannels = new HashMap<String, Integer>();
+
+ private HashMap<String, String> mNames = new HashMap<String, String>();
+
+ public static final String BLUETOOTHMNS_NAME_PREFERENCE = "btmns_names";
+
+ public static final String BLUETOOTHMNS_CHANNEL_PREFERENCE = "btmns_channels";
+
+
+ public static BluetoothMnsPreference getInstance(Context context) {
+ synchronized (INSTANCE_LOCK) {
+ if (INSTANCE == null) {
+ INSTANCE = new BluetoothMnsPreference();
+ }
+ if (!INSTANCE.init(context)) {
+ return null;
+ }
+ return INSTANCE;
+ }
+ }
+
+ private boolean init(Context context) {
+ if (mInitialized)
+ return true;
+ mInitialized = true;
+
+ mContext = context;
+
+ mNamePreference = mContext.getSharedPreferences(
+ BLUETOOTHMNS_NAME_PREFERENCE, Context.MODE_PRIVATE);
+ mChannelPreference = mContext.getSharedPreferences(
+ BLUETOOTHMNS_CHANNEL_PREFERENCE, Context.MODE_PRIVATE);
+
+ mNames = (HashMap<String, String>) mNamePreference.getAll();
+ mChannels = (HashMap<String, Integer>) mChannelPreference.getAll();
+
+ return true;
+ }
+
+ private String getChannelKey(BluetoothDevice remoteDevice, int uuid) {
+ return remoteDevice.getAddress() + "_" + Integer.toHexString(uuid);
+ }
+
+ public String getName(BluetoothDevice remoteDevice) {
+ if (remoteDevice.getAddress().equals("FF:FF:FF:00:00:00")) {
+ return "localhost";
+ }
+ if (!mNames.isEmpty()) {
+ String name = mNames.get(remoteDevice.getAddress());
+ if (name != null) {
+ return name;
+ }
+ }
+ return null;
+ }
+
+ public int getChannel(BluetoothDevice remoteDevice, int uuid) {
+ String key = getChannelKey(remoteDevice, uuid);
+ if (V) Log.v(TAG, "getChannel " + key);
+ Integer channel = null;
+ if (mChannels != null) {
+ channel = mChannels.get(key);
+ if (V) Log.v(TAG, "getChannel for " + remoteDevice + "_"
+ + Integer.toHexString(uuid) + " as " + channel);
+ }
+ return (channel != null) ? channel : -1;
+ }
+
+ public void setName(BluetoothDevice remoteDevice, String name) {
+ if (V) Log.v(TAG, "Setname for " + remoteDevice + " to " + name);
+ if (!name.equals(getName(remoteDevice))) {
+ Editor ed = mNamePreference.edit();
+ ed.putString(remoteDevice.getAddress(), name);
+ ed.commit();
+ mNames.put(remoteDevice.getAddress(), name);
+ }
+ }
+
+ public void setChannel(BluetoothDevice remoteDevice, int uuid, int channel) {
+ if (V) Log.v(TAG, "Setchannel for " + remoteDevice + "_"
+ + Integer.toHexString(uuid) + " to " + channel);
+ if (channel != getChannel(remoteDevice, uuid)) {
+ String key = getChannelKey(remoteDevice, uuid);
+ Editor ed = mChannelPreference.edit();
+ ed.putInt(key, channel);
+ ed.commit();
+ mChannels.put(key, channel);
+ }
+ }
+
+ public void removeChannel(BluetoothDevice remoteDevice, int uuid) {
+ String key = getChannelKey(remoteDevice, uuid);
+ Editor ed = mChannelPreference.edit();
+ ed.remove(key);
+ ed.commit();
+ mChannels.remove(key);
+ }
+
+ public void dump() {
+ Log.d(TAG, "Dumping Names: ");
+ Log.d(TAG, mNames.toString());
+ Log.d(TAG, "Dumping Channels: ");
+ Log.d(TAG, mChannels.toString());
+ }
+}
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMnsRfcommTransport.java b/src/org/codeaurora/bluetooth/map/BluetoothMnsRfcommTransport.java
new file mode 100644
index 0000000..3a3cfd9
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMnsRfcommTransport.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.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();
+ }
+
+}
diff --git a/src/org/codeaurora/bluetooth/map/BluetoothMnsSmsMms.java b/src/org/codeaurora/bluetooth/map/BluetoothMnsSmsMms.java
new file mode 100644
index 0000000..b7a062a
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/BluetoothMnsSmsMms.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.os.Handler;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.Sms;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import org.codeaurora.bluetooth.map.BluetoothMns.MnsClient;
+import com.google.android.mms.pdu.PduHeaders;
+
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.DELETED;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.OUTBOX;
+import static org.codeaurora.bluetooth.map.MapUtils.SmsMmsUtils.SENT;
+
+/**
+ * This class run an MNS session.
+ */
+public class BluetoothMnsSmsMms extends MnsClient {
+ private static final String TAG = "BluetoothMnsSmsMms";
+ private static final boolean V = BluetoothMasService.VERBOSE;
+ private static final String MSG_TO_MAP[] = {
+ "", // ALL
+ "inbox", // INBOX
+ "sent", // SENT
+ "draft", // DRAFT
+ "outbox", // OUTBOX
+ "outbox", // FAILED
+ "outbox", // QUEUED
+ "inbox", // INBOX_SUB1
+ "inbox", // INBOX_SUB2
+ };
+ private static final String SMS_GSM = "SMS_GSM";
+ private static final String SMS_CDMA = "SMS_CDMA";
+ private static final String MMS = "MMS";
+ private final long SMS_OFFSET_START;
+ private final long MMS_OFFSET_START;
+
+ private MmsSmsContentObserver mMmsSmsObserver = new MmsSmsContentObserver();
+
+ public BluetoothMnsSmsMms(Context context, Integer masId) {
+ super(context, masId);
+ SMS_OFFSET_START = OFFSET_START;
+ MMS_OFFSET_START = OFFSET_START + ((OFFSET_END - OFFSET_START) / 2);
+ }
+
+ @Override
+ protected void registerContentObserver() {
+ if (V) Log.v(TAG, "REGISTERING SMS/MMS MNS");
+ mMmsSmsObserver.update(true);
+ mContext.getContentResolver().registerContentObserver(MmsSms.CONTENT_URI, true,
+ mMmsSmsObserver);
+ if (V) Log.v(TAG, "REGISTERING SMS/MMS MNS DONE");
+ }
+
+ @Override
+ protected void unregisterContentObserver() {
+ if (V) Log.v(TAG, "UNREGISTERING MNS SMS/MMS");
+ mContext.getContentResolver().unregisterContentObserver(mMmsSmsObserver);
+ if (V) Log.v(TAG, "UNREGISTERING MNS SMS/MMS DONE");
+ }
+
+ private static final String[] MMS_PROJECTION = new String[] {Mms._ID, Mms.MESSAGE_BOX,
+ Mms.THREAD_ID, Mms.MESSAGE_TYPE, Mms.DATE};
+ private static final int MMS_ID_COL = 0;
+ private static final int MMS_BOX_TYPE_COL = 1;
+ private static final int MMS_THREAD_ID_COL = 2;
+ private static final int MMS_MSG_TYPE_COL = 3;
+ private static final int MMS_DATE_COL = 4;
+
+ private static final String[] SMS_PROJECTION = new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.DATE};
+ private static final int SMS_ID_COL = 0;
+ private static final int SMS_TYPE_COL = 1;
+ private static final int SMS_THREAD_ID_COL = 2;
+ private static final int SMS_DATE_COL = 3;
+
+ static class Message {
+ long mId;
+ String mFolderName;
+ int mType;
+ long mThreadId;
+ long mDate;
+
+ public Message(long id, String folderName, int type, long threadId, long date) {
+ mId = id;
+ mFolderName = folderName;
+ mType = type;
+ mThreadId = threadId;
+ mDate = date;
+ }
+ }
+
+ private class MmsSmsContentObserver extends ContentObserver {
+ private static final String TAG = "MmsSmsContentObserver";
+ private ConcurrentHashMap<Long, Message> mSmsList = new ConcurrentHashMap<Long, Message>();
+ private ConcurrentHashMap<Long, Message> mSmsAddedList = new ConcurrentHashMap<Long, Message>();
+ private ConcurrentHashMap<Long, Message> mSmsDeletedList = new ConcurrentHashMap<Long, Message>();
+
+ private ConcurrentHashMap<Long, Message> mMmsList = new ConcurrentHashMap<Long, Message>();
+ private ConcurrentHashMap<Long, Message> mMmsAddedList = new ConcurrentHashMap<Long, Message>();
+ private ConcurrentHashMap<Long, Message> mMmsDeletedList = new ConcurrentHashMap<Long, Message>();
+
+ private static final int UPDATE = 0;
+ private static final int THRESHOLD = 1500; // 1.5 sec
+
+ public MmsSmsContentObserver() {
+ super(null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ if (V) Log.v(TAG, "onChange(" + selfChange + ") SMS mas Id: " + mMasId);
+ if (mHandler.hasMessages(UPDATE)) {
+ mHandler.removeMessages(UPDATE);
+ }
+ mHandler.sendEmptyMessageDelayed(UPDATE, THRESHOLD);
+ }
+
+ private Handler mHandler = new Handler() {
+ private static final String TAG = "MmsSmsContentObserver.Hanlder";
+ @Override
+ public void handleMessage(android.os.Message msg) {
+ if (V) Log.v(TAG, "handleMessage(" + msg.what + ") mas Id: " + mMasId);
+ switch (msg.what) {
+ case UPDATE:
+ new Thread(new Runnable() {
+ public void run() {
+ update(false);
+ sendEvents();
+ }
+ }, "MmsSms Content Observer Thread").start();
+ break;
+ }
+ }
+ };
+
+ void update(boolean init) {
+ updateSms(init);
+ updateMms(init);
+ }
+
+ void updateSms(boolean init) {
+ if (init) {
+ clearSms();
+ }
+
+ if(V) Log.v(TAG, "MNS_SMS: updateSMS");
+ final ContentResolver resolver = mContext.getContentResolver();
+ Cursor crSms = resolver.query(Sms.CONTENT_URI, SMS_PROJECTION, null, null, null);
+ if (crSms != null) {
+ if (crSms.moveToFirst()) {
+ ConcurrentHashMap<Long, Message> oldSmsList = mSmsList;
+ ConcurrentHashMap<Long, Message> newSmsList = new ConcurrentHashMap<Long, Message>();
+ do {
+ final long id = crSms.getLong(SMS_ID_COL);
+ final int type = crSms.getInt(SMS_TYPE_COL);
+ final long threadId = crSms.getLong(SMS_THREAD_ID_COL);
+ final long date = crSms.getLong(SMS_DATE_COL);
+ if (type > 0 && type < MSG_TO_MAP.length) {
+ final Message msg = new Message(id, MSG_TO_MAP[type], type, threadId,
+ date);
+ if(V) Log.v(TAG, "MNS_SMS: Found: id: " + id+ " type: " + type + " threadId: " + threadId + " date: " + date +"\n");
+ newSmsList.put(id, msg);
+ if (oldSmsList.containsKey(id)) {
+ Message old_msg = oldSmsList.remove(id);
+ if (msg.mType != old_msg.mType) {
+ if(V) Log.v(TAG, "MNS_SMS: Add to mSmsAddedList from old");
+ mSmsAddedList.put(id, msg);
+ }
+ }
+ else {
+ final Message oldMsg = oldSmsList.remove(id);
+ if (!init && (oldMsg == null || oldMsg.mDate != date)) {
+ if(V) Log.v(TAG, "MNS_SMS: Add to mSmsAddedList");
+ mSmsAddedList.put(id, msg);
+ }
+ }
+ }
+ } while (crSms.moveToNext());
+ mSmsList = newSmsList;
+ mSmsDeletedList = oldSmsList;
+ }
+ else
+ {
+ // Last sms to be deleted
+ if(mSmsList.size() > 0)
+ {
+ if(V) Log.v(TAG, "MNS_SMS: mSmsList Length: " + mSmsList.size());
+ mSmsDeletedList = mSmsList;
+ }
+ }
+ crSms.close();
+ }
+ }
+
+ void updateMms(boolean init) {
+ if (init) {
+ clearMms();
+ }
+ final ContentResolver resolver = mContext.getContentResolver();
+ Cursor crMms = resolver.query(Mms.CONTENT_URI, MMS_PROJECTION, null, null, null);
+ if (crMms != null) {
+ if (crMms.moveToFirst()) {
+ ConcurrentHashMap<Long, Message> oldMmsList = mMmsList;
+ ConcurrentHashMap<Long, Message> newMmsList = new ConcurrentHashMap<Long, Message>();
+ do {
+ final long id = crMms.getInt(MMS_ID_COL);
+ final int boxType = crMms.getInt(MMS_BOX_TYPE_COL);
+ final long threadId = crMms.getLong(MMS_THREAD_ID_COL);
+ final int msgType = crMms.getInt(MMS_MSG_TYPE_COL);
+ final long date = crMms.getLong(MMS_DATE_COL);
+ // TODO need to filter out Pdu by message type?
+ if (msgType != PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND &&
+ msgType != PduHeaders.MESSAGE_TYPE_DELIVERY_IND) {
+ final Message msg = new Message(id, MSG_TO_MAP[boxType],
+ boxType, threadId, date);
+ newMmsList.put(id, msg);
+ final Message oldMsg = oldMmsList.remove(id);
+ if (!init && (oldMsg == null || oldMsg.mDate != date)) {
+ mMmsAddedList.put(id, msg);
+ }
+ }
+ } while (crMms.moveToNext());
+ mMmsList = newMmsList;
+ mMmsDeletedList = oldMmsList;
+ }
+ crMms.close();
+ }
+ }
+
+ private void sendEvents() {
+ if (mSmsAddedList.size() > 0) {
+ newSms();
+ mSmsAddedList.clear();
+ }
+ if (mSmsDeletedList.size() > 0) {
+ deletedSms();
+ mSmsDeletedList.clear();
+ }
+ if (mMmsAddedList.size() > 0) {
+ newMms();
+ mMmsAddedList.clear();
+ }
+ if (mMmsDeletedList.size() > 0) {
+ deletedMms();
+ mMmsDeletedList.clear();
+ }
+ }
+
+ private void clearSms() {
+ mSmsList.clear();
+ mSmsAddedList.clear();
+ mSmsDeletedList.clear();
+ }
+
+ private void clearMms() {
+ mMmsList.clear();
+ mMmsAddedList.clear();
+ mMmsDeletedList.clear();
+ }
+
+ private void newSms() {
+ if (V) Log.v(TAG, "newSms() SMS mas Id: " + mMasId);
+ if (mListener != null) {
+ final int phoneType = TelephonyManager.getDefault().getPhoneType();
+ final String type = (phoneType == TelephonyManager.PHONE_TYPE_CDMA)
+ ? SMS_CDMA : SMS_GSM;
+ Collection<Message> values = mSmsAddedList.values();
+ for (Message msg : values) {
+ if (msg.mType == Sms.MESSAGE_TYPE_SENT) {
+ mListener.onSendingSuccess(mMasId, String.valueOf(SMS_OFFSET_START +
+ msg.mId), PRE_PATH + SENT, type);
+ } else if (msg.mType == Sms.MESSAGE_TYPE_FAILED) {
+ mListener.onSendingFailure(mMasId, String.valueOf(SMS_OFFSET_START +
+ msg.mId), PRE_PATH + OUTBOX, type);
+ } else if ((msg.mType == Sms.MESSAGE_TYPE_INBOX) ||
+ (msg.mType == Sms.MESSAGE_TYPE_OUTBOX)) {
+ String folderName = (msg.mThreadId == -1) ? DELETED : msg.mFolderName;
+ mListener.onNewMessage(mMasId, String.valueOf(SMS_OFFSET_START + msg.mId),
+ PRE_PATH + folderName, type);
+ }
+ }
+ }
+ }
+
+ private void deletedSms() {
+ if (V) Log.v(TAG, "deletedSms() SMS mas Id: " + mMasId);
+ if (mListener != null) {
+ final int phoneType = TelephonyManager.getDefault().getPhoneType();
+ final String type = (phoneType == TelephonyManager.PHONE_TYPE_CDMA)
+ ? SMS_CDMA : SMS_GSM;
+ Collection<Message> values = mSmsDeletedList.values();
+ for (Message msg : values) {
+ String folderName = (msg.mThreadId == -1) ? DELETED : msg.mFolderName;
+ mListener.onMessageDeleted(mMasId, String.valueOf(SMS_OFFSET_START + msg.mId),
+ PRE_PATH + folderName, type);
+ }
+ }
+ }
+
+ private void newMms() {
+ if (V) Log.v(TAG, "newMms() MMS mas Id: " + mMasId);
+ if (mListener != null) {
+ Collection<Message> values = mMmsAddedList.values();
+ for (Message msg : values) {
+ String folderName = (msg.mThreadId == -1) ? DELETED : msg.mFolderName;
+ mListener.onNewMessage(mMasId, String.valueOf(MMS_OFFSET_START + msg.mId),
+ PRE_PATH + folderName, MMS);
+ if (msg.mType == Mms.MESSAGE_BOX_SENT) {
+ mListener.onSendingSuccess(mMasId, String.valueOf(SMS_OFFSET_START +
+ msg.mId), PRE_PATH + SENT, MMS);
+ }
+ }
+ }
+ }
+
+ private void deletedMms() {
+ if (V) Log.v(TAG, "deletedMms() MMS mas Id: " + mMasId);
+ if (mListener != null) {
+ Collection<Message> values = mMmsDeletedList.values();
+ for (Message msg : values) {
+ String folderName = (msg.mThreadId == -1) ? DELETED : msg.mFolderName;
+ mListener.onMessageDeleted(mMasId, String.valueOf(MMS_OFFSET_START + msg.mId),
+ PRE_PATH + folderName, MMS);
+ }
+ }
+ }
+ };
+}
diff --git a/src/org/codeaurora/bluetooth/map/IBluetoothMasApp.java b/src/org/codeaurora/bluetooth/map/IBluetoothMasApp.java
new file mode 100644
index 0000000..c5bf471
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/IBluetoothMasApp.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map;
+
+import android.bluetooth.BluetoothDevice;
+
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMasMessageListingRsp;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMasMessageRsp;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMasPushMsgRsp;
+import org.codeaurora.bluetooth.map.MapUtils.MapUtils.BadRequestException;
+
+import java.io.File;
+
+public interface IBluetoothMasApp {
+ public static final int BIT_SUBJECT = 0x1;
+ public static final int BIT_DATETIME = 0x2;
+ public static final int BIT_SENDER_NAME = 0x4;
+ public static final int BIT_SENDER_ADDRESSING = 0x8;
+
+ public static final int BIT_RECIPIENT_NAME = 0x10;
+ public static final int BIT_RECIPIENT_ADDRESSING = 0x20;
+ public static final int BIT_TYPE = 0x40;
+ public static final int BIT_SIZE = 0x80;
+
+ public static final int BIT_RECEPTION_STATUS = 0x100;
+ public static final int BIT_TEXT = 0x200;
+ public static final int BIT_ATTACHMENT_SIZE = 0x400;
+ public static final int BIT_PRIORITY = 0x800;
+
+ public static final int BIT_READ = 0x1000;
+ public static final int BIT_SENT = 0x2000;
+ public static final int BIT_PROTECTED = 0x4000;
+ public static final int BIT_REPLYTO_ADDRESSING = 0x8000;
+
+ /**
+ * Message Access Profile SPEC V10
+ * 3.1.1 Handle
+ * The handle shall be a 64 bit unsigned integer whose value is defined by the MSE.
+ * 7.1 SDP Interoperability Requirements
+ * Up to 12 MAS Instances may be supported by a MSE device.
+ * The value range of the MASInstanceID. shall be 0..255.
+ * Although id range is 0..255, 0~11 are selected for conventional indexing
+ */
+ public static final long HANDLE_OFFSET[] = {
+ 0, // MAS id 0
+ 1 << 59, // MAS id 1
+ 2 << 59, // MAS id 2
+ 3 << 59, // MAS id 3
+ 4 << 59, // MAS id 4
+ 5 << 59, // MAS id 5
+ 6 << 59, // MAS id 6
+ 7 << 59, // MAS id 7
+ 8 << 59, // MAS id 8
+ 9 << 59, // MAS id 9
+ 10 << 59, // MAS id 10
+ 11 << 59, // MAS id 11
+ Long.MAX_VALUE
+ };
+
+ public static final int EMAIL_MAX_PUSHMSG_SIZE = 409600;
+
+ public static final int DELETED_THREAD_ID = -1;
+
+ public static final String TELECOM = "telecom";
+ public static final String MSG = "msg";
+
+ public static final int MESSAGE_TYPE_EMAIL = 1 << 0;
+ public static final int MESSAGE_TYPE_SMS_GSM = 1 << 1;
+ public static final int MESSAGE_TYPE_SMS_CDMA = 1 << 2;
+ public static final int MESSAGE_TYPE_MMS = 1 << 3;
+ public static final int MESSAGE_TYPE_SMS = MESSAGE_TYPE_SMS_GSM | MESSAGE_TYPE_SMS_CDMA;
+ public static final int MESSAGE_TYPE_SMS_MMS = MESSAGE_TYPE_SMS | MESSAGE_TYPE_MMS;
+
+ public boolean setPath(boolean up, String name);
+ public boolean checkPath(boolean up, String name, boolean setPathFlag);
+ public int folderListingSize();
+ public String folderListing(BluetoothMasAppParams appParam);
+ //private String getFullPath(String child);
+ public BluetoothMasMessageListingRsp msgListing(String name,
+ BluetoothMasAppParams appParams);
+ public BluetoothMasMessageRsp msg(String msgHandle,
+ BluetoothMasAppParams bluetoothMasAppParams);
+ public BluetoothMasPushMsgRsp pushMsg(String name, File file,
+ BluetoothMasAppParams bluetoothMasAppParams) throws BadRequestException;
+ public int msgStatus(String msgHandle, BluetoothMasAppParams bluetoothMasAppParams);
+ public int msgUpdate();
+ public void onConnect();
+ public void onDisconnect();
+ public int notification(BluetoothDevice remoteDevice,
+ BluetoothMasAppParams bluetoothMasAppParams);
+ public void startMnsSession(BluetoothDevice remoteDevice);
+ public void stopMnsSession(BluetoothDevice remoteDevice);
+ public int getMasId();
+ public boolean checkPrecondition();
+
+ public interface MessageNotificationListener {
+ public static final String NEW_MESSAGE = "NewMessage";
+ public static final String DELIVERY_SUCCESS = "DeliverySuccess";
+ public static final String SENDING_SUCCESS = "SendingSuccess";
+ public static final String DELIVERY_FAILURE = "DeliveryFailure";
+ public static final String SENDING_FAILURE = "SendingFailure";
+ public static final String MESSAGE_DELETED = "MessageDeleted";
+ public static final String MESSAGE_SHIFT = "MessageShift";
+ public void onNewMessage(int masId, String handle, String folder, String msgType);
+ public void onDeliverySuccess(int masId, String handle, String folder, String msgType);
+ public void onSendingSuccess(int masId, String handle, String folder, String msgType);
+ public void onDeliveryFailure(int masId, String handle, String folder, String msgType);
+ public void onSendingFailure(int masId, String handle, String folder, String msgType);
+ public void onMessageDeleted(int masId, String handle, String folder, String msgType);
+ public void onMessageShift(int masId, String handle, String toFolder, String fromFolder,
+ String msgType);
+ }
+
+ public interface MnsRegister {
+ public void register(MessageNotificationListener listener);
+ public boolean isRegistered();
+ }
+}
diff --git a/src/org/codeaurora/bluetooth/map/MapUtils/BmessageConsts.java b/src/org/codeaurora/bluetooth/map/MapUtils/BmessageConsts.java
new file mode 100644
index 0000000..e424653
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/MapUtils/BmessageConsts.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map.MapUtils;
+
+public class BmessageConsts {
+
+ public String bmsg_version = null;
+ public String status = null;
+ public String type = null;
+ public String folder = null;
+ public String vcard_version = null;
+ public String recipient_vcard_name = null;
+ public String recipient_vcard_phone_number = null;
+ public String recipient_vcard_email = null;
+ public String originator_vcard_name = null;
+ public String originator_vcard_phone_number = null;
+ public String originator_vcard_email = null;
+ public String body_encoding = null;
+ public int body_length = 0;
+ public String body_msg = null;
+ public String body_part_ID = null;
+ public String body_language = null;
+ public String body_charset = null;
+ public String subject = null;
+
+ // Setters and Getters
+
+ public String getBody_language() {
+ return body_language;
+ }
+
+ public void setBody_language(String body_language) {
+ this.body_language = body_language;
+ }
+
+ public String getBody_part_ID() {
+ return body_part_ID;
+ }
+
+ public void setBody_part_ID(String body_part_ID) {
+ this.body_part_ID = body_part_ID;
+ }
+
+ public String getBmsg_version() {
+ return bmsg_version;
+ }
+
+ public void setBmsg_version(String bmsg_version) {
+ this.bmsg_version = bmsg_version;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getFolder() {
+ return folder;
+ }
+
+ public void setFolder(String folder) {
+ this.folder = folder;
+ }
+
+ public String getVcard_version() {
+ return vcard_version;
+ }
+
+ public void setVcard_version(String vcard_version) {
+ this.vcard_version = vcard_version;
+ }
+
+ // Name
+ public String getRecipientVcard_name() {
+ return recipient_vcard_name;
+ }
+
+ public String getOriginatorVcard_name() {
+ return originator_vcard_name;
+ }
+
+ // recipient_vcard_name
+
+ public void setRecipientVcard_name(String vcard_name) {
+ this.recipient_vcard_name = vcard_name;
+ }
+
+ public void setOriginatorVcard_name(String vcard_name) {
+ this.originator_vcard_name = vcard_name;
+ }
+
+ // Name
+
+ // Phone
+ public String getRecipientVcard_email() {
+ return recipient_vcard_email;
+ }
+
+ public String getOriginatorVcard_email() {
+ return originator_vcard_email;
+ }
+
+ public void setRecipientVcard_email(String email) {
+ this.recipient_vcard_email = email;
+ }
+
+ public void setOriginatorVcard_email(String email) {
+ this.originator_vcard_email = email;
+ }
+
+ // Phone
+ //Email
+ public String getRecipientVcard_phone_number() {
+ return recipient_vcard_phone_number;
+ }
+
+ public String getOriginatorVcard_phone_number() {
+ return originator_vcard_phone_number;
+ }
+
+ public void setRecipientVcard_phone_number(String vcard_phone_number) {
+ this.recipient_vcard_phone_number = vcard_phone_number;
+ }
+
+ public void setOriginatorVcard_phone_number(String vcard_phone_number) {
+ this.originator_vcard_phone_number = vcard_phone_number;
+ }
+
+
+
+
+ //end Email
+
+ public String getBody_charset() {
+ return body_charset;
+ }
+
+ public void setBody_charset(String body_charset) {
+ this.body_charset = body_charset;
+ }
+
+ public String getBody_encoding() {
+ return body_encoding;
+ }
+
+ public void setBody_encoding(String body_encoding) {
+ this.body_encoding = body_encoding;
+ }
+
+ public int getBody_length() {
+ return body_length;
+ }
+
+ public void setBody_length(int body_length) {
+ this.body_length = body_length;
+ }
+
+ public String getBody_msg() {
+ return body_msg;
+ }
+
+ public void setBody_msg(String body_msg) {
+ this.body_msg = body_msg;
+ }
+ public String getSubject() {
+ return subject;
+ }
+
+ public void setSubject(String subject) {
+ this.subject = subject;
+ }
+
+}
diff --git a/src/org/codeaurora/bluetooth/map/MapUtils/CommonUtils.java b/src/org/codeaurora/bluetooth/map/MapUtils/CommonUtils.java
new file mode 100644
index 0000000..73546d0
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/MapUtils/CommonUtils.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map.MapUtils;
+
+import android.content.Context;
+import android.text.format.Time;
+import android.util.Log;
+import android.util.TimeFormatException;
+
+import org.codeaurora.bluetooth.map.BluetoothMasAppParams;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.obex.ResponseCodes;
+
+public class CommonUtils {
+ public static final String TAG = "CommonUtils";
+
+ public static final ArrayList<String> FIXED_FOLDERS;
+
+ static {
+ FIXED_FOLDERS = new ArrayList<String>();
+ FIXED_FOLDERS.add("inbox");
+ FIXED_FOLDERS.add("sent");
+ FIXED_FOLDERS.add("deleted");
+ FIXED_FOLDERS.add("outbox");
+ FIXED_FOLDERS.add("draft");
+ }
+
+ public static class BluetoothMasPushMsgRsp {
+ public int response;
+ public String msgHandle;
+ }
+
+ public static class BluetoothMasMessageListingRsp {
+ public File file = null;
+ public int msgListingSize = 0;
+ public byte newMessage = 0;
+ public int rsp = ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ public static class BluetoothMasMessageRsp {
+ public byte fractionDeliver = 0;
+ public File file = null;
+ public int rsp = ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ public static class BluetoothMsgListRsp {
+ public int messageListingSize = 0;
+ public BluetoothMasMessageListingRsp rsp;
+ public List<MsgListingConsts> msgList = new ArrayList<MsgListingConsts>();
+ }
+
+ public static String getFullPath(String child, Context context, List<String> folderList, String CurrentPath) {
+ String tempPath = null;
+ if (child != null) {
+ if (CurrentPath == null) {
+ if (child.equals("telecom")) {
+ // Telecom is fine
+ tempPath = "telecom";
+ }
+ }
+ else if (CurrentPath.equals("telecom")) {
+ if (child.equals("msg")) {
+ tempPath = CurrentPath + "/" + child;
+ }
+ }
+ else { //Get Path for any folder or subfolder
+ for (String folder : folderList) { //TODO NEED TO LOOK INTO THIS
+ if(child.toUpperCase().contains("GMAIL")){
+ if (folder.equalsIgnoreCase(child)
+ || folder.toUpperCase().contains(child.toUpperCase())) {
+ //added second condition above for gmail sent folder
+ tempPath = CurrentPath + "/" + folder;
+ break;
+ }
+ }
+ else{
+ if (folder.equalsIgnoreCase(child)) {
+ tempPath = CurrentPath + "/" + folder;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return tempPath;
+ }
+
+ public static int validateFilterPeriods(BluetoothMasAppParams appParams) {
+ int filterPeriodValid = -1;
+ String periodStr = "";
+ /* Filter Period Begin */
+ if ((appParams.FilterPeriodBegin != null)
+ && (appParams.FilterPeriodBegin.length() > 0)) {
+ Time time = new Time();
+ try {
+ time.parse(appParams.FilterPeriodBegin.trim());
+ if (periodStr.length() != 0) {
+ periodStr += " AND ";
+ }
+ periodStr += "date >= " + time.toMillis(false);
+ } catch (TimeFormatException e) {
+ Log.d(TAG, "Bad formatted FilterPeriodBegin "
+ + appParams.FilterPeriodBegin);
+ filterPeriodValid = 0;
+ }
+ }
+
+ /* Filter Period End */
+ if ((appParams.FilterPeriodEnd != null)
+ && (appParams.FilterPeriodEnd.length() > 0 )) {
+ Time time = new Time();
+ try {
+ time.parse(appParams.FilterPeriodEnd.trim());
+ if (periodStr.length() != 0) {
+ periodStr += " AND ";
+ }
+ periodStr += "date < " + time.toMillis(false);
+ } catch (TimeFormatException e) {
+ Log.d(TAG, "Bad formatted FilterPeriodEnd "
+ + appParams.FilterPeriodEnd);
+ filterPeriodValid = 0;
+ }
+ }
+ return filterPeriodValid;
+ }
+}
diff --git a/src/org/codeaurora/bluetooth/map/MapUtils/EmailUtils.java b/src/org/codeaurora/bluetooth/map/MapUtils/EmailUtils.java
new file mode 100644
index 0000000..5f92b3c
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/MapUtils/EmailUtils.java
@@ -0,0 +1,1054 @@
+/*
+ * Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map.MapUtils;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.text.Html;
+import android.text.format.Time;
+import android.util.Log;
+import android.util.TimeFormatException;
+
+import org.codeaurora.bluetooth.map.BluetoothMasAppParams;
+import org.codeaurora.bluetooth.map.BluetoothMasService;
+import org.codeaurora.bluetooth.map.MapUtils.CommonUtils.BluetoothMasMessageRsp;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Random;
+import java.util.Locale;
+
+public class EmailUtils {
+ public static final String TAG = "EmailUtils";
+ public static final boolean V = BluetoothMasService.VERBOSE;
+
+ public static final int BIT_SUBJECT = 0x1;
+ public static final int BIT_DATETIME = 0x2;
+ public static final int BIT_SENDER_NAME = 0x4;
+ public static final int BIT_SENDER_ADDRESSING = 0x8;
+
+ public static final int BIT_RECIPIENT_NAME = 0x10;
+ public static final int BIT_RECIPIENT_ADDRESSING = 0x20;
+ public static final int BIT_TYPE = 0x40;
+ public static final int BIT_SIZE = 0x80;
+
+ public static final int BIT_RECEPTION_STATUS = 0x100;
+ public static final int BIT_TEXT = 0x200;
+ public static final int BIT_ATTACHMENT_SIZE = 0x400;
+ public static final int BIT_PRIORITY = 0x800;
+
+ public static final int BIT_READ = 0x1000;
+ public static final int BIT_SENT = 0x2000;
+ public static final int BIT_PROTECTED = 0x4000;
+ public static final int BIT_REPLYTO_ADDRESSING = 0x8000;
+
+ public static final String BMW = "BMW";
+
+ public static List<String> folderListEmail(List<String> folderList, Context context) {
+ String[] projection = new String[] {"displayName"};
+ Uri uri = Uri.parse("content://com.android.email.provider/mailbox");
+ Cursor cr = context.getContentResolver().query(uri, projection, null, null, null);
+
+ if (cr != null && cr.moveToFirst()) {
+ do {
+ if (V){
+ Log.v(TAG, " Column Name: "+ cr.getColumnName(0) + " Value: " + cr.getString(0));
+ }
+ int folderFlag = 0;
+ for(int i=0; i< folderList.size(); i++){
+ if(folderList.get(i).equalsIgnoreCase(cr.getString(0))){
+ folderFlag = 1;
+ break;
+ }
+ }
+ if(cr.getString(0).equalsIgnoreCase("Drafts")){ //TODO need to remove this hardcoded value
+ folderFlag = 1;
+ }
+ if(folderFlag == 0){
+ folderList.add(cr.getString(0));
+ }
+
+ } while ( cr.moveToNext());
+ }
+ if (V){
+ Log.v(TAG, " Folder Listing of SMS,MMS and EMAIL: "+folderList);
+ }
+ if (cr != null) {
+ cr.close();
+ }
+ return folderList;
+ }
+
+ public static String getWhereIsQueryForTypeEmail(String folder, Context context, int masId) {
+ String query = "mailboxKey = -1";
+ String folderId;
+ Uri uri = Uri.parse("content://com.android.email.provider/mailbox");
+ if (folder == null) {
+ return query;
+ }
+ if (folder.contains("'")){
+ folder = folder.replace("'", "''");
+ }
+ Cursor cr = context.getContentResolver().query(
+ uri, null, "(" + ACCOUNT_KEY + "=" + getAccountId(masId) +
+ ") AND (UPPER(displayName) = '"+ folder.toUpperCase()+"')" , null, null);
+ if (cr != null) {
+ if ( cr.moveToFirst()) {
+ do {
+ folderId = cr.getString(cr.getColumnIndex("_id"));
+ query = "mailboxKey = "+ folderId;
+ break;
+ } while ( cr.moveToNext());
+ }
+ cr.close();
+ }
+ return query;
+ }
+
+ public static int getMessageSizeEmail(long messageId, Context context) {
+ if (V){
+ Log.v(TAG, ":: Message Id in getMessageSizeEmail ::"+ messageId);
+ }
+ int msgSize = 0;
+ String textContent, htmlContent;
+ Uri uri = Uri.parse("content://com.android.email.provider/body");
+
+ Cursor cr = context.getContentResolver().query(
+ uri, null, "messageKey = "+ messageId , null, null);
+
+ if (cr != null && cr.moveToFirst()) {
+ do {
+ textContent = cr.getString(cr.getColumnIndex("textContent"));
+ htmlContent = cr.getString(cr.getColumnIndex("htmlContent"));
+ if(textContent != null && textContent.length() != 0){
+ msgSize = textContent.length();
+ }
+ else if(textContent == null){
+ if(htmlContent != null && htmlContent.length() != 0){
+ msgSize = htmlContent.length();
+ }
+ }
+ break;
+ } while (cr.moveToNext());
+ }
+ if (cr != null) {
+ cr.close();
+ }
+ return msgSize;
+ }
+
+ public static int getAttachmentSizeEmail(long messageId, Context context) {
+ if (V){
+ Log.v(TAG, ":: Message Id in getAttachmentSizeEmail ::"+ messageId);
+ }
+ int attchSize = 0;
+ Uri uri = Uri.parse("content://com.android.email.provider/attachment");
+
+ Cursor cr = context.getContentResolver().query(
+ uri, new String[]{"size"}, "messageKey = "+ messageId , null, null);
+ if (cr != null && cr.moveToFirst()) {
+ do {
+ attchSize += cr.getInt(0);
+ } while (cr.moveToNext());
+ }
+ if (cr != null) {
+ cr.close();
+ }
+ return attchSize;
+ }
+
+ public static String getFolderName(String[] splitStringsEmail) {
+ String folderName=" ";
+ int len = splitStringsEmail.length;
+ if (V){
+ Log.v(TAG, ":: Split Strings Array in getFolderName ::"+ splitStringsEmail);
+ }
+ folderName = splitStringsEmail[len -1];
+ if (V){
+ Log.v(TAG, "folderName :: " + folderName);
+ }
+ return folderName;
+ }
+
+ public static String getConditionString(String folderName, Context context,
+ BluetoothMasAppParams appParams, int masId) {
+ String whereClauseEmail = getWhereIsQueryForTypeEmail(folderName, context, masId);
+
+ /* Filter readstatus: 0 no filtering, 0x01 get unread, 0x10 get read */
+ if (appParams.FilterReadStatus != 0) {
+ if ((appParams.FilterReadStatus & 0x1) != 0) {
+ if (whereClauseEmail.length() != 0) {
+ whereClauseEmail += " AND ";
+ }
+ whereClauseEmail += " flagRead = 0 ";
+ }
+ if (V){
+ Log.v(TAG, "Filter Read Status Value:"+appParams.FilterReadStatus);
+ Log.v(TAG, "Filter Read Status Condition Value:"+(appParams.FilterReadStatus & 0x02));
+ }
+ if ((appParams.FilterReadStatus & 0x02) != 0) {
+ if (whereClauseEmail.length() != 0) {
+ whereClauseEmail += " AND ";
+ }
+ whereClauseEmail += " flagRead = 1 ";
+ }
+ }
+ if (V){
+ Log.v(TAG, "Filter Recipient Value:"+appParams.FilterRecipient);
+ Log.v(TAG, "Filter Recipient Condition 1 :"+(appParams.FilterRecipient != null));
+ if (appParams.FilterRecipient != null) {
+ Log.v(TAG, "Filter Recipient Condition 2 :"+(appParams.FilterRecipient.length() != 0));
+ }
+ }
+ //Filter Recipient
+ if ((appParams.FilterRecipient != null) && (appParams.FilterRecipient.length() > 0)
+ && !(appParams.FilterRecipient.equalsIgnoreCase("*"))) {
+ if(appParams.FilterRecipient.contains("*")){
+ appParams.FilterRecipient = appParams.FilterRecipient.replace('*', '%');
+ }
+ if (whereClauseEmail.length() != 0) {
+ whereClauseEmail += " AND ";
+ }
+ whereClauseEmail += " toList LIKE '%"+appParams.FilterRecipient.trim()+"%'";
+ }
+
+ // TODO Filter Originator
+
+ if ((appParams.FilterOriginator != null)
+ && (appParams.FilterOriginator.length() > 0)
+ && !(appParams.FilterOriginator.equalsIgnoreCase("*"))) {
+ if(appParams.FilterOriginator.contains("*")){
+ appParams.FilterOriginator = appParams.FilterOriginator.replace('*', '%');
+ }
+ // For incoming message
+ if (whereClauseEmail.length() != 0) {
+ whereClauseEmail += " AND ";
+ }
+ String str1 = appParams.FilterOriginator;
+ whereClauseEmail += "fromList LIKE '%"+appParams.FilterOriginator.trim()+"%'";
+ }
+ if (V){
+ Log.v(TAG, "whereClauseEmail :" + whereClauseEmail);
+ }// TODO Filter priority?
+
+ /* Filter Period Begin */
+ if ((appParams.FilterPeriodBegin != null)
+ && (appParams.FilterPeriodBegin.length() != 0)) {
+ Time time = new Time();
+
+ try {
+ time.parse(appParams.FilterPeriodBegin.trim());
+ if (whereClauseEmail.length() != 0) {
+ whereClauseEmail += " AND ";
+ }
+ whereClauseEmail += "timeStamp >= " + time.toMillis(false);
+
+ } catch (TimeFormatException e) {
+ Log.d(TAG, "Bad formatted FilterPeriodBegin, Ignore"
+ + appParams.FilterPeriodBegin);
+ }
+ }
+
+ /* Filter Period End */
+ if ((appParams.FilterPeriodEnd != null)
+ && (appParams.FilterPeriodEnd.length() != 0)) {
+ Time time = new Time();
+ try {
+ time.parse(appParams.FilterPeriodEnd.trim());
+ if (whereClauseEmail.length() != 0) {
+ whereClauseEmail += " AND ";
+ }
+ whereClauseEmail += "timeStamp < " + time.toMillis(false);
+ } catch (TimeFormatException e) {
+ Log.d(TAG, "Bad formatted FilterPeriodEnd, Ignore"
+ + appParams.FilterPeriodEnd);
+ }
+ }
+
+ return whereClauseEmail;
+ }
+
+ public static MsgListingConsts bldEmailMsgLstItem(Context context, String folderName,
+ BluetoothMasAppParams appParams, String subject, String timestamp,
+ String senderName, String senderAddressing, String recipientName,
+ String recipientAddressing, String msgId, String readStatus, String replyToStr,
+ long offset) {
+
+ MsgListingConsts emailMsg = new MsgListingConsts();
+ emailMsg.setMsg_handle(Long.valueOf(msgId)+ offset);
+
+ Time time = new Time();
+ time.set(Long.valueOf(timestamp));
+
+ String datetimeStr = time.toString().substring(0, 15);
+
+ emailMsg.msgInfo.setDateTime(datetimeStr);
+
+ if (V){
+ Log.v(TAG, "bldEmailMsgLstItem");
+ Log.v(TAG, "Subject: " + subject);
+ Log.v(TAG, "timestamp: " + timestamp);
+ Log.v(TAG, "senderName: " + senderName);
+ Log.v(TAG, "senderAddressing: " + senderAddressing);
+ Log.v(TAG, "recipientName: " + recipientName);
+ Log.v(TAG, "recipientAddressing: " + recipientAddressing);
+ Log.v(TAG, "msgId: " + msgId);
+ Log.v(TAG, "readStatus: " + readStatus);
+ Log.v(TAG, "replyToStr: " + replyToStr);
+ Log.v(TAG, "offset: " + offset);
+ }
+
+ if ((appParams.ParameterMask & BIT_SUBJECT) != 0) {
+
+ if (V){
+ Log.v(TAG, "bldEmailMsgLstItem :: Subject " + subject);
+ }
+ if ((subject != null && appParams.SubjectLength > 0)
+ && (subject.length() > appParams.SubjectLength)) {
+ subject = subject.substring(0,
+ appParams.SubjectLength);
+ }
+ if(subject != null){
+ emailMsg.setSubject(subject.trim());
+ }
+ emailMsg.sendSubject = true;
+ }
+
+ if ((appParams.ParameterMask & BIT_DATETIME) != 0) {
+ /*emailMsg.setDatetime(time.toString().substring(0, 15)
+ + "-0700");*/
+ emailMsg.setDatetime(datetimeStr);
+ }
+
+ if ((appParams.ParameterMask & BIT_SENDER_NAME) != 0) {
+ if(senderName != null) {
+ if(senderName.contains("")){
+ String[] senderStr = senderName.split("");
+ if(senderStr !=null && senderStr.length > 0){
+ if (V){
+ Log.v(TAG, " ::Sender name split String 0:: " + senderStr[0]
+ + "::Sender name split String 1:: " + senderStr[1]);
+ }
+ emailMsg.setSender_name(senderStr[1].trim());
+ }
+ }
+ else{
+ emailMsg.setSender_name(senderName.trim());
+ }
+ }
+ }
+
+ if ((appParams.ParameterMask & BIT_SENDER_ADDRESSING) != 0) {
+ if(senderAddressing != null) {
+ if(senderAddressing.contains("")){
+ String[] senderAddrStr = senderAddressing.split("");
+ if(senderAddrStr !=null && senderAddrStr.length > 0){
+ if (V){
+ Log.v(TAG, " ::Sender Addressing split String 0:: " + senderAddrStr[0]
+ + "::Sender Addressing split String 1:: " + senderAddrStr[1]);
+ }
+ emailMsg.setSender_addressing(senderAddrStr[0].trim());
+ }
+ }
+ else{
+ emailMsg.setSender_addressing(senderAddressing.trim());
+ }
+ }
+ }
+
+ if ((appParams.ParameterMask & BIT_RECIPIENT_NAME) != 0) {
+ String multiRecepients = "";
+ if(recipientName != null){
+ if(recipientName.contains("")){
+ List<String> recipientNameArr = new ArrayList<String>();
+ List<String> recipientEmailArr = new ArrayList<String>();
+ String[] multiRecipientStr = recipientName.split("");
+ for(int i=0; i < multiRecipientStr.length ; i++){
+ if(multiRecipientStr[i].contains("")){
+ String[] recepientStr = multiRecipientStr[i].split("");
+ recipientNameArr.add(recepientStr[1]);
+ recipientEmailArr.add(recepientStr[0]);
+ }
+ }
+ if(recipientNameArr != null && recipientNameArr.size() > 0){
+ for(int i=0; i < recipientNameArr.size() ; i++){
+ if(i < (recipientNameArr.size()-1)){
+ multiRecepients += recipientNameArr.get(i)+";";
+ }
+ else{
+ multiRecepients += recipientNameArr.get(i);
+ }
+ }
+ }
+ emailMsg.setRecepient_name(multiRecepients.trim());
+ }
+ else if(recipientName.contains("")){
+ String[] recepientStr = recipientName.split("");
+ if(recepientStr !=null && recepientStr.length > 0){
+ if (V){
+ Log.v(TAG, " ::Recepient name split String 0:: " + recepientStr[0]
+ + "::Recepient name split String 1:: " + recepientStr[1]);
+ }
+ emailMsg.setRecepient_name(recepientStr[1].trim());
+ }
+ }
+ else{
+ emailMsg.setRecepient_name(recipientName.trim());
+ }
+ }
+ }
+
+ if ((appParams.ParameterMask & BIT_RECIPIENT_ADDRESSING) != 0) {
+ String multiRecepientAddrs = "";
+
+ if (recipientAddressing != null) {
+ if (recipientAddressing.contains("")) {
+ List<String> recipientNameArr = new ArrayList<String>();
+ List<String> recipientEmailArr = new ArrayList<String>();
+ if (recipientName != null) {
+ String[] multiRecipientStr = recipientName.split("");
+ for (int i=0; i < multiRecipientStr.length ; i++) {
+ if (multiRecipientStr[i].contains("")) {
+ String[] recepientStr = multiRecipientStr[i].split("");
+ recipientNameArr.add(recepientStr[1]);
+ recipientEmailArr.add(recepientStr[0]);
+ }
+ }
+ }
+ final int recipientEmailArrSize = recipientEmailArr.size();
+ if (recipientEmailArrSize > 0) {
+ for (int i=0; i < recipientEmailArrSize ; i++) {
+ if (i < (recipientEmailArrSize-1)) {
+ multiRecepientAddrs += recipientEmailArr.get(i)+";";
+ } else {
+ multiRecepientAddrs += recipientEmailArr.get(i);
+ }
+ }
+ }
+ emailMsg.setRecepient_addressing(multiRecepientAddrs.trim());
+ emailMsg.setSendRecipient_addressing(true);
+ } else if (recipientAddressing.contains("")) {
+ String[] recepientAddrStr = recipientAddressing.split("");
+ if (recepientAddrStr !=null && recepientAddrStr.length > 0) {
+ if (V){
+ Log.v(TAG, " ::Recepient addressing split String 0:: " + recepientAddrStr[0]
+ + "::Recepient addressing split String 1:: " + recepientAddrStr[1]);
+ }
+ emailMsg.setRecepient_addressing(recepientAddrStr[0].trim());
+ emailMsg.setSendRecipient_addressing(true);
+ }
+ } else {
+ emailMsg.setRecepient_addressing(recipientAddressing.trim());
+ emailMsg.setSendRecipient_addressing(true);
+ }
+ }
+ }
+
+ if ((appParams.ParameterMask & BIT_TYPE) != 0) {
+ emailMsg.setType("EMAIL");
+ }
+
+ if ((appParams.ParameterMask & BIT_SIZE) != 0) {
+ int msgSize = 0;
+ msgSize = getMessageSizeEmail(Long.valueOf(msgId), context);
+ emailMsg.setSize(msgSize);
+ }
+
+ if ((appParams.ParameterMask & BIT_RECEPTION_STATUS) != 0) {
+ emailMsg.setReception_status("complete");
+ }
+
+ if ((appParams.ParameterMask & BIT_TEXT) != 0) {
+ // TODO Set text to "yes"
+ emailMsg.setContains_text("yes");
+ }
+
+ if ((appParams.ParameterMask & BIT_ATTACHMENT_SIZE) != 0) {
+ emailMsg.setAttachment_size(getAttachmentSizeEmail(Long.valueOf(msgId), context));
+ }
+
+ if ((appParams.ParameterMask & BIT_PRIORITY) != 0) {
+ // TODO Get correct priority
+ emailMsg.setPriority("no");
+ }
+
+ if ((appParams.ParameterMask & BIT_READ) != 0) {
+ if (V){
+ Log.v(TAG, " ::Read Status:: " + readStatus);
+ }
+ if (readStatus.equalsIgnoreCase("1")) {
+ emailMsg.setRead("yes");
+ } else {
+ emailMsg.setRead("no");
+ }
+ }
+
+ if ((appParams.ParameterMask & BIT_SENT) != 0) {
+ // TODO Get sent status?
+ if (folderName.equalsIgnoreCase("sent") || folderName.toUpperCase().contains("SENT")) {
+ emailMsg.setSent("yes");
+ } else {
+ emailMsg.setSent("no");
+ }
+ }
+
+ if ((appParams.ParameterMask & BIT_PROTECTED) != 0) {
+ emailMsg.setMsg_protected("no");
+ }
+
+ if ((appParams.ParameterMask & BIT_REPLYTO_ADDRESSING) != 0) {
+ //TODO need to test
+ if (V){
+ Log.v(TAG, " ::Reply To addressing:: " + replyToStr);
+ }
+ if (replyToStr !=null && replyToStr.length() != 0){
+ if (replyToStr.contains("")){
+ String replyToStrArr[] = replyToStr.split("");
+ replyToStr = replyToStrArr[0];
+ }
+ emailMsg.setReplyTo_addressing(replyToStr);
+ } else{
+ emailMsg.setReplyTo_addressing(emailMsg.getSender_addressing());
+ }
+ }
+ return emailMsg;
+ }
+
+ public static String bldEmailBmsg(long msgHandle, BluetoothMasMessageRsp rsp, Context context,
+ String remoteDeviceName) {
+ String str = null;
+ //Query the message table for obtaining the message related details
+ Cursor cr1 = null;
+ int folderId;
+ String timeStamp = null;
+ String subjectText = null;
+ Uri uri1 = Uri.parse("content://com.android.email.provider/message");
+ String whereClause = " _id = " + msgHandle;
+
+ // Create a bMessage
+ BmessageConsts bmsg = new BmessageConsts();
+
+ cr1 = context.getContentResolver().query(uri1, null, whereClause, null,
+ null);
+ if (cr1 != null && cr1.getCount() > 0) {
+ cr1.moveToFirst();
+ folderId = cr1.getInt(cr1.getColumnIndex("mailboxKey"));
+ String containingFolder = getContainingFolderEmail(folderId, context);
+ timeStamp = cr1.getString(cr1.getColumnIndex("timeStamp"));
+ subjectText = cr1.getString(cr1.getColumnIndex("subject"));
+
+ // TODO Get Current type
+ bmsg.setType("EMAIL");
+
+ bmsg.setBmsg_version("1.0");
+ if (cr1.getString(cr1.getColumnIndex("flagRead")).equalsIgnoreCase("1")) {
+ bmsg.setStatus("READ");
+ } else {
+ bmsg.setStatus("UNREAD");
+ }
+
+ bmsg.setFolder(MapUtilsConsts.Telecom + "/" + MapUtilsConsts.Msg +
+ "/" + containingFolder);
+
+ bmsg.setVcard_version("2.1");
+
+ String senderName = null;
+ if((senderName = cr1.getString(cr1.getColumnIndex("fromList"))) != null ){
+ if(senderName.contains("")){
+ String[] senderStr = senderName.split("");
+ if(senderStr !=null && senderStr.length > 0){
+ bmsg.setOriginatorVcard_name(senderStr[1].trim());
+ bmsg.setOriginatorVcard_email(senderStr[0].trim());
+ }
+ }
+ else{
+ bmsg.setOriginatorVcard_name(senderName.trim());
+ bmsg.setOriginatorVcard_email(senderName.trim());
+ }
+ }
+ String recipientName = null;
+ String multiRecepients = null;
+ if((recipientName = cr1.getString(cr1.getColumnIndex("toList"))) != null){
+ if(recipientName.contains("")){
+ String[] recepientStr = recipientName.split("");
+ if(recepientStr !=null && recepientStr.length > 0){
+ bmsg.setRecipientVcard_name(recepientStr[1].trim());
+ bmsg.setRecipientVcard_email(recepientStr[0].trim());
+ }
+ }
+ else if(recipientName.contains("")){
+ multiRecepients = recipientName.replace('', ';');
+ if(multiRecepients != null){
+ if (V){
+ Log.v(TAG, " ::Recepient name :: " + multiRecepients);
+ }
+ bmsg.setRecipientVcard_name(multiRecepients.trim());
+ bmsg.setRecipientVcard_email(multiRecepients.trim());
+ }
+ }
+ else{
+ bmsg.setRecipientVcard_name(recipientName.trim());
+ bmsg.setRecipientVcard_email(recipientName.trim());
+ }
+ }
+ }
+ if (cr1 != null) {
+ cr1.close();
+ }
+
+ //Query the body table for obtaining the message body
+ Cursor cr2 = null;
+ Uri uri2 = Uri.parse("content://com.android.email.provider/body");
+ String whereStr = " messageKey = " + msgHandle;
+ cr2 = context.getContentResolver().query(uri2, null, whereStr, null,
+ null);
+ if (cr2 != null) {
+ StringBuilder sb = new StringBuilder();
+ String emailBody = null;
+
+ //Set content type according to content from body table
+ String contenType = "text/plain";
+ if (cr2.getCount() > 0) {
+ cr2.moveToFirst();
+ emailBody = cr2.getString(cr2.getColumnIndex("textContent"));
+ if (emailBody == null || emailBody.length() == 0){
+ String msgBody = cr2.getString(cr2.getColumnIndex("htmlContent"));
+ if (msgBody != null){
+ CharSequence msgText = Html.fromHtml(msgBody);
+ emailBody = msgText.toString();
+ }
+ }
+ }
+
+ Date date = new Date(Long.parseLong(timeStamp));
+ sb.append("From:").append(bmsg.getOriginatorVcard_email()).append("\r\n");
+ sb.append("To:").append(bmsg.getRecipientVcard_email()).append("\r\n");
+ if (remoteDeviceName != null && remoteDeviceName.startsWith(BMW)) {
+ sb.append("Mime-Version: 1.0").append("\r\n");
+ sb.append("Content-Type: text/plain; charset=\"UTF-8\"").append("\r\n");
+ sb.append("Content-Transfer-Encoding: 8bit").append("\r\n");
+ // BMW 14692 carkit accepts Date format in "EEE, dd MMM yyyy HH:mm:ss Z"
+ sb.append("Date:");
+ sb.append(new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US).format(date));
+ sb.append("\r\n");
+ sb.append("Subject:").append(subjectText).append("\r\n").append("\r\n");
+ sb.append(emailBody).append("\r\n");
+ } else {
+ Random randomGenerator = new Random();
+ int randomInt = randomGenerator.nextInt(1000);
+ String boundary = "MessageBoundary."+randomInt;
+ if (emailBody != null){
+ while (emailBody.contains(boundary)){
+ randomInt = randomGenerator.nextInt(1000);
+ boundary = "MessageBoundary."+randomInt;
+ }
+ }
+ sb.append("Date: ").append(date.toString()).append("\r\n");
+ sb.append("Subject:").append(subjectText).append("\r\n");
+ sb.append("Mime-Version: 1.0").append("\r\n");
+ sb.append(
+ "Content-Type: multipart/mixed; boundary=\""+boundary+"\"")
+ .append("\r\n");
+ sb.append("Content-Transfer-Encoding: 8bit").append("\r\n")
+ .append("\r\n");
+ sb.append("MIME Message").append("\r\n");
+ sb.append("--"+boundary).append("\r\n");
+ sb.append("Content-Type: text/plain; charset=\"UTF-8\"").append("\r\n");
+ sb.append("Content-Transfer-Encoding: 8bit").append("\r\n");
+ sb.append("Content-Disposition:inline").append("\r\n")
+ .append("\r\n");
+ sb.append(emailBody).append("\r\n");
+ sb.append("--"+boundary+"--").append("\r\n");
+ }
+
+ bmsg.setBody_msg(sb.toString());
+ bmsg.setBody_length(sb.length() + 22);
+ // Send a bMessage
+ if (V){
+ Log.v(TAG, "bMessageEmail test\n");
+ Log.v(TAG, "=======================\n\n");
+ }
+ str = MapUtils.toBmessageEmail(bmsg);
+ cr2.close();
+ }
+ if (V){
+ Log.v(TAG, "======FINAL BMSG=================\n\n");
+ Log.v(TAG, str);
+ Log.v(TAG, "\n\n");
+ }
+ return str;
+ }
+
+ /**
+ * Get the folder name (MAP representation) for Email based on the
+ * mailboxKey value in message table
+ */
+ public static String getContainingFolderEmail(int folderId, Context context) {
+ Cursor cr;
+ String folderName = null;
+ String whereClause = "_id = " + folderId;
+ cr = context.getContentResolver().query(
+ Uri.parse("content://com.android.email.provider/mailbox"),
+ null, whereClause, null, null);
+ if (cr != null) {
+ if (cr.getCount() > 0) {
+ cr.moveToFirst();
+ folderName = cr.getString(cr.getColumnIndex("displayName"));
+ }
+ cr.close();
+ }
+ return folderName;
+ }
+
+ public static final String AUTHORITY = "com.android.email.provider";
+ public static final Uri EMAIL_URI = Uri.parse("content://" + AUTHORITY);
+ public static final Uri EMAIL_ACCOUNT_URI = Uri.withAppendedPath(EMAIL_URI, "account");
+ public static final Uri EMAIL_BOX_URI = Uri.withAppendedPath(EMAIL_URI, "mailbox");
+ public static final Uri EMAIL_MESSAGE_URI = Uri.withAppendedPath(EMAIL_URI, "message");
+ public static final String RECORD_ID = "_id";
+ public static final String DISPLAY_NAME = "displayName";
+ public static final String SERVER_ID = "serverId";
+ public static final String ACCOUNT_KEY = "accountKey";
+ public static final String MAILBOX_KEY = "mailboxKey";
+ public static final String EMAIL_ADDRESS = "emailAddress";
+ public static final String IS_DEFAULT = "isDefault";
+ public static final String TYPE = "type";
+ public static final String[] EMAIL_BOX_PROJECTION = new String[] {
+ RECORD_ID, DISPLAY_NAME, ACCOUNT_KEY, TYPE
+ };
+ public static final int EMAIL_BOX_COLUMN_RECORD_ID = 0;
+ public static final int EMAIL_BOX_COLUMN_DISPLAY_NAME = 1;
+ public static final int EMAIL_BOX_COLUMN_ACCOUNT_KEY = 2;
+ public static final int EMAIL_BOX_COLUMN_TYPE = 3;
+ public static final String[] EMAIL_MESSAGE_PROJECTION = new String[] {
+ RECORD_ID, MAILBOX_KEY, ACCOUNT_KEY
+ };
+ public static final int MSG_COL_RECORD_ID = 0;
+ public static final int MSG_COL_MAILBOX_KEY = 1;
+ public static final int MSG_COL_ACCOUNT_KEY = 2;
+ private static final String[] ACCOUNT_ID_PROJECTION = new String[] {
+ RECORD_ID, EMAIL_ADDRESS, IS_DEFAULT
+ };
+ private static final String[] ACCOUNT_ID_NAME_PROJECTION = new String[] {
+ RECORD_ID, EMAIL_ADDRESS, DISPLAY_NAME
+ };
+ // Types of mailboxes. From EmailContent.java
+ // inbox
+ public static final int TYPE_INBOX = 0;
+ // draft
+ public static final int TYPE_DRAFT = 3;
+ // outbox
+ public static final int TYPE_OUTBOX = 4;
+ // sent
+ public static final int TYPE_SENT = 5;
+ // deleted
+ public static final int TYPE_DELETED = 6;
+
+ public static HashMap<Long, Integer> sAccToMas = new HashMap<Long, Integer>();
+ public static HashMap<Integer, Long> sMasToAcc = new HashMap<Integer, Long>();
+
+ public static void clearMapTable() {
+ sAccToMas.clear();
+ sMasToAcc.clear();
+ }
+
+ public static void updateMapTable(long accountId, int masId) {
+ if (sAccToMas.containsKey(accountId)) {
+ sAccToMas.remove(accountId);
+ }
+ if (sMasToAcc.containsKey(masId)) {
+ sMasToAcc.remove(masId);
+ }
+ sAccToMas.put(accountId, masId);
+ sMasToAcc.put(masId, accountId);
+ }
+
+ public static long getAccountId(int masId) {
+ Long accountId = sMasToAcc.get(masId);
+ return (accountId != null) ? accountId : -1;
+ }
+
+ public static int getMasId(long accountId) {
+ Integer masId = sAccToMas.get(accountId);
+ return (masId != null) ? masId : -1;
+ }
+
+ public static void removeMasIdIfNotPresent(List<Long> accountIdList) {
+ Collection<Long> oldList = sMasToAcc.values();
+ ArrayList<Long> toRemove = new ArrayList<Long>();
+ for (long oldId : oldList) {
+ if (!accountIdList.contains(oldId)) {
+ // remove it
+ toRemove.add(oldId);
+ }
+ }
+ for (long accountId : toRemove) {
+ Integer masId = sAccToMas.remove(accountId);
+ if (masId != null) {
+ sMasToAcc.remove(masId);
+ }
+ }
+ }
+
+ public static int countEmailAccount(Context context) {
+ return SqlHelper.count(context, EMAIL_ACCOUNT_URI, null, null);
+ }
+
+ /**
+ * Returns whether Email account exists
+ * @param context the calling Context
+ * @return true if any Email account exists; false otherwise
+ */
+ public static boolean hasEmailAccount(Context context) {
+ int numAccounts = SqlHelper.count(context, EMAIL_ACCOUNT_URI, null, null);
+ if (numAccounts > 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public static boolean hasEmailAccount(Context context, long accountId) {
+ String where = RECORD_ID + "=" + accountId;
+ int numAccounts = SqlHelper.count(context, EMAIL_ACCOUNT_URI, where, null);
+ if (numAccounts > 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the first Email account id that satisfies where condition
+ * @param context the calling Context
+ * @param where the condition respect to {@link #RECORD_ID}, {@link #EMAIL_ADDRESS}, {@link #IS_DEFAULT}
+ * @return Email account id
+ */
+ public static long getEmailAccountId(Context context, String where) {
+ if (V) Log.v(TAG, "getEmailAccountId(" + where + ")");
+ long id = -1;
+ Cursor cursor = context.getContentResolver().query(EMAIL_ACCOUNT_URI,
+ ACCOUNT_ID_PROJECTION, where, null, null);
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ id = cursor.getLong(0);
+ }
+ cursor.close();
+ }
+ if (V) Log.v(TAG, "id = " + id);
+ return id;
+ }
+
+ /**
+ * Returns the first Email account id name that satisfies where condition
+ * @param context the calling Context
+ * @param where the condition respect to {@link #RECORD_ID}, {@link #EMAIL_ADDRESS}, {@link #IS_DEFAULT}
+ * @return Email account id Email
+ */
+ public static String getEmailAccountIdEmail(Context context, String where) {
+ if (V) Log.v(TAG, "getEmailAccountIdName(" + where + ")");
+ String idEmail = null;
+ Cursor cursor = context.getContentResolver().query(EMAIL_ACCOUNT_URI,
+ ACCOUNT_ID_NAME_PROJECTION, where, null, null);
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ idEmail = cursor.getString(1);
+ }
+ cursor.close();
+ }
+ if (V) Log.v(TAG, "idEmail = " + idEmail);
+ return idEmail;
+ }
+
+ /**
+ * Returns the first Email account id name that satisfies where condition
+ * @param context the calling Context
+ * @param where the condition respect to {@link #RECORD_ID}, {@link #EMAIL_ADDRESS}, {@link #IS_DEFAULT}
+ * @return Email account id Display Name
+ */
+ public static String getEmailAccountDisplayName(Context context, String where) {
+ if (V) Log.v(TAG, "getEmailAccountIdName(" + where + ")");
+ String displayName = null;
+ Cursor cursor = context.getContentResolver().query(EMAIL_ACCOUNT_URI,
+ ACCOUNT_ID_NAME_PROJECTION, where, null, null);
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ displayName = cursor.getString(2);
+ }
+ cursor.close();
+ }
+ if (V) Log.v(TAG, "displayName = " + displayName);
+ return displayName;
+ }
+
+
+ /**
+ * Returns the default Email account id; the first account id if no default account
+ * @param context the calling Context
+ * @return the default Email account id
+ */
+ public static long getDefaultEmailAccountId(Context context) {
+ if (V) Log.v(TAG, "getDefaultEmailAccountId()");
+ long id = getEmailAccountId(context, IS_DEFAULT + "=1");
+ if (id == -1) {
+ id = getEmailAccountId(context, null);
+ }
+ if (V) Log.v(TAG, "id = " + id);
+ return id;
+ }
+
+ public static List<Long> getEmailAccountIdList(Context context) {
+ if (V) Log.v(TAG, "getEmailAccountIdList()");
+ long id = -1;
+ ArrayList<Long> list = new ArrayList<Long>();
+ Cursor cursor = context.getContentResolver().query(EMAIL_ACCOUNT_URI,
+ ACCOUNT_ID_PROJECTION, null, null, null);
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ do {
+ id = cursor.getLong(0);
+ list.add(id);
+ if (V) Log.v(TAG, "id = " + id);
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ }
+ return list;
+ }
+
+ /**
+ * Return Email sub folder list for the id and serverId path
+ * @param context the calling Context
+ * @param id the email account id
+ * @return the list of server id's of Email sub folder
+ */
+ public static List<String> getEmailFolderListAtPath(Context context, long id, String path) {
+ if (V) Log.v(TAG, "getEmailFolderListAtPath: id = " + id + "path: " + path);
+ StringBuilder sb = new StringBuilder();
+ if (id > 0) {
+ sb.append(ACCOUNT_KEY);
+ sb.append("=");
+ sb.append(id);
+ sb.append(" AND ");
+ }
+ //Return Default List for no path
+ if(path.equals("")) {
+ sb.append(SERVER_ID);
+ sb.append("=");
+ sb.append(DISPLAY_NAME);
+ }
+ else {
+ sb.append(SERVER_ID);
+ sb.append(" LIKE '");
+ sb.append(path);
+ sb.append("/%'");
+ }
+ return SqlHelper.getListForColumn(context, EMAIL_BOX_URI, SERVER_ID, sb.toString(), null);
+ }
+ /**
+ * Return Email folder list for the id
+ * @param context the calling Context
+ * @param id the email account id
+ * @return the list of Email folder
+ */
+ public static List<String> getEmailFolderList(Context context, long id) {
+ if (V) Log.v(TAG, "getEmailFolderList: id = " + id);
+ StringBuilder sb = new StringBuilder();
+ if (id > 0) {
+ sb.append(ACCOUNT_KEY);
+ sb.append("=");
+ sb.append(id);
+ }
+ return SqlHelper.getListForColumn(context, EMAIL_BOX_URI, DISPLAY_NAME, sb.toString(), null);
+ }
+
+ /**
+ * Return folder name for the type of mailbox
+ * @param context the calling Context
+ * @param id the email account id
+ * @param type
+ * @return
+ */
+ public static String getFolderForType(Context context, long id, int type) {
+ if (V) Log.v(TAG, "getFolderForType: id = " + id + ", type = " + type);
+ StringBuilder sb = new StringBuilder();
+ if (id > 0) {
+ sb.append(ACCOUNT_KEY);
+ sb.append("=");
+ sb.append(id);
+ sb.append(" AND ");
+ }
+ sb.append(TYPE);
+ sb.append("=");
+ sb.append(type);
+ return SqlHelper.getFirstValueForColumn(context, EMAIL_BOX_URI, DISPLAY_NAME, sb.toString(), null);
+ }
+
+ /**
+ * Return list of folder names for the type of mailbox
+ * @param context the calling Context
+ * @param id the email account id
+ * @param type
+ * @return
+ */
+ public static List<String> getFoldersForType(Context context, long id, int type) {
+ if (V) Log.v(TAG, "getFolderForType: id = " + id + ", type = " + type);
+ StringBuilder sb = new StringBuilder();
+ if (id > 0) {
+ sb.append(ACCOUNT_KEY);
+ sb.append("=");
+ sb.append(id);
+ sb.append(" AND ");
+ }
+ sb.append(TYPE);
+ sb.append("=");
+ sb.append(type);
+ return SqlHelper.getListForColumn(context, EMAIL_BOX_URI, DISPLAY_NAME, sb.toString(), null);
+ }
+
+ public static int getTypeForFolder(Context context, long id, String folderName) {
+ if (V) Log.v(TAG, "getTypeForFolder: id = " + id + ", folderName = " + folderName);
+ StringBuilder sb = new StringBuilder();
+ if (id > 0) {
+ sb.append(ACCOUNT_KEY);
+ sb.append("=");
+ sb.append(id);
+ sb.append(" AND ");
+ }
+ sb.append(DISPLAY_NAME);
+ sb.append("=");
+ sb.append("'"+folderName+"'");
+ return SqlHelper.getFirstIntForColumn(context, EMAIL_BOX_URI, TYPE, sb.toString(), null);
+ }
+}
diff --git a/src/org/codeaurora/bluetooth/map/MapUtils/MapUtils.java b/src/org/codeaurora/bluetooth/map/MapUtils/MapUtils.java
new file mode 100644
index 0000000..6def60e
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/MapUtils/MapUtils.java
@@ -0,0 +1,1986 @@
+/*
+ * Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map.MapUtils;
+
+import android.content.Context;
+import com.android.vcard.VCardProperty;
+import com.android.vcard.VCardInterpreter;
+import com.android.vcard.VCardParser;
+import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
+import com.android.vcard.exception.VCardException;
+import com.android.vcard.exception.VCardVersionException;
+import android.util.Log;
+import android.util.Xml;
+import java.io.UnsupportedEncodingException;
+
+import org.codeaurora.bluetooth.map.BluetoothMasService;
+
+import org.xmlpull.v1.XmlSerializer;
+import com.android.internal.util.FastXmlSerializer;
+
+import java.io.IOException;
+import java.io.StringBufferInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.StringWriter;
+import java.util.List;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+
+import static com.android.vcard.VCardConstants.PROPERTY_EMAIL;
+import static com.android.vcard.VCardConstants.PROPERTY_FN;
+import static com.android.vcard.VCardConstants.PROPERTY_N;
+import static com.android.vcard.VCardConstants.PROPERTY_TEL;
+import static com.android.vcard.VCardConstants.VERSION_V21;
+import static com.android.vcard.VCardConstants.VERSION_V30;
+
+/**
+ * MapUtils is a class of utility methods that provide routines for converting
+ * data to either XML or bMessage formats. The class shall also support parsing
+ * XML and bMessage formatted data.
+ * <p>
+ * The following methods are currently supported:
+ * <p>
+ * folderListingXML()
+ *
+ * @version 0.1
+ *
+ */
+public class MapUtils {
+ public static final String TAG = "MapUtils";
+ public static final boolean V = BluetoothMasService.VERBOSE;
+ private static final String CRLF = "\r\n";
+
+ /**
+ * folderListingXML
+ *
+ * This method takes a list of folder names and returns a String with the
+ * XML version of the List
+ *
+ * @param list
+ * An array of strings where each element represents a folder
+ * name
+ * @return This method returns either null or a String
+ */
+ public static String folderListingXML(List<String> list) {
+ String str = "<?xml version=\"1.0\"?><!DOCTYPE folder-listing SYSTEM \"obex-folder-listing.dtd\"><folder-listing version=\"1.0\">";
+
+ for (String s : list) {
+ str += "<folder name=\"";
+ str += s;
+ str += "\"/>";
+ }
+
+ str += "</folder-listing>";
+
+ return str;
+ }
+
+ /**
+ * messageListingXML
+ *
+ * This method takes a list of message objects and returns a String with the
+ * XML version of the List
+ *
+ * @param list
+ * An array of message objects where each element represents a
+ * message
+ * @return This method returns either null or a String
+ */
+ public static String messageListingXML(List<MsgListingConsts> list) {
+ XmlSerializer serializer = new FastXmlSerializer();
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ OutputStreamWriter myOutputStreamWriter = null;
+ try {
+ myOutputStreamWriter = new OutputStreamWriter(outputStream, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, "Failed to encode: charset=" + "UTF-8");
+ return null;
+ }
+ try {
+ String str1;
+ String str2 = "<?xml version=\"1.0\"?>";
+ serializer.setOutput(myOutputStreamWriter);
+ serializer.startDocument("UTF-8", true);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ serializer.text("\n");
+ serializer.startTag(null, "MAP-msg-listing");
+ serializer.attribute(null, "version", "1.0");
+ for (MsgListingConsts msg : list) {
+ serializer.startTag(null, "msg");
+
+ serializer.attribute(null, "handle", ("" + msg.msg_handle));
+ if (msg.sendSubject == true) {
+ if (msg.subject == null){
+ serializer.attribute(null, "subject", "");
+ } else {
+ serializer.attribute(null, "subject", msg.subject);
+ }
+
+ }
+ if (msg.datetime != null) {
+ serializer.attribute(null, "datetime", msg.datetime);
+ }
+ if (msg.sender_name != null) {
+ serializer.attribute(null, "sender_name", msg.sender_name);
+ }
+
+ if (msg.sender_addressing != null) {
+ serializer.attribute(null, "sender_addressing",
+ msg.sender_addressing);
+ }
+
+ if (msg.replyto_addressing != null) {
+ serializer.attribute(null, "replyto_addressing",
+ msg.replyto_addressing);
+ }
+
+ if (msg.recepient_name != null) {
+ serializer.attribute(null, "recipient_name",
+ msg.recepient_name);
+ }
+ if (msg.sendRecipient_addressing == true) {
+ if (msg.recepient_addressing != null) {
+ serializer.attribute(null, "recipient_addressing",
+ msg.recepient_addressing);
+ } else {
+ serializer.attribute(null, "recipient_addressing", "");
+ }
+ }
+ if (msg.type != null) {
+ serializer.attribute(null, "type", msg.type);
+ }
+ if (msg.size != -1) {
+ serializer.attribute(null, "size", ("" + msg.size));
+ }
+
+ if (msg.contains_text != null) {
+ serializer.attribute(null, "text", msg.contains_text);
+ }
+
+ if (msg.reception_status != null) {
+ serializer.attribute(null, "reception_status",
+ msg.reception_status);
+ }
+
+ if (msg.attachment_size != -1) {
+ serializer.attribute(null, "attachment_size",
+ ("" + Integer.toString(msg.attachment_size)));
+ }
+
+ if (msg.priority != null) {
+ serializer.attribute(null, "priority", msg.priority);
+ }
+
+ if (msg.read != null) {
+ serializer.attribute(null, "read", msg.read);
+ }
+
+ if (msg.sent != null) {
+ serializer.attribute(null, "sent", msg.sent);
+ }
+
+ if (msg.msg_protected != null) {
+ serializer.attribute(null, "protected", msg.msg_protected);
+ }
+
+ serializer.endTag(null, "msg");
+
+ }
+ serializer.endTag(null, "MAP-msg-listing");
+ serializer.endDocument();
+ try {
+ str1 = outputStream.toString("UTF-8");
+ if (V) Log.v(TAG, "Printing XML-Converted String: " + str1);
+ int line1 = 0;
+ line1 = str1.indexOf("\n");
+ str2 += str1.substring(line1 + 1);
+ if (list.size() > 0) {
+ int indxHandle = str2.indexOf("msg handle");
+ String str3 = "<" + str2.substring(indxHandle);
+ str2 = str2.substring(0, (indxHandle - 1)) + str3;
+ }
+ return str2;
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, "Failed to encode: charset=" + "UTF-8");
+ return null;
+ }
+ } catch (IllegalArgumentException e) {
+
+ e.printStackTrace();
+ } catch (IllegalStateException e) {
+
+ e.printStackTrace();
+ } catch (IOException e) {
+
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * msgListingGetHdrXML
+ *
+ * This method returns a String with the XML header
+ *
+ * @return This method returns a String
+ */
+ public static String msgListingGetHdrXML() {
+ String str1 = "<MAP-msg-listing version = \"1.0\">\n";
+ return str1;
+ }
+
+ /**
+ * msgListingGetFooterXML
+ *
+ * This method returns a String with the XML footer
+ *
+ * @return This method returns a String
+ */
+ public static String msgListingGetFooterXML() {
+ String str1 = "</MAP-msg-listing>\n";
+ return str1;
+ }
+
+ /**
+ * msgListingGetMsgsXML
+ *
+ * This method takes a list of message objects and returns a String with the
+ * XML messages
+ *
+ * @param list
+ * An array of message objects where each element represents a
+ * message
+ * @return This method returns either null or a String
+ */
+ public static String msgListingGetMsgsXML(List<MsgListingConsts> list) {
+ XmlSerializer serializer = Xml.newSerializer();
+ StringWriter writer = new StringWriter();
+ try {
+ String str1;
+ serializer.setOutput(writer);
+ serializer.startDocument("", false);
+ serializer.text("\n");
+ for (MsgListingConsts msg : list) {
+ serializer.startTag("", "msg");
+ serializer.attribute("", "handle", ("" + msg.msg_handle));
+ if (msg.subject != null) {
+ serializer.attribute("", "subject", msg.subject);
+ } else {
+
+ }
+ if (msg.datetime != null) {
+ serializer.attribute("", "datetime", msg.datetime);
+ } else {
+
+ }
+ if (msg.sender_name != null) {
+ serializer.attribute("", "sender_name", msg.sender_name);
+ } else {
+
+ }
+
+ if (msg.sender_addressing != null) {
+ serializer.attribute("", "sender_addressing",
+ msg.sender_addressing);
+ } else {
+
+ }
+ if (msg.recepient_name != null) {
+ serializer.attribute("", "recipient_name",
+ msg.recepient_name);
+ } else {
+
+ }
+ if (msg.recepient_addressing != null) {
+ serializer.attribute("", "recipient_addressing",
+ msg.recepient_addressing);
+ } else {
+
+ }
+ if (msg.type != null) {
+ serializer.attribute("", "type", msg.type);
+ } else {
+
+ }
+ if (msg.size != 0) {
+ serializer.attribute("", "size", ("" + msg.size));
+ } else {
+
+ }
+ if (msg.attachment_size != -1) {
+ serializer.attribute("", "attachment_size",
+ ("" + Integer.toString(msg.attachment_size)));
+ } else {
+
+ }
+ if (msg.contains_text != null) {
+ serializer.attribute("", "text", msg.contains_text);
+ } else {
+
+ }
+ if (msg.priority != null) {
+ serializer.attribute("", "priority", msg.priority);
+ } else {
+
+ }
+ if (msg.read != null) {
+ serializer.attribute("", "read", msg.read);
+ } else {
+
+ }
+ if (msg.sent != null) {
+ serializer.attribute("", "sent", msg.sent);
+ } else {
+
+ }
+
+ if (msg.replyto_addressing != null) {
+ serializer.attribute("", "replyto_addressing",
+ msg.replyto_addressing);
+ } else {
+
+ }
+
+ serializer.endTag("", "msg");
+
+ }
+ serializer.endDocument();
+ str1 = writer.toString();
+
+ int line1 = 0;
+ line1 = str1.indexOf("\n");
+ if (line1 > 0) {
+ return (str1.substring((line1)));
+ } else {
+ return str1;
+ }
+
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * mapEventReportXML
+ *
+ * This method takes a list of map event report object and returns a String
+ * with the XML message
+ *
+ * @param type
+ * Report type (e.g. NewMessage)
+ * @param handle
+ * handle created by caller
+ * @param folder
+ * Path to folder to use
+ * @param Oldfolder
+ * Path to old folder to use
+ * @param msgType
+ * Type of message (SMS_GSM, SMS_CDMA)
+ *
+ * @return This method returns either null or a String
+ */
+ public static String mapEventReportXML(String type, String handle, String folder,
+ String oldFolder, String msgType) {
+ XmlSerializer serializer = Xml.newSerializer();
+ StringWriter writer = new StringWriter();
+
+ try {
+ String str1;
+ serializer.setOutput(writer);
+ serializer.startDocument("", false);
+ serializer.text("\n");
+ serializer.startTag("", "MAP-event-report");
+ serializer.attribute("", "version", "1.0");
+ serializer.text("\n");
+
+ serializer.startTag("", "event");
+ if (type != null) {
+ serializer.attribute("", "type", ("" + type));
+ } else {
+
+ }
+ if (handle != null) {
+ serializer.attribute("", "handle", ("" + handle));
+ } else {
+
+ }
+ if (folder != null) {
+ serializer.attribute("", "folder", ("" + folder));
+ } else {
+
+ }
+ if (oldFolder != null) {
+ serializer.attribute("", "old_folder", ("" + oldFolder));
+ } else {
+
+ }
+
+ if (msgType != null) {
+ serializer.attribute("", "msg_type", ("" + msgType));
+ } else {
+
+ }
+ serializer.endTag("", "event");
+ serializer.text("\n");
+ serializer.endTag("", "MAP-event-report");
+ serializer.endDocument();
+ str1 = writer.toString();
+ int line1 = 0;
+ line1 = str1.indexOf("\n");
+ if (line1 > 0) {
+ int index = str1.indexOf("event type");
+ String str2 = "<" + str1.substring(index);
+ str1 = "<MAP-event-report version=\"1.0\">" + "\n" + str2;
+ return str1;
+ } else {
+ return str1;
+ }
+
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ /**
+ * toBmessageSMS
+ *
+ * This method takes as input a list of BmessageConsts objects and creates a
+ * String in the bMessage format.
+ *
+ * @param list
+ * An array of message objects where each element represents a
+ * message
+ * @return This method returns either null or a String
+ */
+ public static String toBmessageSMS(BmessageConsts bmsg) {
+ StringBuilder sb = new StringBuilder();
+
+ try {
+ sb.append("BEGIN:BMSG");
+ sb.append("\r\n");
+ if (bmsg.bmsg_version != null) {
+ sb.append("VERSION:").append(bmsg.bmsg_version).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.status != null) {
+ sb.append("STATUS:").append(bmsg.status).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.type != null) {
+ sb.append("TYPE:").append(bmsg.type).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.folder != null) {
+ sb.append("FOLDER:").append(bmsg.folder).append("\r\n");
+ } else {
+
+ }
+
+ // Originator
+ sb.append("BEGIN:VCARD").append("\r\n");
+
+ if (bmsg.vcard_version != null) {
+ sb.append("VERSION:").append(bmsg.vcard_version).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.originator_vcard_name != null) {
+ sb.append("N:").append(bmsg.originator_vcard_name)
+ .append("\r\n");
+ } else {
+
+ }
+ if (bmsg.originator_vcard_phone_number != null) {
+ sb.append("TEL:").append(bmsg.originator_vcard_phone_number)
+ .append("\r\n");
+ } else {
+
+ }
+
+ sb.append("END:VCARD").append("\r\n");
+ // End Originator
+
+ sb.append("BEGIN:BENV").append("\r\n");
+
+ // Recipient
+ sb.append("BEGIN:VCARD").append("\r\n");
+
+ if (bmsg.vcard_version != null) {
+ sb.append("VERSION:").append(bmsg.vcard_version).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.recipient_vcard_name != null) {
+ sb.append("N:").append(bmsg.recipient_vcard_name)
+ .append("\r\n");
+ } else {
+
+ }
+ if (bmsg.recipient_vcard_phone_number != null) {
+ sb.append("TEL:").append(bmsg.recipient_vcard_phone_number)
+ .append("\r\n");
+ } else {
+
+ }
+ sb.append("END:VCARD").append("\r\n");
+ // End Recipient
+
+ sb.append("BEGIN:BBODY").append("\r\n");
+
+ if (bmsg.body_charset != null) {
+ sb.append("CHARSET:").append(bmsg.body_charset)
+ .append("\r\n");
+ } else {
+
+ }
+
+ if (bmsg.body_encoding != null) {
+ sb.append("ENCODING:").append(bmsg.body_encoding)
+ .append("\r\n");
+ } else {
+
+ }
+ if (bmsg.body_length != 0) {
+ sb.append("LENGTH:").append(bmsg.body_length).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.body_msg != null) {
+ sb.append("BEGIN:MSG\r\n");
+ sb.append(bmsg.body_msg).append("\r\n");
+ sb.append("END:MSG\r\n");
+
+ } else {
+
+ }
+
+ sb.append("END:BBODY").append("\r\n");
+ sb.append("END:BENV").append("\r\n");
+
+ sb.append("END:BMSG");
+ sb.append("\r\n");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return sb.toString();
+
+ }
+
+ /**
+ * toBmessageMMS
+ *
+ * This method takes as input a list of BmessageConsts objects and creates a
+ * String in the bMessage format.
+ *
+ * @param list
+ * An array of message objects where each element represents a
+ * message
+ * @return This method returns either null or a String
+ */
+ public static String toBmessageMMS(BmessageConsts bmsg) {
+ StringBuilder sb = new StringBuilder();
+
+ try {
+ sb.append("BEGIN:BMSG");
+ sb.append("\r\n");
+ if (bmsg.bmsg_version != null) {
+ sb.append("VERSION:").append(bmsg.bmsg_version).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.status != null) {
+ sb.append("STATUS:").append(bmsg.status).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.type != null) {
+ sb.append("TYPE:").append(bmsg.type).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.folder != null) {
+ sb.append("FOLDER:").append(bmsg.folder).append("\r\n");
+ } else {
+
+ }
+
+ // Originator
+ sb.append("BEGIN:VCARD").append("\r\n");
+
+ if (bmsg.vcard_version != null) {
+ sb.append("VERSION:").append(bmsg.vcard_version).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.originator_vcard_name != null) {
+ sb.append("N:").append(bmsg.originator_vcard_name)
+ .append("\r\n");
+ } else {
+
+ }
+ if (bmsg.originator_vcard_phone_number != null) {
+ sb.append("TEL:").append(bmsg.originator_vcard_phone_number)
+ .append("\r\n");
+ } else {
+
+ }
+
+ sb.append("END:VCARD").append("\r\n");
+ // End Originator
+
+ sb.append("BEGIN:BENV").append("\r\n");
+
+ if ((bmsg.recipient_vcard_phone_number != null) &&
+ (bmsg.recipient_vcard_phone_number.contains(";"))) {
+ if (V) Log.v (TAG, "recipient_vcard_name:" + bmsg.recipient_vcard_name);
+ if (V) Log.v (TAG, "recipient_vcard_phone_number:" + bmsg.recipient_vcard_phone_number);
+ String numberTokens[] = bmsg.recipient_vcard_phone_number.split(";");
+ String nameTokens[] = bmsg.recipient_vcard_name.split(";");
+ if (V) Log.v (TAG, "Length:name:" + nameTokens.length + "number:" + numberTokens.length);
+ for (int i=0; i < numberTokens.length; i++) {
+ // Recipient
+ sb.append("BEGIN:VCARD").append("\r\n");
+
+ if (bmsg.vcard_version != null) {
+ sb.append("VERSION:").append(bmsg.vcard_version).append("\r\n");
+ } else {
+
+ }
+
+ if (nameTokens[i] != null) {
+ sb.append("N:").append(nameTokens[i])
+ .append("\r\n");
+ } else {
+
+ }
+
+ if (numberTokens[i] != null) {
+ sb.append("TEL:").append(numberTokens[i])
+ .append("\r\n");
+ } else {
+
+ }
+
+ sb.append("END:VCARD").append("\r\n");
+ // End Recipient
+ }
+ } else {
+ // Recipient
+ sb.append("BEGIN:VCARD").append("\r\n");
+
+ if (bmsg.vcard_version != null) {
+ sb.append("VERSION:").append(bmsg.vcard_version).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.recipient_vcard_name != null) {
+ sb.append("N:").append(bmsg.recipient_vcard_name)
+ .append("\r\n");
+ } else {
+
+ }
+ if (bmsg.recipient_vcard_phone_number != null) {
+ sb.append("TEL:").append(bmsg.recipient_vcard_phone_number)
+ .append("\r\n");
+ } else {
+
+ }
+ sb.append("END:VCARD").append("\r\n");
+ // End Recipient
+ }
+
+ sb.append("BEGIN:BBODY").append("\r\n");
+
+ sb.append("PARTID:26988").append("\r\n");
+
+ if (bmsg.body_encoding != null) {
+ sb.append("ENCODING:").append(bmsg.body_encoding)
+ .append("\r\n");
+ } else {
+
+ }
+ sb.append("CHARSET:UTF-8").append("\r\n");
+ sb.append("LANGUAGE:").append("\r\n");
+
+ if (bmsg.body_length != 0) {
+ sb.append("LENGTH:").append(bmsg.body_length).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.body_msg != null) {
+ sb.append("BEGIN:MSG\r\n");
+ sb.append(bmsg.body_msg).append("\r\n");
+ sb.append("END:MSG\r\n");
+
+ } else {
+
+ }
+
+ sb.append("END:BBODY").append("\r\n");
+ sb.append("END:BENV").append("\r\n");
+
+ sb.append("END:BMSG");
+ sb.append("\r\n");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * toBmessageEmail
+ *
+ * This method takes as input a list of BmessageConsts objects and creates a
+ * String in the bMessage format.
+ *
+ * @param list
+ * An array of message objects where each element represents a
+ * message
+ * @return This method returns either null or a String
+ */
+ public static String toBmessageEmail(BmessageConsts bmsg) {
+ StringBuilder sb = new StringBuilder();
+
+ try {
+ sb.append("BEGIN:BMSG");
+ sb.append("\r\n");
+ if (bmsg.bmsg_version != null) {
+ sb.append("VERSION:").append(bmsg.bmsg_version).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.status != null) {
+ sb.append("STATUS:").append(bmsg.status).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.type != null) {
+ sb.append("TYPE:").append(bmsg.type).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.folder != null) {
+ sb.append("FOLDER:").append(bmsg.folder).append("\r\n");
+ } else {
+
+ }
+
+ // Originator
+ sb.append("BEGIN:VCARD").append("\r\n");
+
+ if (bmsg.vcard_version != null) {
+ sb.append("VERSION:").append(bmsg.vcard_version).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.originator_vcard_name != null) {
+ sb.append("N:").append(bmsg.originator_vcard_name)
+ .append("\r\n");
+ } else {
+
+ }
+ sb.append("TEL:").append("\r\n");
+ if (bmsg.originator_vcard_email != null) {
+ sb.append("EMAIL:").append(bmsg.originator_vcard_email)
+ .append("\r\n");
+ } else {
+
+ }
+
+ sb.append("END:VCARD").append("\r\n");
+ // End Originator
+
+ sb.append("BEGIN:BENV").append("\r\n");
+
+ // Recipient
+ sb.append("BEGIN:VCARD").append("\r\n");
+
+ if (bmsg.vcard_version != null) {
+ sb.append("VERSION:").append(bmsg.vcard_version).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.recipient_vcard_name != null) {
+ sb.append("N:").append(bmsg.recipient_vcard_name)
+ .append("\r\n");
+ } else {
+
+ }
+ if (bmsg.recipient_vcard_name != null) {
+ sb.append("FN:").append(bmsg.recipient_vcard_name)
+ .append("\r\n");
+ } else {
+
+ }
+ sb.append("TEL:").append("\r\n");
+ if (bmsg.recipient_vcard_email != null) {
+ sb.append("EMAIL:").append(bmsg.recipient_vcard_email)
+ .append("\r\n");
+ } else {
+
+ }
+ sb.append("END:VCARD").append("\r\n");
+ // End Recipient
+
+ sb.append("BEGIN:BBODY").append("\r\n");
+
+ if (bmsg.body_encoding != null) {
+ sb.append("ENCODING:").append(bmsg.body_encoding)
+ .append("\r\n");
+ } else {
+ sb.append("ENCODING:8BIT").append("\r\n");
+ }
+
+ sb.append("CHARSET:UTF-8").append("\r\n");
+
+ if (bmsg.body_length != 0) {
+ sb.append("LENGTH:").append(bmsg.body_length).append("\r\n");
+ } else {
+
+ }
+ if (bmsg.body_msg != null) {
+ sb.append("BEGIN:MSG\r\n");
+ sb.append(bmsg.body_msg).append("\r\n");
+ sb.append("END:MSG\r\n");
+
+ } else {
+
+ }
+
+ sb.append("END:BBODY").append("\r\n");
+ sb.append("END:BENV").append("\r\n");
+
+ sb.append("END:BMSG");
+ sb.append("\r\n");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return sb.toString();
+
+ }
+
+
+ /**
+ * fromBmessageSMS
+ *
+ * This method takes as input a String in the bMessage format. It parses the
+ * String and loads a BmessageConsts object that is returned
+ *
+ * @param String
+ * - which is a bMessage formatted SMS message
+ * @return This method returns a BmessageConsts object
+ * @throws BadRequestException
+ */
+ public static BmessageConsts fromBmessageSMS(String bmsg) throws BadRequestException {
+ BmessageConsts bMsgObj = new BmessageConsts();
+ String vCard = fetchRecipientVcard(bmsg);
+
+ RecipientVCard recipient = parseVCard(vCard);
+ if (recipient.mTel.length() == 0) {
+ throw new BadRequestException("No TEL in vCard");
+ }
+ bMsgObj.setRecipientVcard_phone_number(recipient.mTel);
+ if (V) Log.v(TAG, "Tel: " + recipient.mTel);
+
+ // Extract vCard Version
+ bMsgObj.setVcard_version(recipient.mVersion);
+
+ // Extract bMessage Version
+ bMsgObj.setBmsg_version(fetchVersion(bmsg));
+
+ // Extract Message Status
+ bMsgObj.setStatus(fetchReadStatus(bmsg));
+
+ // Extract Message Type
+ bMsgObj.setType(fetchType(bmsg));
+
+ // Extract Message Folder
+ bMsgObj.setFolder(fetchFolder(bmsg));
+
+ // Fetch Message Length
+ bMsgObj.setBody_length(fetchBodyLength(bmsg));
+
+ // Extract Message
+ bMsgObj.setBody_msg(fetchBodyMsg(bmsg));
+
+ // Extract Message encoding
+ bMsgObj.setBody_encoding(fetchBodyEncoding(bmsg));
+
+ return bMsgObj;
+ }
+
+ /**
+ * fromBmessageMMS
+ *
+ * This method takes as input a String in the bMessage format. It parses the
+ * String and loads a BmessageConsts object that is returned
+ *
+ * @param String
+ * - which is a bMessage formatted SMS message
+ * @return This method returns a BmessageConsts object
+ * @throws BadRequestException
+ */
+ public static BmessageConsts fromBmessageMMS(String bmsg) throws BadRequestException {
+ BmessageConsts bMsgObj = new BmessageConsts();
+
+ String phoneNumber = null;
+ String vCard = fetchRecipientVcard(bmsg);
+ if (V) Log.v(TAG, "vCard Info: " + vCard);
+
+ RecipientVCard recipient = parseVCard(vCard);
+ if (recipient.mEmail.length() > 0) {
+ phoneNumber = recipient.mEmail;
+ } else if (recipient.mTel.length() > 0) {
+ phoneNumber = recipient.mTel;
+ } else {
+ throw new BadRequestException("No Email/Tel in vCard");
+ }
+
+ if (V) Log.v(TAG, "Email: " + recipient.mEmail);
+ if (V) Log.v(TAG, "Tel: " + recipient.mTel);
+ if (V) Log.v(TAG, "Recipeint address: " + phoneNumber);
+ bMsgObj.setRecipientVcard_phone_number(phoneNumber);
+
+ // Extract vCard Version
+ bMsgObj.setVcard_version(recipient.mVersion);
+
+ // Extract bMessage Version
+ bMsgObj.setBmsg_version(fetchVersion(bmsg));
+
+ // Extract Message Status
+ bMsgObj.setStatus(fetchReadStatus(bmsg));
+
+ // Extract Message Type
+ bMsgObj.setType(fetchType(bmsg));
+
+ // Extract Message Folder
+ bMsgObj.setFolder(fetchFolder(bmsg));
+
+ // Fetch Message Length
+ bMsgObj.setBody_length(fetchBodyLength(bmsg));
+
+ // Extract Message
+ bMsgObj.setBody_msg(fetchBodyMsgMMS(bmsg));
+
+ // Extract Message encoding
+ bMsgObj.setBody_encoding(fetchBodyEncoding(bmsg));
+
+ return bMsgObj;
+ }
+
+ /**
+ * fromBmessageEmail
+ *
+ * This method takes as input a String in the bMessage format. It parses the
+ * String and loads a BmessageConsts object that is returned
+ *
+ * @param String
+ * - which is a bMessage formatted Email message
+ * @return This method returns a BmessageConsts object
+ */
+
+ public static BmessageConsts fromBmessageEmail(Context context,
+ String bmsg, int mMasId) throws BadRequestException {
+ BmessageConsts bMsgObj = new BmessageConsts();
+ String vCard = fetchRecipientVcard(bmsg);
+ if (V) Log.v(TAG, "vCard Info: " + vCard);
+
+ RecipientVCard recipient = parseVCard(vCard);
+ if (recipient.mEmail.length() == 0) {
+ throw new BadRequestException("No Email in recipient vCard");
+ }
+ bMsgObj.setRecipientVcard_email(recipient.mEmail);
+ if (V) Log.v(TAG, "Email: " + recipient.mEmail);
+
+ String vcardOrig = fetchOriginatorVcardEmail(bmsg);
+ RecipientVCard originator = parseVCard(vcardOrig);
+ if (originator.mEmail.length() == 0) {
+ long accountId = -1;
+ accountId = EmailUtils.getAccountId(mMasId);
+ if ((accountId != -1) && (context != null)) {
+ originator.mEmail = EmailUtils.getEmailAccountIdEmail
+ (context,EmailUtils.RECORD_ID + "=" + accountId);
+ Log.v(TAG, "Orig Email inserted by MSE as: " + originator.mEmail);
+ originator.mFormattedName = EmailUtils.getEmailAccountDisplayName
+ (context,EmailUtils.RECORD_ID + "=" + accountId);
+ Log.v(TAG, "Orig F-Name inserted by MSE as: " + originator.mFormattedName);
+ }
+ if (originator.mEmail.length() == 0) {
+ throw new BadRequestException("No Email in originator vCard");
+ }
+ }
+ bMsgObj.setOriginatorVcard_email(originator.mEmail);
+ if (V) Log.v(TAG, "Orig Email: " + originator.mEmail);
+ if (originator.mFormattedName.length() > 0) {
+ if (V) Log.v(TAG, "Orig Formatted Name: " + originator.mFormattedName);
+ bMsgObj.setOriginatorVcard_name(originator.mFormattedName);
+ } else {
+ if (V) Log.v(TAG, "Orig Name: " + originator.mName);
+ bMsgObj.setOriginatorVcard_name(originator.mName);
+ }
+
+ if (V){
+ Log.v(TAG, "Bmsg version:: "+fetchVersion(bmsg));
+ }
+ // Extract bMessage Version
+ bMsgObj.setBmsg_version(fetchVersion(bmsg));
+
+ if (V){
+ Log.v(TAG, "Read status:: "+fetchReadStatus(bmsg));
+ }
+ // Extract Message Status
+ bMsgObj.setStatus(fetchReadStatus(bmsg));
+
+ if (V){
+ Log.v(TAG, "Message Type:: "+fetchType(bmsg));
+ }
+ // Extract Message Type
+ bMsgObj.setType(fetchType(bmsg));
+
+ if (V){
+ Log.v(TAG, "Folder:: "+fetchFolder(bmsg));
+ }
+ // Extract Message Folder
+ bMsgObj.setFolder(fetchFolder(bmsg));
+
+ if (V){
+ Log.v(TAG, "body length:: "+fetchBodyLength(bmsg));
+ }
+ // Fetch Message Length
+ bMsgObj.setBody_length(fetchBodyLength(bmsg));
+ // Extract Message
+ bMsgObj.setBody_msg(fetchBodyEmail(bmsg));
+
+ if (V){
+ Log.v(TAG, "Message encoding:: "+fetchBodyEncoding(bmsg));
+ }
+ // Extract Message encoding
+ bMsgObj.setBody_encoding(fetchBodyEncoding(bmsg));
+
+ // Extract Subject of the email
+ bMsgObj.setSubject(fetchSubjectEmail(bmsg));
+
+ return bMsgObj;
+ }
+
+ /**
+ * fetchVcardEmail
+ *
+ * This method takes as input a vCard formatted String. It parses the String
+ * and returns the vCard Email as a String
+ *
+ * @param
+ * @return String This method returns a vCard Email String
+ */
+ private static String fetchVcardEmail(String vCard) {
+ int pos = vCard.indexOf(("EMAIL:"));
+ int beginVersionPos = pos + (("EMAIL:").length());
+ if (V){
+ Log.v(TAG,"Begin Version Position Email:: "+beginVersionPos);
+ }
+ int endVersionPos = vCard.indexOf("\n", beginVersionPos);
+ if (V){
+ Log.v(TAG,"End version Pos Email:: "+endVersionPos);
+ }
+ return vCard.substring(beginVersionPos, endVersionPos);
+ }
+
+ private static String fetchOriginatorEmail(String vCard) {
+ int pos = vCard.indexOf(("From:"));
+ int beginVersionPos = pos + (("From:").length());
+ int endVersionPos = vCard.indexOf(CRLF, beginVersionPos);
+ return vCard.substring(beginVersionPos, endVersionPos);
+ }
+
+ private static String fetchRecipientEmail(String vCard) {
+ int pos = vCard.indexOf(("To:"));
+ int beginVersionPos = pos + (("To:").length());
+ int endVersionPos = vCard.indexOf(CRLF, beginVersionPos);
+ return vCard.substring(beginVersionPos, endVersionPos);
+ }
+
+ private static String fetchRecepientVcardEmail(String bmsg) {
+ // Find the position of the first vCard in the string
+ int pos = bmsg.indexOf("BEGIN:BENV");
+ if (V){
+ Log.v(TAG, "vCard start position:: "+pos);
+ }
+ if (pos > 0) {
+ if (V){
+ Log.v(TAG, "vCard start position greater than 0::");
+ }
+ int beginVcardPos = pos + ("\r\n".length());
+ int endVcardPos = bmsg.indexOf("END:BENV");
+
+ return bmsg.substring(beginVcardPos, endVcardPos);
+
+ } else {
+ return "";
+ }
+ }
+
+ private static String fetchOriginatorVcardEmail(String bmsg) throws BadRequestException {
+ // Find the position of the first vCard in the string
+ int vCardBeginPos = bmsg.indexOf("BEGIN:VCARD");
+ if (vCardBeginPos == -1) {
+ // no vCard bad request
+ throw new BadRequestException("No Vcard");
+ }
+ if (V) Log.v(TAG, "vCard start position: " + vCardBeginPos);
+ int bEnvPos = bmsg.indexOf("BEGIN:BENV");
+ if (vCardBeginPos > bEnvPos) {
+ // the first vCard is not originator
+ return "";
+ }
+ int vCardEndPos = bmsg.indexOf("END:VCARD", vCardBeginPos);
+ if (vCardEndPos == -1) {
+ // no END:VCARD bad request
+ throw new BadRequestException("No END:VCARD");
+ }
+ vCardEndPos += "END:VCARD".length();
+
+ return bmsg.substring(vCardBeginPos, vCardEndPos);
+ }
+
+ private static String fetchSubjectEmail(String body) {
+ int pos = body.indexOf("Subject:");
+
+ if (pos > 0) {
+ int beginVersionPos = pos + (("Subject:").length());
+ int endVersionPos = body.indexOf("\n", beginVersionPos);
+ return body.substring(beginVersionPos, endVersionPos);
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * fetchVersion
+ *
+ * This method takes as input a String in the bMessage format. It parses the
+ * String and returns the bMessage version string
+ *
+ * @param
+ * @return String This method returns a Version String
+ */
+ private static String fetchVersion(String bmsg) {
+ int pos = bmsg.indexOf("VERSION:");
+ if (pos > 0) {
+
+ int beginVersionPos = pos + (("VERSION:").length());
+ int endVersionPos = bmsg.indexOf(CRLF, beginVersionPos);
+ return bmsg.substring(beginVersionPos, endVersionPos);
+
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * fetchOriginatorVcard
+ *
+ * This method takes as input a String in the bMessage format. It parses the
+ * String and returns the orginator vCard string
+ *
+ * @param
+ * @return String This method returns a vCard String
+ */
+ private static String fetchOriginatorVcard(String bmsg) {
+ // Find the position of the first vCard in the string
+ int pos = bmsg.indexOf("\r\nBEGIN:VCARD");
+ if (pos > 0) {
+ int beginVcardPos = pos + ("\r\n".length());
+ int endVcardPos = bmsg.indexOf("END:VCARD");
+
+ return bmsg.substring(beginVcardPos, endVcardPos);
+
+ } else {
+
+ return null;
+
+ }
+ }
+
+ /**
+ * fetchRecipientVcard
+ *
+ * This method takes as input a String in the bMessage format. It parses the
+ * String looking for the first envelope. Once it finds the envelop, it then
+ * looks for the first vCard and returns it as a String
+ *
+ * @param
+ * @return String This method returns a Vcard String
+ */
+ private static String fetchRecipientVcard(String bmsg) throws BadRequestException {
+ // Locate BENV
+ int locBENV = 0;
+ int pos = 0;
+ locBENV = bmsg.indexOf(CRLF + "BEGIN:BENV");
+ pos = bmsg.indexOf(CRLF + "BEGIN:VCARD", locBENV);
+ if (pos < 0) {
+ // no vCard in BENV
+ throw new BadRequestException("No vCard in BENV");
+ }
+ if (pos > 0) {
+ int beginVcardPos = pos + CRLF.length();
+ int endVcardPos = bmsg.indexOf("END:VCARD", pos);
+ if (endVcardPos < 0) {
+ // no END:VCARD in BENV
+ throw new BadRequestException("No END:VCARD in BENV");
+ }
+ endVcardPos += "END:VCARD".length();
+ return bmsg.substring(beginVcardPos, endVcardPos);
+
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * fetchReadStatus
+ *
+ * This method takes as input a String in the bMessage format. It parses the
+ * String and returns the bMessage read status String
+ *
+ * @param
+ * @return String This method returns a Read Status String
+ */
+ private static String fetchReadStatus(String bmsg) {
+ int pos = bmsg.indexOf("STATUS:");
+ if (pos > 0) {
+
+ int beginStatusPos = pos + (("STATUS:").length());
+ int endStatusPos = bmsg.indexOf(CRLF, beginStatusPos);
+ return bmsg.substring(beginStatusPos, endStatusPos);
+
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * fetchType
+ *
+ * This method takes as input a String in the bMessage format. It parses the
+ * String and returns the bMessage type String
+ *
+ * @param
+ * @return String This method returns a message type String
+ */
+ public static String fetchType(String bmsg) {
+ int pos = bmsg.indexOf("TYPE:");
+ if (pos > 0) {
+ int beginTypePos = pos + (("TYPE:").length());
+ int endTypePos = bmsg.indexOf(CRLF, beginTypePos);
+
+ return bmsg.substring(beginTypePos, endTypePos);
+
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * fetchFolder
+ *
+ * This method takes as input a String in the bMessage format. It parses the
+ * String and returns the bMessage Folder path String
+ *
+ * @param
+ * @return String This method returns a Folder path String
+ */
+ private static String fetchFolder(String bmsg) {
+ int pos = bmsg.indexOf("FOLDER:");
+ if (pos > 0) {
+ int beginVersionPos = pos + (("FOLDER:").length());
+ int endVersionPos = bmsg.indexOf(CRLF, beginVersionPos);
+
+ return bmsg.substring(beginVersionPos, endVersionPos);
+
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * fetchBody
+ *
+ * This method takes as input a String in the bMessage format. It parses the
+ * String and returns the bMessage Body as a String
+ *
+ * @param
+ * @return String This method returns a Body String
+ */
+ @SuppressWarnings("unused")
+ private static String fetchBody(String bmsg) {
+ int pos = bmsg.indexOf("BEGIN:BBODY");
+ if (pos > 0) {
+ int beginVersionPos = pos + (("BEGIN:BBODY").length());
+ int endVersionPos = bmsg.indexOf("END:BBODY", beginVersionPos);
+
+ return bmsg.substring(beginVersionPos, endVersionPos);
+
+ } else {
+ return null;
+ }
+
+ }
+
+ /**
+ * fetchBodyPartID
+ *
+ * This method takes as input a String consisting of the body portion of the
+ * bMessage. It parses the String and returns the bMessage Body Part ID as a
+ * String
+ *
+ * @param
+ * @return String This method returns a Body Part ID String
+ */
+ @SuppressWarnings("unused")
+ private static String fetchBodyPartID(String body) {
+ int pos = body.indexOf("PARTID:");
+ if (pos > 0) {
+ int beginVersionPos = pos + (("PARTID:").length());
+ int endVersionPos = body.indexOf(CRLF, beginVersionPos);
+ return body.substring(beginVersionPos, endVersionPos);
+
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * fetchCharset
+ *
+ * This method takes as input a String consisting of the body portion of the
+ * bMessage. It parses the String and returns the bMessage Charset as a
+ * String
+ *
+ * @param
+ * @return String This method returns a Charset String
+ */
+ @SuppressWarnings("unused")
+ private static String fetchCharset(String body) {
+ int pos = body.indexOf("CHARSET:");
+ if (pos > 0) {
+
+ int beginVersionPos = pos + (("CHARSET:").length());
+ int endVersionPos = body.indexOf(CRLF, beginVersionPos);
+
+ return body.substring(beginVersionPos, endVersionPos);
+
+ } else {
+
+ return null;
+ }
+ }
+
+ /**
+ * fetchBodyEncoding
+ *
+ * This method takes as input a String consisting of the body portion of the
+ * bMessage. It parses the String and returns the bMessage Body Encoding as
+ * a String
+ *
+ * @param
+ * @return String This method returns a Body Encoding String
+ */
+ private static String fetchBodyEncoding(String body) {
+ int pos = body.indexOf("ENCODING:");
+ if (pos > 0) {
+ int beginVersionPos = pos + (("ENCODING:").length());
+ int endVersionPos = body.indexOf(CRLF, beginVersionPos);
+ return body.substring(beginVersionPos, endVersionPos);
+
+ } else {
+
+ return null;
+ }
+ }
+
+ /**
+ * fetchBodyLanguage
+ *
+ * This method takes as input a String consisting of the body portion of the
+ * bMessage. It parses the String and returns the bMessage Body Language as
+ * a String
+ *
+ * @param
+ * @return String This method returns a Body Language String
+ */
+ @SuppressWarnings("unused")
+ private static String fetchBodyLanguage(String body) {
+ int pos = body.indexOf("LANGUAGE:");
+ if (pos > 0) {
+ int beginVersionPos = pos + (("LANGUAGE:").length());
+ int endVersionPos = body.indexOf(CRLF, beginVersionPos);
+ return body.substring(beginVersionPos, endVersionPos);
+
+ } else {
+
+ return null;
+ }
+ }
+
+ /**
+ * fetchBodyLength
+ *
+ * This method takes as input a String consisting of the body portion of the
+ * bMessage. It parses the String and returns the bMessage Body Length as an
+ * Integer
+ *
+ * @param
+ * @return String This method returns a Body Length Integer
+ */
+ private static Integer fetchBodyLength(String body) {
+ int pos = body.indexOf("LENGTH:");
+ if (pos > 0) {
+ int beginVersionPos = pos + (("LENGTH:").length());
+ int endVersionPos = body.indexOf(CRLF, beginVersionPos);
+ String bd = body.substring(beginVersionPos,
+ endVersionPos);
+ Integer bl = Integer.valueOf(bd);
+ return bl;
+
+ } else {
+
+ return null;
+ }
+ }
+
+ /**
+ * fetchBodyMsg
+ *
+ * This method takes as input a String consisting of the body portion of the
+ * bMessage. It parses the String and returns the bMessage Body Message as a
+ * String
+ *
+ * @param
+ * @return String This method returns a Body Message String
+ */
+ private static String fetchBodyMsg(String body) {
+ int pos = body.indexOf("BEGIN:MSG");
+ if (pos > 0) {
+ int beginVersionPos = pos
+ + (("BEGIN:MSG").length() + CRLF.length());
+ int endVersionPos = (body.indexOf("END:MSG", beginVersionPos) - CRLF
+ .length());
+ return body.substring(beginVersionPos, endVersionPos);
+ } else {
+ return "";
+ }
+ }
+
+ private static String fetchBodyMsgMMS(String body) {
+ int pos = body.indexOf("BEGIN:MSG");
+ if (pos > 0) {
+ /* MMS body should contain both Begin and End message tags */
+ int beginVersionPos = pos;
+ int endVersionPos = body.indexOf("END:MSG", beginVersionPos) + ("END:MSG").length();
+ return body.substring(beginVersionPos, endVersionPos);
+ } else {
+ return "";
+ }
+ }
+
+ private static String fetchBodyMsgEmail(String body) {
+ if (V){
+ Log.v(TAG, "bMessageEmail inside fetch body ::"+body);
+ }
+ int pos = body.indexOf("Content-Disposition:inline");
+ if (pos > 0) {
+ int beginVersionPos = pos
+ + (("Content-Disposition:inline").length() + CRLF.length());
+ int endVersionPos = (body.indexOf("--RPI-Messaging", beginVersionPos) - CRLF
+ .length());
+
+ return body.substring(beginVersionPos, endVersionPos);
+
+ } else {
+
+ return null;
+ }
+ }
+
+ private static String fetchBoundary(String body) {
+ int pos = body.indexOf("boundary=\"");
+ if (pos > 0) {
+ int beginVersionPos = pos + (("boundary=\"").length());
+ int endVersionPos = body.indexOf("\"", beginVersionPos);
+ return body.substring(beginVersionPos, endVersionPos);
+
+ } else {
+
+ return null;
+ }
+ }
+
+ public static String fetchBodyEmail(String body) throws BadRequestException {
+ if (V) Log.v(TAG, "inside fetch body Email :"+body);
+
+ int beginVersionPos = -1;
+ int rfc822Flag = 0;
+ int mimeFlag = 0;
+ int beginVersionPos1 = -1;
+ String contentType;
+ int pos1 = 0;
+ String boundary = fetchBoundary(body);
+ if(boundary != null && !boundary.equalsIgnoreCase("")){
+ pos1 = body.indexOf("--"+boundary);
+ mimeFlag = 1;
+ }
+ else{
+ pos1 = body.indexOf("Date:");
+ mimeFlag = 0;
+ }
+ int contentIndex = body.indexOf("Content-Type",pos1);
+ if(contentIndex > 0){
+ contentType = fetchContentType(body, boundary);
+ if(contentType != null && contentType.trim().equalsIgnoreCase("message/rfc822")){
+ rfc822Flag = 1;
+ }
+ }
+ int pos = body.indexOf(CRLF, pos1) + CRLF.length();
+ while (pos > 0) {
+ if(body.startsWith(CRLF, pos)) {
+ beginVersionPos = pos + CRLF.length();
+ break;
+ } else {
+ final int next = body.indexOf(CRLF, pos);
+ if (next == -1) {
+ // throw new BadRequestException("Ill-formatted bMessage, no empty line");
+ // PTS: Instead of throwing Exception, return MSG
+ int beginMsg = body.indexOf("BEGIN:MSG");
+ if (beginMsg == -1) {
+ throw new BadRequestException("Ill-formatted bMessage, no BEGIN:MSG");
+ }
+ int endMsg = body.indexOf("END:MSG", beginMsg);
+ if (endMsg == -1) {
+ throw new BadRequestException("Ill-formatted bMessage, no END:MSG");
+ }
+ return body.substring(beginMsg + "BEGIN:MSG".length(), endMsg - CRLF.length());
+ } else {
+ pos = next + CRLF.length();
+ }
+ }
+ }
+ if(beginVersionPos > 0){
+ int endVersionPos;
+ if(rfc822Flag == 0){
+ if(mimeFlag == 0) {
+ endVersionPos = body.indexOf("END:MSG", beginVersionPos) ;
+ if (endVersionPos != -1) {
+ return body.substring(beginVersionPos, (endVersionPos - CRLF.length()));
+ }
+ else {
+ return body.substring(beginVersionPos);
+ }
+ } else {
+ endVersionPos = (body.indexOf("--"+boundary+"--", beginVersionPos) - CRLF.length());
+ }
+ try {
+ return body.substring(beginVersionPos, endVersionPos);
+ } catch (IndexOutOfBoundsException e) {
+ throw new BadRequestException("Ill-formatted bMessage, no end boundary");
+ }
+ }
+ else if(rfc822Flag == 1){
+ endVersionPos = (body.indexOf("--"+boundary+"--", beginVersionPos));
+ try {
+ body = body.substring(beginVersionPos, endVersionPos);
+ } catch (IndexOutOfBoundsException e) {
+ throw new BadRequestException("Ill-formatted bMessage, no end boundary");
+ }
+ int pos2 = body.indexOf(CRLF) + CRLF.length();
+ while (pos2 > 0) {
+ if(body.startsWith(CRLF, pos2)) {
+ beginVersionPos1 = pos2 + CRLF.length();
+ break;
+ } else {
+ final int next = body.indexOf(CRLF, pos2);
+ if (next == -1) {
+ throw new BadRequestException("Ill-formatted bMessage, no empty line");
+ } else {
+ pos2 = next + CRLF.length();
+ }
+ }
+ }
+ if(beginVersionPos1 > 0){
+ return body.substring(beginVersionPos1);
+ }
+ }
+ }
+ return null;
+ }
+
+ private static String fetchContentType(String bmsg, String boundary) {
+ int pos1 = bmsg.indexOf("--"+boundary);
+ int pos = bmsg.indexOf("Content-Type:", pos1);
+ if (pos > 0) {
+
+ int beginVersionPos = pos + (("Content-Type:").length());
+ int endVersionPos = bmsg.indexOf(CRLF, beginVersionPos);
+ return bmsg.substring(beginVersionPos, endVersionPos);
+
+ } else {
+
+ return null;
+
+ }
+ }
+
+ /**
+ * fetchNumEnv
+ *
+ * This method takes as input a String in the bMessage format. It parses the
+ * String and returns the number of envelope headers that it finds as an
+ * Integer
+ *
+ * @param
+ * @return String This method returns the number of Envelope headers as an
+ * Integer
+ */
+ @SuppressWarnings("unused")
+ private static Integer fetchNumEnv(String bmsg) {
+ int envCnt = 0;
+ int pos = 0;
+ int loopCnt = 0;
+ pos = bmsg.indexOf((CRLF + "BEGIN:BENV"), pos);
+ if (pos < 0) {
+ loopCnt = 4;
+ } else {
+ envCnt = envCnt + 1;
+ }
+ while (loopCnt < 4) {
+ pos = bmsg.indexOf((CRLF + "BEGIN:BENV"), pos + CRLF.length());
+ if (pos < 0) {
+ loopCnt = 4;
+ } else {
+ envCnt = envCnt + 1;
+ }
+ }
+
+ return envCnt;
+ }
+
+ /**
+ * fetchVcardVersion
+ *
+ * This method takes as input a vCard formatted String. It parses the String
+ * and returns the vCard version as a String
+ *
+ * @param
+ * @return String This method returns a vCard version String
+ */
+ private static String fetchVcardVersion(String vCard) {
+ int pos = vCard.indexOf(CRLF + "VERSION:");
+ int beginVersionPos = pos + (("VERSION:").length() + CRLF.length());
+ int endVersionPos = vCard.indexOf(CRLF, beginVersionPos);
+
+ return vCard.substring(beginVersionPos, endVersionPos);
+ }
+
+ /**
+ * fetchVcardName
+ *
+ * This method takes as input a vCard formatted String. It parses the String
+ * and returns the vCard name as a String
+ *
+ * @param
+ * @return String This method returns a vCard name String
+ */
+ @SuppressWarnings("unused")
+ private static String fetchVcardName(String vCard) {
+
+ int pos = vCard.indexOf((CRLF + "N:"));
+ int beginNPos = pos + "N:".length() + CRLF.length();
+ int endNPos = vCard.indexOf(CRLF, beginNPos);
+ return vCard.substring(beginNPos, endNPos);
+ }
+
+ /**
+ * fetchVcardTel
+ *
+ * This method takes as input a vCard formatted String. It parses the String
+ * and returns the vCard phone number as a String
+ *
+ * @param
+ * @return String This method returns a vCard phone number String
+ */
+ private static String fetchVcardTel(String vCard) {
+
+ int pos = vCard.indexOf((CRLF + "TEL:"));
+ int beginVersionPos = pos + (("TEL:").length() + CRLF.length());
+ int endVersionPos = vCard.indexOf(CRLF, beginVersionPos);
+ return vCard.substring(beginVersionPos, endVersionPos);
+ }
+
+ /**
+ * fetchVcardEmail
+ *
+ * This method takes as input a vCard formatted String. It parses the String
+ * and returns the vCard phone number as a String
+ *
+ * @param
+ * @return String This method returns a vCard phone number String
+ */
+ private static String fetchVcardEmailforMms(String vCard) {
+ int pos = vCard.indexOf((CRLF + "EMAIL:"));
+ int beginVersionPos = pos + (("EMAIL:").length() + CRLF.length());
+ int endVersionPos = vCard.indexOf(CRLF, beginVersionPos);
+ return vCard.substring(beginVersionPos, endVersionPos);
+ }
+
+ public static class BadRequestException extends Exception {
+ public BadRequestException(String reason) {
+ super("BadRequestException: " + reason);
+ }
+ }
+
+ public static class RecipientVCard implements VCardInterpreter {
+ String mName = "";
+ String mFormattedName = "";
+ String mTel = "";
+ String mEmail = "";
+ String mVersion = "";
+ String mCurrentProperty = "";
+
+ /* This is a workaround to replace predifined XML escaping entities
+ * with original characters from VCardInterpreter to handle the XML
+ * parsing limitation of MCE (Denso Carkit).
+ * TODO: Above limitaion MUST be handled @MCE itself.
+ */
+ private static String replaceSpecialVcardString(String input) {
+ String str = input;
+ if(str != null){
+ str = str.toLowerCase().replace("&lt;", "<");
+ str = str.toLowerCase().replace("&gt;", ">");
+ }
+ return str;
+ }
+
+ public void end() {
+ if (V) Log.v(TAG, "end()");
+ }
+
+ public void endEntry() {
+ if (V) Log.v(TAG, "endEntry()");
+ }
+
+ public void endProperty() {
+ if (V) Log.v(TAG, "endProperty()");
+ mCurrentProperty = "";
+ }
+
+ public void propertyGroup(String group) {
+ if (V) Log.v(TAG, "propertyGroup(" + group + ")");
+ }
+
+ public void propertyName(String name) {
+ if (V) Log.v(TAG, "propertyName(" + name + ")");
+ mCurrentProperty = name;
+ }
+
+ public void propertyParamType(String type) {
+ if (V) Log.v(TAG, "propertyParamType(" + type + ")");
+ }
+
+ public void onPropertyCreated(VCardProperty property) {
+ List <String> values;
+
+ if (V) Log.v(TAG, "onPropertyCreated(" + property + ")");
+
+ values = property.getValueList();
+ if (values == null){
+ Log.e(TAG, "NULL Value List received");
+ return;
+ }
+ /* TODO: replaceSpecialVcardString() is a temporary fix bought to
+ * overcome the limitation of XML parsing with MCE (Denso Carkit)
+ * and later this must to be handled from MCE itself.
+ */
+ // The first appeared property in a vCard will be used
+ if (PROPERTY_N.equals(property.getName()) && mName.length() == 0) {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(values.get(0));
+ final int size = values.size();
+ for (int i = 0; i < size; i ++) {
+ sb.append(", ");
+ sb.append(values.get(i));
+ }
+ mName = RecipientVCard.replaceSpecialVcardString(sb.toString());
+ if (V) Log.v(TAG, PROPERTY_N + ": " + mName);
+ } else if (PROPERTY_TEL.equals(property.getName()) && mTel.length() == 0) {
+ mTel = RecipientVCard.replaceSpecialVcardString(values.get(0));
+ if (V) Log.v(TAG, PROPERTY_TEL + ": " + mTel);
+ } else if (PROPERTY_EMAIL.equals(property.getName()) && mEmail.length() == 0) {
+ mEmail = RecipientVCard.replaceSpecialVcardString(values.get(0));
+ if (V) Log.v(TAG, PROPERTY_EMAIL + ": " + mEmail);
+ } else if (PROPERTY_FN.equals(property.getName()) && mFormattedName.length() == 0) {
+ mFormattedName = RecipientVCard.replaceSpecialVcardString(values.get(0));
+ if (V) Log.v(TAG, PROPERTY_FN + ": " + mFormattedName);
+ }
+ }
+
+ public void onVCardStarted() {
+ if (V) Log.v(TAG, "onVCardStarted");
+ }
+
+ public void onVCardEnded() {
+ if (V) Log.v(TAG, "onVCardEnded");
+ }
+
+ public void onEntryStarted() {
+ if (V) Log.v(TAG, "onEntryStarted");
+ mName = "";
+ mFormattedName = "";
+ mTel = "";
+ mEmail = "";
+ }
+
+ public void onEntryEnded() {
+ if (V) Log.v(TAG, "onEntryEnded");
+ }
+
+
+ public void propertyParamValue(String value) {
+ if (V) Log.v(TAG, "propertyParamValue(" + value + ")");
+ }
+
+ public void propertyValues(List<String> values) {
+ if (V) Log.v(TAG, "propertyValues(" + values.toString() + "), Property=" + mCurrentProperty);
+ /* TODO: replaceSpecialVcardString() is a temporary fix bought to
+ * overcome the limitation of XML parsing with MCE (Denso Carkit)
+ * and later this must to be handled from MCE itself.
+ */
+ // The first appeared property in a vCard will be used
+ if (PROPERTY_N.equals(mCurrentProperty) && mName.length() == 0) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(values.get(0));
+ final int size = values.size();
+ for (int i = 0; i < size; i ++) {
+ sb.append(", ");
+ sb.append(values.get(i));
+ }
+ mName = RecipientVCard.replaceSpecialVcardString(sb.toString());
+ if (V) Log.v(TAG, PROPERTY_N + ": " + mName);
+ } else if (PROPERTY_TEL.equals(mCurrentProperty) && mTel.length() == 0) {
+ mTel = RecipientVCard.replaceSpecialVcardString(values.get(0));
+ if (V) Log.v(TAG, PROPERTY_TEL + ": " + mTel);
+ } else if (PROPERTY_EMAIL.equals(mCurrentProperty) && mEmail.length() == 0) {
+ mEmail = RecipientVCard.replaceSpecialVcardString(values.get(0));
+ if (V) Log.v(TAG, PROPERTY_EMAIL + ": " + mEmail);
+ } else if (PROPERTY_FN.equals(mCurrentProperty) && mFormattedName.length() == 0) {
+ mFormattedName = RecipientVCard.replaceSpecialVcardString(values.get(0));
+ if (V) Log.v(TAG, PROPERTY_FN + ": " + mFormattedName);
+ }
+ }
+
+ public void start() {
+ if (V) Log.v(TAG, "start()");
+ }
+
+ public void startEntry() {
+ if (V) Log.v(TAG, "startEntry()");
+ mName = "";
+ mFormattedName = "";
+ mTel = "";
+ mEmail = "";
+ }
+
+ public void startProperty() {
+ if (V) Log.v(TAG, "startProperty()");
+ mCurrentProperty = "";
+ }
+ }
+
+ static RecipientVCard parseVCard(String vCard) throws BadRequestException {
+ if (V) Log.v(TAG, "parseVCard(" + vCard + ")");
+ RecipientVCard recipient = new RecipientVCard();
+
+ if (vCard.length() == 0) {
+ return recipient;
+ }
+
+ try {
+ ByteArrayInputStream is = null;
+ try {
+ byte vCardBytes[] = vCard.getBytes("UTF-8");
+ is = new ByteArrayInputStream(vCardBytes);
+ } catch (UnsupportedEncodingException ex) {
+ Log.w(TAG, "Unable to parse vCard", ex);
+ throw new BadRequestException("Unable to parse vCard");
+ }
+ VCardParser parser = new VCardParser_V21();
+ try {
+ if (V) Log.v(TAG, "try " + VERSION_V21);
+ recipient.mVersion = VERSION_V21;
+ parser.parse(is, recipient);
+ } catch (VCardVersionException e) {
+ is.close();
+ is = new ByteArrayInputStream(vCard.getBytes("UTF-8"));
+ try {
+ if (V) Log.v(TAG, "try " + VERSION_V30);
+ recipient.mVersion = VERSION_V30;
+ parser = new VCardParser_V30();
+ parser.parse(is, recipient);
+ } catch (VCardVersionException e1) {
+ throw new VCardException("vCard with unsupported version.");
+ }
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to parse vCard", e);
+ throw new BadRequestException("Unable to parse vCard");
+ } catch (VCardException e) {
+ Log.w(TAG, "Unable to parse vCard", e);
+ throw new BadRequestException("Unable to parse vCard");
+ }
+
+ return recipient;
+ }
+}
diff --git a/src/org/codeaurora/bluetooth/map/MapUtils/MapUtilsConsts.java b/src/org/codeaurora/bluetooth/map/MapUtils/MapUtilsConsts.java
new file mode 100644
index 0000000..a77b3dd
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/MapUtils/MapUtilsConsts.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map.MapUtils;
+
+/**
+ * List of strings used to construct Bmessages
+ *
+ */
+public final class MapUtilsConsts {
+ public static final String BEGIN_BMSG = "BEGIN:BMSG";
+ public static final String END_BMSG = "END:BMSG";
+ public static final String BEGIN_BENV = "BEGIN:BENV";
+ public static final String END_BBENV = "END:BENV";
+
+ public static final String Telecom = "telecom";
+ public static final String Msg = "msg";
+
+ public static final String Inbox = "inbox";
+ public static final String Outbox = "outbox";
+ public static final String Sent = "sent";
+ public static final String Deleted = "deleted";
+ public static final String Draft = "draft";
+ public static final String Drafts = "drafts";
+ public static final String Undelivered = "undelivered";
+ public static final String Failed = "failed";
+ public static final String Queued = "queued";
+}
diff --git a/src/org/codeaurora/bluetooth/map/MapUtils/MsgListingConsts.java b/src/org/codeaurora/bluetooth/map/MapUtils/MsgListingConsts.java
new file mode 100644
index 0000000..3d81642
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/MapUtils/MsgListingConsts.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map.MapUtils;
+
+public class MsgListingConsts {
+
+ public class MsgInfo {
+ public String dateTime = null;
+
+ public String getDateTime() {
+ return dateTime;
+ }
+
+ public void setDateTime(String dateTime) {
+ this.dateTime = dateTime;
+ }
+
+ }
+ public MsgInfo msgInfo = new MsgInfo();
+
+ public long msg_handle = 0;
+ public String subject = null;
+ public boolean sendSubject = false;
+ public String datetime = null;
+ public String sender_name = null;
+ public String sender_addressing = null;
+ public String recepient_name = null;
+ public boolean sendRecipient_addressing = false;
+ public String recepient_addressing = null;
+ public String type = null;
+ public int size = -1;
+ public String reception_status = null;
+ public int attachment_size = -1;
+ public String contains_text = null;
+ public String priority = null;
+ public String read = null;
+ public String sent = null;
+ public String msg_protected = null;
+ public String replyto_addressing = null;
+
+ public long getMsg_handle() {
+ return msg_handle;
+ }
+
+ public void setMsg_handle(long msg_handle) {
+ this.msg_handle = msg_handle;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public void setSubject(String subject) {
+ this.subject = subject;
+ }
+
+ public void setSendSubject(boolean flag) {
+ this.sendSubject = flag;
+ }
+
+ public String getDatetime() {
+ return datetime;
+ }
+
+ public void setDatetime(String datetime) {
+ this.datetime = datetime;
+ }
+
+ public String getSender_name() {
+ return sender_name;
+ }
+
+ public void setSender_name(String sender_name) {
+ this.sender_name = sender_name;
+ }
+
+ public String getSender_addressing() {
+ return sender_addressing;
+ }
+
+ public void setSender_addressing(String sender_addressing) {
+ this.sender_addressing = sender_addressing;
+ }
+
+ public String getRecepient_name() {
+ return recepient_name;
+ }
+
+ public void setRecepient_name(String recepient_name) {
+ this.recepient_name = recepient_name;
+ }
+
+ public String getRecepient_addressing() {
+ return recepient_addressing;
+ }
+
+ public void setSendRecipient_addressing(boolean flag) {
+ this.sendRecipient_addressing = flag;
+ }
+
+
+ public void setRecepient_addressing(String recepient_addressing) {
+ this.recepient_addressing = recepient_addressing;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public void setSize(int size) {
+ this.size = size;
+ }
+
+ public String getReception_status() {
+ return reception_status;
+ }
+
+ public void setReception_status(String reception_status) {
+ this.reception_status = reception_status;
+ }
+
+ public int getAttachment_size() {
+ return attachment_size;
+ }
+
+ public void setAttachment_size(int attachment_size) {
+ this.attachment_size = attachment_size;
+ }
+
+ public String getContains_text() {
+ return contains_text;
+ }
+
+ public void setContains_text(String contains_text) {
+ this.contains_text = contains_text;
+ }
+
+ 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 void setSent(String sent) {
+ this.sent = sent;
+ }
+
+ public String getSent() {
+ return sent;
+ }
+
+ public String getMsg_protected() {
+ return msg_protected;
+ }
+
+ public void setMsg_protected(String msg_protected) {
+ this.msg_protected = msg_protected;
+ }
+
+ public String getReplyTo_addressing() {
+ return sender_addressing;
+ }
+
+ public void setReplyTo_addressing(String replyto_addressing) {
+ this.replyto_addressing = replyto_addressing;
+ }
+
+}
diff --git a/src/org/codeaurora/bluetooth/map/MapUtils/SmsMmsUtils.java b/src/org/codeaurora/bluetooth/map/MapUtils/SmsMmsUtils.java
new file mode 100644
index 0000000..73964c7
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/MapUtils/SmsMmsUtils.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map.MapUtils;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.text.format.Time;
+import android.util.Log;
+import android.util.TimeFormatException;
+
+import org.codeaurora.bluetooth.map.BluetoothMasAppParams;
+
+import java.util.ArrayList;
+
+public class SmsMmsUtils {
+ public static final String TAG = "SmsMmsUtils";
+ public static final int BIT_SUBJECT = 0x1;
+ public static final int BIT_DATETIME = 0x2;
+ public static final int BIT_SENDER_NAME = 0x4;
+ public static final int BIT_SENDER_ADDRESSING = 0x8;
+
+ public static final int BIT_RECIPIENT_NAME = 0x10;
+ public static final int BIT_RECIPIENT_ADDRESSING = 0x20;
+ public static final int BIT_TYPE = 0x40;
+ public static final int BIT_SIZE = 0x80;
+
+ public static final int BIT_RECEPTION_STATUS = 0x100;
+ public static final int BIT_TEXT = 0x200;
+ public static final int BIT_ATTACHMENT_SIZE = 0x400;
+ public static final int BIT_PRIORITY = 0x800;
+
+ public static final int BIT_READ = 0x1000;
+ public static final int BIT_SENT = 0x2000;
+ public static final int BIT_PROTECTED = 0x4000;
+ public static final int BIT_REPLYTO_ADDRESSING = 0x8000;
+
+ public static final String INBOX = "inbox";
+ public static final String OUTBOX = "outbox";
+ public static final String SENT = "sent";
+ public static final String DELETED = "deleted";
+ public static final String DRAFT = "draft";
+ public static final String DRAFTS = "drafts";
+ public static final String UNDELIVERED = "undelivered";
+ public static final String FAILED = "failed";
+ public static final String QUEUED = "queued";
+
+ public static final int DELETED_THREAD_ID = -1;
+
+ static final int PHONELOOKUP_ID_COLUMN_INDEX = 0;
+ static final int PHONELOOKUP_LOOKUP_KEY_COLUMN_INDEX = 1;
+ static final int PHONELOOKUP_DISPLAY_NAME_COLUMN_INDEX = 2;
+
+ static final int EMAIL_DATA_COLUMN_INDEX = 0;
+
+ public static class VcardContent {
+ public String name = "";
+ public String tel = "";
+ public String email = "";
+ }
+
+ public static final ArrayList<String> FORLDER_LIST_SMS_MMS;
+ public static final ArrayList<String> FORLDER_LIST_SMS_MMS_MNS;
+
+ static {
+ FORLDER_LIST_SMS_MMS = new ArrayList<String>();
+ FORLDER_LIST_SMS_MMS.add(INBOX);
+ FORLDER_LIST_SMS_MMS.add(OUTBOX);
+ FORLDER_LIST_SMS_MMS.add(SENT);
+ FORLDER_LIST_SMS_MMS.add(DELETED);
+ FORLDER_LIST_SMS_MMS.add(DRAFT);
+
+ FORLDER_LIST_SMS_MMS_MNS = new ArrayList<String>();
+ FORLDER_LIST_SMS_MMS_MNS.add(INBOX);
+ FORLDER_LIST_SMS_MMS_MNS.add(OUTBOX);
+ FORLDER_LIST_SMS_MMS_MNS.add(SENT);
+ FORLDER_LIST_SMS_MMS_MNS.add(DRAFT);
+ FORLDER_LIST_SMS_MMS_MNS.add(FAILED);
+ FORLDER_LIST_SMS_MMS_MNS.add(QUEUED);
+ }
+
+ public static int getFolderTypeMms(String folder) {
+ int folderType = -5 ;
+
+ if (INBOX.equalsIgnoreCase(folder)) {
+ folderType = 1;
+ }
+ else if (OUTBOX.equalsIgnoreCase(folder)) {
+ folderType = 4;
+ }
+ else if (SENT.equalsIgnoreCase(folder)) {
+ folderType = 2;
+ }
+ else if (DRAFT.equalsIgnoreCase(folder) || DRAFTS.equalsIgnoreCase(folder)) {
+ folderType = 3;
+ }
+ else if (DELETED.equalsIgnoreCase(folder)) {
+ folderType = -1;
+ }
+ return folderType;
+ }
+
+ public static String getWhereIsQueryForType(String folder) {
+ String query = null;
+
+ if (INBOX.equalsIgnoreCase(folder)) {
+ query = "type = 1 AND thread_id <> " + DELETED_THREAD_ID;
+ }
+ else if (OUTBOX.equalsIgnoreCase(folder)) {
+ query = "(type = 4 OR type = 5 OR type = 6) AND thread_id <> " + DELETED_THREAD_ID;
+ }
+ else if (SENT.equalsIgnoreCase(folder)) {
+ query = "type = 2 AND thread_id <> " + DELETED_THREAD_ID;
+ }
+ else if (DRAFT.equalsIgnoreCase(folder)) {
+ query = "type = 3 AND thread_id <> " + DELETED_THREAD_ID;
+ }
+ else if (DELETED.equalsIgnoreCase(folder)) {
+ query = "thread_id = " + DELETED_THREAD_ID;
+ }
+ else{
+ query = "type = -1";
+ }
+ return query;
+ }
+
+ public static String getConditionStringSms(String folderName, BluetoothMasAppParams appParams) {
+ String whereClause = getWhereIsQueryForType(folderName);
+
+ /* Filter readstatus: 0 no filtering, 0x01 get unread, 0x10 get read */
+ if (appParams.FilterReadStatus != 0) {
+ if ((appParams.FilterReadStatus & 0x1) != 0) {
+ if (whereClause.length() != 0) {
+ whereClause += " AND ";
+ }
+ whereClause += " read=0 ";
+ }
+ if ((appParams.FilterReadStatus & 0x02) != 0) {
+ if (whereClause.length() != 0) {
+ whereClause += " AND ";
+ }
+ whereClause += " read=1 ";
+ }
+ }
+ // TODO Filter priority?
+
+ /* Filter Period Begin */
+ if ((appParams.FilterPeriodBegin != null)
+ && (appParams.FilterPeriodBegin.length() > 0)) {
+ Time time = new Time();
+ try {
+ time.parse(appParams.FilterPeriodBegin.trim());
+ if (whereClause.length() != 0) {
+ whereClause += " AND ";
+ }
+ whereClause += "date >= " + time.toMillis(false);
+ } catch (TimeFormatException e) {
+ Log.d(TAG, "Bad formatted FilterPeriodBegin "
+ + appParams.FilterPeriodBegin);
+ }
+ }
+
+ /* Filter Period End */
+ if ((appParams.FilterPeriodEnd != null)
+ && (appParams.FilterPeriodEnd.length() > 0 )) {
+ Time time = new Time();
+ try {
+ time.parse(appParams.FilterPeriodEnd.trim());
+ if (whereClause.length() != 0) {
+ whereClause += " AND ";
+ }
+ whereClause += "date < " + time.toMillis(false);
+ } catch (TimeFormatException e) {
+ Log.d(TAG, "Bad formatted FilterPeriodEnd "
+ + appParams.FilterPeriodEnd);
+ }
+ }
+ return whereClause;
+ }
+
+ public static final Uri SMS_URI = Uri.parse("content://sms");
+ public static final Uri MMS_URI = Uri.parse("content://mms");
+ public static final String[] THREAD_ID_COLUMN = new String[]{"thread_id"};
+
+ /**
+ * Obtain the number of MMS messages
+ */
+ public static int getNumMmsMsgs(Context context, String name) {
+ int msgCount = 0;
+
+ if (DELETED.equalsIgnoreCase(name)) {
+ Uri uri = Uri.parse("content://mms/");
+ ContentResolver cr = context.getContentResolver();
+ Cursor cursor = cr.query(uri, null, "thread_id = " + DELETED_THREAD_ID, null, null);
+ if(cursor != null){
+ msgCount = cursor.getCount();
+ cursor.close();
+ }
+ } else {
+ Uri uri = Uri.parse("content://mms/" + name);
+ ContentResolver cr = context.getContentResolver();
+ Cursor cursor = cr.query(uri, null, "thread_id <> " + DELETED_THREAD_ID, null, null);
+ if(cursor != null){
+ msgCount = cursor.getCount();
+ cursor.close();
+ }
+ }
+ return msgCount;
+ }
+}
diff --git a/src/org/codeaurora/bluetooth/map/MapUtils/SortMsgListByDate.java b/src/org/codeaurora/bluetooth/map/MapUtils/SortMsgListByDate.java
new file mode 100644
index 0000000..a230895
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/MapUtils/SortMsgListByDate.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2010-2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map.MapUtils;
+
+import java.util.Comparator;
+import org.codeaurora.bluetooth.map.MapUtils.MsgListingConsts;
+
+public class SortMsgListByDate implements Comparator<MsgListingConsts> {
+
+ public int compare(MsgListingConsts object1, MsgListingConsts object2) {
+
+ return object2.msgInfo.getDateTime().compareTo(object1.msgInfo.getDateTime());
+ }
+
+}
diff --git a/src/org/codeaurora/bluetooth/map/MapUtils/SqlHelper.java b/src/org/codeaurora/bluetooth/map/MapUtils/SqlHelper.java
new file mode 100644
index 0000000..e4e87c6
--- /dev/null
+++ b/src/org/codeaurora/bluetooth/map/MapUtils/SqlHelper.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2011, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.bluetooth.map.MapUtils;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.util.Log;
+
+import org.codeaurora.bluetooth.map.BluetoothMasService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SqlHelper {
+ public static final String TAG = "SqlHelper";
+ public static final boolean V = BluetoothMasService.VERBOSE;
+ private static final String[] COUNT_COLUMNS = new String[]{"count(*)"};
+
+ /**
+ * Generic count method that can be used for any ContentProvider
+ * @param resolver the calling Context
+ * @param uri the Uri for the provider query
+ * @param selection as with a query call
+ * @param selectionArgs as with a query call
+ * @return the number of items matching the query (or zero)
+ */
+ public static int count(Context context, Uri uri, String selection, String[] selectionArgs) {
+ if (V) Log.v(TAG, "count(" + uri + ", " + selection + ", " + selectionArgs + ")");
+ int cnt = 0;
+ Cursor cursor = context.getContentResolver().query(uri,
+ COUNT_COLUMNS, selection, selectionArgs, null);
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ cnt = cursor.getInt(0);
+ if (V) Log.v(TAG, "count = " + cnt);
+ }
+ cursor.close();
+ }
+ return cnt;
+ }
+
+ /**
+ * Generic method to retrieve the first value for the column
+ * @param resolver the calling Context
+ * @param uri the Uri for the provider query
+ * @param columnName the column name to be retrieved
+ * @param selection as with a query call
+ * @param selectionArgs as with a query call
+ * @return the value first of that column
+ */
+ public static String getFirstValueForColumn(Context context, Uri uri,
+ String columnName, String selection, String[] selectionArgs) {
+ if (V) Log.v(TAG, "getFirstValueForColumn(" + uri + ", " + columnName +
+ ", " + selection + ", " + selectionArgs + ")");
+ String value = "";
+ Cursor cr = context.getContentResolver().query(uri, null, selection, selectionArgs, null);
+ if (cr != null) {
+ if (cr.moveToFirst()) {
+ value = cr.getString(cr.getColumnIndex(columnName));
+ if (V) Log.v(TAG, "value = " + value);
+ }
+ cr.close();
+ }
+ return value;
+ }
+
+ /**
+ * Generic method to retrieve the first value for the column
+ * @param resolver the calling Context
+ * @param uri the Uri for the provider query
+ * @param columnName the column name to be retrieved
+ * @param selection as with a query call
+ * @param selectionArgs as with a query call
+ * @return the value first of that column
+ */
+ public static int getFirstIntForColumn(Context context, Uri uri,
+ String columnName, String selection, String[] selectionArgs) {
+ if (V) Log.v(TAG, "getFirstIntForColumn(" + uri + ", " + columnName +
+ ", " + selection + ", " + selectionArgs + ")");
+ int value = -1;
+ Cursor cr = context.getContentResolver().query(uri, null, selection, selectionArgs, null);
+ if (cr != null) {
+ if (cr.moveToFirst()) {
+ value = cr.getInt(cr.getColumnIndex(columnName));
+ if (V) Log.v(TAG, "value = " + value);
+ }
+ cr.close();
+ }
+ return value;
+ }
+
+ /**
+ * Generic method to retrieve the list for the column
+ * @param resolver the calling Context's ContentResolver
+ * @param uri the Uri for the provider query
+ * @param columnName the column name to be retrieved
+ * @param selection as with a query call
+ * @param selectionArgs as with a query call
+ * @return the value first of that column
+ */
+ public static List<String> getListForColumn(Context context, Uri uri,
+ String columnName, String selection, String[] selectionArgs) {
+ ArrayList<String> list = new ArrayList<String>();
+ if (V) Log.v(TAG, "getListForColumn(" + uri + ", " + columnName + ", " +
+ selection + ", " + selectionArgs + ")");
+ Cursor cr = context.getContentResolver().query(uri, null, selection, selectionArgs, null);
+ if (cr != null) {
+ if (cr.moveToFirst()) {
+ final int columnIndex = cr.getColumnIndex(columnName);
+ do {
+ final String value = cr.getString(columnIndex);
+ list.add(value);
+ if (V) Log.v(TAG, "adding: " + value);
+ } while (cr.moveToNext());
+ }
+ cr.close();
+ }
+ return list;
+ }
+}