diff options
author | Jonathan Bensen <cybertronic@gmail.com> | 2011-12-04 19:31:49 -0800 |
---|---|---|
committer | Emilio López <turl@tuxfamily.org> | 2011-12-05 04:37:39 +0000 |
commit | f6e080b7d269bd6dfbca36134d1c2d12cf8177ac (patch) | |
tree | 72bc0a02fe031c6869faa49568fae197d498f31c | |
parent | b6cf743fe3994eda247c42d933c4256d044e6923 (diff) | |
download | android_packages_apps_Bluetooth-f6e080b7d269bd6dfbca36134d1c2d12cf8177ac.tar.gz android_packages_apps_Bluetooth-f6e080b7d269bd6dfbca36134d1c2d12cf8177ac.tar.bz2 android_packages_apps_Bluetooth-f6e080b7d269bd6dfbca36134d1c2d12cf8177ac.zip |
Bluetooth: Add Map profile and features (#3875)
Change-Id: Ibe0595b99ce1634aaec47effe4874cda72df302e
24 files changed, 13237 insertions, 0 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a96b8ee23..b6219f965 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -20,6 +20,14 @@ <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + <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="com.android.email.permission.ACCESS_PROVIDER"></uses-permission> + <uses-permission android:name="com.android.email.permission.READ_ATTACHMENT"></uses-permission> + <application android:icon="@drawable/bt_share" android:label="@string/app_name"> @@ -137,5 +145,24 @@ <action android:name="android.bluetooth.adapter.action.STATE_CHANGED"/> </intent-filter> </receiver> + <activity android:name=".map.BluetoothMasActivity" + android:process="@string/process" + android:excludeFromRecents="true" + android:theme="@*android:style/Theme.Dialog.Alert"> + <intent-filter> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + <service + android:process="@string/process" + android:name=".map.BluetoothMasService" > + </service> + <receiver + android:process="@string/process" + android:name=".map.BluetoothMasReceiver"> + <intent-filter> + <action android:name="android.bluetooth.adapter.action.STATE_CHANGED"/> + </intent-filter> + </receiver> </application> </manifest> diff --git a/src/com/android/bluetooth/map/BluetoothMapAuthenticator.java b/src/com/android/bluetooth/map/BluetoothMapAuthenticator.java new file mode 100644 index 000000000..64e0a9277 --- /dev/null +++ b/src/com/android/bluetooth/map/BluetoothMapAuthenticator.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map; + +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +import javax.obex.*; + +/** + * 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) { + byte[] b = null; + return b; + } +} + diff --git a/src/com/android/bluetooth/map/BluetoothMapRfcommTransport.java b/src/com/android/bluetooth/map/BluetoothMapRfcommTransport.java new file mode 100644 index 000000000..acef3c57d --- /dev/null +++ b/src/com/android/bluetooth/map/BluetoothMapRfcommTransport.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + + +import javax.obex.*; + +import android.bluetooth.BluetoothSocket; + +public class BluetoothMapRfcommTransport implements ObexTransport { + private BluetoothSocket mSocket = null; + + public BluetoothMapRfcommTransport(BluetoothSocket rfs) { + super(); + this.mSocket = rfs; + } + + public void close() throws IOException { + mSocket.close(); + } + + public DataInputStream openDataInputStream() throws IOException { + return new DataInputStream(openInputStream()); + } + + public DataOutputStream openDataOutputStream() throws IOException { + return new DataOutputStream(openOutputStream()); + } + + public InputStream openInputStream() throws IOException { + return mSocket.getInputStream(); + } + + public OutputStream openOutputStream() throws IOException { + return mSocket.getOutputStream(); + } + + public void connect() throws IOException { + } + + public void create() throws IOException { + } + + public void disconnect() throws IOException { + } + + public void listen() throws IOException { + } + + public boolean isConnected() throws IOException { + return true; + } + +} + diff --git a/src/com/android/bluetooth/map/BluetoothMasActivity.java b/src/com/android/bluetooth/map/BluetoothMasActivity.java new file mode 100644 index 000000000..78e8ebb60 --- /dev/null +++ b/src/com/android/bluetooth/map/BluetoothMasActivity.java @@ -0,0 +1,291 @@ + /* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map; + +import com.android.bluetooth.R; + +import android.app.Activity; +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 android.widget.Toast; + + +/** + * MapActivity shows two dialogues: One for accepting incoming ftp request and + * the other prompts the user to enter a session key for authentication with a + * remote Bluetooth device. + */ +public class BluetoothMasActivity 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 createDisplayText(final int id) { + String mRemoteName = BluetoothMasService.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() { + } + + @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/com/android/bluetooth/map/BluetoothMasAppIf.java b/src/com/android/bluetooth/map/BluetoothMasAppIf.java new file mode 100644 index 000000000..e95fad353 --- /dev/null +++ b/src/com/android/bluetooth/map/BluetoothMasAppIf.java @@ -0,0 +1,3396 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map; + +import com.android.bluetooth.R; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import android.app.Activity; +import android.app.PendingIntent; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.PhoneLookup; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.telephony.SmsManager; +import android.telephony.SmsMessage; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.text.format.Time; +import android.util.Log; +import android.util.TimeFormatException; + +import com.android.bluetooth.map.MapUtils.BmessageConsts; +import com.android.bluetooth.map.MapUtils.MapUtils; +import com.android.bluetooth.map.MapUtils.MsgListingConsts; +import com.android.bluetooth.map.MapUtils.SmsMmsUtils; +import com.android.bluetooth.map.MapUtils.SortMsgListByDate; +import com.android.bluetooth.map.MapUtils.EmailUtils; +import com.android.bluetooth.map.MapUtils.CommonUtils; + + +import javax.obex.*; + +/** + * 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 class BluetoothMasAppIf { + + public Context context; + public String mode; + public final String TAG = "BluetoothMasAppIf"; + + 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 int MMS_HDLR_CONSTANT = 100000; + public static final int EMAIL_HDLR_CONSTANT = 200000; + + private final String RootPath = "root"; + + private String CurrentPath = null; + + private final String Telecom = "telecom"; + private final String Msg = "msg"; + + private final String Inbox = "inbox"; + private final String Outbox = "outbox"; + private final String Sent = "sent"; + private final String Deleted = "deleted"; + private final String Draft = "draft"; + private final String Drafts = "drafts"; + private final String Undelivered = "undelivered"; + private final String Failed = "failed"; + private final String Queued = "queued"; + + private final int DELETED_THREAD_ID = -1; + + private final boolean mnsServiceEnabled = false; + + + // root -> telecom -> msg -> (FolderList) includes inbox, outbox, sent, etc + // For SMS/MMS FolderList[] = { Inbox, Outbox, Sent, Deleted, Draft }; + + //added email + List<String> folderList = new ArrayList<String>(); + //end email + + private final MapUtils mu; + + private final BluetoothMns mnsClient; + + public BluetoothMasAppIf(Context context, String mode) { + this.context = context; + this.mode = mode; + mu = new MapUtils(); + mnsClient = new BluetoothMns(context); + + // Clear out deleted items from database + clearDeletedItems(); + + SmsMmsUtils smu = new SmsMmsUtils(); + folderList = smu.folderListSmsMms(folderList); + + Log.d(TAG, "Constructor called"); + } + + /** + * 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) { + Log.d(TAG, "setPath called"); + /* + * /* Up and empty string cd .. Up and name - cd ../name Down and name + * - cd name Down and empty string cd to root + */ + + List<String> completeFolderList = new ArrayList<String>(); + EmailUtils eu = new EmailUtils(); + + if(mode !=null && (mode.equalsIgnoreCase("SMS") + || mode.equalsIgnoreCase("SMS_MMS") + || mode.equalsIgnoreCase("SMS_MMS_EMAIL"))){ + completeFolderList = folderList; + } + if(mode !=null && (mode.equalsIgnoreCase("SMS_MMS_EMAIL") + || mode.equalsIgnoreCase("EMAIL"))){ + completeFolderList = eu.folderListEmail(folderList, context); + } + + Log.d(TAG, "CurrentPath::"+CurrentPath); + Log.d(TAG, "name::"+name); + if ((up == false)) { + if(name == null) { + CurrentPath = (setPathFlag) ? null : CurrentPath; + return true; + } else if(name.equals("")){ + CurrentPath = (setPathFlag) ? null : CurrentPath; + return true; + } + } + + if (up == true) { + if (CurrentPath == null) { + // Can't go above root + return false; + } else { + int LastIndex = CurrentPath.lastIndexOf('/'); + if (LastIndex < 0) { + // Reaches root + CurrentPath = null; + } else { + CurrentPath = CurrentPath.substring(0, LastIndex); + } + } + if (name == null) { + // Only going up by one + return true; + } + } + + if (CurrentPath == null) { + if (name.equals(Telecom)) { + CurrentPath = (setPathFlag) ? Telecom : CurrentPath; + return true; + } else { + return false; + } + } + + String splitStrings[] = CurrentPath.split("/"); + + boolean Result = false; + switch (splitStrings.length) { + case 1: + if (name.equals(Msg)) { + CurrentPath += (setPathFlag) ? ("/" + name) : ""; + Result = true; + } + break; + case 2: + for (String FolderName : completeFolderList) { + if(FolderName.equalsIgnoreCase(name) + || FolderName.toUpperCase().contains(name.toUpperCase())){ + //added second condition for gmail sent folder + CurrentPath += (setPathFlag) ? ("/" + name) : ""; + Result = true; + break; + } + } + break; + // TODO SUBFOLDERS: Add check for sub-folders (add more cases) + + default: + Result = false; + 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() { + Log.d(TAG, "folderListingSize called, current path " + CurrentPath); + + List<String> completeFolderList = new ArrayList<String>(); + EmailUtils eu = new EmailUtils(); + + if(mode !=null && (mode.equalsIgnoreCase("SMS") + || mode.equalsIgnoreCase("SMS_MMS") + || mode.equalsIgnoreCase("SMS_MMS_EMAIL"))){ + completeFolderList = folderList; + } + if(mode !=null && (mode.equalsIgnoreCase("SMS_MMS_EMAIL") + || mode.equalsIgnoreCase("EMAIL"))){ + completeFolderList = eu.folderListEmail(folderList, context); + } + + if (CurrentPath == null) { + // at root, only telecom folder should be present + return 1; + } + + if (CurrentPath.equals(Telecom)) { + // at root -> telecom, only msg folder should be present + return 1; + } + + if (CurrentPath.equals(Telecom + "/" + Msg)) { + // at root -> telecom -> msg, FolderList should be present + return completeFolderList.size(); + } + // TODO SUBFOLDERS: Add check for sub-folders + + return 0; + } + + /** + * Get the XML listing of the folders at CurrenthPath + * + * @return XML listing of the folders + */ + public String folderListing(BluetoothMasAppParams appParam) { + Log.d(TAG, "folderListing called, current path " + CurrentPath); + + List<String> list = new ArrayList<String>(); + + List<String> completeFolderList = new ArrayList<String>(); + EmailUtils eu = new EmailUtils(); + if(mode !=null && (mode.equalsIgnoreCase("SMS") + || mode.equalsIgnoreCase("SMS_MMS") + || mode.equalsIgnoreCase("SMS_MMS_EMAIL"))){ + completeFolderList = folderList; + } + if(mode !=null && (mode.equalsIgnoreCase("SMS_MMS_EMAIL") + || mode.equalsIgnoreCase("EMAIL"))){ + completeFolderList = eu.folderListEmail(folderList, context); + } + + if (CurrentPath == null) { + // at root, only telecom folder should be present + if (appParam.ListStartOffset == 0) { + list.add(Telecom); + } + return mu.folderListingXML(list); + } + + if (CurrentPath.equals(Telecom)) { + // at root -> telecom, only msg folder should be present + if (appParam.ListStartOffset == 0) { + list.add(Msg); + } + return mu.folderListingXML(list); + } + + if (CurrentPath.equals(Telecom + "/" + Msg)) { + int offset = 0; + int added = 0; + // at root -> telecom -> msg, FolderList should be present + for (String Folder : completeFolderList) { + offset++; + if ((offset > appParam.ListStartOffset) + && (added < appParam.MaxListCount)) { + list.add(Folder); + added++; + } + } + return mu.folderListingXML(list); + } + + if (CurrentPath.equals(Telecom + "/" + Msg + "/" + Inbox) || + CurrentPath.equals(Telecom + "/" + Msg + "/" + Outbox) || + CurrentPath.equals(Telecom + "/" + Msg + "/" + Draft) || + CurrentPath.equals(Telecom + "/" + Msg + "/" + Deleted) || + CurrentPath.equals(Telecom + "/" + Msg + "/" + Sent) + ) { + return mu.folderListingXML(list); + } + + return null; + } + + /** + * Append child folder to the CurrentPath + */ + private String getFullPath(String child) { + + String tempPath = null; + List<String> completeFolderList = new ArrayList<String>(); + EmailUtils eu = new EmailUtils(); + completeFolderList = eu.folderListEmail(folderList, context); + + if (child != null) { + if (CurrentPath == null) { + if (child.equals("telecom")) { + // Telecom is fine + tempPath = new String("telecom"); + } + } else if (CurrentPath.equals("telecom")) { + if (child.equals("msg")) { + tempPath = CurrentPath + "/" + child; + } + } else if (CurrentPath.equals("telecom/msg")) { + for (String Folder : completeFolderList) { //TODO NEED TO LOOK INTO THIS + if (child.equalsIgnoreCase(Folder)) { + tempPath = CurrentPath + "/" + Folder; + } + } + } + } + return tempPath; + } + + public class BluetoothMasMessageListingRsp { + public File file = null; + public int msgListingSize = 0; + public byte newMessage = 0; + public int rsp = ResponseCodes.OBEX_HTTP_OK; + } + + private class VcardContent { + public String name = ""; + public String tel = ""; + public String email = ""; + } + + 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> list; + + private VcardContent getVcardContent(String phoneAddress) { + + VcardContent vCard = new VcardContent(); + vCard.tel = phoneAddress; + + Uri uriContacts = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, + Uri.encode(phoneAddress)); + Cursor cursorContacts = context.getContentResolver().query( + uriContacts, + new String[] { PhoneLookup._ID, PhoneLookup.LOOKUP_KEY, + PhoneLookup.DISPLAY_NAME }, null, null, null); + + cursorContacts.moveToFirst(); + + if (cursorContacts.getCount() > 0) { + long contactId = cursorContacts + .getLong(PHONELOOKUP_ID_COLUMN_INDEX); + String lookupKey = cursorContacts + .getString(PHONELOOKUP_LOOKUP_KEY_COLUMN_INDEX); + + Uri lookUpUri = Contacts.getLookupUri(contactId, lookupKey); + String Id = lookUpUri.getLastPathSegment(); + + Cursor crEm = context.getContentResolver().query(Email.CONTENT_URI, + new String[] { Email.DATA }, Email.CONTACT_ID + "=?", + new String[] { Id }, null); + crEm.moveToFirst(); + + vCard.name = cursorContacts + .getString(PHONELOOKUP_DISPLAY_NAME_COLUMN_INDEX); + + vCard.email = ""; + if (crEm.moveToFirst()) { + do { + vCard.email += crEm.getString(EMAIL_DATA_COLUMN_INDEX) + + ";"; + } while (crEm.moveToNext()); + } + } + return vCard; + } + + /** + * Check if the entry is not to be filtered out (allowed) + */ + private boolean allowEntry(String phoneAddress, String filterString) { + + boolean found = false; + VcardContent foundEntry = null; + for (VcardContent elem : list) { + if (elem.tel.contains(phoneAddress)) { + found = true; + foundEntry = elem; + } + } + if (found == false) { + VcardContent vCard = getVcardContent(phoneAddress); + if (vCard != null) { + list.add(vCard); + found = true; + foundEntry = vCard; + Log.d(TAG, " NEW VCARD ADDED " + vCard.tel + vCard.name + + vCard.email); + } else { + Log.d(TAG, "VCARD NOT FOUND ERROR"); + } + } + + if (found == true) { + if ((foundEntry.tel.contains(filterString)) + || (foundEntry.name.contains(filterString)) + || (foundEntry.email.contains(filterString))) { + return true; + } + } + return false; + } + + /** + * Get the contact name for the given phone number + */ + private String getContactName(String phoneNumber) { + // TODO Optimize this (get from list) + + boolean found = false; + VcardContent foundEntry = null; + if(phoneNumber == null){ + return null; + } + for (VcardContent elem : list) { + if (elem.tel == null){ + continue; + } + if (elem.tel.contains(phoneNumber)) { + found = true; + foundEntry = elem; + break; + } + } + if (found == false) { + foundEntry = getVcardContent(phoneNumber); + if (foundEntry != null) { + list.add(foundEntry); + found = true; + } + } + if (found == true) { + return foundEntry.name; + } + + return null; + } + + private class OwnerInfo { + public String Name; + public String Number; + } + + private OwnerInfo getOwnerInfo() { + OwnerInfo info = new OwnerInfo(); + TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); + if (tm != null) { + String sLocalPhoneNum = tm.getLine1Number(); + String sLocalPhoneName = ""; + + Method m; + try { + m = tm.getClass().getMethod("getLine1AlphaTag", new Class[] { }); + sLocalPhoneName = (String) m.invoke(tm); + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + if (TextUtils.isEmpty(sLocalPhoneNum)) { + sLocalPhoneNum = "0000000000"; + } + if (TextUtils.isEmpty(sLocalPhoneName)) { + sLocalPhoneName = "QCOM"; + } + info.Name = sLocalPhoneName; + info.Number = sLocalPhoneNum; + } + return info; + } + + private OwnerInfo ownerInfo = null; + + + + /** + * Get the owners name + */ + public String getOwnerName() { + // TODO + if ( ownerInfo == null ) { + ownerInfo = getOwnerInfo(); + } + return ownerInfo.Name; + } + + /** + * Get the owners phone number + */ + public String getOwnerNumber() { + // TODO + if ( ownerInfo == null ) { + ownerInfo = getOwnerInfo(); + } + return ownerInfo.Number; + } + + private boolean isOutgoingSMSMessage(int type) { + if (type == 1) { + return false; + } + return true; + } + + /** + * 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) { + // TODO Auto-generated method stub + + BluetoothMasMessageListingRsp rsp = new BluetoothMasMessageListingRsp(); + CommonUtils cu = new CommonUtils(); + boolean fileGenerated = false; + + // TODO Do this based on the MasInstance + final String FILENAME = "msglist"; + + int writeCount = 0; + int processCount = 0; + int messageListingSize = 0; + + List<MsgListingConsts> msgList = new ArrayList<MsgListingConsts>(); + + if (appParams == null) { + return null; + } + + String tempPath = null; + if (name != null) { + tempPath = cu.getFullPath(name, context, folderList, CurrentPath); + if (tempPath == null) { + // Child folder not present + rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST; + return rsp; + } + } else { + tempPath = CurrentPath; + } + + FileOutputStream bos = null; + + try { + bos = context.openFileOutput(FILENAME, Context.MODE_PRIVATE); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + Log.d(TAG, "appParams.FilterMessageType ::"+ appParams.FilterMessageType); + Log.d(TAG, "Condition result::"+ (appParams.FilterMessageType & 0x09)); + // TODO: Take care of subfolders + // TODO: Check only for SMS_GSM + // Look for messages only if both SMS/MMS are not filtered out + if ((tempPath != null) && (tempPath.split("/").length == 3 || tempPath.contains("Gmail") + || tempPath.split("/").length == 4) && ((appParams.FilterMessageType & 0x13) != 0x13)){ + + String splitStrings[] = tempPath.split("/"); + + // TODO: Take care of subfolders + + Log.d(TAG, "splitString[2] = " + splitStrings[2]); + + // TODO Assuming only for SMS. + String url = "content://sms/"; + Uri uri = Uri.parse(url); + ContentResolver cr = context.getContentResolver(); + SmsMmsUtils smu = new SmsMmsUtils(); + + String whereClause = smu.getConditionStringSms(splitStrings[2], appParams); + + if (appParams.FilterReadStatus > 0x02) { + rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST; + return rsp; + } + + // 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) { + if(mode !=null && (mode.equalsIgnoreCase("SMS") || mode.equalsIgnoreCase("SMS_MMS") || mode.equalsIgnoreCase("SMS_MMS_EMAIL"))){ + if ((appParams.FilterMessageType & 0x01) == 0) { + Cursor cursor = cr.query(uri, null, whereClause, null, + "date desc"); + + 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"); + + Log.d(TAG, "move to First" + cursor.moveToFirst()); + Log.d(TAG, + "move to Liststartoffset" + + cursor.moveToPosition(appParams.ListStartOffset)); + + this.list = new ArrayList<VcardContent>(); + + if (cursor.moveToFirst()) { + + 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(); + String onumber = getOwnerNumber(); + + int msgType = cursor.getInt(typeInd); + + // TODO Filter Recipient + if (isOutgoingSMSMessage(msgType) == true) { + if ((appParams.FilterOriginator != null) + && (appParams.FilterOriginator.length() != 0) + && ((appParams.FilterOriginator + .compareTo(oname) != 0) && (appParams.FilterOriginator + .compareTo(onumber) != 0))) { + continue; + } + if ((appParams.FilterRecipient != null) + && (appParams.FilterRecipient.length() != 0)) { + filterString = appParams.FilterRecipient; + Log.d(TAG, "appParams.FilterRecipient" + + appParams.FilterRecipient); + } + } + if (isOutgoingSMSMessage(msgType) == false) { + if ((appParams.FilterRecipient != null) + && (appParams.FilterRecipient.length() != 0) + && ((appParams.FilterRecipient + .compareTo(oname) != 0) && (appParams.FilterRecipient + .compareTo(onumber) != 0))) { + continue; + } + if ((appParams.FilterOriginator != null) + && (appParams.FilterOriginator.length() != 0)) { + filterString = appParams.FilterOriginator; + Log.d(TAG, "appParams.FilterOriginator" + + appParams.FilterOriginator); + } + } + + if (filterString != null) { + Log.d(TAG, "filterString = " + filterString); + if (allowEntry(cursor.getString(addressInd), + filterString) == true) { + Log.d(TAG, + "+++ ALLOWED +++++++++ " + + cursor.getString(addressInd) + + " - " + cursor.getPosition()); + } else { + Log.d(TAG, + "+++ DENIED +++++++++ " + + cursor.getString(addressInd) + + " - " + cursor.getPosition()); + continue; + } + } + Log.d(TAG, " msgListSize " + messageListingSize + + "write count " + writeCount); + + messageListingSize++; + + /* + * Don't want the listing; just send the listing size + * after applying all the filters. + */ + if (appParams.MaxListCount == 0) { + continue; + } + + processCount++; + + MsgListingConsts ml = new MsgListingConsts(); + ml.setMsg_handle(Integer.valueOf(cursor + .getString(idInd))); + + Time time = new Time(); + time.set(Long.valueOf(cursor.getString(dateInd))); + + 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); + String subject = cursor.getString(bodyInd); + 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(cursor + .getString(addressInd)); + } + 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 = cursor.getString(addressInd); + } + 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(cursor + .getString(addressInd)); + } + 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 = cursor + .getString(addressInd); + } + ml.setRecepient_addressing(recipientAddressing); + ml.setSendRecipient_addressing(true); + } + + if ((appParams.ParameterMask & BIT_TYPE) != 0) { + // TODO GSM or CDMA SMS? + ml.setType("SMS_GSM"); + } + + if ((appParams.ParameterMask & BIT_SIZE) != 0) { + ml.setSize(cursor.getString(bodyInd).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 (cursor.getString(readInd).equalsIgnoreCase("1")) { + ml.setRead("yes"); + } else { + ml.setRead("no"); + } + } + + if ((appParams.ParameterMask & BIT_SENT) != 0) { + // TODO Get sent status? + if (cursor.getInt(typeInd) == 2) { + ml.setSent("yes"); + } else { + ml.setSent("no"); + } + } + + if ((appParams.ParameterMask & BIT_PROTECTED) != 0) { + ml.setMsg_protected("no"); + } + + // TODO replyto_addressing is used only for email + + // New Message? + if ((rsp.newMessage == 0) + && (cursor.getInt(readInd) == 0)) { + rsp.newMessage = 1; + } + + msgList.add(ml); + writeCount++; + } while (cursor.moveToNext()); + + cursor.close(); + } + } + } + if(mode !=null && (mode.equalsIgnoreCase("SMS_MMS") || mode.equalsIgnoreCase("SMS_MMS_EMAIL"))){ + // Now that all of the SMS messages have been listed. Look for + // any + // MMS messages and provide them + if((appParams.FilterMessageType & 0x08) == 0) { + + String splitStringsMms[] = tempPath.split("/"); + name = splitStringsMms[2]; + + // MMS draft folder is called //mms/drafts not //mms/draft like + // SMS + if (name.equalsIgnoreCase("draft")) { + name = "drafts"; + } + + if (getNumMmsMsgs(name) != 0) { + MsgListingConsts mmsl = new MsgListingConsts(); + List<Integer> list = getMmsMsgMIDs(bldMmsWhereClause( + appParams, getMMSFolderType(name))); + for (int msgId : list) { + Log.d(TAG, "\n MMS Text message ==> " + + getMmsMsgTxt(msgId)); + Log.d(TAG, "\n MMS message subject ==> " + + getMmsMsgSubject(msgId)); + + String datetime = getMmsMsgDate(msgId); + Time time = new Time(); + Date dt = new Date(Long.valueOf(datetime)); + time.set((dt.getTime() * 1000)); + + String datetimeStr = time.toString().substring(0, 15); + + mmsl = bldMmsMsgLstItem(msgId, appParams, name, datetimeStr); + mmsl.msgInfo.setDateTime(datetimeStr); + + if ((rsp.newMessage == 0) + && (getMmsMsgReadStatus(msgId).equalsIgnoreCase("no"))) { + rsp.newMessage = 1; + } + + msgList.add(mmsl); + messageListingSize++; + } + } + } + } + if(mode !=null && (mode.equalsIgnoreCase("EMAIL") + || mode.equalsIgnoreCase("SMS_MMS_EMAIL"))){ + //Email messages + if((appParams.FilterMessageType & 0x04) == 0){ + EmailUtils eu = new EmailUtils(); + String folderName; + + String splitStringsEmail[] = tempPath.split("/"); + Log.d(TAG, "splitStringsEmail[2] = " + splitStringsEmail[2]); + // TODO: Take care of subfolders + + folderName = eu.getFolderName(splitStringsEmail); + if(folderName != null && folderName.equalsIgnoreCase("draft")){ + folderName = "Drafts"; + } + + String urlEmail = "content://com.android.email.provider/message"; + Uri uriEmail = Uri.parse(urlEmail); + ContentResolver crEmail = context.getContentResolver(); + + String whereClauseEmail = eu.getConditionString(folderName, context, appParams); + + Log.d(TAG, "## whereClauseEmail ##:" + whereClauseEmail); + Cursor cursor = crEmail.query(uriEmail, null, whereClauseEmail, null, "timeStamp desc"); + + int idInd = cursor.getColumnIndex("_id"); + 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"); + + Log.d(TAG, "move to First" + cursor.moveToFirst()); + Log.d(TAG, "move to Liststartoffset" + + cursor.moveToPosition(appParams.ListStartOffset)); + + + if (cursor.moveToFirst()) { + + do { + /* + * Apply remaining filters + */ + + Log.d(TAG, " msgListSize " + messageListingSize + + "write count " + writeCount); + + messageListingSize++; + + String subject = cursor.getString(subjectInd); + String timestamp = cursor.getString(dateInd); + String senderName = cursor.getString(fromIndex); + 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. + */ + + processCount++; + /* + * TODO Skip the first ListStartOffset record(s). Don't write + * more than MaxListCount record(s). + */ + + MsgListingConsts emailMsg = new MsgListingConsts(); + emailMsg = eu.bldEmailMsgLstItem(context, folderName, appParams, + subject, timestamp, senderName, senderAddressing, + recipientName, recipientAddressing, + msgId, readStatus, replyToStr); + + // New Message? + if ((rsp.newMessage == 0) && (cursor.getInt(readInd) != 0)) { + rsp.newMessage = 1; + } + msgList.add(emailMsg); + writeCount++; + } while (cursor.moveToNext()); + + cursor.close(); + } + }//end email Messages if + } + } + else { + if (appParams.FilterPriority > 0x02) { + rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST; + } + } + } + + // Now that the message list exists, we can sort the list by date + Collections.sort(msgList, new SortMsgListByDate()); + + // 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) { + str = ""; + } else { + + if (msgDelta <= appParams.MaxListCount) { + stopIdx = startIdx + msgDelta; + } else { + stopIdx = startIdx + appParams.MaxListCount; + } + List<MsgListingConsts> msgSubList = msgList.subList(startIdx, + stopIdx); + str = mu.messageListingXML(msgSubList); + + } + // TODO Undo the following check + int pos = str.indexOf(" msg handle=\""); + while (pos > 0) { + Log.d(TAG, " Msg listing str modified"); + String str2 = str.substring(0, pos); + str2 += str.substring(pos + 1); + str = str2; + pos = str.indexOf(" msg handle=\""); + } + + // String str = "this is a test for the data file"; + try { + bos.write(str.getBytes()); + bos.flush(); + bos.close(); + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + msgList.clear(); + + Log.d(TAG, ""); + + Log.d(TAG, " MESSAGE LISTING FULL ( total length)" + str.length()); + Log.d(TAG, str); + + byte[] readBytes = new byte[10]; + try { + + FileInputStream fis = new FileInputStream(context.getFilesDir() + + "/" + FILENAME); + fis.close(); + fileGenerated = true; + + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + if (fileGenerated == true) { + File file = new File(context.getFilesDir() + "/" + FILENAME); + rsp.file = file; + } + rsp.msgListingSize = messageListingSize; + + return rsp; + } + + public class BluetoothMasMessageRsp { + public byte fractionDeliver = 0; + public File file = null; + public int rsp = ResponseCodes.OBEX_HTTP_OK; + } + + /** + * 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){ + Log.d(TAG, "getMapFolder cannot parse folder type"); + return folder; + } + + 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(String msgHandle) { + Cursor cr; + + cr = context.getContentResolver().query( + Uri.parse("content://sms/" + msgHandle), + new String[] { "_id", "type", "thread_id" }, null, null, null); + if (cr.getCount() > 0) { + cr.moveToFirst(); + return getMAPFolder(cr.getString(cr.getColumnIndex("type")), + cr.getString(cr.getColumnIndex("thread_id"))); + } + return null; + } + + /** + * 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++){ + Log.d(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; + } + + /** + * 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; + } + + Cursor cr = null; + Uri uri = Uri.parse("content://sms/"); + String whereClause = " _id = " + msgHandle; + try { + cr = context.getContentResolver().query(uri, null, whereClause, null, + null); + } catch (Exception e){ + rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST; + return rsp; + } + + if (cr.getCount() > 0) { + cr.moveToFirst(); + String containingFolder = getContainingFolder(msgHandle); + BmessageConsts bmsg = new BmessageConsts(); + + // Create a bMessage + + // TODO Get Current type + 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")); + // Inbox is type 1. + 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); + } + + bmsg.setBody_length(22 + smsBody.length()); + + bmsg.setBody_msg(smsBody); + + + // Send a bMessage + Log.d(TAG, "bMessageSMS test\n"); + Log.d(TAG, "=======================\n\n"); + String str = mu.toBmessageSMS(bmsg); + Log.d(TAG, str); + Log.d(TAG, "\n\n"); + + if (str != null && (str.length() > 0)) { + final String FILENAME = "message"; + // BufferedOutputStream bos = null; + FileOutputStream bos = null; + File file = new File(context.getFilesDir() + "/" + FILENAME); + file.delete(); + + try { + bos = context + .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(); + } + + File fileR = new File(context.getFilesDir() + "/" + FILENAME); + if (fileR.exists() == true) { + rsp.file = fileR; + rsp.fractionDeliver = 1; + } + + } + + } + else{ + + // No SMS message was discovered with that handle, now look for an + // MMS message + + /* + * 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; + } + int mmsMsgID = 0; + try { + mmsMsgID = getMmsMsgHndToID(Integer.valueOf(msgHandle)); + } catch (Exception e) { + rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST; + return rsp; + } + if (mmsMsgID > 0) { + bldMmsBmsg(mmsMsgID, rsp); + } + else{ //No SMS and MMS messages were discovered. now look for email messages + Log.d(TAG, "Inside email :"); + // Email message + int emailMsgID = 0; + EmailUtils eu = new EmailUtils(); + + emailMsgID = (Integer.valueOf(msgHandle) - EMAIL_HDLR_CONSTANT); + String str = eu.bldEmailBmsg(emailMsgID, rsp, context, mu); + + Log.d(TAG, str); + Log.d(TAG, "\n\n"); + + if (str != null && (str.length() > 0)) { + final String FILENAME = "message"; + FileOutputStream bos = null; + File file = new File(context.getFilesDir() + "/" + FILENAME); + file.delete(); + + try { + bos = context.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(); + } + + File fileR = new File(context.getFilesDir() + "/" + FILENAME); + if (fileR.exists() == true) { + rsp.file = fileR; + rsp.fractionDeliver = 1; + } + else { + rsp.rsp = ResponseCodes.OBEX_HTTP_BAD_REQUEST; + } + + } + } + } + + return rsp; + } + + public class BluetoothMasPushMsgRsp { + public int response; + public String msgHandle; + } + + /** + * Retrieve the conversation thread id + */ + private int getThreadId(String address) { + + Cursor cr = context.getContentResolver().query( + Uri.parse("content://sms/"), null, + "address = '" + address + "'", null, null); + if (cr.moveToFirst()) { + int threadId = Integer.valueOf(cr.getString(cr + .getColumnIndex("thread_id"))); + Log.d(TAG, " Found the entry, thread id = " + threadId); + + return (threadId); + } + return 0; + } + + /** + * Adds a SMS to the Sms ContentProvider + */ + + public String addToSmsFolder(String folder, String address, String text) { + + int threadId = getThreadId(address); + Log.d(TAG, "-------------"); + Log.d(TAG, "address " + address + " TEXT " + text + " Thread ID " + + threadId); + + ContentValues values = new ContentValues(); + values.put("thread_id", threadId); + 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 = context.getContentResolver().insert( + Uri.parse("content://sms/" + folder), values); + Log.d(TAG, " NEW URI " + uri.toString()); + + String str = uri.toString(); + String[] splitStr = str.split("/"); + Log.d(TAG, " NEW HANDLE " + splitStr[3]); + return splitStr[3]; + } + /** + * Adds an Email to the Email ContentProvider + */ + + public String addToEmailFolder(String folder, String address, String text, String subject, String OrigEmail, String OrigName) { + + Log.d(TAG, "-------------"); + Log.d(TAG, "address " + address); + Log.d(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 = 0; + int accountId = 0; + Time timeObj = new Time(); + timeObj.setToNow(); + + String whereClause = "UPPER(displayName) LIKE '%"+folder.toUpperCase().trim()+"%'"; + cr = context.getContentResolver().query( + Uri.parse("content://com.android.email.provider/mailbox"), + null, whereClause, null, null); + if (cr.getCount() > 0) { + cr.moveToFirst(); + folderId = cr.getInt(cr.getColumnIndex("_id")); + } + + + + Cursor cr1; + String whereClause1 = "UPPER(emailAddress) LIKE '"+OrigEmail.toUpperCase().trim()+"'"; + cr1 = context.getContentResolver().query( + Uri.parse("content://com.android.email.provider/account"), + null, whereClause1, null, null); + if (cr1.getCount() > 0) { + cr1.moveToFirst(); + accountId = cr1.getInt(cr1.getColumnIndex("_id")); + } + + + + Log.d(TAG, "-------------"); + Log.d(TAG, "To address " + address); + Log.d(TAG, "Text " + text); + Log.d(TAG, "Originator email address:: " + OrigEmail); + Log.d(TAG, "Originator email name:: " + OrigName); + Log.d(TAG, "Time Stamp:: " + timeObj.toMillis(false)); + Log.d(TAG, "Account Key:: " + accountId); + Log.d(TAG, "Folder Id:: " + folderId); + Log.d(TAG, "Folder Name:: " + folder); + Log.d(TAG, "Subject" + subject); + + ContentValues values = new ContentValues(); + //Hardcoded values + values.put("syncServerId", "1"); + //end Hardcoded values + values.put("syncServerTimeStamp", timeObj.toMillis(false)); + 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 = context.getContentResolver().insert( + Uri.parse("content://com.android.email.provider/message"), values); + Log.d(TAG, " NEW URI " + uri.toString()); + + String str = uri.toString(); + String[] splitStr = str.split("/"); + Log.d(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); + + Uri uri1 = context.getContentResolver().insert( + Uri.parse("content://com.android.email.provider/body"), valuesBody); + + return splitStr[4]; + + } + public long getEmailAccountId(String email) { + Cursor cr1; + long accountId = -1; + String whereClause1 = "UPPER(emailAddress) LIKE '"+email.toUpperCase().trim()+"'"; + cr1 = context.getContentResolver().query( + Uri.parse("content://com.android.email.provider/account"), + null, whereClause1, null, null); + if (cr1.getCount() > 0) { + cr1.moveToFirst(); + accountId = cr1.getInt(cr1.getColumnIndex("_id")); + } + return accountId; + } + + /** + * Get the type (as in Sms ContentProvider) for the given table name + */ + private int getSMSFolderType(String folder) { + int type = 0; + if (folder.equalsIgnoreCase(Inbox)) { + type = 1; + } else if (folder.equalsIgnoreCase(Sent)) { + type = 2; + } else if (folder.equalsIgnoreCase(Draft)) { + type = 3; + } else if (folder.equalsIgnoreCase(Outbox)) { + type = 4; + } else if (folder.equalsIgnoreCase(Failed)) { + type = 5; + } else if (folder.equalsIgnoreCase(Queued)) { + type = 6; + } + return type; + } + + /** + * Modify the type (as in Sms ContentProvider) For eg: move from outbox to + * send type + */ + private void moveToFolder(String handle, String folder) { + // Make sure the handle is not null (Tranparent == 0) + if (handle != null) { + ContentValues values = new ContentValues(); + values.put("type", getSMSFolderType(folder)); + Uri uri = Uri.parse("content://sms/" + handle); + context.getContentResolver().update(uri, values, null, null); + } + } + + private String PhoneAddress; + private String SmsText; + private String SmsHandle; + + + private String EmailAddress; + private String EmailText; + private String EmailHandle; + private String EmailSubject; + private String EmailOriginator; + private String EmailOrigName; + + /** + * Push a outgoing message from MAS Client to the network + * + * @return Response to push command + */ + public BluetoothMasPushMsgRsp pushMsg(String name, File file, + BluetoothMasAppParams bluetoothMasAppParams) { + // TODO Auto-generated method stub + + BluetoothMasPushMsgRsp rsp = new BluetoothMasPushMsgRsp(); + rsp.response = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + rsp.msgHandle = null; + + final String SENT = "Sent"; + final String DELIVERED = "Delivered"; + + if(!checkPath(false, name, false) || + CurrentPath == null || + CurrentPath.equals("telecom") || + (CurrentPath.equals("telecom/msg") && (name == null)) ) { + rsp.response = ResponseCodes.OBEX_HTTP_BAD_REQUEST; + return rsp; + } + + + + byte[] readBytes = null; + FileInputStream fis; + try { + fis = new FileInputStream(file); + readBytes = new byte[(int) file.length()]; + fis.read(readBytes); + + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return rsp; + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return rsp; + } + + String readStr = new String(readBytes); + MapUtils mu = new MapUtils(); + String type = mu.fetchType(readStr); + if(type!=null && (type.equalsIgnoreCase("SMS_GSM") || type.equalsIgnoreCase("MMS"))){ + BmessageConsts bMsg = mu.fromBmessageSMS(readStr); + PhoneAddress = bMsg.getRecipientVcard_phone_number(); + SmsText = bMsg.getBody_msg(); + + // If the message to be pushed is an MMS message, extract any text, + // discard + // any attachments and convert the message to an SMS + + if ((bMsg.getType()).equals("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 + */ + + moveMMStoDrafts(readStr); + moveMMSfromDraftstoOutbox(); + + Log.d(TAG, "\nBroadcasting Intent to MmsSystemEventReceiver\n "); + Intent sendIntent = new Intent("android.intent.action.MMS_PUSH"); + context.sendBroadcast(sendIntent); + + return rsp; + + } + + String tmpPath = ""; + tmpPath = (name == null) ? CurrentPath : CurrentPath + "/" + name; + + if(!tmpPath.equalsIgnoreCase("telecom/msg/outbox")) { + String splitStrings[] = CurrentPath.split("/"); + mnsClient.addMceInitiatedOperation("+"); + int tmp = splitStrings.length; + String folderName; + if (name != null) { + if (name.equals("")){ + folderName = splitStrings[tmp - 1]; + } else { + folderName = name; + } + } else { + folderName = splitStrings[tmp - 1]; + } + SmsHandle = addToSmsFolder(folderName, PhoneAddress, SmsText); + rsp.msgHandle = SmsHandle; + rsp.response = ResponseCodes.OBEX_HTTP_OK; + return rsp; + } + + PendingIntent sentPI = PendingIntent.getBroadcast(this.context, 0, + new Intent(SENT), 0); + + PendingIntent deliveredPI = PendingIntent.getBroadcast(this.context, 0, + new Intent(DELIVERED), 0); + + // ---when the SMS has been sent--- + context.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context arg0, Intent arg1) { + Log.d(TAG, "Sms SENT STATUS "); + + switch (getResultCode()) { + case Activity.RESULT_OK: + Log.d(TAG, "Sms Sent"); + moveToFolder(SmsHandle, Sent); + break; + case SmsManager.RESULT_ERROR_GENERIC_FAILURE: + Log.d(TAG, "Generic Failure"); + moveToFolder(SmsHandle, Failed); + break; + case SmsManager.RESULT_ERROR_NO_SERVICE: + Log.d(TAG, "NO SERVICE ERROR"); + moveToFolder(SmsHandle, Queued); + break; + case SmsManager.RESULT_ERROR_NULL_PDU: + Log.d(TAG, "Null PDU"); + moveToFolder(SmsHandle, Failed); + break; + case SmsManager.RESULT_ERROR_RADIO_OFF: + Log.d(TAG, "RADIO OFF"); + moveToFolder(SmsHandle, Queued); + break; + } + context.unregisterReceiver(this); + } + }, new IntentFilter(SENT)); + + // ---when the SMS has been delivered--- + context.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context arg0, Intent arg1) { + Log.d(TAG, "Sms SENT DELIVERED "); + + switch (getResultCode()) { + case Activity.RESULT_OK: + Log.d(TAG, "Sms Delivered"); + break; + case Activity.RESULT_CANCELED: + Log.d(TAG, "Sms NOT Delivered"); + break; + } + context.unregisterReceiver(this); + } + }, new IntentFilter(DELIVERED)); + + SmsHandle = null; + rsp.msgHandle = ""; + + if(bluetoothMasAppParams.Transparent == 0) { + mnsClient.addMceInitiatedOperation("+"); + SmsHandle = addToSmsFolder("outbox", PhoneAddress, SmsText); + rsp.msgHandle = SmsHandle; + } + + + SmsManager sms = SmsManager.getDefault(); + + Log.d(TAG, " Trying to send SMS "); + Log.d(TAG, " Text " + SmsText + " address " + PhoneAddress); + + try { + sms.sendTextMessage(PhoneAddress, null, SmsText, sentPI, + deliveredPI); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + + } + } + else if(type!=null && type.equalsIgnoreCase("EMAIL")){ + Log.d(TAG, " Before fromBmessageemail method:: "+readStr); + BmessageConsts bMsg = mu.fromBmessageEmail(readStr); + EmailAddress = bMsg.getRecipientVcard_email(); + EmailText = bMsg.getBody_msg(); + EmailSubject = bMsg.getSubject(); + EmailOriginator = bMsg.getOriginatorVcard_email(); + EmailOrigName = bMsg.getOriginatorVcard_name(); + + String tmpPath = ""; + tmpPath = (name == null) ? CurrentPath : CurrentPath + "/" + name; + + if(!tmpPath.equalsIgnoreCase("telecom/msg/sent1")) { + String splitStrings[] = CurrentPath.split("/"); + mnsClient.addMceInitiatedOperation("+"); + int tmp = splitStrings.length; + String folderName; + if (name != null) { + if (name.equals("")){ + folderName = splitStrings[tmp - 1]; + } else { + folderName = name; + } + } else { + folderName = splitStrings[tmp - 1]; + } + EmailHandle = addToEmailFolder(folderName, EmailAddress, EmailText, EmailSubject, + EmailOriginator, EmailOrigName); + rsp.msgHandle = EmailHandle; + rsp.response = ResponseCodes.OBEX_HTTP_OK; + + long accountId = getEmailAccountId(EmailOriginator); + Log.d(TAG, " Account id before Mail service:: "+accountId); + + Intent emailIn = new Intent(); + + emailIn.setAction("com.android.email.intent.action.MAIL_SERVICE_WAKEUP"); + emailIn.putExtra("com.android.email.intent.extra.ACCOUNT", accountId); + this.context.startService(emailIn); + + return rsp; + } + else{ + String splitStrings[] = CurrentPath.split("/"); + mnsClient.addMceInitiatedOperation("+"); + int tmp = splitStrings.length; + String folderName; + if (name != null) { + if (name.equals("")){ + folderName = splitStrings[tmp - 1]; + } else { + folderName = name; + } + } + else { + folderName = splitStrings[tmp - 1]; + } + + } + + } + + rsp.response = ResponseCodes.OBEX_HTTP_OK; + + return rsp; + } + + private void updateMMSThreadId(String msgHandle, int threadId){ + ContentValues values = new ContentValues(); + values.put("thread_id", threadId); + context.getContentResolver().update( Uri.parse("content://mms/" + msgHandle), values, null, null); + } + + private void deleteMMS(String msgHandle){ + Cursor cr = context.getContentResolver().query(Uri.parse("content://mms/" + msgHandle), null, null, null, null); + if ( cr.moveToFirst()){ + int threadId = cr.getInt(cr.getColumnIndex(("thread_id"))); + if ( threadId != DELETED_THREAD_ID){ + // Move to deleted folder + updateMMSThreadId(msgHandle, Integer.valueOf(DELETED_THREAD_ID)); + } else { + // Delete the message permanently + int msgId = Integer.valueOf(msgHandle) + MMS_HDLR_CONSTANT; + mnsClient.addMceInitiatedOperation(Integer.toString(msgId)); + context.getContentResolver().delete(Uri.parse("content://mms/" + msgHandle), null, null); + } + cr.close(); + } + } + + private void unDeleteMMS(String msgHandle){ + + Cursor cr = context.getContentResolver().query(Uri.parse("content://mms/" + msgHandle), null, null, null, null ); + + 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){ + Log.d(TAG, " Not in delete folder"); + return; + } + + // Fetch the address of the deleted message + + String address = getMmsMsgAddress(Integer.valueOf(msgHandle)); + + // Search the database for the given message ID + Cursor crThreadId = context.getContentResolver().query(Uri.parse("content://mms/"), null,"_id = " + msgHandle + " AND thread_id != -1", null, null); + if (crThreadId.moveToFirst()) { + // A thread for the given message ID exists in the database + String threadIdStr = crThreadId.getString(crThreadId.getColumnIndex("thread_id")); + Log.d(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 = context.getContentResolver().insert( Uri.parse("content://sms/"), tempValue); + + if ( tempUri != null ) { + String tempMsgId = tempUri.getLastPathSegment(); + Cursor tempCr = context.getContentResolver().query(tempUri, null, null, null, null); + tempCr.moveToFirst(); + String newThreadIdStr = tempCr.getString(tempCr.getColumnIndex("thread_id")); + tempCr.close(); + + updateMMSThreadId(msgHandle, Integer.valueOf(newThreadIdStr)); + + context.getContentResolver().delete(tempUri, null, null); + } + else { + Log.d(TAG, "Error in undelete"); + } + } + crThreadId.close(); + } + else + { + Log.d(TAG, "msgHandle not found"); + } + cr.close(); + } + + private void updateSMSThreadId(String msgHandle, int threadId){ + ContentValues values = new ContentValues(); + values.put("thread_id", threadId); + context.getContentResolver().update( Uri.parse("content://sms/" + msgHandle), values, null, null); + } + + private void deleteSMS(String msgHandle){ + Cursor cr = context.getContentResolver().query(Uri.parse("content://sms/" + msgHandle), null, null, null, null); + if ( cr.moveToFirst()){ + int threadId = cr.getInt(cr.getColumnIndex(("thread_id"))); + if ( threadId != DELETED_THREAD_ID){ + // Move to deleted folder + updateSMSThreadId(msgHandle, Integer.valueOf(DELETED_THREAD_ID)); + } else { + // Delete the message permanently + mnsClient.addMceInitiatedOperation(msgHandle); + context.getContentResolver().delete(Uri.parse("content://sms/" + msgHandle), null, null); + } + cr.close(); + } + } + + private void unDeleteSMS(String msgHandle){ + + Cursor cr = context.getContentResolver().query(Uri.parse("content://sms/" + msgHandle), null, null, null, null ); + + 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){ + Log.d(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 = context.getContentResolver().query(Uri.parse("content://sms/"), + null, "address = " + address + " AND thread_id != -1", null, null); + if (crThreadId.moveToFirst()){ + // A thread for the given address exists in the database + String threadIdStr = crThreadId.getString(crThreadId.getColumnIndex("thread_id")); + Log.d(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 = context.getContentResolver().insert( Uri.parse("content://sms/"), tempValue); + + if ( tempUri != null ) { + String tempMsgId = tempUri.getLastPathSegment(); + Cursor tempCr = context.getContentResolver().query(tempUri, null, null, null, null); + tempCr.moveToFirst(); + String newThreadIdStr = tempCr.getString(tempCr.getColumnIndex("thread_id")); + tempCr.close(); + + updateSMSThreadId(msgHandle, Integer.valueOf(newThreadIdStr)); + + context.getContentResolver().delete(tempUri, null, null); + } + } + crThreadId.close(); + } + else + { + Log.d(TAG, "msgHandle not found"); + } + cr.close(); + } + + + /** + * Sets the message status (read/unread, delete) + * + * @return Obex response code + */ + public int msgStatus(String name, + BluetoothMasAppParams bluetoothMasAppParams) { + + if ((bluetoothMasAppParams.StatusIndicator != 0) + && (bluetoothMasAppParams.StatusIndicator != 1)) { + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + + if ((bluetoothMasAppParams.StatusValue != 0) + && (bluetoothMasAppParams.StatusValue != 1)) { + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + + Uri uri = Uri.parse("content://sms/" + name); + + Cursor cr = context.getContentResolver().query(uri, null, null, null, + null); + if (cr.moveToFirst()) { + if (bluetoothMasAppParams.StatusIndicator == 0) { + /* Read Status */ + ContentValues values = new ContentValues(); + values.put("read", bluetoothMasAppParams.StatusValue); + context.getContentResolver().update(uri, values, null, null); + } else { + if (bluetoothMasAppParams.StatusValue == 1) { + deleteSMS(name); + } else if (bluetoothMasAppParams.StatusValue == 0) { + unDeleteSMS(name); + } + } + return ResponseCodes.OBEX_HTTP_OK; + } + // MMS MessageStatus processing begins here + cr.close(); + String whereClause = " _id= " + getMmsMsgHndToID(Integer.valueOf(name)); + uri = Uri.parse("content://mms/"); + if(getMmsMsgHndToID(Integer.valueOf(name)) > 0){ + cr = context.getContentResolver().query(uri, null, null, null, null); + } + if (cr.getCount() > 0) { + cr.moveToFirst(); + if (bluetoothMasAppParams.StatusIndicator == 0) { + /* Read Status */ + ContentValues values = new ContentValues(); + values.put("read", bluetoothMasAppParams.StatusValue); + int rowUpdate = context.getContentResolver().update(uri, + values, whereClause, null); + Log.d(TAG, "\nRows updated => " + Integer.toString(rowUpdate)); + return ResponseCodes.OBEX_HTTP_OK; + } else { + if (bluetoothMasAppParams.StatusValue == 1) { + deleteMMS(Integer.toString(getMmsMsgHndToID(Integer.valueOf(name)))); + } else if (bluetoothMasAppParams.StatusValue == 0) { + unDeleteMMS(Integer.toString(getMmsMsgHndToID(Integer.valueOf(name)))); + } + + return ResponseCodes.OBEX_HTTP_OK; + } + } + cr.close(); + + //Email + //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 = context.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.getCount() > 0) { + 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()); + } + + + //Query the message table for the given message id + int emailMsgId = 0; + Log.d(TAG, "\n name ::=> " + (Integer.valueOf(name))); + emailMsgId = (Integer.valueOf(name) - EMAIL_HDLR_CONSTANT); + + Uri uri2 = Uri.parse("content://com.android.email.provider/message/"+emailMsgId); + cr = context.getContentResolver().query(uri2, null, null, null, + null); + if (cr.moveToFirst()) { + + if (bluetoothMasAppParams.StatusIndicator == 0) { + /* Read Status */ + ContentValues values = new ContentValues(); + values.put("flagRead", bluetoothMasAppParams.StatusValue); + context.getContentResolver().update(uri2, values, null, null); + } + else { + if (bluetoothMasAppParams.StatusValue == 1) { //if the email is deleted + msgFolderId = cr.getInt(cr.getColumnIndex("mailboxKey")); + if(msgFolderId == deletedFolderId){ + //TODO need to add notification for deleted email here + mnsClient.addMceInitiatedOperation(name); + context.getContentResolver().delete( + Uri.parse("content://com.android.email.provider/message/" + + emailMsgId), null, null); + } + else{ + ContentValues values = new ContentValues(); + values.put("mailboxKey", deletedFolderId); + context.getContentResolver().update(uri2, values, null, null); + } + } + else{ // if the email is undeleted + ContentValues values = new ContentValues(); + values.put("mailboxKey", inboxFolderId); + context.getContentResolver().update(uri2, values, null, null); + } + } + return ResponseCodes.OBEX_HTTP_OK; + } + + + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + /** + * Sets the message update + * + * @return Obex response code + */ + public int msgUpdate(String name, + BluetoothMasAppParams bluetoothMasAppParams) { + + if ((bluetoothMasAppParams.StatusIndicator != 0) + && (bluetoothMasAppParams.StatusIndicator != 1)) { + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + + if ((bluetoothMasAppParams.StatusValue != 0) + && (bluetoothMasAppParams.StatusValue != 1)) { + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + + Uri uri = Uri.parse("content://sms/" + name); + + Cursor cr = context.getContentResolver().query(uri, null, null, null, + null); + if (cr.moveToFirst()) { + if (bluetoothMasAppParams.StatusIndicator == 0) { + /* Read Status */ + ContentValues values = new ContentValues(); + values.put("read", bluetoothMasAppParams.StatusValue); + context.getContentResolver().update(uri, values, null, null); + } else { + if (bluetoothMasAppParams.StatusValue == 1) { + deleteSMS(name); + } else if (bluetoothMasAppParams.StatusValue == 0) { + unDeleteSMS(name); + } + } + return ResponseCodes.OBEX_HTTP_OK; + } + // MMS MessageStatus processing begins here + cr.close(); + String whereClause = " _id= " + getMmsMsgHndToID(Integer.valueOf(name)); + uri = Uri.parse("content://mms/"); + if(getMmsMsgHndToID(Integer.valueOf(name)) > 0){ + cr = context.getContentResolver().query(uri, null, null, null, null); + } + if (cr.getCount() > 0) { + cr.moveToFirst(); + if (bluetoothMasAppParams.StatusIndicator == 0) { + /* Read Status */ + ContentValues values = new ContentValues(); + values.put("read", bluetoothMasAppParams.StatusValue); + int rowUpdate = context.getContentResolver().update(uri, + values, whereClause, null); + Log.d(TAG, "\nRows updated => " + Integer.toString(rowUpdate)); + return ResponseCodes.OBEX_HTTP_OK; + } else { + if (bluetoothMasAppParams.StatusValue == 1) { + deleteMMS(Integer.toString(getMmsMsgHndToID(Integer.valueOf(name)))); + } else if (bluetoothMasAppParams.StatusValue == 0) { + unDeleteMMS(Integer.toString(getMmsMsgHndToID(Integer.valueOf(name)))); + } + + return ResponseCodes.OBEX_HTTP_OK; + } + } + cr.close(); + + //Email + //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 = context.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.getCount() > 0) { + 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()); + } + + + //Query the message table for the given message id + int emailMsgId = 0; + Log.d(TAG, "\n name ::=> " + (Integer.valueOf(name))); + emailMsgId = (Integer.valueOf(name) - EMAIL_HDLR_CONSTANT); + + Uri uri2 = Uri.parse("content://com.android.email.provider/message/"+emailMsgId); + cr = context.getContentResolver().query(uri2, null, null, null, + null); + if (cr.moveToFirst()) { + + if (bluetoothMasAppParams.StatusIndicator == 0) { + /* Read Status */ + ContentValues values = new ContentValues(); + values.put("flagRead", bluetoothMasAppParams.StatusValue); + context.getContentResolver().update(uri2, values, null, null); + } + else { + if (bluetoothMasAppParams.StatusValue == 1) { //if the email is deleted + msgFolderId = cr.getInt(cr.getColumnIndex("mailboxKey")); + if(msgFolderId == deletedFolderId){ + //TODO need to add notification for deleted email here + mnsClient.addMceInitiatedOperation(name); + context.getContentResolver().delete( + Uri.parse("content://com.android.email.provider/message/" + + emailMsgId), null, null); + } + else{ + ContentValues values = new ContentValues(); + values.put("mailboxKey", deletedFolderId); + context.getContentResolver().update(uri2, values, null, null); + } + } + else{ // if the email is undeleted + ContentValues values = new ContentValues(); + values.put("mailboxKey", inboxFolderId); + context.getContentResolver().update(uri2, values, null, null); + } + } + return ResponseCodes.OBEX_HTTP_OK; + } + + + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + + + /** + * Enable/disable notification + * + * @return Obex response code + */ + public int notification(BluetoothDevice remoteDevice, + BluetoothMasAppParams bluetoothMasAppParams) { + // TODO Auto-generated method stub + + 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_INTERNAL_ERROR; + + } + + private void clearDeletedItems() { + // Remove the deleted item entries + context.getContentResolver().delete(Uri.parse("content://sms/"), + "thread_id = " + DELETED_THREAD_ID, null); + context.getContentResolver().delete(Uri.parse("content://mms/"), + "thread_id = " + DELETED_THREAD_ID, null); + } + + public void disconnect() { + clearDeletedItems(); + } + + + /** + * Start an MNS obex client session and push notification whenever available + */ + public void startMnsSession(BluetoothDevice remoteDevice) { + Log.d(TAG, "Start MNS Client"); + mnsClient.getHandler() + .obtainMessage(BluetoothMns.MNS_CONNECT, -1, -1, remoteDevice) + .sendToTarget(); + } + + /** + * Stop pushing notifications and disconnect MNS obex session + */ + public void stopMnsSession(BluetoothDevice remoteDevice) { + Log.d(TAG, "Stop MNS Client"); + mnsClient + .getHandler() + .obtainMessage(BluetoothMns.MNS_DISCONNECT, -1, -1, + remoteDevice).sendToTarget(); + } + + /** + * Push an event over MNS client to MNS server + */ + private void sendMnsEvent(String msg, String handle, String folder, + String old_folder, String msgType) { + Log.d(TAG, "Send MNS Event"); + mnsClient.sendMnsEvent(msg, handle, folder, old_folder, msgType); + } + + /** + * 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 = 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; + } + + /** + * Obtain the MMS message Handle + */ + + private Integer getMmsMsgHnd(int msgID) { + int handle = 0; + String whereClause = " mid= " + msgID + " AND ct=\"text/plain\""; + Uri uri = Uri.parse("content://mms/part"); + ContentResolver cr = context.getContentResolver(); + Cursor cursor = cr.query(uri, null, whereClause, null, null); + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + int handleInd = cursor.getColumnIndex("_id"); + handle = cursor.getInt(handleInd); + } + cursor.close(); + return handle; + } + + /** + * Obtain the MMS message ID from Handle + */ + + private Integer getMmsMsgHndToID(int msgHandle) { + int msgID = -1; + String whereClause = " mid= " + (msgHandle - MMS_HDLR_CONSTANT); + Uri uri = Uri.parse("content://mms/part"); + ContentResolver cr = context.getContentResolver(); + Cursor cursor = cr.query(uri, null, whereClause, null, null); + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + int handleInd = cursor.getColumnIndex("mid"); + msgID = cursor.getInt(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 = context.getContentResolver(); + Cursor crID = cr.query(uri, null, whereClause, null, 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 != 0) { + // Inbox, Outbox, Sent, Draft folders + whereClause = "msg_box=" + foldertype + " AND thread_id <> " + DELETED_THREAD_ID; + } else { + // Deleted folder + whereClause = "thread_id = " + DELETED_THREAD_ID; + } + + /* Filter readstatus: 0 no filtering, 0x01 get unread, 0x10 get read */ + if (appParams.FilterReadStatus != 0) { + if ((appParams.FilterReadStatus & 0x1) != 0) { + if (whereClause != "") { + whereClause += " AND "; + } + whereClause += " read=0 "; + } + if ((appParams.FilterReadStatus & 0x10) != 0) { + if (whereClause != "") { + 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); + if (whereClause != "") { + 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); + if (whereClause != "") { + whereClause += " AND "; + } + whereClause += "date < " + (time.toMillis(false))/1000; + } catch (TimeFormatException e) { + Log.d(TAG, "Bad formatted FilterPeriodEnd " + + appParams.FilterPeriodEnd); + } + } + + return whereClause; + } + + /** + * Obtain the MMS msg_box id + */ + + private int getMmsMsgBox(int msgID) { + int val = -1; + String whereClause = " _id= " + msgID; + Uri uri = Uri.parse("content://mms/"); + ContentResolver cr = context.getContentResolver(); + Cursor cursor = cr.query(uri, null, whereClause, null, 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(int msgID) { + String text = null; + String whereClause = " mid= " + msgID + " AND ct=\"text/plain\""; + Uri uri = Uri.parse("content://mms/part"); + ContentResolver cr = context.getContentResolver(); + Cursor cursor = cr.query(uri, null, whereClause, null, 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(int msgID) { + String text = null; + String whereClause = " _id= " + msgID; + Uri uri = Uri.parse("content://mms/"); + ContentResolver cr = context.getContentResolver(); + Cursor cursor = cr.query(uri, null, whereClause, null, 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 String getMmsMsgDate(int msgID) { + String text = null; + String whereClause = " _id= " + msgID; + Uri uri = Uri.parse("content://mms/"); + ContentResolver cr = context.getContentResolver(); + Cursor cursor = cr.query(uri, null, whereClause, null, null); + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + int dateInd = cursor.getColumnIndex("date"); + text = cursor.getString(dateInd); + } + cursor.close(); + return text; + + } + + /** + * Obtain the MMS attachment size + */ + + private int getMmsMsgAttachSize(int msgID) { + int attachSize = 0; + String whereClause = " _id= " + msgID; + Uri uri = Uri.parse("content://mms/"); + ContentResolver cr = context.getContentResolver(); + Cursor cursor = cr.query(uri, null, whereClause, null, 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(int msgID) { + String text = null; + String whereClause = " _id= " + msgID; + Uri uri = Uri.parse("content://mms/"); + ContentResolver cr = context.getContentResolver(); + Cursor cursor = cr.query(uri, null, whereClause, null, 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(int 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(int 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 = context.getContentResolver(); + Cursor cursor = cr.query(uri, null, whereClause, null, null); + if (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; + } + } + + cursor.close(); + return text; + + } + + /** + * Obtain the MMS message read protected + */ + + private String getMmsMsgProtected(int msgID) { + String text = null; + String whereClause = " _id= " + msgID; + Uri uri = Uri.parse("content://mms/"); + ContentResolver cr = context.getContentResolver(); + Cursor cursor = cr.query(uri, null, whereClause, null, 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 + */ + + private String getMmsMsgAddress(int msgID) { + String text = null; + String whereClause = " address != \"insert-address-token\""; + Uri uri = Uri.parse("content://mms/" + msgID + "/addr"); + ContentResolver cr = context.getContentResolver(); + Cursor cursor = cr.query(uri, null, whereClause, null, null); + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + int addressInd = cursor.getColumnIndex("address"); + text = cursor.getString(addressInd); + } + cursor.close(); + return text; + } + + /** + * Get the folder name (MAP representation) based on the message Handle + */ + private int getMmsContainingFolder(int msgID) { + int folderNum = 0; + String whereClause = " _id= " + msgID; + Uri uri = Uri.parse("content://mms/"); + ContentResolver cr = context.getContentResolver(); + Cursor cursor = cr.query(uri, null, whereClause, null, 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 String bldMmsBmsg(int msgID, BluetoothMasMessageRsp rsp) { + String bMessage = null; + Cursor cr = null; + Uri uri = Uri.parse("content://mms/"); + String whereClause = " _id = " + msgID; + cr = context.getContentResolver().query(uri, null, whereClause, null, + null); + + if (cr.getCount() > 0) { + cr.moveToFirst(); + String containingFolder = getMmsMapVirtualFolderName((getMmsContainingFolder(msgID))); + BmessageConsts bmsg = new BmessageConsts(); + + // Create a bMessage + + // TODO Get Current type + 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"); + VcardContent vcard = getVcardContent(getMmsMsgAddress(msgID)); + 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(); + Date date = new Date(Integer.valueOf(getMmsMsgDate(msgID))); + sb.append("Date: ").append(date.toString()).append("\r\n"); + + boolean NO_MIME = false; + boolean MIME = true; + boolean msgFormat = NO_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 = mu.toBmessageMMS(bmsg); + Log.d(TAG, str); + Log.d(TAG, "\n\n"); + + if (str != null && (str.length() > 0)) { + final String FILENAME = "message"; + FileOutputStream bos = null; + File file = new File(context.getFilesDir() + "/" + FILENAME); + file.delete(); + + try { + bos = context + .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(); + } + + File fileR = new File(context.getFilesDir() + "/" + FILENAME); + if (fileR.exists() == true) { + rsp.file = fileR; + rsp.fractionDeliver = 1; + } + + } + } + return bMessage; + } + + private boolean isOutgoingMMSMessage( int 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(int mmsMsgID, + BluetoothMasAppParams appParams, String folderName, String datetimeStr) { + + MsgListingConsts ml = new MsgListingConsts(); + + // Set the message handle + ml.setMsg_handle((mmsMsgID + MMS_HDLR_CONSTANT)); + + // 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) { + if (getMmsMsgTxt(mmsMsgID) == null) { + ml.setSize(0); + } else { + + ml.setSize(getMmsMsgTxt(mmsMsgID).length()); + + } + } + + // Set message type + if ((appParams.ParameterMask & BIT_TYPE) != 0) { + ml.setType("MMS"); + } + + 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 (isOutgoingMMSMessage(mmsMsgID) == false) { + recipientName = getOwnerName(); + } else { + recipientName = getContactName(getMmsMsgAddress(mmsMsgID)); + } + 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 = null; + if (isOutgoingMMSMessage(mmsMsgID) == true) { + senderName = getOwnerName(); + } else { + senderName = getContactName(getMmsMsgAddress(mmsMsgID)); + } + 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) { + if (getMmsMsgReadStatus(mmsMsgID).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_SENT) != 0) { + ml.setAttachment_size(getMmsMsgAttachSize(mmsMsgID)); + } + + return ml; + } + + /** + * Get the type (as in Mms ContentProvider) for the given table name + */ + private int getMMSFolderType(String folder) { + int type = 0; + if (folder.equalsIgnoreCase(Inbox)) { + type = 1; + } else if (folder.equalsIgnoreCase(Sent)) { + type = 2; + } else if (folder.equalsIgnoreCase(Drafts)) { + type = 3; + } else if (folder.equalsIgnoreCase(Outbox)) { + type = 4; + } else if (folder.equalsIgnoreCase(Failed)) { + type = 5; + } else if (folder.equalsIgnoreCase(Queued)) { + type = 6; + } + // Deleted will be folder 0 + return type; + } + /** + * 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 = context.getContentResolver(); + Cursor crID = cr.query(uri, null, null, null, null); + if (crID.getCount() > 0) { + crID.moveToFirst(); + int msgIDInd = crID.getColumnIndex("_id"); + handle = crID.getString(msgIDInd); + } + + if (handle != null) { + String whereClause = " _id= " + handle; + uri = Uri.parse("content://mms"); + crID = cr.query(uri, null, whereClause, null, null); + if (crID.getCount() > 0) { + crID.moveToFirst(); + ContentValues values = new ContentValues(); + values.put("msg_box", 4); + cr.update(uri, values, whereClause, null); + } + } + } + /** + * This method is used to take a Bmessage that was pushed and move it to the + * folder + */ + private void moveMMStoDrafts(String mmsMsg) { + + String folder = "drafts"; + BmessageConsts bMsg = mu.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 RPI-Messaging. If + * it does not /* find it then it then it assumes PTS tester format + */ + + int beginPos = MmsText.indexOf("Content-Disposition:inline") + + "Content-Disposition:inline".length(); + int endPos = MmsText.indexOf("--RPI-Messaging", beginPos); + + if (endPos < 0) { + beginPos = 0; + endPos = MmsText.length(); + } + + MmsText = (MmsText.substring(beginPos, endPos)).trim(); + + ContentValues values = new ContentValues(); + values.put("msg_box", 3); + 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 = Uri.parse("content://mms/" + folder); + ContentResolver cr = context.getContentResolver(); + uri = cr.insert(uri, values); + + String msgNum = uri.getLastPathSegment(); + int msgID = Integer.parseInt(msgNum); + Log.d(TAG, " NEW URI " + uri.toString()); + + // 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); + Log.d(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); + Log.d(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); + Log.d(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); + Log.d(TAG, " NEW URI " + uri.toString()); + + } + /** + * Method to construct body of bmessage using either MIME or no MIME + * + */ + private String bldMMSBody(BmessageConsts bMsg, boolean msgType, int msgID) { + boolean MIME = true; + StringBuilder sb = new StringBuilder(); + String retString = null; + if (msgType == MIME) { + 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=\"RPI-Messaging.42156.0\"") + .append("\r\n"); + sb.append("Content-Transfer-Encoding: 7bit").append("\r\n") + .append("\r\n"); + sb.append("MIME Message").append("\r\n"); + sb.append("--RPI-Messaging.42156.0").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("--RPI-Messaging.42156.0--").append("\r\n") + .append("\r\n"); + retString = sb.toString(); + + } 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"); + retString = sb.toString(); + } + return retString; + } + /** + * Method to create a thread for a pushed MMS message + * + */ + + private int createMMSThread(String address) { + int returnValue = 1; + ContentValues tempValue = new ContentValues(); + tempValue.put("address", address); + tempValue.put("type", "20"); + Uri tempUri = context.getContentResolver().insert( + Uri.parse("content://sms/"), tempValue); + + if (tempUri != null) { + Cursor tempCr = context.getContentResolver().query(tempUri, null, + null, null, null); + tempCr.moveToFirst(); + String newThreadIdStr = tempCr.getString(tempCr + .getColumnIndex("thread_id")); + tempCr.close(); + returnValue = Integer.valueOf(newThreadIdStr); + + context.getContentResolver().delete(tempUri, null, null); + } + + return returnValue; + } + +} diff --git a/src/com/android/bluetooth/map/BluetoothMasAppParams.java b/src/com/android/bluetooth/map/BluetoothMasAppParams.java new file mode 100644 index 000000000..8d30f6b35 --- /dev/null +++ b/src/com/android/bluetooth/map/BluetoothMasAppParams.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.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/com/android/bluetooth/map/BluetoothMasMsg.java b/src/com/android/bluetooth/map/BluetoothMasMsg.java new file mode 100644 index 000000000..31d5c59b1 --- /dev/null +++ b/src/com/android/bluetooth/map/BluetoothMasMsg.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map; + +import java.io.File; + +public class BluetoothMasMsg { + + byte fractionDeliver; + File file; + +} diff --git a/src/com/android/bluetooth/map/BluetoothMasObexServer.java b/src/com/android/bluetooth/map/BluetoothMasObexServer.java new file mode 100644 index 000000000..a24a91235 --- /dev/null +++ b/src/com/android/bluetooth/map/BluetoothMasObexServer.java @@ -0,0 +1,1122 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map; + +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 android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Message; +import android.text.format.Time; +import android.util.Log; + +import javax.obex.*; + + +import com.android.bluetooth.map.BluetoothMasAppIf.BluetoothMasMessageListingRsp; +import com.android.bluetooth.map.BluetoothMasAppIf.BluetoothMasMessageRsp; +import com.android.bluetooth.map.BluetoothMasAppIf.BluetoothMasPushMsgRsp; + + +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; + + 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 static MasState state = 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 BluetoothMasAppIf appIf; + + 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]); + Log.d(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]); + Log.d(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]; + } + Log.d(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]; + } + Log.d(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]; + + } + Log.d(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]; + } + Log.d(TAG, "FilterPeriodOriginator " + + appParams.FilterOriginator); + i += params[i]; + i += 1; + break; + + case BluetoothMasSpecParams.MAS_TAG_FILTER_MESSAGE_TYPE: + i += 2; + appParams.FilterMessageType = params[i]; + Log.d(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]; + Log.d(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]; + Log.d(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]; + Log.d(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]; + Log.d(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); + Log.d(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; + } + Log.d(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]; + Log.d(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]; + Log.d(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]; + Log.d(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]; + Log.d(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]; + Log.d(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) { + super(); + appIf = new BluetoothMasAppIf(context, "SMS_MMS_EMAIL"); + mConnectionId = -1; + mCallback = callback; + mContext = context; + mRemoteDevice = remoteDevice; + + Log.d(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 (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; + } + } + reply.setHeader(HeaderSet.WHO, uuid); + } catch (IOException e) { + Log.e(TAG, e.toString()); + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + + try { + byte[] remote = (byte[]) request.getHeader(HeaderSet.WHO); + if (remote != null) { + if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote)); + reply.setHeader(HeaderSet.TARGET, remote); + } + } catch (IOException e) { + Log.e(TAG, e.toString()); + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + + if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " + + "MSG_SESSION_ESTABLISHED msg."); + + Message msg = Message.obtain(mCallback); + msg.what = BluetoothMasService.MSG_SESSION_ESTABLISHED; + msg.sendToTarget(); + + state = MasState.MAS_SERVER_CONNECTED; + if (D) Log.d(TAG, "Connect(): Success"); + return ResponseCodes.OBEX_HTTP_OK; + } + + @Override + public void onDisconnect(final HeaderSet req, final HeaderSet resp) { + if (D) Log.d(TAG, "onDisconnect(): enter"); + appIf.disconnect(); + + 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 + appIf.stopMnsSession(mRemoteDevice); + + state = MasState.MAS_SERVER_DISCONNECTED; + } + + @Override + public int onAbort(HeaderSet request, HeaderSet reply) { + if (D) Log.e(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 (D) Log.e(TAG, "onSetPath(): supports SetPath request."); + + String tmpPath = null; + boolean retVal; + boolean tmpBackup = backup; + + if (tmpBackup && create) { + tmpBackup = true; + } else { + tmpBackup = false; + } + if (state != MasState.MAS_SERVER_CONNECTED) { + if (D) + Log.e(TAG, "onSetPath() Failed: Mas Server not connected"); + return ResponseCodes.OBEX_HTTP_UNAVAILABLE; + } + state = MasState.MAS_SERVER_SET_FOLDER; + + try { + tmpPath = (String) request.getHeader(HeaderSet.NAME); + } catch (IOException e) { + Log.e(TAG, "Get name header fail"); + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + if (D) + Log.e(TAG, "backup=" + backup + " create=" + create + " name=" + + tmpPath); + + retVal = appIf.setPath(backup, tmpPath); + state = MasState.MAS_SERVER_CONNECTED; + if (retVal == true) { + if (V) + Log.e(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 (mCallback != null) { + Message msg = Message.obtain(mCallback); + msg.what = BluetoothMasService.MSG_SERVERSESSION_CLOSE; + msg.sendToTarget(); + if (D) Log.e(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out."); + } + } + + @Override + public int onGet(Operation op) { + + byte[] appParams = null; + boolean retVal = true; + + if (D) Log.e(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"); + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + + masAppParams.clear(); + retVal = masAppParams.parse(appParams); + + if (type == null || (retVal == false) ) { + return ResponseCodes.OBEX_HTTP_BAD_REQUEST; + } + + Log.e(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); + } + + Log.e(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 DEFAULT_FILE = "PushMsg.txt"; + 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; + + file = new File(mContext.getFilesDir() + "/" + DEFAULT_FILE); + + try { + is = op.openInputStream(); + } catch (IOException e1) { + Log.e(TAG, "Error while opening InputStream"); + error = true; + } + + if (error != true) { + try { + + FileOutputStream fos = mContext.openFileOutput(DEFAULT_FILE, + 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) { + pMsg = appIf.pushMsg(name, file, masAppParams.get()); + + 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 ResponseCodes.OBEX_HTTP_BAD_REQUEST; + } + } else { + return ResponseCodes.OBEX_HTTP_BAD_REQUEST; + } + } + + private final int msgStatus(Operation op, String name) { + if (D) Log.d(TAG, "msgStatus: Enter"); + return appIf.msgStatus(name, masAppParams.get()); + } + + private final int msgUpdate(Operation op, String name) { + if (D) Log.d(TAG, "msgUpdate: Enter"); + return appIf.msgUpdate(name, masAppParams.get()); + } + + private final int notification(Operation op) { + return appIf.notification(mRemoteDevice, masAppParams.get()); + } + + @Override + public int onPut(Operation op) { + + byte[] appParams = null; + boolean retVal = true; + BluetoothMasAppParams tmp; + + 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"); + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + masAppParams.clear(); + if ( appParams != null ){ + masAppParams.parse(appParams); + } + if(type == null || retVal == false) { + return ResponseCodes.OBEX_HTTP_BAD_REQUEST; + } + + tmp = masAppParams.get(); + + if (tmp.Charset == 0x00) { + return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; + } + Log.e(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, name); + } + if (type.equals(TYPE_MESSAGE_NOTIFICATION)) { + return notification(op); + } + Log.e(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; + } + 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_INTERNAL_ERROR; + } + + int position = 0; + long timestamp = 0; + int outputBufferSize = op.getMaxPacketSize(); + if (V) Log.d(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_INTERNAL_ERROR; + break; + } + if (V) { + Log.d(TAG, "Sending folderlist String position = " + position + + " readLength " + readLength + " bytes took " + + (System.currentTimeMillis() - timestamp) + " ms"); + } + position += readLength; + } + + if (V) Log.e(TAG, "Send Data complete!"); + + if (!closeStream(outputStream, op)) { + Log.e(TAG,"Send Folder Listing Body - Close output stream error! "); + pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + if (V) Log.e(TAG, "Send Folder Listing Body complete! result = " + pushResult); + return pushResult; + } + + private final int sendBody(Operation op, File fileinfo) { + + Log.e(TAG, "sendFile = " + fileinfo.getName()); + int position = 0; + int readLength = 0; + int outputBufferSize = op.getMaxPacketSize(); + long timestamp = 0; + FileInputStream fileInputStream; + 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.e(TAG, "Sending file position = " + position + + " readLength " + readLength + " bytes took " + + (System.currentTimeMillis() - timestamp) + " ms"); + } + } + } catch (IOException e) { + return ResponseCodes.OBEX_HTTP_BAD_REQUEST; + } + 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; + byte[] val = new byte[1]; + + if (D) Log.d(TAG, "SendMsg : Enter"); + msg = appIf.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; + + if (D) Log.d(TAG, "SendFolderListing : Enter"); + folderListSize = appIf.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, appIf.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 = appIf.msgListing(name, + masAppParams.get()); + + if(appIfMsgListRsp == null || appIfMsgListRsp.rsp != ResponseCodes.OBEX_HTTP_OK) { + return appIfMsgListRsp.rsp; + } + + if (D) Log.d(TAG, "SendMsgListing : Enter"); + + 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; + } +}; + diff --git a/src/com/android/bluetooth/map/BluetoothMasReceiver.java b/src/com/android/bluetooth/map/BluetoothMasReceiver.java new file mode 100644 index 000000000..c0860848f --- /dev/null +++ b/src/com/android/bluetooth/map/BluetoothMasReceiver.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map; +import android.bluetooth.BluetoothAdapter; +import android.content.Intent; +import android.content.BroadcastReceiver; +import android.content.Context; +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()); + Log.e(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 (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + 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/com/android/bluetooth/map/BluetoothMasService.java b/src/com/android/bluetooth/map/BluetoothMasService.java new file mode 100644 index 000000000..9028499a3 --- /dev/null +++ b/src/com/android/bluetooth/map/BluetoothMasService.java @@ -0,0 +1,629 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map; + + +import com.android.bluetooth.R; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +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.database.Cursor; +import android.database.CursorJoiner; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.PowerManager; +import android.text.TextUtils; +import android.util.Log; + +import javax.obex.*; + + +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; + + private int mState; + + /** + * Intent indicating incoming connection request which is sent to + * BluetoothMasActivity + */ + public static final String ACCESS_REQUEST_ACTION = "com.android.bluetooth.map.accessrequest"; + + /** + * Intent indicating incoming connection request accepted by user which is + * sent from BluetoothMasActivity + */ + public static final String ACCESS_ALLOWED_ACTION = "com.android.bluetooth.map.accessallowed"; + + /** + * Intent indicating incoming connection request denied by user which is + * sent from BluetoothMasActivity + */ + public static final String ACCESS_DISALLOWED_ACTION = "com.android.bluetooth.map.accessdisallowed"; + + /** + * Intent indicating incoming obex authentication request which is from + * PCE(Carkit) + */ + public static final String AUTH_CHALL_ACTION = "com.android.bluetooth.map.authchall"; + + /** + * Intent indicating obex session key input complete by user which is sent + * from BluetoothMasActivity + */ + public static final String AUTH_RESPONSE_ACTION = "com.android.bluetooth.map.authresponse"; + + /** + * Intent indicating user canceled obex authentication session key input + * which is sent from BluetoothMasActivity + */ + public static final String AUTH_CANCELLED_ACTION = "com.android.bluetooth.map.authcancelled"; + + /** + * Intent indicating timeout for user confirmation, which is sent to + * BluetoothMasActivity + */ + public static final String USER_CONFIRM_TIMEOUT_ACTION = "com.android.bluetooth.map.userconfirmtimeout"; + + public static final String THIS_PACKAGE_NAME = "com.android.bluetooth"; + + /** + * Intent Extra name indicating always allowed which is sent from + * BluetoothMasActivity + */ + public static final String EXTRA_ALWAYS_ALLOWED = "com.android.bluetooth.map.alwaysallowed"; + + /** + * Intent Extra name indicating session key which is sent from + * BluetoothMasActivity + */ + public static final String EXTRA_SESSION_KEY = "com.android.bluetooth.map.sessionkey"; + + 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 START_LISTENER = 1; + + private static final int USER_TIMEOUT = 2; + + private static final int AUTH_TIMEOUT = 3; + + private static final int PORT_NUM = 16; + + private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000; + + private static final int TIME_TO_WAIT_VALUE = 6000; + + // Ensure not conflict with Opp notification ID + private static final int NOTIFICATION_ID_ACCESS = -1000005; + + private static final int NOTIFICATION_ID_AUTH = -1000006; + + private PowerManager.WakeLock mWakeLock = null; + + private BluetoothAdapter mAdapter; + + private SocketAcceptThread mAcceptThread = null; + + private BluetoothMapAuthenticator mAuth = null; + + private BluetoothServerSocket mServerSocket = null; + + private BluetoothSocket mConnSocket = null; + + public static BluetoothDevice mRemoteDevice = null; + + private static String sRemoteDeviceName = null; + + private boolean mHasStarted = false; + + private volatile boolean mInterrupted; + + private int mStartId = -1; + + private BluetoothMasObexServer mMapServer = null; + + private ServerSession mServerSession = null; + + public BluetoothMasService() { + } + + @Override + public void onCreate() { + super.onCreate(); + if (VERBOSE) + Log.e(TAG, "Map Service onCreate"); + Log.e(TAG, "Map Service onCreate"); + + mAdapter = BluetoothAdapter.getDefaultAdapter(); + + if (!mHasStarted) { + mHasStarted = true; + if (VERBOSE) + Log.e(TAG, "Starting MAP service"); + + int state = mAdapter.getState(); + if (state == BluetoothAdapter.STATE_ON) { + mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler + .obtainMessage(START_LISTENER), TIME_TO_WAIT_VALUE); + } + } + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (VERBOSE) + Log.e(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.e(TAG, "action: " + action); + + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + BluetoothAdapter.ERROR); + boolean removeTimeoutMsg = true; + if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + removeTimeoutMsg = false; + if (state == BluetoothAdapter.STATE_OFF) { + // Release all resources + closeService(); + } + } else if (action.equals(Intent.ACTION_MEDIA_EJECT) + || action.equals(Intent.ACTION_MEDIA_MOUNTED)) { + closeService(); + } else if (action.equals(ACCESS_ALLOWED_ACTION)) { + if (intent.getBooleanExtra(EXTRA_ALWAYS_ALLOWED, false)) { + boolean result = true; + if (VERBOSE) + Log.v(TAG, "setTrust() result=" + result); + } + try { + if (mConnSocket != null) { + startObexServerSession(); + } else { + stopObexServerSession(); + } + } catch (IOException ex) { + Log.e(TAG, "Caught the error: " + ex.toString()); + } + } else if (action.equals(ACCESS_DISALLOWED_ACTION)) { + stopObexServerSession(); + } else if (action.equals(AUTH_RESPONSE_ACTION)) { + String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY); + notifyAuthKeyInput(sessionkey); + } else if (action.equals(AUTH_CANCELLED_ACTION)) { + notifyAuthCancelled(); + } else { + removeTimeoutMsg = false; + } + + if (removeTimeoutMsg) { + mSessionStatusHandler.removeMessages(USER_TIMEOUT); + } + } + + @Override + public void onDestroy() { + if (VERBOSE) + Log.e(TAG, "Map Service onDestroy"); + + super.onDestroy(); + if (mWakeLock != null) { + mWakeLock.release(); + mWakeLock = null; + } + closeService(); + } + + @Override + public IBinder onBind(Intent intent) { + if (VERBOSE) + Log.e(TAG, "Map Service onBind"); + return null;// mBind; + } + + private void startRfcommSocketListener() { + if (VERBOSE) + Log.e(TAG, "Map Service startRfcommSocketListener"); + + if (mServerSocket == null) { + if (!initSocket()) { + closeService(); + return; + } + } + if (mAcceptThread == null) { + mAcceptThread = new SocketAcceptThread(); + mAcceptThread.setName("BluetoothMapAcceptThread"); + mAcceptThread.start(); + } + } + + private final boolean initSocket() { + if (VERBOSE) + Log.e(TAG, "Map Service initSocket"); + + boolean initSocketOK = true; + final int CREATE_RETRY_TIME = 10; + + // It's possible that create will fail in some cases. retry for 10 times + for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) { + try { + Method m = mAdapter.getClass().getMethod("listenUsingRfcommOn", + new Class[] { int.class }); + try { + mServerSocket = (BluetoothServerSocket) m.invoke(mAdapter, + PORT_NUM); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + if (!initSocketOK) { + synchronized (this) { + try { + if (VERBOSE) + Log.e(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.e(TAG, "Succeed to create listening socket on channel " + + PORT_NUM); + + } else { + Log.e(TAG, "Error to create listening socket after " + + CREATE_RETRY_TIME + " try"); + } + return initSocketOK; + } + + private final void closeSocket(boolean server, boolean accept) + throws IOException { + if (server == true) { + // Stop the possible trying to init serverSocket + mInterrupted = true; + + if (mServerSocket != null) { + mServerSocket.close(); + } + } + + if (accept == true) { + if (mConnSocket != null) { + mConnSocket.close(); + } + } + } + + private final void closeService() { + if (VERBOSE) + Log.e(TAG, "Map Service closeService"); + + try { + closeSocket(true, true); + } catch (IOException ex) { + Log.e(TAG, "CloseSocket error: " + ex); + } + + if (mAcceptThread != null) { + try { + mAcceptThread.shutdown(); + mAcceptThread.join(); + mAcceptThread = null; + } catch (InterruptedException ex) { + Log.w(TAG, "mAcceptThread close error" + ex); + } + } + mServerSocket = null; + mConnSocket = null; + + if (mServerSession != null) { + mServerSession.close(); + mServerSession = null; + } + + mHasStarted = false; + if (stopSelfResult(mStartId)) { + if (VERBOSE) + Log.e(TAG, "successfully stopped map service"); + } + } + + private final void startObexServerSession() throws IOException { + if (VERBOSE) + Log.v(TAG, "Map Service startObexServerSession"); + + // acquire the wakeLock before start Obex transaction thread + if (mWakeLock == null) { + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, + "StartingObexMapTransaction"); + mWakeLock.setReferenceCounted(false); + mWakeLock.acquire(); + } + + mMapServer = new BluetoothMasObexServer(mSessionStatusHandler, + mRemoteDevice, this); + synchronized (this) { + mAuth = new BluetoothMapAuthenticator(mSessionStatusHandler); + mAuth.setChallenged(false); + mAuth.setCancelled(false); + } + BluetoothMapRfcommTransport transport = new BluetoothMapRfcommTransport( + mConnSocket); + mServerSession = new ServerSession(transport, mMapServer, mAuth); + if (VERBOSE) { + Log.e(TAG, "startObexServerSession() success!"); + } + } + + private void stopObexServerSession() { + if (VERBOSE) + Log.e(TAG, "Map Service stopObexServerSession"); + + // Release the wake lock if obex transaction is over + if (mWakeLock != null) { + mWakeLock.release(); + mWakeLock = null; + } + + if (mServerSession != null) { + mServerSession.close(); + mServerSession = null; + } + + mAcceptThread = null; + + try { + closeSocket(false, true); + mConnSocket = null; + } catch (IOException e) { + Log.e(TAG, "closeSocket error: " + e.toString()); + } + // Last obex transaction is finished, we start to listen for incoming + // connection again + if (mAdapter.isEnabled()) { + startRfcommSocketListener(); + } + } + + private void notifyAuthKeyInput(final String key) { + synchronized (mAuth) { + if (key != null) { + mAuth.setSessionKey(key); + } + mAuth.setChallenged(true); + mAuth.notify(); + } + } + + private void notifyAuthCancelled() { + synchronized (mAuth) { + mAuth.setCancelled(true); + mAuth.notify(); + } + } + + /** + * A thread that runs in the background waiting for remote rfcomm + * connect.Once a remote socket connected, this thread shall be + * shutdown.When the remote disconnect,this thread shall run again waiting + * for next request. + */ + private class SocketAcceptThread extends Thread { + + private boolean stopped = false; + + @Override + public void run() { + while (!stopped) { + try { + mConnSocket = mServerSocket.accept(); + + mRemoteDevice = mConnSocket.getRemoteDevice(); + if (mRemoteDevice == null) { + Log.i(TAG, "getRemoteDevice() = null"); + break; + } + sRemoteDeviceName = mRemoteDevice.getName(); + // In case getRemoteName failed and return null + if (TextUtils.isEmpty(sRemoteDeviceName)) { + sRemoteDeviceName = getString(R.string.defaultname); + } + boolean trust = true; + if (VERBOSE) + Log.e(TAG, "GetTrustState() = " + trust); + + if (trust) { + try { + if (VERBOSE) Log.e( TAG, "incomming connection accepted from: " + + sRemoteDeviceName + " automatically as trusted device"); + startObexServerSession(); + } catch (IOException ex) { + Log.e(TAG, "catch exception starting obex server session" + + ex.toString()); + } + } else { + createMapNotification(ACCESS_REQUEST_ACTION); + if (VERBOSE) Log.e(TAG, "incomming connection accepted from: " + + sRemoteDeviceName); + mSessionStatusHandler.sendMessageDelayed( + mSessionStatusHandler.obtainMessage(USER_TIMEOUT), + USER_CONFIRM_TIMEOUT_VALUE); + } + stopped = true; // job done ,close this thread; + } catch (IOException ex) { + if (stopped) { + break; + } + if (VERBOSE) + Log.e(TAG, "Accept exception: " + ex.toString()); + } + } + } + + void shutdown() { + stopped = true; + interrupt(); + } + } + + private final Handler mSessionStatusHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (VERBOSE) + Log.e(TAG, "Handler(): got msg=" + msg.what); + + switch (msg.what) { + case START_LISTENER: + if (mAdapter.isEnabled()) { + startRfcommSocketListener(); + } else { + closeService();// release all resources + } + break; + case USER_TIMEOUT: + Intent intent = new Intent(USER_CONFIRM_TIMEOUT_ACTION); + sendBroadcast(intent); + removeMapNotification(NOTIFICATION_ID_ACCESS); + break; + case AUTH_TIMEOUT: + Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION); + sendBroadcast(i); + removeMapNotification(NOTIFICATION_ID_AUTH); + notifyAuthCancelled(); + break; + case MSG_SERVERSESSION_CLOSE: + stopObexServerSession(); + break; + case MSG_SESSION_ESTABLISHED: + break; + case MSG_SESSION_DISCONNECTED: + // case MSG_SERVERSESSION_CLOSE will handle ,so just skip + break; + default: + break; + } + } + }; + + private void createMapNotification(String action) { + } + + private void removeMapNotification(int id) { + Context context = getApplicationContext(); + NotificationManager nm = (NotificationManager) context + .getSystemService(Context.NOTIFICATION_SERVICE); + nm.cancel(id); + } + + public static String getRemoteDeviceName() { + return sRemoteDeviceName; + } +}; diff --git a/src/com/android/bluetooth/map/BluetoothMasSpecParams.java b/src/com/android/bluetooth/map/BluetoothMasSpecParams.java new file mode 100644 index 000000000..dfeb7561a --- /dev/null +++ b/src/com/android/bluetooth/map/BluetoothMasSpecParams.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.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/com/android/bluetooth/map/BluetoothMasTestActivity.java b/src/com/android/bluetooth/map/BluetoothMasTestActivity.java new file mode 100644 index 000000000..e1abf82b5 --- /dev/null +++ b/src/com/android/bluetooth/map/BluetoothMasTestActivity.java @@ -0,0 +1,291 @@ + /* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map; + + +import com.android.bluetooth.R; + +import android.app.Activity; +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; + +/** + * 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 createDisplayText(final int id) { + String mRemoteName = BluetoothMasService.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/com/android/bluetooth/map/BluetoothMns.java b/src/com/android/bluetooth/map/BluetoothMns.java new file mode 100644 index 000000000..c1387d8c8 --- /dev/null +++ b/src/com/android/bluetooth/map/BluetoothMns.java @@ -0,0 +1,3477 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map; + + + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSocket; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.database.Cursor; +import android.database.CursorJoiner; +import android.net.Uri; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelUuid; +import android.os.Parcelable; +import android.os.Process; +import android.text.format.Time; +import android.util.Log; + +import com.android.bluetooth.map.MapUtils.MapUtils; +import com.android.bluetooth.map.MapUtils.EmailUtils; + +import javax.obex.*; + +/** + * This class run an MNS session. + */ +public class BluetoothMns { + private static final String TAG = "BtMns"; + + private static final boolean D = BluetoothMasService.DEBUG; + + 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 SDP_RESULT = 12; + + public static final int MNS_CONNECT = 13; + + public static final int MNS_DISCONNECT = 14; + + public static final int MNS_SEND_EVENT = 15; + + private static final int CONNECT_WAIT_TIMEOUT = 45000; + + private static final int CONNECT_RETRY_TIME = 100; + + 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"; + + public static final int MMS_HDLR_CONSTANT = 100000; + + public static final int EMAIL_HDLR_CONSTANT = 200000; + + private static final int MSG_CP_INBOX_TYPE = 1; + + private static final int MSG_CP_SENT_TYPE = 2; + + private static final int MSG_CP_DRAFT_TYPE = 3; + + private static final int MSG_CP_OUTBOX_TYPE = 4; + + private static final int MSG_CP_FAILED_TYPE = 5; + + private static final int MSG_CP_QUEUED_TYPE = 6; + + private Context mContext; + + private BluetoothAdapter mAdapter; + + private BluetoothMnsObexSession mSession; + + private int mStartId = -1; + + private ObexTransport mTransport; + + private HandlerThread mHandlerThread; + + private EventHandler mSessionHandler; + + private BluetoothDevice mDestination; + + private MapUtils mu = null; + + public static final ParcelUuid BluetoothUuid_ObexMns = ParcelUuid + .fromString("00001133-0000-1000-8000-00805F9B34FB"); + + private long mTimestamp; + + public String deletedFolderName = null; + + public BluetoothMns(Context context) { + /* check Bluetooth enable status */ + /* + * normally it's impossible to reach here if BT is disabled. Just check + * for safety + */ + + mAdapter = BluetoothAdapter.getDefaultAdapter(); + mContext = context; + + mDestination = BluetoothMasService.mRemoteDevice; + + mu = new MapUtils(); + + if (!mAdapter.isEnabled()) { + Log.e(TAG, "Can't send event when Bluetooth is disabled "); + return; + } + + if (mHandlerThread == null) { + if (V) Log.v(TAG, "Create handler thread for batch "); + mHandlerThread = new HandlerThread("Bt MNS Transfer Handler", + Process.THREAD_PRIORITY_BACKGROUND); + mHandlerThread.start(); + mSessionHandler = new EventHandler(mHandlerThread.getLooper()); + } + } + + public Handler getHandler() { + return mSessionHandler; + } + + /* + * Receives events from mConnectThread & mSession back in the main thread. + */ + private class EventHandler extends Handler { + public EventHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + Log.d(TAG, " Handle Message " + msg.what); + switch (msg.what) { + case MNS_CONNECT: + if (mSession != null) { + Log.d(TAG, "Disconnect previous obex connection"); + mSession.disconnect(); + mSession = null; + } + start((BluetoothDevice) msg.obj); + break; + case MNS_DISCONNECT: + deregisterUpdates(); + stop(); + break; + case SDP_RESULT: + if (V) Log.v(TAG, "SDP request returned " + msg.arg1 + + " (" + (System.currentTimeMillis() - mTimestamp + " ms)")); + if (!((BluetoothDevice) msg.obj).equals(mDestination)) { + return; + } + try { + mContext.unregisterReceiver(mReceiver); + } catch (IllegalArgumentException e) { + // ignore + } + if (msg.arg1 > 0) { + mConnectThread = new SocketConnectThread(mDestination, + msg.arg1); + mConnectThread.start(); + } else { + /* SDP query fail case */ + Log.e(TAG, "SDP query failed!"); + } + + 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"); + mConnectThread = null; + + break; + /* + * RFCOMM connected. Do an OBEX connect by starting the session + */ + case RFCOMM_CONNECTED: + if (V) Log.v(TAG, "Transfer receive RFCOMM_CONNECTED msg"); + mConnectThread = null; + mTransport = (ObexTransport) msg.obj; + startObexSession(); + registerUpdates(); + + break; + + /* Handle the error state of an Obex session */ + case BluetoothMnsObexSession.MSG_SESSION_ERROR: + if (V) Log.v(TAG, "receive MSG_SESSION_ERROR"); + deregisterUpdates(); + mSession.disconnect(); + mSession = null; + break; + + case BluetoothMnsObexSession.MSG_CONNECT_TIMEOUT: + if (V) Log.v(TAG, "receive MSG_CONNECT_TIMEOUT"); + /* + * for outbound transfer, the block point is + * BluetoothSocket.write() The only way to unblock is to tear + * down lower transport + */ + try { + if (mTransport == null) { + Log.v(TAG,"receive MSG_SHARE_INTERRUPTED but " + + "mTransport = null"); + } else { + mTransport.close(); + } + } catch (IOException e) { + Log.e(TAG, "failed to close mTransport"); + } + if (V) Log.v(TAG, "mTransport closed "); + + break; + + case MNS_SEND_EVENT: + sendEvent((String) msg.obj); + break; + } + } + } + + /* + * 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(); + + for ( BluetoothMnsMsgHndlMceInitOp op: opList) { + // Remove stale entries + if ( currentTime.toMillis(false) - op.time.toMillis(false) > 10000 ) { + 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; + } + } + } + return location; + } + + + /** + * Post a MNS Event to the MNS thread + */ + public void sendMnsEvent(String msg, String handle, String folder, + String old_folder, String 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 { + location = findLocationMceInitiatedOperation(handle); + } + + if (location == -1) { + String str = mu.mapEventReportXML(msg, handle, folder, old_folder, + msgType); + mSessionHandler.obtainMessage(MNS_SEND_EVENT, -1, -1, str) + .sendToTarget(); + } else { + removeMceInitiatedOperation(location); + } + } + + /** + * Push the message over Obex client session + */ + private void sendEvent(String str) { + if (str != null && (str.length() > 0)) { + + Log.d(TAG, "--------------"); + Log.d(TAG, " CONTENT OF EVENT REPORT FILE: " + str); + + final String FILENAME = "EventReport"; + 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) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + File fileR = new File(mContext.getFilesDir() + "/" + FILENAME); + if (fileR.exists() == true) { + Log.d(TAG, " Sending event report file "); + mSession.sendEvent(fileR, (byte) 0); + } else { + Log.d(TAG, " ERROR IN CREATING SEND EVENT OBJ FILE"); + } + } + } + + private boolean updatesRegistered = false; + + /** + * Register with content provider to receive updates + * of change on cursor. + */ + private void registerUpdates() { + + Log.d(TAG, "REGISTER MNS UPDATES"); + + Uri smsUri = Uri.parse("content://sms/"); + crSmsA = mContext.getContentResolver().query(smsUri, + new String[] { "_id", "body", "type" }, null, null, "_id asc"); + crSmsB = mContext.getContentResolver().query(smsUri, + new String[] { "_id", "body", "type" }, null, null, "_id asc"); + + Uri smsInboxUri = Uri.parse("content://sms/inbox/"); + crSmsInboxA = mContext.getContentResolver().query(smsInboxUri, + new String[] { "_id", "body", "type" }, null, null, "_id asc"); + crSmsInboxB = mContext.getContentResolver().query(smsInboxUri, + new String[] { "_id", "body", "type" }, null, null, "_id asc"); + + Uri smsSentUri = Uri.parse("content://sms/sent/"); + crSmsSentA = mContext.getContentResolver().query(smsSentUri, + new String[] { "_id", "body", "type" }, null, null, "_id asc"); + crSmsSentB = mContext.getContentResolver().query(smsSentUri, + new String[] { "_id", "body", "type" }, null, null, "_id asc"); + + Uri smsDraftUri = Uri.parse("content://sms/draft/"); + crSmsDraftA = mContext.getContentResolver().query(smsDraftUri, + new String[] { "_id", "body", "type" }, null, null, "_id asc"); + crSmsDraftB = mContext.getContentResolver().query(smsDraftUri, + new String[] { "_id", "body", "type" }, null, null, "_id asc"); + + Uri smsOutboxUri = Uri.parse("content://sms/outbox/"); + crSmsOutboxA = mContext.getContentResolver().query(smsOutboxUri, + new String[] { "_id", "body", "type" }, null, null, "_id asc"); + crSmsOutboxB = mContext.getContentResolver().query(smsOutboxUri, + new String[] { "_id", "body", "type" }, null, null, "_id asc"); + + Uri smsFailedUri = Uri.parse("content://sms/failed/"); + crSmsFailedA = mContext.getContentResolver().query(smsFailedUri, + new String[] { "_id", "body", "type" }, null, null, "_id asc"); + crSmsFailedB = mContext.getContentResolver().query(smsFailedUri, + new String[] { "_id", "body", "type" }, null, null, "_id asc"); + + Uri smsQueuedUri = Uri.parse("content://sms/queued/"); + crSmsQueuedA = mContext.getContentResolver().query(smsQueuedUri, + new String[] { "_id", "body", "type" }, null, null, "_id asc"); + crSmsQueuedB = mContext.getContentResolver().query(smsQueuedUri, + new String[] { "_id", "body", "type" }, null, null, "_id asc"); + + Uri smsObserverUri = Uri.parse("content://mms-sms/"); + mContext.getContentResolver().registerContentObserver(smsObserverUri, + true, smsContentObserver); + + Uri mmsUri = Uri.parse("content://mms/"); + crMmsA = mContext.getContentResolver() + .query(mmsUri, new String[] { "_id", "read", "m_type" }, null, + null, "_id asc"); + crMmsB = mContext.getContentResolver() + .query(mmsUri, new String[] { "_id", "read", "m_type" }, null, + null, "_id asc"); + + Uri mmsOutboxUri = Uri.parse("content://mms/outbox/"); + crMmsOutboxA = mContext.getContentResolver() + .query(mmsOutboxUri, new String[] { "_id", "read", "m_type" }, + null, null, "_id asc"); + crMmsOutboxB = mContext.getContentResolver() + .query(mmsOutboxUri, new String[] { "_id", "read", "m_type" }, + null, null, "_id asc"); + Uri mmsDraftUri = Uri.parse("content://mms/drafts/"); + crMmsDraftA = mContext.getContentResolver() + .query(mmsDraftUri, new String[] { "_id", "read", "m_type" }, + null, null, "_id asc"); + crMmsDraftB = mContext.getContentResolver() + .query(mmsDraftUri, new String[] { "_id", "read", "m_type" }, + null, null, "_id asc"); + Uri mmsInboxUri = Uri.parse("content://mms/inbox/"); + crMmsInboxA = mContext.getContentResolver() + .query(mmsInboxUri, new String[] { "_id", "read", "m_type" }, + null, null, "_id asc"); + crMmsInboxB = mContext.getContentResolver() + .query(mmsInboxUri, new String[] { "_id", "read", "m_type" }, + null, null, "_id asc"); + + Uri mmsSentUri = Uri.parse("content://mms/sent/"); + crMmsSentA = mContext.getContentResolver() + .query(mmsSentUri, new String[] { "_id", "read", "m_type" }, + null, null, "_id asc"); + crMmsSentB = mContext.getContentResolver() + .query(mmsSentUri, new String[] { "_id", "read", "m_type" }, + null, null, "_id asc"); + + //email start + Uri emailUri = Uri.parse("content://com.android.email.provider/message"); + crEmailA = mContext.getContentResolver().query(emailUri, + new String[] { "_id", "mailboxkey" }, null, null, "_id asc"); + crEmailB = mContext.getContentResolver().query(emailUri, + new String[] { "_id", "mailboxkey" }, null, null, "_id asc"); + + EmailUtils eu = new EmailUtils(); + String emailInboxCondition = eu.getWhereIsQueryForTypeEmail("inbox", mContext); + crEmailInboxA = mContext.getContentResolver().query(emailUri, + new String[] { "_id", "mailboxkey" }, emailInboxCondition, null, "_id asc"); + crEmailInboxB = mContext.getContentResolver().query(emailUri, + new String[] { "_id", "mailboxkey" }, emailInboxCondition, null, "_id asc"); + + String emailSentCondition = eu.getWhereIsQueryForTypeEmail("sent", mContext); + crEmailSentA = mContext.getContentResolver().query(emailUri, + new String[] {"_id", "mailboxkey" }, emailSentCondition, null, "_id asc"); + crEmailSentB = mContext.getContentResolver().query(emailUri, + new String[] {"_id", "mailboxkey" }, emailSentCondition, null, "_id asc"); + + String emailDraftCondition = eu.getWhereIsQueryForTypeEmail("drafts", mContext); + crEmailDraftA = mContext.getContentResolver().query(emailUri, + new String[] {"_id", "mailboxkey"}, emailDraftCondition, null, "_id asc"); + crEmailDraftB = mContext.getContentResolver().query(emailUri, + new String[] {"_id", "mailboxkey" }, emailDraftCondition, null, "_id asc"); + + String emailOutboxCondition = eu.getWhereIsQueryForTypeEmail("outbox", mContext); + crEmailOutboxA = mContext.getContentResolver().query(emailUri, + new String[] {"_id", "mailboxkey"}, emailOutboxCondition, null, "_id asc"); + crEmailOutboxB = mContext.getContentResolver().query(emailUri, + new String[] { "_id", "mailboxkey"}, emailOutboxCondition, null, "_id asc"); + + Uri emailObserverUri = Uri.parse("content://com.android.email.provider/message"); + mContext.getContentResolver().registerContentObserver(emailObserverUri, + true, emailContentObserver); + + Uri emailInboxObserverUri = Uri.parse("content://com.android.email.provider/message"); + mContext.getContentResolver().registerContentObserver( + emailInboxObserverUri, true, emailInboxContentObserver); + + Uri emailSentObserverUri = Uri.parse("content://com.android.email.provider/message"); + mContext.getContentResolver().registerContentObserver( + emailSentObserverUri, true, emailSentContentObserver); + + Uri emailDraftObserverUri = Uri.parse("content://com.android.email.provider/message"); + mContext.getContentResolver().registerContentObserver( + emailDraftObserverUri, true, emailDraftContentObserver); + + Uri emailOutboxObserverUri = Uri.parse("content://com.android.email.provider/message"); + mContext.getContentResolver().registerContentObserver( + emailOutboxObserverUri, true, emailOutboxContentObserver); + + //email end + + + Uri smsInboxObserverUri = Uri.parse("content://mms-sms/inbox"); + mContext.getContentResolver().registerContentObserver( + smsInboxObserverUri, true, inboxContentObserver); + + Uri smsSentObserverUri = Uri.parse("content://mms-sms/sent"); + mContext.getContentResolver().registerContentObserver( + smsSentObserverUri, true, sentContentObserver); + + Uri smsDraftObserverUri = Uri.parse("content://mms-sms/draft"); + mContext.getContentResolver().registerContentObserver( + smsDraftObserverUri, true, draftContentObserver); + + Uri smsOutboxObserverUri = Uri.parse("content://mms-sms/outbox"); + mContext.getContentResolver().registerContentObserver( + smsOutboxObserverUri, true, outboxContentObserver); + + Uri smsFailedObserverUri = Uri.parse("content://mms-sms/failed"); + mContext.getContentResolver().registerContentObserver( + smsFailedObserverUri, true, failedContentObserver); + + Uri smsQueuedObserverUri = Uri.parse("content://mms-sms/queued"); + mContext.getContentResolver().registerContentObserver( + smsQueuedObserverUri, true, queuedContentObserver); + + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW); + filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); + mContext.registerReceiver(mStorageStatusReceiver, filter); + + updatesRegistered = true; + Log.d(TAG, " ---------------- "); + Log.d(TAG, " REGISTERED MNS UPDATES "); + Log.d(TAG, " ---------------- "); + } + + /** + * Stop listening to changes in cursor + */ + private void deregisterUpdates() { + + if ( updatesRegistered == true ){ + updatesRegistered = false; + Log.d(TAG, "DEREGISTER MNS SMS UPDATES"); + mContext.getContentResolver().unregisterContentObserver( + smsContentObserver); + mContext.getContentResolver().unregisterContentObserver( + inboxContentObserver); + mContext.getContentResolver().unregisterContentObserver( + sentContentObserver); + mContext.getContentResolver().unregisterContentObserver( + draftContentObserver); + mContext.getContentResolver().unregisterContentObserver( + outboxContentObserver); + mContext.getContentResolver().unregisterContentObserver( + failedContentObserver); + mContext.getContentResolver().unregisterContentObserver( + queuedContentObserver); + + //email start + mContext.getContentResolver().unregisterContentObserver( + emailContentObserver); + //email end + + mContext.unregisterReceiver(mStorageStatusReceiver); + + crSmsA.close(); + crSmsB.close(); + currentCRSms = CR_SMS_A; + crSmsInboxA.close(); + crSmsInboxB.close(); + currentCRSmsInbox = CR_SMS_INBOX_A; + crSmsSentA.close(); + crSmsSentB.close(); + currentCRSmsSent = CR_SMS_SENT_A; + crSmsDraftA.close(); + crSmsDraftB.close(); + currentCRSmsDraft = CR_SMS_DRAFT_A; + crSmsOutboxA.close(); + crSmsOutboxB.close(); + currentCRSmsOutbox = CR_SMS_OUTBOX_A; + crSmsFailedA.close(); + crSmsFailedB.close(); + currentCRSmsFailed = CR_SMS_FAILED_A; + crSmsQueuedA.close(); + crSmsQueuedB.close(); + currentCRSmsQueued = CR_SMS_QUEUED_A; + + crMmsA.close(); + crMmsB.close(); + currentCRMms = CR_MMS_A; + crMmsOutboxA.close(); + crMmsOutboxB.close(); + currentCRMmsOutbox = CR_MMS_OUTBOX_A; + crMmsDraftA.close(); + crMmsDraftB.close(); + currentCRMmsDraft = CR_MMS_DRAFT_A; + crMmsInboxA.close(); + crMmsInboxB.close(); + currentCRMmsInbox = CR_MMS_INBOX_A; + crMmsSentA.close(); + crMmsSentB.close(); + currentCRMmsSent = CR_MMS_SENT_A; + + //email start + crEmailA.close(); + crEmailB.close(); + currentCREmail = CR_EMAIL_A; + crEmailOutboxA.close(); + crEmailOutboxB.close(); + currentCREmailOutbox = CR_EMAIL_OUTBOX_A; + crEmailDraftA.close(); + crEmailDraftB.close(); + currentCREmailDraft = CR_EMAIL_DRAFT_A; + crEmailInboxA.close(); + crEmailInboxB.close(); + currentCREmailInbox = CR_EMAIL_INBOX_A; + crEmailSentA.close(); + crEmailSentB.close(); + currentCREmailSent = CR_EMAIL_SENT_A; + //email end + + } + + } + + private SmsContentObserverClass smsContentObserver = new SmsContentObserverClass(); + private InboxContentObserverClass inboxContentObserver = new InboxContentObserverClass(); + private SentContentObserverClass sentContentObserver = new SentContentObserverClass(); + private DraftContentObserverClass draftContentObserver = new DraftContentObserverClass(); + private OutboxContentObserverClass outboxContentObserver = new OutboxContentObserverClass(); + private FailedContentObserverClass failedContentObserver = new FailedContentObserverClass(); + private QueuedContentObserverClass queuedContentObserver = new QueuedContentObserverClass(); + + private EmailContentObserverClass emailContentObserver = new EmailContentObserverClass(); + private EmailInboxContentObserverClass emailInboxContentObserver = new EmailInboxContentObserverClass(); + private EmailSentContentObserverClass emailSentContentObserver = new EmailSentContentObserverClass(); + private EmailDraftContentObserverClass emailDraftContentObserver = new EmailDraftContentObserverClass(); + private EmailOutboxContentObserverClass emailOutboxContentObserver = new EmailOutboxContentObserverClass(); + + private Cursor crSmsA = null; + private Cursor crSmsB = null; + private Cursor crSmsInboxA = null; + private Cursor crSmsInboxB = null; + private Cursor crSmsSentA = null; + private Cursor crSmsSentB = null; + private Cursor crSmsDraftA = null; + private Cursor crSmsDraftB = null; + private Cursor crSmsOutboxA = null; + private Cursor crSmsOutboxB = null; + private Cursor crSmsFailedA = null; + private Cursor crSmsFailedB = null; + private Cursor crSmsQueuedA = null; + private Cursor crSmsQueuedB = null; + + private Cursor crMmsA = null; + private Cursor crMmsB = null; + private Cursor crMmsOutboxA = null; + private Cursor crMmsOutboxB = null; + private Cursor crMmsDraftA = null; + private Cursor crMmsDraftB = null; + private Cursor crMmsInboxA = null; + private Cursor crMmsInboxB = null; + private Cursor crMmsSentA = null; + private Cursor crMmsSentB = null; + + + private Cursor crEmailA = null; + private Cursor crEmailB = null; + private Cursor crEmailOutboxA = null; + private Cursor crEmailOutboxB = null; + private Cursor crEmailDraftA = null; + private Cursor crEmailDraftB = null; + private Cursor crEmailInboxA = null; + private Cursor crEmailInboxB = null; + private Cursor crEmailSentA = null; + private Cursor crEmailSentB = null; + + private final int CR_SMS_A = 1; + private final int CR_SMS_B = 2; + private int currentCRSms = CR_SMS_A; + private final int CR_SMS_INBOX_A = 1; + private final int CR_SMS_INBOX_B = 2; + private int currentCRSmsInbox = CR_SMS_INBOX_A; + private final int CR_SMS_SENT_A = 1; + private final int CR_SMS_SENT_B = 2; + private int currentCRSmsSent = CR_SMS_SENT_A; + private final int CR_SMS_DRAFT_A = 1; + private final int CR_SMS_DRAFT_B = 2; + private int currentCRSmsDraft = CR_SMS_DRAFT_A; + private final int CR_SMS_OUTBOX_A = 1; + private final int CR_SMS_OUTBOX_B = 2; + private int currentCRSmsOutbox = CR_SMS_OUTBOX_A; + private final int CR_SMS_FAILED_A = 1; + private final int CR_SMS_FAILED_B = 2; + private int currentCRSmsFailed = CR_SMS_FAILED_A; + private final int CR_SMS_QUEUED_A = 1; + private final int CR_SMS_QUEUED_B = 2; + private int currentCRSmsQueued = CR_SMS_QUEUED_A; + + private final int CR_MMS_A = 1; + private final int CR_MMS_B = 2; + private int currentCRMms = CR_MMS_A; + private final int CR_MMS_OUTBOX_A = 1; + private final int CR_MMS_OUTBOX_B = 2; + private int currentCRMmsOutbox = CR_MMS_OUTBOX_A; + private final int CR_MMS_DRAFT_A = 1; + private final int CR_MMS_DRAFT_B = 2; + private int currentCRMmsDraft = CR_MMS_DRAFT_A; + private final int CR_MMS_INBOX_A = 1; + private final int CR_MMS_INBOX_B = 2; + private int currentCRMmsInbox = CR_MMS_INBOX_A; + private final int CR_MMS_SENT_A = 1; + private final int CR_MMS_SENT_B = 2; + private int currentCRMmsSent = CR_MMS_SENT_A; + + private final int CR_EMAIL_A = 1; + private final int CR_EMAIL_B = 2; + private int currentCREmail = CR_EMAIL_A; + private final int CR_EMAIL_OUTBOX_A = 1; + private final int CR_EMAIL_OUTBOX_B = 2; + private int currentCREmailOutbox = CR_EMAIL_OUTBOX_A; + private final int CR_EMAIL_DRAFT_A = 1; + private final int CR_EMAIL_DRAFT_B = 2; + private int currentCREmailDraft = CR_EMAIL_DRAFT_A; + private final int CR_EMAIL_INBOX_A = 1; + private final int CR_EMAIL_INBOX_B = 2; + private int currentCREmailInbox = CR_EMAIL_INBOX_A; + private final int CR_EMAIL_SENT_A = 1; + private final int CR_EMAIL_SENT_B = 2; + private int currentCREmailSent = CR_EMAIL_SENT_A; + + + /** + * Get the folder name (MAP representation) based on the + * folder type value in SMS database + */ + private String getMAPFolder(int type) { + String folder = null; + switch (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 based on the type in SMS ContentProvider + */ + private String getFolder(int type) { + String folder = null; + switch (type) { + case 1: + folder = "inbox"; + break; + case 2: + folder = "sent"; + break; + case 3: + folder = "draft"; + break; + case 4: + folder = "outbox"; + break; + case 5: + folder = "failed"; + break; + case 6: + folder = "queued"; + break; + default: + break; + } + return folder; + } + + /** + * Gets the table type (as in Sms Content Provider) for the + * given id + */ + private int getMessageType(String id) { + Cursor cr = mContext.getContentResolver().query( + Uri.parse("content://sms/" + id), + new String[] { "_id", "type" }, null, null, null); + if (cr.moveToFirst()) { + return cr.getInt(cr.getColumnIndex("type")); + } + return -1; + } + /** + * Gets the table type (as in Email Content Provider) for the + * given id + */ + private int getDeletedFlagEmail(String id) { + int deletedFlag =0; + Cursor cr = mContext.getContentResolver().query( + Uri.parse("content://com.android.email.provider/message/" + id), + new String[] { "_id", "mailboxKey" }, null, null, null); + int folderId = -1; + if (cr.moveToFirst()) { + folderId = cr.getInt(cr.getColumnIndex("mailboxKey")); + } + + Cursor cr1 = mContext.getContentResolver().query( + Uri.parse("content://com.android.email.provider/mailbox"), + new String[] { "_id", "displayName" }, "_id ="+ folderId, null, null); + String folderName = null; + if (cr1.moveToFirst()) { + folderName = cr1.getString(cr1.getColumnIndex("displayName")); + } + if(folderName !=null && (folderName.equalsIgnoreCase("Trash") || + folderName.toUpperCase().contains("TRASH"))){ + deletedFlag = 1; + } + return deletedFlag; + } + + /** + * Get the folder name (table name of Sms Content Provider) + */ + private String getContainingFolder(String oldFolder, String id, + String dateTime) { + String newFolder = null; + Cursor cr = mContext.getContentResolver().query( + Uri.parse("content://sms/"), + new String[] { "_id", "date", "type" }, " _id = " + id, null, + null); + if (cr.moveToFirst()) { + return getFolder(cr.getInt(cr.getColumnIndex("type"))); + } + return newFolder; + } + + + private BroadcastReceiver mStorageStatusReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_LOW)) { + Log.d(TAG, " Memory Full "); + sendMnsEvent(MEMORY_FULL, null, null, null, null); + } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_OK)) { + Log.d(TAG, " Memory Available "); + sendMnsEvent(MEMORY_AVAILABLE, null, null, null, null); + } + } + }; + + /** + * This class listens for changes in Email Content Provider's inbox table + * It acts, only when a entry gets removed from the table + */ + private class EmailInboxContentObserverClass extends ContentObserver { + + public EmailInboxContentObserverClass() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + int currentItemCount = 0; + int newItemCount = 0; + + if (currentCREmailInbox == CR_EMAIL_INBOX_A) { + currentItemCount = crEmailInboxA.getCount(); + crEmailInboxB.requery(); + newItemCount = crEmailInboxB.getCount(); + } else { + currentItemCount = crEmailInboxB.getCount(); + crEmailInboxA.requery(); + newItemCount = crEmailInboxA.getCount(); + } + + Log.d(TAG, "EMAIL INBOX current " + currentItemCount + " new " + + newItemCount); + + if (currentItemCount > newItemCount) { + crEmailInboxA.moveToFirst(); + crEmailInboxB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crEmailInboxA, + new String[] { "_id" }, crEmailInboxB, + new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCREmailInbox == CR_EMAIL_INBOX_A) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " EMAIL DELETED FROM INBOX "); + String id = crEmailInboxA.getString(crEmailInboxA + .getColumnIndex("_id")); + Log.d(TAG, " DELETED EMAIL ID " + id); + int deletedFlag = getDeletedFlagEmail(id); //TODO + if(deletedFlag == 1){ + id = Integer.toString(Integer.valueOf(id) + + EMAIL_HDLR_CONSTANT); + + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/INBOX", null, "EMAIL"); + } + else { + Log.d(TAG, "Shouldn't reach here as you cannot " + + "move msg from Inbox to any other folder"); + } + + } else { + // TODO - The current(old) query doesn't have this row; + // implies it was added + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCREmailInbox == CR_EMAIL_INBOX_B) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " EMAIL DELETED FROM INBOX "); + String id = crEmailInboxB.getString(crEmailInboxB + .getColumnIndex("_id")); + Log.d(TAG, " DELETED EMAIL ID " + id); + int deletedFlag = getDeletedFlagEmail(id); //TODO + if(deletedFlag == 1){ + id = Integer.toString(Integer.valueOf(id) + + EMAIL_HDLR_CONSTANT); + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/INBOX", null, "EMAIL"); + } + else { + Log.d(TAG, "Shouldn't reach here as you cannot " + + "move msg from Inbox to any other folder"); + } + } + else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + + if (currentCREmailInbox == CR_EMAIL_INBOX_A) { + currentCREmailInbox = CR_EMAIL_INBOX_B; + } else { + currentCREmailInbox = CR_EMAIL_INBOX_A; + } + } + } + /** + * This class listens for changes in Email Content Provider's Sent table + * It acts, only when a entry gets removed from the table + */ + private class EmailSentContentObserverClass extends ContentObserver { + + public EmailSentContentObserverClass() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + int currentItemCount = 0; + int newItemCount = 0; + + if (currentCREmailSent == CR_EMAIL_SENT_A) { + currentItemCount = crEmailSentA.getCount(); + crEmailSentB.requery(); + newItemCount = crEmailSentB.getCount(); + } else { + currentItemCount = crEmailSentB.getCount(); + crEmailSentA.requery(); + newItemCount = crEmailSentA.getCount(); + } + + Log.d(TAG, "EMAIL SENT current " + currentItemCount + " new " + + newItemCount); + + if (currentItemCount > newItemCount) { + crEmailSentA.moveToFirst(); + crEmailSentB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crEmailSentA, + new String[] { "_id" }, crEmailSentB, + new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCREmailSent == CR_EMAIL_SENT_A) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " EMAIL DELETED FROM SENT "); + String id = crEmailSentA.getString(crEmailSentA + .getColumnIndex("_id")); + Log.d(TAG, " DELETED EMAIL ID " + id); + + int deletedFlag = getDeletedFlagEmail(id); + if(deletedFlag == 1){ + id = Integer.toString(Integer.valueOf(id) + + EMAIL_HDLR_CONSTANT); + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/SENT", null, "EMAIL"); + } else { + Log.d(TAG,"Shouldn't reach here as you cannot " + + "move msg from Sent to any other folder"); + } + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCREmailSent == CR_EMAIL_SENT_B) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " EMAIL DELETED FROM SENT "); + String id = crEmailSentB.getString(crEmailSentB + .getColumnIndex("_id")); + Log.d(TAG, " DELETED EMAIL ID " + id); + int deletedFlag = getDeletedFlagEmail(id); + if(deletedFlag == 1){ + id = Integer.toString(Integer.valueOf(id) + + EMAIL_HDLR_CONSTANT); + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/SENT", null, "EMAIL"); + } else { + Log.d(TAG, "Shouldn't reach here as " + + "you cannot move msg from Sent to " + + "any other folder"); + } + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCREmailSent == CR_EMAIL_SENT_A) { + currentCREmailSent = CR_EMAIL_SENT_B; + } else { + currentCREmailSent = CR_EMAIL_SENT_A; + } + } + } + + /** + * This class listens for changes in Email Content Provider's Draft table + * It acts, only when a entry gets removed from the table + */ + private class EmailDraftContentObserverClass extends ContentObserver { + + public EmailDraftContentObserverClass() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + int currentItemCount = 0; + int newItemCount = 0; + + if (currentCREmailDraft == CR_EMAIL_DRAFT_A) { + currentItemCount = crEmailDraftA.getCount(); + crEmailDraftB.requery(); + newItemCount = crEmailDraftB.getCount(); + } else { + currentItemCount = crEmailDraftB.getCount(); + crEmailDraftA.requery(); + newItemCount = crEmailDraftA.getCount(); + } + + Log.d(TAG, "EMAIL DRAFT current " + currentItemCount + " new " + + newItemCount); + + if (currentItemCount > newItemCount) { + crEmailDraftA.moveToFirst(); + crEmailDraftB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crEmailDraftA, + new String[] { "_id" }, crEmailDraftB, + new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCREmailDraft == CR_EMAIL_DRAFT_A) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " EMAIL DELETED FROM DRAFT "); + String id = crEmailDraftA.getString(crEmailDraftA + .getColumnIndex("_id")); + Log.d(TAG, " DELETED EMAIL ID " + id); + + int deletedFlag = getDeletedFlagEmail(id); + if(deletedFlag == 1){ + id = Integer.toString(Integer.valueOf(id) + + EMAIL_HDLR_CONSTANT); + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/DRAFT", null, "EMAIL"); + } else { + Cursor cr1 = null; + int folderId; + String containingFolder = null; + EmailUtils eu = new EmailUtils(); + Uri uri1 = Uri.parse("content://com.android.email.provider/message"); + String whereClause = " _id = " + id; + cr1 = mContext.getContentResolver().query(uri1, null, whereClause, null, + null); + + if (cr1.getCount() > 0) { + cr1.moveToFirst(); + folderId = cr1.getInt(cr1.getColumnIndex("mailboxKey")); + containingFolder = eu.getContainingFolderEmail(folderId, mContext); + } + + String newFolder = containingFolder; + id = Integer.toString(Integer.valueOf(id) + + EMAIL_HDLR_CONSTANT); + sendMnsEvent(MESSAGE_SHIFT, id, "TELECOM/MSG/" + + newFolder, "TELECOM/MSG/DRAFT", + "EMAIL"); + if ( newFolder.equalsIgnoreCase("sent")) { + sendMnsEvent(SENDING_SUCCESS, id, + "TELECOM/MSG/" + newFolder, + null, "EMAIL"); + } + } + + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCREmailDraft == CR_EMAIL_DRAFT_B) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " EMAIL DELETED FROM DRAFT "); + String id = crEmailDraftB.getString(crEmailDraftB + .getColumnIndex("_id")); + Log.d(TAG, " DELETED EMAIL ID " + id); + int deletedFlag = getDeletedFlagEmail(id); + if(deletedFlag == 1){ + id = Integer.toString(Integer.valueOf(id) + + EMAIL_HDLR_CONSTANT); + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/DRAFT", null, "EMAIL"); + } else { + Cursor cr1 = null; + int folderId; + String containingFolder = null; + EmailUtils eu = new EmailUtils(); + Uri uri1 = Uri.parse("content://com.android.email.provider/message"); + String whereClause = " _id = " + id; + cr1 = mContext.getContentResolver().query(uri1, null, whereClause, null, + null); + + if (cr1.getCount() > 0) { + cr1.moveToFirst(); + folderId = cr1.getInt(cr1.getColumnIndex("mailboxKey")); + containingFolder = eu.getContainingFolderEmail(folderId, mContext); + } + + String newFolder = containingFolder; + id = Integer.toString(Integer.valueOf(id) + + EMAIL_HDLR_CONSTANT); + sendMnsEvent(MESSAGE_SHIFT, id, "TELECOM/MSG/" + + newFolder, "TELECOM/MSG/DRAFT", + "EMAIL"); + if ( newFolder.equalsIgnoreCase("sent")) { + sendMnsEvent(SENDING_SUCCESS, id, + "TELECOM/MSG/" + newFolder, + null, "EMAIL"); + } + } + + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCREmailDraft == CR_EMAIL_DRAFT_A) { + currentCREmailDraft = CR_EMAIL_DRAFT_B; + } else { + currentCREmailDraft = CR_EMAIL_DRAFT_A; + } + } + } + + /** + * This class listens for changes in Sms Content Provider's Outbox table + * It acts only when a entry gets removed from the table + */ + private class EmailOutboxContentObserverClass extends ContentObserver { + + public EmailOutboxContentObserverClass() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + int currentItemCount = 0; + int newItemCount = 0; + + if (currentCREmailOutbox == CR_EMAIL_OUTBOX_A) { + currentItemCount = crEmailOutboxA.getCount(); + crEmailOutboxB.requery(); + newItemCount = crEmailOutboxB.getCount(); + } else { + currentItemCount = crEmailOutboxB.getCount(); + crEmailOutboxA.requery(); + newItemCount = crEmailOutboxA.getCount(); + } + + Log.d(TAG, "EMAIL OUTBOX current " + currentItemCount + " new " + + newItemCount); + + if (currentItemCount > newItemCount) { + crEmailOutboxA.moveToFirst(); + crEmailOutboxB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crEmailOutboxA, + new String[] { "_id" }, crEmailOutboxB, + new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCREmailOutbox == CR_EMAIL_OUTBOX_A) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " EMAIL DELETED FROM OUTBOX "); + String id = crEmailOutboxA.getString(crEmailOutboxA + .getColumnIndex("_id")); + Log.d(TAG, " DELETED EMAIL ID " + id); + int deletedFlag = getDeletedFlagEmail(id); + if(deletedFlag == 1){ + id = Integer.toString(Integer.valueOf(id) + + EMAIL_HDLR_CONSTANT); + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/OUTBOX", null, "EMAIL"); + } else { + Cursor cr1 = null; + int folderId; + String containingFolder = null; + EmailUtils eu = new EmailUtils(); + Uri uri1 = Uri.parse("content://com.android.email.provider/message"); + String whereClause = " _id = " + id; + cr1 = mContext.getContentResolver().query(uri1, null, whereClause, null, + null); + + if (cr1.getCount() > 0) { + cr1.moveToFirst(); + folderId = cr1.getInt(cr1.getColumnIndex("mailboxKey")); + containingFolder = eu.getContainingFolderEmail(folderId, mContext); + } + + String newFolder = containingFolder; + id = Integer.toString(Integer.valueOf(id) + + EMAIL_HDLR_CONSTANT); + + if ((newFolder != null) + && (!newFolder + .equalsIgnoreCase("outbox"))) { + // The message has moved on MAP virtual + // folder representation. + sendMnsEvent(MESSAGE_SHIFT, id, + "TELECOM/MSG/" + newFolder, + "TELECOM/MSG/OUTBOX", "EMAIL"); + if ( newFolder.equalsIgnoreCase("sent")) { + sendMnsEvent(SENDING_SUCCESS, id, + "TELECOM/MSG/" + newFolder, + null, "EMAIL"); + } + } + } + + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCREmailOutbox == CR_EMAIL_OUTBOX_B) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " EMAIL DELETED FROM OUTBOX "); + String id = crEmailOutboxB.getString(crEmailOutboxB + .getColumnIndex("_id")); + Log.d(TAG, " DELETED EMAIL ID " + id); + int deletedFlag = getDeletedFlagEmail(id); + if(deletedFlag == 1){ + id = Integer.toString(Integer.valueOf(id) + + EMAIL_HDLR_CONSTANT); + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/OUTBOX", null, "EMAIL"); + } else { + Cursor cr1 = null; + int folderId; + String containingFolder = null; + EmailUtils eu = new EmailUtils(); + Uri uri1 = Uri.parse("content://com.android.email.provider/message"); + String whereClause = " _id = " + id; + cr1 = mContext.getContentResolver().query(uri1, null, whereClause, null, + null); + + if (cr1.getCount() > 0) { + cr1.moveToFirst(); + folderId = cr1.getInt(cr1.getColumnIndex("mailboxKey")); + containingFolder = eu.getContainingFolderEmail(folderId, mContext); + } + + String newFolder = containingFolder; + id = Integer.toString(Integer.valueOf(id) + + EMAIL_HDLR_CONSTANT); + + if ((newFolder != null) + && (!newFolder + .equalsIgnoreCase("outbox"))) { + // The message has moved on MAP virtual + // folder representation. + sendMnsEvent(MESSAGE_SHIFT, id, + "TELECOM/MSG/" + newFolder, + "TELECOM/MSG/OUTBOX", "EMAIL"); + } + } + + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCREmailOutbox == CR_EMAIL_OUTBOX_A) { + currentCREmailOutbox = CR_EMAIL_OUTBOX_B; + } else { + currentCREmailOutbox = CR_EMAIL_OUTBOX_A; + } + } + } + + + + /** + * This class listens for changes in Sms Content Provider + * It acts, only when a new entry gets added to database + */ + private class SmsContentObserverClass extends ContentObserver { + + public SmsContentObserverClass() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + int currentItemCount = 0; + int newItemCount = 0; + + checkMmsAdded(); + + // Synchronize this? + if (currentCRSms == CR_SMS_A) { + currentItemCount = crSmsA.getCount(); + crSmsB.requery(); + newItemCount = crSmsB.getCount(); + } else { + currentItemCount = crSmsB.getCount(); + crSmsA.requery(); + newItemCount = crSmsA.getCount(); + } + + Log.d(TAG, "SMS current " + currentItemCount + " new " + + newItemCount); + + if (newItemCount > currentItemCount) { + crSmsA.moveToFirst(); + crSmsB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crSmsA, + new String[] { "_id" }, crSmsB, new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCRSms == CR_SMS_A) { + // The new query doesn't have this row; implies it + // was deleted + } else { + // The current(old) query doesn't have this row; + // implies it was added + Log.d(TAG, " SMS ADDED TO INBOX "); + String body1 = crSmsA.getString(crSmsA + .getColumnIndex("body")); + String id1 = crSmsA.getString(crSmsA + .getColumnIndex("_id")); + Log.d(TAG, " ADDED SMS ID " + id1 + " BODY " + + body1); + String folder = getMAPFolder(crSmsA.getInt(crSmsA + .getColumnIndex("type"))); + if ( folder != null ) { + sendMnsEvent(NEW_MESSAGE, id1, "TELECOM/MSG/" + + folder, null, "SMS_GSM"); + } else { + Log.d(TAG, " ADDED TO UNKNOWN FOLDER"); + } + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCRSms == CR_SMS_B) { + // The new query doesn't have this row; implies it + // was deleted + } else { + // The current(old) query doesn't have this row; + // implies it was added + Log.d(TAG, " SMS ADDED "); + String body1 = crSmsB.getString(crSmsB + .getColumnIndex("body")); + String id1 = crSmsB.getString(crSmsB + .getColumnIndex("_id")); + Log.d(TAG, " ADDED SMS ID " + id1 + " BODY " + + body1); + String folder = getMAPFolder(crSmsB.getInt(crSmsB + .getColumnIndex("type"))); + if ( folder != null ) { + sendMnsEvent(NEW_MESSAGE, id1, "TELECOM/MSG/" + + folder, null, "SMS_GSM"); + } else { + Log.d(TAG, " ADDED TO UNKNOWN FOLDER"); + } + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCRSms == CR_SMS_A) { + currentCRSms = CR_SMS_B; + } else { + currentCRSms = CR_SMS_A; + } + } + } + + /** + * This class listens for changes in Email Content Provider + * It acts, only when a new entry gets added to database + */ + private class EmailContentObserverClass extends ContentObserver { + + public EmailContentObserverClass() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + int currentItemCount = 0; + int newItemCount = 0; + EmailUtils eu = new EmailUtils(); + String containingFolder = null; + + // Synchronize this? + if (currentCREmail == CR_EMAIL_A) { + currentItemCount = crEmailA.getCount(); + crEmailB.requery(); + newItemCount = crEmailB.getCount(); + } else { + currentItemCount = crEmailB.getCount(); + crEmailA.requery(); + newItemCount = crEmailA.getCount(); + } + + Log.d(TAG, "Email current " + currentItemCount + " new " + + newItemCount); + + if (newItemCount > currentItemCount) { + crEmailA.moveToFirst(); + crEmailB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crEmailA, + new String[] { "_id" }, crEmailB, new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCREmail == CR_EMAIL_A) { + // The new query doesn't have this row; implies it + // was deleted + } else { + // The current(old) query doesn't have this row; + // implies it was added + Log.d(TAG, " EMAIL ADDED TO INBOX "); + String id1 = crEmailA.getString(crEmailA + .getColumnIndex("_id")); + Log.d(TAG, " ADDED EMAIL ID " + id1); + Cursor cr1 = null; + int folderId; + Uri uri1 = Uri.parse("content://com.android.email.provider/message"); + String whereClause = " _id = " + id1; + cr1 = mContext.getContentResolver().query(uri1, null, whereClause, null, + null); + if ( cr1.moveToFirst()) { + do { + for(int i=0;i<cr1.getColumnCount();i++){ + Log.d(TAG, " Column Name: "+ cr1.getColumnName(i) + " Value: " + cr1.getString(i)); + } + } while ( cr1.moveToNext()); + } + + if (cr1.getCount() > 0) { + cr1.moveToFirst(); + folderId = cr1.getInt(cr1.getColumnIndex("mailboxKey")); + containingFolder = eu.getContainingFolderEmail(folderId, mContext); + } + + if ( containingFolder != null ) { + Log.d(TAG, " containingFolder:: "+containingFolder); + id1 = Integer.toString(Integer.valueOf(id1) + + EMAIL_HDLR_CONSTANT); + sendMnsEvent(NEW_MESSAGE, id1, "TELECOM/MSG/" + + containingFolder, null, "EMAIL"); + } else { + Log.d(TAG, " ADDED TO UNKNOWN FOLDER"); + } + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCREmail == CR_EMAIL_B) { + // The new query doesn't have this row; implies it + // was deleted + } else { + // The current(old) query doesn't have this row; + // implies it was added + Log.d(TAG, " EMAIL ADDED "); + String id1 = crEmailB.getString(crEmailB + .getColumnIndex("_id")); + Log.d(TAG, " ADDED EMAIL ID " + id1); + Cursor cr1 = null; + int folderId; + Uri uri1 = Uri.parse("content://com.android.email.provider/message"); + String whereClause = " _id = " + id1; + cr1 = mContext.getContentResolver().query(uri1, null, whereClause, null, + null); + + if ( cr1.moveToFirst()) { + do { + for(int i=0;i<cr1.getColumnCount();i++){ + Log.d(TAG, " Column Name: "+ cr1.getColumnName(i) + + " Value: " + cr1.getString(i)); + } + } while ( cr1.moveToNext()); + } + + if (cr1.getCount() > 0) { + cr1.moveToFirst(); + folderId = cr1.getInt(cr1.getColumnIndex("mailboxKey")); + containingFolder = eu.getContainingFolderEmail(folderId, mContext); + } + if ( containingFolder != null ) { + Log.d(TAG, " containingFolder:: "+containingFolder); + id1 = Integer.toString(Integer.valueOf(id1) + + EMAIL_HDLR_CONSTANT); + sendMnsEvent(NEW_MESSAGE, id1, "TELECOM/MSG/" + + containingFolder, null, "EMAIL"); + } else { + Log.d(TAG, " ADDED TO UNKNOWN FOLDER"); + } + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCREmail == CR_EMAIL_A) { + currentCREmail = CR_EMAIL_B; + } else { + currentCREmail = CR_EMAIL_A; + } + } + } + + + /** + * This class listens for changes in Sms Content Provider's inbox table + * It acts, only when a entry gets removed from the table + */ + private class InboxContentObserverClass extends ContentObserver { + + public InboxContentObserverClass() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + int currentItemCount = 0; + int newItemCount = 0; + + checkMmsInbox(); + + if (currentCRSmsInbox == CR_SMS_INBOX_A) { + currentItemCount = crSmsInboxA.getCount(); + crSmsInboxB.requery(); + newItemCount = crSmsInboxB.getCount(); + } else { + currentItemCount = crSmsInboxB.getCount(); + crSmsInboxA.requery(); + newItemCount = crSmsInboxA.getCount(); + } + + Log.d(TAG, "SMS INBOX current " + currentItemCount + " new " + + newItemCount); + + if (currentItemCount > newItemCount) { + crSmsInboxA.moveToFirst(); + crSmsInboxB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crSmsInboxA, + new String[] { "_id" }, crSmsInboxB, + new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCRSmsInbox == CR_SMS_INBOX_A) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " SMS DELETED FROM INBOX "); + String body = crSmsInboxA.getString(crSmsInboxA + .getColumnIndex("body")); + String id = crSmsInboxA.getString(crSmsInboxA + .getColumnIndex("_id")); + Log.d(TAG, " DELETED SMS ID " + id + " BODY " + + body); + int msgType = getMessageType(id); + if (msgType == -1) { + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/INBOX", null, "SMS_GSM"); + } else { + Log.d(TAG, "Shouldn't reach here as you cannot " + + "move msg from Inbox to any other folder"); + } + } else { + // TODO - The current(old) query doesn't have this row; + // implies it was added + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCRSmsInbox == CR_SMS_INBOX_B) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " SMS DELETED FROM INBOX "); + String body = crSmsInboxB.getString(crSmsInboxB + .getColumnIndex("body")); + String id = crSmsInboxB.getString(crSmsInboxB + .getColumnIndex("_id")); + Log.d(TAG, " DELETED SMS ID " + id + " BODY " + + body); + int msgType = getMessageType(id); + if (msgType == -1) { + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/INBOX", null, "SMS_GSM"); + } else { + Log.d(TAG,"Shouldn't reach here as you cannot " + + "move msg from Inbox to any other folder"); + } + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCRSmsInbox == CR_SMS_INBOX_A) { + currentCRSmsInbox = CR_SMS_INBOX_B; + } else { + currentCRSmsInbox = CR_SMS_INBOX_A; + } + } + } + + + + /** + * This class listens for changes in Sms Content Provider's Sent table + * It acts, only when a entry gets removed from the table + */ + private class SentContentObserverClass extends ContentObserver { + + public SentContentObserverClass() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + int currentItemCount = 0; + int newItemCount = 0; + + checkMmsSent(); + + if (currentCRSmsSent == CR_SMS_SENT_A) { + currentItemCount = crSmsSentA.getCount(); + crSmsSentB.requery(); + newItemCount = crSmsSentB.getCount(); + } else { + currentItemCount = crSmsSentB.getCount(); + crSmsSentA.requery(); + newItemCount = crSmsSentA.getCount(); + } + + Log.d(TAG, "SMS SENT current " + currentItemCount + " new " + + newItemCount); + + if (currentItemCount > newItemCount) { + crSmsSentA.moveToFirst(); + crSmsSentB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crSmsSentA, + new String[] { "_id" }, crSmsSentB, + new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + // while((CursorJointer.Result joinerResult = joiner.next()) != + // null) + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCRSmsSent == CR_SMS_SENT_A) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " SMS DELETED FROM SENT "); + String body = crSmsSentA.getString(crSmsSentA + .getColumnIndex("body")); + String id = crSmsSentA.getString(crSmsSentA + .getColumnIndex("_id")); + Log.d(TAG, " DELETED SMS ID " + id + " BODY " + + body); + + int msgType = getMessageType(id); + if (msgType == -1) { + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/SENT", null, "SMS_GSM"); + } else { + Log.d(TAG,"Shouldn't reach here as you cannot " + + "move msg from Sent to any other folder"); + } + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCRSmsSent == CR_SMS_SENT_B) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " SMS DELETED FROM SENT "); + String body = crSmsSentB.getString(crSmsSentB + .getColumnIndex("body")); + String id = crSmsSentB.getString(crSmsSentB + .getColumnIndex("_id")); + Log.d(TAG, " DELETED SMS ID " + id + " BODY " + + body); + int msgType = getMessageType(id); + if (msgType == -1) { + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/SENT", null, "SMS_GSM"); + } else { + Log.d(TAG, "Shouldn't reach here as " + + "you cannot move msg from Sent to " + + "any other folder"); + } + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCRSmsSent == CR_SMS_SENT_A) { + currentCRSmsSent = CR_SMS_SENT_B; + } else { + currentCRSmsSent = CR_SMS_SENT_A; + } + } + } + + /** + * This class listens for changes in Sms Content Provider's Draft table + * It acts, only when a entry gets removed from the table + */ + private class DraftContentObserverClass extends ContentObserver { + + public DraftContentObserverClass() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + int currentItemCount = 0; + int newItemCount = 0; + + checkMmsDrafts(); + + if (currentCRSmsDraft == CR_SMS_DRAFT_A) { + currentItemCount = crSmsDraftA.getCount(); + crSmsDraftB.requery(); + newItemCount = crSmsDraftB.getCount(); + } else { + currentItemCount = crSmsDraftB.getCount(); + crSmsDraftA.requery(); + newItemCount = crSmsDraftA.getCount(); + } + + Log.d(TAG, "SMS DRAFT current " + currentItemCount + " new " + + newItemCount); + + if (currentItemCount > newItemCount) { + crSmsDraftA.moveToFirst(); + crSmsDraftB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crSmsDraftA, + new String[] { "_id" }, crSmsDraftB, + new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCRSmsDraft == CR_SMS_DRAFT_A) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " SMS DELETED FROM DRAFT "); + String body = crSmsDraftA.getString(crSmsDraftA + .getColumnIndex("body")); + String id = crSmsDraftA.getString(crSmsDraftA + .getColumnIndex("_id")); + Log.d(TAG, " DELETED SMS ID " + id + " BODY " + + body); + + int msgType = getMessageType(id); + if (msgType == -1) { + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/DRAFT", null, "SMS_GSM"); + } else { + String newFolder = getMAPFolder(msgType); + sendMnsEvent(MESSAGE_SHIFT, id, "TELECOM/MSG/" + + newFolder, "TELECOM/MSG/DRAFT", + "SMS_GSM"); + if ( newFolder.equalsIgnoreCase("sent")) { + sendMnsEvent(SENDING_SUCCESS, id, + "TELECOM/MSG/" + newFolder, + null, "SMS_GSM"); + } + } + + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCRSmsDraft == CR_SMS_DRAFT_B) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " SMS DELETED FROM DRAFT "); + String body = crSmsDraftB.getString(crSmsDraftB + .getColumnIndex("body")); + String id = crSmsDraftB.getString(crSmsDraftB + .getColumnIndex("_id")); + Log.d(TAG, " DELETED SMS ID " + id + " BODY " + + body); + int msgType = getMessageType(id); + if (msgType == -1) { + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/DRAFT", null, "SMS_GSM"); + } else { + String newFolder = getMAPFolder(msgType); + sendMnsEvent(MESSAGE_SHIFT, id, "TELECOM/MSG/" + + newFolder, "TELECOM/MSG/DRAFT", + "SMS_GSM"); + if ( newFolder.equalsIgnoreCase("sent")) { + sendMnsEvent(SENDING_SUCCESS, id, + "TELECOM/MSG/" + newFolder, + null, "SMS_GSM"); + } + } + + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCRSmsDraft == CR_SMS_DRAFT_A) { + currentCRSmsDraft = CR_SMS_DRAFT_B; + } else { + currentCRSmsDraft = CR_SMS_DRAFT_A; + } + } + } + + /** + * This class listens for changes in Sms Content Provider's Outbox table + * It acts only when a entry gets removed from the table + */ + private class OutboxContentObserverClass extends ContentObserver { + + public OutboxContentObserverClass() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + int currentItemCount = 0; + int newItemCount = 0; + + // Check MMS Outbox for changes + + checkMmsOutbox(); + + // Check SMS Outbox for changes + + if (currentCRSmsOutbox == CR_SMS_OUTBOX_A) { + currentItemCount = crSmsOutboxA.getCount(); + crSmsOutboxB.requery(); + newItemCount = crSmsOutboxB.getCount(); + } else { + currentItemCount = crSmsOutboxB.getCount(); + crSmsOutboxA.requery(); + newItemCount = crSmsOutboxA.getCount(); + } + + Log.d(TAG, "SMS OUTBOX current " + currentItemCount + " new " + + newItemCount); + + if (currentItemCount > newItemCount) { + crSmsOutboxA.moveToFirst(); + crSmsOutboxB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crSmsOutboxA, + new String[] { "_id" }, crSmsOutboxB, + new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCRSmsOutbox == CR_SMS_OUTBOX_A) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " SMS DELETED FROM OUTBOX "); + String body = crSmsOutboxA.getString(crSmsOutboxA + .getColumnIndex("body")); + String id = crSmsOutboxA.getString(crSmsOutboxA + .getColumnIndex("_id")); + Log.d(TAG, " DELETED SMS ID " + id + " BODY " + + body); + int msgType = getMessageType(id); + if (msgType == -1) { + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/OUTBOX", null, "SMS_GSM"); + } else { + String newFolder = getMAPFolder(msgType); + if ((newFolder != null) + && (!newFolder + .equalsIgnoreCase("outbox"))) { + // The message has moved on MAP virtual + // folder representation. + sendMnsEvent(MESSAGE_SHIFT, id, + "TELECOM/MSG/" + newFolder, + "TELECOM/MSG/OUTBOX", "SMS_GSM"); + if ( newFolder.equalsIgnoreCase("sent")) { + sendMnsEvent(SENDING_SUCCESS, id, + "TELECOM/MSG/" + newFolder, + null, "SMS_GSM"); + } + } + if ( (msgType == MSG_CP_QUEUED_TYPE) || + (msgType == MSG_CP_FAILED_TYPE)) { + // Message moved from outbox to queue or + // failed folder + sendMnsEvent(SENDING_FAILURE, id, + "TELECOM/MSG/OUTBOX", null, "SMS_GSM"); + } + } + + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCRSmsOutbox == CR_SMS_OUTBOX_B) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " SMS DELETED FROM OUTBOX "); + String body = crSmsOutboxB.getString(crSmsOutboxB + .getColumnIndex("body")); + String id = crSmsOutboxB.getString(crSmsOutboxB + .getColumnIndex("_id")); + Log.d(TAG, " DELETED SMS ID " + id + " BODY " + + body); + int msgType = getMessageType(id); + if (msgType == -1) { + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/OUTBOX", null, "SMS_GSM"); + } else { + String newFolder = getMAPFolder(msgType); + if ((newFolder != null) + && (!newFolder + .equalsIgnoreCase("outbox"))) { + // The message has moved on MAP virtual + // folder representation. + sendMnsEvent(MESSAGE_SHIFT, id, + "TELECOM/MSG/" + newFolder, + "TELECOM/MSG/OUTBOX", "SMS_GSM"); + } + } + + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCRSmsOutbox == CR_SMS_OUTBOX_A) { + currentCRSmsOutbox = CR_SMS_OUTBOX_B; + } else { + currentCRSmsOutbox = CR_SMS_OUTBOX_A; + } + } + } + + /** + * This class listens for changes in Sms Content Provider's Failed table + * It acts only when a entry gets removed from the table + */ + private class FailedContentObserverClass extends ContentObserver { + + public FailedContentObserverClass() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + int currentItemCount = 0; + int newItemCount = 0; + + // Mms doesn't have Failed type + + if (currentCRSmsFailed == CR_SMS_FAILED_A) { + currentItemCount = crSmsFailedA.getCount(); + crSmsFailedB.requery(); + newItemCount = crSmsFailedB.getCount(); + } else { + currentItemCount = crSmsFailedB.getCount(); + crSmsFailedA.requery(); + newItemCount = crSmsFailedA.getCount(); + } + + Log.d(TAG, "SMS FAILED current " + currentItemCount + " new " + + newItemCount); + + if (currentItemCount > newItemCount) { + crSmsFailedA.moveToFirst(); + crSmsFailedB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crSmsFailedA, + new String[] { "_id" }, crSmsFailedB, + new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCRSmsFailed == CR_SMS_FAILED_A) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " SMS DELETED FROM FAILED "); + String body = crSmsFailedA.getString(crSmsFailedA + .getColumnIndex("body")); + String id = crSmsFailedA.getString(crSmsFailedA + .getColumnIndex("_id")); + Log.d(TAG, " DELETED SMS ID " + id + " BODY " + + body); + int msgType = getMessageType(id); + if (msgType == -1) { + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/OUTBOX", null, "SMS_GSM"); + } else { + String newFolder = getMAPFolder(msgType); + if ((newFolder != null) + && (!newFolder + .equalsIgnoreCase("outbox"))) { + // The message has moved on MAP virtual + // folder representation. + sendMnsEvent(MESSAGE_SHIFT, id, + "TELECOM/MSG/" + newFolder, + "TELECOM/MSG/OUTBOX", "SMS_GSM"); + if ( newFolder.equalsIgnoreCase("sent")) { + sendMnsEvent(SENDING_SUCCESS, id, + "TELECOM/MSG/" + newFolder, + null, "SMS_GSM"); + } + } + } + + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCRSmsFailed == CR_SMS_FAILED_B) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " SMS DELETED FROM FAILED "); + String body = crSmsFailedB.getString(crSmsFailedB + .getColumnIndex("body")); + String id = crSmsFailedB.getString(crSmsFailedB + .getColumnIndex("_id")); + Log.d(TAG, " DELETED SMS ID " + id + " BODY " + + body); + int msgType = getMessageType(id); + if (msgType == -1) { + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/OUTBOX", null, "SMS_GSM"); + } else { + String newFolder = getMAPFolder(msgType); + if ((newFolder != null) + && (!newFolder + .equalsIgnoreCase("outbox"))) { + // The message has moved on MAP virtual + // folder representation. + sendMnsEvent(MESSAGE_SHIFT, id, + "TELECOM/MSG/" + newFolder, + "TELECOM/MSG/OUTBOX", "SMS_GSM"); + if ( newFolder.equalsIgnoreCase("sent")) { + sendMnsEvent(SENDING_SUCCESS, id, + "TELECOM/MSG/" + newFolder, + null, "SMS_GSM"); + } + } + } + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCRSmsFailed == CR_SMS_FAILED_A) { + currentCRSmsFailed = CR_SMS_FAILED_B; + } else { + currentCRSmsFailed = CR_SMS_FAILED_A; + } + } + } + + /** + * This class listens for changes in Sms Content Provider's Queued table + * It acts only when a entry gets removed from the table + */ + private class QueuedContentObserverClass extends ContentObserver { + + public QueuedContentObserverClass() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + int currentItemCount = 0; + int newItemCount = 0; + + // Mms doesn't have Queued type + + if (currentCRSmsQueued == CR_SMS_QUEUED_A) { + currentItemCount = crSmsQueuedA.getCount(); + crSmsQueuedB.requery(); + newItemCount = crSmsQueuedB.getCount(); + } else { + currentItemCount = crSmsQueuedB.getCount(); + crSmsQueuedA.requery(); + newItemCount = crSmsQueuedA.getCount(); + } + + Log.d(TAG, "SMS QUEUED current " + currentItemCount + " new " + + newItemCount); + + if (currentItemCount > newItemCount) { + crSmsQueuedA.moveToFirst(); + crSmsQueuedB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crSmsQueuedA, + new String[] { "_id" }, crSmsQueuedB, + new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCRSmsQueued == CR_SMS_QUEUED_A) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " SMS DELETED FROM QUEUED "); + String body = crSmsQueuedA.getString(crSmsQueuedA + .getColumnIndex("body")); + String id = crSmsQueuedA.getString(crSmsQueuedA + .getColumnIndex("_id")); + Log.d(TAG, " DELETED SMS ID " + id + " BODY " + + body); + int msgType = getMessageType(id); + if (msgType == -1) { + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/OUTBOX", null, "SMS_GSM"); + } else { + String newFolder = getMAPFolder(msgType); + if ((newFolder != null) + && (!newFolder + .equalsIgnoreCase("outbox"))) { + // The message has moved on MAP virtual + // folder representation. + sendMnsEvent(MESSAGE_SHIFT, id, + "TELECOM/MSG/" + newFolder, + "TELECOM/MSG/OUTBOX", "SMS_GSM"); + if ( newFolder.equalsIgnoreCase("sent")) { + sendMnsEvent(SENDING_SUCCESS, id, + "TELECOM/MSG/" + newFolder, + null, "SMS_GSM"); + } + } + } + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCRSmsQueued == CR_SMS_QUEUED_B) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " SMS DELETED FROM QUEUED "); + String body = crSmsQueuedB.getString(crSmsQueuedB + .getColumnIndex("body")); + String id = crSmsQueuedB.getString(crSmsQueuedB + .getColumnIndex("_id")); + Log.d(TAG, " DELETED SMS ID " + id + " BODY " + + body); + int msgType = getMessageType(id); + if (msgType == -1) { + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/OUTBOX", null, "SMS_GSM"); + } else { + String newFolder = getMAPFolder(msgType); + if ((newFolder != null) + && (!newFolder + .equalsIgnoreCase("outbox"))) { + // The message has moved on MAP virtual + // folder representation. + sendMnsEvent(MESSAGE_SHIFT, id, + "TELECOM/MSG/" + newFolder, + "TELECOM/MSG/OUTBOX", "SMS_GSM"); + if ( newFolder.equalsIgnoreCase("sent")) { + sendMnsEvent(SENDING_SUCCESS, id, + "TELECOM/MSG/" + newFolder, + null, "SMS_GSM"); + } + } + } + + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCRSmsQueued == CR_SMS_QUEUED_A) { + currentCRSmsQueued = CR_SMS_QUEUED_B; + } else { + currentCRSmsQueued = CR_SMS_QUEUED_A; + } + } + } + + /** + * Start MNS connection + */ + public void start(BluetoothDevice mRemoteDevice) { + /* check Bluetooth enable status */ + /* + * normally it's impossible to reach here if BT is disabled. Just check + * for safety + */ + if (!mAdapter.isEnabled()) { + Log.e(TAG, "Can't send event when Bluetooth is disabled "); + return; + } + + mDestination = mRemoteDevice; + int channel = -1; + // TODO Fix this below + + if (channel != -1) { + if (D) Log.d(TAG, "Get MNS channel " + channel + " from cache for " + + mDestination); + mTimestamp = System.currentTimeMillis(); + mSessionHandler.obtainMessage(SDP_RESULT, channel, -1, mDestination) + .sendToTarget(); + } else { + sendMnsSdp(); + } + } + + /** + * Stop the transfer + */ + public void stop() { + if (V) + Log.v(TAG, "stop"); + if (mConnectThread != null) { + try { + mConnectThread.interrupt(); + if (V) Log.v(TAG, "waiting for connect thread to terminate"); + mConnectThread.join(); + } catch (InterruptedException e) { + if (V) Log.v(TAG, + "Interrupted waiting for connect thread to join"); + } + mConnectThread = null; + } + if (mSession != null) { + if (V) + Log.v(TAG, "Stop mSession"); + mSession.disconnect(); + mSession = null; + } + // TODO Do this somewhere else - Should the handler thread be gracefully closed. + } + + /** + * Connect the MNS Obex client to remote server + */ + private void startObexSession() { + + if (V) + Log.v(TAG, "Create Client session with transport " + + mTransport.toString()); + mSession = new BluetoothMnsObexSession(mContext, mTransport); + mSession.connect(); + } + + /** + * Check if local database contains remote device's info + * Else start sdp query + */ + private void sendMnsSdp() { + if (V) + Log.v(TAG, "Do Opush SDP request for address " + mDestination); + + mTimestamp = System.currentTimeMillis(); + + int channel = -1; + + Method m; + try { + m = mDestination.getClass().getMethod("getServiceChannel", + new Class[] { ParcelUuid.class }); + channel = (Integer) m.invoke(mDestination, BluetoothUuid_ObexMns); + } catch (SecurityException e2) { + // TODO Auto-generated catch block + e2.printStackTrace(); + } catch (NoSuchMethodException e2) { + // TODO Auto-generated catch block + e2.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + // TODO: channel = mDestination.getServiceChannel(BluetoothUuid_ObexMns); + + if (channel != -1) { + if (D) + Log.d(TAG, "Get MNS channel " + channel + " from SDP for " + + mDestination); + + mSessionHandler + .obtainMessage(SDP_RESULT, channel, -1, mDestination) + .sendToTarget(); + return; + + } else { + + boolean result = false; + if (V) + Log.v(TAG, "Remote Service channel not in cache"); + + Method m2; + try { + m2 = mDestination.getClass().getMethod("fetchUuidsWithSdp", + new Class[] {}); + result = (Boolean) m2.invoke(mDestination); + + } catch (SecurityException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } catch (NoSuchMethodException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + if (result == false) { + Log.e(TAG, "Start SDP query failed"); + } else { + // we expect framework send us Intent ACTION_UUID. otherwise we + // will fail + if (V) + Log.v(TAG, "Start new SDP, wait for result"); + IntentFilter intentFilter = new IntentFilter( + "android.bleutooth.device.action.UUID"); + mContext.registerReceiver(mReceiver, intentFilter); + return; + } + } + Message msg = mSessionHandler.obtainMessage(SDP_RESULT, channel, -1, + mDestination); + mSessionHandler.sendMessageDelayed(msg, 2000); + } + + /** + * Receives the response of SDP query + */ + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + + Log.d(TAG, " MNS BROADCAST RECV intent: " + intent.getAction()); + + if (intent.getAction().equals( + "android.bleutooth.device.action.UUID")) { + BluetoothDevice device = intent + .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (V) + Log.v(TAG, "ACTION_UUID for device " + device); + if (device.equals(mDestination)) { + int channel = -1; + Parcelable[] uuid = intent + .getParcelableArrayExtra("android.bluetooth.device.extra.UUID"); + if (uuid != null) { + ParcelUuid[] uuids = new ParcelUuid[uuid.length]; + for (int i = 0; i < uuid.length; i++) { + uuids[i] = (ParcelUuid) uuid[i]; + } + + boolean result = false; + + // TODO Fix this error + result = true; + + try { + Class c = Class + .forName("android.bluetooth.BluetoothUuid"); + Method m = c.getMethod("isUuidPresent", + new Class[] { ParcelUuid[].class, + ParcelUuid.class }); + + Boolean bool = false; + bool = (Boolean) m.invoke(c, uuids, + BluetoothUuid_ObexMns); + result = bool.booleanValue(); + + } catch (ClassNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + if (result) { + // TODO: Check if UUID IS PRESENT + if (V) + Log.v(TAG, "SDP get MNS result for device " + + device); + + // TODO: Get channel from mDestination + // TODO: .getServiceChannel(BluetoothUuid_ObexMns); + Method m1; + try { + + m1 = device.getClass().getMethod( + "getServiceChannel", + new Class[] { ParcelUuid.class }); + Integer chan = (Integer) m1.invoke(device, + BluetoothUuid_ObexMns); + + channel = chan.intValue(); + Log.d(TAG, " MNS SERVER Channel no " + channel); + if (channel == -1) { + channel = 2; + Log.d(TAG, " MNS SERVER USE TEMP CHANNEL " + + channel); + } + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + mSessionHandler.obtainMessage(SDP_RESULT, channel, -1, + mDestination).sendToTarget(); + } + } + } + }; + + private SocketConnectThread mConnectThread; + + /** + * This thread is used to establish rfcomm connection to + * remote device + */ + private class SocketConnectThread extends Thread { + private final String host; + + private final BluetoothDevice device; + + private final int channel; + + private boolean isConnected; + + private long timestamp; + + private BluetoothSocket btSocket = null; + + /* create a Rfcomm Socket */ + public SocketConnectThread(BluetoothDevice device, int channel) { + super("Socket Connect Thread"); + this.device = device; + this.host = null; + this.channel = channel; + isConnected = false; + } + + public void interrupt() { + } + + @Override + public void run() { + + timestamp = System.currentTimeMillis(); + + /* Use BluetoothSocket to connect */ + try { + try { + Method m = device.getClass().getMethod( + "createInsecureRfcommSocket", + new Class[] { int.class }); + btSocket = (BluetoothSocket) m.invoke(device, channel); + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } catch (Exception e1) { + // TODO Auto-generated catch block + Log.e(TAG, "Rfcomm socket create error"); + markConnectionFailed(btSocket); + return; + } + + try { + btSocket.connect(); + if (V) Log.v(TAG, "Rfcomm socket connection attempt took " + + (System.currentTimeMillis() - timestamp) + " ms"); + BluetoothMnsRfcommTransport transport; + transport = new BluetoothMnsRfcommTransport(btSocket); + + BluetoothMnsPreference.getInstance(mContext).setChannel(device, + MNS_UUID16, channel); + BluetoothMnsPreference.getInstance(mContext).setName(device, + device.getName()); + + if (V) Log.v(TAG, "Send transport message " + + transport.toString()); + + mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport) + .sendToTarget(); + } catch (IOException e) { + Log.e(TAG, "Rfcomm socket connect exception "); + BluetoothMnsPreference.getInstance(mContext).removeChannel( + device, MNS_UUID16); + markConnectionFailed(btSocket); + return; + } + } + + /** + * RFCOMM connection failed + */ + private void markConnectionFailed(Socket s) { + try { + s.close(); + } catch (IOException e) { + Log.e(TAG, "TCP socket close error"); + } + mSessionHandler.obtainMessage(RFCOMM_ERROR).sendToTarget(); + } + + /** + * RFCOMM connection failed + */ + private void markConnectionFailed(BluetoothSocket s) { + try { + s.close(); + } catch (IOException e) { + if (V) Log.e(TAG, "Error when close socket"); + } + mSessionHandler.obtainMessage(RFCOMM_ERROR).sendToTarget(); + return; + } + } + + /** + * Check for change in MMS outbox and send a notification if there is a + * change + */ + private void checkMmsOutbox() { + + int currentItemCount = 0; + int newItemCount = 0; + + if (currentCRMmsOutbox == CR_MMS_OUTBOX_A) { + currentItemCount = crMmsOutboxA.getCount(); + crMmsOutboxB.requery(); + newItemCount = crMmsOutboxB.getCount(); + } else { + currentItemCount = crMmsOutboxB.getCount(); + crMmsOutboxA.requery(); + newItemCount = crMmsOutboxA.getCount(); + } + + Log.d(TAG, "MMS OUTBOX current " + currentItemCount + " new " + + newItemCount); + + if (currentItemCount > newItemCount) { + crMmsOutboxA.moveToFirst(); + crMmsOutboxB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crMmsOutboxA, + new String[] { "_id" }, crMmsOutboxB, + new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCRMmsOutbox == CR_MMS_OUTBOX_A) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " MMS DELETED FROM OUTBOX "); + String id = crMmsOutboxA.getString(crMmsOutboxA + .getColumnIndex("_id")); + int msgType = getMmsContainingFolder(Integer + .parseInt(id)); + if (msgType == -1) { + // Convert to virtual handle for MMS + id = Integer.toString(Integer.valueOf(id) + + MMS_HDLR_CONSTANT); + + Log.d(TAG, " DELETED MMS ID " + id); + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/OUTBOX", null, "MMS"); + } else { + String newFolder = getMAPFolder(msgType); + // Convert to virtual handle for MMS + id = Integer.toString(Integer.valueOf(id) + + MMS_HDLR_CONSTANT); + + Log.d(TAG, " MESSAGE_SHIFT MMS ID " + id); + if ((newFolder != null) + && (!newFolder.equalsIgnoreCase("outbox"))) { + // The message has moved on MAP virtual + // folder representation. + sendMnsEvent(MESSAGE_SHIFT, id, "TELECOM/MSG/" + + newFolder, "TELECOM/MSG/OUTBOX", + "MMS"); + if ( newFolder.equalsIgnoreCase("sent")) { + sendMnsEvent(SENDING_SUCCESS, id, + "TELECOM/MSG/" + newFolder, + null, "MMS"); + } + } + /* Mms doesn't have failed or queued type + * Cannot send SENDING_FAILURE as there + * is no indication if Sending failed + */ + } + + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCRMmsOutbox == CR_MMS_OUTBOX_B) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " MMS DELETED FROM OUTBOX "); + String id = crMmsOutboxB.getString(crMmsOutboxB + .getColumnIndex("_id")); + int msgType = getMmsContainingFolder(Integer + .parseInt(id)); + if (msgType == -1) { + // Convert to virtual handle for MMS + id = Integer.toString(Integer.valueOf(id) + + MMS_HDLR_CONSTANT); + Log.d(TAG, " DELETED MMS ID " + id); + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/OUTBOX", null, "MMS"); + } else { + // Convert to virtual handle for MMS + id = Integer.toString(Integer.valueOf(id) + + MMS_HDLR_CONSTANT); + Log.d(TAG, " DELETED MMS ID " + id); + String newFolder = getMAPFolder(msgType); + if ((newFolder != null) + && (!newFolder.equalsIgnoreCase("outbox"))) { + // The message has moved on MAP virtual + // folder representation. + sendMnsEvent(MESSAGE_SHIFT, id, "TELECOM/MSG/" + + newFolder, "TELECOM/MSG/OUTBOX", + "MMS"); + if ( newFolder.equalsIgnoreCase("sent")) { + sendMnsEvent(SENDING_SUCCESS, id, + "TELECOM/MSG/" + newFolder, + null, "MMS"); + } + } + /* Mms doesn't have failed or queued type + * Cannot send SENDING_FAILURE as there + * is no indication if Sending failed + */ + } + + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCRMmsOutbox == CR_MMS_OUTBOX_A) { + currentCRMmsOutbox = CR_MMS_OUTBOX_B; + } else { + currentCRMmsOutbox = CR_MMS_OUTBOX_A; + } + } + + /** + * Check for change in MMS Drafts folder and send a notification if there is + * a change + */ + private void checkMmsDrafts() { + + int currentItemCount = 0; + int newItemCount = 0; + + if (currentCRMmsDraft == CR_MMS_OUTBOX_A) { + currentItemCount = crMmsDraftA.getCount(); + crMmsDraftA.requery(); + newItemCount = crMmsDraftB.getCount(); + } else { + currentItemCount = crMmsDraftB.getCount(); + crMmsDraftA.requery(); + newItemCount = crMmsDraftA.getCount(); + } + + if (newItemCount != 0) { + Log.d(TAG, "MMS DRAFT breakpoint placeholder"); + } + + Log.d(TAG, "MMS DRAFT current " + currentItemCount + " new " + + newItemCount); + + if (currentItemCount > newItemCount) { + crMmsDraftA.moveToFirst(); + crMmsDraftB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crMmsDraftA, + new String[] { "_id" }, crMmsDraftB, new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCRMmsDraft == CR_MMS_DRAFT_A) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " MMS DELETED FROM DRAFT "); + String id = crMmsDraftA.getString(crMmsDraftA + .getColumnIndex("_id")); + int msgType = getMmsContainingFolder(Integer + .parseInt(id)); + if (msgType == -1) { + // Convert to virtual handle for MMS + id = Integer.toString(Integer.valueOf(id) + + MMS_HDLR_CONSTANT); + Log.d(TAG, " DELETED MMS ID " + id); + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/DRAFT", null, "MMS"); + } else { + // Convert to virtual handle for MMS + id = Integer.toString(Integer.valueOf(id) + + MMS_HDLR_CONSTANT); + Log.d(TAG, " DELETED MMS ID " + id); + String newFolder = getMAPFolder(msgType); + if ((newFolder != null) + && (!newFolder.equalsIgnoreCase("draft"))) { + // The message has moved on MAP virtual + // folder representation. + sendMnsEvent(MESSAGE_SHIFT, id, "TELECOM/MSG/" + + newFolder, "TELECOM/MSG/DRAFT", "MMS"); + if ( newFolder.equalsIgnoreCase("sent")) { + sendMnsEvent(SENDING_SUCCESS, id, + "TELECOM/MSG/" + newFolder, + null, "MMS"); + } + } + } + + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCRMmsDraft == CR_MMS_DRAFT_B) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " MMS DELETED FROM DRAFT "); + String id = crMmsDraftB.getString(crMmsDraftB + .getColumnIndex("_id")); + int msgType = getMmsContainingFolder(Integer + .parseInt(id)); + if (msgType == -1) { + // Convert to virtual handle for MMS + id = Integer.toString(Integer.valueOf(id) + + MMS_HDLR_CONSTANT); + Log.d(TAG, " DELETED MMS ID " + id); + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/DRAFT", null, "MMS"); + } else { + // Convert to virtual handle for MMS + id = Integer.toString(Integer.valueOf(id) + + MMS_HDLR_CONSTANT); + Log.d(TAG, " DELETED MMS ID " + id); + String newFolder = getMAPFolder(msgType); + if ((newFolder != null) + && (!newFolder.equalsIgnoreCase("draft"))) { + // The message has moved on MAP virtual + // folder representation. + sendMnsEvent(MESSAGE_SHIFT, id, "TELECOM/MSG/" + + newFolder, "TELECOM/MSG/DRAFT", "MMS"); + if ( newFolder.equalsIgnoreCase("sent")) { + sendMnsEvent(SENDING_SUCCESS, id, + "TELECOM/MSG/" + newFolder, + null, "MMS"); + } + } + } + + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCRMmsDraft == CR_MMS_DRAFT_A) { + currentCRMmsDraft = CR_MMS_DRAFT_B; + } else { + currentCRMmsDraft = CR_MMS_DRAFT_A; + } + } + + /** + * Check for change in MMS Inbox folder and send a notification if there is + * a change + */ + private void checkMmsInbox() { + + int currentItemCount = 0; + int newItemCount = 0; + + if (currentCRMmsInbox == CR_MMS_INBOX_A) { + currentItemCount = crMmsInboxA.getCount(); + crMmsInboxB.requery(); + newItemCount = crMmsInboxB.getCount(); + } else { + currentItemCount = crMmsInboxB.getCount(); + crMmsInboxA.requery(); + newItemCount = crMmsInboxA.getCount(); + } + + Log.d(TAG, "MMS INBOX current " + currentItemCount + " new " + + newItemCount); + + if (currentItemCount > newItemCount) { + crMmsInboxA.moveToFirst(); + crMmsInboxB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crMmsInboxA, + new String[] { "_id" }, crMmsInboxB, new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCRMmsInbox == CR_MMS_INBOX_A) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " MMS DELETED FROM INBOX "); + String id = crMmsInboxA.getString(crMmsInboxA + .getColumnIndex("_id")); + int msgType = getMmsContainingFolder(Integer + .parseInt(id)); + if (msgType == -1) { + // Convert to virtual handle for MMS + id = Integer.toString(Integer.valueOf(id) + + MMS_HDLR_CONSTANT); + Log.d(TAG, " DELETED MMS ID " + id); + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/INBOX", null, "MMS"); + } else { + Log.d(TAG, "Shouldn't reach here as you cannot " + + "move msg from Inbox to any other folder"); + } + } else { + // TODO - The current(old) query doesn't have this + // row; + // implies it was added + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCRMmsInbox == CR_MMS_INBOX_B) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " MMS DELETED FROM INBOX "); + String id = crMmsInboxB.getString(crMmsInboxB + .getColumnIndex("_id")); + int msgType = getMmsContainingFolder(Integer + .parseInt(id)); + if (msgType == -1) { + // Convert to virtual handle for MMS + id = Integer.toString(Integer.valueOf(id) + + MMS_HDLR_CONSTANT); + Log.d(TAG, " DELETED MMS ID " + id); + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/INBOX", null, "MMS"); + } else { + Log.d(TAG, "Shouldn't reach here as you cannot " + + "move msg from Inbox to any other folder"); + } + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCRMmsInbox == CR_MMS_INBOX_A) { + currentCRMmsInbox = CR_MMS_INBOX_B; + } else { + currentCRMmsInbox = CR_MMS_INBOX_A; + } + } + /** + * Check for change in MMS Sent folder and send a notification if there is + * a change + */ + private void checkMmsSent() { + + int currentItemCount = 0; + int newItemCount = 0; + + if (currentCRMmsSent == CR_MMS_SENT_A) { + currentItemCount = crMmsSentA.getCount(); + crMmsSentB.requery(); + newItemCount = crMmsSentB.getCount(); + } else { + currentItemCount = crMmsSentB.getCount(); + crMmsSentA.requery(); + newItemCount = crMmsSentA.getCount(); + } + + Log.d(TAG, "MMS SENT current " + currentItemCount + " new " + + newItemCount); + + if (currentItemCount > newItemCount) { + crMmsSentA.moveToFirst(); + crMmsSentB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crMmsSentA, + new String[] { "_id" }, crMmsSentB, new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCRMmsSent == CR_MMS_SENT_A) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " MMS DELETED FROM SENT "); + String id = crMmsSentA.getString(crMmsSentA + .getColumnIndex("_id")); + int msgType = getMmsContainingFolder(Integer + .parseInt(id)); + if (msgType == -1) { + // Convert to virtual handle for MMS + id = Integer.toString(Integer.valueOf(id) + + MMS_HDLR_CONSTANT); + Log.d(TAG, " DELETED MMS ID " + id); + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/SENT", null, "MMS"); + } else { + Log.d(TAG, "Shouldn't reach here as you cannot " + + "move msg from Sent to any other folder"); + } + } else { + // TODO - The current(old) query doesn't have this + // row; + // implies it was added + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCRMmsSent == CR_MMS_SENT_B) { + // The new query doesn't have this row; implies it + // was deleted + Log.d(TAG, " MMS DELETED FROM SENT "); + String id = crMmsSentB.getString(crMmsSentB + .getColumnIndex("_id")); + int msgType = getMmsContainingFolder(Integer + .parseInt(id)); + if (msgType == -1) { + // Convert to virtual handle for MMS + id = Integer.toString(Integer.valueOf(id) + + MMS_HDLR_CONSTANT); + Log.d(TAG, " DELETED MMS ID " + id); + sendMnsEvent(MESSAGE_DELETED, id, + "TELECOM/MSG/SENT", null, "MMS"); + } else { + Log.d(TAG, "Shouldn't reach here as you cannot " + + "move msg from Sent to any other folder"); + } + } else { + // The current(old) query doesn't have this row; + // implies it was added + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCRMmsSent == CR_MMS_SENT_A) { + currentCRMmsSent = CR_MMS_SENT_B; + } else { + currentCRMmsSent = CR_MMS_SENT_A; + } + } + + /** + * Check for MMS message being added and send a notification if there is a + * change + */ + private void checkMmsAdded() { + + int currentItemCount = 0; + int newItemCount = 0; + + if (currentCRMms == CR_MMS_A) { + currentItemCount = crMmsA.getCount(); + crMmsB.requery(); + newItemCount = crMmsB.getCount(); + } else { + currentItemCount = crMmsB.getCount(); + crMmsA.requery(); + newItemCount = crMmsA.getCount(); + } + + Log.d(TAG, "MMS current " + currentItemCount + " new " + newItemCount); + + if (newItemCount > currentItemCount) { + crMmsA.moveToFirst(); + crMmsB.moveToFirst(); + + CursorJoiner joiner = new CursorJoiner(crMmsA, + new String[] { "_id" }, crMmsB, new String[] { "_id" }); + + CursorJoiner.Result joinerResult; + while (joiner.hasNext()) { + joinerResult = joiner.next(); + switch (joinerResult) { + case LEFT: + // handle case where a row in cursor1 is unique + if (currentCRMms == CR_MMS_A) { + // The new query doesn't have this row; implies it + // was deleted + } else { + // The current(old) query doesn't have this row; + // implies it was added + Log.d(TAG, " MMS ADDED TO INBOX "); + String id1 = crMmsA.getString(crMmsA + .getColumnIndex("_id")); + int msgType = getMmsContainingFolder(Integer + .parseInt(id1)); + String folder = getMAPFolder(msgType); + if (folder != null) { + // Convert to virtual handle for MMS + id1 = Integer.toString(Integer.valueOf(id1) + + MMS_HDLR_CONSTANT); + Log.d(TAG, " ADDED MMS ID " + id1); + sendMnsEvent(NEW_MESSAGE, id1, "TELECOM/MSG/" + + folder, null, "MMS"); + } else { + Log.d(TAG, " ADDED TO UNKNOWN FOLDER"); + } + } + break; + case RIGHT: + // handle case where a row in cursor2 is unique + if (currentCRMms == CR_MMS_B) { + // The new query doesn't have this row; implies it + // was deleted + } else { + // The current(old) query doesn't have this row; + // implies it was added + Log.d(TAG, " MMS ADDED "); + String id1 = crMmsB.getString(crMmsB + .getColumnIndex("_id")); + int msgType = getMmsContainingFolder(Integer + .parseInt(id1)); + String folder = getMAPFolder(msgType); + if (folder != null) { + // Convert to virtual handle for MMS + id1 = Integer.toString(Integer.valueOf(id1) + + MMS_HDLR_CONSTANT); + + Log.d(TAG, " ADDED MMS ID " + id1); + sendMnsEvent(NEW_MESSAGE, id1, "TELECOM/MSG/" + + folder, null, "MMS"); + } else { + Log.d(TAG, " ADDED TO UNKNOWN FOLDER"); + } + } + break; + case BOTH: + // handle case where a row with the same key is in both + // cursors + break; + } + } + } + if (currentCRMms == CR_MMS_A) { + currentCRMms = CR_MMS_B; + } else { + currentCRMms = CR_MMS_A; + } + } + /** + * Get the folder name (MAP representation) based on the message Handle + */ + private int getMmsContainingFolder(int msgID) { + int folderNum = -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.getCount() > 0) { + cursor.moveToFirst(); + int msgboxInd = cursor.getColumnIndex("msg_box"); + folderNum = cursor.getInt(msgboxInd); + } + cursor.close(); + return folderNum; + } + +} diff --git a/src/com/android/bluetooth/map/BluetoothMnsObexSession.java b/src/com/android/bluetooth/map/BluetoothMnsObexSession.java new file mode 100644 index 000000000..6d3c663ee --- /dev/null +++ b/src/com/android/bluetooth/map/BluetoothMnsObexSession.java @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map; + +import android.content.ContentValues; +import android.content.Context; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.os.PowerManager; +import android.os.Process; +import android.util.Log; + +import javax.obex.*; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.FileInputStream; +import java.io.File; + +import org.apache.http.Header; +/** + * 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 Handler mCallback; + + 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 void disconnect() { + 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); + } + 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); + } + if (mTransport != null) { + try { + mTransport.close(); + } catch (IOException e) { + Log.e(TAG, "mTransport.close error"); + } + + } + } + + private HeaderSet hsConnect = null; + + public void connect() { + Log.d(TAG, "Create ClientSession with transport " + mTransport.toString()); + try { + mCs = new ClientSession(mTransport); + mConnected = true; + } catch (IOException e1) { + Log.e(TAG, "OBEX session create error"); + } + if (mConnected) { + 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"); + } + } + synchronized (this) { + mWaitingForRemote = false; + } + } + + public int sendEvent(File file, byte masInstanceId) { + + 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; + InputStream inputStream = 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 "); + error = true; + } + synchronized (this) { + mWaitingForRemote = false; + } + if (!error) { + try { + if (V) Log.v(TAG, "Send headerset Event "); + outputStream = putOperation.openOutputStream(); + inputStream = putOperation.openInputStream(); + } catch (IOException e) { + Log.e(TAG, "Error when opening OutputStream"); + error = true; + } + } + + if (!error) { + int position = 0; + int readLength = 0; + boolean okToProceed = true; + long timestamp = 0; + int outputBufferSize = putOperation.getMaxPacketSize(); + byte[] buffer = new byte[outputBufferSize]; + + + FileInputStream fileInputStream = new FileInputStream(file); + BufferedInputStream a = new BufferedInputStream(fileInputStream, 0x4000); + + while (okToProceed && (position != file.length())) { + if (V) timestamp = System.currentTimeMillis(); + + readLength = a.read(buffer, 0, outputBufferSize); + outputStream.write(buffer, 0, readLength); + + /* check remote abort */ + responseCode = putOperation.getResponseCode(); + responseCode = ResponseCodes.OBEX_HTTP_OK; + + if (V) Log.v(TAG, "Response code is " + responseCode); + if (responseCode != ResponseCodes.OBEX_HTTP_CONTINUE + && responseCode != ResponseCodes.OBEX_HTTP_OK) { + /* abort happens */ + okToProceed = false; + } else { + 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()); + } catch (NullPointerException e) { + handleSendException(e.toString()); + } catch (IndexOutOfBoundsException e) { + handleSendException(e.toString()); + } finally { + 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 (inputStream != null) { + inputStream.close(); + } + if (putOperation != null) { + putOperation.close(); + } + } catch (IOException e) { + Log.e(TAG, "Error when closing stream after send"); + } + } + + return responseCode; + } + + private void handleSendException(String exception) { + Log.e(TAG, "Error when sending event: " + exception); + } +} diff --git a/src/com/android/bluetooth/map/BluetoothMnsPreference.java b/src/com/android/bluetooth/map/BluetoothMnsPreference.java new file mode 100644 index 000000000..0da490f16 --- /dev/null +++ b/src/com/android/bluetooth/map/BluetoothMnsPreference.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map; + +import java.util.HashMap; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.util.Log; + +/** + * 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/com/android/bluetooth/map/BluetoothMnsRfcommTransport.java b/src/com/android/bluetooth/map/BluetoothMnsRfcommTransport.java new file mode 100644 index 000000000..d1a48bf4f --- /dev/null +++ b/src/com/android/bluetooth/map/BluetoothMnsRfcommTransport.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.obex.*; +import android.bluetooth.BluetoothSocket; + + + +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/com/android/bluetooth/map/MapUtils/BmessageConsts.java b/src/com/android/bluetooth/map/MapUtils/BmessageConsts.java new file mode 100644 index 000000000..fa5ee6fb0 --- /dev/null +++ b/src/com/android/bluetooth/map/MapUtils/BmessageConsts.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.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/com/android/bluetooth/map/MapUtils/CommonUtils.java b/src/com/android/bluetooth/map/MapUtils/CommonUtils.java new file mode 100644 index 000000000..b76b17555 --- /dev/null +++ b/src/com/android/bluetooth/map/MapUtils/CommonUtils.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map.MapUtils; + +import java.util.HashSet; +import java.util.Set; +import java.util.ArrayList; +import java.util.List; + +import android.content.Context; + +public class CommonUtils { + + public final String TAG = "CommonUtils"; + + public String getFullPath(String child, Context context, List<String> folderList, String CurrentPath) { + + String tempPath = null; + List<String> completeFolderList = new ArrayList<String>(); + EmailUtils eu = new EmailUtils(); + completeFolderList = eu.folderListEmail(folderList, context); + + if (child != null) { + if (CurrentPath == null) { + if (child.equals("telecom")) { + // Telecom is fine + tempPath = new String("telecom"); + } + } + else if (CurrentPath.equals("telecom")) { + if (child.equals("msg")) { + tempPath = CurrentPath + "/" + child; + } + } + else if (CurrentPath.equals("telecom/msg")) { + for (String folder : completeFolderList) { //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; + } + +} diff --git a/src/com/android/bluetooth/map/MapUtils/EmailUtils.java b/src/com/android/bluetooth/map/MapUtils/EmailUtils.java new file mode 100644 index 000000000..d3a98b970 --- /dev/null +++ b/src/com/android/bluetooth/map/MapUtils/EmailUtils.java @@ -0,0 +1,561 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map.MapUtils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Date; +import java.util.List; +import java.util.Set; + +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 com.android.bluetooth.map.BluetoothMasAppIf.BluetoothMasMessageRsp; +import com.android.bluetooth.map.BluetoothMasAppParams; + + +public class EmailUtils { + + public final String TAG = "EmailUtils"; + 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 int EMAIL_HDLR_CONSTANT = 200000; + + // TODO: Is this needed? public static int deletedEmailFolderId = -1; + + public 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.moveToFirst()) { + do { + Log.d(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()); + } + Log.d(TAG, " Folder Listing of SMS,MMS and EMAIL: "+folderList); + + return folderList; + + } + + public String getWhereIsQueryForTypeEmail(String folder, Context context) { + + Log.d(TAG, ":: Inside getWhereIsQueryForTypeEmail ::"+ folder); + String query = "mailboxKey = -1"; + String folderId; + Uri uri = Uri.parse("content://com.android.email.provider/mailbox"); + + Cursor cr = context.getContentResolver().query( + uri, null, "(UPPER(displayName) = '"+ folder.toUpperCase()+"')" , null, null); + + if ( cr.moveToFirst()) { + do { + Log.d(TAG, ":: Inside getWhereIsQueryForTypeEmail Folder Name ::"+ cr.getString(cr.getColumnIndex("displayName"))); + folderId = cr.getString(cr.getColumnIndex("_id")); + query = "mailboxKey = "+ folderId; + break; + } while ( cr.moveToNext()); + } + + return query; + } + + public int getMessageSizeEmail(int messageId, Context context) { + + Log.d(TAG, ":: Inside getMessageSizeEmail ::"+ messageId); + int msgSize = 0; + String textContent; + Uri uri = Uri.parse("content://com.android.email.provider/body"); + + Cursor cr = context.getContentResolver().query( + uri, null, "messageKey = "+ messageId , null, null); + + if ( cr.moveToFirst()) { + do { + textContent = cr.getString(cr.getColumnIndex("textContent")); + msgSize = textContent.length(); + break; + } while ( cr.moveToNext()); + } + + return msgSize; + } + + public String getFolderName(String[] splitStringsEmail) { + String folderName; + Log.d(TAG, ":: Inside getFolderName ::"+ splitStringsEmail); + + if(splitStringsEmail[2].trim().equalsIgnoreCase("[Gmail]") || splitStringsEmail[2].trim().contains("Gmail")){ + folderName = splitStringsEmail[2]+"/"+splitStringsEmail[3]; + } + else{ + folderName = splitStringsEmail[2]; + } + + Log.d(TAG, "folderName :: " + folderName); + + return folderName; + } + + public String getConditionString(String folderName, Context context, BluetoothMasAppParams appParams) { + String whereClauseEmail = getWhereIsQueryForTypeEmail(folderName, context); + + /* Filter readstatus: 0 no filtering, 0x01 get unread, 0x10 get read */ + if (appParams.FilterReadStatus != 0) { + Log.d(TAG, "##Inside Filter Read Status.....##:"); + if ((appParams.FilterReadStatus & 0x1) != 0) { + if (whereClauseEmail != "") { + whereClauseEmail += " AND "; + } + whereClauseEmail += " flagRead = 0 "; + } + Log.d(TAG, "##Filter Read Status Value##:"+appParams.FilterReadStatus); + Log.d(TAG, "##Filter Read Status Condition Value##:"+(appParams.FilterReadStatus & 0x10)); + if ((appParams.FilterReadStatus & 0x10) != 0) { + if (whereClauseEmail != "") { + whereClauseEmail += " AND "; + } + Log.d(TAG, "##Filter Read Status Value Appended to Query##:"); + whereClauseEmail += " flagRead = 1 "; + } + } + + Log.d(TAG, "##Filter Recipient Value##:"+appParams.FilterRecipient); + Log.d(TAG, "##Filter Recipient Condition 1 ##:"+(appParams.FilterRecipient != null)); + Log.d(TAG, "##Filter Recipient Condition 2 ##:"+(appParams.FilterRecipient != "")); + //Filter Recipient + if ((appParams.FilterRecipient != null) && (appParams.FilterRecipient != "") && (appParams.FilterRecipient.length() > 0)) { + if (whereClauseEmail != "") { + whereClauseEmail += " AND "; + } + whereClauseEmail += " toList LIKE '%"+appParams.FilterRecipient.trim()+"%'"; + } + + // TODO Filter Originator + + if ((appParams.FilterOriginator != null) + && (appParams.FilterOriginator.length() != 0)) { + + // For incoming message + if (whereClauseEmail != "") { + whereClauseEmail += " AND "; + } + String str1 = appParams.FilterOriginator; + whereClauseEmail += "fromList LIKE '%"+appParams.FilterOriginator.trim()+"%'"; + } + Log.d(TAG, "##11 whereClauseEmail 11##:" + whereClauseEmail); + // TODO Filter priority? + + /* Filter Period Begin */ + if ((appParams.FilterPeriodBegin != null) + && (appParams.FilterPeriodBegin != "")) { + Time time = new Time(); + + try { + time.parse(appParams.FilterPeriodBegin); + if (whereClauseEmail != "") { + 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 != "")) { + Time time = new Time(); + try { + time.parse(appParams.FilterPeriodEnd); + if (whereClauseEmail != "") { + whereClauseEmail += " AND "; + } + whereClauseEmail += "timeStamp < " + time.toMillis(false); + } catch (TimeFormatException e) { + Log.d(TAG, "Bad formatted FilterPeriodEnd, Ignore" + + appParams.FilterPeriodEnd); + } + } + + return whereClauseEmail; + } + + public 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) { + + MsgListingConsts emailMsg = new MsgListingConsts(); + emailMsg.setMsg_handle(Integer.valueOf(msgId)+ EMAIL_HDLR_CONSTANT); + + Time time = new Time(); + time.set(Long.valueOf(timestamp)); + + String datetimeStr = time.toString().substring(0, 15); + + emailMsg.msgInfo.setDateTime(datetimeStr); + + if ((appParams.ParameterMask & BIT_SUBJECT) != 0) { + + Log.d(TAG, "Fileter Subject Length ::"+appParams.SubjectLength); + if ((subject != null && appParams.SubjectLength > 0) + && (subject.length() > appParams.SubjectLength)) { + subject = subject.substring(0, + appParams.SubjectLength); + } + emailMsg.setSubject(subject.trim()); + emailMsg.sendSubject = true; + } + + if ((appParams.ParameterMask & BIT_DATETIME) != 0) { + // TODO Clarify if OFFSET is needed. + emailMsg.setDatetime(datetimeStr); + } + + if ((appParams.ParameterMask & BIT_SENDER_NAME) != 0) { + if(senderName.contains("")){ + String[] senderStr = senderName.split(""); + if(senderStr !=null && senderStr.length > 0){ + Log.d(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.contains("")){ + String[] senderAddrStr = senderAddressing.split(""); + if(senderAddrStr !=null && senderAddrStr.length > 0){ + Log.d(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 = null; + + if(recipientName.contains("")){ + String[] recepientStr = recipientName.split(""); + if(recepientStr !=null && recepientStr.length > 0){ + Log.d(TAG, " ::Recepient name split String 0:: " + recepientStr[0] + + "::Recepient name split String 1:: " + recepientStr[1]); + emailMsg.setRecepient_name(recepientStr[1].trim()); + } + } + else if(recipientName.contains("")){ + multiRecepients = recipientName.replace('', ';'); + Log.d(TAG, " ::Recepient name :: " + multiRecepients); + emailMsg.setRecepient_name(multiRecepients.trim()); + } + else{ + emailMsg.setRecepient_name(recipientName.trim()); + } + } + + if ((appParams.ParameterMask & BIT_RECIPIENT_ADDRESSING) != 0) { + String multiRecepientAddrs = null; + + if(recipientAddressing.contains("")){ + String[] recepientAddrStr = recipientAddressing.split(""); + if(recepientAddrStr !=null && recepientAddrStr.length > 0){ + Log.d(TAG, " ::Recepient addressing split String 0:: " + recepientAddrStr[0] + + "::Recepient addressing split String 1:: " + recepientAddrStr[1]); + emailMsg.setRecepient_addressing(recepientAddrStr[0].trim()); + } + } + else if(recipientAddressing.contains("")){ + multiRecepientAddrs = recipientAddressing.replace('', ';'); + Log.d(TAG, " ::Recepient Address:: " + multiRecepientAddrs); + emailMsg.setRecepient_addressing(multiRecepientAddrs.trim()); + } + else{ + emailMsg.setRecepient_addressing(recipientAddressing.trim()); + } + } + + if ((appParams.ParameterMask & BIT_TYPE) != 0) { + emailMsg.setType("EMAIL"); + } + + if ((appParams.ParameterMask & BIT_SIZE) != 0) { + int msgSize = 0; + msgSize = getMessageSizeEmail(Integer.parseInt(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(0); + } + + if ((appParams.ParameterMask & BIT_PRIORITY) != 0) { + // TODO Get correct priority + emailMsg.setPriority("no"); + } + + if ((appParams.ParameterMask & BIT_READ) != 0) { + Log.d(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 + Log.d(TAG, " ::Reply To addressing:: " + replyToStr); + emailMsg.setReplyTo_addressing(replyToStr); + } + + return emailMsg; + } + public String bldEmailBmsg(int msgHandle, BluetoothMasMessageRsp rsp, Context context, MapUtils mu) { + Log.d(TAG, "Inside bldEmailBmsg:"); + 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; + cr1 = context.getContentResolver().query(uri1, null, whereClause, null, + null); + + if (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")); + BmessageConsts bmsg = new BmessageConsts(); + + // Create a bMessage + + // 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("TELECOM/MSG/" + containingFolder); + + bmsg.setVcard_version("2.1"); + + String senderName = null; + senderName = cr1.getString(cr1.getColumnIndex("fromList")); + 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; + recipientName = cr1.getString(cr1.getColumnIndex("toList")); + 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('', ';'); + Log.d(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()); + } + + + // TODO Set either Encoding or Native + + // TODO how to get body for MMS? This is for SMS only + + StringBuilder sb = new StringBuilder(); + Date date = new Date(Long.parseLong(timeStamp)); + sb.append("Date: ").append(date.toString()).append("\r\n"); + sb.append("To:").append(bmsg.getRecipientVcard_email()).append("\r\n"); + sb.append("From:").append(bmsg.getOriginatorVcard_email()).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=\"RPI-Messaging.123456789.0\"") + .append("\r\n"); + sb.append("Content-Transfer-Encoding: 7bit").append("\r\n") + .append("\r\n"); + sb.append("MIME Message").append("\r\n"); + sb.append("--RPI-Messaging.123456789.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"); + sb.append("Content-Disposition:inline").append("\r\n") + .append("\r\n"); + + //Query the body table for obtaining the message body + Cursor cr2 = null; + String emailBody = 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.getCount() > 0) { + cr2.moveToFirst(); + emailBody = cr2.getString(cr2.getColumnIndex("textContent")); + } + sb.append(emailBody).append("\r\n"); + + sb.append("--RPI-Messaging.123456789.0--").append("\r\n"); + bmsg.setBody_msg(sb.toString()); + bmsg.setBody_length(sb.length() + 22); + Log.d(TAG, "bMessageEmail test 44444444\n"); + // Send a bMessage + Log.d(TAG, "bMessageEmail test\n"); + Log.d(TAG, "=======================\n\n"); + str = mu.toBmessageEmail(bmsg); + Log.d(TAG, str); + Log.d(TAG, "\n\n"); + } + + return str; + } + + /** + * Get the folder name (MAP representation) for Email based on the + * mailboxKey value in message table + */ + public 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.getCount() > 0) { + cr.moveToFirst(); + folderName = cr.getString(cr.getColumnIndex("displayName")); + return folderName; + } + return null; + } + + +} diff --git a/src/com/android/bluetooth/map/MapUtils/MapUtils.java b/src/com/android/bluetooth/map/MapUtils/MapUtils.java new file mode 100644 index 000000000..b98bd6c11 --- /dev/null +++ b/src/com/android/bluetooth/map/MapUtils/MapUtils.java @@ -0,0 +1,1623 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map.MapUtils; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; +import android.util.Log; + +import org.xmlpull.v1.XmlSerializer; + +import android.util.Xml; + +/** + * 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 { + + private 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 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 String messageListingXML(List<MsgListingConsts> list) { + XmlSerializer serializer = Xml.newSerializer(); + StringWriter writer = new StringWriter(); + try { + String str1; + String str2 = "<?xml version=\"1.0\"?>"; + serializer.setOutput(writer); + serializer.startDocument(null, null); + serializer.text("\n"); + serializer.startTag("", "MAP-msg-listing"); + serializer.attribute("", "version", "1.0"); + for (MsgListingConsts msg : list) { + serializer.startTag("", ""); + + serializer.attribute("", "msg handle", ("" + msg.msg_handle)); + if (msg.sendSubject == true) { + if (msg.subject == null){ + serializer.attribute("", "subject", ""); + } else { + serializer.attribute("", "subject", msg.subject); + } + + } + if (msg.datetime != null) { + serializer.attribute("", "datetime", msg.datetime); + } + if (msg.sender_name != null) { + serializer.attribute("", "sender_name", msg.sender_name); + } + + if (msg.sender_addressing != null) { + serializer.attribute("", "sender_addressing", + msg.sender_addressing); + } + + if (msg.replyto_addressing != null) { + serializer.attribute("", "replyto_addressing", + msg.replyto_addressing); + } + + if (msg.recepient_name != null) { + serializer.attribute("", "recipient_name", + msg.recepient_name); + } + if (msg.sendRecipient_addressing == true) { + if (msg.recepient_addressing != null) { + serializer.attribute("", "recipient_addressing", + msg.recepient_addressing); + } else { + serializer.attribute("", "recipient_addressing", ""); + } + } + if (msg.type != null) { + serializer.attribute("", "type", msg.type); + } + if (msg.size != 0) { + serializer.attribute("", "size", ("" + msg.size)); + } + + if (msg.contains_text != null) { + serializer.attribute("", "text", msg.contains_text); + } + + if (msg.reception_status != null) { + serializer.attribute("", "reception_status", + msg.reception_status); + } + + if (msg.attachment_size != -1) { + serializer.attribute("", "attachment_size", + ("" + Integer.toString(msg.attachment_size))); + } + + if (msg.priority != null) { + serializer.attribute("", "priority", msg.priority); + } + + if (msg.read != null) { + serializer.attribute("", "read", msg.read); + } + + if (msg.sent != null) { + serializer.attribute("", "sent", msg.sent); + } + + if (msg.msg_protected != null) { + serializer.attribute("", "protected", msg.msg_protected); + } + + serializer.endTag("", ""); + + } + serializer.endTag("", "MAP-msg-listing"); + serializer.endDocument(); + str1 = writer.toString(); + + str1 = removeMsgHdrSpace(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 (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 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 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 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("", ""); + serializer.attribute("", "msg 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("", ""); + + } + serializer.endDocument(); + str1 = writer.toString(); + + str1 = removeMsgHdrSpace(str1); + 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 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("", ""); + if (type != null) { + serializer.attribute("", "event 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("", ""); + 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 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 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"); + + // 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 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"); + + sb.append("LANGUAGE:English").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 + */ + + public BmessageConsts fromBmessageSMS(String bmsg) { + + BmessageConsts bMsgObj = new BmessageConsts(); + + // Extract Telephone number of sender + String phoneNumber = null; + String vCard = null; + vCard = fetchRecipientVcard(bmsg); + phoneNumber = fetchVcardTel(vCard); + bMsgObj.setRecipientVcard_phone_number(phoneNumber); + + // Extract vCard Version + bMsgObj.setVcard_version(fetchVcardVersion(vCard)); + + // Extract vCard Name + bMsgObj.setVcard_version(fetchVcardVersion(vCard)); + + // 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 + */ + + public BmessageConsts fromBmessageMMS(String bmsg) { + + BmessageConsts bMsgObj = new BmessageConsts(); + + // Extract Telephone number of sender + String phoneNumber = null; + String vCard = null; + vCard = fetchRecipientVcard(bmsg); + + if (vCard.indexOf("EMAIL:") > 0) { + phoneNumber = fetchVcardEmailforMms(vCard); + } else { + phoneNumber = fetchVcardTel(vCard); + } + + bMsgObj.setRecipientVcard_phone_number(phoneNumber); + + // Extract vCard Version + bMsgObj.setVcard_version(fetchVcardVersion(vCard)); + + // Extract vCard Name + bMsgObj.setVcard_version(fetchVcardVersion(vCard)); + + // 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; + + } + + /** + * 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 BmessageConsts fromBmessageEmail(String bmsg) { + + BmessageConsts bMsgObj = new BmessageConsts(); + Log.d("MapUtils", "Inside fromBmessageEmail method::"); + // Extract Telephone number of sender + String email = null; + String vCard = null; + vCard = fetchRecepientVcardEmail(bmsg); + Log.d("MapUtils", "vCard Info:: "+vCard); + email = fetchVcardEmail(vCard); + + Log.d("MapUtils", "email Info:: "+email); + bMsgObj.setRecipientVcard_email(email); + + String vcardOrig = fetchOriginatorVcardEmail(bmsg); + String emailOrig = fetchVcardEmail(vcardOrig); + Log.d("MapUtils", "Vcard Originator Email:: "+emailOrig); + bMsgObj.setOriginatorVcard_email(emailOrig); + + Log.d("MapUtils", "Vcard Originatore Name:: "+fetchVcardName(vcardOrig)); + String nameOrig = fetchVcardName(vcardOrig); + bMsgObj.setOriginatorVcard_name(nameOrig); + + Log.d("MapUtils", "Vcard version:: "+fetchVcardVersion(vCard)); + // Extract vCard Version + bMsgObj.setVcard_version(fetchVcardVersion(vCard)); + + // Extract vCard Name + + Log.d("MapUtils", "Bmsg version:: "+fetchVersion(bmsg)); + // Extract bMessage Version + bMsgObj.setBmsg_version(fetchVersion(bmsg)); + + Log.d("MapUtils", "Read status:: "+fetchReadStatus(bmsg)); + // Extract Message Status + bMsgObj.setStatus(fetchReadStatus(bmsg)); + + Log.d("MapUtils", "Message Type:: "+fetchType(bmsg)); + // Extract Message Type + bMsgObj.setType(fetchType(bmsg)); + + Log.d("MapUtils", "Folder:: "+fetchFolder(bmsg)); + // Extract Message Folder + bMsgObj.setFolder(fetchFolder(bmsg)); + + Log.d("MapUtils", "body length:: "+fetchBodyLength(bmsg)); + // Fetch Message Length + bMsgObj.setBody_length(fetchBodyLength(bmsg)); + + Log.d("MapUtils", "Message body:: "+fetchBodyMsgEmail(bmsg)); + // Extract Message + bMsgObj.setBody_msg(fetchBodyMsgEmail(bmsg)); + + Log.d("MapUtils", "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 String fetchVcardEmail(String vCard) { + + int pos = vCard.indexOf(("EMAIL:")); + int beginVersionPos = pos + (("EMAIL:").length()); + Log.d("Map Utils","Begin Version Position Email:: "+beginVersionPos); + int endVersionPos = vCard.indexOf("\n", beginVersionPos); + Log.d("Map Utils","End version Pos Email:: "+endVersionPos); + return vCard.substring(beginVersionPos, endVersionPos); + } + private String fetchRecepientVcardEmail(String bmsg) { + + // Find the position of the first vCard in the string + int pos = bmsg.indexOf("BEGIN:BENV"); + Log.d("fetchOriginatorVcard", "vCard start position:: "+pos); + if (pos > 0) { + Log.d("fetchOriginatorVcard", "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 null; + + } + } + + private String fetchOriginatorVcardEmail(String bmsg) { + + // Find the position of the first vCard in the string + int pos = bmsg.indexOf("BEGIN:VCARD"); + Log.d("fetchOriginatorVcard", "vCard start position:: "+pos); + if (pos > 0) { + Log.d("fetchOriginatorVcard", "vCard start position greater than 0::"); + int beginVcardPos = pos + ("\r\n".length()); + int endVcardPos = bmsg.indexOf("END:VCARD"); + + return bmsg.substring(beginVcardPos, endVcardPos); + + } else { + + return null; + + } + } + private 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 null; + } + } + + + /** + * 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 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 null; + + } + } + + /** + * 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 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 + */ + + @SuppressWarnings("unused") + private String fetchRecipientVcard(String bmsg) { + + // Locate BENV + int locBENV = 0; + int pos = 0; + locBENV = bmsg.indexOf(CRLF + "BEGIN:BENV"); + pos = bmsg.indexOf(CRLF + "BEGIN:VCARD", locBENV); + if (locBENV < pos) { + pos = bmsg.indexOf(CRLF + "BEGIN:VCARD", locBENV); + } else { + pos = bmsg.indexOf(CRLF + "BEGIN:VCARD"); + } + if (pos > 0) { + int beginVcardPos = pos; + int endVcardPos = bmsg.indexOf("END:VCARD", pos); + return bmsg.substring(beginVcardPos, endVcardPos); + + } else { + return null; + } + + } + + /** + * 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 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 null; + + } + } + + /** + * 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 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 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 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 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 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 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 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 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 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 null; + } + } + + private String fetchBodyMsgEmail(String body) { + Log.d("MapUtils", "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; + } + } + + /** + * 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 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 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 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 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); + } + + /** + * removeMsgHdrSpace + * + * This method takes as input a String that contains message listings and + * removes a space between the < and the msg handle value. This space is + * inserted by the serializer causing an error in processing the message + * correctly. This private method parses the message and removes the spaces. + * + * @param String + * Message to be parsed + * @return String This method returns the message String + */ + + private String removeMsgHdrSpace(String message) { + String str1 = null; + String str2 = null; + + Integer index = 0; + Integer endSubstring = 0; + + index = message.indexOf("< msg handle"); + if (index < 0) { + str2 = message; + + } else { + str2 = message.substring(0, index); + str2 = str2 + "\n"; + index = 0; + while ((index = message.indexOf("msg handle", index)) > 0) { + endSubstring = message.indexOf("/>", index) + "/>".length(); + str1 = "<" + message.substring(index, endSubstring); + index = index + 1; + str2 = str2 + str1; + + } + } + + return str2 + "</MAP-msg-listing>"; + + } + /** + * 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 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); + } + +} diff --git a/src/com/android/bluetooth/map/MapUtils/MapUtilsConsts.java b/src/com/android/bluetooth/map/MapUtils/MapUtilsConsts.java new file mode 100644 index 000000000..729b89129 --- /dev/null +++ b/src/com/android/bluetooth/map/MapUtils/MapUtilsConsts.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.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"; + +} diff --git a/src/com/android/bluetooth/map/MapUtils/MsgListingConsts.java b/src/com/android/bluetooth/map/MapUtils/MsgListingConsts.java new file mode 100644 index 000000000..a9dccd6d5 --- /dev/null +++ b/src/com/android/bluetooth/map/MapUtils/MsgListingConsts.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.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 int 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 = 0; + 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 int getMsg_handle() { + return msg_handle; + } + + public void setMsg_handle(int 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/com/android/bluetooth/map/MapUtils/SmsMmsUtils.java b/src/com/android/bluetooth/map/MapUtils/SmsMmsUtils.java new file mode 100644 index 000000000..d00573ec1 --- /dev/null +++ b/src/com/android/bluetooth/map/MapUtils/SmsMmsUtils.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map.MapUtils; + +import java.util.List; +import java.util.Set; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.PhoneLookup; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.text.format.Time; +import android.util.Log; +import android.util.TimeFormatException; + +import com.android.bluetooth.map.BluetoothMasAppParams; + + + +public class SmsMmsUtils { + + public 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; + + private final String Inbox = "inbox"; + private final String Outbox = "outbox"; + private final String Sent = "sent"; + private final String Deleted = "deleted"; + private final String Draft = "draft"; + private final String Drafts = "drafts"; + private final String Undelivered = "undelivered"; + private final String Failed = "failed"; + private final String Queued = "queued"; + + private 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; + + private List<VcardContent> list; + + private class VcardContent { + public String name = ""; + public String tel = ""; + public String email = ""; + } + + public List<String> folderListSmsMms(List<String> folderList) { + folderList.add(Inbox); + folderList.add(Outbox); + folderList.add(Sent); + folderList.add(Deleted); + folderList.add(Draft); + + return folderList; + + } + public String getWhereIsQueryForType(String folder) { + + String query = null; + + if (folder.equalsIgnoreCase(Inbox)) { + query = "type = 1 AND thread_id <> " + DELETED_THREAD_ID; + } + else if (folder.equalsIgnoreCase(Outbox)) { + query = "(type = 4 OR type = 5 OR type = 6) AND thread_id <> " + DELETED_THREAD_ID; + } + else if (folder.equalsIgnoreCase(Sent)) { + query = "type = 2 AND thread_id <> " + DELETED_THREAD_ID; + } + else if (folder.equalsIgnoreCase(Draft)) { + query = "type = 3 AND thread_id <> " + DELETED_THREAD_ID; + } + else if (folder.equalsIgnoreCase(Deleted)) { + query = "thread_id = " + DELETED_THREAD_ID; + } + else{ + query = "type = -1"; + } + return query; + + } + public 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 != "") { + whereClause += " AND "; + } + whereClause += " read=0 "; + } + if ((appParams.FilterReadStatus & 0x02) != 0) { + if (whereClause != "") { + 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); + if (whereClause != "") { + 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); + if (whereClause != "") { + whereClause += " AND "; + } + whereClause += "date < " + time.toMillis(false); + } catch (TimeFormatException e) { + Log.d(TAG, "Bad formatted FilterPeriodEnd " + + appParams.FilterPeriodEnd); + } + } + return whereClause; + + } + +} diff --git a/src/com/android/bluetooth/map/MapUtils/SortMsgListByDate.java b/src/com/android/bluetooth/map/MapUtils/SortMsgListByDate.java new file mode 100644 index 000000000..0b31e4a75 --- /dev/null +++ b/src/com/android/bluetooth/map/MapUtils/SortMsgListByDate.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 Code Aurora 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 com.android.bluetooth.map.MapUtils; + +import java.util.Comparator; +import com.android.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()); + } + +} |