Update to Bluetooth MAP 1.2 (server)
- Change folder name lookup to a map
Replaced the arrays used to convert mailbox ID/msg type to a folder name with a static map.
This is to avoid null pointer exception for unknown values, and to catch any changes in
the ID/type values at compile time in stead of runtime.
Bug-id:16874441
- Added Instance Information support and Extended Event support.
Still missing integration wiht SDP MAP feature bit mask support
- Adding Abstract implementation to support conversations
- added IM account handling, IM type definition, Application paramenters.
- addedgetConversactionList functionality
- added method to strip encoding in headers
- Fixed messagelist showing both email address and name in the name fields.
- Fixed Index out of bounds exception was hit when the subject contained invalid chars.
- Added functionality to support the getConversationListReq
Works for SMS/MMS, Email and IM
For Email/IM it depends on the convoContact table in the contract.
For SMS/MMS it uses the contact number+ name if available in contact database.
- Added new parameters to msgListing also in contract class
- Added Test framework for "near system level" tests
Currently only includes an entry point for single device tests.
- Added support for setOwnerStatus
- Added support for vcard type X-BT-UID
- Introduced type SignedLongLong to handle 128 bit values which needs to be handled as hex-strings.
- Added convocontact notification events for IM
- Added support for IM getMessage
- Added setEventFilter function.
- Added event filtering before enquing an event to be send.
- Added selective observers, depending on the active filter.
- Fixed timestamp to be from seconds to seconds (not from milisec)
- Fixed version number in bMessage if remote featurebit is set for v 1.1
- Added content encoding to QP for text that are not USACII
- Corrected the addresses in to/from for IM messages
- Added btuid and btuci to vcard
- Fixed (some) longlines
- Added extendedData support (empty when sending, just logging when receiving)
- Fixed Email folderName compairison changed to ignore case
- Fixed problem with names containing "null"
- BluetoothMapbMessageMms changed to BluetoothMapbMessageMime
- Fixrf addOriginator in getMessage request
- Add missing subjects in events for SMS
- Don't send ReadStatusChanged when pushing a message
- Temp way of adding names/uci to IM msg listing
- Added messageHandle filtering in msgListing
- Convolisting parameter mask support
- Added support for using handle when filtering in root folder during msgLising
- Added subject to event in sms
- Fixed so attribute_mime_type is only sent when parameter is requested
- Fixed feature bit check to messageListing version
- Fixed leaking cursors
- Added support for database identifier
- Added folder and conversation version counters
Change-Id: I4d2954b795aa7ed2a41dd034384da30f240b518f
diff --git a/Android.mk b/Android.mk
index eb1b579..7d2fdfa 100644
--- a/Android.mk
+++ b/Android.mk
@@ -7,8 +7,8 @@
$(call all-java-files-under, lib/mapapi)
LOCAL_MODULE := bluetooth.mapsapi
-
-include $(BUILD_JAVA_LIBRARY)
+LOCAL_MULTILIB := 32
+include $(BUILD_STATIC_JAVA_LIBRARY)
include $(CLEAR_VARS)
@@ -21,8 +21,8 @@
LOCAL_CERTIFICATE := platform
LOCAL_JNI_SHARED_LIBRARIES := libbluetooth_jni
-LOCAL_JAVA_LIBRARIES := javax.obex telephony-common bluetooth.mapsapi
-LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard
+LOCAL_JAVA_LIBRARIES := javax.obex telephony-common
+LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard bluetooth.mapsapi
LOCAL_REQUIRED_MODULES := bluetooth.default
LOCAL_MULTILIB := 32
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e879a75..b0a3ef8 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -27,6 +27,8 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
+ <!-- WRITE_CONTACTS is used for test cases only -->
+ <uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
@@ -234,13 +236,13 @@
android:enabled="@bool/profile_supported_map" >
<intent-filter>
<action android:name="android.bluetooth.IBluetoothMap" />
- <action android:name="android.btmap.intent.action.SHOW_MAPS_EMAIL_SETTINGS" />
+ <action android:name="android.btmap.intent.action.SHOW_MAPS_SETTINGS" />
<action android:name="com.android.bluetooth.map.USER_CONFIRM_TIMEOUT"/>
</intent-filter>
</service>
- <activity android:name=".map.BluetoothMapEmailSettings"
+ <activity android:name=".map.BluetoothMapSettings"
android:process="@string/process"
- android:label="@string/bluetooth_map_email_settings_title"
+ android:label="@string/bluetooth_map_settings_title"
android:excludeFromRecents="true"
android:configChanges="orientation|keyboardHidden"
android:enabled="@bool/profile_supported_map">
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index 02c293e..aa54d4d 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -22,9 +22,10 @@
#include "cutils/properties.h"
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/Log.h"
+
#include <string.h>
#include <pthread.h>
-#include <binder/Parcel.h>
+
#include <sys/stat.h>
#include <fcntl.h>
@@ -471,7 +472,6 @@
acl_state_changed_callback,
callback_thread_event,
dut_mode_recv_callback,
-
le_test_mode_recv_callback,
energy_info_recv_callback
};
@@ -488,7 +488,8 @@
.group = NULL
};
-static bool set_wake_alarm_callout(uint64_t delay_millis, bool should_wake, alarm_cb cb, void *data) {
+static bool set_wake_alarm_callout(uint64_t delay_millis, bool should_wake,
+ alarm_cb cb, void *data) {
JNIEnv *env;
JavaVM *vm = AndroidRuntime::getJavaVM();
jint status = vm->GetEnv((void **)&env, JNI_VERSION_1_6);
@@ -507,7 +508,8 @@
sAlarmCallbackData = data;
jboolean jshould_wake = should_wake ? JNI_TRUE : JNI_FALSE;
- jboolean ret = env->CallBooleanMethod(sJniAdapterServiceObj, method_setWakeAlarm, (jlong)delay_millis, jshould_wake);
+ jboolean ret = env->CallBooleanMethod(sJniAdapterServiceObj, method_setWakeAlarm,
+ (jlong)delay_millis, jshould_wake);
if (!ret) {
sAlarmCallback = NULL;
sAlarmCallbackData = NULL;
@@ -1042,7 +1044,8 @@
}
static int createSocketChannelNative(JNIEnv *env, jobject object, jint type,
- jstring name_str, jbyteArray uuidObj, jint channel, jint flag) {
+ jstring name_str, jbyteArray uuidObj,
+ jint channel, jint flag) {
const char *service_name = NULL;
jbyte *uuid = NULL;
int socket_fd;
diff --git a/jni/com_android_bluetooth_sdp.cpp b/jni/com_android_bluetooth_sdp.cpp
index 8c1bc0f..a266bfa 100644
--- a/jni/com_android_bluetooth_sdp.cpp
+++ b/jni/com_android_bluetooth_sdp.cpp
@@ -148,7 +148,7 @@
ALOGD("%s UUID %.*X",__FUNCTION__,16, (uint8_t*)uuid);
- if ( (ret = sBluetoothSdpInterface->sdp_search((bt_bdaddr_t *)addr,
+ if ((ret = sBluetoothSdpInterface->sdp_search((bt_bdaddr_t *)addr,
(const uint8_t*)uuid)) != BT_STATUS_SUCCESS) {
ALOGE("SDP Search initialization failed: %d", ret);
goto Fail;
diff --git a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
index 32018ba..6a40a27 100644
--- a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
+++ b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
@@ -21,7 +21,7 @@
/**
- * This class defines the minimum sets of data needed for an E-mail client to
+ * This class defines the minimum sets of data needed for a client to
* implement to claim support for the Bluetooth Message Access Profile.
* Access to three data sets are needed:
* <ul>
@@ -35,10 +35,12 @@
* might provide.</li>
* <li>Folder data set with the folder structure for the messages. Each message is linked to an
* entry in this data set.</li>
+ * <li>Conversation data set with the thread structure of the messages. Each message is linked
+ * to an entry in this data set.</li>
* </ul>
*
* To enable that the Bluetooth Message Access Server can detect the content provider implementing
- * this interface, the {@code provider} tag for the bluetooth related content provider must
+ * this interface, the {@code provider} tag for the Bluetooth related content provider must
* have an intent-filter like the following in the manifest:
* <pre class="prettyprint"><provider android:authorities="[PROVIDER AUTHORITY]"
android:exported="true"
@@ -66,8 +68,10 @@
* Provider interface that should be used as intent-filter action in the provider section
* of the manifest file.
*/
- public static final String PROVIDER_INTERFACE = "android.bluetooth.action.BLUETOOTH_MAP_PROVIDER";
-
+ public static final String PROVIDER_INTERFACE_EMAIL =
+ "android.bluetooth.action.BLUETOOTH_MAP_PROVIDER";
+ public static final String PROVIDER_INTERFACE_IM =
+ "android.bluetooth.action.BLUETOOTH_MAP_IM_PROVIDER";
/**
* The Bluetooth Message Access profile allows a remote BT-MAP client to trigger
* an update of a folder for a specific e-mail account, register for reception
@@ -98,6 +102,48 @@
public static final String EXTRA_UPDATE_FOLDER_ID = "UpdateFolderId";
/**
+ * The Bluetooth Message Access profile allows a remote BT-MAP Client to update
+ * the owners presence and chat state
+ *
+ * ContentProvider.call() is used for these purposes, and the METHOD_SET_OWNER_STATUS
+ * method name shall trigger a change in owner/users presence or chat properties for an
+ * account or conversation.
+ *
+ * This shall be a non blocking call simply setting the properties, and the change should
+ * be sent to the remote server/users, depending on what property is changed.
+ * Bundle extra parameter will carry following values:
+ * EXTRA_ACCOUNT_ID containing the account_id
+ * EXTRA_PRESENCE_STATE containing the presence state of the owner account
+ * EXTRA_PRESENCE_STATUS containing the presence status text from the owner
+ * EXTRA_LAST_ACTIVE containing the last activity time stamp of the owner account
+ * EXTRA_CHAT_STATE containing the chat state of a specific conversation
+ * EXTRA_CONVERSATION_ID containing the conversation that is changed
+ */
+ public static final String METHOD_SET_OWNER_STATUS = "SetOwnerStatus";
+ public static final String EXTRA_ACCOUNT_ID = "AccountId"; // Is this needed
+ public static final String EXTRA_PRESENCE_STATE = "PresenceState";
+ public static final String EXTRA_PRESENCE_STATUS = "PresenceStatus";
+ public static final String EXTRA_LAST_ACTIVE = "LastActive";
+ public static final String EXTRA_CHAT_STATE = "ChatState";
+ public static final String EXTRA_CONVERSATION_ID = "ConversationId";
+
+ /**
+ * The Bluetooth Message Access profile can inform the messaging application of the Bluetooth
+ * state, whether is is turned 'on' or 'off'
+ *
+ * ContentProvider.call() is used for these purposes, and the METHOD_SET_BLUETOOTH_STATE
+ * method name shall trigger a change in owner/users presence or chat properties for an
+ * account or conversation.
+ *
+ * This shall be a non blocking call simply setting the properties.
+ *
+ * Bundle extra parameter will carry following values:
+ * EXTRA_BLUETOOTH_STATE containing the state of the Bluetooth connectivity
+ */
+ public static final String METHOD_SET_BLUETOOTH_STATE = "SetBtState";
+ public static final String EXTRA_BLUETOOTH_STATE = "BluetoothState";
+
+ /**
* These column names are used as last path segment of the URI (getLastPathSegment()).
* Access to a specific row in the tables is done by using the where-clause, hence
* support for .../#id if not needed for the Email clients.
@@ -105,11 +151,13 @@
* content://ProviderAuthority/TABLE_ACCOUNT
* content://ProviderAuthority/account_id/TABLE_MESSAGE
* content://ProviderAuthority/account_id/TABLE_FOLDER
- */
+ * content://ProviderAuthority/account_id/TABLE_CONVERSATION
+ * content://ProviderAuthority/account_id/TABLE_CONVOCONTACT
+ **/
/**
* Build URI representing the given Accounts data-set in a
- * bluetooth provider. When queried, the direct URI for the account
+ * Bluetooth provider. When queried, the direct URI for the account
* with the given accountID is returned.
*/
public static Uri buildAccountUri(String authority) {
@@ -130,7 +178,7 @@
}
/**
* Build URI representing the entire Message table in a
- * bluetooth provider.
+ * Bluetooth provider.
*/
public static Uri buildMessageUri(String authority) {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
@@ -140,7 +188,7 @@
}
/**
* Build URI representing the given Message data-set in a
- * bluetooth provider. When queried, the URI for the Messages
+ * Bluetooth provider. When queried, the URI for the Messages
* with the given accountID is returned.
*/
public static Uri buildMessageUri(String authority, String accountId) {
@@ -152,7 +200,7 @@
}
/**
* Build URI representing the given Message data-set with specific messageId in a
- * bluetooth provider. When queried, the direct URI for the account
+ * Bluetooth provider. When queried, the direct URI for the account
* with the given accountID is returned.
*/
public static Uri buildMessageUriWithId(String authority, String accountId,String messageId) {
@@ -165,7 +213,7 @@
}
/**
* Build URI representing the given Message data-set in a
- * bluetooth provider. When queried, the direct URI for the account
+ * Bluetooth provider. When queried, the direct URI for the folder
* with the given accountID is returned.
*/
public static Uri buildFolderUri(String authority, String accountId) {
@@ -177,11 +225,66 @@
}
/**
+ * Build URI representing the given Message data-set in a
+ * Bluetooth provider. When queried, the direct URI for the conversation
+ * with the given accountID is returned.
+ */
+ public static Uri buildConversationUri(String authority, String accountId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority)
+ .appendPath(accountId)
+ .appendPath(TABLE_CONVERSATION)
+ .build();
+ }
+
+ /**
+ * Build URI representing the given Contact data-set in a
+ * Bluetooth provider. When queried, the direct URI for the contacts
+ * with the given accountID is returned.
+ */
+ public static Uri buildConvoContactsUri(String authority) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority)
+ .appendPath(TABLE_CONVOCONTACT)
+ .build();
+ }
+
+ /**
+ * Build URI representing the given Contact data-set in a
+ * Bluetooth provider. When queried, the direct URI for the contacts
+ * with the given accountID is returned.
+ */
+ public static Uri buildConvoContactsUri(String authority, String accountId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority)
+ .appendPath(accountId)
+ .appendPath(TABLE_CONVOCONTACT)
+ .build();
+ }
+ /**
+ * Build URI representing the given Contact data-set in a
+ * Bluetooth provider. When queried, the direct URI for the contact
+ * with the given contactID and accountID is returned.
+ */
+ public static Uri buildConvoContactsUriWithId(String authority, String accountId,
+ String contactId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority)
+ .appendPath(accountId)
+ .appendPath(TABLE_CONVOCONTACT)
+ .appendPath(contactId)
+ .build();
+ }
+ /**
* @hide
*/
- public static final String TABLE_ACCOUNT = "Account";
- public static final String TABLE_MESSAGE = "Message";
- public static final String TABLE_FOLDER = "Folder";
+ public static final String TABLE_ACCOUNT = "Account";
+ public static final String TABLE_MESSAGE = "Message";
+ public static final String TABLE_MESSAGE_PART = "Part";
+ public static final String TABLE_FOLDER = "Folder";
+ public static final String TABLE_CONVERSATION = "Conversation";
+ public static final String TABLE_CONVOCONTACT = "ConvoContact";
+
/**
* Mandatory folders for the Bluetooth message access profile.
@@ -189,11 +292,22 @@
* E.g. as a mapping for them such that the naming will match the underlying
* matching folder ID's.
*/
- public static final String FOLDER_NAME_INBOX = "inbox";
- public static final String FOLDER_NAME_OUTBOX = "outbox";
- public static final String FOLDER_NAME_SENT = "sent";
- public static final String FOLDER_NAME_DELETED = "deleted";
- public static final String FOLDER_NAME_DRAFT = "draft";
+ public static final String FOLDER_NAME_INBOX = "INBOX";
+ public static final String FOLDER_NAME_SENT = "SENT";
+ public static final String FOLDER_NAME_OUTBOX = "OUTBOX";
+ public static final String FOLDER_NAME_DRAFT = "DRAFT";
+ public static final String FOLDER_NAME_DELETED = "DELETED";
+ public static final String FOLDER_NAME_OTHER = "OTHER";
+
+ /**
+ * Folder IDs to be used with Instant Messaging virtual folders
+ */
+ public static final long FOLDER_ID_OTHER = 0;
+ public static final long FOLDER_ID_INBOX = 1;
+ public static final long FOLDER_ID_SENT = 2;
+ public static final long FOLDER_ID_DRAFT = 3;
+ public static final long FOLDER_ID_OUTBOX = 4;
+ public static final long FOLDER_ID_DELETED = 5;
/**
@@ -296,15 +410,107 @@
*
* This setting shall not be used to enforce whether or not an account should be shared
* or not if the account is bound by an administrative security policy. In this case
- * the email app should not list the account at all if it is not to be shareable over BT.
+ * the email app should not list the account at all if it is not to be sharable over BT.
*
* <P>Type: INTEGER (boolean) hide = 0, show = 1</P>
*/
public static final String FLAG_EXPOSE = "flag_expose";
+
+ /**
+ * The account unique identifier representing this account. For most IM clients this will be
+ * the fully qualified user name to which an invite message can be sent, from another use.
+ *
+ * e.g.: "map_test_user_12345@gmail.com" - for a Hangouts account
+ *
+ * This value will only be visible to authenticated Bluetooth devices, and will be
+ * transmitted using an encrypted link.
+ * <P>Type: TEXT</P>
+ * read-only
+ */
+ public static final String ACCOUNT_UCI = "account_uci";
+
+
+ /**
+ * The Bluetooth SIG maintains a list of assigned numbers(text strings) for IM clients.
+ * If your client/account has such a string, this is the place to return it.
+ * If supported by both devices, the presence of this prefix will make it possible to
+ * respond to a message by making a voice-call, using the same account information.
+ * (The call will be made using the HandsFree profile)
+ * https://www.bluetooth.org/en-us/specification/assigned-numbers/uniform-caller-identifiers
+ *
+ * e.g.: "hgus" - for Hangouts
+ *
+ * <P>Type: TEXT</P>
+ * read-only
+ */
+ public static final String ACCOUNT_UCI_PREFIX = "account_uci_PREFIX";
+
}
/**
+ * Message Data Parts Table
+ * The columns needed to contain the actual data of the messageparts in IM messages.
+ * Each "part" has its own row and represent a single mime-part in a multipart-mime
+ * formatted message.
+ *
+ */
+ public interface MessagePartColumns {
+
+ /**
+ * The unique ID for a row.
+ * <P>Type: INTEGER (long)</P>
+ * read-only
+ */
+ public static final String _ID = "_id";
+ // FIXME add message parts for IM attachments
+ /**
+ * is this a text part yes/no?
+ * <P>Type: TEXT</P>
+ * read-only
+ */
+ public static final String TEXT = "text";
+
+ /**
+ * The charset used in the content if it is text or 8BIT if it is
+ * binary data
+ *
+ * <P>Type: TEXT</P>
+ * read-only
+ */
+ public static final String CHARSET = "charset";
+
+ /**
+ * The filename representing the data file of the raw data in the database
+ * If this is empty, then it must be text and part of the message body.
+ * This is the name that the data will have when it is included as attachment
+ *
+ * <P>Type: TEXT</P>
+ * read-only
+ */
+
+ public static final String FILENAME = "filename";
+
+ /**
+ * Identifier for the content in the data. This can be used to
+ * refer directly to the data in the body part.
+ *
+ * <P>Type: TEXT</P>
+ * read-only
+ */
+
+ public static final String CONTENT_ID = "cid";
+
+ /**
+ * The raw data in either text format or binary format
+ *
+ * <P>Type: BLOB</P>
+ * read-only
+ */
+ public static final String RAW_DATA = "raw_data";
+
+ }
+ /**
* The actual message table containing all messages.
* Content that must support filtering using WHERE clauses:
* - To, From, Cc, Bcc, Date, ReadFlag, PriorityFlag, folder_id, account_id
@@ -319,7 +525,7 @@
* date etc) written through file-i/o takes precedence over the inserted values and should
* overwrite them.
*/
- public interface MessageColumns {
+ public interface MessageColumns extends EmailMessageColumns {
/**
* The unique ID for a row.
@@ -336,6 +542,14 @@
*/
public static final String DATE = "date";
+ //TODO REMOVE WHEN Parts Table is in place
+ /**
+ * Message body. Used by Instant Messaging
+ * <P>Type: TEXT</P>
+ * read-only.
+ */
+ public static final String BODY = "body";
+
/**
* Message subject.
* <P>Type: TEXT</P>
@@ -359,11 +573,18 @@
/**
* Reception state - the amount of the message that have been loaded from the server.
- * <P>Type: INTEGER see RECEPTION_STATE_ constants below </P>
+ * <P>Type: TEXT see RECEPTION_STATE_* constants below </P>
* read-only
*/
public static final String RECEPTION_STATE = "reception_state";
+ /**
+ * Delivery state - the amount of the message that have been loaded from the server.
+ * <P>Type: TEXT see DELIVERY_STATE_* constants below </P>
+ * read-only
+ */
+ public static final String DEVILERY_STATE = "delivery_state";
+
/** To be able to filter messages with attachments, we need this flag.
* <P>Type: INTEGER (boolean) no attachment = 0, attachment = 1 </P>
* read-only
@@ -375,6 +596,12 @@
*/
public static final String ATTACHMENT_SIZE = "attachment_size";
+ /** The mine type of the attachments for the message.
+ * <P>Type: TEXT </P>
+ * read-only
+ */
+ public static final String ATTACHMENT_MINE_TYPES = "attachment_mime_types";
+
/** The overall size in bytes of the message including any attachments.
* This value is informative only and should be the size an email client
* would display as size for the message.
@@ -406,6 +633,40 @@
public static final String TO_LIST = "to_list";
/**
+ * The unique ID for a row in the folder table in which this message belongs.
+ * <P>Type: INTEGER (long)</P>
+ * read/write
+ */
+ public static final String FOLDER_ID = "folder_id";
+
+ /**
+ * The unique ID for a row in the account table which owns this message.
+ * <P>Type: INTEGER (long)</P>
+ * read-only
+ */
+ public static final String ACCOUNT_ID = "account_id";
+
+ /**
+ * The ID identify the thread/conversation a message belongs to.
+ * If no thread id is available, set value to "-1"
+ * <P>Type: INTEGER (long)</P>
+ * read-only
+ */
+ public static final String THREAD_ID = "thread_id";
+
+ /**
+ * The Name of the thread/conversation a message belongs to.
+ * <P>Type: TEXT</P>
+ * read-only
+ */
+ public static final String THREAD_NAME = "thread_name";
+ }
+
+ public interface EmailMessageColumns {
+
+
+
+ /**
* A comma-delimited list of CC addresses in RFC2822 format.
* The list must be compatible with Rfc822Tokenizer.tokenize();
* <P>Type: TEXT</P>
@@ -429,30 +690,19 @@
*/
public static final String REPLY_TO_LIST = "reply_to_List";
- /**
- * The unique ID for a row in the folder table in which this message belongs.
- * <P>Type: INTEGER (long)</P>
- * read/write
- */
- public static final String FOLDER_ID = "folder_id";
- /**
- * The unique ID for a row in the account table which owns this message.
- * <P>Type: INTEGER (long)</P>
- * read-only
- */
- public static final String ACCOUNT_ID = "account_id";
-
- /**
- * The ID identify the thread a message belongs to. If no thread id is available,
- * set value to "-1"
- * <P>Type: INTEGER (long)</P>
- * read-only
- */
- public static final String THREAD_ID = "thread_id";
}
/**
+ * Indicates the complete message has been delivered to the recipient.
+ */
+ public static final String DELIVERY_STATE_DELIVERED = "delivered";
+ /**
+ * Indicates that the complete message has been sent from the MSE to the remote network.
+ */
+ public static final String DELIVERY_STATE_SENT = "sent";
+
+ /**
* Indicates that the message, including any attachments, has been received from the
* server to the device.
*/
@@ -510,6 +760,313 @@
*/
public static final String PARENT_FOLDER_ID = "parent_id";
}
+
+ /**
+ * Message conversation structure. Enables use of a conversation structure for messages across
+ * folders, further binding contacts to conversations.
+ * Content that must be supplied:
+ * - Name, LastActivity, ReadStatus, VersionCounter
+ * Content that must support update:
+ * - READ_STATUS, LAST_ACTIVITY and VERSION_COUNTER (VERSION_COUNTER used to validity of _ID)
+ * Additional insert of a new conversation with the following values shall be supported:
+ * - FOLDER_ID
+ * When querying this table, the cursor returned must contain one row for each contact member
+ * in a thread.
+ * For filter/search parameters attributes to the URI will be used. The following columns must
+ * support filtering:
+ * - ConvoContactColumns.NAME
+ * - ConversationColumns.THREAD_ID
+ * - ConversationColumns.LAST_ACTIVITY
+ * - ConversationColumns.READ_STATUS
+ */
+ public interface ConversationColumns extends ConvoContactColumns {
+
+ /**
+ * The unique ID for a row.
+ * <P>Type: INTEGER (long)</P>
+ * read-only
+ */
+// Should not be needed anymore public static final String _ID = "_id";
+
+ /**
+ * The unique ID for a Thread.
+ * <P>Type: INTEGER (long)</P>
+ * read-only
+ */
+ public static final String THREAD_ID = "thread_id";
+
+ /**
+ * The unique ID for a row.
+ * <P>Type: INTEGER (long)</P>
+ * read-only
+ */
+// TODO: IS THIS NECESSARY - or do we need the thread ID to hold thread Id from message
+// or can we be sure we are in control and can use the _ID and put that in the message DB
+ //public static final String THREAD_ID = "thread_id";
+
+ /**
+ * The type of conversation, see {@link ConversationType}
+ * <P>Type: TEXT</P>
+ * read-only
+ */
+// TODO: IS THIS NECESSARY - no conversation type is available in the latest,
+// guess it can be found from number of contacts in the conversation
+ //public static final String TYPE = "type";
+
+ /**
+ * The name of the conversation, e.g. group name in case of group chat
+ * <P>Type: TEXT</P>
+ * read-only
+ */
+ public static final String THREAD_NAME = "thread_name";
+
+ /**
+ * The time stamp of the last activity in the conversation as a unix timestamp
+ * (miliseconds since 00:00:00 UTC 1/1-1970)
+ * <P>Type: INTEGER (long)</P>
+ * read-only
+ */
+ public static final String LAST_THREAD_ACTIVITY = "last_thread_activity";
+
+ /**
+ * The status on the conversation, either 'read' or 'unread'
+ * <P>Type: INTEGER (boolean) unread = 0, read = 1</P>
+ * read/write
+ */
+ public static final String READ_STATUS = "read_status";
+
+ /**
+ * A counter that keep tack of version of the table content, count up on ID reuse
+ * <P>Type: INTEGER (long)</P>
+ * read-only
+ */
+// TODO: IS THIS NECESSARY - skal den ligge i databasen?
+ // CB: If we need it, it must be in the database, or initialized with a random value at
+ // BT-ON
+ // UPDATE: TODO: Change to the last_activity time stamp (as a long value). This will
+ // provide the information needed for BT clients - currently unused
+ public static final String VERSION_COUNTER = "version_counter";
+
+ /**
+ * A short description of the latest activity on conversation - typically
+ * part of the last message.
+ * <P>Type: TEXT</P>
+ * read-only
+ */
+ public static final String SUMMARY = "convo_summary";
+
+
+ }
+
+ /**
+ * MAP enables access to contacts for the conversation
+ * The conversation table must provide filtering (using WHERE clauses) of following entries:
+ * - convo_id linking contacts to conversations
+ * - x_bt_uid linking contacts to PBAP contacts
+ * The conversation contact table must have a convo_id and a name for each entry.
+ */
+ public interface ConvoContactColumns extends ChatStatusColumns, PresenceColumns {
+ /**
+ * The unique ID for a contact in Conversation
+ * <P>Type: INTEGER (long)</P>
+ * read-only
+ */
+// Should not be needed anymore public static final String _ID = "_id";
+
+ /**
+ * The ID of the conversation the contact is part of.
+ * <P>Type: INTEGER (long)</P>
+ * read-only
+ */
+ public static final String CONVO_ID = "convo_id";
+
+ /**
+ * The name of contact in instant message application
+ * <P>Type: TEXT</P>
+ * read-only
+ */
+ public static final String NAME = "name";
+
+ /**
+ * The nickname of contact in instant message group chat conversation.
+ * <P>Type: TEXT</P>
+ * read-only
+ */
+ public static final String NICKNAME = "nickname";
+
+
+ /**
+ * The unique ID for all Bluetooth contacts available through PBAP.
+ * <P>Type: INTEGER (long)</P>
+ * read-only
+ */
+ public static final String X_BT_UID = "x_bt_uid";
+
+ /**
+ * The unique ID for the contact within the domain of the interfacing service.
+ * (UCI: Unique Call Identity)
+ * It is expected that a message send to this ID will reach the recipient regardless
+ * through which interface the message is send.
+ * For E-mail this will be the e-mail address, for Google+ this will be the e-mail address
+ * associated with the contact account.
+ * This ID
+ * <P>Type: TEXT</P>
+ * read-only
+ */
+ public static final String UCI = "x_bt_uci";
+ }
+
+ /**
+ * The name of query parameter used to filter on recipient
+ */
+ public static final String FILTER_RECIPIENT_SUBSTRING = "rec_sub_str";
+
+ /**
+ * The name of query parameter used to filter on originator
+ */
+ public static final String FILTER_ORIGINATOR_SUBSTRING = "org_sub_str";
+
+ /**
+ * The name of query parameter used to filter on read status.
+ * - true - return only threads with all messages marked as read
+ * - false - return only threads with one or more unread messages
+ * - omitted as query parameter - do not filter on read status
+ */
+ public static final String FILTER_READ_STATUS = "read";
+
+ /**
+ * Time in ms since epoch. For conversations this will be for last activity
+ * as a unix timestamp (miliseconds since 00:00:00 UTC 1/1-1970)
+ */
+ public static final String FILTER_PERIOD_BEGIN = "t_begin";
+
+ /**
+ * Time in ms since epoch. For conversations this will be for last activity
+ * as a unix timestamp (miliseconds since 00:00:00 UTC 1/1-1970)
+ */
+ public static final String FILTER_PERIOD_END = "t_end";
+
+ /**
+ * Filter for a specific ThreadId
+ */
+ public static final String FILTER_THREAD_ID = "thread_id";
+
+
+ public interface ChatState {
+ int UNKNOWN = 0;
+ int INACITVE = 1;
+ int ACITVE = 2;
+ int COMPOSING = 3;
+ int PAUSED = 4;
+ int GONE = 5;
+ }
+
+ /**
+ * Instant Messaging contact chat state information
+ * MAP enables access to contacts chat state for the instant messaging application
+ * The chat state table must provide filtering (use of WHERE clauses) of the following entries:
+ * - contact_id (linking chat state to contacts)
+ * - thread_id (linking chat state to conversations and messages)
+ * The presence table must have a contact_id for each entry.
+ */
+ public interface ChatStatusColumns {
+
+// /**
+// * The contact ID of a instant messaging contact.
+// * <P>Type: TEXT </P>
+// * read-only
+// */
+// public static final String CONTACT_ID = "contact_id";
+//
+// /**
+// * The thread id for a conversation.
+// * <P>Type: INTEGER (long)</P>
+// * read-only
+// */
+// public static final String CONVO_ID = "convo_id";
+
+ /**
+ * The chat state of contact in conversation, see {@link ChatState}
+ * <P>Type: INTERGER</P>
+ * read-only
+ */
+ public static final String CHAT_STATE = "chat_state";
+
+// /**
+// * The geo location of the contact
+// * <P>Type: TEXT</P>
+// * read-only
+// */
+//// TODO: IS THIS NEEDED - not in latest specification
+// public static final String GEOLOC = "geoloc";
+
+ /**
+ * The time stamp of the last time this contact was active in the conversation
+ * <P>Type: INTEGER (long)</P>
+ * read-only
+ */
+ public static final String LAST_ACTIVE = "last_active";
+
+ }
+
+ public interface PresenceState {
+ int UNKNOWN = 0;
+ int OFFLINE = 1;
+ int ONLINE = 2;
+ int AWAY = 3;
+ int DO_NOT_DISTURB = 4;
+ int BUSY = 5;
+ int IN_A_MEETING = 6;
+ }
+
+ /**
+ * Instant Messaging contact presence information
+ * MAP enables access to contacts presences information for the instant messaging application
+ * The presence table must provide filtering (use of WHERE clauses) of the following entries:
+ * - contact_id (linking contacts to presence)
+ * The presence table must have a contact_id for each entry.
+ */
+ public interface PresenceColumns {
+
+// /**
+// * The contact ID of a instant messaging contact.
+// * <P>Type: TEXT </P>
+// * read-only
+// */
+// public static final String CONTACT_ID = "contact_id";
+
+ /**
+ * The presence state of contact, see {@link PresenceState}
+ * <P>Type: INTERGER</P>
+ * read-only
+ */
+ public static final String PRESENCE_STATE = "presence_state";
+
+ /**
+ * The priority of contact presence
+ * <P>Type: INTERGER</P>
+ * read-only
+ */
+// TODO: IS THIS NEEDED - not in latest specification
+ public static final String PRIORITY = "priority";
+
+ /**
+ * The last status text from contact
+ * <P>Type: TEXT</P>
+ * read-only
+ */
+ public static final String STATUS_TEXT = "status_text";
+
+ /**
+ * The time stamp of the last time the contact was online
+ * <P>Type: INTEGER (long)</P>
+ * read-only
+ */
+ public static final String LAST_ONLINE = "last_online";
+
+ }
+
+
/**
* A projection of all the columns in the Message table
*/
@@ -517,21 +1074,43 @@
MessageColumns._ID,
MessageColumns.DATE,
MessageColumns.SUBJECT,
- MessageColumns.FLAG_READ,
- MessageColumns.FLAG_ATTACHMENT,
+ //TODO REMOVE WHEN Parts Table is in place
+ MessageColumns.BODY,
+ MessageColumns.MESSAGE_SIZE,
MessageColumns.FOLDER_ID,
- MessageColumns.ACCOUNT_ID,
+ MessageColumns.FLAG_READ,
+ MessageColumns.FLAG_PROTECTED,
+ MessageColumns.FLAG_HIGH_PRIORITY,
+ MessageColumns.FLAG_ATTACHMENT,
+ MessageColumns.ATTACHMENT_SIZE,
MessageColumns.FROM_LIST,
MessageColumns.TO_LIST,
MessageColumns.CC_LIST,
MessageColumns.BCC_LIST,
MessageColumns.REPLY_TO_LIST,
+ MessageColumns.RECEPTION_STATE,
+ MessageColumns.DEVILERY_STATE,
+ MessageColumns.THREAD_ID
+ };
+
+ public static final String[] BT_INSTANT_MESSAGE_PROJECTION = new String[] {
+ MessageColumns._ID,
+ MessageColumns.DATE,
+ MessageColumns.SUBJECT,
+ MessageColumns.MESSAGE_SIZE,
+ MessageColumns.FOLDER_ID,
+ MessageColumns.FLAG_READ,
MessageColumns.FLAG_PROTECTED,
MessageColumns.FLAG_HIGH_PRIORITY,
- MessageColumns.MESSAGE_SIZE,
+ MessageColumns.FLAG_ATTACHMENT,
MessageColumns.ATTACHMENT_SIZE,
+ MessageColumns.ATTACHMENT_MINE_TYPES,
+ MessageColumns.FROM_LIST,
+ MessageColumns.TO_LIST,
MessageColumns.RECEPTION_STATE,
- MessageColumns.THREAD_ID
+ MessageColumns.DEVILERY_STATE,
+ MessageColumns.THREAD_ID,
+ MessageColumns.THREAD_NAME
};
/**
@@ -544,6 +1123,18 @@
};
/**
+ * A projection of all the columns in the Account table
+ * TODO: Is this the way to differentiate
+ */
+ public static final String[] BT_IM_ACCOUNT_PROJECTION = new String[] {
+ AccountColumns._ID,
+ AccountColumns.ACCOUNT_DISPLAY_NAME,
+ AccountColumns.FLAG_EXPOSE,
+ AccountColumns.ACCOUNT_UCI,
+ AccountColumns.ACCOUNT_UCI_PREFIX
+ };
+
+ /**
* A projection of all the columns in the Folder table
*/
public static final String[] BT_FOLDER_PROJECTION = new String[] {
@@ -554,4 +1145,74 @@
};
+ /**
+ * A projection of all the columns in the Conversation table
+ */
+ public static final String[] BT_CONVERSATION_PROJECTION = new String[] {
+ /* Thread information */
+ ConversationColumns.THREAD_ID,
+ ConversationColumns.THREAD_NAME,
+ ConversationColumns.READ_STATUS,
+ ConversationColumns.LAST_THREAD_ACTIVITY,
+ ConversationColumns.VERSION_COUNTER,
+ ConversationColumns.SUMMARY,
+ /* Contact information */
+ ConversationColumns.UCI,
+ ConversationColumns.NAME,
+ ConversationColumns.NICKNAME,
+ ConversationColumns.CHAT_STATE,
+ ConversationColumns.LAST_ACTIVE,
+ ConversationColumns.X_BT_UID,
+ ConversationColumns.PRESENCE_STATE,
+ ConversationColumns.STATUS_TEXT,
+ ConversationColumns.PRIORITY
+ };
+
+ /**
+ * A projection of the Contact Info and Presence columns in the Contact Info in table
+ */
+ public static final String[] BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION = new String[] {
+ ConvoContactColumns.UCI,
+ ConvoContactColumns.CONVO_ID,
+ ConvoContactColumns.NAME,
+ ConvoContactColumns.NICKNAME,
+ ConvoContactColumns.X_BT_UID,
+ ConvoContactColumns.CHAT_STATE,
+ ConvoContactColumns.LAST_ACTIVE,
+ ConvoContactColumns.PRESENCE_STATE,
+ ConvoContactColumns.PRIORITY,
+ ConvoContactColumns.STATUS_TEXT,
+ ConvoContactColumns.LAST_ONLINE
+ };
+
+ /**
+ * A projection of the Contact Info the columns in Contacts Info table
+ */
+ public static final String[] BT_CONTACT_PROJECTION = new String[] {
+ ConvoContactColumns.UCI,
+ ConvoContactColumns.CONVO_ID,
+ ConvoContactColumns.X_BT_UID,
+ ConvoContactColumns.NAME,
+ ConvoContactColumns.NICKNAME
+ };
+
+
+ /**
+ * A projection of all the columns in the Chat Status table
+ */
+ public static final String[] BT_CHATSTATUS_PROJECTION = new String[] {
+ ChatStatusColumns.CHAT_STATE,
+ ChatStatusColumns.LAST_ACTIVE,
+ };
+
+ /**
+ * A projection of all the columns in the Presence table
+ */
+ public static final String[] BT_PRESENCE_PROJECTION = new String[] {
+ PresenceColumns.PRESENCE_STATE,
+ PresenceColumns.PRIORITY,
+ PresenceColumns.STATUS_TEXT,
+ PresenceColumns.LAST_ONLINE
+ };
+
}
diff --git a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapIMProvider.java b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapIMProvider.java
new file mode 100644
index 0000000..d0c642c
--- /dev/null
+++ b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapIMProvider.java
@@ -0,0 +1,689 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.android.bluetooth.mapapi;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * A base implementation of the BluetoothMapContract.
+ * A base class for a ContentProvider that allows access to Instant messages from a Bluetooth
+ * device through the Message Access Profile.
+ */
+public abstract class BluetoothMapIMProvider extends ContentProvider {
+
+ private static final String TAG = "BluetoothMapIMProvider";
+ private static final boolean D = true;
+
+ private static final int MATCH_ACCOUNT = 1;
+ private static final int MATCH_MESSAGE = 3;
+ private static final int MATCH_CONVERSATION = 4;
+ private static final int MATCH_CONVOCONTACT = 5;
+
+ protected ContentResolver mResolver;
+
+ private Uri CONTENT_URI = null;
+ private String mAuthority;
+ private UriMatcher mMatcher;
+
+ /**
+ * @return the CONTENT_URI exposed. This will be used to send out notifications.
+ */
+ abstract protected Uri getContentUri();
+
+ /**
+ * Implementation is provided by the parent class.
+ */
+ @Override
+ public void attachInfo(Context context, ProviderInfo info) {
+ mAuthority = info.authority;
+
+ mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ mMatcher.addURI(mAuthority, BluetoothMapContract.TABLE_ACCOUNT, MATCH_ACCOUNT);
+ mMatcher.addURI(mAuthority, "#/"+ BluetoothMapContract.TABLE_MESSAGE, MATCH_MESSAGE);
+ mMatcher.addURI(mAuthority, "#/"+ BluetoothMapContract.TABLE_CONVERSATION,
+ MATCH_CONVERSATION);
+ mMatcher.addURI(mAuthority, "#/"+ BluetoothMapContract.TABLE_CONVOCONTACT,
+ MATCH_CONVOCONTACT);
+
+ // Sanity check our setup
+ if (!info.exported) {
+ throw new SecurityException("Provider must be exported");
+ }
+ // Enforce correct permissions are used
+ if (!android.Manifest.permission.BLUETOOTH_MAP.equals(info.writePermission)){
+ throw new SecurityException("Provider must be protected by " +
+ android.Manifest.permission.BLUETOOTH_MAP);
+ }
+ if(D) Log.d(TAG,"attachInfo() mAuthority = " + mAuthority);
+
+ mResolver = context.getContentResolver();
+ super.attachInfo(context, info);
+ }
+
+ /**
+ * This function shall be called when any Account database content have changed
+ * to Notify any attached observers.
+ * @param accountId the ID of the account that changed. Null is a valid value,
+ * if accountId is unknown or multiple accounts changed.
+ */
+ protected void onAccountChanged(String accountId) {
+ Uri newUri = null;
+
+ if(mAuthority == null){
+ return;
+ }
+ if(accountId == null){
+ newUri = BluetoothMapContract.buildAccountUri(mAuthority);
+ } else {
+ newUri = BluetoothMapContract.buildAccountUriwithId(mAuthority, accountId);
+ }
+
+ if(D) Log.d(TAG,"onAccountChanged() accountId = " + accountId + " URI: " + newUri);
+ mResolver.notifyChange(newUri, null);
+ }
+
+ /**
+ * This function shall be called when any Message database content have changed
+ * to notify any attached observers.
+ * @param accountId Null is a valid value, if accountId is unknown, but
+ * recommended for increased performance.
+ * @param messageId Null is a valid value, if multiple messages changed or the
+ * messageId is unknown, but recommended for increased performance.
+ */
+ protected void onMessageChanged(String accountId, String messageId) {
+ Uri newUri = null;
+
+ if(mAuthority == null){
+ return;
+ }
+ if(accountId == null){
+ newUri = BluetoothMapContract.buildMessageUri(mAuthority);
+ } else {
+ if(messageId == null)
+ {
+ newUri = BluetoothMapContract.buildMessageUri(mAuthority,accountId);
+ } else {
+ newUri = BluetoothMapContract.buildMessageUriWithId(mAuthority,accountId,
+ messageId);
+ }
+ }
+ if(D) Log.d(TAG,"onMessageChanged() accountId = " + accountId
+ + " messageId = " + messageId + " URI: " + newUri);
+ mResolver.notifyChange(newUri, null);
+ }
+
+
+ /**
+ * This function shall be called when any Message database content have changed
+ * to notify any attached observers.
+ * @param accountId Null is a valid value, if accountId is unknown, but
+ * recommended for increased performance.
+ * @param contactId Null is a valid value, if multiple contacts changed or the
+ * contactId is unknown, but recommended for increased performance.
+ */
+ protected void onContactChanged(String accountId, String contactId) {
+ Uri newUri = null;
+
+ if(mAuthority == null){
+ return;
+ }
+ if(accountId == null){
+ newUri = BluetoothMapContract.buildConvoContactsUri(mAuthority);
+ } else {
+ if(contactId == null)
+ {
+ newUri = BluetoothMapContract.buildConvoContactsUri(mAuthority,accountId);
+ } else {
+ newUri = BluetoothMapContract.buildConvoContactsUriWithId(mAuthority, accountId,
+ contactId);
+ }
+ }
+ if(D) Log.d(TAG,"onContactChanged() accountId = " + accountId
+ + " contactId = " + contactId + " URI: " + newUri);
+ mResolver.notifyChange(newUri, null);
+ }
+
+ /**
+ * Not used, this is just a dummy implementation.
+ * TODO: We might need to something intelligent here after introducing IM
+ */
+ @Override
+ public String getType(Uri uri) {
+ return "InstantMessage";
+ }
+
+ /**
+ * The MAP specification states that a delete request from MAP client is a folder shift to the
+ * 'deleted' folder.
+ * Only use case of delete() is when transparency is requested for push messages, then
+ * message should not remain in sent folder and therefore must be deleted
+ */
+ @Override
+ public int delete(Uri uri, String where, String[] selectionArgs) {
+ if (D) Log.d(TAG, "delete(): uri=" + uri.toString() );
+ int result = 0;
+
+ String table = uri.getPathSegments().get(1);
+ if(table == null)
+ throw new IllegalArgumentException("Table missing in URI");
+ // the id of the entry to be deleted from the database
+ String messageId = uri.getLastPathSegment();
+ if (messageId == null)
+ throw new IllegalArgumentException("Message ID missing in update values!");
+
+ String accountId = getAccountId(uri);
+ if (accountId == null)
+ throw new IllegalArgumentException("Account ID missing in update values!");
+
+ long callingId = Binder.clearCallingIdentity();
+ try {
+ if(table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
+ return deleteMessage(accountId, messageId);
+ } else {
+ if (D) Log.w(TAG, "Unknown table name: " + table);
+ return result;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ /**
+ * This function deletes a message.
+ * @param accountId the ID of the Account
+ * @param messageId the ID of the message to delete.
+ * @return the number of messages deleted - 0 if the message was not found.
+ */
+ abstract protected int deleteMessage(String accountId, String messageId);
+
+ /**
+ * Insert is used to add new messages to the data base.
+ * Insert message approach:
+ * - Insert an empty message to get an _id with only a folder_id
+ * - Open the _id for write
+ * - Write the message content
+ * (When the writer completes, this provider should do an update of the message)
+ */
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ String table = uri.getLastPathSegment();
+ if(table == null)
+ throw new IllegalArgumentException("Table missing in URI");
+
+ String accountId = getAccountId(uri);
+ if (accountId == null)
+ throw new IllegalArgumentException("Account ID missing in URI");
+
+ // TODO: validate values?
+
+ String id; // the id of the entry inserted into the database
+ long callingId = Binder.clearCallingIdentity();
+ Log.d(TAG, "insert(): uri=" + uri.toString() + " - getLastPathSegment() = " +
+ uri.getLastPathSegment());
+ try {
+ if(table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
+ id = insertMessage(accountId, values);
+ if(D) Log.i(TAG, "insert() ID: " + id);
+ return Uri.parse(uri.toString() + "/" + id);
+ } else {
+ Log.w(TAG, "Unknown table name: " + table);
+ return null;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+
+ /**
+ * Inserts an empty message into the Message data base in the specified folder.
+ * This is done before the actual message content is written by fileIO.
+ * @param accountId the ID of the account
+ * @param folderId the ID of the folder to create a new message in.
+ * @return the message id as a string
+ */
+ abstract protected String insertMessage(String accountId, ContentValues values);
+
+ /**
+ * Utility function to build a projection based on a projectionMap.
+ *
+ * "btColumnName" -> "imColumnName as btColumnName" for each entry.
+ *
+ * This supports SQL statements in the column name entry.
+ * @param projection
+ * @param projectionMap <string, string>
+ * @return the converted projection
+ */
+ protected String[] convertProjection(String[] projection, Map<String,String> projectionMap) {
+ String[] newProjection = new String[projection.length];
+ for(int i = 0; i < projection.length; i++) {
+ newProjection[i] = projectionMap.get(projection[i]) + " as " + projection[i];
+ }
+ return newProjection;
+ }
+
+ /**
+ * This query needs to map from the data used in the e-mail client to
+ * BluetoothMapContract type of data.
+ */
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ long callingId = Binder.clearCallingIdentity();
+ try {
+ String accountId = null;
+ if(D)Log.w(TAG, "query(): uri =" + mAuthority + " uri=" + uri.toString());
+
+ switch (mMatcher.match(uri)) {
+ case MATCH_ACCOUNT:
+ return queryAccount(projection, selection, selectionArgs, sortOrder);
+ case MATCH_MESSAGE:
+ // TODO: Extract account from URI
+ accountId = getAccountId(uri);
+ return queryMessage(accountId, projection, selection, selectionArgs, sortOrder);
+ case MATCH_CONVERSATION:
+ accountId = getAccountId(uri);
+ String value;
+ String searchString =
+ uri.getQueryParameter(BluetoothMapContract.FILTER_ORIGINATOR_SUBSTRING);
+ Long periodBegin = null;
+ value = uri.getQueryParameter(BluetoothMapContract.FILTER_PERIOD_BEGIN);
+ if(value != null) {
+ periodBegin = Long.parseLong(value);
+ }
+ Long periodEnd = null;
+ value = uri.getQueryParameter(BluetoothMapContract.FILTER_PERIOD_END);
+ if(value != null) {
+ periodEnd = Long.parseLong(value);
+ }
+ Boolean read = null;
+ value = uri.getQueryParameter(BluetoothMapContract.FILTER_READ_STATUS);
+ if(value != null) {
+ read = value.equalsIgnoreCase("true");
+ }
+ Long threadId = null;
+ value = uri.getQueryParameter(BluetoothMapContract.FILTER_THREAD_ID);
+ if(value != null) {
+ threadId = Long.parseLong(value);
+ }
+ return queryConversation(accountId, threadId, read, periodEnd, periodBegin,
+ searchString, projection, sortOrder);
+ case MATCH_CONVOCONTACT:
+ accountId = getAccountId(uri);
+ long contactId = 0;
+ return queryConvoContact(accountId, contactId, projection,
+ selection, selectionArgs, sortOrder);
+ default:
+ throw new UnsupportedOperationException("Unsupported Uri " + uri);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ /**
+ * Query account information.
+ * This function shall return only exposable e-mail accounts. Hence shall not
+ * return accounts that has policies suggesting not to be shared them.
+ * @param projection
+ * @param selection
+ * @param selectionArgs
+ * @param sortOrder
+ * @return a cursor to the accounts that are subject to exposure over BT.
+ */
+ abstract protected Cursor queryAccount(String[] projection, String selection,
+ String[] selectionArgs, String sortOrder);
+
+ /**
+ * For the message table the selection (where clause) can only include the following columns:
+ * date: less than, greater than and equals
+ * flagRead: = 1 or = 0
+ * flagPriority: = 1 or = 0
+ * folder_id: the ID of the folder only equals
+ * toList: partial name/address search
+ * fromList: partial name/address search
+ * Additionally the COUNT and OFFSET shall be supported.
+ * @param accountId the ID of the account
+ * @param projection
+ * @param selection
+ * @param selectionArgs
+ * @param sortOrder
+ * @return a cursor to query result
+ */
+ abstract protected Cursor queryMessage(String accountId, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder);
+
+ /**
+ * For the Conversation table the selection (where clause) can only include
+ * the following columns:
+ * _id: the ID of the conversation only equals
+ * name: partial name search
+ * last_activity: less than, greater than and equals
+ * version_counter: updated IDs are regenerated
+ * Additionally the COUNT and OFFSET shall be supported.
+ * @param accountId the ID of the account
+ * @param threadId the ID of the conversation
+ * @param projection
+ * @param selection
+ * @param selectionArgs
+ * @param sortOrder
+ * @return a cursor to query result
+ */
+// abstract protected Cursor queryConversation(Long threadId, String[] projection,
+// String selection, String[] selectionArgs, String sortOrder);
+
+ /**
+ * Query for conversations with contact information. The expected result is a cursor pointing
+ * to one row for each contact in a conversation.
+ * E.g.:
+ * ThreadId | ThreadName | ... | ContactName | ContactPrecence | ... |
+ * 1 | "Bowling" | ... | Hans | 1 | ... |
+ * 1 | "Bowling" | ... | Peter | 2 | ... |
+ * 2 | "" | ... | Peter | 2 | ... |
+ * 3 | "" | ... | Hans | 1 | ... |
+ *
+ * @param accountId the ID of the account
+ * @param threadId filter on a single threadId - null if no filtering is needed.
+ * @param read filter on a read status:
+ * null: no filtering on read is needed.
+ * true: return only threads that has NO unread messages.
+ * false: return only threads that has unread messages.
+ * @param periodEnd last_activity time stamp of the the newest thread to include in the
+ * result.
+ * @param periodBegin last_activity time stamp of the the oldest thread to include in the
+ * result.
+ * @param searchString if not null, include only threads that has contacts that matches the
+ * searchString as part of the contact name or nickName.
+ * @param projection A list of the columns that is needed in the result
+ * @param sortOrder the sort order
+ * @return a Cursor representing the query result.
+ */
+ abstract protected Cursor queryConversation(String accountId, Long threadId, Boolean read,
+ Long periodEnd, Long periodBegin, String searchString, String[] projection,
+ String sortOrder);
+
+ /**
+ * For the ConvoContact table the selection (where clause) can only include the
+ * following columns:
+ * _id: the ID of the contact only equals
+ * convo_id: id of conversation contact is part of
+ * name: partial name search
+ * x_bt_uid: the ID of the bt uid only equals
+ * chat_state: active, inactive, gone, composing, paused
+ * last_active: less than, greater than and equals
+ * presence_state: online, do_not_disturb, away, offline
+ * priority: level of priority 0 - 100
+ * last_online: less than, greater than and equals
+ * @param accountId the ID of the account
+ * @param contactId the ID of the contact
+ * @param projection
+ * @param selection
+ * @param selectionArgs
+ * @param sortOrder
+ * @return a cursor to query result
+ */
+ abstract protected Cursor queryConvoContact(String accountId, Long contactId,
+ String[] projection, String selection, String[] selectionArgs, String sortOrder);
+
+ /**
+ * update()
+ * Messages can be modified in the following cases:
+ * - the folder_key of a message - hence the message can be moved to a new folder,
+ * but the content cannot be modified.
+ * - the FLAG_READ state can be changed.
+ * Conversations can be modified in the following cases:
+ * - the read status - changing between read, unread
+ * - the last activity - the time stamp of last message sent of received in the conversation
+ * ConvoContacts can be modified in the following cases:
+ * - the chat_state - chat status of the contact in conversation
+ * - the last_active - the time stamp of last action in the conversation
+ * - the presence_state - the time stamp of last time contact online
+ * - the status - the status text of the contact available in a conversation
+ * - the last_online - the time stamp of last time contact online
+ * The selection statement will always be selection of a message ID, when updating a message,
+ * hence this function will be called multiple times if multiple messages must be updated
+ * due to the nature of the Bluetooth Message Access profile.
+ */
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+
+ String table = uri.getLastPathSegment();
+ if(table == null){
+ throw new IllegalArgumentException("Table missing in URI");
+ }
+ if(selection != null) {
+ throw new IllegalArgumentException("selection shall not be used, ContentValues " +
+ "shall contain the data");
+ }
+
+ long callingId = Binder.clearCallingIdentity();
+ if(D)Log.w(TAG, "update(): uri=" + uri.toString() + " - getLastPathSegment() = " +
+ uri.getLastPathSegment());
+ try {
+ if(table.equals(BluetoothMapContract.TABLE_ACCOUNT)) {
+ String accountId = values.getAsString(BluetoothMapContract.AccountColumns._ID);
+ if(accountId == null) {
+ throw new IllegalArgumentException("Account ID missing in update values!");
+ }
+ Integer exposeFlag = values.getAsInteger(
+ BluetoothMapContract.AccountColumns.FLAG_EXPOSE);
+ if(exposeFlag == null){
+ throw new IllegalArgumentException("Expose flag missing in update values!");
+ }
+ return updateAccount(accountId, exposeFlag);
+ } else if(table.equals(BluetoothMapContract.TABLE_FOLDER)) {
+ return 0; // We do not support changing folders
+ } else if(table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
+ String accountId = getAccountId(uri);
+ if(accountId == null) {
+ throw new IllegalArgumentException("Account ID missing in update values!");
+ }
+ Long messageId = values.getAsLong(BluetoothMapContract.MessageColumns._ID);
+ if(messageId == null) {
+ throw new IllegalArgumentException("Message ID missing in update values!");
+ }
+ Long folderId = values.getAsLong(BluetoothMapContract.MessageColumns.FOLDER_ID);
+ Boolean flagRead = values.getAsBoolean(
+ BluetoothMapContract.MessageColumns.FLAG_READ);
+ return updateMessage(accountId, messageId, folderId, flagRead);
+ } else if(table.equals(BluetoothMapContract.TABLE_CONVERSATION)) {
+ return 0; // We do not support changing conversation
+ } else if(table.equals(BluetoothMapContract.TABLE_CONVOCONTACT)) {
+ return 0; // We do not support changing contacts
+ } else {
+ if(D)Log.w(TAG, "Unknown table name: " + table);
+ return 0;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ /**
+ * Update an entry in the account table. Only the expose flag will be
+ * changed through this interface.
+ * @param accountId the ID of the account to change.
+ * @param flagExpose the updated value.
+ * @return the number of entries changed - 0 if account not found or value cannot be changed.
+ */
+ abstract protected int updateAccount(String accountId, Integer flagExpose);
+
+ /**
+ * Update an entry in the message table.
+ * @param accountId ID of the account to which the messageId relates
+ * @param messageId the ID of the message to update
+ * @param folderId the new folder ID value to set - ignore if null.
+ * @param flagRead the new flagRead value to set - ignore if null.
+ * @return
+ */
+ abstract protected int updateMessage(String accountId, Long messageId, Long folderId,
+ Boolean flagRead);
+
+ /**
+ * Utility function to Creates a ContentValues object based on a modified valuesSet.
+ * To be used after changing the keys and optionally values of a valueSet obtained
+ * from a ContentValues object received in update().
+ * @param valueSet the values as received in the contentProvider
+ * @param keyMap the key map <btKey, emailKey>
+ * @return a new ContentValues object with the keys replaced as specified in the
+ * keyMap
+ */
+ protected ContentValues createContentValues(Set<Entry<String,Object>> valueSet,
+ Map<String, String> keyMap) {
+ ContentValues values = new ContentValues(valueSet.size());
+ for(Entry<String,Object> ent : valueSet) {
+ String key = keyMap.get(ent.getKey()); // Convert the key name
+ Object value = ent.getValue();
+ if(value == null) {
+ values.putNull(key);
+ } else if(ent.getValue() instanceof Boolean) {
+ values.put(key, (Boolean) value);
+ } else if(ent.getValue() instanceof Byte) {
+ values.put(key, (Byte) value);
+ } else if(ent.getValue() instanceof byte[]) {
+ values.put(key, (byte[]) value);
+ } else if(ent.getValue() instanceof Double) {
+ values.put(key, (Double) value);
+ } else if(ent.getValue() instanceof Float) {
+ values.put(key, (Float) value);
+ } else if(ent.getValue() instanceof Integer) {
+ values.put(key, (Integer) value);
+ } else if(ent.getValue() instanceof Long) {
+ values.put(key, (Long) value);
+ } else if(ent.getValue() instanceof Short) {
+ values.put(key, (Short) value);
+ } else if(ent.getValue() instanceof String) {
+ values.put(key, (String) value);
+ } else {
+ throw new IllegalArgumentException("Unknown data type in content value");
+ }
+ }
+ return values;
+ }
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ long callingId = Binder.clearCallingIdentity();
+ if(D)Log.w(TAG, "call(): method=" + method + " arg=" + arg + "ThreadId: "
+ + Thread.currentThread().getId());
+ int ret = -1;
+ try {
+ if(method.equals(BluetoothMapContract.METHOD_UPDATE_FOLDER)) {
+ long accountId = extras.getLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, -1);
+ if(accountId == -1) {
+ Log.w(TAG, "No account ID in CALL");
+ return null;
+ }
+ long folderId = extras.getLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID, -1);
+ if(folderId == -1) {
+ Log.w(TAG, "No folder ID in CALL");
+ return null;
+ }
+ ret = syncFolder(accountId, folderId);
+ } else if (method.equals(BluetoothMapContract.METHOD_SET_OWNER_STATUS)) {
+ int presenceState = extras.getInt(BluetoothMapContract.EXTRA_PRESENCE_STATE);
+ String presenceStatus = extras.getString(
+ BluetoothMapContract.EXTRA_PRESENCE_STATUS);
+ long lastActive = extras.getLong(BluetoothMapContract.EXTRA_LAST_ACTIVE);
+ int chatState = extras.getInt(BluetoothMapContract.EXTRA_CHAT_STATE);
+ String convoId = extras.getString(BluetoothMapContract.EXTRA_CONVERSATION_ID);
+ ret = setOwnerStatus(presenceState, presenceStatus, lastActive, chatState, convoId);
+
+ } else if (method.equals(BluetoothMapContract.METHOD_SET_BLUETOOTH_STATE)) {
+ boolean bluetoothState = extras.getBoolean(
+ BluetoothMapContract.EXTRA_BLUETOOTH_STATE);
+ ret = setBluetoothStatus(bluetoothState);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ if(ret == 0) {
+ return new Bundle();
+ }
+ return null;
+ }
+
+ /**
+ * Trigger a sync of the specified folder.
+ * @param accountId the ID of the account that owns the folder
+ * @param folderId the ID of the folder.
+ * @return 0 at success
+ */
+ abstract protected int syncFolder(long accountId, long folderId);
+
+ /**
+ * Set the properties that should change presence or chat state of owner
+ * e.g. when the owner is active on a BT client device but not on the BT server device
+ * where the IM application is installed, it should still be possible to show an active status.
+ * @param presenceState should follow the contract specified values
+ * @param presenceStatus string the owners current status
+ * @param lastActive time stamp of the owners last activity
+ * @param chatState should follow the contract specified values
+ * @param convoId ID to the conversation to change
+ * @return 0 at success
+ */
+ abstract protected int setOwnerStatus(int presenceState, String presenceStatus,
+ long lastActive, int chatState, String convoId);
+
+ /**
+ * Notify the application of the Bluetooth state
+ * @param bluetoothState 'on' of 'off'
+ * @return 0 at success
+ */
+ abstract protected int setBluetoothStatus(boolean bluetoothState);
+
+
+
+ /**
+ * Need this to suppress warning in unit tests.
+ */
+ @Override
+ public void shutdown() {
+ // Don't call super.shutdown(), which emits a warning...
+ }
+
+ /**
+ * Extract the BluetoothMapContract.AccountColumns._ID from the given URI.
+ */
+ public static String getAccountId(Uri uri) {
+ final List<String> segments = uri.getPathSegments();
+ if (segments.size() < 1) {
+ throw new IllegalArgumentException("No AccountId pressent in URI: " + uri);
+ }
+ return segments.get(0);
+ }
+}
diff --git a/res/layout/bluetooth_map_email_settings.xml b/res/layout/bluetooth_map_settings.xml
similarity index 78%
rename from res/layout/bluetooth_map_email_settings.xml
rename to res/layout/bluetooth_map_settings.xml
index 02acdd8..f256a02 100644
--- a/res/layout/bluetooth_map_email_settings.xml
+++ b/res/layout/bluetooth_map_settings.xml
@@ -17,21 +17,21 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/bluetooth_map_email_settings_liniar_layout"
+ android:id="@+id/bluetooth_map_settings_liniar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- tools:context=".BluetoothMapEmailSettings" >
+ tools:context=".BluetoothMapSettings" >
<TextView
- android:id="@+id/bluetooth_map_email_settings_description_text_view"
+ android:id="@+id/bluetooth_map_settings_description_text_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:text="@string/bluetooth_map_email_settings_intro" />
+ android:text="@string/bluetooth_map_settings_intro" />
<ExpandableListView
- android:id="@+id/bluetooth_map_email_settings_list_view"
+ android:id="@+id/bluetooth_map_settings_list_view"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="3"
diff --git a/res/layout/bluetooth_map_email_settings_account_group.xml b/res/layout/bluetooth_map_settings_account_group.xml
similarity index 83%
rename from res/layout/bluetooth_map_email_settings_account_group.xml
rename to res/layout/bluetooth_map_settings_account_group.xml
index abb8de8..7bbfca4 100644
--- a/res/layout/bluetooth_map_email_settings_account_group.xml
+++ b/res/layout/bluetooth_map_settings_account_group.xml
@@ -21,7 +21,7 @@
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
- android:id="@+id/bluetooth_map_email_settings_group_icon"
+ android:id="@+id/bluetooth_map_settings_group_icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_alignParentLeft="true"
@@ -31,12 +31,12 @@
android:layout_marginLeft="30dp"
/>
<TextView
- android:id ="@+id/bluetooth_map_email_settings_group_text_view"
+ android:id ="@+id/bluetooth_map_settings_group_text_view"
android:layout_width ="wrap_content"
android:layout_height ="wrap_content"
- android:layout_toRightOf="@id/bluetooth_map_email_settings_group_icon"/>
+ android:layout_toRightOf="@id/bluetooth_map_settings_group_icon"/>
<CheckBox
- android:id ="@+id/bluetooth_map_email_settings_group_checkbox"
+ android:id ="@+id/bluetooth_map_settings_group_checkbox"
android:layout_width ="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
diff --git a/res/layout/bluetooth_map_email_settings_account_item.xml b/res/layout/bluetooth_map_settings_account_item.xml
similarity index 86%
rename from res/layout/bluetooth_map_email_settings_account_item.xml
rename to res/layout/bluetooth_map_settings_account_item.xml
index f0469d5..b6fbf8a 100644
--- a/res/layout/bluetooth_map_email_settings_account_item.xml
+++ b/res/layout/bluetooth_map_settings_account_item.xml
@@ -24,17 +24,17 @@
android:orientation="horizontal"
android:paddingLeft="51dp"
android:paddingRight="25dp"
- tools:context=".BluetoothMapEmailSettings" android:baselineAligned="false">
+ tools:context=".BluetoothMapSettings" android:baselineAligned="false">
<TextView
- android:id="@+id/bluetooth_map_email_settings_item_text_view"
+ android:id="@+id/bluetooth_map_settings_item_text_view"
android:layout_width="match_parent"
android:layout_height="51dp"
android:layout_weight="1"
android:gravity="left|center_vertical"/>
<CheckBox
- android:id="@+id/bluetooth_map_email_settings_item_check"
+ android:id="@+id/bluetooth_map_settings_item_check"
android:layout_width="110dp"
android:layout_height="51dp"
android:layout_gravity="right"
diff --git a/res/layout/bluetooth_transfer_item.xml b/res/layout/bluetooth_transfer_item.xml
index ffc1052..520763b 100644
--- a/res/layout/bluetooth_transfer_item.xml
+++ b/res/layout/bluetooth_transfer_item.xml
@@ -27,7 +27,7 @@
android:layout_height="@android:dimen/app_icon_size"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
- android:contentDescription ="@string/bluetooth_map_email_settings_app_icon"
+ android:contentDescription ="@string/bluetooth_map_settings_app_icon"
android:scaleType="center"
/>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f1e2ca1..df9407d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -231,11 +231,11 @@
<string name="process" translate="false"><xliff:g id="x" /></string>
- <string name="bluetooth_map_email_settings_save">Save</string>
- <string name="bluetooth_map_email_settings_cancel">Cancel</string>
- <string name="bluetooth_map_email_settings_intro">Select the email accounts you want to share through Bluetooth. You still have to accept any acceess to the accounts when connecting.</string>
- <string name="bluetooth_map_email_settings_count">Slots left:</string>
- <string name="bluetooth_map_email_settings_app_icon">Application Icon</string>
- <string name="bluetooth_map_email_settings_title">Bluetooth Message Sharing Settings</string>
- <string name="bluetooth_map_email_settings_no_account_slots_left">Cannot select account. 0 slots left</string>
+ <string name="bluetooth_map_settings_save">Save</string>
+ <string name="bluetooth_map_settings_cancel">Cancel</string>
+ <string name="bluetooth_map_settings_intro">Select the accounts you want to share through Bluetooth. You still have to accept any acceess to the accounts when connecting.</string>
+ <string name="bluetooth_map_settings_count">Slots left:</string>
+ <string name="bluetooth_map_settings_app_icon">Application Icon</string>
+ <string name="bluetooth_map_settings_title">Bluetooth Message Sharing Settings</string>
+ <string name="bluetooth_map_settings_no_account_slots_left">Cannot select account. 0 slots left</string>
</resources>
diff --git a/src/com/android/bluetooth/BluetoothObexTransport.java b/src/com/android/bluetooth/BluetoothObexTransport.java
index 4ed5141..216ced2 100644
--- a/src/com/android/bluetooth/BluetoothObexTransport.java
+++ b/src/com/android/bluetooth/BluetoothObexTransport.java
@@ -73,14 +73,14 @@
}
public int getMaxTransmitPacketSize() {
- if(mSocket.getConnectionType() != BluetoothSocket.TYPE_L2CAP) {
- return -1;
+ if (mSocket.getConnectionType() != BluetoothSocket.TYPE_L2CAP) {
+ return -1;
}
return mSocket.getMaxTransmitPacketSize();
}
public int getMaxReceivePacketSize() {
- if(mSocket.getConnectionType() != BluetoothSocket.TYPE_L2CAP) {
+ if (mSocket.getConnectionType() != BluetoothSocket.TYPE_L2CAP) {
return -1;
}
return mSocket.getMaxReceivePacketSize();
diff --git a/src/com/android/bluetooth/SignedLongLong.java b/src/com/android/bluetooth/SignedLongLong.java
new file mode 100644
index 0000000..42fd4a8
--- /dev/null
+++ b/src/com/android/bluetooth/SignedLongLong.java
@@ -0,0 +1,126 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+
+package com.android.bluetooth;
+
+import java.io.UnsupportedEncodingException;
+
+import com.android.bluetooth.map.BluetoothMapUtils;
+
+/**
+ * Class to represent a 128bit value using two long member variables.
+ * Has functionality to convert to/from hex-strings.
+ * Mind that since signed variables are used to store the value internally
+ * is used, the MSB/LSB long values can be negative.
+ */
+public class SignedLongLong implements Comparable<SignedLongLong> {
+
+ private long mMostSigBits;
+ private long mLeastSigBits;
+
+ public SignedLongLong(long leastSigBits, long mostSigBits) {
+ this.mMostSigBits = mostSigBits;
+ this.mLeastSigBits = leastSigBits;
+ }
+
+ /**
+ * Create a SignedLongLong from a Hex-String without "0x" prefix
+ * @param value the hex-string
+ * @return the created object
+ * @throws UnsupportedEncodingException
+ */
+ public static SignedLongLong fromString(String value) throws UnsupportedEncodingException {
+ String lsbStr, msbStr;
+ long lsb = 0, msb = 0;
+
+ lsbStr = msbStr = null;
+ if(value == null) throw new NullPointerException();
+ value=value.trim();
+ int valueLength = value.length();
+ if(valueLength == 0 || valueLength > 32) {
+ throw new NumberFormatException("invalid string length: " + valueLength);
+ }
+ if(valueLength <= 16){
+ lsbStr = value;
+ } else {
+ lsbStr = value.substring(valueLength-16, valueLength);
+ msbStr = value.substring(0, valueLength-16);
+ msb = BluetoothMapUtils.getLongFromString(msbStr);
+ }
+ lsb = BluetoothMapUtils.getLongFromString(lsbStr);
+ return new SignedLongLong(lsb, msb);
+ }
+
+ @Override
+ public int compareTo(SignedLongLong another) {
+ if(mMostSigBits == another.mMostSigBits) {
+ if(mLeastSigBits == another.mLeastSigBits) {
+ return 0;
+ }
+ if(mLeastSigBits < another.mLeastSigBits) {
+ return -1;
+ }
+ return 1;
+ }
+ if(mMostSigBits < another.mMostSigBits) {
+ return -1;
+ }
+ return 1;
+ }
+
+ @Override
+ public String toString() {
+ return toHexString();
+ }
+
+ /**
+ *
+ * @return a hex-string representation of the object values
+ */
+ public String toHexString(){
+ return BluetoothMapUtils.getLongLongAsString(mLeastSigBits, mMostSigBits);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ SignedLongLong other = (SignedLongLong) obj;
+ if (mLeastSigBits != other.mLeastSigBits) {
+ return false;
+ }
+ if (mMostSigBits != other.mMostSigBits) {
+ return false;
+ }
+ return true;
+ }
+
+ public long getMostSignificantBits() {
+ return mMostSigBits;
+ }
+
+ public long getLeastSignificantBits() {
+ return mLeastSigBits;
+ }
+
+}
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index 3363855..af160c1 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -174,7 +174,8 @@
private boolean mNativeAvailable;
private boolean mCleaningUp;
private HashMap<String,Integer> mProfileServicesState = new HashMap<String,Integer>();
- private RemoteCallbackList<IBluetoothCallback> mCallbacks;//Only BluetoothManagerService should be registered
+ //Only BluetoothManagerService should be registered
+ private RemoteCallbackList<IBluetoothCallback> mCallbacks;
private int mCurrentRequestId;
private boolean mQuietmode = false;
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index dd752df..e49b486 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -239,7 +239,7 @@
type = types[j];
val = values[j];
if(val.length <= 0)
- errorLog("devicePropertyChangedCallback: bdDevice: " + bdDevice
+ errorLog("devicePropertyChangedCallback: bdDevice: " + bdDevice
+ ", value is empty for type: " + type);
else {
synchronized(mObject) {
diff --git a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsItem.java b/src/com/android/bluetooth/map/BluetoothMapAccountItem.java
similarity index 63%
rename from src/com/android/bluetooth/map/BluetoothMapEmailSettingsItem.java
rename to src/com/android/bluetooth/map/BluetoothMapAccountItem.java
index ec1e148..1a493b8 100644
--- a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsItem.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAccountItem.java
@@ -23,30 +23,50 @@
* It can be used for both Email Apps (group Parent item) and Accounts (Group child Item).
*
*/
-public class BluetoothMapEmailSettingsItem implements Comparable<BluetoothMapEmailSettingsItem>{
- private static final String TAG = "BluetoothMapEmailSettingsItem";
+public class BluetoothMapAccountItem implements Comparable<BluetoothMapAccountItem>{
+ private static final String TAG = "BluetoothMapAccountItem";
private static final boolean D = BluetoothMapService.DEBUG;
private static final boolean V = BluetoothMapService.VERBOSE;
protected boolean mIsChecked;
- private String mName;
- private String mPackageName;
- private String mId;
- private String mProviderAuthority;
- private Drawable mIcon;
- public String mBase_uri;
- public String mBase_uri_no_account;
- public BluetoothMapEmailSettingsItem(String id, String name, String packageName, String authority, Drawable icon) {
+ private final String mName;
+ private final String mPackageName;
+ private final String mId;
+ private final String mProviderAuthority;
+ private final Drawable mIcon;
+ private final BluetoothMapUtils.TYPE mType;
+ public final String mBase_uri;
+ public final String mBase_uri_no_account;
+ private final String mUci;
+ private final String mUciPrefix;
+
+ public BluetoothMapAccountItem(String id, String name, String packageName, String authority,
+ Drawable icon, BluetoothMapUtils.TYPE appType, String uci, String uciPrefix) {
this.mName = name;
this.mIcon = icon;
this.mPackageName = packageName;
this.mId = id;
this.mProviderAuthority = authority;
+ this.mType = appType;
this.mBase_uri_no_account = "content://" + authority;
this.mBase_uri = mBase_uri_no_account + "/"+id;
+ this.mUci = uci;
+ this.mUciPrefix = uciPrefix;
}
+ public static BluetoothMapAccountItem create(String id, String name, String packageName,
+ String authority, Drawable icon, BluetoothMapUtils.TYPE appType) {
+ return new BluetoothMapAccountItem(id, name, packageName, authority,
+ icon, appType, null, null);
+ }
+
+ public static BluetoothMapAccountItem create(String id, String name, String packageName,
+ String authority, Drawable icon, BluetoothMapUtils.TYPE appType, String uci,
+ String uciPrefix) {
+ return new BluetoothMapAccountItem(id, name, packageName, authority,
+ icon, appType, uci, uciPrefix);
+ }
public long getAccountId() {
if(mId != null) {
return Long.parseLong(mId);
@@ -54,8 +74,24 @@
return -1;
}
+ public String getUci() {
+ return mUci;
+ }
+
+ public String getUciPrefix(){
+ return mUciPrefix;
+ }
+
+ public String getUciFull(){
+ if(mUci == null)
+ return null;
+ if(mUciPrefix == null)
+ return null;
+ return new StringBuilder(mUciPrefix).append(":").append(mUci).toString();
+ }
+
@Override
- public int compareTo(BluetoothMapEmailSettingsItem other) {
+ public int compareTo(BluetoothMapAccountItem other) {
if(!other.mId.equals(this.mId)){
if(V) Log.d(TAG, "Wrong id : " + this.mId + " vs " + other.mId);
@@ -66,17 +102,23 @@
return -1;
}
if(!other.mPackageName.equals(this.mPackageName)){
- if(V) Log.d(TAG, "Wrong packageName : " + this.mPackageName + " vs " + other.mPackageName);
+ if(V) Log.d(TAG, "Wrong packageName : " + this.mPackageName + " vs "
+ + other.mPackageName);
return -1;
}
if(!other.mProviderAuthority.equals(this.mProviderAuthority)){
- if(V) Log.d(TAG, "Wrong providerName : " + this.mProviderAuthority + " vs " + other.mProviderAuthority);
+ if(V) Log.d(TAG, "Wrong providerName : " + this.mProviderAuthority + " vs "
+ + other.mProviderAuthority);
return -1;
}
if(other.mIsChecked != this.mIsChecked){
if(V) Log.d(TAG, "Wrong isChecked : " + this.mIsChecked + " vs " + other.mIsChecked);
return -1;
}
+ if(!other.mType.equals(this.mType)){
+ if(V) Log.d(TAG, "Wrong appType : " + this.mType + " vs " + other.mType);
+ return -1;
+ }
return 0;
}
@@ -101,7 +143,7 @@
return false;
if (getClass() != obj.getClass())
return false;
- BluetoothMapEmailSettingsItem other = (BluetoothMapEmailSettingsItem) obj;
+ BluetoothMapAccountItem other = (BluetoothMapAccountItem) obj;
if (mId == null) {
if (other.mId != null)
return false;
@@ -122,6 +164,11 @@
return false;
} else if (!mProviderAuthority.equals(other.mProviderAuthority))
return false;
+ if (mType == null) {
+ if (other.mType != null)
+ return false;
+ } else if (!mType.equals(other.mType))
+ return false;
return true;
}
@@ -134,39 +181,24 @@
return mIcon;
}
- public void setIcon(Drawable icon) {
- this.mIcon = icon;
- }
-
public String getName() {
return mName;
}
- public void setName(String name) {
- this.mName = name;
- }
-
public String getId() {
return mId;
}
- public void setId(String id) {
- this.mId = id;
- }
-
public String getPackageName() {
return mPackageName;
}
- public void setPackageName(String packageName) {
- this.mPackageName = packageName;
- }
-
public String getProviderAuthority() {
return mProviderAuthority;
}
- public void setProviderAuthority(String providerAuthority) {
- this.mProviderAuthority = providerAuthority;
+ public BluetoothMapUtils.TYPE getType() {
+ return mType;
}
-}
\ No newline at end of file
+
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java b/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
new file mode 100644
index 0000000..5e14387
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
@@ -0,0 +1,246 @@
+/*
+* Copyright (C) 2014 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.android.bluetooth.map;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import com.android.bluetooth.map.BluetoothMapAccountItem;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+
+
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+import android.text.format.DateUtils;
+import android.util.Log;
+
+
+public class BluetoothMapAccountLoader {
+ private static final String TAG = "BluetoothMapAccountLoader";
+ private static final boolean D = BluetoothMapService.DEBUG;
+ private static final boolean V = BluetoothMapService.VERBOSE;
+ private Context mContext = null;
+ private PackageManager mPackageManager = null;
+ private ContentResolver mResolver;
+ private int mAccountsEnabledCount = 0;
+ private ContentProviderClient mProviderClient = null;
+ private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
+
+ public BluetoothMapAccountLoader(Context ctx)
+ {
+ mContext = ctx;
+ }
+
+ /**
+ * Method to look through all installed packages system-wide and find those that contain one of
+ * the BT-MAP intents in their manifest file. For each app the list of accounts are fetched
+ * using the method parseAccounts().
+ * @return LinkedHashMap with the packages as keys(BluetoothMapAccountItem) and
+ * values as ArrayLists of BluetoothMapAccountItems.
+ */
+ public LinkedHashMap<BluetoothMapAccountItem,
+ ArrayList<BluetoothMapAccountItem>> parsePackages(boolean includeIcon) {
+
+ LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> groups =
+ new LinkedHashMap<BluetoothMapAccountItem,
+ ArrayList<BluetoothMapAccountItem>>();
+ Intent[] searchIntents = new Intent[2];
+ //Array <Intent> searchIntents = new Array <Intent>();
+ searchIntents[0] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_EMAIL);
+ searchIntents[1] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_IM);
+ // reset the counter every time this method is called.
+ mAccountsEnabledCount=0;
+ // find all installed packages and filter out those that do not support Bluetooth Map.
+ // this is done by looking for a apps with content providers containing the intent-filter
+ // in the manifest file.
+ mPackageManager = mContext.getPackageManager();
+
+ for (Intent searchIntent : searchIntents) {
+ List<ResolveInfo> resInfos =
+ mPackageManager.queryIntentContentProviders(searchIntent, 0);
+ if (resInfos != null ) {
+ if(D) Log.d(TAG,"Found " + resInfos.size() + " application(s) with intent "
+ + searchIntent.getAction().toString());
+ BluetoothMapUtils.TYPE msgType = (searchIntent.getAction().toString() ==
+ BluetoothMapContract.PROVIDER_INTERFACE_EMAIL) ?
+ BluetoothMapUtils.TYPE.EMAIL : BluetoothMapUtils.TYPE.IM;
+ for (ResolveInfo rInfo : resInfos) {
+ if(D) Log.d(TAG,"ResolveInfo " + rInfo.toString());
+ // We cannot rely on apps that have been force-stopped in the
+ // application settings menu.
+ if ((rInfo.providerInfo.applicationInfo.flags &
+ ApplicationInfo.FLAG_STOPPED) == 0) {
+ BluetoothMapAccountItem app = createAppItem(rInfo, includeIcon, msgType);
+ if (app != null){
+ ArrayList<BluetoothMapAccountItem> accounts = parseAccounts(app);
+ // we do not want to list apps without accounts
+ if(accounts.size() > 0)
+ {// we need to make sure that the "select all" checkbox
+ // is checked if all accounts in the list are checked
+ app.mIsChecked = true;
+ for (BluetoothMapAccountItem acc: accounts)
+ {
+ if(!acc.mIsChecked)
+ {
+ app.mIsChecked = false;
+ break;
+ }
+ }
+ groups.put(app, accounts);
+ }
+ }
+ } else {
+ if(D)Log.d(TAG,"Ignoring force-stopped authority "
+ + rInfo.providerInfo.authority +"\n");
+ }
+ }
+ }
+ else {
+ if(D) Log.d(TAG,"Found no applications");
+ }
+ }
+ return groups;
+ }
+
+ public BluetoothMapAccountItem createAppItem(ResolveInfo rInfo, boolean includeIcon,
+ BluetoothMapUtils.TYPE type) {
+ String provider = rInfo.providerInfo.authority;
+ if(provider != null) {
+ String name = rInfo.loadLabel(mPackageManager).toString();
+ if(D)Log.d(TAG,rInfo.providerInfo.packageName + " - " + name +
+ " - meta-data(provider = " + provider+")\n");
+ BluetoothMapAccountItem app = BluetoothMapAccountItem.create(
+ "0",
+ name,
+ rInfo.providerInfo.packageName,
+ provider,
+ (includeIcon == false)? null : rInfo.loadIcon(mPackageManager),
+ type);
+ return app;
+ }
+
+ return null;
+ }
+
+ /**
+ * Method for getting the accounts under a given contentprovider from a package.
+ * @param app The parent app object
+ * @return An ArrayList of BluetoothMapAccountItems containing all the accounts from the app
+ */
+ public ArrayList<BluetoothMapAccountItem> parseAccounts(BluetoothMapAccountItem app) {
+ Cursor c = null;
+ if(D) Log.d(TAG,"Finding accounts for app "+app.getPackageName());
+ ArrayList<BluetoothMapAccountItem> children = new ArrayList<BluetoothMapAccountItem>();
+ // Get the list of accounts from the email apps content resolver (if possible)
+ mResolver = mContext.getContentResolver();
+ try{
+ mProviderClient = mResolver.acquireUnstableContentProviderClient(
+ Uri.parse(app.mBase_uri_no_account));
+ if (mProviderClient == null) {
+ throw new RemoteException("Failed to acquire provider for " + app.getPackageName());
+ }
+ mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
+
+ Uri uri = Uri.parse(app.mBase_uri_no_account + "/"
+ + BluetoothMapContract.TABLE_ACCOUNT);
+
+ if(app.getType() == TYPE.IM) {
+ c = mProviderClient.query(uri, BluetoothMapContract.BT_IM_ACCOUNT_PROJECTION,
+ null, null, BluetoothMapContract.AccountColumns._ID+" DESC");
+ } else {
+ c = mProviderClient.query(uri, BluetoothMapContract.BT_ACCOUNT_PROJECTION,
+ null, null, BluetoothMapContract.AccountColumns._ID+" DESC");
+ }
+ } catch (RemoteException e){
+ if(D)Log.d(TAG,"Could not establish ContentProviderClient for "+app.getPackageName()+
+ " - returning empty account list" );
+ return children;
+ } finally {
+ mProviderClient.release();
+ }
+
+ if (c != null) {
+ c.moveToPosition(-1);
+ int idIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns._ID);
+ int dispNameIndex = c.getColumnIndex(
+ BluetoothMapContract.AccountColumns.ACCOUNT_DISPLAY_NAME);
+ int exposeIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns.FLAG_EXPOSE);
+ int uciIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns.ACCOUNT_UCI);
+ int uciPreIndex = c.getColumnIndex(
+ BluetoothMapContract.AccountColumns.ACCOUNT_UCI_PREFIX);
+ while (c.moveToNext()) {
+ if(D)Log.d(TAG,"Adding account " + c.getString(dispNameIndex) +
+ " with ID " + String.valueOf(c.getInt(idIndex)));
+ String uci = null;
+ String uciPrefix = null;
+ if(app.getType() == TYPE.IM){
+ uci = c.getString(uciIndex);
+ uciPrefix = c.getString(uciPreIndex);
+ if(D)Log.d(TAG," Account UCI " + uci);
+ }
+
+ BluetoothMapAccountItem child = BluetoothMapAccountItem.create(
+ String.valueOf((c.getInt(idIndex))),
+ c.getString(dispNameIndex),
+ app.getPackageName(),
+ app.getProviderAuthority(),
+ null,
+ app.getType(),
+ uci,
+ uciPrefix);
+
+ child.mIsChecked = (c.getInt(exposeIndex) != 0);
+ child.mIsChecked = true; // TODO: Revert when this works
+ /* update the account counter
+ * so we can make sure that not to many accounts are checked. */
+ if(child.mIsChecked)
+ {
+ mAccountsEnabledCount++;
+ }
+ children.add(child);
+ }
+ c.close();
+ } else {
+ if(D)Log.d(TAG, "query failed");
+ }
+ return children;
+ }
+ /**
+ * Gets the number of enabled accounts in total across all supported apps.
+ * NOTE that this method should not be called before the parsePackages method
+ * has been successfully called.
+ * @return number of enabled accounts
+ */
+ public int getAccountsEnabledCount() {
+ if(D)Log.d(TAG,"Enabled Accounts count:"+ mAccountsEnabledCount);
+ return mAccountsEnabledCount;
+ }
+
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppObserver.java b/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
new file mode 100644
index 0000000..4b3ecc1
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
@@ -0,0 +1,331 @@
+/*
+* Copyright (C) 2014 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.android.bluetooth.map;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+import android.util.Log;
+
+/**
+ * Class to construct content observers for for email applications on the system.
+ *
+ *
+ */
+
+public class BluetoothMapAppObserver{
+
+ private static final String TAG = "BluetoothMapAppObserver";
+
+ private static final boolean D = BluetoothMapService.DEBUG;
+ private static final boolean V = BluetoothMapService.VERBOSE;
+ /* */
+ private LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> mFullList;
+ private LinkedHashMap<String,ContentObserver> mObserverMap =
+ new LinkedHashMap<String,ContentObserver>();
+ private ContentResolver mResolver;
+ private Context mContext;
+ private BroadcastReceiver mReceiver;
+ private PackageManager mPackageManager = null;
+ BluetoothMapAccountLoader mLoader;
+ BluetoothMapService mMapService = null;
+
+ public BluetoothMapAppObserver(final Context context, BluetoothMapService mapService) {
+ mContext = context;
+ mMapService = mapService;
+ mResolver = context.getContentResolver();
+ mLoader = new BluetoothMapAccountLoader(mContext);
+ mFullList = mLoader.parsePackages(false); /* Get the current list of apps */
+ createReceiver();
+ initObservers();
+ }
+
+
+ private BluetoothMapAccountItem getApp(String authoritiesName) {
+ if(V) Log.d(TAG, "getApp(): Looking for " + authoritiesName);
+ for(BluetoothMapAccountItem app:mFullList.keySet()){
+ if(V) Log.d(TAG, " Comparing: " + app.getProviderAuthority());
+ if(app.getProviderAuthority().equals(authoritiesName)) {
+ if(V) Log.d(TAG, " found " + app.mBase_uri_no_account);
+ return app;
+ }
+ }
+ if(V) Log.d(TAG, " NOT FOUND!");
+ return null;
+ }
+
+ private void handleAccountChanges(String packageNameWithProvider) {
+
+ if(D)Log.d(TAG,"handleAccountChanges (packageNameWithProvider: "
+ +packageNameWithProvider+"\n");
+ //String packageName = packageNameWithProvider.replaceFirst("\\.[^\\.]+$", "");
+ BluetoothMapAccountItem app = getApp(packageNameWithProvider);
+ if(app != null) {
+ ArrayList<BluetoothMapAccountItem> newAccountList = mLoader.parseAccounts(app);
+ ArrayList<BluetoothMapAccountItem> oldAccountList = mFullList.get(app);
+ ArrayList<BluetoothMapAccountItem> addedAccountList =
+ (ArrayList<BluetoothMapAccountItem>)newAccountList.clone();
+ ArrayList<BluetoothMapAccountItem> removedAccountList = mFullList.get(app);
+ // Same as oldAccountList.clone
+
+ mFullList.put(app, newAccountList);
+ for(BluetoothMapAccountItem newAcc: newAccountList){
+ for(BluetoothMapAccountItem oldAcc: oldAccountList){
+ if(newAcc.getId() == oldAcc.getId()){
+ // For each match remove from both removed and added lists
+ removedAccountList.remove(oldAcc);
+ addedAccountList.remove(newAcc);
+ if(!newAcc.getName().equals(oldAcc.getName()) && newAcc.mIsChecked){
+ // Name Changed and the acc is visible - Change Name in SDP record
+ mMapService.updateMasInstances(
+ BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED);
+ if(V)Log.d(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED");
+ }
+ if(newAcc.mIsChecked != oldAcc.mIsChecked) {
+ // Visibility changed
+ if(newAcc.mIsChecked){
+ // account added - create SDP record
+ mMapService.updateMasInstances(
+ BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
+ if(V)Log.d(TAG, "UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " +
+ "isChecked changed");
+ } else {
+ // account removed - remove SDP record
+ mMapService.updateMasInstances(
+ BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
+ if(V)Log.d(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " +
+ "isChecked changed");
+ }
+ }
+ break;
+ }
+ }
+ }
+ // Notify on any removed accounts
+ for(BluetoothMapAccountItem removedAcc: removedAccountList){
+ mMapService.updateMasInstances(
+ BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
+ if(V)Log.d(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " + removedAcc);
+ }
+ // Notify on any new accounts
+ for(BluetoothMapAccountItem addedAcc: addedAccountList){
+ mMapService.updateMasInstances(
+ BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
+ if(V)Log.d(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " + addedAcc);
+ }
+
+ } else {
+ Log.e(TAG, "Received change notification on package not registered for notifications!");
+
+ }
+ }
+
+ /**
+ * Adds a new content observer to the list of content observers.
+ * The key for the observer is the uri as string
+ * @param uri uri for the package that supports MAP email
+ */
+
+ public void registerObserver(BluetoothMapAccountItem app) {
+ Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
+ if (V) Log.d(TAG, "registerObserver for URI "+uri.toString()+"\n");
+ ContentObserver observer = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ onChange(selfChange, null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId()
+ + " Uri: " + uri + " selfchange: " + selfChange);
+ if(uri != null) {
+ handleAccountChanges(uri.getHost());
+ } else {
+ Log.e(TAG, "Unable to handle change as the URI is NULL!");
+ }
+
+ }
+ };
+ mObserverMap.put(uri.toString(), observer);
+ mResolver.registerContentObserver(uri, true, observer);
+ }
+
+ public void unregisterObserver(BluetoothMapAccountItem app) {
+ Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
+ if (V) Log.d(TAG, "unregisterObserver("+uri.toString()+")\n");
+ mResolver.unregisterContentObserver(mObserverMap.get(uri.toString()));
+ mObserverMap.remove(uri.toString());
+ }
+
+ private void initObservers(){
+ if(D)Log.d(TAG,"initObservers()");
+ for(BluetoothMapAccountItem app: mFullList.keySet()){
+ registerObserver(app);
+ }
+ }
+
+ private void deinitObservers(){
+ if(D)Log.d(TAG,"deinitObservers()");
+ for(BluetoothMapAccountItem app: mFullList.keySet()){
+ unregisterObserver(app);
+ }
+ }
+
+ private void createReceiver(){
+ if(D)Log.d(TAG,"createReceiver()\n");
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ intentFilter.addDataScheme("package");
+ mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if(D)Log.d(TAG,"onReceive\n");
+ String action = intent.getAction();
+
+ if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+ Uri data = intent.getData();
+ String packageName = data.getEncodedSchemeSpecificPart();
+ if(D)Log.d(TAG,"The installed package is: "+ packageName);
+
+ BluetoothMapUtils.TYPE msgType = BluetoothMapUtils.TYPE.NONE;
+ ResolveInfo resolveInfo = null;
+ Intent[] searchIntents = new Intent[2];
+ //Array <Intent> searchIntents = new Array <Intent>();
+ searchIntents[0] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_EMAIL);
+ searchIntents[1] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_IM);
+ // Find all installed packages and filter out those that support Bluetooth Map.
+
+ mPackageManager = mContext.getPackageManager();
+
+ for (Intent searchIntent : searchIntents) {
+ List<ResolveInfo> resInfos =
+ mPackageManager.queryIntentContentProviders(searchIntent, 0);
+ if (resInfos != null ) {
+ if(D) Log.d(TAG,"Found " + resInfos.size()
+ + " application(s) with intent "
+ + searchIntent.getAction().toString());
+ for (ResolveInfo rInfo : resInfos) {
+ if(rInfo != null) {
+ // Find out if package contain Bluetooth MAP support
+ if (packageName.equals(rInfo.providerInfo.packageName)) {
+ resolveInfo = rInfo;
+ if(searchIntent.getAction() ==
+ BluetoothMapContract.PROVIDER_INTERFACE_EMAIL){
+ msgType = BluetoothMapUtils.TYPE.EMAIL;
+ } else if (searchIntent.getAction() ==
+ BluetoothMapContract.PROVIDER_INTERFACE_IM){
+ msgType = BluetoothMapUtils.TYPE.IM;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ // if application found with Bluetooth MAP support add to list
+ if(resolveInfo != null) {
+ if(D) Log.d(TAG,"Found " + resolveInfo.providerInfo.packageName
+ + " application of type " + msgType);
+ BluetoothMapAccountItem app = mLoader.createAppItem(resolveInfo,
+ false, msgType);
+ if(app != null) {
+ registerObserver(app);
+ // Add all accounts to mFullList
+ ArrayList<BluetoothMapAccountItem> newAccountList =
+ mLoader.parseAccounts(app);
+ mFullList.put(app, newAccountList);
+ }
+ }
+
+ }
+ else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ Uri data = intent.getData();
+ String packageName = data.getEncodedSchemeSpecificPart();
+ if(D)Log.d(TAG,"The removed package is: "+ packageName);
+ BluetoothMapAccountItem app = getApp(packageName);
+ /* Find the object and remove from fullList */
+ if(app != null) {
+ unregisterObserver(app);
+ mFullList.remove(app);
+ }
+ }
+ }
+ };
+ mContext.registerReceiver(mReceiver,intentFilter);
+ }
+
+ private void removeReceiver(){
+ if(D)Log.d(TAG,"removeReceiver()\n");
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ /**
+ * Method to get a list of the accounts (across all apps) that are set to be shared
+ * through MAP.
+ * @return Arraylist<BluetoothMapAccountItem> containing all enabled accounts
+ */
+ public ArrayList<BluetoothMapAccountItem> getEnabledAccountItems(){
+ if(D)Log.d(TAG,"getEnabledAccountItems()\n");
+ ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>();
+ for(BluetoothMapAccountItem app:mFullList.keySet()){
+ ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
+ for(BluetoothMapAccountItem acc: accountList){
+ if(acc.mIsChecked) {
+ list.add(acc);
+ }
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Method to get a list of the accounts (across all apps).
+ * @return Arraylist<BluetoothMapAccountItem> containing all accounts
+ */
+ public ArrayList<BluetoothMapAccountItem> getAllAccountItems(){
+ if(D)Log.d(TAG,"getAllAccountItems()\n");
+ ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>();
+ for(BluetoothMapAccountItem app:mFullList.keySet()){
+ ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
+ list.addAll(accountList);
+ }
+ return list;
+ }
+
+
+ /**
+ * Cleanup all resources - must be called to avoid leaks.
+ */
+ public void shutdown() {
+ deinitObservers();
+ removeReceiver();
+ }
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppParams.java b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
index df05612..4aaed63 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAppParams.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
@@ -21,9 +21,12 @@
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
+import java.util.UUID;
import android.util.Log;
+import com.android.bluetooth.SignedLongLong;
+
/**
* This class encapsulates the appParams needed for MAP.
*/
@@ -32,99 +35,148 @@
private static final String TAG = "BluetoothMapAppParams";
private static final int MAX_LIST_COUNT = 0x01;
- private static final int MAX_LIST_COUNT_LEN = 0x02; //, 0x0000, 0xFFFF),
private static final int START_OFFSET = 0x02;
- private static final int START_OFFSET_LEN = 0x02; //, 0x0000, 0xFFFF),
private static final int FILTER_MESSAGE_TYPE = 0x03;
- private static final int FILTER_MESSAGE_TYPE_LEN = 0x01; //, 0x0000, 0x000f),
private static final int FILTER_PERIOD_BEGIN = 0x04;
private static final int FILTER_PERIOD_END = 0x05;
private static final int FILTER_READ_STATUS = 0x06;
- private static final int FILTER_READ_STATUS_LEN = 0x01; //, 0x0000, 0x0002),
private static final int FILTER_RECIPIENT = 0x07;
private static final int FILTER_ORIGINATOR = 0x08;
private static final int FILTER_PRIORITY = 0x09;
- private static final int FILTER_PRIORITY_LEN = 0x01; //, 0x0000, 0x0002),
private static final int ATTACHMENT = 0x0A;
- private static final int ATTACHMENT_LEN = 0x01; //, 0x0000, 0x0001),
private static final int TRANSPARENT = 0x0B;
- private static final int TRANSPARENT_LEN = 0x01; //, 0x0000, 0x0001),
private static final int RETRY = 0x0C;
- private static final int RETRY_LEN = 0x01; //, 0x0000, 0x0001),
private static final int NEW_MESSAGE = 0x0D;
- private static final int NEW_MESSAGE_LEN = 0x01; //, 0x0000, 0x0001),
private static final int NOTIFICATION_STATUS = 0x0E;
- private static final int NOTIFICATION_STATUS_LEN = 0x01; //, 0x0000, 0xFFFF),
private static final int MAS_INSTANCE_ID = 0x0F;
- private static final int MAS_INSTANCE_ID_LEN = 0x01; //, 0x0000, 0x00FF),
private static final int PARAMETER_MASK = 0x10;
- private static final int PARAMETER_MASK_LEN = 0x04; //, 0x0000, 0x0000),
private static final int FOLDER_LISTING_SIZE = 0x11;
- private static final int FOLDER_LISTING_SIZE_LEN = 0x02; //, 0x0000, 0xFFFF),
private static final int MESSAGE_LISTING_SIZE = 0x12;
- private static final int MESSAGE_LISTING_SIZE_LEN = 0x02; //, 0x0000, 0xFFFF),
private static final int SUBJECT_LENGTH = 0x13;
- private static final int SUBJECT_LENGTH_LEN = 0x01; //, 0x0000, 0x00FF),
private static final int CHARSET = 0x14;
- private static final int CHARSET_LEN = 0x01; //, 0x0000, 0x0001),
private static final int FRACTION_REQUEST = 0x15;
- private static final int FRACTION_REQUEST_LEN = 0x01; //, 0x0000, 0x0001),
private static final int FRACTION_DELIVER = 0x16;
- private static final int FRACTION_DELIVER_LEN = 0x01; //, 0x0000, 0x0001),
private static final int STATUS_INDICATOR = 0x17;
- private static final int STATUS_INDICATOR_LEN = 0x01; //, 0x0000, 0x0001),
private static final int STATUS_VALUE = 0x18;
- private static final int STATUS_VALUE_LEN = 0x01; //, 0x0000, 0x0001),
private static final int MSE_TIME = 0x19;
+ private static final int DATABASE_INDETIFIER = 0x1A;
+ private static final int CONVO_LIST_VER_COUNTER = 0x1B;
+ private static final int PRESENCE_AVAILABLE = 0x1C;
+ private static final int PRESENCE_TEXT = 0x1D;
+ private static final int LAST_ACTIVITY = 0x1E;
+ private static final int CHAT_STATE = 0x1F;
+ private static final int FILTER_CONVO_ID = 0x20;
+ private static final int CONVO_LISTING_SIZE = 0x21;
+ private static final int FILTER_PRESENCE = 0x22;
+ private static final int FILTER_UID_PRESENT = 0x23;
+ private static final int CHAT_STATE_CONVO_ID = 0x24;
+ private static final int FOLDER_VER_COUNTER = 0x25;
+ private static final int FILTER_MESSAGE_HANDLE = 0x26;
+ private static final int NOTIFICATION_FILTER = 0x27;
+ private static final int CONVO_PARAMETER_MASK = 0x28;
- public static final int INVALID_VALUE_PARAMETER = -1;
- public static final int NOTIFICATION_STATUS_NO = 0;
- public static final int NOTIFICATION_STATUS_YES = 1;
- public static final int STATUS_INDICATOR_READ = 0;
- public static final int STATUS_INDICATOR_DELETED = 1;
- public static final int STATUS_VALUE_YES = 1;
- public static final int STATUS_VALUE_NO = 0;
- public static final int CHARSET_NATIVE = 0;
- public static final int CHARSET_UTF8 = 1;
- public static final int FRACTION_REQUEST_FIRST = 0;
- public static final int FRACTION_REQUEST_NEXT = 1;
- public static final int FRACTION_DELIVER_MORE = 0;
- public static final int FRACTION_DELIVER_LAST = 1;
+ // Length defined for Application Parameters
+ private static final int MAX_LIST_COUNT_LEN = 0x02; //, 0x0000, 0xFFFF),
+ private static final int START_OFFSET_LEN = 0x02; //, 0x0000, 0xFFFF),
+ private static final int FILTER_MESSAGE_TYPE_LEN = 0x01; //, 0x0000, 0x000f),
+ private static final int FILTER_READ_STATUS_LEN = 0x01; //, 0x0000, 0x0002),
+ private static final int FILTER_PRIORITY_LEN = 0x01; //, 0x0000, 0x0002),
+ private static final int ATTACHMENT_LEN = 0x01; //, 0x0000, 0x0001),
+ private static final int TRANSPARENT_LEN = 0x01; //, 0x0000, 0x0001),
+ private static final int RETRY_LEN = 0x01; //, 0x0000, 0x0001),
+ private static final int NEW_MESSAGE_LEN = 0x01; //, 0x0000, 0x0001),
+ private static final int NOTIFICATION_STATUS_LEN = 0x01; //, 0x0000, 0xFFFF),
+ private static final int MAS_INSTANCE_ID_LEN = 0x01; //, 0x0000, 0x00FF),
+ private static final int PARAMETER_MASK_LEN = 0x04; //, 0x0000, 0x0000),
+ private static final int FOLDER_LISTING_SIZE_LEN = 0x02; //, 0x0000, 0xFFFF),
+ private static final int MESSAGE_LISTING_SIZE_LEN = 0x02; //, 0x0000, 0xFFFF),
+ private static final int SUBJECT_LENGTH_LEN = 0x01; //, 0x0000, 0x00FF),
+ private static final int CHARSET_LEN = 0x01; //, 0x0000, 0x0001),
+ private static final int FRACTION_REQUEST_LEN = 0x01; //, 0x0000, 0x0001),
+ private static final int FRACTION_DELIVER_LEN = 0x01; //, 0x0000, 0x0001),
+ private static final int STATUS_INDICATOR_LEN = 0x01; //, 0x0000, 0x0001),
+ private static final int STATUS_VALUE_LEN = 0x01; //, 0x0000, 0x0001),
+ private static final int DATABASE_INDETIFIER_LEN = 0x10;
+ private static final int CONVO_LIST_VER_COUNTER_LEN = 0x10;
+ private static final int PRESENCE_AVAILABLE_LEN = 0X01;
+ private static final int CHAT_STATE_LEN = 0x01;
+ private static final int CHAT_STATE_CONVO_ID_LEN = 0x10;
+ private static final int FILTER_CONVO_ID_LEN = 0x20;
+ private static final int CONVO_LISTING_SIZE_LEN = 0x02;
+ private static final int FILTER_PRESENCE_LEN = 0x01;
+ private static final int FILTER_UID_PRESENT_LEN = 0x01;
+ private static final int FOLDER_VER_COUNTER_LEN = 0x10;
+ private static final int FILTER_MESSAGE_HANDLE_LEN= 0x10;
+ private static final int NOTIFICATION_FILTER_LEN = 0x04;
+ private static final int CONVO_PARAMETER_MASK_LEN = 0x04;
+ // Default values
+ public static final int INVALID_VALUE_PARAMETER =-1;
+ public static final int NOTIFICATION_STATUS_NO = 0;
+ public static final int NOTIFICATION_STATUS_YES = 1;
+ public static final int STATUS_INDICATOR_READ = 0;
+ public static final int STATUS_INDICATOR_DELETED = 1;
+ public static final int STATUS_VALUE_YES = 1;
+ public static final int STATUS_VALUE_NO = 0;
+ public static final int CHARSET_NATIVE = 0;
+ public static final int CHARSET_UTF8 = 1;
+ public static final int FRACTION_REQUEST_FIRST = 0;
+ public static final int FRACTION_REQUEST_NEXT = 1;
+ public static final int FRACTION_DELIVER_MORE = 0;
+ public static final int FRACTION_DELIVER_LAST = 1;
- public static final int FILTER_NO_SMS_GSM = 0x01;
- public static final int FILTER_NO_SMS_CDMA = 0x02;
- public static final int FILTER_NO_EMAIL = 0x04;
- public static final int FILTER_NO_MMS = 0x08;
+ public static final int FILTER_NO_SMS_GSM = 0x01;
+ public static final int FILTER_NO_SMS_CDMA = 0x02;
+ public static final int FILTER_NO_EMAIL = 0x04;
+ public static final int FILTER_NO_MMS = 0x08;
+ public static final int FILTER_NO_IM = 0x10;
+ public static final int FILTER_MSG_TYPE_MASK = 0x1F;
- /* Default values for omitted application parameters */
- public static final long PARAMETER_MASK_ALL_ENABLED = 0xFFFF; // TODO: Update when bit 16-31 will be used.
-
- private int mMaxListCount = INVALID_VALUE_PARAMETER;
- private int mStartOffset = INVALID_VALUE_PARAMETER;
- private int mFilterMessageType = INVALID_VALUE_PARAMETER;
- private long mFilterPeriodBegin = INVALID_VALUE_PARAMETER;
- private long mFilterPeriodEnd = INVALID_VALUE_PARAMETER;
- private int mFilterReadStatus = INVALID_VALUE_PARAMETER;
- private String mFilterRecipient = null;
- private String mFilterOriginator = null;
- private int mFilterPriority = INVALID_VALUE_PARAMETER;
- private int mAttachment = INVALID_VALUE_PARAMETER;
- private int mTransparent = INVALID_VALUE_PARAMETER;
- private int mRetry = INVALID_VALUE_PARAMETER;
- private int mNewMessage = INVALID_VALUE_PARAMETER;
- private int mNotificationStatus = INVALID_VALUE_PARAMETER;
- private int mMasInstanceId = INVALID_VALUE_PARAMETER;
- private long mParameterMask = INVALID_VALUE_PARAMETER;
- private int mFolderListingSize = INVALID_VALUE_PARAMETER;
- private int mMessageListingSize = INVALID_VALUE_PARAMETER;
- private int mSubjectLength = INVALID_VALUE_PARAMETER;
- private int mCharset = INVALID_VALUE_PARAMETER;
- private int mFractionRequest = INVALID_VALUE_PARAMETER;
- private int mFractionDeliver = INVALID_VALUE_PARAMETER;
- private int mStatusIndicator = INVALID_VALUE_PARAMETER;
- private int mStatusValue = INVALID_VALUE_PARAMETER;
- private long mMseTime = INVALID_VALUE_PARAMETER;
+ private int mMaxListCount = INVALID_VALUE_PARAMETER;
+ private int mStartOffset = INVALID_VALUE_PARAMETER;
+ private int mFilterMessageType = INVALID_VALUE_PARAMETER;
+ // It seems like these are not implemented...
+ private long mFilterPeriodBegin = INVALID_VALUE_PARAMETER;
+ private long mFilterPeriodEnd = INVALID_VALUE_PARAMETER;
+ private int mFilterReadStatus = INVALID_VALUE_PARAMETER;
+ private String mFilterRecipient = null;
+ private String mFilterOriginator = null;
+ private int mFilterPriority = INVALID_VALUE_PARAMETER;
+ private int mAttachment = INVALID_VALUE_PARAMETER;
+ private int mTransparent = INVALID_VALUE_PARAMETER;
+ private int mRetry = INVALID_VALUE_PARAMETER;
+ private int mNewMessage = INVALID_VALUE_PARAMETER;
+ private int mNotificationStatus = INVALID_VALUE_PARAMETER;
+ private long mNotificationFilter = INVALID_VALUE_PARAMETER;
+ private int mMasInstanceId = INVALID_VALUE_PARAMETER;
+ private long mParameterMask = INVALID_VALUE_PARAMETER;
+ private int mFolderListingSize = INVALID_VALUE_PARAMETER;
+ private int mMessageListingSize = INVALID_VALUE_PARAMETER;
+ private int mConvoListingSize = INVALID_VALUE_PARAMETER;
+ private int mSubjectLength = INVALID_VALUE_PARAMETER;
+ private int mCharset = INVALID_VALUE_PARAMETER;
+ private int mFractionRequest = INVALID_VALUE_PARAMETER;
+ private int mFractionDeliver = INVALID_VALUE_PARAMETER;
+ private int mStatusIndicator = INVALID_VALUE_PARAMETER;
+ private int mStatusValue = INVALID_VALUE_PARAMETER;
+ private long mMseTime = INVALID_VALUE_PARAMETER;
+ // TODO: Change to use SignedLongLong?
+ private long mConvoListingVerCounterLow = INVALID_VALUE_PARAMETER;
+ private long mConvoListingVerCounterHigh = INVALID_VALUE_PARAMETER;
+ private long mDatabaseIdentifierLow = INVALID_VALUE_PARAMETER;
+ private long mDatabaseIdentifierHigh = INVALID_VALUE_PARAMETER;
+ private long mFolderVerCounterLow = INVALID_VALUE_PARAMETER;
+ private long mFolderVerCounterHigh = INVALID_VALUE_PARAMETER;
+ private int mPresenceAvailability = INVALID_VALUE_PARAMETER;
+ private String mPresenceStatus = null;
+ private long mLastActivity = INVALID_VALUE_PARAMETER;
+ private int mChatState = INVALID_VALUE_PARAMETER;
+ private SignedLongLong mFilterConvoId = null;
+ private int mFilterPresence = INVALID_VALUE_PARAMETER;
+ private int mFilterUidPresent = INVALID_VALUE_PARAMETER;
+ private SignedLongLong mChatStateConvoId = null;
+ private long mFilterMsgHandle = INVALID_VALUE_PARAMETER;
+ private long mConvoParameterMask = INVALID_VALUE_PARAMETER;
/**
* Default constructor, used to build an application parameter object to be
@@ -158,7 +210,7 @@
}
/**
- * Parse an application parameter OBEX header stored in a ByteArray.
+ * Parse an application parameter OBEX header stored in a byte array.
*
* @param appParams
* the byte array containing the application parameters OBEX
@@ -183,185 +235,319 @@
if (tagLength != MAX_LIST_COUNT_LEN) {
Log.w(TAG, "MAX_LIST_COUNT: Wrong length received: " + tagLength
+ " expected: " + MAX_LIST_COUNT_LEN);
- break;
+ } else {
+ setMaxListCount(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
}
- setMaxListCount(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
break;
case START_OFFSET:
if (tagLength != START_OFFSET_LEN) {
Log.w(TAG, "START_OFFSET: Wrong length received: " + tagLength + " expected: "
+ START_OFFSET_LEN);
- break;
+ } else {
+ setStartOffset(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
}
- setStartOffset(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
break;
case FILTER_MESSAGE_TYPE:
if (tagLength != FILTER_MESSAGE_TYPE_LEN) {
- Log.w(TAG, "FILTER_MESSAGE_TYPE: Wrong length received: " + tagLength + " expected: "
- + FILTER_MESSAGE_TYPE_LEN);
- break;
+ Log.w(TAG, "FILTER_MESSAGE_TYPE: Wrong length received: " + tagLength
+ + " expected: " + FILTER_MESSAGE_TYPE_LEN);
+ } else {
+ setFilterMessageType(appParams[i] & 0x1f);
}
- setFilterMessageType(appParams[i] & 0x0f);
break;
case FILTER_PERIOD_BEGIN:
if(tagLength != 0) {
setFilterPeriodBegin(new String(appParams, i, tagLength));
+ } else {
+ Log.w(TAG, "FILTER_PERIOD_BEGIN: Wrong length received: " + tagLength +
+ " expected to be more than 0");
}
break;
case FILTER_PERIOD_END:
if(tagLength != 0) {
setFilterPeriodEnd(new String(appParams, i, tagLength));
+ } else {
+ Log.w(TAG, "FILTER_PERIOD_END: Wrong length received: " + tagLength +
+ " expected to be more than 0");
}
break;
case FILTER_READ_STATUS:
if (tagLength != FILTER_READ_STATUS_LEN) {
- Log.w(TAG, "FILTER_READ_STATUS: Wrong length received: " + tagLength + " expected: "
- + FILTER_READ_STATUS_LEN);
- break;
- }
- setFilterReadStatus(appParams[i] & 0x03); // Lower two bits
+ Log.w(TAG, "FILTER_READ_STATUS: Wrong length received: " + tagLength +
+ " expected: " + FILTER_READ_STATUS_LEN);
+ } else {
+ setFilterReadStatus(appParams[i] & 0x03); // Lower two bits
+ }
break;
case FILTER_RECIPIENT:
if(tagLength != 0) {
setFilterRecipient(new String(appParams, i, tagLength));
+ } else {
+ Log.w(TAG, "FILTER_RECIPIENT: Wrong length received: " + tagLength +
+ " expected to be more than 0");
}
break;
case FILTER_ORIGINATOR:
if(tagLength != 0) {
setFilterOriginator(new String(appParams, i, tagLength));
+ } else {
+ Log.w(TAG, "FILTER_ORIGINATOR: Wrong length received: " + tagLength +
+ " expected to be more than 0");
}
break;
case FILTER_PRIORITY:
if (tagLength != FILTER_PRIORITY_LEN) {
- Log.w(TAG, "FILTER_PRIORITY: Wrong length received: " + tagLength + " expected: "
- + FILTER_PRIORITY_LEN);
- break;
+ Log.w(TAG, "FILTER_PRIORITY: Wrong length received: " + tagLength +
+ " expected: " + FILTER_PRIORITY_LEN);
+ } else {
+ setFilterPriority(appParams[i] & 0x03); // Lower two bits
}
- setFilterPriority(appParams[i] & 0x03); // Lower two bits
break;
case ATTACHMENT:
if (tagLength != ATTACHMENT_LEN) {
Log.w(TAG, "ATTACHMENT: Wrong length received: " + tagLength + " expected: "
+ ATTACHMENT_LEN);
- break;
+ } else {
+ setAttachment(appParams[i] & 0x01); // Lower bit
}
- setAttachment(appParams[i] & 0x01); // Lower bit
break;
case TRANSPARENT:
if (tagLength != TRANSPARENT_LEN) {
Log.w(TAG, "TRANSPARENT: Wrong length received: " + tagLength + " expected: "
+ TRANSPARENT_LEN);
- break;
+ } else {
+ setTransparent(appParams[i] & 0x01); // Lower bit
}
- setTransparent(appParams[i] & 0x01); // Lower bit
break;
case RETRY:
if (tagLength != RETRY_LEN) {
Log.w(TAG, "RETRY: Wrong length received: " + tagLength + " expected: "
+ RETRY_LEN);
- break;
+ } else {
+ setRetry(appParams[i] & 0x01); // Lower bit
}
- setRetry(appParams[i] & 0x01); // Lower bit
break;
case NEW_MESSAGE:
if (tagLength != NEW_MESSAGE_LEN) {
Log.w(TAG, "NEW_MESSAGE: Wrong length received: " + tagLength + " expected: "
+ NEW_MESSAGE_LEN);
- break;
+ } else {
+ setNewMessage(appParams[i] & 0x01); // Lower bit
}
- setNewMessage(appParams[i] & 0x01); // Lower bit
break;
case NOTIFICATION_STATUS:
if (tagLength != NOTIFICATION_STATUS_LEN) {
- Log.w(TAG, "NOTIFICATION_STATUS: Wrong length received: " + tagLength + " expected: "
- + NOTIFICATION_STATUS_LEN);
- break;
+ Log.w(TAG, "NOTIFICATION_STATUS: Wrong length received: " + tagLength +
+ " expected: " + NOTIFICATION_STATUS_LEN);
+ } else {
+ setNotificationStatus(appParams[i] & 0x01); // Lower bit
}
- setNotificationStatus(appParams[i] & 0x01); // Lower bit
+ break;
+ case NOTIFICATION_FILTER:
+ if (tagLength != NOTIFICATION_FILTER_LEN) {
+ Log.w(TAG, "NOTIFICATION_FILTER: Wrong length received: " + tagLength +
+ " expected: " + NOTIFICATION_FILTER_LEN);
+ } else {
+ setNotificationFilter(appParamBuf.getInt(i) & 0xffffffffL); // 4 bytes
+ }
break;
case MAS_INSTANCE_ID:
if (tagLength != MAS_INSTANCE_ID_LEN) {
- Log.w(TAG, "MAS_INSTANCE_ID: Wrong length received: " + tagLength + " expected: "
- + MAS_INSTANCE_ID_LEN);
- break;
+ Log.w(TAG, "MAS_INSTANCE_ID: Wrong length received: " + tagLength +
+ " expected: " + MAS_INSTANCE_ID_LEN);
+ } else {
+ setMasInstanceId(appParams[i] & 0xff);
}
- setMasInstanceId(appParams[i] & 0xff);
break;
case PARAMETER_MASK:
if (tagLength != PARAMETER_MASK_LEN) {
- Log.w(TAG, "PARAMETER_MASK: Wrong length received: " + tagLength + " expected: "
- + PARAMETER_MASK_LEN);
- break;
+ Log.w(TAG, "PARAMETER_MASK: Wrong length received: " + tagLength +
+ " expected: " + PARAMETER_MASK_LEN);
+ } else {
+ setParameterMask(appParamBuf.getInt(i) & 0xffffffffL); // Make it unsigned
}
- setParameterMask(appParamBuf.getInt(i) & 0xffffffffL); // Make it unsigned
break;
case FOLDER_LISTING_SIZE:
if (tagLength != FOLDER_LISTING_SIZE_LEN) {
- Log.w(TAG, "FOLDER_LISTING_SIZE: Wrong length received: " + tagLength + " expected: "
- + FOLDER_LISTING_SIZE_LEN);
- break;
+ Log.w(TAG, "FOLDER_LISTING_SIZE: Wrong length received: " + tagLength +
+ " expected: " + FOLDER_LISTING_SIZE_LEN);
+ } else {
+ setFolderListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
}
- setFolderListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
break;
case MESSAGE_LISTING_SIZE:
if (tagLength != MESSAGE_LISTING_SIZE_LEN) {
- Log.w(TAG, "MESSAGE_LISTING_SIZE: Wrong length received: " + tagLength + " expected: "
- + MESSAGE_LISTING_SIZE_LEN);
- break;
+ Log.w(TAG, "MESSAGE_LISTING_SIZE: Wrong length received: " + tagLength +
+ " expected: " + MESSAGE_LISTING_SIZE_LEN);
+ } else {
+ setMessageListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
}
- setMessageListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
break;
case SUBJECT_LENGTH:
if (tagLength != SUBJECT_LENGTH_LEN) {
- Log.w(TAG, "SUBJECT_LENGTH: Wrong length received: " + tagLength + " expected: "
- + SUBJECT_LENGTH_LEN);
- break;
+ Log.w(TAG, "SUBJECT_LENGTH: Wrong length received: " + tagLength +
+ " expected: " + SUBJECT_LENGTH_LEN);
+ } else {
+ setSubjectLength(appParams[i] & 0xff);
}
- setSubjectLength(appParams[i] & 0xff);
break;
case CHARSET:
if (tagLength != CHARSET_LEN) {
Log.w(TAG, "CHARSET: Wrong length received: " + tagLength + " expected: "
+ CHARSET_LEN);
- break;
+ } else {
+ setCharset(appParams[i] & 0x01); // Lower bit
}
- setCharset(appParams[i] & 0x01); // Lower bit
break;
case FRACTION_REQUEST:
if (tagLength != FRACTION_REQUEST_LEN) {
- Log.w(TAG, "FRACTION_REQUEST: Wrong length received: " + tagLength + " expected: "
- + FRACTION_REQUEST_LEN);
- break;
+ Log.w(TAG, "FRACTION_REQUEST: Wrong length received: " + tagLength +
+ " expected: " + FRACTION_REQUEST_LEN);
+ } else {
+ setFractionRequest(appParams[i] & 0x01); // Lower bit
}
- setFractionRequest(appParams[i] & 0x01); // Lower bit
break;
case FRACTION_DELIVER:
if (tagLength != FRACTION_DELIVER_LEN) {
- Log.w(TAG, "FRACTION_DELIVER: Wrong length received: " + tagLength + " expected: "
- + FRACTION_DELIVER_LEN);
- break;
+ Log.w(TAG, "FRACTION_DELIVER: Wrong length received: " + tagLength +
+ " expected: " + FRACTION_DELIVER_LEN);
+ } else {
+ setFractionDeliver(appParams[i] & 0x01); // Lower bit
}
- setFractionDeliver(appParams[i] & 0x01); // Lower bit
break;
case STATUS_INDICATOR:
if (tagLength != STATUS_INDICATOR_LEN) {
- Log.w(TAG, "STATUS_INDICATOR: Wrong length received: " + tagLength + " expected: "
- + STATUS_INDICATOR_LEN);
- break;
+ Log.w(TAG, "STATUS_INDICATOR: Wrong length received: " + tagLength +
+ " expected: " + STATUS_INDICATOR_LEN);
+ } else {
+ setStatusIndicator(appParams[i] & 0x01); // Lower bit
}
- setStatusIndicator(appParams[i] & 0x01); // Lower bit
break;
case STATUS_VALUE:
if (tagLength != STATUS_VALUE_LEN) {
Log.w(TAG, "STATUS_VALUER: Wrong length received: " + tagLength + " expected: "
+ STATUS_VALUE_LEN);
- break;
+ } else {
+ setStatusValue(appParams[i] & 0x01); // Lower bit
}
- setStatusValue(appParams[i] & 0x01); // Lower bit
break;
case MSE_TIME:
setMseTime(new String(appParams, i, tagLength));
break;
+ case DATABASE_INDETIFIER:
+ if((tagLength != DATABASE_INDETIFIER_LEN)){
+ Log.w(TAG, "DATABASE_IDENTIFIER: Wrong length received: " + tagLength +
+ " expected: " + DATABASE_INDETIFIER_LEN);
+ } else {
+ setDatabaseIdentifier(appParamBuf.getLong(i)/*MSB*/,
+ appParamBuf.getLong(i+8)/*LSB*/);
+ }
+ break;
+ case CONVO_LIST_VER_COUNTER:
+ if((tagLength != CONVO_LIST_VER_COUNTER_LEN)){
+ Log.w(TAG, "CONVO_LIST_VER_COUNTER: Wrong length received: " + tagLength +
+ " expected: " + CONVO_LIST_VER_COUNTER_LEN);
+ } else {
+ setConvoListingVerCounter(appParamBuf.getLong(i)/*MSB*/,
+ appParamBuf.getLong(i+8)/*LSB*/);
+ }
+ break;
+ case PRESENCE_AVAILABLE:
+ if((tagLength != PRESENCE_AVAILABLE_LEN)){
+ Log.w(TAG, "PRESENCE_AVAILABLE: Wrong length received: " + tagLength +
+ " expected: " + PRESENCE_AVAILABLE_LEN);
+ } else {
+ setPresenceAvailability(appParams[i]);
+ }
+ break;
+ case PRESENCE_TEXT:
+ if(tagLength != 0) {
+ setPresenceStatus(new String(appParams, i, tagLength));
+ } else
+ Log.w(TAG, "PRESENCE_STATUS: Wrong length received: " + tagLength +
+ " expected to be more than 0");
+ break;
+ case LAST_ACTIVITY:
+ if(tagLength != 0) {
+ setLastActivity(new String(appParams, i, tagLength));
+ } else
+ Log.w(TAG, "LAST_ACTIVITY: Wrong length received: " + tagLength +
+ " expected to be more than 0");
+ break;
+ case CHAT_STATE:
+ if((tagLength != CHAT_STATE_LEN)){
+ Log.w(TAG, "CHAT_STATE: Wrong length received: " + tagLength +
+ " expected: " + CHAT_STATE_LEN);
+ } else {
+ setChatState(appParams[i]);
+ }
+ break;
+ case FILTER_CONVO_ID:
+ if((tagLength != 0) && (tagLength <= FILTER_CONVO_ID_LEN)){
+ setFilterConvoId(new String(appParams, i, tagLength));
+ } else {
+ Log.w(TAG, "FILTER_CONVO_ID: Wrong length received: " + tagLength +
+ " expected: " + FILTER_CONVO_ID_LEN);
+ }
+ break;
+ case CONVO_LISTING_SIZE:
+ if(tagLength != CONVO_LISTING_SIZE_LEN){
+ Log.w(TAG, "LISTING_SIZE: Wrong length received: "+ tagLength+" expected: "+
+ CONVO_LISTING_SIZE_LEN);
+
+ } else {
+ setConvoListingSize(appParamBuf.getShort(i) & 0xffff);
+ }
+ break;
+ case FILTER_PRESENCE:
+ if((tagLength != FILTER_PRESENCE_LEN)){
+ Log.w(TAG, "FILTER_PRESENCE: Wrong length received: " + tagLength +
+ " expected: " + FILTER_PRESENCE_LEN);
+ } else {
+ setFilterPresence(appParams[i]);
+ }
+ break;
+ case FILTER_UID_PRESENT:
+ if((tagLength != FILTER_UID_PRESENT_LEN)){
+ Log.w(TAG, "FILTER_UID_PRESENT: Wrong length received: " + tagLength +
+ " expected: " + FILTER_UID_PRESENT_LEN);
+ } else {
+ setFilterUidPresent(appParams[i]&0x1);
+ }
+ break;
+ case CHAT_STATE_CONVO_ID:
+ if((tagLength != CHAT_STATE_CONVO_ID_LEN)){
+ Log.w(TAG, "CHAT_STATE_CONVO_ID: Wrong length received: " + tagLength +
+ " expected: " + CHAT_STATE_CONVO_ID_LEN);
+ } else {
+ /* TODO: Is this correct convoId handling? */
+ setChatStateConvoId(appParamBuf.getLong(i)/*MSB*/,
+ appParamBuf.getLong(i+8)/*LSB*/);
+ Log.d(TAG, "CHAT_STATE_CONVO_ID: convo id " +
+ "MSB=" + BluetoothMapUtils.getLongAsString(appParamBuf.getLong(i)) +
+ ", LSB(+8)=" + BluetoothMapUtils.getLongAsString(appParamBuf.getLong(i+8)));
+
+ }
+ break;
+ case FOLDER_VER_COUNTER:
+ break;
+ case FILTER_MESSAGE_HANDLE:
+ if((tagLength != 0 && tagLength <= FILTER_MESSAGE_HANDLE_LEN)){
+ setFilterMsgHandle(new String(appParams, i, tagLength));
+ } else {
+ Log.w(TAG, "FILTER_MESSAGE_HANDLE: Wrong length received: " + tagLength +
+ " expected: " + FILTER_MESSAGE_HANDLE_LEN);
+ }
+
+ break;
+ case CONVO_PARAMETER_MASK:
+ if (tagLength != CONVO_PARAMETER_MASK_LEN) {
+ Log.w(TAG, "CONVO_PARAMETER_MASK: Wrong length received: " + tagLength +
+ " expected: " + CONVO_PARAMETER_MASK_LEN);
+ } else {
+ setConvoParameterMask(appParamBuf.getInt(i) & 0xffffffffL); // Make it unsigned
+ }
+ break;
default:
// Just skip unknown Tags, no need to report error
Log.w(TAG, "Unknown TagId received ( 0x" + Integer.toString(tagId, 16)
@@ -382,15 +568,18 @@
*/
private int getParamMaxLength() throws UnsupportedEncodingException {
int length = 0;
- length += 25 * 2; // tagId + tagLength
- length += 27; // fixed sizes
- length += getFilterPeriodBegin() == INVALID_VALUE_PARAMETER ? 0 : 15;
- length += getFilterPeriodEnd() == INVALID_VALUE_PARAMETER ? 0 : 15;
+ length += 38 * 2; // tagId + tagLength
+ length += 33+4*16; // fixed sizes TODO: Update when spec is ready
+ length += getFilterPeriodBegin() == INVALID_VALUE_PARAMETER ? 0 : 20;
+ length += getFilterPeriodEnd() == INVALID_VALUE_PARAMETER ? 0 : 20;
if (getFilterRecipient() != null)
length += getFilterRecipient().getBytes("UTF-8").length;
if (getFilterOriginator() != null)
length += getFilterOriginator().getBytes("UTF-8").length;
length += getMseTime() == INVALID_VALUE_PARAMETER ? 0 : 20;
+ if(getPresenceStatus() != null)
+ length += getPresenceStatus().getBytes("UTF-8").length;
+ length += (getLastActivity() == INVALID_VALUE_PARAMETER) ? 0 : 20;
return length;
}
@@ -476,6 +665,11 @@
appParamBuf.put((byte) NOTIFICATION_STATUS_LEN);
appParamBuf.putShort((short) getNotificationStatus());
}
+ if (getNotificationFilter() != INVALID_VALUE_PARAMETER) {
+ appParamBuf.put((byte) NOTIFICATION_FILTER);
+ appParamBuf.put((byte) NOTIFICATION_FILTER_LEN);
+ appParamBuf.putInt((int) getNotificationFilter());
+ }
if (getMasInstanceId() != INVALID_VALUE_PARAMETER) {
appParamBuf.put((byte) MAS_INSTANCE_ID);
appParamBuf.put((byte) MAS_INSTANCE_ID_LEN);
@@ -531,6 +725,80 @@
appParamBuf.put((byte) getMseTimeString().getBytes("UTF-8").length);
appParamBuf.put(getMseTimeString().getBytes("UTF-8"));
}
+ // Note: New for IM
+ if (getDatabaseIdentifier() != null) {
+ appParamBuf.put((byte)DATABASE_INDETIFIER);
+ appParamBuf.put((byte)DATABASE_INDETIFIER_LEN);
+ appParamBuf.put(getDatabaseIdentifier());
+ }
+ if (getConvoListingVerCounter() != null) {
+ appParamBuf.put((byte)CONVO_LIST_VER_COUNTER);
+ appParamBuf.put((byte)CONVO_LIST_VER_COUNTER_LEN);
+ appParamBuf.put(getConvoListingVerCounter());
+ }
+ if (getPresenceAvailability() != INVALID_VALUE_PARAMETER) {
+ appParamBuf.put((byte)PRESENCE_AVAILABLE);
+ appParamBuf.put((byte)PRESENCE_AVAILABLE_LEN);
+ appParamBuf.putInt((int)getPresenceAvailability());
+ }
+ if (getPresenceStatus()!= null) {
+ appParamBuf.put((byte)PRESENCE_TEXT);
+ appParamBuf.put((byte)getPresenceStatus().getBytes("UTF-8").length);
+ appParamBuf.put(getPresenceStatus().getBytes());
+ }
+ if (getLastActivity() != INVALID_VALUE_PARAMETER) {
+ appParamBuf.put((byte)LAST_ACTIVITY);
+ appParamBuf.put((byte)getLastActivityString().getBytes("UTF-8").length);
+ appParamBuf.put(getLastActivityString().getBytes());
+ }
+ if (getChatState() != INVALID_VALUE_PARAMETER) {
+ appParamBuf.put((byte)CHAT_STATE);
+ appParamBuf.put((byte)CHAT_STATE_LEN);
+ appParamBuf.putShort((short)getChatState());
+ }
+ if (getFilterConvoId() != null) {
+ appParamBuf.put((byte)FILTER_CONVO_ID);
+ appParamBuf.put((byte)FILTER_CONVO_ID_LEN);
+ appParamBuf.putLong(getFilterConvoId().getMostSignificantBits());
+ appParamBuf.putLong(getFilterConvoId().getLeastSignificantBits());
+ }
+ if (getConvoListingSize() != INVALID_VALUE_PARAMETER) {
+ appParamBuf.put((byte)CONVO_LISTING_SIZE);
+ appParamBuf.put((byte)CONVO_LISTING_SIZE_LEN);
+ appParamBuf.putShort((short)getConvoListingSize());
+ }
+ if (getFilterPresence() != INVALID_VALUE_PARAMETER) {
+ appParamBuf.put((byte)FILTER_PRESENCE);
+ appParamBuf.put((byte)FILTER_PRESENCE_LEN);
+ appParamBuf.putShort((short)getFilterPresence());
+ }
+ if (getFilterUidPresent() != INVALID_VALUE_PARAMETER) {
+ appParamBuf.put((byte)FILTER_UID_PRESENT);
+ appParamBuf.put((byte)FILTER_UID_PRESENT_LEN);
+ appParamBuf.putShort((short)getFilterUidPresent());
+ }
+ if (getChatStateConvoId() != null) {
+ appParamBuf.put((byte)CHAT_STATE_CONVO_ID);
+ appParamBuf.put((byte)CHAT_STATE_CONVO_ID_LEN);
+ appParamBuf.putLong(getChatStateConvoId().getMostSignificantBits());
+ appParamBuf.putLong(getChatStateConvoId().getLeastSignificantBits());
+ }
+ if (getFolderVerCounter() != null) {
+ appParamBuf.put((byte)FOLDER_VER_COUNTER);
+ appParamBuf.put((byte)FOLDER_VER_COUNTER_LEN);
+ appParamBuf.put(getFolderVerCounter());
+ }
+ if (getFilterMsgHandle() != INVALID_VALUE_PARAMETER) {
+ appParamBuf.put((byte)FILTER_MESSAGE_HANDLE);
+ appParamBuf.put((byte)FILTER_MESSAGE_HANDLE_LEN);
+ appParamBuf.putLong(getFilterMsgHandle());
+ }
+ if (getConvoParameterMask() != INVALID_VALUE_PARAMETER) {
+ appParamBuf.put((byte) CONVO_PARAMETER_MASK);
+ appParamBuf.put((byte) CONVO_PARAMETER_MASK_LEN);
+ appParamBuf.putInt((int) getConvoParameterMask());
+ }
+
// We need to reduce the length of the array to match the content
retBuf = Arrays.copyOfRange(appParamBuf.array(), appParamBuf.arrayOffset(),
appParamBuf.arrayOffset() + appParamBuf.position());
@@ -562,8 +830,8 @@
}
public void setFilterMessageType(int filterMessageType) throws IllegalArgumentException {
- if (filterMessageType < 0 || filterMessageType > 0x000F)
- throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x000F");
+ if (filterMessageType < 0 || filterMessageType > 0x001F)
+ throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x001F");
this.mFilterMessageType = filterMessageType;
}
@@ -587,10 +855,45 @@
this.mFilterPeriodBegin = date.getTime();
}
+ public long getFilterLastActivityBegin() {
+ return mFilterPeriodBegin;
+ }
+ public String getFilterLastActivityBeginString() {
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+ Date date = new Date(mFilterPeriodBegin);
+ return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+ }
+ public void setFilterLastActivityBegin(long filterPeriodBegin) {
+ this.mFilterPeriodBegin = filterPeriodBegin;
+ }
+
+ public void setFilterLastActivityBegin(String filterPeriodBegin)throws ParseException {
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+ Date date = format.parse(filterPeriodBegin);
+ this.mFilterPeriodBegin = date.getTime();
+ }
public long getFilterPeriodEnd() {
return mFilterPeriodEnd;
}
+ public long getFilterLastActivityEnd() {
+ return mFilterPeriodEnd;
+ }
+ public String getFilterLastActivityEndString() {
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+ Date date = new Date(mFilterPeriodEnd);
+ return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+ }
+
+ public void setFilterLastActivityEnd(long filterPeriodEnd) {
+ this.mFilterPeriodEnd= filterPeriodEnd; //er reuse the same
+ }
+
+ public void setFilterPeriodEnd(String filterPeriodEnd) throws ParseException {
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+ Date date = format.parse(filterPeriodEnd);
+ this.mFilterPeriodEnd = date.getTime();
+ }
public String getFilterPeriodEndString() {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
Date date = new Date(mFilterPeriodEnd);
@@ -601,12 +904,11 @@
this.mFilterPeriodEnd = filterPeriodEnd;
}
- public void setFilterPeriodEnd(String filterPeriodEnd) throws ParseException {
+ public void setFilterLastActivityEnd(String filterPeriodEnd) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
Date date = format.parse(filterPeriodEnd);
this.mFilterPeriodEnd = date.getTime();
}
-
public int getFilterReadStatus() {
return mFilterReadStatus;
}
@@ -643,6 +945,190 @@
this.mFilterPriority = filterPriority;
}
+ public void setDatabaseIdentifier(long idHigh, long idLow) {
+ this.mDatabaseIdentifierHigh = idHigh;
+ this.mDatabaseIdentifierLow = idLow;
+ }
+
+ public byte[] getDatabaseIdentifier() {
+ if(mDatabaseIdentifierLow != INVALID_VALUE_PARAMETER
+ && mDatabaseIdentifierHigh != INVALID_VALUE_PARAMETER) {
+ ByteBuffer ret = ByteBuffer.allocate(16);
+ ret.putLong(mDatabaseIdentifierHigh);
+ ret.putLong(mDatabaseIdentifierLow);
+ return ret.array();
+ }else return null;
+ }
+
+ public void setConvoListingVerCounter(long countLow, long countHigh) {
+ this.mConvoListingVerCounterHigh = countHigh;
+ this.mConvoListingVerCounterLow = countLow;
+ }
+
+ public byte[] getConvoListingVerCounter(){
+ if(mConvoListingVerCounterHigh != INVALID_VALUE_PARAMETER &&
+ mConvoListingVerCounterLow != INVALID_VALUE_PARAMETER) {
+ ByteBuffer ret = ByteBuffer.allocate(16);
+ ret.putLong(mConvoListingVerCounterHigh);
+ ret.putLong(mConvoListingVerCounterLow);
+ return ret.array();
+ } else return null;
+ }
+
+ public void setFolderVerCounter(long countLow, long countHigh) {
+ this.mFolderVerCounterHigh = countHigh;
+ this.mFolderVerCounterLow = countLow;
+ }
+
+ public byte[] getFolderVerCounter(){
+ if(mFolderVerCounterHigh != INVALID_VALUE_PARAMETER &&
+ mFolderVerCounterLow != INVALID_VALUE_PARAMETER) {
+ ByteBuffer ret = ByteBuffer.allocate(16);
+ ret.putLong(mFolderVerCounterHigh);
+ ret.putLong(mFolderVerCounterLow);
+ return ret.array();
+ } else return null;
+ }
+
+ public SignedLongLong getChatStateConvoId(){
+ return mChatStateConvoId;
+ }
+
+ public byte[] getChatStateConvoIdByteArray() {
+ if(mChatStateConvoId != null) {
+ ByteBuffer ret = ByteBuffer.allocate(16);
+ ret.putLong(mChatStateConvoId.getMostSignificantBits());
+ ret.putLong(mChatStateConvoId.getLeastSignificantBits());
+ return ret.array();
+ } else return null;
+ }
+
+ public String getChatStateConvoIdString() {
+ String str = null;
+ str = new String(this.getChatStateConvoIdByteArray());
+ return str;
+ }
+
+ public void setChatStateConvoId(long idHigh, long idLow) {
+ mChatStateConvoId = new SignedLongLong(idLow, idHigh);
+ }
+
+ public void setFilterMsgHandle(String handle) {
+ try {
+ mFilterMsgHandle = BluetoothMapUtils.getLongFromString(handle);
+ } catch (UnsupportedEncodingException e) {
+ Log.w(TAG,"Error creating long from handle string", e);
+ }
+ }
+
+ public long getFilterMsgHandle(){
+ return mFilterMsgHandle;
+ }
+
+ public String getFilterMsgHandleString() {
+ String str = null;
+ if(mFilterMsgHandle != INVALID_VALUE_PARAMETER) {
+ str = BluetoothMapUtils.getLongAsString(mFilterMsgHandle);
+ }
+ return str;
+ }
+
+ public int getFilterUidPresent() {
+ return mFilterUidPresent;
+ }
+
+ public void setFilterUidPresent(int present) {
+ if (present < 0 || present > 0x00FF)
+ throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x00FF");
+ this.mFilterUidPresent = present;
+ }
+
+ public int getFilterPresence() {
+ return mFilterPresence;
+ }
+
+
+
+ public SignedLongLong getFilterConvoId(){
+ return mFilterConvoId;
+ }
+
+ /**
+ * Get a decimal representation of the lower bits of the ConvoId - used for queries.
+ * The upper bits are used for convo-type.
+ * @return decimal representation of the convo ID.
+ */
+ public String getFilterConvoIdString() {
+ String str = null;
+ if(mFilterConvoId != null) {
+ str = BluetoothMapUtils.getLongAsString(mFilterConvoId.getLeastSignificantBits());
+ }
+ return str;
+ }
+
+
+ public void setFilterConvoId(String id) {
+ try {
+ mFilterConvoId = SignedLongLong.fromString(id);
+ } catch (UnsupportedEncodingException e) {
+ Log.w(TAG,"Error creating long from id string", e);
+ }
+ }
+
+
+ public void setChatState(int state) {
+ if (state < 0 || state > 0x00FF)
+ throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x00FF");
+ this.mChatState = state;
+ }
+
+ public int getChatState() {
+ return mChatState;
+ }
+
+ public long getLastActivity(){
+ return this.mLastActivity;
+ }
+ public String getLastActivityString(){
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmssZ");
+ Date date = new Date(mLastActivity);
+ return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+ }
+ public void setLastActivity(long last){
+ this.mLastActivity = last;
+ }
+ public void setLastActivity(String lastActivity) throws ParseException {
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmssZ");
+ Date date = format.parse(lastActivity);
+ this.mLastActivity = date.getTime();
+ }
+
+ public void setPresenceStatus(String status){
+ this.mPresenceStatus = status;
+ }
+ public String getPresenceStatus(){
+ return this.mPresenceStatus;
+ }
+
+ public void setFilterPresence(int presence) {
+ if (presence < 0 || presence > 0xFFFF)
+ throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFF");
+ this.mFilterPresence = presence;
+ }
+
+ public void setPresenceAvailability(int availability) {
+ if (availability < 0 || availability > 0x00FF)
+ throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x00FF");
+ this.mPresenceAvailability = availability;
+ }
+
+ public int getPresenceAvailability() {
+ return mPresenceAvailability;
+ }
+
+ public int getSubjectLength() {
+ return mSubjectLength;
+ }
public int getAttachment() {
return mAttachment;
}
@@ -693,6 +1179,17 @@
this.mNotificationStatus = notificationStatus;
}
+ public long getNotificationFilter() {
+ return mNotificationFilter;
+ }
+
+ public void setNotificationFilter(long notificationFilter) throws IllegalArgumentException {
+ if (notificationFilter < 0 || notificationFilter > 0xFFFFFFFFL)
+ throw new IllegalArgumentException(
+ "Out of range, valid range is 0x0000 to 0xFFFFFFFFL");
+ this.mNotificationFilter = notificationFilter;
+ }
+
public int getMasInstanceId() {
return mMasInstanceId;
}
@@ -713,6 +1210,16 @@
this.mParameterMask = parameterMask;
}
+ public void setConvoParameterMask(long parameterMask) {
+ if (parameterMask < 0 || parameterMask > 0xFFFFFFFFL)
+ throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFFFFFF");
+ this.mConvoParameterMask = parameterMask;
+ }
+
+ public long getConvoParameterMask(){
+ return mConvoParameterMask;
+ }
+
public int getFolderListingSize() {
return mFolderListingSize;
}
@@ -733,10 +1240,15 @@
this.mMessageListingSize = messageListingSize;
}
- public int getSubjectLength() {
- return mSubjectLength;
+ public int getConvoListingSize() {
+ return mConvoListingSize;
}
+ public void setConvoListingSize(int convoListingSize) {
+ if (convoListingSize < 0 || convoListingSize > 0xFFFF)
+ throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFF");
+ this.mConvoListingSize = convoListingSize;
+ }
public void setSubjectLength(int subjectLength) {
if (subjectLength < 0 || subjectLength > 0xFF)
throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x00FF");
@@ -812,4 +1324,8 @@
Date date = format.parse(mseTime);
this.mMseTime = date.getTime();
}
+
+
+
+
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapContent.java b/src/com/android/bluetooth/map/BluetoothMapContent.java
index 540cbbb..dfc10f0 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -14,34 +14,35 @@
*/
package com.android.bluetooth.map;
-import org.apache.http.util.ByteArrayBuffer;
-
+import android.annotation.TargetApi;
import android.content.ContentResolver;
-import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
-import android.os.Debug;
+import android.net.Uri.Builder;
import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns;
-import com.android.bluetooth.mapapi.BluetoothMapContract;
-import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.Telephony.Mms;
import android.provider.Telephony.Sms;
+import android.provider.Telephony.Threads;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
import android.util.Log;
+import android.util.SparseArray;
-import com.android.bluetooth.map.BluetoothMapSmsPdu.SmsPdu;
+import com.android.bluetooth.SignedLongLong;
+import com.android.bluetooth.map.BluetoothMapContentObserver.Msg;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+import com.android.bluetooth.mapapi.BluetoothMapContract.ConversationColumns;
import com.google.android.mms.pdu.CharacterSets;
import com.google.android.mms.pdu.PduHeaders;
-import com.android.bluetooth.map.BluetoothMapAppParams;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
@@ -50,51 +51,103 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
-import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+@TargetApi(19)
public class BluetoothMapContent {
+
private static final String TAG = "BluetoothMapContent";
private static final boolean D = BluetoothMapService.DEBUG;
private static final boolean V = BluetoothMapService.VERBOSE;
- private static final int MASK_SUBJECT = 0x1;
- private static final int MASK_DATETIME = 0x2;
- private static final int MASK_SENDER_NAME = 0x4;
- private static final int MASK_SENDER_ADDRESSING = 0x8;
+ // Parameter Mask for selection of parameters to return in listings
+ private static final int MASK_SUBJECT = 0x00000001;
+ private static final int MASK_DATETIME = 0x00000002;
+ private static final int MASK_SENDER_NAME = 0x00000004;
+ private static final int MASK_SENDER_ADDRESSING = 0x00000008;
+ private static final int MASK_RECIPIENT_NAME = 0x00000010;
+ private static final int MASK_RECIPIENT_ADDRESSING = 0x00000020;
+ private static final int MASK_TYPE = 0x00000040;
+ private static final int MASK_SIZE = 0x00000080;
+ private static final int MASK_RECEPTION_STATUS = 0x00000100;
+ private static final int MASK_TEXT = 0x00000200;
+ private static final int MASK_ATTACHMENT_SIZE = 0x00000400;
+ private static final int MASK_PRIORITY = 0x00000800;
+ private static final int MASK_READ = 0x00001000;
+ private static final int MASK_SENT = 0x00002000;
+ private static final int MASK_PROTECTED = 0x00004000;
+ private static final int MASK_REPLYTO_ADDRESSING = 0x00008000;
+ // TODO: Duplicate in proposed spec
+ // private static final int MASK_RECEPTION_STATE = 0x00010000;
+ private static final int MASK_DELIVERY_STATUS = 0x00020000;
+ private static final int MASK_CONVERSATION_ID = 0x00040000;
+ private static final int MASK_CONVERSATION_NAME = 0x00080000;
+ private static final int MASK_FOLDER_TYPE = 0x00100000;
+ // TODO: about to be removed from proposed spec
+ // private static final int MASK_SEQUENCE_NUMBER = 0x00200000;
+ private static final int MASK_ATTACHMENT_MIME = 0x00400000;
- private static final int MASK_RECIPIENT_NAME = 0x10;
- private static final int MASK_RECIPIENT_ADDRESSING = 0x20;
- private static final int MASK_TYPE = 0x40;
- private static final int MASK_SIZE = 0x80;
+ private static final int CONVO_PARAM_MASK_CONVO_NAME = 0x00000001;
+ private static final int CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY = 0x00000002;
+ private static final int CONVO_PARAM_MASK_CONVO_READ_STATUS = 0x00000004;
+ private static final int CONVO_PARAM_MASK_CONVO_VERSION_COUNTER = 0x00000008;
+ private static final int CONVO_PARAM_MASK_CONVO_SUMMARY = 0x00000010;
+ private static final int CONVO_PARAM_MASK_PARTTICIPANTS = 0x00000020;
+ private static final int CONVO_PARAM_MASK_PART_UCI = 0x00000040;
+ private static final int CONVO_PARAM_MASK_PART_DISP_NAME = 0x00000080;
+ private static final int CONVO_PARAM_MASK_PART_CHAT_STATE = 0x00000100;
+ private static final int CONVO_PARAM_MASK_PART_LAST_ACTIVITY = 0x00000200;
+ private static final int CONVO_PARAM_MASK_PART_X_BT_UID = 0x00000400;
+ private static final int CONVO_PARAM_MASK_PART_NAME = 0x00000800;
+ private static final int CONVO_PARAM_MASK_PART_PRESENCE = 0x00001000;
+ private static final int CONVO_PARAM_MASK_PART_PRESENCE_TEXT = 0x00002000;
+ private static final int CONVO_PARAM_MASK_PART_PRIORITY = 0x00004000;
- private static final int MASK_RECEPTION_STATUS = 0x100;
- private static final int MASK_TEXT = 0x200;
- private static final int MASK_ATTACHMENT_SIZE = 0x400;
- private static final int MASK_PRIORITY = 0x800;
+ /* Default values for omitted or 0 parameterMask application parameters */
+ // MAP specification states that the default value for parameter mask are
+ // the #REQUIRED attributes in the DTD, and not all enabled
+ public static final long PARAMETER_MASK_ALL_ENABLED = 0xFFFFFFFFL;
+ public static final long PARAMETER_MASK_DEFAULT = 0x5E3L;
+ public static final long CONVO_PARAMETER_MASK_ALL_ENABLED = 0xFFFFFFFFL;
+ public static final long CONVO_PARAMETER_MASK_DEFAULT =
+ CONVO_PARAM_MASK_CONVO_NAME |
+ CONVO_PARAM_MASK_PARTTICIPANTS |
+ CONVO_PARAM_MASK_PART_UCI |
+ CONVO_PARAM_MASK_PART_DISP_NAME;
- private static final int MASK_READ = 0x1000;
- private static final int MASK_SENT = 0x2000;
- private static final int MASK_PROTECTED = 0x4000;
- private static final int MASK_REPLYTO_ADDRESSING = 0x8000;
+
+
+
+ private static final int FILTER_READ_STATUS_UNREAD_ONLY = 0x01;
+ private static final int FILTER_READ_STATUS_READ_ONLY = 0x02;
+ private static final int FILTER_READ_STATUS_ALL = 0x00;
/* Type of MMS address. From Telephony.java it must be one of PduHeaders.BCC, */
/* PduHeaders.CC, PduHeaders.FROM, PduHeaders.TO. These are from PduHeaders.java */
- public static final int MMS_FROM = 0x89;
- public static final int MMS_TO = 0x97;
- public static final int MMS_BCC = 0x81;
- public static final int MMS_CC = 0x82;
+ public static final int MMS_FROM = 0x89;
+ public static final int MMS_TO = 0x97;
+ public static final int MMS_BCC = 0x81;
+ public static final int MMS_CC = 0x82;
public static final String INSERT_ADDRES_TOKEN = "insert-address-token";
- private Context mContext;
- private ContentResolver mResolver;
- private String mBaseEmailUri = null;
+ private final Context mContext;
+ private final ContentResolver mResolver;
+ private final String mBaseUri;
+ private final BluetoothMapAccountItem mAccount;
+ /* The MasInstance reference is used to update persistent (over a connection) version counters*/
+ private final BluetoothMapMasInstance mMasInstance;
+ private String mMessageVersion = BluetoothMapUtils.MAP_V10_STR;
+
+ private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
+ private int mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10;
static final String[] SMS_PROJECTION = new String[] {
BaseColumns._ID,
@@ -122,67 +175,237 @@
Mms.READ,
Mms.MESSAGE_BOX,
Mms.STATUS,
- Mms.PRIORITY
+ Mms.PRIORITY,
};
- private class FilterInfo {
- public static final int TYPE_SMS = 0;
- public static final int TYPE_MMS = 1;
- public static final int TYPE_EMAIL = 2;
+ static final String[] SMS_CONVO_PROJECTION = new String[] {
+ BaseColumns._ID,
+ Sms.THREAD_ID,
+ Sms.ADDRESS,
+ Sms.DATE,
+ Sms.READ,
+ Sms.TYPE,
+ Sms.STATUS,
+ Sms.LOCKED,
+ Sms.ERROR_CODE
+ };
+ static final String[] MMS_CONVO_PROJECTION = new String[] {
+ BaseColumns._ID,
+ Mms.THREAD_ID,
+ Mms.MESSAGE_ID,
+ Mms.MESSAGE_SIZE,
+ Mms.SUBJECT,
+ Mms.CONTENT_TYPE,
+ Mms.TEXT_ONLY,
+ Mms.DATE,
+ Mms.DATE_SENT,
+ Mms.READ,
+ Mms.MESSAGE_BOX,
+ Mms.STATUS,
+ Mms.PRIORITY,
+ Mms.Addr.ADDRESS
+ };
+
+ /* CONVO LISTING projections and column indexes */
+ private static final String[] MMS_SMS_THREAD_PROJECTION = {
+ Threads._ID,
+ Threads.DATE,
+ Threads.SNIPPET,
+ Threads.SNIPPET_CHARSET,
+ Threads.READ,
+ Threads.RECIPIENT_IDS
+ };
+
+ private static final String[] CONVO_VERSION_PROJECTION = new String[] {
+ /* Thread information */
+ ConversationColumns.THREAD_ID,
+ ConversationColumns.THREAD_NAME,
+ ConversationColumns.READ_STATUS,
+ ConversationColumns.LAST_THREAD_ACTIVITY,
+ ConversationColumns.SUMMARY,
+ };
+
+ /* Optimize the Cursor access to avoid the need to do a getColumnIndex() */
+ private static final int MMS_SMS_THREAD_COL_ID;
+ private static final int MMS_SMS_THREAD_COL_DATE;
+ private static final int MMS_SMS_THREAD_COL_SNIPPET;
+ private static final int MMS_SMS_THREAD_COL_SNIPPET_CS;
+ private static final int MMS_SMS_THREAD_COL_READ;
+ private static final int MMS_SMS_THREAD_COL_RECIPIENT_IDS;
+ static {
+ // TODO: This might not work, if the projection is mapped in the content provider...
+ // Change to init at first query? (Current use in the AOSP code is hard coded values
+ // unrelated to the projection used)
+ List<String> projection = Arrays.asList(MMS_SMS_THREAD_PROJECTION);
+ MMS_SMS_THREAD_COL_ID = projection.indexOf(Threads._ID);
+ MMS_SMS_THREAD_COL_DATE = projection.indexOf(Threads.DATE);
+ MMS_SMS_THREAD_COL_SNIPPET = projection.indexOf(Threads.SNIPPET);
+ MMS_SMS_THREAD_COL_SNIPPET_CS = projection.indexOf(Threads.SNIPPET_CHARSET);
+ MMS_SMS_THREAD_COL_READ = projection.indexOf(Threads.READ);
+ MMS_SMS_THREAD_COL_RECIPIENT_IDS = projection.indexOf(Threads.RECIPIENT_IDS);
+ }
+
+ private class FilterInfo {
+ public static final int TYPE_SMS = 0;
+ public static final int TYPE_MMS = 1;
+ public static final int TYPE_EMAIL = 2;
+ public static final int TYPE_IM = 3;
+
+ // TODO: Change to ENUM, to ensure correct usage
int mMsgType = TYPE_SMS;
int mPhoneType = 0;
String mPhoneNum = null;
String mPhoneAlphaTag = null;
/*column indices used to optimize queries */
- public int mEmailColThreadId = -1;
- public int mEmailColProtected = -1;
- public int mEmailColFolder = -1;
- public int mMmsColFolder = -1;
+ public int mMessageColId = -1;
+ public int mMessageColDate = -1;
+ public int mMessageColBody = -1;
+ public int mMessageColSubject = -1;
+ public int mMessageColFolder = -1;
+ public int mMessageColRead = -1;
+ public int mMessageColSize = -1;
+ public int mMessageColFromAddress = -1;
+ public int mMessageColToAddress = -1;
+ public int mMessageColCcAddress = -1;
+ public int mMessageColBccAddress = -1;
+ public int mMessageColReplyTo = -1;
+ public int mMessageColAccountId = -1;
+ public int mMessageColAttachment = -1;
+ public int mMessageColAttachmentSize = -1;
+ public int mMessageColAttachmentMime = -1;
+ public int mMessageColPriority = -1;
+ public int mMessageColProtected = -1;
+ public int mMessageColReception = -1;
+ public int mMessageColDelivery = -1;
+ public int mMessageColThreadId = -1;
+ public int mMessageColThreadName = -1;
+
public int mSmsColFolder = -1;
- public int mEmailColRead = -1;
public int mSmsColRead = -1;
- public int mMmsColRead = -1;
- public int mEmailColPriority = -1;
- public int mMmsColAttachmentSize = -1;
- public int mEmailColAttachment = -1;
- public int mEmailColAttachementSize = -1;
- public int mMmsColTextOnly = -1;
- public int mMmsColId = -1;
public int mSmsColId = -1;
- public int mEmailColSize = -1;
public int mSmsColSubject = -1;
- public int mMmsColSize = -1;
- public int mEmailColToAddress = -1;
- public int mEmailColCcAddress = -1;
- public int mEmailColBccAddress = -1;
public int mSmsColAddress = -1;
public int mSmsColDate = -1;
- public int mMmsColDate = -1;
- public int mEmailColDate = -1;
- public int mMmsColSubject = -1;
- public int mEmailColSubject = -1;
public int mSmsColType = -1;
- public int mEmailColFromAddress = -1;
- public int mEmailColId = -1;
+ public int mSmsColThreadId = -1;
+
+ public int mMmsColRead = -1;
+ public int mMmsColFolder = -1;
+ public int mMmsColAttachmentSize = -1;
+ public int mMmsColTextOnly = -1;
+ public int mMmsColId = -1;
+ public int mMmsColSize = -1;
+ public int mMmsColDate = -1;
+ public int mMmsColSubject = -1;
+ public int mMmsColThreadId = -1;
+
+ public int mConvoColConvoId = -1;
+ public int mConvoColLastActivity = -1;
+ public int mConvoColName = -1;
+ public int mConvoColRead = -1;
+ public int mConvoColVersionCounter = -1;
+ public int mConvoColSummary = -1;
+ public int mContactColBtUid = -1;
+ public int mContactColChatState = -1;
+ public int mContactColContactUci = -1;
+ public int mContactColNickname = -1;
+ public int mContactColLastActive = -1;
+ public int mContactColName = -1;
+ public int mContactColPresenceState = -1;
+ public int mContactColPresenceText = -1;
+ public int mContactColPriority = -1;
- public void setEmailColumns(Cursor c) {
- mEmailColThreadId = c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_ID);
- mEmailColProtected = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_PROTECTED);
- mEmailColFolder = c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID);
- mEmailColRead = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ);
- mEmailColPriority = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY);
- mEmailColAttachment = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_ATTACHMENT);
- mEmailColAttachementSize = c.getColumnIndex(BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE);
- mEmailColSize = c.getColumnIndex(BluetoothMapContract.MessageColumns.MESSAGE_SIZE);
- mEmailColToAddress = c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST);
- mEmailColCcAddress = c.getColumnIndex(BluetoothMapContract.MessageColumns.CC_LIST);
- mEmailColBccAddress = c.getColumnIndex(BluetoothMapContract.MessageColumns.BCC_LIST);
- mEmailColDate = c.getColumnIndex(BluetoothMapContract.MessageColumns.DATE);
- mEmailColSubject = c.getColumnIndex(BluetoothMapContract.MessageColumns.SUBJECT);
- mEmailColFromAddress = c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST);
- mEmailColId = c.getColumnIndex(BluetoothMapContract.MessageColumns._ID);
+ public void setMessageColumns(Cursor c) {
+ mMessageColId = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns._ID);
+ mMessageColDate = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.DATE);
+ mMessageColSubject = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.SUBJECT);
+ mMessageColFolder = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.FOLDER_ID);
+ mMessageColRead = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.FLAG_READ);
+ mMessageColSize = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.MESSAGE_SIZE);
+ mMessageColFromAddress = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.FROM_LIST);
+ mMessageColToAddress = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.TO_LIST);
+ mMessageColAttachment = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.FLAG_ATTACHMENT);
+ mMessageColAttachmentSize = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE);
+ mMessageColPriority = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY);
+ mMessageColProtected = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.FLAG_PROTECTED);
+ mMessageColReception = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.RECEPTION_STATE);
+ mMessageColDelivery = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.DEVILERY_STATE);
+ mMessageColThreadId = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.THREAD_ID);
+ }
+
+ public void setEmailMessageColumns(Cursor c) {
+ setMessageColumns(c);
+ mMessageColCcAddress = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.CC_LIST);
+ mMessageColBccAddress = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.BCC_LIST);
+ mMessageColReplyTo = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.REPLY_TO_LIST);
+ }
+
+ public void setImMessageColumns(Cursor c) {
+ setMessageColumns(c);
+ mMessageColThreadName = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.THREAD_NAME);
+ mMessageColAttachmentMime = c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.ATTACHMENT_MINE_TYPES);
+ //TODO this is temporary as text should come from parts table instead
+ mMessageColBody = c.getColumnIndex(BluetoothMapContract.MessageColumns.BODY);
+
+ }
+
+ public void setEmailImConvoColumns(Cursor c) {
+ mConvoColConvoId = c.getColumnIndex(
+ BluetoothMapContract.ConversationColumns.THREAD_ID);
+ mConvoColLastActivity = c.getColumnIndex(
+ BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY);
+ mConvoColName = c.getColumnIndex(
+ BluetoothMapContract.ConversationColumns.THREAD_NAME);
+ mConvoColRead = c.getColumnIndex(
+ BluetoothMapContract.ConversationColumns.READ_STATUS);
+ mConvoColVersionCounter = c.getColumnIndex(
+ BluetoothMapContract.ConversationColumns.VERSION_COUNTER);
+ mConvoColSummary = c.getColumnIndex(
+ BluetoothMapContract.ConversationColumns.SUMMARY);
+ setEmailImConvoContactColumns(c);
+ }
+
+ public void setEmailImConvoContactColumns(Cursor c){
+ mContactColBtUid = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.X_BT_UID);
+ mContactColChatState = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.CHAT_STATE);
+ mContactColContactUci = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.UCI);
+ mContactColNickname = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.NICKNAME);
+ mContactColLastActive = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE);
+ mContactColName = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.NAME);
+ mContactColPresenceState = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE);
+ mContactColPresenceText = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.STATUS_TEXT);
+ mContactColPriority = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.PRIORITY);
}
public void setSmsColumns(Cursor c) {
@@ -191,8 +414,9 @@
mSmsColRead = c.getColumnIndex(Sms.READ);
mSmsColSubject = c.getColumnIndex(Sms.BODY);
mSmsColAddress = c.getColumnIndex(Sms.ADDRESS);
- mSmsColDate = c.getColumnIndex(Sms.DATE);
- mSmsColType = c.getColumnIndex(Sms.TYPE);
+ mSmsColDate = c.getColumnIndex(Sms.DATE);
+ mSmsColType = c.getColumnIndex(Sms.TYPE);
+ mSmsColThreadId= c.getColumnIndex(Sms.THREAD_ID);
}
public void setMmsColumns(Cursor c) {
@@ -204,32 +428,40 @@
mMmsColSize = c.getColumnIndex(Mms.MESSAGE_SIZE);
mMmsColDate = c.getColumnIndex(Mms.DATE);
mMmsColSubject = c.getColumnIndex(Mms.SUBJECT);
-
+ mMmsColThreadId = c.getColumnIndex(Mms.THREAD_ID);
}
}
- public BluetoothMapContent(final Context context, String emailBaseUri) {
+ public BluetoothMapContent(final Context context, BluetoothMapAccountItem account,
+ BluetoothMapMasInstance mas) {
mContext = context;
mResolver = mContext.getContentResolver();
+ mMasInstance = mas;
if (mResolver == null) {
if (D) Log.d(TAG, "getContentResolver failed");
}
- mBaseEmailUri = emailBaseUri;
- }
+ if(account != null){
+ mBaseUri = account.mBase_uri + "/";
+ mAccount = account;
+ } else {
+ mBaseUri = null;
+ mAccount = null;
+ }
+ }
private static void close(Closeable c) {
try {
if (c != null) c.close();
} catch (IOException e) {
}
}
-
private void setProtected(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_PROTECTED) != 0) {
String protect = "no";
- if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- int flagProtected = c.getInt(fi.mEmailColProtected);
+ if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
+ int flagProtected = c.getInt(fi.mMessageColProtected);
if (flagProtected == 1) {
protect = "yes";
}
@@ -239,18 +471,39 @@
}
}
- /**
- * Email only
- */
private void setThreadId(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
- if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- long threadId = c.getLong(fi.mEmailColThreadId);
- e.setThreadId(threadId);
+ if ((ap.getParameterMask() & MASK_CONVERSATION_ID) != 0) {
+ long threadId = 0;
+ TYPE type = TYPE.SMS_GSM; // Just used for handle encoding
+ if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+ threadId = c.getLong(fi.mSmsColThreadId);
+ } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+ threadId = c.getLong(fi.mMmsColThreadId);
+ type = TYPE.MMS;// Just used for handle encoding
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
+ threadId = c.getLong(fi.mMessageColThreadId);
+ type = TYPE.EMAIL;// Just used for handle encoding
+ }
+ e.setThreadId(threadId,type);
if (V) Log.d(TAG, "setThreadId: " + threadId + "\n");
}
}
+ private void setThreadName(BluetoothMapMessageListingElement e, Cursor c,
+ FilterInfo fi, BluetoothMapAppParams ap) {
+ // TODO: Maybe this should be valid for SMS/MMS
+ if ((ap.getParameterMask() & MASK_CONVERSATION_NAME) != 0) {
+ if (fi.mMsgType == FilterInfo.TYPE_IM) {
+ String threadName = c.getString(fi.mMessageColThreadName);
+ e.setThreadName(threadName);
+ if (V) Log.d(TAG, "setThreadName: " + threadName + "\n");
+ }
+ }
+ }
+
+
private void setSent(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_SENT) != 0) {
@@ -259,8 +512,9 @@
msgType = c.getInt(fi.mSmsColFolder);
} else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
msgType = c.getInt(fi.mMmsColFolder);
- } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- msgType = c.getInt(fi.mEmailColFolder);
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
+ msgType = c.getInt(fi.mMessageColFolder);
}
String sent = null;
if (msgType == 2) {
@@ -280,21 +534,33 @@
read = c.getInt(fi.mSmsColRead);
} else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
read = c.getInt(fi.mMmsColRead);
- } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- read = c.getInt(fi.mEmailColRead);
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
+ read = c.getInt(fi.mMessageColRead);
}
String setread = null;
if (V) Log.d(TAG, "setRead: " + setread);
e.setRead((read==1?true:false), ((ap.getParameterMask() & MASK_READ) != 0));
}
+ private void setConvoRead(BluetoothMapConvoListingElement e, Cursor c,
+ FilterInfo fi, BluetoothMapAppParams ap) {
+ String setread = null;
+ int read = 0;
+ read = c.getInt(fi.mConvoColRead);
+
+
+ if (V) Log.d(TAG, "setRead: " + setread);
+ e.setRead((read==1?true:false), ((ap.getParameterMask() & MASK_READ) != 0));
+ }
private void setPriority(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_PRIORITY) != 0) {
String priority = "no";
- if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- int highPriority = c.getInt(fi.mEmailColPriority);
+ if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
+ int highPriority = c.getInt(fi.mMessageColPriority);
if (highPriority == 1) {
priority = "yes";
}
@@ -318,10 +584,11 @@
* the total message size. To provide a more accurate attachment size, one could
* extract the length (in bytes) of the text parts.
*/
- private void setAttachmentSize(BluetoothMapMessageListingElement e, Cursor c,
+ private void setAttachment(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_ATTACHMENT_SIZE) != 0) {
int size = 0;
+ String attachmentMimeTypes = null;
if (fi.mMsgType == FilterInfo.TYPE_MMS) {
if(c.getInt(fi.mMmsColTextOnly) == 0) {
size = c.getInt(fi.mMmsColAttachmentSize);
@@ -333,10 +600,11 @@
+ " Changing size to 1");
size = 1;
}
+ // TODO: Add handling of attachemnt mime types
}
} else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- int attachment = c.getInt(fi.mEmailColAttachment);
- size = c.getInt(fi.mEmailColAttachementSize);
+ int attachment = c.getInt(fi.mMessageColAttachment);
+ size = c.getInt(fi.mMessageColAttachmentSize);
if(attachment == 1 && size == 0) {
if (D) Log.d(TAG, "Error in message database, attachment size reported as: " + size
+ " Changing size to 1");
@@ -344,9 +612,24 @@
message has attachments, in case the e-mail client do not
report a size */
}
+ } else if (fi.mMsgType == FilterInfo.TYPE_IM) {
+ int attachment = c.getInt(fi.mMessageColAttachment);
+ size = c.getInt(fi.mMessageColAttachmentSize);
+ if(attachment == 1 && size == 0) {
+ size = 1; /* Ensure we indicate we have attachments in the size, it the
+ message has attachments, in case the e-mail client do not
+ report a size */
+ attachmentMimeTypes = c.getString(fi.mMessageColAttachmentMime);
+ }
}
- if (V) Log.d(TAG, "setAttachmentSize: " + size);
+ if (V) Log.d(TAG, "setAttachmentSize: " + size + "\n" +
+ "setAttachmentMimeTypes: " + attachmentMimeTypes );
e.setAttachmentSize(size);
+
+ if( (mMsgListingVersion > BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10)
+ && ((ap.getParameterMask() & MASK_ATTACHMENT_MIME) != 0) ){
+ e.setAttachmentMimeTypes(attachmentMimeTypes);
+ }
}
}
@@ -362,14 +645,15 @@
hasText = "yes";
} else {
long id = c.getLong(fi.mMmsColId);
- String text = getTextPartsMms(id);
+ String text = getTextPartsMms(mResolver, id);
if (text != null && text.length() > 0) {
hasText = "yes";
} else {
hasText = "no";
}
}
- } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
hasText = "yes";
}
if (V) Log.d(TAG, "setText: " + hasText);
@@ -386,6 +670,20 @@
}
}
+ private void setDeliveryStatus(BluetoothMapMessageListingElement e, Cursor c,
+ FilterInfo fi, BluetoothMapAppParams ap) {
+ if ((ap.getParameterMask() & MASK_DELIVERY_STATUS) != 0) {
+ String deliveryStatus = "delivered";
+ // TODO: Should be handled for SMS and MMS as well
+ if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
+ deliveryStatus = c.getString(fi.mMessageColDelivery);
+ }
+ if (V) Log.d(TAG, "setDeliveryStatus: " + deliveryStatus);
+ e.setDeliveryStatus(deliveryStatus);
+ }
+ }
+
private void setSize(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_SIZE) != 0) {
@@ -395,8 +693,9 @@
size = subject.length();
} else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
size = c.getInt(fi.mMmsColSize);
- } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- size = c.getInt(fi.mEmailColSize);
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
+ size = c.getInt(fi.mMessageColSize);
}
if(size <= 0) {
// A message cannot have size 0
@@ -411,49 +710,209 @@
}
}
- private void setType(BluetoothMapMessageListingElement e, Cursor c,
- FilterInfo fi, BluetoothMapAppParams ap) {
- if ((ap.getParameterMask() & MASK_TYPE) != 0) {
- TYPE type = null;
- if (fi.mMsgType == FilterInfo.TYPE_SMS) {
- if (fi.mPhoneType == TelephonyManager.PHONE_TYPE_GSM) {
- type = TYPE.SMS_GSM;
- } else if (fi.mPhoneType == TelephonyManager.PHONE_TYPE_CDMA) {
- type = TYPE.SMS_CDMA;
- }
- } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
- type = TYPE.MMS;
- } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- type = TYPE.EMAIL;
+ private TYPE getType(Cursor c, FilterInfo fi) {
+ TYPE type = null;
+ if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+ if (fi.mPhoneType == TelephonyManager.PHONE_TYPE_GSM) {
+ type = TYPE.SMS_GSM;
+ } else if (fi.mPhoneType == TelephonyManager.PHONE_TYPE_CDMA) {
+ type = TYPE.SMS_CDMA;
}
- if (V) Log.d(TAG, "setType: " + type);
- e.setType(type);
+ } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+ type = TYPE.MMS;
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
+ type = TYPE.EMAIL;
+ } else if (fi.mMsgType == FilterInfo.TYPE_IM) {
+ type = TYPE.IM;
+ }
+ if (V) Log.d(TAG, "getType: " + type);
+
+ return type;
+ }
+ private void setFolderType(BluetoothMapMessageListingElement e, Cursor c,
+ FilterInfo fi, BluetoothMapAppParams ap) {
+ if ((ap.getParameterMask() & MASK_FOLDER_TYPE) != 0) {
+ String folderType = null;
+ int folderId = 0;
+ if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+ folderId = c.getInt(fi.mSmsColFolder);
+ if (folderId == 1)
+ folderType = BluetoothMapContract.FOLDER_NAME_INBOX;
+ else if (folderId == 2)
+ folderType = BluetoothMapContract.FOLDER_NAME_SENT;
+ else if (folderId == 3)
+ folderType = BluetoothMapContract.FOLDER_NAME_DRAFT;
+ else if (folderId == 4 || folderId == 5 || folderId == 6)
+ folderType = BluetoothMapContract.FOLDER_NAME_OUTBOX;
+ else
+ folderType = BluetoothMapContract.FOLDER_NAME_DELETED;
+ } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+ folderId = c.getInt(fi.mMmsColFolder);
+ if (folderId == 1)
+ folderType = BluetoothMapContract.FOLDER_NAME_INBOX;
+ else if (folderId == 2)
+ folderType = BluetoothMapContract.FOLDER_NAME_SENT;
+ else if (folderId == 3)
+ folderType = BluetoothMapContract.FOLDER_NAME_DRAFT;
+ else if (folderId == 4)
+ folderType = BluetoothMapContract.FOLDER_NAME_OUTBOX;
+ else
+ folderType = BluetoothMapContract.FOLDER_NAME_DELETED;
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
+ // TODO: need to find name from id and then set folder type
+ } else if (fi.mMsgType == FilterInfo.TYPE_IM) {
+ folderId = c.getInt(fi.mMessageColFolder);
+ if (folderId == BluetoothMapContract.FOLDER_ID_INBOX)
+ folderType = BluetoothMapContract.FOLDER_NAME_INBOX;
+ else if (folderId == BluetoothMapContract.FOLDER_ID_SENT)
+ folderType = BluetoothMapContract.FOLDER_NAME_SENT;
+ else if (folderId == BluetoothMapContract.FOLDER_ID_DRAFT)
+ folderType = BluetoothMapContract.FOLDER_NAME_DRAFT;
+ else if (folderId == BluetoothMapContract.FOLDER_ID_OUTBOX)
+ folderType = BluetoothMapContract.FOLDER_NAME_OUTBOX;
+ else if (folderId == BluetoothMapContract.FOLDER_ID_DELETED)
+ folderType = BluetoothMapContract.FOLDER_NAME_DELETED;
+ else
+ folderType = BluetoothMapContract.FOLDER_NAME_OTHER;
+ }
+ if (V) Log.d(TAG, "setFolderType: " + folderType);
+ e.setFolderType(folderType);
}
}
- private String setRecipientAddressingEmail(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi) {
- String toAddress, ccAddress, bccAddress;
- toAddress = c.getString(fi.mEmailColToAddress);
- ccAddress = c.getString(fi.mEmailColCcAddress);
- bccAddress = c.getString(fi.mEmailColBccAddress);
+ private String getRecipientNameEmail(BluetoothMapMessageListingElement e,
+ Cursor c,
+ FilterInfo fi) {
- String address = "";
+ String toAddress, ccAddress, bccAddress;
+ toAddress = c.getString(fi.mMessageColToAddress);
+ ccAddress = c.getString(fi.mMessageColCcAddress);
+ bccAddress = c.getString(fi.mMessageColBccAddress);
+
+ StringBuilder sb = new StringBuilder();
if (toAddress != null) {
- address += toAddress;
+ Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(toAddress);
+ if (tokens.length != 0) {
+ if(D) Log.d(TAG, "toName count= " + tokens.length);
+ int i = 0;
+ boolean first = true;
+ while (i < tokens.length) {
+ if(V) Log.d(TAG, "ToName = " + tokens[i].toString());
+ String name = tokens[i].getName();
+ if(!first) sb.append("; "); //Delimiter
+ sb.append(name);
+ first = false;
+ i++;
+ }
+ }
+
if (ccAddress != null) {
- address += ",";
+ sb.append("; ");
}
}
if (ccAddress != null) {
- address += ccAddress;
+ Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(ccAddress);
+ if (tokens.length != 0) {
+ if(D) Log.d(TAG, "ccName count= " + tokens.length);
+ int i = 0;
+ boolean first = true;
+ while (i < tokens.length) {
+ if(V) Log.d(TAG, "ccName = " + tokens[i].toString());
+ String name = tokens[i].getName();
+ if(!first) sb.append("; "); //Delimiter
+ sb.append(name);
+ first = false;
+ i++;
+ }
+ }
if (bccAddress != null) {
- address += ",";
+ sb.append("; ");
}
}
if (bccAddress != null) {
- address += bccAddress;
+ Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(bccAddress);
+ if (tokens.length != 0) {
+ if(D) Log.d(TAG, "bccName count= " + tokens.length);
+ int i = 0;
+ boolean first = true;
+ while (i < tokens.length) {
+ if(V) Log.d(TAG, "bccName = " + tokens[i].toString());
+ String name = tokens[i].getName();
+ if(!first) sb.append("; "); //Delimiter
+ sb.append(name);
+ first = false;
+ i++;
+ }
+ }
}
- return address;
+ return sb.toString();
+ }
+
+ private String getRecipientAddressingEmail(BluetoothMapMessageListingElement e,
+ Cursor c,
+ FilterInfo fi) {
+ String toAddress, ccAddress, bccAddress;
+ toAddress = c.getString(fi.mMessageColToAddress);
+ ccAddress = c.getString(fi.mMessageColCcAddress);
+ bccAddress = c.getString(fi.mMessageColBccAddress);
+
+ StringBuilder sb = new StringBuilder();
+ if (toAddress != null) {
+ Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(toAddress);
+ if (tokens.length != 0) {
+ if(D) Log.d(TAG, "toAddress count= " + tokens.length);
+ int i = 0;
+ boolean first = true;
+ while (i < tokens.length) {
+ if(V) Log.d(TAG, "ToAddress = " + tokens[i].toString());
+ String email = tokens[i].getAddress();
+ if(!first) sb.append("; "); //Delimiter
+ sb.append(email);
+ first = false;
+ i++;
+ }
+ }
+
+ if (ccAddress != null) {
+ sb.append("; ");
+ }
+ }
+ if (ccAddress != null) {
+ Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(ccAddress);
+ if (tokens.length != 0) {
+ if(D) Log.d(TAG, "ccAddress count= " + tokens.length);
+ int i = 0;
+ boolean first = true;
+ while (i < tokens.length) {
+ if(V) Log.d(TAG, "ccAddress = " + tokens[i].toString());
+ String email = tokens[i].getAddress();
+ if(!first) sb.append("; "); //Delimiter
+ sb.append(email);
+ first = false;
+ i++;
+ }
+ }
+ if (bccAddress != null) {
+ sb.append("; ");
+ }
+ }
+ if (bccAddress != null) {
+ Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(bccAddress);
+ if (tokens.length != 0) {
+ if(D) Log.d(TAG, "bccAddress count= " + tokens.length);
+ int i = 0;
+ boolean first = true;
+ while (i < tokens.length) {
+ if(V) Log.d(TAG, "bccAddress = " + tokens[i].toString());
+ String email = tokens[i].getAddress();
+ if(!first) sb.append("; "); //Delimiter
+ sb.append(email);
+ first = false;
+ i++;
+ }
+ }
+ }
+ return sb.toString();
}
private void setRecipientAddressing(BluetoothMapMessageListingElement e, Cursor c,
@@ -472,7 +931,7 @@
address = getAddressMms(mResolver, id, MMS_TO);
} else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
/* Might be another way to handle addresses */
- address = setRecipientAddressingEmail(e, c,fi);
+ address = getRecipientAddressingEmail(e, c,fi);
}
if (V) Log.v(TAG, "setRecipientAddressing: " + address);
if(address == null)
@@ -490,7 +949,7 @@
if (msgType != 1) {
String phone = c.getString(fi.mSmsColAddress);
if (phone != null && !phone.isEmpty())
- name = getContactNameFromPhone(phone);
+ name = getContactNameFromPhone(phone, mResolver);
} else {
name = fi.mPhoneAlphaTag;
}
@@ -503,10 +962,10 @@
phone = e.getRecipientAddressing();
}
if (phone != null && !phone.isEmpty())
- name = getContactNameFromPhone(phone);
+ name = getContactNameFromPhone(phone, mResolver);
} else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
/* Might be another way to handle address and names */
- name = setRecipientAddressingEmail(e,c,fi);
+ name = getRecipientNameEmail(e,c,fi);
}
if (V) Log.v(TAG, "setRecipientName: " + name);
if(name == null)
@@ -518,7 +977,7 @@
private void setSenderAddressing(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_SENDER_ADDRESSING) != 0) {
- String address = null;
+ String address = "";
String tempAddress;
if (fi.mMsgType == FilterInfo.TYPE_SMS) {
int msgType = c.getInt(fi.mSmsColType);
@@ -532,11 +991,13 @@
hence will typically not have any SMS messages. */
} else {
address = PhoneNumberUtils.extractNetworkPortion(tempAddress);
- /* extractNetworkPortion can return N if the number is a service "number" = a string
- * with the a name in (i.e. "Some-Tele-company" would return N because of the N in compaNy)
+ /* extractNetworkPortion can return N if the number is a service "number" =
+ * a string with the a name in (i.e. "Some-Tele-company" would return N
+ * because of the N in compaNy)
* Hence we need to check if the number is actually a string with alpha chars.
* */
- Boolean alpha = PhoneNumberUtils.stripSeparators(tempAddress).matches("[0-9]*[a-zA-Z]+[0-9]*");
+ Boolean alpha = PhoneNumberUtils.stripSeparators(tempAddress).matches(
+ "[0-9]*[a-zA-Z]+[0-9]*");
if(address == null || address.length() < 2 || alpha) {
address = tempAddress; // if the number is a service acsii text just use it
@@ -549,8 +1010,46 @@
if(address == null || address.length() < 1){
address = tempAddress; // if the number is a service acsii text just use it
}
- } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- address = c.getString(fi.mEmailColFromAddress);
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL/* ||
+ fi.mMsgType == FilterInfo.TYPE_IM*/) {
+ String nameEmail = c.getString(fi.mMessageColFromAddress);
+ Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
+ if (tokens.length != 0) {
+ if(D) Log.d(TAG, "Originator count= " + tokens.length);
+ int i = 0;
+ boolean first = true;
+ while (i < tokens.length) {
+ if(V) Log.d(TAG, "SenderAddress = " + tokens[i].toString());
+ String[] emails = new String[1];
+ emails[0] = tokens[i].getAddress();
+ String name = tokens[i].getName();
+ if(!first) address += "; "; //Delimiter
+ address += emails[0];
+ first = false;
+ i++;
+ }
+ }
+ } else if(fi.mMsgType == FilterInfo.TYPE_IM) {
+ // TODO: For IM we add the contact ID in the addressing
+ long contact_id = c.getLong(fi.mMessageColFromAddress);
+ // TODO: This is a BAD hack, that we map the contact ID to a conversation ID!!!
+ // We need to reach a conclusion on what to do
+ Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
+ Cursor contacts = mResolver.query(contactsUri,
+ BluetoothMapContract.BT_CONTACT_PROJECTION,
+ BluetoothMapContract.ConvoContactColumns.CONVO_ID
+ + " = " + contact_id, null, null);
+ try {
+ // TODO this will not work for group-chats
+ if(contacts != null && contacts.moveToFirst()){
+ address = contacts.getString(
+ contacts.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.UCI));
+ }
+ } finally {
+ if (contacts != null) contacts.close();
+ }
+
}
if (V) Log.v(TAG, "setSenderAddressing: " + address);
if(address == null)
@@ -562,13 +1061,13 @@
private void setSenderName(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_SENDER_NAME) != 0) {
- String name = null;
+ String name = "";
if (fi.mMsgType == FilterInfo.TYPE_SMS) {
int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
if (msgType == 1) {
String phone = c.getString(fi.mSmsColAddress);
if (phone != null && !phone.isEmpty())
- name = getContactNameFromPhone(phone);
+ name = getContactNameFromPhone(phone, mResolver);
} else {
name = fi.mPhoneAlphaTag;
}
@@ -581,9 +1080,44 @@
phone = e.getSenderAddressing();
}
if (phone != null && !phone.isEmpty() )
- name = getContactNameFromPhone(phone);
- } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- name = c.getString(fi.mEmailColFromAddress);
+ name = getContactNameFromPhone(phone, mResolver);
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL/* ||
+ fi.mMsgType == FilterInfo.TYPE_IM*/) {
+ String nameEmail = c.getString(fi.mMessageColFromAddress);
+ Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
+ if (tokens.length != 0) {
+ if(D) Log.d(TAG, "Originator count= " + tokens.length);
+ int i = 0;
+ boolean first = true;
+ while (i < tokens.length) {
+ if(V) Log.d(TAG, "senderName = " + tokens[i].toString());
+ String[] emails = new String[1];
+ emails[0] = tokens[i].getAddress();
+ String nameIn = tokens[i].getName();
+ if(!first) name += "; "; //Delimiter
+ name += nameIn;
+ first = false;
+ i++;
+ }
+ }
+ } else if(fi.mMsgType == FilterInfo.TYPE_IM) {
+ // For IM we add the contact ID in the addressing
+ long contact_id = c.getLong(fi.mMessageColFromAddress);
+ Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
+ Cursor contacts = mResolver.query(contactsUri,
+ BluetoothMapContract.BT_CONTACT_PROJECTION,
+ BluetoothMapContract.ConvoContactColumns.CONVO_ID
+ + " = " + contact_id, null, null);
+ try {
+ // TODO this will not work for group-chats
+ if(contacts != null && contacts.moveToFirst()){
+ name = contacts.getString(
+ contacts.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.NAME));
+ }
+ } finally {
+ if (contacts != null) contacts.close();
+ }
}
if (V) Log.v(TAG, "setSenderName: " + name);
if(name == null)
@@ -592,6 +1126,9 @@
}
}
+
+
+
private void setDateTime(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_DATETIME) != 0) {
@@ -609,34 +1146,54 @@
/* } else { */
/* date = c.getLong(c.getColumnIndex(Mms.DATE_SENT)) * 1000L; */
/* } */
- } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- date = c.getLong(fi.mEmailColDate);
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
+ date = c.getLong(fi.mMessageColDate);
}
e.setDateTime(date);
}
}
- private String getTextPartsMms(long id) {
+
+ private void setLastActivity(BluetoothMapConvoListingElement e, Cursor c,
+ FilterInfo fi, BluetoothMapAppParams ap) {
+ long date = 0;
+ if (fi.mMsgType == FilterInfo.TYPE_SMS ||
+ fi.mMsgType == FilterInfo.TYPE_MMS ) {
+ date = c.getLong(MMS_SMS_THREAD_COL_DATE);
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
+ date = c.getLong(fi.mConvoColLastActivity);
+ }
+ e.setLastActivity(date);
+ if (V) Log.v(TAG, "setDateTime: " + e.getLastActivityString());
+
+ }
+
+ static public String getTextPartsMms(ContentResolver r, long id) {
String text = "";
String selection = new String("mid=" + id);
String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/part");
Uri uriAddress = Uri.parse(uriStr);
// TODO: maybe use a projection with only "ct" and "text"
-
- Cursor c = mResolver.query(uriAddress, null, selection, null, null);
+ Cursor c = r.query(uriAddress, null, selection,
+ null, null);
try {
- while(c != null && c.moveToNext()) {
- String ct = c.getString(c.getColumnIndex("ct"));
- if (ct.equals("text/plain")) {
- String part = c.getString(c.getColumnIndex("text"));
- if(part != null) {
- text += part;
+ if (c != null && c.moveToFirst()) {
+ do {
+ String ct = c.getString(c.getColumnIndex("ct"));
+ if (ct.equals("text/plain")) {
+ String part = c.getString(c.getColumnIndex("text"));
+ if(part != null) {
+ text += part;
+ }
}
- }
+ } while(c.moveToNext());
}
} finally {
- close(c);
+ if (c != null) c.close();
}
+
return text;
}
@@ -655,10 +1212,11 @@
if (subject == null || subject.length() == 0) {
/* Get subject from mms text body parts - if any exists */
long id = c.getLong(fi.mMmsColId);
- subject = getTextPartsMms(id);
+ subject = getTextPartsMms(mResolver, id);
}
- } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- subject = c.getString(fi.mEmailColSubject);
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
+ subject = c.getString(fi.mMessageColSubject);
}
if (subject != null && subject.length() > subLength) {
subject = subject.substring(0, subLength);
@@ -675,8 +1233,9 @@
handle = c.getLong(fi.mSmsColId);
} else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
handle = c.getLong(fi.mMmsColId);
- } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- handle = c.getLong(fi.mEmailColId);
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
+ handle = c.getLong(fi.mMessageColId);
}
if (V) Log.d(TAG, "setHandle: " + handle );
e.setHandle(handle);
@@ -687,7 +1246,7 @@
BluetoothMapMessageListingElement e = new BluetoothMapMessageListingElement();
setHandle(e, c, fi, ap);
setDateTime(e, c, fi, ap);
- setType(e, c, fi, ap);
+ e.setType(getType(c, fi), ((ap.getParameterMask() & MASK_TYPE) != 0) ? true : false);
setRead(e, c, fi, ap);
// we set number and name for sender/recipient later
// they require lookup on contacts so no need to
@@ -696,7 +1255,19 @@
return e;
}
- private String getContactNameFromPhone(String phone) {
+ private BluetoothMapConvoListingElement createConvoElement(Cursor c, FilterInfo fi,
+ BluetoothMapAppParams ap) {
+ BluetoothMapConvoListingElement e = new BluetoothMapConvoListingElement();
+ setLastActivity(e, c, fi, ap);
+ e.setType(getType(c, fi));
+// setConvoRead(e, c, fi, ap);
+ e.setCursorIndex(c.getPosition());
+ return e;
+ }
+
+ /* TODO: Change to use SmsMmsContacts.getContactNameFromPhone() with proper use of
+ * caching. */
+ public static String getContactNameFromPhone(String phone, ContentResolver resolver) {
String name = null;
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
@@ -705,14 +1276,18 @@
String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME};
String selection = Contacts.IN_VISIBLE_GROUP + "=1";
String orderBy = Contacts.DISPLAY_NAME + " ASC";
-
- Cursor c = mResolver.query(uri, projection, selection, null, orderBy);
+ Cursor c = null;
try {
- if (c != null && c.moveToFirst()) {
- name = c.getString(c.getColumnIndex(Contacts.DISPLAY_NAME));
- };
+ c = resolver.query(uri, projection, selection, null, orderBy);
+ if(c != null) {
+ int colIndex = c.getColumnIndex(Contacts.DISPLAY_NAME);
+ if (c.getCount() >= 1) {
+ c.moveToFirst();
+ name = c.getString(colIndex);
+ }
+ }
} finally {
- close(c);
+ if(c != null) c.close();
}
return name;
}
@@ -722,17 +1297,22 @@
String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr");
Uri uriAddress = Uri.parse(uriStr);
String addr = null;
-
- Cursor c = r.query(uriAddress, null, selection, null, null);
+ String[] projection = {Mms.Addr.ADDRESS};
+ Cursor c = null;
try {
- if (c != null && c.moveToFirst()) {
- addr = c.getString(c.getColumnIndex(Mms.Addr.ADDRESS));
- if (addr.equals(INSERT_ADDRES_TOKEN)) addr = "";
+ c = r.query(uriAddress, projection, selection, null, null); // TODO: Add projection
+ int colIndex = c.getColumnIndex(Mms.Addr.ADDRESS);
+ if (c != null) {
+ if(c.moveToFirst()) {
+ addr = c.getString(colIndex);
+ if(addr.equals(INSERT_ADDRES_TOKEN)) {
+ addr = "";
+ }
+ }
}
} finally {
- close(c);
+ if (c != null) c.close();
}
-
return addr;
}
@@ -749,7 +1329,7 @@
if (V) Log.v(TAG, "matchRecipientMms: match recipient phone = " + phone);
res = true;
} else {
- String name = getContactNameFromPhone(phone);
+ String name = getContactNameFromPhone(phone, mResolver);
if (name != null && name.length() > 0 && name.matches(recip)) {
if (V) Log.v(TAG, "matchRecipientMms: match recipient name = " + name);
res = true;
@@ -785,7 +1365,7 @@
if (V) Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone);
res = true;
} else {
- String name = getContactNameFromPhone(phone);
+ String name = getContactNameFromPhone(phone, mResolver);
if (name != null && name.length() > 0 && name.matches(recip)) {
if (V) Log.v(TAG, "matchRecipientSms: match recipient name = " + name);
res = true;
@@ -829,7 +1409,7 @@
if (V) Log.v(TAG, "matchOriginatorMms: match originator phone = " + phone);
res = true;
} else {
- String name = getContactNameFromPhone(phone);
+ String name = getContactNameFromPhone(phone, mResolver);
if (name != null && name.length() > 0 && name.matches(orig)) {
if (V) Log.v(TAG, "matchOriginatorMms: match originator name = " + name);
res = true;
@@ -853,7 +1433,7 @@
if (V) Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone);
res = true;
} else {
- String name = getContactNameFromPhone(phone);
+ String name = getContactNameFromPhone(phone, mResolver);
if (name != null && name.length() > 0 && name.matches(orig)) {
if (V) Log.v(TAG, "matchOriginatorSms: match originator name = " + name);
res = true;
@@ -957,14 +1537,32 @@
return where;
}
- private String setWhereFilterFolderType(BluetoothMapFolderElement folderElement, FilterInfo fi) {
+ private String setWhereFilterFolderTypeIm(long folderId) {
String where = "";
- if (fi.mMsgType == FilterInfo.TYPE_SMS) {
- where = setWhereFilterFolderTypeSms(folderElement.getName());
- } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
- where = setWhereFilterFolderTypeMms(folderElement.getName());
- } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- where = setWhereFilterFolderTypeEmail(folderElement.getEmailFolderId());
+ if (folderId > BluetoothMapContract.FOLDER_ID_OTHER) {
+ where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + folderId;
+ } else {
+ Log.e(TAG, "setWhereFilterFolderTypeIm: not valid!" );
+ throw new IllegalArgumentException("Invalid folder ID");
+ }
+ return where;
+ }
+
+ private String setWhereFilterFolderType(BluetoothMapFolderElement folderElement,
+ FilterInfo fi) {
+ String where = "";
+ if(folderElement.shouldIgnore()) {
+ where = "1=1";
+ } else {
+ if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+ where = setWhereFilterFolderTypeSms(folderElement.getName());
+ } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+ where = setWhereFilterFolderTypeMms(folderElement.getName());
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
+ where = setWhereFilterFolderTypeEmail(folderElement.getFolderId());
+ } else if (fi.mMsgType == FilterInfo.TYPE_IM) {
+ where = setWhereFilterFolderTypeIm(folderElement.getFolderId());
+ }
}
return where;
}
@@ -988,11 +1586,11 @@
if ((ap.getFilterReadStatus() & 0x02) != 0) {
where = " AND " + Mms.READ + "= 1";
}
- } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
if ((ap.getFilterReadStatus() & 0x01) != 0) {
where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 0";
}
-
if ((ap.getFilterReadStatus() & 0x02) != 0) {
where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 1";
}
@@ -1003,27 +1601,55 @@
private String setWhereFilterPeriod(BluetoothMapAppParams ap, FilterInfo fi) {
String where = "";
+
if ((ap.getFilterPeriodBegin() != -1)) {
if (fi.mMsgType == FilterInfo.TYPE_SMS) {
- where = " AND " + Sms.DATE + " >= " + ap.getFilterPeriodBegin();
+ where = " AND " + Sms.DATE + " >= " + ap.getFilterPeriodBegin();
} else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
where = " AND " + Mms.DATE + " >= " + (ap.getFilterPeriodBegin() / 1000L);
- } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- where = " AND " + BluetoothMapContract.MessageColumns.DATE + " >= " + (ap.getFilterPeriodBegin());
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
+ where = " AND " + BluetoothMapContract.MessageColumns.DATE +
+ " >= " + (ap.getFilterPeriodBegin());
}
}
if ((ap.getFilterPeriodEnd() != -1)) {
if (fi.mMsgType == FilterInfo.TYPE_SMS) {
- where += " AND " + Sms.DATE + " < " + ap.getFilterPeriodEnd();
+ where += " AND " + Sms.DATE + " < " + ap.getFilterPeriodEnd();
} else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
where += " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L);
- } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
- where += " AND " + BluetoothMapContract.MessageColumns.DATE + " < " + (ap.getFilterPeriodEnd());
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
+ where += " AND " + BluetoothMapContract.MessageColumns.DATE +
+ " < " + (ap.getFilterPeriodEnd());
}
}
-
-
+ return where;
+ }
+ private String setWhereFilterLastActivity(BluetoothMapAppParams ap, FilterInfo fi) {
+ String where = "";
+ if ((ap.getFilterLastActivityBegin() != -1)) {
+ if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+ where = " AND " + Sms.DATE + " >= " + ap.getFilterLastActivityBegin();
+ } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+ where = " AND " + Mms.DATE + " >= " + (ap.getFilterLastActivityBegin() / 1000L);
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
+ fi.mMsgType == FilterInfo.TYPE_IM ) {
+ where = " AND " + BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY +
+ " >= " + (ap.getFilterPeriodBegin());
+ }
+ }
+ if ((ap.getFilterLastActivityEnd() != -1)) {
+ if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+ where += " AND " + Sms.DATE + " < " + ap.getFilterLastActivityEnd();
+ } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+ where += " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L);
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||fi.mMsgType == FilterInfo.TYPE_IM) {
+ where += " AND " + BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
+ + " < " + (ap.getFilterLastActivityEnd());
+ }
+ }
return where;
}
@@ -1035,7 +1661,6 @@
ContactsContract.Contacts.DISPLAY_NAME + " like ?",
new String[]{str},
ContactsContract.Contacts.DISPLAY_NAME + " ASC");
-
try {
while (c != null && c.moveToNext()) {
String contactId = c.getString(c.getColumnIndex(ContactsContract.Contacts._ID));
@@ -1044,25 +1669,30 @@
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
new String[]{contactId},
null);
-
try {
while (p != null && p.moveToNext()) {
String number = p.getString(
p.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
where += " address = " + "'" + number + "'";
- if (!p.isLast()) where += " OR ";
+ if (!p.isLast()) {
+ where += " OR ";
+ }
+ }
+ if (!c.isLast()) {
+ where += " OR ";
}
} finally {
- close(p);
+ if (p != null) p.close();
}
if (!c.isLast()) where += " OR ";
}
} finally {
- close(c);
+ if (c != null) c.close();
}
+
if (str != null && str.length() > 0) {
if (where.length() > 0) {
where += " OR ";
@@ -1080,10 +1710,25 @@
/* Be aware of wild cards in the beginning of string, may not be valid? */
if (orig != null && orig.length() > 0) {
orig = orig.replace("*", "%");
- where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST + " LIKE '%" + orig + "%'";
+ where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST
+ + " LIKE '%" + orig + "%'";
}
return where;
}
+
+ private String setWhereFilterOriginatorIM(BluetoothMapAppParams ap) {
+ String where = "";
+ String orig = ap.getFilterOriginator();
+
+ /* Be aware of wild cards in the beginning of string, may not be valid? */
+ if (orig != null && orig.length() > 0) {
+ orig = orig.replace("*", "%");
+ where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST
+ + " LIKE '%" + orig + "%'";
+ }
+ return where;
+ }
+
private String setWhereFilterPriority(BluetoothMapAppParams ap, FilterInfo fi) {
String where = "";
int pri = ap.getFilterPriority();
@@ -1099,6 +1744,17 @@
Integer.toString(PduHeaders.PRIORITY_HIGH);
}
}
+ if(fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+ fi.mMsgType == FilterInfo.TYPE_IM)
+ {
+ if(pri == 0x0002)
+ {
+ where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "!=1";
+ }else if(pri == 0x0001) {
+ where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "=1";
+ }
+ }
+ // TODO: no priority filtering in IM
return where;
}
@@ -1117,26 +1773,117 @@
return where;
}
+ private String setWhereFilterMessageHandle(BluetoothMapAppParams ap, FilterInfo fi) {
+ String where = "";
+ long id = -1;
+ String msgHandle = ap.getFilterMsgHandleString();
+ if(msgHandle != null) {
+ id = BluetoothMapUtils.getCpHandle(msgHandle);
+ if(D)Log.d(TAG,"id: " + id);
+ }
+ if(id != -1) {
+ if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+ where = " AND " + Sms._ID + " = " + id;
+ } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+ where = " AND " + Mms._ID + " = " + id;
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
+ where = " AND " + BluetoothMapContract.MessageColumns._ID + " = " + id;
+ }
+ }
+ return where;
+ }
+
+ private String setWhereFilterThreadId(BluetoothMapAppParams ap, FilterInfo fi) {
+ String where = "";
+ long id = -1;
+ String msgHandle = ap.getFilterConvoIdString();
+ if(msgHandle != null) {
+ id = BluetoothMapUtils.getMsgHandleAsLong(msgHandle);
+ if(D)Log.d(TAG,"id: " + id);
+ }
+ if(id > 0) {
+ if (fi.mMsgType == FilterInfo.TYPE_SMS) {
+ where = " AND " + Sms.THREAD_ID + " = " + id;
+ } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+ where = " AND " + Mms.THREAD_ID + " = " + id;
+ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
+ fi.mMsgType == FilterInfo.TYPE_IM) {
+ where = " AND " + BluetoothMapContract.MessageColumns.THREAD_ID + " = " + id;
+ }
+ }
+
+ return where;
+ }
+
private String setWhereFilter(BluetoothMapFolderElement folderElement,
FilterInfo fi, BluetoothMapAppParams ap) {
String where = "";
-
where += setWhereFilterFolderType(folderElement, fi);
- if(!where.isEmpty()) {
- where += setWhereFilterReadStatus(ap, fi);
- where += setWhereFilterPeriod(ap, fi);
- where += setWhereFilterPriority(ap,fi);
+ String msgHandleWhere = setWhereFilterMessageHandle(ap, fi);
+ /* if message handle filter is available, the other filters should be ignored */
+ if(msgHandleWhere.isEmpty()) {
+ where += setWhereFilterReadStatus(ap, fi);
+ where += setWhereFilterPriority(ap,fi);
+ where += setWhereFilterPeriod(ap, fi);
if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
where += setWhereFilterOriginatorEmail(ap);
where += setWhereFilterRecipientEmail(ap);
}
+ if (fi.mMsgType == FilterInfo.TYPE_IM) {
+ where += setWhereFilterOriginatorIM(ap);
+ // TODO: set 'where' filer recipient?
+ }
+ where += setWhereFilterThreadId(ap, fi);
+ } else {
+ where += msgHandleWhere;
}
-
return where;
}
+
+ /* Used only for SMS/MMS */
+ private void setConvoWhereFilterSmsMms(StringBuilder selection, ArrayList<String> selectionArgs,
+ FilterInfo fi, BluetoothMapAppParams ap) {
+
+ if (smsSelected(fi, ap) || mmsSelected(ap)) {
+
+ // Filter Read Status
+ if(ap.getFilterReadStatus() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
+ if ((ap.getFilterReadStatus() & FILTER_READ_STATUS_UNREAD_ONLY) != 0) {
+ selection.append(" AND ").append(Threads.READ).append(" = 0");
+ }
+ if ((ap.getFilterReadStatus() & FILTER_READ_STATUS_READ_ONLY) != 0) {
+ selection.append(" AND ").append(Threads.READ).append(" = 1");
+ }
+ }
+
+ // Filter time
+ if ((ap.getFilterLastActivityBegin() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)){
+ selection.append(" AND ").append(Threads.DATE).append(" >= ")
+ .append(ap.getFilterLastActivityBegin());
+ }
+ if ((ap.getFilterLastActivityEnd() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)) {
+ selection.append(" AND ").append(Threads.DATE).append(" <= ")
+ .append(ap.getFilterLastActivityEnd());
+ }
+
+ // Filter ConvoId
+ long convoId = -1;
+ if(ap.getFilterConvoId() != null) {
+ convoId = ap.getFilterConvoId().getLeastSignificantBits();
+ }
+ if(convoId > 0) {
+ selection.append(" AND ").append(Threads._ID).append(" = ")
+ .append(Long.toString(convoId));
+ }
+ }
+ }
+
+
+
/**
* Determine from application parameter if sms should be included.
* The filter mask is set for message types not selected
@@ -1150,16 +1897,19 @@
if (D) Log.d(TAG, "smsSelected msgType: " + msgType);
- if (msgType == -1)
+ if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
return true;
- if ((msgType & 0x03) == 0)
+ if ((msgType & (BluetoothMapAppParams.FILTER_NO_SMS_CDMA
+ |BluetoothMapAppParams.FILTER_NO_SMS_GSM)) == 0)
return true;
- if (((msgType & 0x01) == 0) && (phoneType == TelephonyManager.PHONE_TYPE_GSM))
+ if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_GSM) == 0)
+ && (phoneType == TelephonyManager.PHONE_TYPE_GSM))
return true;
- if (((msgType & 0x02) == 0) && (phoneType == TelephonyManager.PHONE_TYPE_CDMA))
+ if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_CDMA) == 0)
+ && (phoneType == TelephonyManager.PHONE_TYPE_CDMA))
return true;
return false;
@@ -1170,17 +1920,17 @@
* The filter mask is set for message types not selected
* @param fi
* @param ap
- * @return boolean true if sms is selected, false if not
+ * @return boolean true if mms is selected, false if not
*/
- private boolean mmsSelected(FilterInfo fi, BluetoothMapAppParams ap) {
+ private boolean mmsSelected(BluetoothMapAppParams ap) {
int msgType = ap.getFilterMessageType();
if (D) Log.d(TAG, "mmsSelected msgType: " + msgType);
- if (msgType == -1)
+ if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
return true;
- if ((msgType & 0x08) == 0)
+ if ((msgType & BluetoothMapAppParams.FILTER_NO_MMS) == 0)
return true;
return false;
@@ -1191,24 +1941,46 @@
* The filter mask is set for message types not selected
* @param fi
* @param ap
- * @return boolean true if sms is selected, false if not
+ * @return boolean true if email is selected, false if not
*/
- private boolean emailSelected(FilterInfo fi, BluetoothMapAppParams ap) {
+ private boolean emailSelected(BluetoothMapAppParams ap) {
int msgType = ap.getFilterMessageType();
if (D) Log.d(TAG, "emailSelected msgType: " + msgType);
- if (msgType == -1)
+ if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
return true;
- if ((msgType & 0x04) == 0)
+ if ((msgType & BluetoothMapAppParams.FILTER_NO_EMAIL) == 0)
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Determine from application parameter if IM should be included.
+ * The filter mask is set for message types not selected
+ * @param fi
+ * @param ap
+ * @return boolean true if im is selected, false if not
+ */
+ private boolean imSelected(BluetoothMapAppParams ap) {
+ int msgType = ap.getFilterMessageType();
+
+ if (D) Log.d(TAG, "imSelected msgType: " + msgType);
+
+ if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+ return true;
+
+ if ((msgType & BluetoothMapAppParams.FILTER_NO_IM) == 0)
return true;
return false;
}
private void setFilterInfo(FilterInfo fi) {
- TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ TelephonyManager tm =
+ (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
if (tm != null) {
fi.mPhoneType = tm.getPhoneType();
fi.mPhoneNum = tm.getLine1Number();
@@ -1227,20 +1999,21 @@
*/
public BluetoothMapMessageListing msgListing(BluetoothMapFolderElement folderElement,
BluetoothMapAppParams ap) {
- if (D) Log.d(TAG, "msgListing: folderName = " + folderElement.getName()
- + " folderId = " + folderElement.getEmailFolderId()
- + " messageType = " + ap.getFilterMessageType() );
- BluetoothMapMessageListing bmList = new BluetoothMapMessageListing();
+ if (D) Log.d(TAG, "msgListing: messageType = " + ap.getFilterMessageType() );
+ BluetoothMapMessageListing bmList = new BluetoothMapMessageListing();
/* We overwrite the parameter mask here if it is 0 or not present, as this
* should cause all parameters to be included in the message list. */
if(ap.getParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
ap.getParameterMask() == 0) {
- ap.setParameterMask(BluetoothMapAppParams.PARAMETER_MASK_ALL_ENABLED);
+ ap.setParameterMask(PARAMETER_MASK_DEFAULT);
if (V) Log.v(TAG, "msgListing(): appParameterMask is zero or not present, " +
- "changing to: " + ap.getParameterMask());
+ "changing to default: " + ap.getParameterMask());
}
+ if (V) Log.v(TAG, "folderElement hasSmsMmsContent = " + folderElement.hasSmsMmsContent() +
+ " folderElement.hasEmailContent = " + folderElement.hasEmailContent() +
+ " folderElement.hasImContent = " + folderElement.hasImContent());
/* Cache some info used throughout filtering */
FilterInfo fi = new FilterInfo();
@@ -1248,23 +2021,25 @@
Cursor smsCursor = null;
Cursor mmsCursor = null;
Cursor emailCursor = null;
-
- try {
- String limit = "";
- int countNum = ap.getMaxListCount();
- int offsetNum = ap.getStartOffset();
- if(ap.getMaxListCount()>0){
- limit=" LIMIT "+ (ap.getMaxListCount()+ap.getStartOffset());
- }
-
+ Cursor imCursor = null;
+ String limit = "";
+ int countNum = ap.getMaxListCount();
+ int offsetNum = ap.getStartOffset();
+ if(ap.getMaxListCount()>0){
+ limit=" LIMIT "+ (ap.getMaxListCount()+ap.getStartOffset());
+ }
+ try{
if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
BluetoothMapAppParams.FILTER_NO_MMS|
- BluetoothMapAppParams.FILTER_NO_SMS_GSM)||
+ BluetoothMapAppParams.FILTER_NO_SMS_GSM|
+ BluetoothMapAppParams.FILTER_NO_IM)||
ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
BluetoothMapAppParams.FILTER_NO_MMS|
- BluetoothMapAppParams.FILTER_NO_SMS_CDMA)){
- //set real limit and offset if only this type is used (only if offset/limit is used
+ BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
+ BluetoothMapAppParams.FILTER_NO_IM)){
+ //set real limit and offset if only this type is used
+ // (only if offset/limit is used)
limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
if(D) Log.d(TAG, "SMS Limit => "+limit);
offsetNum = 0;
@@ -1272,31 +2047,32 @@
fi.mMsgType = FilterInfo.TYPE_SMS;
if(ap.getFilterPriority() != 1){ /*SMS cannot have high priority*/
String where = setWhereFilter(folderElement, fi, ap);
- if(!where.isEmpty()) {
- if (D) Log.d(TAG, "msgType: " + fi.mMsgType);
- smsCursor = mResolver.query(Sms.CONTENT_URI,
- SMS_PROJECTION, where, null, Sms.DATE + " DESC" + limit);
- if (smsCursor != null) {
- BluetoothMapMessageListingElement e = null;
- // store column index so we dont have to look them up anymore (optimization)
- if(D) Log.d(TAG, "Found " + smsCursor.getCount() + " sms messages.");
- fi.setSmsColumns(smsCursor);
- while (smsCursor.moveToNext()) {
- if (matchAddresses(smsCursor, fi, ap)) {
- e = element(smsCursor, fi, ap);
- bmList.add(e);
- }
+ if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
+ smsCursor = mResolver.query(Sms.CONTENT_URI,
+ SMS_PROJECTION, where, null, Sms.DATE + " DESC" + limit);
+ if (smsCursor != null) {
+ BluetoothMapMessageListingElement e = null;
+ // store column index so we dont have to look them up anymore (optimization)
+ if(D) Log.d(TAG, "Found " + smsCursor.getCount() + " sms messages.");
+ fi.setSmsColumns(smsCursor);
+ while (smsCursor.moveToNext()) {
+ if (matchAddresses(smsCursor, fi, ap)) {
+ if(V) BluetoothMapUtils.printCursor(smsCursor);
+ e = element(smsCursor, fi, ap);
+ bmList.add(e);
}
}
}
}
}
- if (mmsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
+ if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) {
if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
- BluetoothMapAppParams.FILTER_NO_SMS_GSM)){
- //set real limit and offset if only this type is used (only if offset/limit is used
+ BluetoothMapAppParams.FILTER_NO_SMS_GSM|
+ BluetoothMapAppParams.FILTER_NO_IM)){
+ //set real limit and offset if only this type is used
+ //(only if offset/limit is used)
limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
if(D) Log.d(TAG, "MMS Limit => "+limit);
offsetNum = 0;
@@ -1304,17 +2080,17 @@
fi.mMsgType = FilterInfo.TYPE_MMS;
String where = setWhereFilter(folderElement, fi, ap);
if(!where.isEmpty()) {
- if (D) Log.d(TAG, "msgType: " + fi.mMsgType);
+ if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
mmsCursor = mResolver.query(Mms.CONTENT_URI,
MMS_PROJECTION, where, null, Mms.DATE + " DESC" + limit);
if (mmsCursor != null) {
BluetoothMapMessageListingElement e = null;
// store column index so we dont have to look them up anymore (optimization)
fi.setMmsColumns(mmsCursor);
- int cnt = 0;
if(D) Log.d(TAG, "Found " + mmsCursor.getCount() + " mms messages.");
while (mmsCursor.moveToNext()) {
if (matchAddresses(mmsCursor, fi, ap)) {
+ if(V) BluetoothMapUtils.printCursor(mmsCursor);
e = element(mmsCursor, fi, ap);
bmList.add(e);
}
@@ -1323,11 +2099,13 @@
}
}
- if (emailSelected(fi, ap) && folderElement.getEmailFolderId() != -1) {
+ if (emailSelected(ap) && folderElement.hasEmailContent()) {
if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS|
BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
- BluetoothMapAppParams.FILTER_NO_SMS_GSM)){
- //set real limit and offset if only this type is used (only if offset/limit is used
+ BluetoothMapAppParams.FILTER_NO_SMS_GSM|
+ BluetoothMapAppParams.FILTER_NO_IM)){
+ //set real limit and offset if only this type is used
+ //(only if offset/limit is used)
limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
if(D) Log.d(TAG, "Email Limit => "+limit);
offsetNum = 0;
@@ -1336,17 +2114,19 @@
String where = setWhereFilter(folderElement, fi, ap);
if(!where.isEmpty()) {
- if (D) Log.d(TAG, "msgType: " + fi.mMsgType);
- Uri contentUri = Uri.parse(mBaseEmailUri + BluetoothMapContract.TABLE_MESSAGE);
- emailCursor = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
- where, null, BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
+ if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
+ Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
+ emailCursor = mResolver.query(contentUri,
+ BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null,
+ BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
if (emailCursor != null) {
BluetoothMapMessageListingElement e = null;
// store column index so we dont have to look them up anymore (optimization)
- fi.setEmailColumns(emailCursor);
+ fi.setEmailMessageColumns(emailCursor);
int cnt = 0;
+ if(D) Log.d(TAG, "Found " + emailCursor.getCount() + " email messages.");
while (emailCursor.moveToNext()) {
- if(D) Log.d(TAG, "Found " + emailCursor.getCount() + " email messages.");
+ if(V) BluetoothMapUtils.printCursor(emailCursor);
e = element(emailCursor, fi, ap);
bmList.add(e);
}
@@ -1355,48 +2135,92 @@
}
}
+ if (imSelected(ap) && folderElement.hasImContent()) {
+ if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS|
+ BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
+ BluetoothMapAppParams.FILTER_NO_SMS_GSM|
+ BluetoothMapAppParams.FILTER_NO_EMAIL)){
+ //set real limit and offset if only this type is used
+ //(only if offset/limit is used)
+ limit = " LIMIT " + ap.getMaxListCount() + " OFFSET "+ ap.getStartOffset();
+ if(D) Log.d(TAG, "IM Limit => "+limit);
+ offsetNum = 0;
+ }
+ fi.mMsgType = FilterInfo.TYPE_IM;
+ String where = setWhereFilter(folderElement, fi, ap);
+ if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
+
+ Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
+ imCursor = mResolver.query(contentUri,
+ BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION,
+ where, null, BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
+ if (imCursor != null) {
+ BluetoothMapMessageListingElement e = null;
+ // store column index so we dont have to look them up anymore (optimization)
+ fi.setImMessageColumns(imCursor);
+ if (D) Log.d(TAG, "Found " + imCursor.getCount() + " im messages.");
+ while (imCursor.moveToNext()) {
+ if (V) BluetoothMapUtils.printCursor(imCursor);
+ e = element(imCursor, fi, ap);
+ bmList.add(e);
+ }
+ }
+ }
+
/* Enable this if post sorting and segmenting needed */
bmList.sort();
bmList.segment(ap.getMaxListCount(), offsetNum);
List<BluetoothMapMessageListingElement> list = bmList.getList();
int listSize = list.size();
Cursor tmpCursor = null;
- for (int x=0; x<listSize; x++){
+ for(int x=0;x<listSize;x++){
BluetoothMapMessageListingElement ele = list.get(x);
- if ((ele.getType().equals(TYPE.SMS_GSM)||ele.getType().equals(TYPE.SMS_CDMA)) && smsCursor != null){
+ if((ele.getType().equals(TYPE.SMS_GSM)||ele.getType().equals(TYPE.SMS_CDMA))
+ && smsCursor != null){
tmpCursor = smsCursor;
fi.mMsgType = FilterInfo.TYPE_SMS;
- } else if (ele.getType().equals(TYPE.MMS) && mmsCursor != null){
+ }else if(ele.getType().equals(TYPE.MMS) && mmsCursor != null){
tmpCursor = mmsCursor;
fi.mMsgType = FilterInfo.TYPE_MMS;
- } else if (ele.getType().equals(TYPE.EMAIL) && emailCursor != null){
+ }else if(ele.getType().equals(TYPE.EMAIL) && emailCursor != null){
tmpCursor = emailCursor;
fi.mMsgType = FilterInfo.TYPE_EMAIL;
+ }else if(ele.getType().equals(TYPE.IM) && imCursor != null){
+ tmpCursor = imCursor;
+ fi.mMsgType = FilterInfo.TYPE_IM;
}
-
- if (tmpCursor != null && tmpCursor.moveToPosition(ele.getCursorIndex())) {
+ if(tmpCursor != null){
+ tmpCursor.moveToPosition(ele.getCursorIndex());
setSenderAddressing(ele, tmpCursor, fi, ap);
setSenderName(ele, tmpCursor, fi, ap);
setRecipientAddressing(ele, tmpCursor, fi, ap);
setRecipientName(ele, tmpCursor, fi, ap);
setSubject(ele, tmpCursor, fi, ap);
setSize(ele, tmpCursor, fi, ap);
- setReceptionStatus(ele, tmpCursor, fi, ap);
setText(ele, tmpCursor, fi, ap);
- setAttachmentSize(ele, tmpCursor, fi, ap);
setPriority(ele, tmpCursor, fi, ap);
setSent(ele, tmpCursor, fi, ap);
setProtected(ele, tmpCursor, fi, ap);
- setThreadId(ele, tmpCursor, fi, ap);
+ setReceptionStatus(ele, tmpCursor, fi, ap);
+ setAttachment(ele, tmpCursor, fi, ap);
+
+ if(mMsgListingVersion > BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10 ){
+ setDeliveryStatus(ele, tmpCursor, fi, ap);
+ setThreadId(ele, tmpCursor, fi, ap);
+ setThreadName(ele, tmpCursor, fi, ap);
+ setFolderType(ele, tmpCursor, fi, ap);
+ }
}
}
} finally {
- close(emailCursor);
- close(smsCursor);
- close(mmsCursor);
+ if(emailCursor != null)emailCursor.close();
+ if(smsCursor != null)smsCursor.close();
+ if(mmsCursor != null)mmsCursor.close();
+ if(imCursor != null)imCursor.close();
}
- if (D) Log.d(TAG, "messagelisting end");
+
+ if(D)Log.d(TAG, "messagelisting end");
return bmList;
}
@@ -1420,29 +2244,61 @@
String where = setWhereFilter(folderElement, fi, ap);
Cursor c = mResolver.query(Sms.CONTENT_URI,
SMS_PROJECTION, where, null, Sms.DATE + " DESC");
-
- if (c != null) cnt = c.getCount();
- close(c);
+ try {
+ if (c != null) {
+ cnt = c.getCount();
+ }
+ } finally {
+ if (c != null) c.close();
+ }
}
- if (mmsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
+ if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) {
fi.mMsgType = FilterInfo.TYPE_MMS;
String where = setWhereFilter(folderElement, fi, ap);
Cursor c = mResolver.query(Mms.CONTENT_URI,
MMS_PROJECTION, where, null, Mms.DATE + " DESC");
- if (c != null) cnt += c.getCount();
- close(c);
+ try {
+ if (c != null) {
+ cnt += c.getCount();
+ }
+ } finally {
+ if (c != null) c.close();
+ }
}
- if (emailSelected(fi, ap) && folderElement.getEmailFolderId() != -1) {
+ if (emailSelected(ap) && folderElement.hasEmailContent()) {
fi.mMsgType = FilterInfo.TYPE_EMAIL;
String where = setWhereFilter(folderElement, fi, ap);
- if (!where.isEmpty()) {
- Uri contentUri = Uri.parse(mBaseEmailUri + BluetoothMapContract.TABLE_MESSAGE);
+ if(!where.isEmpty()) {
+ Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
- if (c != null) cnt += c.getCount();
- close(c);
+ try {
+ if (c != null) {
+ cnt += c.getCount();
+ }
+ } finally {
+ if (c != null) c.close();
+ }
+ }
+ }
+
+ if (imSelected(ap) && folderElement.hasImContent()) {
+ fi.mMsgType = FilterInfo.TYPE_IM;
+ String where = setWhereFilter(folderElement, fi, ap);
+ if(!where.isEmpty()) {
+ Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
+ Cursor c = mResolver.query(contentUri,
+ BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION,
+ where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
+ try {
+ if (c != null) {
+ cnt += c.getCount();
+ }
+ } finally {
+ if (c != null) c.close();
+ }
}
}
@@ -1472,35 +2328,68 @@
where += setWhereFilterPeriod(ap, fi);
Cursor c = mResolver.query(Sms.CONTENT_URI,
SMS_PROJECTION, where, null, Sms.DATE + " DESC");
-
- if (c != null) cnt += c.getCount();
- close(c);
+ try {
+ if (c != null) {
+ cnt = c.getCount();
+ }
+ } finally {
+ if (c != null) c.close();
+ }
}
- if (mmsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
+ if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) {
fi.mMsgType = FilterInfo.TYPE_MMS;
String where = setWhereFilterFolderType(folderElement, fi);
where += " AND " + Mms.READ + "=0 ";
where += setWhereFilterPeriod(ap, fi);
Cursor c = mResolver.query(Mms.CONTENT_URI,
MMS_PROJECTION, where, null, Sms.DATE + " DESC");
-
- if (c != null) cnt += c.getCount();
- close(c);
+ try {
+ if (c != null) {
+ cnt += c.getCount();
+ }
+ } finally {
+ if (c != null) c.close();
+ }
}
- if (emailSelected(fi, ap) && folderElement.getEmailFolderId() != -1) {
+ if (emailSelected(ap) && folderElement.getFolderId() != -1) {
fi.mMsgType = FilterInfo.TYPE_EMAIL;
String where = setWhereFilterFolderType(folderElement, fi);
if(!where.isEmpty()) {
where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
where += setWhereFilterPeriod(ap, fi);
- Uri contentUri = Uri.parse(mBaseEmailUri + BluetoothMapContract.TABLE_MESSAGE);
+ Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
- if (c != null) cnt += c.getCount();
- close(c);
+ try {
+ if (c != null) {
+ cnt += c.getCount();
+ }
+ } finally {
+ if (c != null) c.close();
+ }
+ }
+ }
+
+ if (imSelected(ap) && folderElement.hasImContent()) {
+ fi.mMsgType = FilterInfo.TYPE_IM;
+ String where = setWhereFilter(folderElement, fi, ap);
+ if(!where.isEmpty()) {
+ where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
+ where += setWhereFilterPeriod(ap, fi);
+ Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
+ Cursor c = mResolver.query(contentUri,
+ BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION,
+ where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
+ try {
+ if (c != null) {
+ cnt += c.getCount();
+ }
+ } finally {
+ if (c != null) c.close();
+ }
}
}
@@ -1509,6 +2398,826 @@
}
/**
+ * Build the conversation listing.
+ * @param ap The Application Parameters
+ * @param sizeOnly TRUE: don't populate the list members, only build the list to get the size.
+ * @return
+ */
+ public BluetoothMapConvoListing convoListing(BluetoothMapAppParams ap, boolean sizeOnly) {
+
+ if (D) Log.d(TAG, "convoListing: " + " messageType = " + ap.getFilterMessageType() );
+ BluetoothMapConvoListing convoList = new BluetoothMapConvoListing();
+
+ /* We overwrite the parameter mask here if it is 0 or not present, as this
+ * should cause all parameters to be included in the message list. */
+ if(ap.getConvoParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
+ ap.getConvoParameterMask() == 0) {
+ ap.setConvoParameterMask(CONVO_PARAMETER_MASK_DEFAULT);
+ if (D) Log.v(TAG, "convoListing(): appParameterMask is zero or not present, " +
+ "changing to default: " + ap.getConvoParameterMask());
+ }
+
+ /* Possible filters:
+ * - Recipient name (contacts DB) or id (for SMS/MMS this is the thread-id contact-id)
+ * - Activity start/begin
+ * - Read status
+ * - Thread_id
+ * The strategy for SMS/MMS
+ * With no filter on name - use limit and offset.
+ * With a filter on name - build the complete list of conversations and create a filter
+ * mechanism
+ *
+ * The strategy for IM:
+ * Join the conversation table with the contacts table in a way that makes it possible to
+ * get the data needed in a single query.
+ * Manually handle limit/offset
+ * */
+
+ /* Cache some info used throughout filtering */
+ FilterInfo fi = new FilterInfo();
+ setFilterInfo(fi);
+ Cursor smsMmsCursor = null;
+ Cursor imEmailCursor = null;
+ int offsetNum;
+ if(sizeOnly) {
+ offsetNum = 0;
+ } else {
+ offsetNum = ap.getStartOffset();
+ }
+ // Inverse meaning - hence a 1 is include.
+ int msgTypesInclude = ((~ap.getFilterMessageType())
+ & BluetoothMapAppParams.FILTER_MSG_TYPE_MASK);
+ int maxThreads = ap.getMaxListCount()+ap.getStartOffset();
+
+
+ try {
+ if (smsSelected(fi, ap) || mmsSelected(ap)) {
+ String limit = "";
+ if((sizeOnly == false) && (ap.getMaxListCount()>0) &&
+ (ap.getFilterRecipient()==null)){
+ /* We can only use limit if we do not have a contacts filter */
+ limit=" LIMIT " + maxThreads;
+ }
+ StringBuilder sortOrder = new StringBuilder(Threads.DATE + " DESC");
+ if((sizeOnly == false) &&
+ ((msgTypesInclude & ~(BluetoothMapAppParams.FILTER_NO_SMS_GSM |
+ BluetoothMapAppParams.FILTER_NO_SMS_CDMA) |
+ BluetoothMapAppParams.FILTER_NO_MMS) == 0)
+ && ap.getFilterRecipient() == null){
+ // SMS/MMS messages only and no recipient filter - use optimization.
+ limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
+ if(D) Log.d(TAG, "SMS Limit => "+limit);
+ offsetNum = 0;
+ }
+ StringBuilder selection = new StringBuilder(120); // This covers most cases
+ ArrayList<String> selectionArgs = new ArrayList<String>(12); // Covers all cases
+ selection.append("1=1 "); // just to simplify building the where-clause
+ setConvoWhereFilterSmsMms(selection, selectionArgs, fi, ap);
+ String[] args = null;
+ if(selectionArgs.size() > 0) {
+ args = new String[selectionArgs.size()];
+ selectionArgs.toArray(args);
+ }
+ Uri uri = Threads.CONTENT_URI.buildUpon()
+ .appendQueryParameter("simple", "true").build();
+ sortOrder.append(limit);
+ if(D) Log.d(TAG, "Query using selection: " + selection.toString() +
+ " - sortOrder: " + sortOrder.toString());
+ // TODO: Optimize: Reduce projection based on convo parameter mask
+ smsMmsCursor = mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, selection.toString(),
+ args, sortOrder.toString());
+ if (smsMmsCursor != null) {
+ // store column index so we don't have to look them up anymore (optimization)
+ if(D) Log.d(TAG, "Found " + smsMmsCursor.getCount()
+ + " sms/mms conversations.");
+ BluetoothMapConvoListingElement convoElement = null;
+ smsMmsCursor.moveToPosition(-1);
+ if(ap.getFilterRecipient() == null) {
+ int count = 0;
+ // We have no Recipient filter, add contacts after the list is reduced
+ while (smsMmsCursor.moveToNext()) {
+ convoElement = createConvoElement(smsMmsCursor, fi, ap);
+ convoList.add(convoElement);
+ count++;
+ if(sizeOnly == false && count >= maxThreads) {
+ break;
+ }
+ }
+ } else {
+ // We must be able to filter on recipient, add contacts now
+ SmsMmsContacts contacts = new SmsMmsContacts();
+ while (smsMmsCursor.moveToNext()) {
+ int count = 0;
+ convoElement = createConvoElement(smsMmsCursor, fi, ap);
+ String idsStr =
+ smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS);
+ // Add elements only if we do find a contact - if not we cannot apply
+ // the filter, hence the item is irrelevant
+ // TODO: Perhaps the spec. should be changes to be able to search on
+ // phone number as well?
+ if(addSmsMmsContacts(convoElement, contacts, idsStr,
+ ap.getFilterRecipient(), ap)) {
+ convoList.add(convoElement);
+ if(sizeOnly == false && count >= maxThreads) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (emailSelected(ap) || imSelected(ap)) {
+ int count = 0;
+ if(emailSelected(ap)) {
+ fi.mMsgType = FilterInfo.TYPE_EMAIL;
+ } else if(imSelected(ap)) {
+ fi.mMsgType = FilterInfo.TYPE_IM;
+ }
+ if (D) Log.d(TAG, "msgType: " + fi.mMsgType);
+ Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION);
+
+ contentUri = appendConvoListQueryParameters(ap, contentUri);
+ if(V) Log.v(TAG, "URI with parameters: " + contentUri.toString());
+ // TODO: Optimize: Reduce projection based on convo parameter mask
+ imEmailCursor = mResolver.query(contentUri,
+ BluetoothMapContract.BT_CONVERSATION_PROJECTION,
+ null, null, BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
+ + " DESC, " + BluetoothMapContract.ConversationColumns.THREAD_ID
+ + " ASC");
+ if (imEmailCursor != null) {
+ BluetoothMapConvoListingElement e = null;
+ // store column index so we don't have to look them up anymore (optimization)
+ // Here we rely on only a single account-based message type for each MAS.
+ fi.setEmailImConvoColumns(imEmailCursor);
+ boolean isValid = imEmailCursor.moveToNext();
+ if(D) Log.d(TAG, "Found " + imEmailCursor.getCount()
+ + " EMAIL/IM conversations. isValid = " + isValid);
+ while (isValid && ((sizeOnly == true) || (count < maxThreads))) {
+ long threadId = imEmailCursor.getLong(fi.mConvoColConvoId);
+ long nextThreadId;
+ count ++;
+ e = createConvoElement(imEmailCursor, fi, ap);
+ convoList.add(e);
+
+ do {
+ nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId);
+ if(V) Log.i(TAG, " threadId = " + threadId + " newThreadId = " +
+ nextThreadId);
+ // TODO: This seems rather inefficient in the case where we do not need
+ // to reduce the list.
+ } while ((nextThreadId == threadId) &&
+ (isValid = imEmailCursor.moveToNext() == true));
+ }
+ }
+ }
+
+ if(D) Log.d(TAG, "Done adding conversations - list size:" +
+ convoList.getCount());
+
+ // If sizeOnly - we are all done here - return the list as is - no need to populate the
+ // list.
+ if(sizeOnly) {
+ return convoList;
+ }
+
+ /* Enable this if post sorting and segmenting needed */
+ /* This is too early */
+ convoList.sort();
+ convoList.segment(ap.getMaxListCount(), offsetNum);
+ List<BluetoothMapConvoListingElement> list = convoList.getList();
+ int listSize = list.size();
+ if(V) Log.i(TAG, "List Size:" + listSize);
+ Cursor tmpCursor = null;
+ SmsMmsContacts contacts = new SmsMmsContacts();
+ for(int x=0;x<listSize;x++){
+ BluetoothMapConvoListingElement ele = list.get(x);
+ TYPE type = ele.getType();
+ switch(type) {
+ case SMS_CDMA:
+ case SMS_GSM:
+ case MMS: {
+ tmpCursor = null; // SMS/MMS needs special treatment
+ if(smsMmsCursor != null) {
+ populateSmsMmsConvoElement(ele, smsMmsCursor, ap, contacts);
+ }
+ if(D) fi.mMsgType = FilterInfo.TYPE_IM;
+ break;
+ }
+ case EMAIL:
+ tmpCursor = imEmailCursor;
+ fi.mMsgType = FilterInfo.TYPE_EMAIL;
+ break;
+ case IM:
+ tmpCursor = imEmailCursor;
+ fi.mMsgType = FilterInfo.TYPE_IM;
+ break;
+ default:
+ tmpCursor = null;
+ break;
+ }
+
+ if(D) Log.d(TAG, "Working on cursor of type " + fi.mMsgType);
+
+ if(tmpCursor != null){
+ populateImEmailConvoElement(ele, tmpCursor, ap, fi);
+ }else {
+ // No, it will be for SMS/MMS at the moment
+ if(D) Log.d(TAG, "tmpCursor is Null - something is wrong - or the message is" +
+ " of type SMS/MMS");
+ }
+ }
+ } finally {
+ if(imEmailCursor != null)imEmailCursor.close();
+ if(smsMmsCursor != null)smsMmsCursor.close();
+ if(D)Log.d(TAG, "conversation end");
+ }
+ return convoList;
+ }
+
+
+ /**
+ * Refreshes the entire list of SMS/MMS conversation version counters. Use it to generate a
+ * new ConvoListVersinoCounter in mSmsMmsConvoListVersion
+ * @return
+ */
+ /* package */
+ boolean refreshSmsMmsConvoVersions() {
+ boolean listChangeDetected = false;
+ Cursor cursor = null;
+ Uri uri = Threads.CONTENT_URI.buildUpon()
+ .appendQueryParameter("simple", "true").build();
+ cursor = mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, null,
+ null, Threads.DATE + " DESC");
+ try {
+ if (cursor != null) {
+ // store column index so we don't have to look them up anymore (optimization)
+ if(D) Log.d(TAG, "Found " + cursor.getCount()
+ + " sms/mms conversations.");
+ BluetoothMapConvoListingElement convoElement = null;
+ cursor.moveToPosition(-1);
+ synchronized (getSmsMmsConvoList()) {
+ int size = Math.max(getSmsMmsConvoList().size(), cursor.getCount());
+ HashMap<Long,BluetoothMapConvoListingElement> newList =
+ new HashMap<Long,BluetoothMapConvoListingElement>(size);
+ while (cursor.moveToNext()) {
+ // TODO: Extract to function, that can be called at listing, which returns
+ // the versionCounter(existing or new).
+ boolean convoChanged = false;
+ Long id = cursor.getLong(MMS_SMS_THREAD_COL_ID);
+ convoElement = getSmsMmsConvoList().remove(id);
+ if(convoElement == null) {
+ // New conversation added
+ convoElement = new BluetoothMapConvoListingElement();
+ convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, id);
+ listChangeDetected = true;
+ convoElement.setVersionCounter(0);
+ }
+ // Currently we only need to compare name, last_activity and read_status, and
+ // name is not used for SMS/MMS.
+ // msg delete will be handled by update folderVersionCounter().
+ long last_activity = cursor.getLong(MMS_SMS_THREAD_COL_DATE);
+ boolean read = (cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ?
+ true : false;
+
+ if(last_activity != convoElement.getLastActivity()) {
+ convoChanged = true;
+ convoElement.setLastActivity(last_activity);
+ }
+
+ if(read != convoElement.getReadBool()) {
+ convoChanged = true;
+ convoElement.setRead(read, false);
+ }
+
+ String idsStr = cursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS);
+ if(!idsStr.equals(convoElement.getSmsMmsContacts())) {
+ // This should not trigger a change in conversationVersionCounter only the
+ // ConvoListVersionCounter.
+ listChangeDetected = true;
+ convoElement.setSmsMmsContacts(idsStr);
+ }
+
+ if(convoChanged) {
+ listChangeDetected = true;
+ convoElement.incrementVersionCounter();
+ }
+ newList.put(id, convoElement);
+ }
+ // If we still have items on the old list, something was deleted
+ if(getSmsMmsConvoList().size() != 0) {
+ listChangeDetected = true;
+ }
+ setSmsMmsConvoList(newList);
+ }
+
+ if(listChangeDetected) {
+ mMasInstance.updateSmsMmsConvoListVersionCounter();
+ }
+ }
+ } finally {
+ if(cursor != null) {
+ cursor.close();
+ }
+ }
+ return listChangeDetected;
+ }
+
+ /**
+ * Refreshes the entire list of SMS/MMS conversation version counters. Use it to generate a
+ * new ConvoListVersinoCounter in mSmsMmsConvoListVersion
+ * @return
+ */
+ /* package */
+ boolean refreshImEmailConvoVersions() {
+ boolean listChangeDetected = false;
+ FilterInfo fi = new FilterInfo();
+
+ Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION);
+
+ if(V) Log.v(TAG, "URI with parameters: " + contentUri.toString());
+ Cursor imEmailCursor = mResolver.query(contentUri,
+ CONVO_VERSION_PROJECTION,
+ null, null, BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
+ + " DESC, " + BluetoothMapContract.ConversationColumns.THREAD_ID
+ + " ASC");
+ try {
+ if (imEmailCursor != null) {
+ BluetoothMapConvoListingElement convoElement = null;
+ // store column index so we don't have to look them up anymore (optimization)
+ // Here we rely on only a single account-based message type for each MAS.
+ fi.setEmailImConvoColumns(imEmailCursor);
+ boolean isValid = imEmailCursor.moveToNext();
+ if(V) Log.d(TAG, "Found " + imEmailCursor.getCount()
+ + " EMAIL/IM conversations. isValid = " + isValid);
+ synchronized (getImEmailConvoList()) {
+ int size = Math.max(getImEmailConvoList().size(), imEmailCursor.getCount());
+ boolean convoChanged = false;
+ HashMap<Long,BluetoothMapConvoListingElement> newList =
+ new HashMap<Long,BluetoothMapConvoListingElement>(size);
+ while (isValid) {
+ long id = imEmailCursor.getLong(fi.mConvoColConvoId);
+ long nextThreadId;
+ convoElement = getImEmailConvoList().remove(id);
+ if(convoElement == null) {
+ // New conversation added
+ convoElement = new BluetoothMapConvoListingElement();
+ convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id);
+ listChangeDetected = true;
+ convoElement.setVersionCounter(0);
+ }
+ String name = imEmailCursor.getString(fi.mConvoColName);
+ String summary = imEmailCursor.getString(fi.mConvoColSummary);
+ long last_activity = imEmailCursor.getLong(fi.mConvoColLastActivity);
+ boolean read = (imEmailCursor.getInt(fi.mConvoColRead) == 1) ?
+ true : false;
+
+ if(last_activity != convoElement.getLastActivity()) {
+ convoChanged = true;
+ convoElement.setLastActivity(last_activity);
+ }
+
+ if(read != convoElement.getReadBool()) {
+ convoChanged = true;
+ convoElement.setRead(read, false);
+ }
+
+ if(name != null && !name.equals(convoElement.getName())) {
+ convoChanged = true;
+ convoElement.setName(name);
+ }
+
+ if(summary != null && !summary.equals(convoElement.getFullSummary())) {
+ convoChanged = true;
+ convoElement.setSummary(summary);
+ }
+ /* If the query returned one row for each contact, skip all the dublicates */
+ do {
+ nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId);
+ if(V) Log.i(TAG, " threadId = " + id + " newThreadId = " +
+ nextThreadId);
+ } while ((nextThreadId == id) &&
+ (isValid = imEmailCursor.moveToNext() == true));
+
+ if(convoChanged) {
+ listChangeDetected = true;
+ convoElement.incrementVersionCounter();
+ }
+ newList.put(id, convoElement);
+ }
+ // If we still have items on the old list, something was deleted
+ if(getImEmailConvoList().size() != 0) {
+ listChangeDetected = true;
+ }
+ setImEmailConvoList(newList);
+ }
+ }
+ } finally {
+ if(imEmailCursor != null) {
+ imEmailCursor.close();
+ }
+ }
+
+ if(listChangeDetected) {
+ mMasInstance.updateImEmailConvoListVersionCounter();
+ }
+ return listChangeDetected;
+ }
+
+ /**
+ * Update the convoVersionCounter within the element passed as parameter.
+ * This function has the side effect to update the ConvoListVersionCounter if needed.
+ * This function ignores changes to contacts as this shall not change the convoVersionCounter,
+ * only the convoListVersion counter, which will be updated upon request.
+ * @param ele Element to update shall not be null.
+ */
+ private void updateSmsMmsConvoVersion(Cursor cursor, BluetoothMapConvoListingElement ele) {
+ long id = ele.getCpConvoId();
+ BluetoothMapConvoListingElement convoElement = getSmsMmsConvoList().get(id);
+ boolean listChangeDetected = false;
+ boolean convoChanged = false;
+ if(convoElement == null) {
+ // New conversation added
+ convoElement = new BluetoothMapConvoListingElement();
+ getSmsMmsConvoList().put(id, convoElement);
+ convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, id);
+ listChangeDetected = true;
+ convoElement.setVersionCounter(0);
+ }
+ long last_activity = cursor.getLong(MMS_SMS_THREAD_COL_DATE);
+ boolean read = (cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ?
+ true : false;
+
+ if(last_activity != convoElement.getLastActivity()) {
+ convoChanged = true;
+ convoElement.setLastActivity(last_activity);
+ }
+
+ if(read != convoElement.getReadBool()) {
+ convoChanged = true;
+ convoElement.setRead(read, false);
+ }
+
+ if(convoChanged) {
+ listChangeDetected = true;
+ convoElement.incrementVersionCounter();
+ }
+ if(listChangeDetected) {
+ mMasInstance.updateSmsMmsConvoListVersionCounter();
+ }
+ ele.setVersionCounter(convoElement.getVersionCounter());
+ }
+
+ /**
+ * Update the convoVersionCounter within the element passed as parameter.
+ * This function has the side effect to update the ConvoListVersionCounter if needed.
+ * This function ignores changes to contacts as this shall not change the convoVersionCounter,
+ * only the convoListVersion counter, which will be updated upon request.
+ * @param ele Element to update shall not be null.
+ */
+ private void updateImEmailConvoVersion(Cursor cursor, FilterInfo fi,
+ BluetoothMapConvoListingElement ele) {
+ long id = ele.getCpConvoId();
+ BluetoothMapConvoListingElement convoElement = getImEmailConvoList().get(id);
+ boolean listChangeDetected = false;
+ boolean convoChanged = false;
+ if(convoElement == null) {
+ // New conversation added
+ if(V) Log.d(TAG, "Added new conversation with ID = " + id);
+ convoElement = new BluetoothMapConvoListingElement();
+ convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id);
+ getImEmailConvoList().put(id, convoElement);
+ listChangeDetected = true;
+ convoElement.setVersionCounter(0);
+ }
+ String name = cursor.getString(fi.mConvoColName);
+ long last_activity = cursor.getLong(fi.mConvoColLastActivity);
+ boolean read = (cursor.getInt(fi.mConvoColRead) == 1) ?
+ true : false;
+
+ if(last_activity != convoElement.getLastActivity()) {
+ convoChanged = true;
+ convoElement.setLastActivity(last_activity);
+ }
+
+ if(read != convoElement.getReadBool()) {
+ convoChanged = true;
+ convoElement.setRead(read, false);
+ }
+
+ if(name != null && !name.equals(convoElement.getName())) {
+ convoChanged = true;
+ convoElement.setName(name);
+ }
+
+ if(convoChanged) {
+ listChangeDetected = true;
+ if(V) Log.d(TAG, "conversation with ID = " + id + " changed");
+ convoElement.incrementVersionCounter();
+ }
+ if(listChangeDetected) {
+ mMasInstance.updateImEmailConvoListVersionCounter();
+ }
+ ele.setVersionCounter(convoElement.getVersionCounter());
+ }
+
+ /**
+ * @param ele
+ * @param smsMmsCursor
+ * @param ap
+ * @param contacts
+ */
+ private void populateSmsMmsConvoElement(BluetoothMapConvoListingElement ele,
+ Cursor smsMmsCursor, BluetoothMapAppParams ap,
+ SmsMmsContacts contacts) {
+ smsMmsCursor.moveToPosition(ele.getCursorIndex());
+ // TODO: If we ever get beyond 31 bit, change to long
+ int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value
+
+ // TODO: How to determine whether the convo-IDs can be used across message
+ // types?
+ ele.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS,
+ smsMmsCursor.getLong(MMS_SMS_THREAD_COL_ID));
+
+ boolean read = (smsMmsCursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ?
+ true : false;
+ if((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) {
+ ele.setRead(read, true);
+ } else {
+ ele.setRead(read, false);
+ }
+
+ if((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) {
+ long timeStamp = smsMmsCursor.getLong(MMS_SMS_THREAD_COL_DATE);
+ ele.setLastActivity(timeStamp);
+ } else {
+ // We need to delete the time stamp, if it was added for multi msg-type
+ ele.setLastActivity(-1);
+ }
+
+ if((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) {
+ updateSmsMmsConvoVersion(smsMmsCursor, ele);
+ }
+
+ if((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) {
+ ele.setName(""); // We never have a thread name for SMS/MMS
+ }
+
+ if((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) {
+ String summary = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET);
+ String cs = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET_CS);
+ if(summary != null && cs != null && !cs.equals("UTF-8")) {
+ try {
+ // TODO: Not sure this is how to convert to UTF-8
+ summary = new String(summary.getBytes(cs),"UTF-8");
+ } catch (UnsupportedEncodingException e){/*Cannot happen*/}
+ }
+ ele.setSummary(summary);
+ }
+
+ if((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) {
+ if(ap.getFilterRecipient() == null) {
+ // Add contacts only if not already added
+ String idsStr =
+ smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS);
+ addSmsMmsContacts(ele, contacts, idsStr, null, ap);
+ }
+ }
+ }
+
+ /**
+ * @param ele
+ * @param tmpCursor
+ * @param fi
+ */
+ private void populateImEmailConvoElement( BluetoothMapConvoListingElement ele,
+ Cursor tmpCursor, BluetoothMapAppParams ap, FilterInfo fi) {
+ tmpCursor.moveToPosition(ele.getCursorIndex());
+ // TODO: If we ever get beyond 31 bit, change to long
+ int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value
+ long threadId = tmpCursor.getLong(fi.mConvoColConvoId);
+
+ // Mandatory field
+ ele.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, threadId);
+
+ if((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) {
+ ele.setName(tmpCursor.getString(fi.mConvoColName));
+ }
+
+ boolean reportRead = false;
+ if((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) {
+ reportRead = true;
+ }
+ ele.setRead(((1==tmpCursor.getInt(fi.mConvoColRead))?true:false), reportRead);
+
+ long timestamp = tmpCursor.getLong(fi.mConvoColLastActivity);
+ if((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) {
+ ele.setLastActivity(timestamp);
+ } else {
+ // We need to delete the time stamp, if it was added for multi msg-type
+ ele.setLastActivity(-1);
+ }
+
+
+ if((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) {
+ updateImEmailConvoVersion(tmpCursor, fi, ele);
+ }
+ if((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) {
+ ele.setSummary(tmpCursor.getString(fi.mConvoColSummary));
+ }
+ // TODO: For optimization, we could avoid joining the contact and convo tables
+ // if we have no filter nor this bit is set.
+ if((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) {
+ do {
+ BluetoothMapConvoContactElement c = new BluetoothMapConvoContactElement();
+ if((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) != 0) {
+ c.setBtUid(new SignedLongLong(tmpCursor.getLong(fi.mContactColBtUid),0));
+ }
+ if((parameterMask & CONVO_PARAM_MASK_PART_CHAT_STATE) != 0) {
+ c.setChatState(tmpCursor.getInt(fi.mContactColChatState));
+ }
+ if((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE) != 0) {
+ c.setPresenceAvailability(tmpCursor.getInt(fi.mContactColPresenceState));
+ }
+ if((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE_TEXT) != 0) {
+ c.setPresenceStatus(tmpCursor.getString(fi.mContactColPresenceText));
+ }
+ if((parameterMask & CONVO_PARAM_MASK_PART_PRIORITY) != 0) {
+ c.setPriority(tmpCursor.getInt(fi.mContactColPriority));
+ }
+ if((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) != 0) {
+ c.setDisplayName(tmpCursor.getString(fi.mContactColNickname));
+ }
+ if((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) {
+ c.setContactId(tmpCursor.getString(fi.mContactColContactUci));
+ }
+ if((parameterMask & CONVO_PARAM_MASK_PART_LAST_ACTIVITY) != 0) {
+ c.setLastActivity(tmpCursor.getLong(fi.mContactColLastActive));
+ }
+ if((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) {
+ c.setName(tmpCursor.getString(fi.mContactColName));
+ }
+ ele.addContact(c);
+ } while (tmpCursor.moveToNext() == true
+ && tmpCursor.getLong(fi.mConvoColConvoId) == threadId);
+ }
+ }
+
+ /**
+ * Extract the ConvoList parameters from appParams and build the matching URI with
+ * query parameters.
+ * @param ap the appParams from the request
+ * @param contentUri the URI to append parameters to
+ * @return the new URI with the appended parameters (if any)
+ */
+ private Uri appendConvoListQueryParameters(BluetoothMapAppParams ap,
+ Uri contentUri) {
+ Builder newUri = contentUri.buildUpon();
+ String str = ap.getFilterRecipient();
+ if(str != null) {
+ str = str.trim();
+ str = str.replace("*", "%");
+ newUri.appendQueryParameter(BluetoothMapContract.FILTER_ORIGINATOR_SUBSTRING, str);
+ }
+ long time = ap.getFilterLastActivityBegin();
+ if(time > 0) {
+ newUri.appendQueryParameter(BluetoothMapContract.FILTER_PERIOD_BEGIN,
+ Long.toString(time));
+ }
+ time = ap.getFilterLastActivityEnd();
+ if(time > 0) {
+ newUri.appendQueryParameter(BluetoothMapContract.FILTER_PERIOD_END,
+ Long.toString(time));
+ }
+ int readStatus = ap.getFilterReadStatus();
+ if(readStatus > 0) {
+ if(readStatus == 1) {
+ // Conversations with Unread messages only
+ newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS,
+ "false");
+ }else if(readStatus == 2) {
+ // Conversations with all read messages only
+ newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS,
+ "true");
+ }
+ // if both are set it will be the same as requesting an empty list, but
+ // as it makes no sense with such a structure in a bit mask, we treat
+ // requesting both the same as no filtering.
+ }
+ long convoId = -1;
+ if(ap.getFilterConvoId() != null) {
+ convoId = ap.getFilterConvoId().getLeastSignificantBits();
+ }
+ if(convoId > 0) {
+ newUri.appendQueryParameter(BluetoothMapContract.FILTER_THREAD_ID,
+ Long.toString(convoId));
+ }
+ return newUri.build();
+ }
+
+ /**
+ * Procedure if we have a filter:
+ * - loop through all ids to examine if there is a match (this will build the cache)
+ * - If there is a match loop again to add all contacts.
+ *
+ * Procedure if we don't have a filter
+ * - Add all contacts
+ *
+ * @param convoElement
+ * @param contacts
+ * @param idsStr
+ * @param recipientFilter
+ * @return
+ */
+ private boolean addSmsMmsContacts( BluetoothMapConvoListingElement convoElement,
+ SmsMmsContacts contacts, String idsStr, String recipientFilter,
+ BluetoothMapAppParams ap) {
+ BluetoothMapConvoContactElement contactElement;
+ int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value
+ boolean foundContact = false;
+ String[] ids = idsStr.split(" ");
+ long[] longIds = new long[ids.length];
+ if(recipientFilter != null) {
+ recipientFilter = recipientFilter.trim();
+ }
+
+ for (int i = 0; i < ids.length; i++) {
+ long longId;
+ try {
+ longId = Long.parseLong(ids[i]);
+ longIds[i] = longId;
+ if(recipientFilter == null) {
+ // If there is not filter, all we need to do is to parse the ids
+ foundContact = true;
+ continue;
+ }
+ String addr = contacts.getPhoneNumber(mResolver, longId);
+ if(addr == null) {
+ // This can only happen if all messages from a contact is deleted while
+ // performing the query.
+ continue;
+ }
+ MapContact contact =
+ contacts.getContactNameFromPhone(addr, mResolver, recipientFilter);
+ if(D) {
+ Log.d(TAG, " id " + longId + ": " + addr);
+ if(contact != null) {
+ Log.d(TAG," contact name: " + contact.getName() + " X-BT-UID: "
+ + contact.getXBtUid());
+ }
+ }
+ if(contact == null) {
+ continue;
+ }
+ foundContact = true;
+ } catch (NumberFormatException ex) {
+ // skip this id
+ continue;
+ }
+ }
+
+ if(foundContact == true) {
+ foundContact = false;
+ for (long id : longIds) {
+ String addr = contacts.getPhoneNumber(mResolver, id);
+ if(addr == null) {
+ // This can only happen if all messages from a contact is deleted while
+ // performing the query.
+ continue;
+ }
+ foundContact = true;
+ MapContact contact = contacts.getContactNameFromPhone(addr, mResolver);
+
+ if(contact == null) {
+ // We do not have a contact, we need to manually add one
+ contactElement = new BluetoothMapConvoContactElement();
+ if((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) {
+ contactElement.setName(addr); // Use the phone number as name
+ }
+ if((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) {
+ contactElement.setContactId(addr);
+ }
+ } else {
+ contactElement = BluetoothMapConvoContactElement
+ .createFromMapContact(contact, addr);
+ // Remove the parameters not to be reported
+ if((parameterMask & CONVO_PARAM_MASK_PART_UCI) == 0) {
+ contactElement.setContactId(null);
+ }
+ if((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) == 0) {
+ contactElement.setBtUid(null);
+ }
+ if((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) == 0) {
+ contactElement.setDisplayName(null);
+ }
+ }
+ convoElement.addContact(contactElement);
+ }
+ }
+ return foundContact;
+ }
+
+ /**
* Get the folder name of an SMS message or MMS message.
* @param c the cursor pointing at the message
* @return the folder name.
@@ -1534,8 +3243,10 @@
}
public byte[] getMessage(String handle, BluetoothMapAppParams appParams,
- BluetoothMapFolderElement folderElement) throws UnsupportedEncodingException{
+ BluetoothMapFolderElement folderElement, String version)
+ throws UnsupportedEncodingException{
TYPE type = BluetoothMapUtils.getMsgTypeFromHandle(handle);
+ mMessageVersion = version;
long id = BluetoothMapUtils.getCpHandle(handle);
if(appParams.getFractionRequest() == BluetoothMapAppParams.FRACTION_REQUEST_NEXT) {
throw new IllegalArgumentException("FRACTION_REQUEST_NEXT does not make sence as" +
@@ -1549,14 +3260,19 @@
return getMmsMessage(id, appParams);
case EMAIL:
return getEmailMessage(id, appParams, folderElement);
+ case IM:
+ return getIMMessage(id, appParams, folderElement);
}
throw new IllegalArgumentException("Invalid message handle.");
}
- private String setVCardFromPhoneNumber(BluetoothMapbMessage message, String phone, boolean incoming) {
+ private String setVCardFromPhoneNumber(BluetoothMapbMessage message,
+ String phone,
+ boolean incoming) {
String contactId = null, contactName = null;
String[] phoneNumbers = null;
String[] emailAddresses = null;
+ Cursor p;
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
Uri.encode(phone));
@@ -1566,49 +3282,71 @@
String orderBy = Contacts._ID + " ASC";
// Get the contact _ID and name
- Cursor p = mResolver.query(uri, projection, selection, null, orderBy);
-
+ p = mResolver.query(uri, projection, selection, null, orderBy);
try {
- if (p != null && p.moveToFirst()) {
+ if (p != null && p.getCount() >= 1) {
+ p.moveToFirst();
contactId = p.getString(p.getColumnIndex(Contacts._ID));
contactName = p.getString(p.getColumnIndex(Contacts.DISPLAY_NAME));
}
- // Bail out if we are unable to find a contact, based on the phone number
- if(contactId == null) {
- phoneNumbers = new String[1];
- phoneNumbers[0] = phone;
- } else {
- // use only actual phone number
- phoneNumbers = new String[1];
- phoneNumbers[0] = phone;
-
- // Fetch contact e-mail addresses
- close (p);
- p = mResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
- ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
- new String[]{contactId},
- null);
- if (p != null) {
- int i = 0;
- emailAddresses = new String[p.getCount()];
- while (p != null && p.moveToNext()) {
- String emailAddress = p.getString(
- p.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS));
- emailAddresses[i++] = emailAddress;
- }
- }
- }
} finally {
- close(p);
+ if (p != null) p.close();
}
+
+ // Bail out if we are unable to find a contact, based on the phone number
+ if(contactId == null) {
+ phoneNumbers = new String[1];
+ phoneNumbers[0] = phone;
+ } else {
+ // use only actual phone number
+ phoneNumbers = new String[1];
+ phoneNumbers[0] = phone;
+
+ try {
+ if (p != null && p.moveToFirst()) {
+ contactId = p.getString(p.getColumnIndex(Contacts._ID));
+ contactName = p.getString(p.getColumnIndex(Contacts.DISPLAY_NAME));
+ }
+
+ // Bail out if we are unable to find a contact, based on the phone number
+ if(contactId == null) {
+ phoneNumbers = new String[1];
+ phoneNumbers[0] = phone;
+ } else {
+ // use only actual phone number
+ phoneNumbers = new String[1];
+ phoneNumbers[0] = phone;
+
+ // Fetch contact e-mail addresses
+ close (p);
+ p = mResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
+ ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
+ new String[]{contactId},
+ null);
+ if (p != null) {
+ int i = 0;
+ emailAddresses = new String[p.getCount()];
+ while (p != null && p.moveToNext()) {
+ String emailAddress = p.getString(
+ p.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS));
+ emailAddresses[i++] = emailAddress;
+ }
+ }
+ }
+ } finally {
+ close(p);
+ }
+ }
if (incoming == true) {
if(V) Log.d(TAG, "Adding originator for phone:" + phone);
- message.addOriginator(contactName, contactName, phoneNumbers, emailAddresses); // Use version 3.0 as we only have a formatted name
+ // Use version 3.0 as we only have a formatted name
+ message.addOriginator(contactName, contactName, phoneNumbers, emailAddresses,null,null);
} else {
if(V) Log.d(TAG, "Adding recipient for phone:" + phone);
- message.addRecipient(contactName, contactName, phoneNumbers, emailAddresses); // Use version 3.0 as we only have a formatted name
+ // Use version 3.0 as we only have a formatted name
+ message.addRecipient(contactName, contactName, phoneNumbers, emailAddresses,null,null);
}
return contactName;
}
@@ -1628,51 +3366,56 @@
throw new IllegalArgumentException("SMS handle not found");
}
- try {
- if(V) Log.v(TAG,"c.count: " + c.getCount());
+ try{
+ if(c != null && c.moveToFirst())
+ {
+ if(V) Log.v(TAG,"c.count: " + c.getCount());
- if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
- message.setType(TYPE.SMS_GSM);
- } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
- message.setType(TYPE.SMS_CDMA);
- }
-
- String read = c.getString(c.getColumnIndex(Sms.READ));
- if (read.equalsIgnoreCase("1"))
- message.setStatus(true);
- else
- message.setStatus(false);
-
- type = c.getInt(c.getColumnIndex(Sms.TYPE));
- threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
- message.setFolder(getFolderName(type, threadId));
-
- msgBody = c.getString(c.getColumnIndex(Sms.BODY));
-
- String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
-
- time = c.getLong(c.getColumnIndex(Sms.DATE));
- if(type == 1) // Inbox message needs to set the vCard as originator
- setVCardFromPhoneNumber(message, phone, true);
- else // Other messages sets the vCard as the recipient
- setVCardFromPhoneNumber(message, phone, false);
-
- if(charset == MAP_MESSAGE_CHARSET_NATIVE) {
- if(type == 1) //Inbox
- message.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus(msgBody, phone, time));
+ if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
+ message.setType(TYPE.SMS_GSM);
+ } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+ message.setType(TYPE.SMS_CDMA);
+ }
+ message.setVersionString(mMessageVersion);
+ String read = c.getString(c.getColumnIndex(Sms.READ));
+ if (read.equalsIgnoreCase("1"))
+ message.setStatus(true);
else
- message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(msgBody, phone));
- } else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ {
- message.setSmsBody(msgBody);
+ message.setStatus(false);
+
+ type = c.getInt(c.getColumnIndex(Sms.TYPE));
+ threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
+ message.setFolder(getFolderName(type, threadId));
+
+ msgBody = c.getString(c.getColumnIndex(Sms.BODY));
+
+ String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
+
+ time = c.getLong(c.getColumnIndex(Sms.DATE));
+ if(type == 1) // Inbox message needs to set the vCard as originator
+ setVCardFromPhoneNumber(message, phone, true);
+ else // Other messages sets the vCard as the recipient
+ setVCardFromPhoneNumber(message, phone, false);
+
+ if(charset == MAP_MESSAGE_CHARSET_NATIVE) {
+ if(type == 1) //Inbox
+ message.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus(msgBody,
+ phone, time));
+ else
+ message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(msgBody, phone));
+ } else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ {
+ message.setSmsBody(msgBody);
+ }
+ return message.encode();
}
} finally {
- close(c);
+ if (c != null) c.close();
}
return message.encode();
}
- private void extractMmsAddresses(long id, BluetoothMapbMessageMms message) {
+ private void extractMmsAddresses(long id, BluetoothMapbMessageMime message) {
final String[] projection = null;
String selection = new String(Mms.Addr.MSG_ID + "=" + id);
String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr");
@@ -1681,44 +3424,48 @@
Cursor c = mResolver.query( uriAddress, projection, selection, null, null);
try {
- while (c != null && c.moveToNext()) {
- String address = c.getString(c.getColumnIndex(Mms.Addr.ADDRESS));
- if(address.equals(INSERT_ADDRES_TOKEN))
- continue;
- Integer type = c.getInt(c.getColumnIndex(Mms.Addr.TYPE));
- switch(type) {
- case MMS_FROM:
- contactName = setVCardFromPhoneNumber(message, address, true);
- message.addFrom(contactName, address);
- break;
- case MMS_TO:
- contactName = setVCardFromPhoneNumber(message, address, false);
- message.addTo(contactName, address);
- break;
- case MMS_CC:
- contactName = setVCardFromPhoneNumber(message, address, false);
- message.addCc(contactName, address);
- break;
- case MMS_BCC:
- contactName = setVCardFromPhoneNumber(message, address, false);
- message.addBcc(contactName, address);
- break;
- default:
- break;
- }
+ if (c.moveToFirst()) {
+ do {
+ String address = c.getString(c.getColumnIndex(Mms.Addr.ADDRESS));
+ if(address.equals(INSERT_ADDRES_TOKEN))
+ continue;
+ Integer type = c.getInt(c.getColumnIndex(Mms.Addr.TYPE));
+ switch(type) {
+ case MMS_FROM:
+ contactName = setVCardFromPhoneNumber(message, address, true);
+ message.addFrom(contactName, address);
+ break;
+ case MMS_TO:
+ contactName = setVCardFromPhoneNumber(message, address, false);
+ message.addTo(contactName, address);
+ break;
+ case MMS_CC:
+ contactName = setVCardFromPhoneNumber(message, address, false);
+ message.addCc(contactName, address);
+ break;
+ case MMS_BCC:
+ contactName = setVCardFromPhoneNumber(message, address, false);
+ message.addBcc(contactName, address);
+ break;
+ default:
+ break;
+ }
+ } while(c.moveToNext());
}
} finally {
- close (c);
+ if (c != null) c.close();
}
}
+
/**
- * Read out a mms data part and return the data in a byte array.
- * @param partid the content provider id of the mms.
+ * Read out a mime data part and return the data in a byte array.
+ * @param contentPartUri TODO
+ * @param partid the content provider id of the Mime Part.
* @return
*/
- private byte[] readMmsDataPart(long partid) {
- String uriStr = new String(Mms.CONTENT_URI + "/part/" + partid);
+ private byte[] readRawDataPart(Uri contentPartUri, long partid) {
+ String uriStr = new String(contentPartUri+"/"+ partid);
Uri uriAddress = Uri.parse(uriStr);
InputStream is = null;
ByteArrayOutputStream os = new ByteArrayOutputStream();
@@ -1748,7 +3495,7 @@
* @param id the content provider ID of the message
* @param message the bMessage object to add the information to
*/
- private void extractMmsParts(long id, BluetoothMapbMessageMms message)
+ private void extractMmsParts(long id, BluetoothMapbMessageMime message)
{
/* Handling of filtering out non-text parts for exclude
* attachments is handled within the bMessage object. */
@@ -1756,62 +3503,137 @@
String selection = new String(Mms.Part.MSG_ID + "=" + id);
String uriStr = new String(Mms.CONTENT_URI + "/"+ id + "/part");
Uri uriAddress = Uri.parse(uriStr);
- BluetoothMapbMessageMms.MimePart part;
+ BluetoothMapbMessageMime.MimePart part;
Cursor c = mResolver.query(uriAddress, projection, selection, null, null);
-
try {
- while(c != null && c.moveToNext()) {
- Long partId = c.getLong(c.getColumnIndex(BaseColumns._ID));
- String contentType = c.getString(c.getColumnIndex(Mms.Part.CONTENT_TYPE));
- String name = c.getString(c.getColumnIndex(Mms.Part.NAME));
- String charset = c.getString(c.getColumnIndex(Mms.Part.CHARSET));
- String filename = c.getString(c.getColumnIndex(Mms.Part.FILENAME));
- String text = c.getString(c.getColumnIndex(Mms.Part.TEXT));
- Integer fd = c.getInt(c.getColumnIndex(Mms.Part._DATA));
- String cid = c.getString(c.getColumnIndex(Mms.Part.CONTENT_ID));
- String cl = c.getString(c.getColumnIndex(Mms.Part.CONTENT_LOCATION));
- String cdisp = c.getString(c.getColumnIndex(Mms.Part.CONTENT_DISPOSITION));
+ if (c.moveToFirst()) {
+ do {
+ Long partId = c.getLong(c.getColumnIndex(BaseColumns._ID));
+ String contentType = c.getString(c.getColumnIndex(Mms.Part.CONTENT_TYPE));
+ String name = c.getString(c.getColumnIndex(Mms.Part.NAME));
+ String charset = c.getString(c.getColumnIndex(Mms.Part.CHARSET));
+ String filename = c.getString(c.getColumnIndex(Mms.Part.FILENAME));
+ String text = c.getString(c.getColumnIndex(Mms.Part.TEXT));
+ Integer fd = c.getInt(c.getColumnIndex(Mms.Part._DATA));
+ String cid = c.getString(c.getColumnIndex(Mms.Part.CONTENT_ID));
+ String cl = c.getString(c.getColumnIndex(Mms.Part.CONTENT_LOCATION));
+ String cdisp = c.getString(c.getColumnIndex(Mms.Part.CONTENT_DISPOSITION));
- if(V) Log.d(TAG, " _id : " + partId +
- "\n ct : " + contentType +
- "\n partname : " + name +
- "\n charset : " + charset +
- "\n filename : " + filename +
- "\n text : " + text +
- "\n fd : " + fd +
- "\n cid : " + cid +
- "\n cl : " + cl +
- "\n cdisp : " + cdisp);
+ if(V) Log.d(TAG, " _id : " + partId +
+ "\n ct : " + contentType +
+ "\n partname : " + name +
+ "\n charset : " + charset +
+ "\n filename : " + filename +
+ "\n text : " + text +
+ "\n fd : " + fd +
+ "\n cid : " + cid +
+ "\n cl : " + cl +
+ "\n cdisp : " + cdisp);
- part = message.addMimePart();
- part.mContentType = contentType;
- part.mPartName = name;
- part.mContentId = cid;
- part.mContentLocation = cl;
- part.mContentDisposition = cdisp;
+ part = message.addMimePart();
+ part.mContentType = contentType;
+ part.mPartName = name;
+ part.mContentId = cid;
+ part.mContentLocation = cl;
+ part.mContentDisposition = cdisp;
- try {
- if(text != null) {
- part.mData = text.getBytes("UTF-8");
- part.mCharsetName = "utf-8";
- } else {
- part.mData = readMmsDataPart(partId);
- if(charset != null)
- part.mCharsetName = CharacterSets.getMimeName(Integer.parseInt(charset));
+ try {
+ if(text != null) {
+ part.mData = text.getBytes("UTF-8");
+ part.mCharsetName = "utf-8";
+ } else {
+ part.mData =
+ readRawDataPart(Uri.parse(Mms.CONTENT_URI+"/part"), partId);
+ if(charset != null) {
+ part.mCharsetName =
+ CharacterSets.getMimeName(Integer.parseInt(charset));
+ }
+ }
+ } catch (NumberFormatException e) {
+ Log.d(TAG,"extractMmsParts",e);
+ part.mData = null;
+ part.mCharsetName = null;
+ } catch (UnsupportedEncodingException e) {
+ Log.d(TAG,"extractMmsParts",e);
+ part.mData = null;
+ part.mCharsetName = null;
+ } finally {
}
- } catch (NumberFormatException e) {
- Log.d(TAG,"extractMmsParts",e);
- part.mData = null;
- part.mCharsetName = null;
- } catch (UnsupportedEncodingException e) {
- Log.d(TAG,"extractMmsParts",e);
- part.mData = null;
- part.mCharsetName = null;
- }
- part.mFileName = filename;
+ part.mFileName = filename;
+ } while(c.moveToNext());
+ message.updateCharset();
+ }
+
+ } finally {
+ if(c != null) c.close();
+ }
+ }
+ /**
+ * Read out the mms parts and update the bMessage object provided i {@linkplain message}
+ * @param id the content provider ID of the message
+ * @param message the bMessage object to add the information to
+ */
+ private void extractIMParts(long id, BluetoothMapbMessageMime message)
+ {
+ /* Handling of filtering out non-text parts for exclude
+ * attachments is handled within the bMessage object. */
+ final String[] projection = null;
+ String selection = new String(BluetoothMapContract.MessageColumns._ID + "=" + id);
+ String uriStr = new String(mBaseUri
+ + BluetoothMapContract.TABLE_MESSAGE + "/"+ id + "/part");
+ Uri uriAddress = Uri.parse(uriStr);
+ BluetoothMapbMessageMime.MimePart part;
+ Cursor c = mResolver.query(uriAddress, projection, selection, null, null);
+ try{
+ if (c.moveToFirst()) {
+ do {
+ Long partId = c.getLong(
+ c.getColumnIndex(BluetoothMapContract.MessagePartColumns._ID));
+ String charset = c.getString(
+ c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CHARSET));
+ String filename = c.getString(
+ c.getColumnIndex(BluetoothMapContract.MessagePartColumns.FILENAME));
+ String text = c.getString(
+ c.getColumnIndex(BluetoothMapContract.MessagePartColumns.TEXT));
+ String body = c.getString(
+ c.getColumnIndex(BluetoothMapContract.MessagePartColumns.RAW_DATA));
+ String cid = c.getString(
+ c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CONTENT_ID));
+
+ if(V) Log.d(TAG, " _id : " + partId +
+ "\n charset : " + charset +
+ "\n filename : " + filename +
+ "\n text : " + text +
+ "\n cid : " + cid);
+
+ part = message.addMimePart();
+ part.mContentId = cid;
+ try {
+ if(text.equalsIgnoreCase("yes")) {
+ part.mData = body.getBytes("UTF-8");
+ part.mCharsetName = "utf-8";
+ } else {
+ part.mData = readRawDataPart(Uri.parse(mBaseUri
+ + BluetoothMapContract.TABLE_MESSAGE_PART) , partId);
+ if(charset != null)
+ part.mCharsetName = CharacterSets.getMimeName(
+ Integer.parseInt(charset));
+ }
+ } catch (NumberFormatException e) {
+ Log.d(TAG,"extractIMParts",e);
+ part.mData = null;
+ part.mCharsetName = null;
+ } catch (UnsupportedEncodingException e) {
+ Log.d(TAG,"extractIMParts",e);
+ part.mData = null;
+ part.mCharsetName = null;
+ } finally {
+ }
+ part.mFileName = filename;
+ } while(c.moveToNext());
}
} finally {
- close(c);
+ if(c != null) c.close();
}
message.updateCharset();
@@ -1825,41 +3647,51 @@
* @throws UnsupportedEncodingException if UTF-8 is not supported,
* which is guaranteed to be supported on an android device
*/
- public byte[] getMmsMessage(long id, BluetoothMapAppParams appParams) throws UnsupportedEncodingException {
+ public byte[] getMmsMessage(long id,BluetoothMapAppParams appParams)
+ throws UnsupportedEncodingException {
int msgBox, threadId;
if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE)
- throw new IllegalArgumentException("MMS charset native not allowed for MMS - must be utf-8");
+ throw new IllegalArgumentException("MMS charset native not allowed for MMS"
+ +" - must be utf-8");
- BluetoothMapbMessageMms message = new BluetoothMapbMessageMms();
+ BluetoothMapbMessageMime message = new BluetoothMapbMessageMime();
Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, "_ID = " + id, null, null);
- if (c == null || !c.moveToFirst()) {
- throw new IllegalArgumentException("MMS handle not found");
- }
-
try {
- message.setType(TYPE.MMS);
+ if(c != null && c.moveToFirst())
+ {
+ message.setType(TYPE.MMS);
+ message.setVersionString(mMessageVersion);
- // The MMS info:
- String read = c.getString(c.getColumnIndex(Mms.READ));
- if (read.equalsIgnoreCase("1"))
- message.setStatus(true);
- else
- message.setStatus(false);
+ // The MMS info:
+ String read = c.getString(c.getColumnIndex(Mms.READ));
+ if (read.equalsIgnoreCase("1"))
+ message.setStatus(true);
+ else
+ message.setStatus(false);
- msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
- threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
- message.setFolder(getFolderName(msgBox, threadId));
- message.setSubject(c.getString(c.getColumnIndex(Mms.SUBJECT)));
- message.setMessageId(c.getString(c.getColumnIndex(Mms.MESSAGE_ID)));
- message.setContentType(c.getString(c.getColumnIndex(Mms.CONTENT_TYPE)));
- message.setDate(c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L);
- message.setTextOnly(c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) == 0 ? false : true);
- message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true);
+ msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
+ threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
+ message.setFolder(getFolderName(msgBox, threadId));
+ message.setSubject(c.getString(c.getColumnIndex(Mms.SUBJECT)));
+ message.setMessageId(c.getString(c.getColumnIndex(Mms.MESSAGE_ID)));
+ message.setContentType(c.getString(c.getColumnIndex(Mms.CONTENT_TYPE)));
+ message.setDate(c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L);
+ message.setTextOnly(c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) == 0 ? false : true);
+ message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true);
+ // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used
+ // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is
- extractMmsParts(id, message);
- extractMmsAddresses(id, message);
+ // The parts
+ extractMmsParts(id, message);
+
+ // The addresses
+ extractMmsAddresses(id, message);
+
+
+ return message.encode();
+ }
} finally {
- close(c);
+ if (c != null) c.close();
}
return message.encode();
@@ -1888,114 +3720,273 @@
throw new IllegalArgumentException("EMAIL charset not UTF-8");
BluetoothMapbMessageEmail message = new BluetoothMapbMessageEmail();
- Uri contentUri = Uri.parse(mBaseEmailUri + BluetoothMapContract.TABLE_MESSAGE);
- Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = " + id, null, null);
- if (c != null && c.moveToFirst()) {
- throw new IllegalArgumentException("EMAIL handle not found");
- }
-
+ Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
+ Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = "
+ + id, null, null);
try {
- BluetoothMapFolderElement folderElement;
+ if(c != null && c.moveToFirst())
+ {
+ BluetoothMapFolderElement folderElement;
+ FileInputStream is = null;
+ ParcelFileDescriptor fd = null;
+ try {
+ // Handle fraction requests
+ int fractionRequest = appParams.getFractionRequest();
+ if (fractionRequest != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
+ // Fraction requested
+ if(V) {
+ String fractionStr = (fractionRequest == 0) ? "FIRST" : "NEXT";
+ Log.v(TAG, "getEmailMessage - FractionRequest " + fractionStr
+ + " - send compete message" );
+ }
+ // Check if message is complete and if not - request message from server
+ if (c.getString(c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.RECEPTION_STATE)).equalsIgnoreCase(
+ BluetoothMapContract.RECEPTION_STATE_COMPLETE) == false) {
+ // TODO: request message from server
+ Log.w(TAG, "getEmailMessage - receptionState not COMPLETE - Not Implemented!" );
+ }
+ }
+ // Set read status:
+ String read = c.getString(
+ c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
+ if (read != null && read.equalsIgnoreCase("1"))
+ message.setStatus(true);
+ else
+ message.setStatus(false);
- // Handle fraction requests
- int fractionRequest = appParams.getFractionRequest();
- if (fractionRequest != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
- // Fraction requested
- if(V) {
- String fractionStr = (fractionRequest == 0) ? "FIRST" : "NEXT";
- Log.v(TAG, "getEmailMessage - FractionRequest " + fractionStr
- + " - send compete message" );
+ // Set message type:
+ message.setType(TYPE.EMAIL);
+ message.setVersionString(mMessageVersion);
+ // Set folder:
+ long folderId = c.getLong(
+ c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
+ folderElement = currentFolder.getFolderById(folderId);
+ message.setCompleteFolder(folderElement.getFullPath());
+
+ // Set recipient:
+ String nameEmail = c.getString(
+ c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST));
+ Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
+ if (tokens.length != 0) {
+ if(D) Log.d(TAG, "Recipient count= " + tokens.length);
+ int i = 0;
+ while (i < tokens.length) {
+ if(V) Log.d(TAG, "Recipient = " + tokens[i].toString());
+ String[] emails = new String[1];
+ emails[0] = tokens[i].getAddress();
+ String name = tokens[i].getName();
+ message.addRecipient(name, name, null, emails, null, null);
+ i++;
+ }
+ }
+
+ // Set originator:
+ nameEmail = c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST));
+ tokens = Rfc822Tokenizer.tokenize(nameEmail);
+ if (tokens.length != 0) {
+ if(D) Log.d(TAG, "Originator count= " + tokens.length);
+ int i = 0;
+ while (i < tokens.length) {
+ if(V) Log.d(TAG, "Originator = " + tokens[i].toString());
+ String[] emails = new String[1];
+ emails[0] = tokens[i].getAddress();
+ String name = tokens[i].getName();
+ message.addOriginator(name, name, null, emails, null, null);
+ i++;
+ }
+ }
+ } finally {
+ if(c != null) c.close();
}
- // Check if message is complete and if not - request message from server
- if (c.getString(c.getColumnIndex(
- BluetoothMapContract.MessageColumns.RECEPTION_STATE)).equalsIgnoreCase(
- BluetoothMapContract.RECEPTION_STATE_COMPLETE) == false) {
- // TODO: request message from server
- Log.w(TAG, "getEmailMessage - receptionState not COMPLETE - Not Implemented!" );
+ // Find out if we get attachments
+ String attStr = (appParams.getAttachment() == 0) ?
+ "/" + BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS : "";
+ Uri uri = Uri.parse(contentUri + "/" + id + attStr);
+
+ // Get email message body content
+ int count = 0;
+ try {
+ fd = mResolver.openFileDescriptor(uri, "r");
+ is = new FileInputStream(fd.getFileDescriptor());
+ StringBuilder email = new StringBuilder("");
+ byte[] buffer = new byte[1024];
+ while((count = is.read(buffer)) != -1) {
+ // TODO: Handle breaks within a UTF8 character
+ email.append(new String(buffer,0,count));
+ if(V) Log.d(TAG, "Email part = "
+ + new String(buffer,0,count)
+ + " count=" + count);
+ }
+ // Set email message body:
+ message.setEmailBody(email.toString());
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, e);
+ } catch (NullPointerException e) {
+ Log.w(TAG, e);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ } finally {
+ try {
+ if(is != null) is.close();
+ } catch (IOException e) {}
+ try {
+ if(fd != null) fd.close();
+ } catch (IOException e) {}
}
- }
- // Set read status:
- String read = c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
- if (read != null && read.equalsIgnoreCase("1"))
- message.setStatus(true);
- else
- message.setStatus(false);
-
- // Set message type:
- message.setType(TYPE.EMAIL);
-
- // Set folder:
- long folderId = c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
- folderElement = currentFolder.getEmailFolderById(folderId);
- message.setCompleteFolder(folderElement.getFullPath());
-
- // Set recipient:
- String nameEmail = c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST));
- Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
- if (tokens.length != 0) {
- if(D) Log.d(TAG, "Recipient count= " + tokens.length);
- int i = 0;
- while (i < tokens.length) {
- if(V) Log.d(TAG, "Recipient = " + tokens[i].toString());
- String[] emails = new String[1];
- emails[0] = tokens[i].getAddress();
- String name = tokens[i].getName();
- message.addRecipient(name, name, null, emails);
- i++;
- }
- }
-
- // Set originator:
- nameEmail = c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST));
- tokens = Rfc822Tokenizer.tokenize(nameEmail);
- if (tokens.length != 0) {
- if(D) Log.d(TAG, "Originator count= " + tokens.length);
- int i = 0;
- while (i < tokens.length) {
- if(V) Log.d(TAG, "Originator = " + tokens[i].toString());
- String[] emails = new String[1];
- emails[0] = tokens[i].getAddress();
- String name = tokens[i].getName();
- message.addOriginator(name, name, null, emails);
- i++;
- }
- }
-
- // Find out if we get attachments
- String attStr = (appParams.getAttachment() == 0) ? "/" + BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS : "";
- Uri uri = Uri.parse(contentUri + "/" + id + attStr);
-
- // Get email message body content
- int count = 0;
- FileInputStream is = null;
- ParcelFileDescriptor fd = null;
-
- try {
- fd = mResolver.openFileDescriptor(uri, "r");
- is = new FileInputStream(fd.getFileDescriptor());
- StringBuilder email = new StringBuilder("");
- byte[] buffer = new byte[1024];
- while((count = is.read(buffer)) != -1) {
- // TODO: Handle breaks within a UTF8 character
- email.append(new String(buffer,0,count));
- if(V) Log.d(TAG, "Email part = " + new String(buffer,0,count) + " count=" + count);
- }
- // Set email message body:
- message.setEmailBody(email.toString());
- } catch (FileNotFoundException e) {
- Log.w(TAG, e);
- } catch (NullPointerException e) {
- Log.w(TAG, e);
- } catch (IOException e) {
- Log.w(TAG, e);
- } finally {
- close(is);
- close(fd);
+ return message.encode();
}
} finally {
- close(c);
+ if (c != null) c.close();
+ }
+ throw new IllegalArgumentException("EMAIL handle not found");
+ }
+ /**
+ *
+ * @param id the content provider id for the message to fetch.
+ * @param appParams The application parameter object received from the client.
+ * @return a byte[] containing the UTF-8 encoded bMessage to send to the client.
+ * @throws UnsupportedEncodingException if UTF-8 is not supported,
+ * which is guaranteed to be supported on an android device
+ */
+
+ /**
+ *
+ * @param id the content provider id for the message to fetch.
+ * @param appParams The application parameter object received from the client.
+ * @return a byte[] containing the utf-8 encoded bMessage to send to the client.
+ * @throws UnsupportedEncodingException if UTF-8 is not supported,
+ * which is guaranteed to be supported on an android device
+ */
+ public byte[] getIMMessage(long id,
+ BluetoothMapAppParams appParams,
+ BluetoothMapFolderElement folderElement)
+ throws UnsupportedEncodingException {
+ long threadId, folderId;
+
+ if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE)
+ throw new IllegalArgumentException(
+ "IM charset native not allowed for IM - must be utf-8");
+
+ BluetoothMapbMessageMime message = new BluetoothMapbMessageMime();
+ Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
+ Cursor c = mResolver.query(contentUri,
+ BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = " + id, null, null);
+ Cursor contacts = null;
+ try {
+ if(c != null && c.moveToFirst()) {
+ message.setType(TYPE.IM);
+ message.setVersionString(mMessageVersion);
+
+ // The IM message info:
+ int read =
+ c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
+ if (read == 1)
+ message.setStatus(true);
+ else
+ message.setStatus(false);
+
+ threadId =
+ c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_ID));
+ folderId =
+ c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
+ folderElement = folderElement.getFolderById(folderId);
+ message.setCompleteFolder(folderElement.getFullPath());
+ message.setSubject(c.getString(
+ c.getColumnIndex(BluetoothMapContract.MessageColumns.SUBJECT)));
+ message.setMessageId(c.getString(
+ c.getColumnIndex(BluetoothMapContract.MessageColumns._ID)));
+ message.setDate(c.getLong(
+ c.getColumnIndex(BluetoothMapContract.MessageColumns.DATE)));
+ message.setTextOnly(c.getInt(c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE)) != 0 ? false : true);
+
+ message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true);
+
+ // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used
+ // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is
+
+ // The parts
+
+ //FIXME use the parts when ready - until then use the body column for text-only
+ // extractIMParts(id, message);
+ //FIXME next few lines are temporary code
+ MimePart part = message.addMimePart();
+ part.mData = c.getString((c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.BODY))).getBytes("UTF-8");
+ part.mCharsetName = "utf-8";
+ part.mContentId = "0";
+ part.mContentType = "text/plain";
+ message.updateCharset();
+ // FIXME end temp code
+
+ Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
+ contacts = mResolver.query(contactsUri,
+ BluetoothMapContract.BT_CONTACT_PROJECTION,
+ BluetoothMapContract.ConvoContactColumns.CONVO_ID
+ + " = " + threadId, null, null);
+ // TODO this will not work for group-chats
+ if(contacts != null && contacts.moveToFirst()){
+ String name = contacts.getString(contacts.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.NAME));
+ String btUid[] = new String[1];
+ btUid[0]= contacts.getString(contacts.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.X_BT_UID));
+ String nickname = contacts.getString(contacts.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.NICKNAME));
+ String btUci[] = new String[1];
+ String btOwnUci[] = new String[1];
+ btOwnUci[0] = mAccount.getUciFull();
+ btUci[0] = contacts.getString(contacts.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.UCI));
+ if(folderId == BluetoothMapContract.FOLDER_ID_SENT
+ || folderId == BluetoothMapContract.FOLDER_ID_OUTBOX) {
+ message.addRecipient(nickname,name,null, null, btUid, btUci);
+ message.addOriginator(null, btOwnUci);
+
+ }else {
+ message.addOriginator(nickname,name,null, null, btUid, btUci);
+ message.addRecipient(null, btOwnUci);
+
+ }
+ }
+ return message.encode();
+ }
+ } finally {
+ if(c != null) c.close();
+ if(contacts != null) contacts.close();
}
- return message.encode();
+ throw new IllegalArgumentException("IM handle not found");
}
+
+ public void setRemoteFeatureMask(int featureMask){
+ this.mRemoteFeatureMask = featureMask;
+ if(V) Log.d(TAG, "setRemoteFeatureMask");
+ if((this.mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)
+ == BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) {
+ if(V) Log.d(TAG, "setRemoteFeatureMask MAP_MESSAGE_LISTING_FORMAT_V11");
+ this.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
+ }
+ }
+
+ public int getRemoteFeatureMask(){
+ return this.mRemoteFeatureMask;
+ }
+
+ HashMap<Long,BluetoothMapConvoListingElement> getSmsMmsConvoList() {
+ return mMasInstance.getSmsMmsConvoList();
+ }
+
+ void setSmsMmsConvoList(HashMap<Long,BluetoothMapConvoListingElement> smsMmsConvoList) {
+ mMasInstance.setSmsMmsConvoList(smsMmsConvoList);
+ }
+
+ HashMap<Long,BluetoothMapConvoListingElement> getImEmailConvoList() {
+ return mMasInstance.getImEmailConvoList();
+ }
+
+ void setImEmailConvoList(HashMap<Long,BluetoothMapConvoListingElement> imEmailConvoList) {
+ mMasInstance.setImEmailConvoList(imEmailConvoList);
+ }
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index dd14c82..d59ab87 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -1,20 +1,61 @@
/*
-* Copyright (C) 2014 Samsung System LSI
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
+ * Copyright (C) 2014 Samsung System LSI
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.bluetooth.map;
-import java.io.Closeable;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentFilter.MalformedMimeTypeException;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.provider.Telephony;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.Sms;
+import android.provider.Telephony.Sms.Inbox;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SmsManager;
+import android.telephony.SmsMessage;
+import android.telephony.TelephonyManager;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns;
+import com.google.android.mms.pdu.PduHeaders;
+
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -32,64 +73,44 @@
import javax.obex.ResponseCodes;
-import org.xmlpull.v1.XmlSerializer;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentFilter.MalformedMimeTypeException;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.provider.BaseColumns;
-import com.android.bluetooth.mapapi.BluetoothMapContract;
-import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns;
-import android.provider.Telephony;
-import android.provider.Telephony.Mms;
-import android.provider.Telephony.MmsSms;
-import android.provider.Telephony.Sms;
-import android.provider.Telephony.Sms.Inbox;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
-import android.telephony.SmsManager;
-import android.telephony.SmsMessage;
-import android.telephony.TelephonyManager;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.Xml;
-import android.os.Looper;
-
-import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
-import com.android.bluetooth.map.BluetoothMapbMessageMms.MimePart;
-import com.google.android.mms.pdu.PduHeaders;
-
+@TargetApi(19)
public class BluetoothMapContentObserver {
private static final String TAG = "BluetoothMapContentObserver";
private static final boolean D = BluetoothMapService.DEBUG;
private static final boolean V = BluetoothMapService.VERBOSE;
- private static final String EVENT_TYPE_DELETE = "MessageDeleted";
- private static final String EVENT_TYPE_SHIFT = "MessageShift";
- private static final String EVENT_TYPE_NEW = "NewMessage";
+ private static final String EVENT_TYPE_NEW = "NewMessage";
+ private static final String EVENT_TYPE_DELETE = "MessageDeleted";
+ private static final String EVENT_TYPE_REMOVED = "MessageRemoved";
+ private static final String EVENT_TYPE_SHIFT = "MessageShift";
private static final String EVENT_TYPE_DELEVERY_SUCCESS = "DeliverySuccess";
private static final String EVENT_TYPE_SENDING_SUCCESS = "SendingSuccess";
private static final String EVENT_TYPE_SENDING_FAILURE = "SendingFailure";
private static final String EVENT_TYPE_DELIVERY_FAILURE = "DeliveryFailure";
+ private static final String EVENT_TYPE_READ_STATUS = "ReadStatusChanged";
+ private static final String EVENT_TYPE_CONVERSATION = "ConversationChanged";
+ private static final String EVENT_TYPE_PRESENCE = "ParticipantPresenceChanged";
+ private static final String EVENT_TYPE_CHAT_STATE = "ParticipantChatStateChanged";
+ private static final long EVENT_FILTER_NEW_MESSAGE = 1L;
+ private static final long EVENT_FILTER_MESSAGE_DELETED = 1L<<1;
+ private static final long EVENT_FILTER_MESSAGE_SHIFT = 1L<<2;
+ private static final long EVENT_FILTER_SENDING_SUCCESS = 1L<<3;
+ private static final long EVENT_FILTER_SENDING_FAILED = 1L<<4;
+ private static final long EVENT_FILTER_DELIVERY_SUCCESS = 1L<<5;
+ private static final long EVENT_FILTER_DELIVERY_FAILED = 1L<<6;
+ private static final long EVENT_FILTER_MEMORY_FULL = 1L<<7; // Unused
+ private static final long EVENT_FILTER_MEMORY_AVAILABLE = 1L<<8; // Unused
+ private static final long EVENT_FILTER_READ_STATUS_CHANGED = 1L<<9;
+ private static final long EVENT_FILTER_CONVERSATION_CHANGED = 1L<<10;
+ private static final long EVENT_FILTER_PARTICIPANT_PRESENCE_CHANGED = 1L<<11;
+ private static final long EVENT_FILTER_PARTICIPANT_CHATSTATE_CHANGED= 1L<<12;
+ private static final long EVENT_FILTER_MESSAGE_REMOVED = 1L<<13;
+ // TODO: If we are requesting a large message from the network, on a slow connection
+ // 20 seconds might not be enough... But then again 20 seconds is long for other
+ // cases.
private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
private Context mContext;
@@ -100,12 +121,28 @@
private int mMasId;
private boolean mEnableSmsMms = false;
private boolean mObserverRegistered = false;
- private BluetoothMapEmailSettingsItem mAccount;
+ private BluetoothMapAccountItem mAccount;
private String mAuthority = null;
+ // Default supported feature bit mask is 0x1f
+ private int mMapSupportedFeatures = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
+ // Default event report version is 1.0
+ private int mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V10;
+
private BluetoothMapFolderElement mFolders =
new BluetoothMapFolderElement("DUMMY", null); // Will be set by the MAS when generated.
private Uri mMessageUri = null;
+ private Uri mContactUri = null;
+
+ private boolean mTransmitEvents = true;
+
+ /* To make the filter update atomic, we declare it volatile.
+ * To avoid a penalty when using it, copy the value to a local
+ * non-volatile variable when used more than once.
+ * Actually we only ever use the lower 4 bytes of this variable,
+ * hence we could manage without the volatile keyword, but as
+ * we tend to copy ways of doing things, we better do it right:-) */
+ private volatile long mEventFilter = 0xFFFFFFFFL;
public static final int DELETED_THREAD_ID = -1;
@@ -117,13 +154,6 @@
private TYPE mSmsType;
- private static void close(Closeable c) {
- try {
- if (c != null) c.close();
- } catch (IOException e) {
- }
- }
-
static final String[] SMS_PROJECTION = new String[] {
Sms._ID,
Sms.THREAD_ID,
@@ -140,59 +170,253 @@
static final String[] SMS_PROJECTION_SHORT = new String[] {
Sms._ID,
Sms.THREAD_ID,
- Sms.TYPE
+ Sms.TYPE,
+ Sms.READ
+ };
+
+ static final String[] SMS_PROJECTION_SHORT_EXT = new String[] {
+ Sms._ID,
+ Sms.THREAD_ID,
+ Sms.ADDRESS,
+ Sms.BODY,
+ Sms.DATE,
+ Sms.READ,
+ Sms.TYPE,
};
static final String[] MMS_PROJECTION_SHORT = new String[] {
Mms._ID,
Mms.THREAD_ID,
Mms.MESSAGE_TYPE,
- Mms.MESSAGE_BOX
+ Mms.MESSAGE_BOX,
+ Mms.READ
};
- static final String[] EMAIL_PROJECTION_SHORT = new String[] {
+ static final String[] MMS_PROJECTION_SHORT_EXT = new String[] {
+ Mms._ID,
+ Mms.THREAD_ID,
+ Mms.MESSAGE_TYPE,
+ Mms.MESSAGE_BOX,
+ Mms.READ,
+ Mms.DATE,
+ Mms.SUBJECT,
+ Mms.PRIORITY
+ };
+
+ static final String[] MSG_PROJECTION_SHORT = new String[] {
BluetoothMapContract.MessageColumns._ID,
BluetoothMapContract.MessageColumns.FOLDER_ID,
BluetoothMapContract.MessageColumns.FLAG_READ
};
+ static final String[] MSG_PROJECTION_SHORT_EXT = new String[] {
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.FLAG_READ,
+ BluetoothMapContract.MessageColumns.DATE,
+ BluetoothMapContract.MessageColumns.SUBJECT,
+ BluetoothMapContract.MessageColumns.FROM_LIST,
+ BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY
+ };
+
+ static final String[] MSG_PROJECTION_SHORT_EXT2 = new String[] {
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.FLAG_READ,
+ BluetoothMapContract.MessageColumns.DATE,
+ BluetoothMapContract.MessageColumns.SUBJECT,
+ BluetoothMapContract.MessageColumns.FROM_LIST,
+ BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY,
+ BluetoothMapContract.MessageColumns.THREAD_ID,
+ BluetoothMapContract.MessageColumns.THREAD_NAME
+ };
public BluetoothMapContentObserver(final Context context,
- BluetoothMnsObexClient mnsClient,
- BluetoothMapMasInstance masInstance,
- BluetoothMapEmailSettingsItem account,
- boolean enableSmsMms) throws RemoteException {
+ BluetoothMnsObexClient mnsClient,
+ BluetoothMapMasInstance masInstance,
+ BluetoothMapAccountItem account,
+ boolean enableSmsMms) throws RemoteException {
mContext = context;
mResolver = mContext.getContentResolver();
mAccount = account;
mMasInstance = masInstance;
mMasId = mMasInstance.getMasId();
+
+ mMapSupportedFeatures = mMasInstance.getRemoteFeatureMask();
+ if (D) Log.d(TAG, "BluetoothMapContentObserver: Supported features " +
+ Integer.toHexString(mMapSupportedFeatures) ) ;
+
+ if((BluetoothMapUtils.MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT
+ & mMapSupportedFeatures) != 0){
+ mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
+ }
+ // Make sure support for all formats result in latest version returned
+ if((BluetoothMapUtils.MAP_FEATURE_EVENT_REPORT_V12_BIT
+ & mMapSupportedFeatures) != 0){
+ mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+ }
+
if(account != null) {
mAuthority = Uri.parse(account.mBase_uri).getAuthority();
mMessageUri = Uri.parse(account.mBase_uri + "/" + BluetoothMapContract.TABLE_MESSAGE);
+ if (mAccount.getType() == TYPE.IM) {
+ mContactUri = Uri.parse(account.mBase_uri + "/"
+ + BluetoothMapContract.TABLE_CONVOCONTACT);
+ }
+ // TODO: We need to release this again!
mProviderClient = mResolver.acquireUnstableContentProviderClient(mAuthority);
if (mProviderClient == null) {
throw new RemoteException("Failed to acquire provider for " + mAuthority);
}
mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
+ mContactList = mMasInstance.getContactList();
+ if(mContactList == null) {
+ setContactList(new HashMap<String, BluetoothMapConvoContactElement>(), false);
+ initContactsList();
+ }
}
-
mEnableSmsMms = enableSmsMms;
mSmsType = getSmsType();
mMnsClient = mnsClient;
+ /* Get the cached list - if any, else create */
+ mMsgListSms = mMasInstance.getMsgListSms();
+ boolean doInit = false;
+ if(mEnableSmsMms) {
+ if(mMsgListSms == null) {
+ setMsgListSms(new HashMap<Long, Msg>(), false);
+ doInit = true;
+ }
+ mMsgListMms = mMasInstance.getMsgListMms();
+ if(mMsgListMms == null) {
+ setMsgListMms(new HashMap<Long, Msg>(), false);
+ doInit = true;
+ }
+ }
+ if(mAccount != null) {
+ mMsgListMsg = mMasInstance.getMsgListMsg();
+ if(mMsgListMsg == null) {
+ setMsgListMsg(new HashMap<Long, Msg>(), false);
+ doInit = true;
+ }
+ }
+ if(doInit) {
+ initMsgList();
+ }
}
+
+ private Map<Long, Msg> getMsgListSms() {
+ return mMsgListSms;
+ }
+
+
+ private void setMsgListSms(Map<Long, Msg> msgListSms, boolean changesDetected) {
+ mMsgListSms = msgListSms;
+ if(changesDetected) {
+ mMasInstance.updateFolderVersionCounter();
+ }
+ mMasInstance.setMsgListSms(msgListSms);
+ }
+
+
+ private Map<Long, Msg> getMsgListMms() {
+ return mMsgListMms;
+ }
+
+
+ private void setMsgListMms(Map<Long, Msg> msgListMms, boolean changesDetected) {
+ mMsgListMms = msgListMms;
+ if(changesDetected) {
+ mMasInstance.updateFolderVersionCounter();
+ }
+ mMasInstance.setMsgListMms(msgListMms);
+ }
+
+
+ private Map<Long, Msg> getMsgListMsg() {
+ return mMsgListMsg;
+ }
+
+
+ private void setMsgListMsg(Map<Long, Msg> msgListMsg, boolean changesDetected) {
+ mMsgListMsg = msgListMsg;
+ if(changesDetected) {
+ mMasInstance.updateFolderVersionCounter();
+ }
+ mMasInstance.setMsgListMsg(msgListMsg);
+ }
+
+ private Map<String, BluetoothMapConvoContactElement> getContactList() {
+ return mContactList;
+ }
+
+
/**
- * Set the folder structure to be used for this instance.
- * @param folderStructure
+ * Currently we only have data for IM / email contacts
+ * @param contactList
+ * @param changesDetected that is not chat state changed nor presence state changed.
*/
- public void setFolderStructure(BluetoothMapFolderElement folderStructure) {
- this.mFolders = folderStructure;
+ private void setContactList(Map<String, BluetoothMapConvoContactElement> contactList,
+ boolean changesDetected) {
+ mContactList = contactList;
+ if(changesDetected) {
+ mMasInstance.updateImEmailConvoListVersionCounter();
+ }
+ mMasInstance.setContactList(contactList);
+ }
+
+ private static boolean sendEventNewMessage(long eventFilter) {
+ return ((eventFilter & EVENT_FILTER_NEW_MESSAGE) > 0);
+ }
+
+ private static boolean sendEventMessageDeleted(long eventFilter) {
+ return ((eventFilter & EVENT_FILTER_MESSAGE_DELETED) > 0);
+ }
+
+ private static boolean sendEventMessageShift(long eventFilter) {
+ return ((eventFilter & EVENT_FILTER_MESSAGE_SHIFT) > 0);
+ }
+
+ private static boolean sendEventSendingSuccess(long eventFilter) {
+ return ((eventFilter & EVENT_FILTER_SENDING_SUCCESS) > 0);
+ }
+
+ private static boolean sendEventSendingFailed(long eventFilter) {
+ return ((eventFilter & EVENT_FILTER_SENDING_FAILED) > 0);
+ }
+
+ private static boolean sendEventDeliverySuccess(long eventFilter) {
+ return ((eventFilter & EVENT_FILTER_DELIVERY_SUCCESS) > 0);
+ }
+
+ private static boolean sendEventDeliveryFailed(long eventFilter) {
+ return ((eventFilter & EVENT_FILTER_DELIVERY_FAILED) > 0);
+ }
+
+ private static boolean sendEventReadStatusChanged(long eventFilter) {
+ return ((eventFilter & EVENT_FILTER_READ_STATUS_CHANGED) > 0);
+ }
+
+ private static boolean sendEventConversationChanged(long eventFilter) {
+ return ((eventFilter & EVENT_FILTER_CONVERSATION_CHANGED) > 0);
+ }
+
+ private static boolean sendEventParticipantPresenceChanged(long eventFilter) {
+ return ((eventFilter & EVENT_FILTER_PARTICIPANT_PRESENCE_CHANGED) > 0);
+ }
+
+ private static boolean sendEventParticipantChatstateChanged(long eventFilter) {
+ return ((eventFilter & EVENT_FILTER_PARTICIPANT_CHATSTATE_CHANGED) > 0);
+ }
+
+ private static boolean sendEventMessageRemoved(long eventFilter) {
+ return ((eventFilter & EVENT_FILTER_MESSAGE_REMOVED) > 0);
}
private TYPE getSmsType() {
TYPE smsType = null;
- TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ TelephonyManager tm = (TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
smsType = TYPE.SMS_GSM;
@@ -203,7 +427,8 @@
return smsType;
}
- private final ContentObserver mObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ private final ContentObserver mObserver = new ContentObserver(
+ new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
onChange(selfChange, null);
@@ -211,58 +436,162 @@
@Override
public void onChange(boolean selfChange, Uri uri) {
+ if(uri == null) {
+ Log.w(TAG, "onChange() with URI == null - not handled.");
+ return;
+ }
if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId()
- + " Uri: " + uri.toString() + " selfchange: " + selfChange);
+ + " Uri: " + uri.toString() + " selfchange: " + selfChange);
- handleMsgListChanges(uri);
+ if(uri.toString().contains(BluetoothMapContract.TABLE_CONVOCONTACT))
+ handleContactListChanges(uri);
+ else
+ handleMsgListChanges(uri);
}
};
- private static final String folderSms[] = {
- "",
- BluetoothMapContract.FOLDER_NAME_INBOX,
- BluetoothMapContract.FOLDER_NAME_SENT,
- BluetoothMapContract.FOLDER_NAME_DRAFT,
- BluetoothMapContract.FOLDER_NAME_OUTBOX,
- BluetoothMapContract.FOLDER_NAME_OUTBOX,
- BluetoothMapContract.FOLDER_NAME_OUTBOX,
- BluetoothMapContract.FOLDER_NAME_INBOX,
- BluetoothMapContract.FOLDER_NAME_INBOX,
- };
+ private static final HashMap<Integer, String> FOLDER_SMS_MAP;
+ static {
+ FOLDER_SMS_MAP = new HashMap<Integer, String>();
+ FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_INBOX, BluetoothMapContract.FOLDER_NAME_INBOX);
+ FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_SENT, BluetoothMapContract.FOLDER_NAME_SENT);
+ FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_DRAFT, BluetoothMapContract.FOLDER_NAME_DRAFT);
+ FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_OUTBOX, BluetoothMapContract.FOLDER_NAME_OUTBOX);
+ FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_FAILED, BluetoothMapContract.FOLDER_NAME_OUTBOX);
+ FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_QUEUED, BluetoothMapContract.FOLDER_NAME_OUTBOX);
+ }
- private static final String folderMms[] = {
- "",
- BluetoothMapContract.FOLDER_NAME_INBOX,
- BluetoothMapContract.FOLDER_NAME_SENT,
- BluetoothMapContract.FOLDER_NAME_DRAFT,
- BluetoothMapContract.FOLDER_NAME_OUTBOX,
- };
+ private static String getSmsFolderName(int type) {
+ String name = FOLDER_SMS_MAP.get(type);
+ if(name != null) {
+ return name;
+ }
+ Log.e(TAG, "New SMS mailbox types have been introduced, without an update in BT...");
+ return "Unknown";
+ }
+
+
+ private static final HashMap<Integer, String> FOLDER_MMS_MAP;
+ static {
+ FOLDER_MMS_MAP = new HashMap<Integer, String>();
+ FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_INBOX, BluetoothMapContract.FOLDER_NAME_INBOX);
+ FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_SENT, BluetoothMapContract.FOLDER_NAME_SENT);
+ FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_DRAFTS, BluetoothMapContract.FOLDER_NAME_DRAFT);
+ FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_OUTBOX, BluetoothMapContract.FOLDER_NAME_OUTBOX);
+ }
+
+ private static String getMmsFolderName(int mailbox) {
+ String name = FOLDER_MMS_MAP.get(mailbox);
+ if(name != null) {
+ return name;
+ }
+ Log.e(TAG, "New MMS mailboxes have been introduced, without an update in BT...");
+ return "Unknown";
+ }
+
+ /**
+ * Set the folder structure to be used for this instance.
+ * @param folderStructure
+ */
+ public void setFolderStructure(BluetoothMapFolderElement folderStructure) {
+ this.mFolders = folderStructure;
+ }
+
+ private class ConvoContactInfo {
+ public int mConvoColConvoId = -1;
+ public int mConvoColLastActivity = -1;
+ public int mConvoColName = -1;
+ // public int mConvoColRead = -1;
+ // public int mConvoColVersionCounter = -1;
+ public int mContactColUci = -1;
+ public int mContactColConvoId = -1;
+ public int mContactColName = -1;
+ public int mContactColNickname = -1;
+ public int mContactColBtUid = -1;
+ public int mContactColChatState = -1;
+ public int mContactColContactId = -1;
+ public int mContactColLastActive = -1;
+ public int mContactColPresenceState = -1;
+ public int mContactColPresenceText = -1;
+ public int mContactColPriority = -1;
+ public int mContactColLastOnline = -1;
+
+ public void setConvoColunms(Cursor c) {
+ // mConvoColConvoId = c.getColumnIndex(
+ // BluetoothMapContract.ConversationColumns.THREAD_ID);
+ // mConvoColLastActivity = c.getColumnIndex(
+ // BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY);
+ // mConvoColName = c.getColumnIndex(
+ // BluetoothMapContract.ConversationColumns.THREAD_NAME);
+ mContactColConvoId = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.CONVO_ID);
+ mContactColName = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.NAME);
+ mContactColNickname = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.NICKNAME);
+ mContactColBtUid = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.X_BT_UID);
+ mContactColChatState = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.CHAT_STATE);
+ mContactColUci = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.UCI);
+ mContactColNickname = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.NICKNAME);
+ mContactColLastActive = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE);
+ mContactColName = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.NAME);
+ mContactColPresenceState = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE);
+ mContactColPresenceText = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.STATUS_TEXT);
+ mContactColPriority = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.PRIORITY);
+ mContactColLastOnline = c.getColumnIndex(
+ BluetoothMapContract.ConvoContactColumns.LAST_ONLINE);
+ }
+ }
private class Event {
String eventType;
long handle;
- String folder;
- String oldFolder;
+ String folder = null;
+ String oldFolder = null;
TYPE msgType;
+ /* Extended event parameters in MAP Event version 1.1 */
+ String datetime = null; // OBEX time "YYYYMMDDTHHMMSS"
+ String uci = null;
+ String subject = null;
+ String senderName = null;
+ String priority = null;
+ /* Event parameters in MAP Event version 1.2 */
+ String conversationName = null;
+ long conversationID = -1;
+ int presenceState = BluetoothMapContract.PresenceState.UNKNOWN;
+ String presenceStatus = null;
+ int chatState = BluetoothMapContract.ChatState.UNKNOWN;
final static String PATH = "telecom/msg/";
- public Event(String eventType, long handle, String folder,
- String oldFolder, TYPE msgType) {
-
- this.eventType = eventType;
- this.handle = handle;
- if (folder != null) {
- if(msgType == TYPE.EMAIL) {
- this.folder = folder;
+ private void setFolderPath(String name, TYPE type) {
+ if (name != null) {
+ if(type == TYPE.EMAIL || type == TYPE.IM) {
+ this.folder = name;
} else {
- this.folder = PATH + folder;
+ this.folder = PATH + name;
}
} else {
this.folder = null;
}
+ }
+
+ public Event(String eventType, long handle, String folder,
+ String oldFolder, TYPE msgType) {
+ this.eventType = eventType;
+ this.handle = handle;
+ setFolderPath(folder, msgType);
if (oldFolder != null) {
- if(msgType == TYPE.EMAIL) {
+ if(msgType == TYPE.EMAIL || msgType == TYPE.IM) {
this.oldFolder = oldFolder;
} else {
this.oldFolder = PATH + oldFolder;
@@ -273,19 +602,110 @@
this.msgType = msgType;
}
+ public Event(String eventType, long handle, String folder, TYPE msgType) {
+ this.eventType = eventType;
+ this.handle = handle;
+ setFolderPath(folder, msgType);
+ this.msgType = msgType;
+ }
+
+ /* extended event type 1.1 */
+ public Event(String eventType, long handle, String folder, TYPE msgType,
+ String datetime, String subject, String senderName, String priority) {
+ this.eventType = eventType;
+ this.handle = handle;
+ setFolderPath(folder, msgType);
+ this.msgType = msgType;
+ this.datetime = datetime;
+ if (subject != null) {
+ this.subject = BluetoothMapUtils.stripInvalidChars(subject);
+ }
+ if (senderName != null) {
+ this.senderName = BluetoothMapUtils.stripInvalidChars(senderName);
+ }
+ this.priority = priority;
+ }
+
+ /* extended event type 1.2 message events */
+ public Event(String eventType, long handle, String folder, TYPE msgType,
+ String datetime, String subject, String senderName, String priority,
+ long conversationID, String conversationName) {
+ this.eventType = eventType;
+ this.handle = handle;
+ setFolderPath(folder, msgType);
+ this.msgType = msgType;
+ this.datetime = datetime;
+ if (subject != null) {
+ this.subject = BluetoothMapUtils.stripInvalidChars(subject);
+ }
+ if (senderName != null) {
+ this.senderName = BluetoothMapUtils.stripInvalidChars(senderName);
+ }
+ if (conversationID != 0) {
+ this.conversationID = conversationID;
+ }
+ if (conversationName != null) {
+ this.conversationName = BluetoothMapUtils.stripInvalidChars(conversationName);
+ }
+ this.priority = priority;
+ }
+
+ /* extended event type 1.2 for conversation, presence or chat state changed events */
+ public Event(String eventType, String uci, TYPE msgType, String name, String priority,
+ String lastActivity, long conversationID, String conversationName,
+ int presenceState, String presenceStatus, int chatState) {
+ this.eventType = eventType;
+ this.uci = uci;
+ this.msgType = msgType;
+ if (name != null) {
+ this.senderName = BluetoothMapUtils.stripInvalidChars(name);
+ }
+ this.priority = priority;
+ this.datetime = lastActivity;
+ if (conversationID != 0) {
+ this.conversationID = conversationID;
+ }
+ if (conversationName != null) {
+ this.conversationName = BluetoothMapUtils.stripInvalidChars(conversationName);
+ }
+ if (presenceState != BluetoothMapContract.PresenceState.UNKNOWN) {
+ this.presenceState = presenceState;
+ }
+ if (presenceStatus != null) {
+ this.presenceStatus = BluetoothMapUtils.stripInvalidChars(presenceStatus);
+ }
+ if (chatState != BluetoothMapContract.ChatState.UNKNOWN) {
+ this.chatState = chatState;
+ }
+ }
+
public byte[] encode() throws UnsupportedEncodingException {
StringWriter sw = new StringWriter();
XmlSerializer xmlEvtReport = Xml.newSerializer();
+
try {
xmlEvtReport.setOutput(sw);
- xmlEvtReport.startDocument(null, null);
+ xmlEvtReport.startDocument("UTF-8", true);
xmlEvtReport.text("\r\n");
xmlEvtReport.startTag("", "MAP-event-report");
- xmlEvtReport.attribute("", "version", "1.0");
-
+ if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+ xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V10_STR);
+ } else if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+ xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V11_STR);
+ } else {
+ xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V12_STR);
+ }
xmlEvtReport.startTag("", "event");
xmlEvtReport.attribute("", "type", eventType);
- xmlEvtReport.attribute("", "handle", BluetoothMapUtils.getMapHandle(handle, msgType));
+ if (eventType.equals(EVENT_TYPE_CONVERSATION) ||
+ eventType.equals(EVENT_TYPE_PRESENCE) ||
+ eventType.equals(EVENT_TYPE_CHAT_STATE)) {
+ xmlEvtReport.attribute("", "participant_uci", uci);
+ } else {
+ xmlEvtReport.attribute("", "handle",
+ BluetoothMapUtils.getMapHandle(handle, msgType));
+ }
+
if (folder != null) {
xmlEvtReport.attribute("", "folder", folder);
}
@@ -293,8 +713,56 @@
xmlEvtReport.attribute("", "old_folder", oldFolder);
}
xmlEvtReport.attribute("", "msg_type", msgType.name());
- xmlEvtReport.endTag("", "event");
+ /* If MAP event report version is above 1.0 send
+ * extended event report parameters */
+ if (datetime != null) {
+ xmlEvtReport.attribute("", "datetime", datetime);
+ }
+ if (subject != null) {
+ xmlEvtReport.attribute("", "subject",
+ subject.substring(0,subject.length() < 256 ? subject.length() : 256));
+ }
+ if (senderName != null) {
+ xmlEvtReport.attribute("", "senderName", senderName);
+ }
+ if (priority != null) {
+ xmlEvtReport.attribute("", "priority", priority);
+ }
+
+ //}
+ /* Include conversation information from event version 1.2 */
+ if (mMapEventReportVersion > BluetoothMapUtils.MAP_EVENT_REPORT_V11 ) {
+ if (conversationName != null) {
+ xmlEvtReport.attribute("", "conversation_name", conversationName);
+ }
+ if (conversationID != -1) {
+ // Convert provider conversation handle to string incl type
+ xmlEvtReport.attribute("", "conversation_id",
+ BluetoothMapUtils.getMapConvoHandle(conversationID, msgType));
+ }
+ if (eventType.equals(EVENT_TYPE_PRESENCE)) {
+ if (presenceState != 0) {
+ // Convert provider conversation handle to string incl type
+ xmlEvtReport.attribute("", "presence_availability",
+ String.valueOf(presenceState));
+ }
+ if (presenceStatus != null) {
+ // Convert provider conversation handle to string incl type
+ xmlEvtReport.attribute("", "presence_status",
+ presenceStatus.substring(
+ 0,presenceStatus.length() < 256 ? subject.length() : 256));
+ }
+ }
+ if (eventType.equals(EVENT_TYPE_PRESENCE)) {
+ if (chatState != 0) {
+ // Convert provider conversation handle to string incl type
+ xmlEvtReport.attribute("", "chat_state", String.valueOf(chatState));
+ }
+ }
+
+ }
+ xmlEvtReport.endTag("", "event");
xmlEvtReport.endTag("", "MAP-event-report");
xmlEvtReport.endDocument();
} catch (IllegalArgumentException e) {
@@ -311,7 +779,7 @@
}
}
- private class Msg {
+ /*package*/ class Msg {
long id;
int type; // Used as folder for SMS/MMS
int threadId; // Used for SMS/MMS at delete
@@ -319,15 +787,18 @@
long oldFolderId = -1; // Used for email undelete
boolean localInitiatedSend = false; // Used for MMS to filter out events
boolean transparent = false; // Used for EMAIL to delete message sent with transparency
+ int flagRead = -1; // Message status read/unread
- public Msg(long id, int type, int threadId) {
+ public Msg(long id, int type, int threadId, int readFlag) {
this.id = id;
this.type = type;
this.threadId = threadId;
+ this.flagRead = readFlag;
}
- public Msg(long id, long folderId) {
+ public Msg(long id, long folderId, int readFlag) {
this.id = id;
this.folderId = folderId;
+ this.flagRead = readFlag;
}
/* Eclipse generated hashCode() and equals() to make
@@ -357,11 +828,13 @@
}
}
- private Map<Long, Msg> mMsgListSms = new HashMap<Long, Msg>();
+ private Map<Long, Msg> mMsgListSms = null;
- private Map<Long, Msg> mMsgListMms = new HashMap<Long, Msg>();
+ private Map<Long, Msg> mMsgListMms = null;
- private Map<Long, Msg> mMsgListEmail = new HashMap<Long, Msg>();
+ private Map<Long, Msg> mMsgListMsg = null;
+
+ private Map<String, BluetoothMapConvoContactElement> mContactList = null;
public int setNotificationRegistration(int notificationStatus) throws RemoteException {
// Forward the request to the MNS thread as a message - including the MAS instance ID.
@@ -375,7 +848,8 @@
mns.sendMessageDelayed(msg, 10); // Send message without forcing a context switch
/* Some devices - e.g. PTS needs to get the unregister confirm before we actually
* disconnect the MNS. */
- if(D) Log.d(TAG,"setNotificationRegistration() MSG_MNS_NOTIFICATION_REGISTRATION send to MNS");
+ if(D) Log.d(TAG,"setNotificationRegistration() MSG_MNS_NOTIFICATION_REGISTRATION " +
+ "send to MNS");
} else {
// This should not happen except at shutdown.
if(D) Log.d(TAG,"setNotificationRegistration() Unable to send registration request");
@@ -389,18 +863,43 @@
return ResponseCodes.OBEX_HTTP_OK;
}
+ boolean eventMaskContainsContacts(long mask) {
+ return sendEventParticipantPresenceChanged(mask);
+ }
+
+ boolean eventMaskContainsCovo(long mask) {
+ return (sendEventConversationChanged(mask)
+ || sendEventParticipantChatstateChanged(mask));
+ }
+
+ /* Overwrite the existing notification filter. Will register/deregister observers for
+ * the Contacts and Conversation table as needed. We keep the message observer
+ * at all times. */
+ /*package*/ synchronized void setNotificationFilter(long newFilter) {
+ long oldFilter = mEventFilter;
+ mEventFilter = newFilter;
+ /* Contacts */
+ if(!eventMaskContainsContacts(oldFilter) &&
+ eventMaskContainsContacts(newFilter)) {
+ // TODO:
+ // Enable the observer
+ // Reset the contacts list
+ }
+ /* Conversations */
+ if(!eventMaskContainsCovo(oldFilter) &&
+ eventMaskContainsCovo(newFilter)) {
+ // TODO:
+ // Enable the observer
+ // Reset the conversations list
+ }
+ }
+
public void registerObserver() throws RemoteException{
if (V) Log.d(TAG, "registerObserver");
if (mObserverRegistered)
return;
- /* Use MmsSms Uri since the Sms Uri is not notified on deletes */
- if(mEnableSmsMms){
- //this is sms/mms
- mResolver.registerContentObserver(MmsSms.CONTENT_URI, false, mObserver);
- mObserverRegistered = true;
- }
if(mAccount != null) {
mProviderClient = mResolver.acquireUnstableContentProviderClient(mAuthority);
@@ -409,8 +908,28 @@
}
mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
+ // If there is a change in the database before we init the lists we will be sending
+ // loads of events - hence init before register.
+ if(mAccount.getType() == TYPE.IM) {
+ // Further add contact list tracking
+ initContactsList();
+ }
+ }
+ // If there is a change in the database before we init the lists we will be sending
+ // loads of events - hence init before register.
+ initMsgList();
+
+ /* Use MmsSms Uri since the Sms Uri is not notified on deletes */
+ if(mEnableSmsMms){
+ //this is sms/mms
+ mResolver.registerContentObserver(MmsSms.CONTENT_URI, false, mObserver);
+ mObserverRegistered = true;
+ }
+
+ if(mAccount != null) {
/* For URI's without account ID */
- Uri uri = Uri.parse(mAccount.mBase_uri_no_account + "/" + BluetoothMapContract.TABLE_MESSAGE);
+ Uri uri = Uri.parse(mAccount.mBase_uri_no_account + "/"
+ + BluetoothMapContract.TABLE_MESSAGE);
if(D) Log.d(TAG, "Registering observer for: " + uri);
mResolver.registerContentObserver(uri, true, mObserver);
@@ -419,9 +938,23 @@
uri = Uri.parse(mAccount.mBase_uri + "/" + BluetoothMapContract.TABLE_MESSAGE);
if(D) Log.d(TAG, "Registering observer for: " + uri);
mResolver.registerContentObserver(uri, true, mObserver);
+
+ if(mAccount.getType() == TYPE.IM) {
+
+ uri = Uri.parse(mAccount.mBase_uri_no_account + "/"
+ + BluetoothMapContract.TABLE_CONVOCONTACT);
+ if(D) Log.d(TAG, "Registering observer for: " + uri);
+ mResolver.registerContentObserver(uri, true, mObserver);
+
+ /* For URI's with account ID - is handled the same way as without ID, but is
+ * only triggered for MAS instances with matching account ID. */
+ uri = Uri.parse(mAccount.mBase_uri + "/" + BluetoothMapContract.TABLE_CONVOCONTACT);
+ if(D) Log.d(TAG, "Registering observer for: " + uri);
+ mResolver.registerContentObserver(uri, true, mObserver);
+ }
+
mObserverRegistered = true;
}
- initMsgList();
}
public void unregisterObserver() {
@@ -434,19 +967,150 @@
}
}
+ /**
+ * Per design it is only possible to call the refreshXxxx functions sequentially, hence it
+ * is safe to modify mTransmitEvents without synchronization.
+ */
+ /* package */ void refreshFolderVersionCounter() {
+ if (mObserverRegistered) {
+ // As we have observers, we already keep the counter up-to-date.
+ return;
+ }
+ /* We need to perform the same functionality, as when we receive a notification change,
+ hence we:
+ - disable the event transmission
+ - triggers the code for updates
+ - enable the event transmission */
+ mTransmitEvents = false;
+ try {
+ if(mEnableSmsMms) {
+ handleMsgListChangesSms();
+ handleMsgListChangesMms();
+ }
+ if(mAccount != null) {
+ try {
+ handleMsgListChangesMsg(mMessageUri);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to update FolderVersionCounter. - Not fatal, but can cause" +
+ " undesirable user experience!", e);
+ }
+ }
+ } finally {
+ // Ensure we always enable events again
+ mTransmitEvents = true;
+ }
+ }
+
+ /* package */ void refreshConvoListVersionCounter() {
+ if (mObserverRegistered) {
+ // As we have observers, we already keep the counter up-to-date.
+ return;
+ }
+ /* We need to perform the same functionality, as when we receive a notification change,
+ hence we:
+ - disable event transmission
+ - triggers the code for updates
+ - enable event transmission */
+ mTransmitEvents = false;
+ try {
+ if((mAccount != null) && (mContactUri != null)) {
+ handleContactListChanges(mContactUri);
+ }
+ } finally {
+ // Ensure we always enable events again
+ mTransmitEvents = true;
+ }
+ }
+
private void sendEvent(Event evt) {
- Log.d(TAG, "sendEvent: " + evt.eventType + " " + evt.handle + " "
- + evt.folder + " " + evt.oldFolder + " " + evt.msgType.name());
+
+ if(mTransmitEvents == false) {
+ if(V) Log.v(TAG, "mTransmitEvents == false - don't send event.");
+ return;
+ }
+
+ if(D)Log.d(TAG, "sendEvent: " + evt.eventType + " " + evt.handle + " " + evt.folder + " "
+ + evt.oldFolder + " " + evt.msgType.name() + " " + evt.datetime + " "
+ + evt.subject + " " + evt.senderName + " " + evt.priority );
if (mMnsClient == null || mMnsClient.isConnected() == false) {
Log.d(TAG, "sendEvent: No MNS client registered or connected- don't send event");
return;
}
+ /* Enable use of the cache for checking the filter */
+ long eventFilter = mEventFilter;
+
+ /* This should have been a switch on the string, but it is not allowed in Java 1.6 */
+ /* WARNING: Here we do pointer compare for the string to speed up things, that is.
+ * HENCE: always use the EVENT_TYPE_"defines" */
+ if(evt.eventType == EVENT_TYPE_NEW) {
+ if(!sendEventNewMessage(eventFilter)) {
+ if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+ return;
+ }
+ } else if(evt.eventType == EVENT_TYPE_DELETE) {
+ if(!sendEventMessageDeleted(eventFilter)) {
+ if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+ return;
+ }
+ } else if(evt.eventType == EVENT_TYPE_REMOVED) {
+ if(!sendEventMessageRemoved(eventFilter)) {
+ if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+ return;
+ }
+ } else if(evt.eventType == EVENT_TYPE_SHIFT) {
+ if(!sendEventMessageShift(eventFilter)) {
+ if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+ return;
+ }
+ } else if(evt.eventType == EVENT_TYPE_DELEVERY_SUCCESS) {
+ if(!sendEventDeliverySuccess(eventFilter)) {
+ if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+ return;
+ }
+ } else if(evt.eventType == EVENT_TYPE_SENDING_SUCCESS) {
+ if(!sendEventSendingSuccess(eventFilter)) {
+ if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+ return;
+ }
+ } else if(evt.eventType == EVENT_TYPE_SENDING_FAILURE) {
+ if(!sendEventSendingFailed(eventFilter)) {
+ if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+ return;
+ }
+ } else if(evt.eventType == EVENT_TYPE_DELIVERY_FAILURE) {
+ if(!sendEventDeliveryFailed(eventFilter)) {
+ if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+ return;
+ }
+ } else if(evt.eventType == EVENT_TYPE_READ_STATUS) {
+ if(!sendEventReadStatusChanged(eventFilter)) {
+ if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+ return;
+ }
+ } else if(evt.eventType == EVENT_TYPE_CONVERSATION) {
+ if(!sendEventConversationChanged(eventFilter)) {
+ if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+ return;
+ }
+ } else if(evt.eventType == EVENT_TYPE_PRESENCE) {
+ if(!sendEventParticipantPresenceChanged(eventFilter)) {
+ if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+ return;
+ }
+ } else if(evt.eventType == EVENT_TYPE_CHAT_STATE) {
+ if(!sendEventParticipantChatstateChanged(eventFilter)) {
+ if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+ return;
+ }
+ }
+
try {
mMnsClient.sendEvent(evt.encode(), mMasId);
} catch (UnsupportedEncodingException ex) {
/* do nothing */
+ if (D) Log.e(TAG, "Exception - should not happen: ",ex);
}
}
@@ -458,145 +1122,274 @@
HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>();
Cursor c = mResolver.query(Sms.CONTENT_URI,
- SMS_PROJECTION_SHORT, null, null, null);
-
+ SMS_PROJECTION_SHORT, null, null, null);
try {
- while (c != null && c.moveToNext()) {
- long id = c.getLong(c.getColumnIndex(Sms._ID));
- int type = c.getInt(c.getColumnIndex(Sms.TYPE));
- int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
+ if (c != null && c.moveToFirst()) {
+ do {
+ long id = c.getLong(c.getColumnIndex(Sms._ID));
+ int type = c.getInt(c.getColumnIndex(Sms.TYPE));
+ int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
+ int read = c.getInt(c.getColumnIndex(Sms.READ));
- Msg msg = new Msg(id, type, threadId);
- msgListSms.put(id, msg);
+ Msg msg = new Msg(id, type, threadId, read);
+ msgListSms.put(id, msg);
+ } while (c.moveToNext());
}
} finally {
- close(c);
+ if (c != null) c.close();
}
- synchronized(mMsgListSms) {
- mMsgListSms.clear();
- mMsgListSms = msgListSms;
+ synchronized(getMsgListSms()) {
+ getMsgListSms().clear();
+ setMsgListSms(msgListSms, true); // Set initial folder version counter
}
HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>();
c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION_SHORT, null, null, null);
-
try {
- while (c != null && c.moveToNext()) {
- long id = c.getLong(c.getColumnIndex(Mms._ID));
- int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
- int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
+ if (c != null && c.moveToFirst()) {
+ do {
+ long id = c.getLong(c.getColumnIndex(Mms._ID));
+ int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
+ int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
+ int read = c.getInt(c.getColumnIndex(Mms.READ));
- Msg msg = new Msg(id, type, threadId);
- msgListMms.put(id, msg);
+ Msg msg = new Msg(id, type, threadId, read);
+ msgListMms.put(id, msg);
+ } while (c.moveToNext());
}
} finally {
- close(c);
+ if (c != null) c.close();
}
- synchronized(mMsgListMms) {
- mMsgListMms.clear();
- mMsgListMms = msgListMms;
+ synchronized(getMsgListMms()) {
+ getMsgListMms().clear();
+ setMsgListMms(msgListMms, true); // Set initial folder version counter
}
}
if(mAccount != null) {
- HashMap<Long, Msg> msgListEmail = new HashMap<Long, Msg>();
+ HashMap<Long, Msg> msgList = new HashMap<Long, Msg>();
Uri uri = mMessageUri;
- Cursor c = mProviderClient.query(uri, EMAIL_PROJECTION_SHORT, null, null, null);
+ Cursor c = mProviderClient.query(uri, MSG_PROJECTION_SHORT, null, null, null);
try {
- while (c != null && c.moveToNext()) {
- long id = c.getLong(c.getColumnIndex(MessageColumns._ID));
- long folderId = c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
-
- Msg msg = new Msg(id, folderId);
- msgListEmail.put(id, msg);
+ if (c != null && c.moveToFirst()) {
+ do {
+ long id = c.getLong(c.getColumnIndex(MessageColumns._ID));
+ long folderId = c.getInt(c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.FOLDER_ID));
+ int readFlag = c.getInt(c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.FLAG_READ));
+ Msg msg = new Msg(id, folderId, readFlag);
+ msgList.put(id, msg);
+ } while (c.moveToNext());
}
} finally {
- close(c);
+ if (c != null) c.close();
}
- synchronized(mMsgListEmail) {
- mMsgListEmail.clear();
- mMsgListEmail = msgListEmail;
+ synchronized(getMsgListMsg()) {
+ getMsgListMsg().clear();
+ setMsgListMsg(msgList, true);
}
}
}
+ private void initContactsList() throws RemoteException {
+ if (V) Log.d(TAG, "initContactsList");
+ if(mContactUri == null) {
+ if (D) Log.d(TAG, "initContactsList() no mContactUri - nothing to init");
+ return;
+ }
+ Uri uri = mContactUri;
+ Cursor c = mProviderClient.query(uri,
+ BluetoothMapContract.BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION,
+ null, null, null);
+ Map<String, BluetoothMapConvoContactElement> contactList =
+ new HashMap<String, BluetoothMapConvoContactElement>();
+ try {
+ if (c != null && c.moveToFirst()) {
+ ConvoContactInfo cInfo = new ConvoContactInfo();
+ cInfo.setConvoColunms(c);
+ do {
+ long convoId = c.getLong(cInfo.mContactColConvoId);
+ if (convoId == 0)
+ continue;
+ if (V) BluetoothMapUtils.printCursor(c);
+ String uci = c.getString(cInfo.mContactColUci);
+ String name = c.getString(cInfo.mContactColName);
+ String displayName = c.getString(cInfo.mContactColNickname);
+ String presenceStatus = c.getString(cInfo.mContactColPresenceText);
+ int presenceState = c.getInt(cInfo.mContactColPresenceState);
+ long lastActivity = c.getLong(cInfo.mContactColLastActive);
+ int chatState = c.getInt(cInfo.mContactColChatState);
+ int priority = c.getInt(cInfo.mContactColPriority);
+ String btUid = c.getString(cInfo.mContactColBtUid);
+ BluetoothMapConvoContactElement contact =
+ new BluetoothMapConvoContactElement(uci, name, displayName,
+ presenceStatus, presenceState, lastActivity, chatState,
+ priority, btUid);
+ contactList.put(uci, contact);
+ } while (c.moveToNext());
+ }
+ } finally {
+ if (c != null) c.close();
+ }
+ synchronized(getContactList()) {
+ getContactList().clear();
+ setContactList(contactList, true);
+ }
+ }
+
private void handleMsgListChangesSms() {
if (V) Log.d(TAG, "handleMsgListChangesSms");
HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>();
+ boolean listChanged = false;
- Cursor c = mResolver.query(Sms.CONTENT_URI,
- SMS_PROJECTION_SHORT, null, null, null);
-
- synchronized(mMsgListSms) {
+ Cursor c;
+ if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+ c = mResolver.query(Sms.CONTENT_URI,
+ SMS_PROJECTION_SHORT, null, null, null);
+ } else {
+ c = mResolver.query(Sms.CONTENT_URI,
+ SMS_PROJECTION_SHORT_EXT, null, null, null);
+ }
+ synchronized(getMsgListSms()) {
try {
- while (c != null && c.moveToNext()) {
- long id = c.getLong(c.getColumnIndex(Sms._ID));
- int type = c.getInt(c.getColumnIndex(Sms.TYPE));
- int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
+ if (c != null && c.moveToFirst()) {
+ do {
+ long id = c.getLong(c.getColumnIndex(Sms._ID));
+ int type = c.getInt(c.getColumnIndex(Sms.TYPE));
+ int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
+ int read = c.getInt(c.getColumnIndex(Sms.READ));
- Msg msg = mMsgListSms.remove(id);
+ Msg msg = getMsgListSms().remove(id);
- /* We must filter out any actions made by the MCE, hence do not send e.g. a message
- * deleted and/or MessageShift for messages deleted by the MCE. */
+ /* We must filter out any actions made by the MCE, hence do not send e.g.
+ * a message deleted and/or MessageShift for messages deleted by the MCE. */
- if (msg == null) {
- /* New message */
- msg = new Msg(id, type, threadId);
- msgListSms.put(id, msg);
-
- /* Incoming message from the network */
- Event evt = new Event(EVENT_TYPE_NEW, id, folderSms[type],
- null, mSmsType);
- sendEvent(evt);
- } else {
- /* Existing message */
- if (type != msg.type) {
- Log.d(TAG, "new type: " + type + " old type: " + msg.type);
- String oldFolder = folderSms[msg.type];
- String newFolder = folderSms[type];
- // Filter out the intermediate outbox steps
- if(!oldFolder.equals(newFolder)) {
- Event evt = new Event(EVENT_TYPE_SHIFT, id, folderSms[type],
- oldFolder, mSmsType);
- sendEvent(evt);
+ if (msg == null) {
+ /* New message */
+ msg = new Msg(id, type, threadId, read);
+ msgListSms.put(id, msg);
+ listChanged = true;
+ Event evt;
+ if (mTransmitEvents == true && // extract contact details only if needed
+ mMapEventReportVersion >
+ BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+ String date = BluetoothMapUtils.getDateTimeString(
+ c.getLong(c.getColumnIndex(Sms.DATE)));
+ String subject = c.getString(c.getColumnIndex(Sms.BODY));
+ String name = "";
+ String phone = "";
+ if (type == 1) { //inbox
+ phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
+ if (phone != null && !phone.isEmpty()) {
+ name = BluetoothMapContent.getContactNameFromPhone(phone,
+ mResolver);
+ if(name == null || name.isEmpty()){
+ name = phone;
+ }
+ }else{
+ name = phone;
+ }
+ } else {
+ TelephonyManager tm =
+ (TelephonyManager)mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ if (tm != null) {
+ phone = tm.getLine1Number();
+ name = tm.getLine1AlphaTag();
+ if(name == null || name.isEmpty()){
+ name = phone;
+ }
+ }
+ }
+ String priority = "no";// no priority for sms
+ /* Incoming message from the network */
+ if (mMapEventReportVersion ==
+ BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+ evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type),
+ mSmsType, date, subject, name, priority);
+ } else {
+ evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type),
+ mSmsType, date, subject, name, priority,
+ (long)threadId, null);
+ }
+ } else {
+ /* Incoming message from the network */
+ evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type),
+ null, mSmsType);
}
- msg.type = type;
- } else if(threadId != msg.threadId) {
- Log.d(TAG, "Message delete change: type: " + type + " old type: " + msg.type
- + "\n threadId: " + threadId + " old threadId: " + msg.threadId);
- if(threadId == DELETED_THREAD_ID) { // Message deleted
- Event evt = new Event(EVENT_TYPE_DELETE, id, BluetoothMapContract.FOLDER_NAME_DELETED,
- folderSms[msg.type], mSmsType);
- sendEvent(evt);
- msg.threadId = threadId;
- } else { // Undelete
- Event evt = new Event(EVENT_TYPE_SHIFT, id, folderSms[msg.type],
- BluetoothMapContract.FOLDER_NAME_DELETED, mSmsType);
- sendEvent(evt);
- msg.threadId = threadId;
+ sendEvent(evt);
+ } else {
+ /* Existing message */
+ if (type != msg.type) {
+ listChanged = true;
+ Log.d(TAG, "new type: " + type + " old type: " + msg.type);
+ String oldFolder = getSmsFolderName(msg.type);
+ String newFolder = getSmsFolderName(type);
+ // Filter out the intermediate outbox steps
+ if(!oldFolder.equalsIgnoreCase(newFolder)) {
+ Event evt = new Event(EVENT_TYPE_SHIFT, id,
+ getSmsFolderName(type), oldFolder, mSmsType);
+ sendEvent(evt);
+ }
+ msg.type = type;
+ } else if(threadId != msg.threadId) {
+ listChanged = true;
+ Log.d(TAG, "Message delete change: type: " + type
+ + " old type: " + msg.type
+ + "\n threadId: " + threadId
+ + " old threadId: " + msg.threadId);
+ if(threadId == DELETED_THREAD_ID) { // Message deleted
+ // TODO:
+ // We shall only use the folder attribute, but can't remember
+ // wether to set it to "deleted" or the name of the folder
+ // from which the message have been deleted.
+ Event evt = new Event(EVENT_TYPE_DELETE, id,
+ BluetoothMapContract.FOLDER_NAME_DELETED,
+ getSmsFolderName(msg.type), mSmsType);
+ sendEvent(evt);
+ msg.threadId = threadId;
+ } else { // Undelete
+ Event evt = new Event(EVENT_TYPE_SHIFT, id,
+ getSmsFolderName(msg.type),
+ BluetoothMapContract.FOLDER_NAME_DELETED, mSmsType);
+ sendEvent(evt);
+ msg.threadId = threadId;
+ }
}
+ if(read != msg.flagRead) {
+ listChanged = true;
+ msg.flagRead = read;
+ if (mMapEventReportVersion >
+ BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+ Event evt = new Event(EVENT_TYPE_READ_STATUS, id,
+ getSmsFolderName(msg.type), mSmsType);
+ sendEvent(evt);
+ }
+ }
+ msgListSms.put(id, msg);
}
- msgListSms.put(id, msg);
- }
+ } while (c.moveToNext());
}
} finally {
- close(c);
+ if (c != null) c.close();
}
- for (Msg msg : mMsgListSms.values()) {
+ for (Msg msg : getMsgListSms().values()) {
Event evt = new Event(EVENT_TYPE_DELETE, msg.id,
- BluetoothMapContract.FOLDER_NAME_DELETED,
- folderSms[msg.type], mSmsType);
+ BluetoothMapContract.FOLDER_NAME_DELETED,
+ getSmsFolderName(msg.type), mSmsType);
sendEvent(evt);
+ listChanged = true;
}
- mMsgListSms = msgListSms;
+ setMsgListSms(msgListSms, listChanged);
}
}
@@ -604,191 +1397,322 @@
if (V) Log.d(TAG, "handleMsgListChangesMms");
HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>();
+ boolean listChanged = false;
+ Cursor c;
+ if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+ c = mResolver.query(Mms.CONTENT_URI,
+ MMS_PROJECTION_SHORT, null, null, null);
+ } else {
+ c = mResolver.query(Mms.CONTENT_URI,
+ MMS_PROJECTION_SHORT_EXT, null, null, null);
+ }
- Cursor c = mResolver.query(Mms.CONTENT_URI,
- MMS_PROJECTION_SHORT, null, null, null);
+ synchronized(getMsgListMms()) {
+ try{
+ if (c != null && c.moveToFirst()) {
+ do {
+ long id = c.getLong(c.getColumnIndex(Mms._ID));
+ int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
+ int mtype = c.getInt(c.getColumnIndex(Mms.MESSAGE_TYPE));
+ int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
+ // TODO: Go through code to see if we have an issue with mismatch in types
+ // for threadId. Seems to be a long in DB??
+ int read = c.getInt(c.getColumnIndex(Mms.READ));
- synchronized(mMsgListMms) {
- try {
- while (c != null && c.moveToNext()) {
- long id = c.getLong(c.getColumnIndex(Mms._ID));
- int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
- int mtype = c.getInt(c.getColumnIndex(Mms.MESSAGE_TYPE));
- int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
+ Msg msg = getMsgListMms().remove(id);
- Msg msg = mMsgListMms.remove(id);
+ /* We must filter out any actions made by the MCE, hence do not send
+ * e.g. a message deleted and/or MessageShift for messages deleted by the
+ * MCE.*/
- /* We must filter out any actions made by the MCE, hence do not send e.g. a message
- * deleted and/or MessageShift for messages deleted by the MCE. */
-
- if (msg == null) {
- /* New message - only notify on retrieve conf */
- if (folderMms[type].equals(BluetoothMapContract.FOLDER_NAME_INBOX) &&
- mtype != MESSAGE_TYPE_RETRIEVE_CONF) {
+ if (msg == null) {
+ /* New message - only notify on retrieve conf */
+ listChanged = true;
+ if (getMmsFolderName(type).equalsIgnoreCase(
+ BluetoothMapContract.FOLDER_NAME_INBOX) &&
+ mtype != MESSAGE_TYPE_RETRIEVE_CONF) {
continue;
- }
-
- msg = new Msg(id, type, threadId);
- msgListMms.put(id, msg);
-
- /* Incoming message from the network */
- Event evt = new Event(EVENT_TYPE_NEW, id, folderMms[type],
- null, TYPE.MMS);
- sendEvent(evt);
- } else {
- /* Existing message */
- if (type != msg.type) {
- Log.d(TAG, "new type: " + type + " old type: " + msg.type);
+ }
+ msg = new Msg(id, type, threadId, read);
+ msgListMms.put(id, msg);
Event evt;
- if(msg.localInitiatedSend == false) {
- // Only send events about local initiated changes
- evt = new Event(EVENT_TYPE_SHIFT, id, folderMms[type],
- folderMms[msg.type], TYPE.MMS);
- sendEvent(evt);
- }
- msg.type = type;
+ if (mTransmitEvents == true && // extract contact details only if needed
+ mMapEventReportVersion !=
+ BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+ String date = BluetoothMapUtils.getDateTimeString(
+ c.getLong(c.getColumnIndex(Mms.DATE)));
+ String subject = c.getString(c.getColumnIndex(Mms.SUBJECT));
+ if (subject == null || subject.length() == 0) {
+ /* Get subject from mms text body parts - if any exists */
+ subject = BluetoothMapContent.getTextPartsMms(mResolver, id);
+ }
+ int tmpPri = c.getInt(c.getColumnIndex(Mms.PRIORITY));
+ Log.d(TAG, "TEMP handleMsgListChangesMms, " +
+ "newMessage 'read' state: " + read +
+ "priority: " + tmpPri);
- if (folderMms[type].equals(BluetoothMapContract.FOLDER_NAME_SENT)
- && msg.localInitiatedSend == true) {
- msg.localInitiatedSend = false; // Stop tracking changes for this message
- evt = new Event(EVENT_TYPE_SENDING_SUCCESS, id,
- folderSms[type], null, TYPE.MMS);
- sendEvent(evt);
+ String address = BluetoothMapContent.getAddressMms(
+ mResolver,id,BluetoothMapContent.MMS_FROM);
+ String priority = "no";
+ if(tmpPri == PduHeaders.PRIORITY_HIGH)
+ priority = "yes";
+
+ /* Incoming message from the network */
+ if (mMapEventReportVersion ==
+ BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+ evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type),
+ TYPE.MMS, date, subject, address, priority);
+ } else {
+ evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type),
+ TYPE.MMS, date, subject, address, priority,
+ (long)threadId, null);
+ }
+
+ } else {
+ /* Incoming message from the network */
+ evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type),
+ null, TYPE.MMS);
}
- } else if(threadId != msg.threadId) {
- Log.d(TAG, "Message delete change: type: " + type + " old type: " + msg.type
- + "\n threadId: " + threadId + " old threadId: " + msg.threadId);
- if(threadId == DELETED_THREAD_ID) { // Message deleted
- Event evt = new Event(EVENT_TYPE_DELETE, id, BluetoothMapContract.FOLDER_NAME_DELETED,
- folderMms[msg.type], TYPE.MMS);
- sendEvent(evt);
- msg.threadId = threadId;
- } else { // Undelete
- Event evt = new Event(EVENT_TYPE_SHIFT, id, folderMms[msg.type],
- BluetoothMapContract.FOLDER_NAME_DELETED, TYPE.MMS);
- sendEvent(evt);
- msg.threadId = threadId;
+
+ sendEvent(evt);
+ } else {
+ /* Existing message */
+ if (type != msg.type) {
+ Log.d(TAG, "new type: " + type + " old type: " + msg.type);
+ Event evt;
+ listChanged = true;
+ if(msg.localInitiatedSend == false) {
+ // Only send events about local initiated changes
+ evt = new Event(EVENT_TYPE_SHIFT, id, getMmsFolderName(type),
+ getMmsFolderName(msg.type), TYPE.MMS);
+ sendEvent(evt);
+ }
+ msg.type = type;
+
+ if (getMmsFolderName(type).equalsIgnoreCase(
+ BluetoothMapContract.FOLDER_NAME_SENT)
+ && msg.localInitiatedSend == true) {
+ // Stop tracking changes for this message
+ msg.localInitiatedSend = false;
+ evt = new Event(EVENT_TYPE_SENDING_SUCCESS, id,
+ getMmsFolderName(type), null, TYPE.MMS);
+ sendEvent(evt);
+ }
+ } else if(threadId != msg.threadId) {
+ Log.d(TAG, "Message delete change: type: " + type + " old type: "
+ + msg.type
+ + "\n threadId: " + threadId + " old threadId: "
+ + msg.threadId);
+ listChanged = true;
+ if(threadId == DELETED_THREAD_ID) { // Message deleted
+ Event evt = new Event(EVENT_TYPE_DELETE, id,
+ BluetoothMapContract.FOLDER_NAME_DELETED,
+ getMmsFolderName(msg.type), TYPE.MMS);
+ sendEvent(evt);
+ msg.threadId = threadId;
+ } else { // Undelete
+ Event evt = new Event(EVENT_TYPE_SHIFT, id,
+ getMmsFolderName(msg.type),
+ BluetoothMapContract.FOLDER_NAME_DELETED, TYPE.MMS);
+ sendEvent(evt);
+ msg.threadId = threadId;
+ }
}
+ if(read != msg.flagRead) {
+ listChanged = true;
+ msg.flagRead = read;
+ if (mMapEventReportVersion >
+ BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+ Event evt = new Event(EVENT_TYPE_READ_STATUS, id,
+ getMmsFolderName(msg.type), TYPE.MMS);
+ sendEvent(evt);
+ }
+ }
+ msgListMms.put(id, msg);
}
- msgListMms.put(id, msg);
- }
+ } while (c.moveToNext());
+
}
} finally {
- close(c);
+ if (c != null) c.close();
}
-
- for (Msg msg : mMsgListMms.values()) {
+ for (Msg msg : getMsgListMms().values()) {
Event evt = new Event(EVENT_TYPE_DELETE, msg.id,
- BluetoothMapContract.FOLDER_NAME_DELETED,
- folderMms[msg.type], TYPE.MMS);
+ BluetoothMapContract.FOLDER_NAME_DELETED,
+ getMmsFolderName(msg.type), TYPE.MMS);
sendEvent(evt);
+ listChanged = true;
}
- mMsgListMms = msgListMms;
+ setMsgListMms(msgListMms, listChanged);
}
}
- private void handleMsgListChangesEmail(Uri uri) throws RemoteException{
- if (V) Log.v(TAG, "handleMsgListChangesEmail uri: " + uri.toString());
+ private void handleMsgListChangesMsg(Uri uri) throws RemoteException{
+ if (V) Log.v(TAG, "handleMsgListChangesMsg uri: " + uri.toString());
// TODO: Change observer to handle accountId and message ID if present
- HashMap<Long, Msg> msgListEmail = new HashMap<Long, Msg>();
-
- Cursor c = mProviderClient.query(mMessageUri, EMAIL_PROJECTION_SHORT, null, null, null);
-
- synchronized(mMsgListEmail) {
+ HashMap<Long, Msg> msgList = new HashMap<Long, Msg>();
+ Cursor c;
+ boolean listChanged = false;
+ if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+ c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT, null, null, null);
+ } else if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+ c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT_EXT, null, null, null);
+ } else {
+ c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT_EXT2, null, null, null);
+ }
+ synchronized(getMsgListMsg()) {
try {
- while (c != null && c.moveToNext()) {
- long id = c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns._ID));
- int folderId = c.getInt(c.getColumnIndex(
- BluetoothMapContract.MessageColumns.FOLDER_ID));
- Msg msg = mMsgListEmail.remove(id);
- BluetoothMapFolderElement folderElement = mFolders.getEmailFolderById(folderId);
- String newFolder;
- if(folderElement != null) {
- newFolder = folderElement.getFullPath();
- } else {
- newFolder = "unknown"; // This can happen if a new folder is created while connected
- }
-
- /* We must filter out any actions made by the MCE, hence do not send e.g. a message
- * deleted and/or MessageShift for messages deleted by the MCE. */
-
- if (msg == null) {
- /* New message */
- msg = new Msg(id, folderId);
- msgListEmail.put(id, msg);
- Event evt = new Event(EVENT_TYPE_NEW, id, newFolder,
- null, TYPE.EMAIL);
- sendEvent(evt);
- } else {
- /* Existing message */
- if (folderId != msg.folderId) {
- if (D) Log.d(TAG, "new folderId: " + folderId + " old folderId: " + msg.folderId);
- BluetoothMapFolderElement oldFolderElement = mFolders.getEmailFolderById(msg.folderId);
- String oldFolder;
- if(oldFolderElement != null) {
- oldFolder = oldFolderElement.getFullPath();
- } else {
- // This can happen if a new folder is created while connected
- oldFolder = "unknown";
- }
- BluetoothMapFolderElement deletedFolder =
- mFolders.getEmailFolderByName(BluetoothMapContract.FOLDER_NAME_DELETED);
- BluetoothMapFolderElement sentFolder =
- mFolders.getEmailFolderByName(BluetoothMapContract.FOLDER_NAME_SENT);
- /*
- * If the folder is now 'deleted', send a deleted-event in stead of a shift
- * or if message is sent initiated by MAP Client, then send sending-success
- * otherwise send folderShift
- */
- if(deletedFolder != null && deletedFolder.getEmailFolderId() == folderId) {
- Event evt = new Event(EVENT_TYPE_DELETE, msg.id, newFolder,
- oldFolder, TYPE.EMAIL);
- sendEvent(evt);
- } else if(sentFolder != null
- && sentFolder.getEmailFolderId() == folderId
- && msg.localInitiatedSend == true) {
- if(msg.transparent) {
- mResolver.delete(ContentUris.withAppendedId(mMessageUri, id), null, null);
+ if (c != null && c.moveToFirst()) {
+ do {
+ long id = c.getLong(c.getColumnIndex(
+ BluetoothMapContract.MessageColumns._ID));
+ int folderId = c.getInt(c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.FOLDER_ID));
+ int readFlag = c.getInt(c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.FLAG_READ));
+ Msg msg = getMsgListMsg().remove(id);
+ BluetoothMapFolderElement folderElement = mFolders.getFolderById(folderId);
+ String newFolder;
+ if(folderElement != null) {
+ newFolder = folderElement.getFullPath();
+ } else {
+ // This can happen if a new folder is created while connected
+ newFolder = "unknown";
+ }
+ /* We must filter out any actions made by the MCE, hence do not send e.g.
+ * a message deleted and/or MessageShift for messages deleted by the MCE. */
+ if (msg == null) {
+ listChanged = true;
+ /* New message - created with message unread */
+ msg = new Msg(id, folderId, 0, readFlag);
+ msgList.put(id, msg);
+ Event evt;
+ /* Incoming message from the network */
+ if (mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+ String date = BluetoothMapUtils.getDateTimeString(
+ c.getLong(c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.DATE)));
+ String subject = c.getString(c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.SUBJECT));
+ String address = c.getString(c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.FROM_LIST));
+ String priority = "no";
+ if(c.getInt(c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY))
+ == 1)
+ priority = "yes";
+ if (mMapEventReportVersion ==
+ BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+ evt = new Event(EVENT_TYPE_NEW, id, newFolder,
+ mAccount.getType(), date, subject, address, priority);
} else {
- msg.localInitiatedSend = false;
- Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id,
- oldFolder, null, TYPE.EMAIL);
- sendEvent(evt);
+ long thread_id = c.getLong(c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.THREAD_ID));
+ String thread_name = c.getString(c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.THREAD_NAME));
+ evt = new Event(EVENT_TYPE_NEW, id, newFolder,
+ mAccount.getType(), date, subject, address, priority,
+ thread_id, thread_name);
}
} else {
- Event evt = new Event(EVENT_TYPE_SHIFT, id, newFolder,
- oldFolder, TYPE.EMAIL);
- sendEvent(evt);
+ evt = new Event(EVENT_TYPE_NEW, id, newFolder, null, TYPE.EMAIL);
}
- msg.folderId = folderId;
+ sendEvent(evt);
+ } else {
+ /* Existing message */
+ if (folderId != msg.folderId && msg.folderId != -1) {
+ if (D) Log.d(TAG, "new folderId: " + folderId + " old folderId: "
+ + msg.folderId);
+ BluetoothMapFolderElement oldFolderElement =
+ mFolders.getFolderById(msg.folderId);
+ String oldFolder;
+ listChanged = true;
+ if(oldFolderElement != null) {
+ oldFolder = oldFolderElement.getFullPath();
+ } else {
+ // This can happen if a new folder is created while connected
+ oldFolder = "unknown";
+ }
+ BluetoothMapFolderElement deletedFolder =
+ mFolders.getFolderByName(
+ BluetoothMapContract.FOLDER_NAME_DELETED);
+ BluetoothMapFolderElement sentFolder =
+ mFolders.getFolderByName(
+ BluetoothMapContract.FOLDER_NAME_SENT);
+ /*
+ * If the folder is now 'deleted', send a deleted-event in stead of
+ * a shift or if message is sent initiated by MAP Client, then send
+ * sending-success otherwise send folderShift
+ */
+ if(deletedFolder != null && deletedFolder.getFolderId()
+ == folderId) {
+ Event evt = new Event(EVENT_TYPE_DELETE, msg.id, newFolder,
+ oldFolder, mAccount.getType());
+ sendEvent(evt);
+ } else if(sentFolder != null
+ && sentFolder.getFolderId() == folderId
+ && msg.localInitiatedSend == true) {
+ if(msg.transparent) {
+ mResolver.delete(
+ ContentUris.withAppendedId(mMessageUri, id),
+ null, null);
+ } else {
+ msg.localInitiatedSend = false;
+ Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id,
+ oldFolder, null, mAccount.getType());
+ sendEvent(evt);
+ }
+ } else {
+ if (!oldFolder.equalsIgnoreCase("root")) {
+ Event evt = new Event(EVENT_TYPE_SHIFT, id, newFolder,
+ oldFolder, mAccount.getType());
+ sendEvent(evt);
+ }
+ }
+ msg.folderId = folderId;
+ }
+ if(readFlag != msg.flagRead) {
+ listChanged = true;
+
+ if (mMapEventReportVersion >
+ BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+ Event evt = new Event(EVENT_TYPE_READ_STATUS, id, newFolder,
+ mAccount.getType());
+ sendEvent(evt);
+ msg.flagRead = readFlag;
+ }
+ }
+
+ msgList.put(id, msg);
}
- msgListEmail.put(id, msg);
- }
+ } while (c.moveToNext());
}
} finally {
- close(c);
+ if (c != null) c.close();
}
-
// For all messages no longer in the database send a delete notification
- for (Msg msg : mMsgListEmail.values()) {
- BluetoothMapFolderElement oldFolderElement = mFolders.getEmailFolderById(msg.folderId);
+ for (Msg msg : getMsgListMsg().values()) {
+ BluetoothMapFolderElement oldFolderElement = mFolders.getFolderById(msg.folderId);
String oldFolder;
+ listChanged = true;
if(oldFolderElement != null) {
oldFolder = oldFolderElement.getFullPath();
} else {
oldFolder = "unknown";
}
- /* Some e-mail clients delete the message after sending, and creates a new message in sent.
- * We cannot track the message anymore, hence send both a send success and delete message.
+ /* Some e-mail clients delete the message after sending, and creates a
+ * new message in sent. We cannot track the message anymore, hence send both a
+ * send success and delete message.
*/
if(msg.localInitiatedSend == true) {
msg.localInitiatedSend = false;
// If message is send with transparency don't set folder as message is deleted
if (msg.transparent)
oldFolder = null;
- Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id, oldFolder, null, TYPE.EMAIL);
+ Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id, oldFolder, null,
+ mAccount.getType());
sendEvent(evt);
}
/* As this message deleted is only send on a real delete - don't set folder.
@@ -796,29 +1720,253 @@
*/
if (!msg.transparent) {
- Event evt = new Event(EVENT_TYPE_DELETE, msg.id, null, oldFolder, TYPE.EMAIL);
+ Event evt = new Event(EVENT_TYPE_DELETE, msg.id, null, oldFolder,
+ mAccount.getType());
sendEvent(evt);
}
}
- mMsgListEmail = msgListEmail;
+ setMsgListMsg(msgList, listChanged);
}
}
private void handleMsgListChanges(Uri uri) {
if(uri.getAuthority().equals(mAuthority)) {
try {
- handleMsgListChangesEmail(uri);
- }catch(RemoteException e){
+ if(D) Log.d(TAG, "handleMsgListChanges: account type = "
+ + mAccount.getType().toString());
+ handleMsgListChangesMsg(uri);
+ } catch(RemoteException e) {
mMasInstance.restartObexServerSession();
- Log.w(TAG, "Problems contacting the ContentProvider in mas Instance "+mMasId+" restaring ObexServerSession");
+ Log.w(TAG, "Problems contacting the ContentProvider in mas Instance "
+ + mMasId + " restaring ObexServerSession");
}
- } else {
+ }
+ // TODO: check to see if there could be problem with IM and SMS in one instance
+ if (mEnableSmsMms) {
handleMsgListChangesSms();
handleMsgListChangesMms();
}
}
+ private void handleContactListChanges(Uri uri) {
+ if (uri.getAuthority().equals(mAuthority)) {
+ try {
+ if (V) Log.v(TAG,"handleContactListChanges uri: " + uri.toString());
+ Cursor c = null;
+ boolean listChanged = false;
+ try {
+ ConvoContactInfo cInfo = new ConvoContactInfo();
+
+ if (mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V10
+ && mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+ c = mProviderClient
+ .query(mContactUri,
+ BluetoothMapContract.
+ BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION,
+ null, null, null);
+ cInfo.setConvoColunms(c);
+ } else {
+ if (V) Log.v(TAG,"handleContactListChanges MAP version does not" +
+ "support convocontact notifications");
+ return;
+ }
+
+ HashMap<String, BluetoothMapConvoContactElement> contactList =
+ new HashMap<String,
+ BluetoothMapConvoContactElement>(getContactList().size());
+
+ synchronized (getContactList()) {
+ if (c != null && c.moveToFirst()) {
+ do {
+ String uci = c.getString(cInfo.mContactColUci);
+ long convoId = c.getLong(cInfo.mContactColConvoId);
+ if (convoId == 0)
+ continue;
+
+ if (V) BluetoothMapUtils.printCursor(c);
+
+ BluetoothMapConvoContactElement contact =
+ getContactList().remove(uci);
+
+ /*
+ * We must filter out any actions made by the
+ * MCE, hence do not send e.g. a message deleted
+ * and/or MessageShift for messages deleted by
+ * the MCE.
+ */
+ if (contact == null) {
+ listChanged = true;
+ /*
+ * New contact - added to conversation and
+ * tracked here
+ */
+ if (mMapEventReportVersion
+ != BluetoothMapUtils.MAP_EVENT_REPORT_V10
+ && mMapEventReportVersion
+ != BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+ Event evt;
+ String name = c
+ .getString(cInfo.mContactColName);
+ String displayName = c
+ .getString(cInfo.mContactColNickname);
+ String presenceStatus = c
+ .getString(cInfo.mContactColPresenceText);
+ int presenceState = c
+ .getInt(cInfo.mContactColPresenceState);
+ long lastActivity = c
+ .getLong(cInfo.mContactColLastActive);
+ int chatState = c
+ .getInt(cInfo.mContactColChatState);
+ int priority = c
+ .getInt(cInfo.mContactColPriority);
+ String btUid = c
+ .getString(cInfo.mContactColBtUid);
+
+ // Get Conversation information for
+ // event
+// Uri convoUri = Uri
+// .parse(mAccount.mBase_uri
+// + "/"
+// + BluetoothMapContract.TABLE_CONVERSATION);
+// String whereClause = "contacts._id = "
+// + convoId;
+// Cursor cConvo = mProviderClient
+// .query(convoUri,
+// BluetoothMapContract.BT_CONVERSATION_PROJECTION,
+// whereClause, null, null);
+ // TODO: will move out of the loop when merged with CB's
+ // changes make sure to look up col index out side loop
+ String convoName = null;
+// if (cConvo != null
+// && cConvo.moveToFirst()) {
+// convoName = cConvo
+// .getString(cConvo
+// .getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME));
+// }
+
+ contact = new BluetoothMapConvoContactElement(
+ uci, name, displayName,
+ presenceStatus, presenceState,
+ lastActivity, chatState,
+ priority, btUid);
+
+ contactList.put(uci, contact);
+
+ evt = new Event(
+ EVENT_TYPE_CONVERSATION,
+ uci,
+ mAccount.getType(),
+ name,
+ String.valueOf(priority),
+ BluetoothMapUtils
+ .getDateTimeString(lastActivity),
+ convoId, convoName,
+ presenceState, presenceStatus,
+ chatState);
+
+ sendEvent(evt);
+ }
+
+ } else {
+ // Not new - compare updated content
+// Uri convoUri = Uri
+// .parse(mAccount.mBase_uri
+// + "/"
+// + BluetoothMapContract.TABLE_CONVERSATION);
+ // TODO: Should be changed to own provider interface name
+// String whereClause = "contacts._id = "
+// + convoId;
+// Cursor cConvo = mProviderClient
+// .query(convoUri,
+// BluetoothMapContract.BT_CONVERSATION_PROJECTION,
+// whereClause, null, null);
+// // TODO: will move out of the loop when merged with CB's
+// // changes make sure to look up col index out side loop
+ String convoName = null;
+// if (cConvo != null && cConvo.moveToFirst()) {
+// convoName = cConvo
+// .getString(cConvo
+// .getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME));
+// }
+
+ // Check if presence is updated
+ int presenceState = c.getInt(cInfo.mContactColPresenceState);
+ String presenceStatus = c.getString(
+ cInfo.mContactColPresenceText);
+ String currentPresenceStatus = contact
+ .getPresenceStatus();
+ if (contact.getPresenceAvailability() != presenceState
+ || currentPresenceStatus != presenceStatus) {
+ long lastOnline = c
+ .getLong(cInfo.mContactColLastOnline);
+ contact.setPresenceAvailability(presenceState);
+ contact.setLastActivity(lastOnline);
+ if (currentPresenceStatus != null
+ && !currentPresenceStatus
+ .equals(presenceStatus)) {
+ contact.setPresenceStatus(presenceStatus);
+ }
+ Event evt = new Event(
+ EVENT_TYPE_PRESENCE,
+ uci,
+ mAccount.getType(),
+ contact.getName(),
+ String.valueOf(contact
+ .getPriority()),
+ BluetoothMapUtils
+ .getDateTimeString(lastOnline),
+ convoId, convoName,
+ presenceState, presenceStatus,
+ 0);
+ sendEvent(evt);
+ }
+
+ // Check if chat state is updated
+ int chatState = c.getInt(cInfo.mContactColChatState);
+ if (contact.getChatState() != chatState) {
+ // Get DB timestamp
+ long lastActivity = c.getLong(cInfo.mContactColLastActive);
+ contact.setLastActivity(lastActivity);
+ contact.setChatState(chatState);
+ Event evt = new Event(
+ EVENT_TYPE_CHAT_STATE,
+ uci,
+ mAccount.getType(),
+ contact.getName(),
+ String.valueOf(contact
+ .getPriority()),
+ BluetoothMapUtils
+ .getDateTimeString(lastActivity),
+ convoId, convoName, 0, null,
+ chatState);
+ sendEvent(evt);
+ }
+ contactList.put(uci, contact);
+ }
+ } while (c.moveToNext());
+ }
+ if(getContactList().size() > 0) {
+ // one or more contacts were deleted, hence the conversation listing
+ // version counter should change.
+ listChanged = true;
+ }
+ setContactList(contactList, listChanged);
+ } // end synchronized
+ } finally {
+ if (c != null) c.close();
+ }
+ } catch (RemoteException e) {
+ mMasInstance.restartObexServerSession();
+ Log.w(TAG,
+ "Problems contacting the ContentProvider in mas Instance "
+ + mMasId + " restaring ObexServerSession");
+ }
+
+ }
+ // TODO: conversation contact updates if IM and SMS(MMS in one instance
+ }
+
private boolean setEmailMessageStatusDelete(BluetoothMapFolderElement mCurrentFolder,
String uriStr, long handle, int status) {
boolean res = false;
@@ -827,15 +1975,15 @@
int updateCount = 0;
ContentValues contentValues = new ContentValues();
BluetoothMapFolderElement deleteFolder = mFolders.
- getEmailFolderByName(BluetoothMapContract.FOLDER_NAME_DELETED);
+ getFolderByName(BluetoothMapContract.FOLDER_NAME_DELETED);
contentValues.put(BluetoothMapContract.MessageColumns._ID, handle);
- synchronized(mMsgListEmail) {
- Msg msg = mMsgListEmail.get(handle);
+ synchronized(getMsgListMsg()) {
+ Msg msg = getMsgListMsg().get(handle);
if (status == BluetoothMapAppParams.STATUS_VALUE_YES) {
/* Set deleted folder id */
long folderId = -1;
if(deleteFolder != null) {
- folderId = deleteFolder.getEmailFolderId();
+ folderId = deleteFolder.getFolderId();
}
contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID,folderId);
updateCount = mResolver.update(uri, contentValues, null, null);
@@ -845,7 +1993,8 @@
res = true;
if (msg != null) {
msg.oldFolderId = msg.folderId;
- // Update the folder ID to avoid triggering an event for MCE initiated actions.
+ /* Update the folder ID to avoid triggering an event for MCE
+ * initiated actions. */
msg.folderId = folderId;
}
if(D) Log.d(TAG, "Deleted MSG: " + handle + " from folderId: " + folderId);
@@ -857,27 +2006,38 @@
/* Undelete message. move to old folder if we know it,
* else move to inbox - as dictated by the spec. */
if(msg != null && deleteFolder != null &&
- msg.folderId == deleteFolder.getEmailFolderId()) {
+ msg.folderId == deleteFolder.getFolderId()) {
/* Only modify messages in the 'Deleted' folder */
long folderId = -1;
+ BluetoothMapFolderElement inboxFolder = mCurrentFolder.
+ getFolderByName(BluetoothMapContract.FOLDER_NAME_INBOX);
if (msg != null && msg.oldFolderId != -1) {
folderId = msg.oldFolderId;
} else {
- BluetoothMapFolderElement inboxFolder = mCurrentFolder.
- getEmailFolderByName(BluetoothMapContract.FOLDER_NAME_INBOX);
if(inboxFolder != null) {
- folderId = inboxFolder.getEmailFolderId();
+ folderId = inboxFolder.getFolderId();
}
- if(D)Log.d(TAG,"We did not delete the message, hence the old folder is unknown. Moving to inbox.");
+ if(D)Log.d(TAG,"We did not delete the message, hence the old folder " +
+ "is unknown. Moving to inbox.");
}
contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId);
updateCount = mResolver.update(uri, contentValues, null, null);
if(updateCount > 0) {
res = true;
- // Update the folder ID to avoid triggering an event for MCE initiated actions.
- msg.folderId = folderId;
+ /* Update the folder ID to avoid triggering an event for MCE
+ * initiated actions. */
+ /* UPDATE: Actually the BT-Spec. states that an undelete is a move of the
+ * message to INBOX - clearified in errata 5591.
+ * Therefore we update the cache to INBOX-folderId - to trigger a message
+ * shift event to the old-folder. */
+ if(inboxFolder != null) {
+ msg.folderId = inboxFolder.getFolderId();
+ } else {
+ msg.folderId = folderId;
+ }
} else {
- if(D)Log.d(TAG,"We did not delete the message, hence the old folder is unknown. Moving to inbox.");
+ if(D)Log.d(TAG,"We did not delete the message, hence the old folder " +
+ "is unknown. Moving to inbox.");
}
}
}
@@ -885,7 +2045,7 @@
BluetoothMapFolderElement folderElement;
String folderName = "unknown";
if (msg != null) {
- folderElement = mCurrentFolder.getEmailFolderById(msg.folderId);
+ folderElement = mCurrentFolder.getFolderById(msg.folderId);
if(folderElement != null) {
folderName = folderElement.getName();
}
@@ -910,15 +2070,14 @@
boolean res = false;
Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
Cursor c = mResolver.query(uri, null, null, null, null);
-
try {
if (c != null && c.moveToFirst()) {
/* Move to deleted folder, or delete if already in deleted folder */
int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
if (threadId != DELETED_THREAD_ID) {
/* Set deleted thread id */
- synchronized(mMsgListMms) {
- Msg msg = mMsgListMms.get(handle);
+ synchronized(getMsgListMms()) {
+ Msg msg = getMsgListMms().get(handle);
if(msg != null) { // This will always be the case
msg.threadId = DELETED_THREAD_ID;
}
@@ -926,8 +2085,8 @@
updateThreadId(uri, Mms.THREAD_ID, DELETED_THREAD_ID);
} else {
/* Delete from observer message list to avoid delete notifications */
- synchronized(mMsgListMms) {
- mMsgListMms.remove(handle);
+ synchronized(getMsgListMms()) {
+ getMsgListMms().remove(handle);
}
/* Delete message */
mResolver.delete(uri, null, null);
@@ -935,7 +2094,7 @@
res = true;
}
} finally {
- close(c);
+ if (c != null) c.close();
}
return res;
@@ -945,43 +2104,48 @@
boolean res = false;
Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
Cursor c = mResolver.query(uri, null, null, null, null);
-
try {
if (c != null && c.moveToFirst()) {
int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
if (threadId == DELETED_THREAD_ID) {
/* Restore thread id from address, or if no thread for address
- * create new thread by insert and remove of fake message */
+ * create new thread by insert and remove of fake message */
String address;
long id = c.getLong(c.getColumnIndex(Mms._ID));
int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
if (msgBox == Mms.MESSAGE_BOX_INBOX) {
address = BluetoothMapContent.getAddressMms(mResolver, id,
- BluetoothMapContent.MMS_FROM);
+ BluetoothMapContent.MMS_FROM);
} else {
address = BluetoothMapContent.getAddressMms(mResolver, id,
- BluetoothMapContent.MMS_TO);
+ BluetoothMapContent.MMS_TO);
}
Set<String> recipients = new HashSet<String>();
recipients.addAll(Arrays.asList(address));
Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients);
- synchronized(mMsgListMms) {
- Msg msg = mMsgListMms.get(handle);
+ synchronized(getMsgListMms()) {
+ Msg msg = getMsgListMms().get(handle);
if(msg != null) { // This will always be the case
msg.threadId = oldThreadId.intValue();
+ // Spec. states that undelete shall shift the message to Inbox.
+ // Hence we need to trigger a message shift from INBOX to old-folder
+ // after undelete.
+ // We do this by changing the cached folder value to being inbox - hence
+ // the event handler will se the update as the message have been shifted
+ // from INBOX to old-folder. (Errata 5591 clearifies this)
+ msg.type = Mms.MESSAGE_BOX_INBOX;
}
}
updateThreadId(uri, Mms.THREAD_ID, oldThreadId);
} else {
Log.d(TAG, "Message not in deleted folder: handle " + handle
- + " threadId " + threadId);
+ + " threadId " + threadId);
}
res = true;
}
} finally {
- close(c);
+ if (c != null) c.close();
}
-
return res;
}
@@ -989,14 +2153,13 @@
boolean res = false;
Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
Cursor c = mResolver.query(uri, null, null, null, null);
-
try {
if (c != null && c.moveToFirst()) {
/* Move to deleted folder, or delete if already in deleted folder */
int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
if (threadId != DELETED_THREAD_ID) {
- synchronized(mMsgListSms) {
- Msg msg = mMsgListSms.get(handle);
+ synchronized(getMsgListSms()) {
+ Msg msg = getMsgListSms().get(handle);
if(msg != null) { // This will always be the case
msg.threadId = DELETED_THREAD_ID;
}
@@ -1005,8 +2168,8 @@
updateThreadId(uri, Sms.THREAD_ID, DELETED_THREAD_ID);
} else {
/* Delete from observer message list to avoid delete notifications */
- synchronized(mMsgListSms) {
- mMsgListSms.remove(handle);
+ synchronized(getMsgListSms()) {
+ getMsgListSms().remove(handle);
}
/* Delete message */
mResolver.delete(uri, null, null);
@@ -1014,9 +2177,8 @@
res = true;
}
} finally {
- close(c);
+ if (c != null) c.close();
}
-
return res;
}
@@ -1024,7 +2186,6 @@
boolean res = false;
Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
Cursor c = mResolver.query(uri, null, null, null, null);
-
try {
if (c != null && c.moveToFirst()) {
int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
@@ -1033,34 +2194,58 @@
Set<String> recipients = new HashSet<String>();
recipients.addAll(Arrays.asList(address));
Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients);
- synchronized(mMsgListSms) {
- Msg msg = mMsgListSms.get(handle);
- if(msg != null) { // This will always be the case
- msg.threadId = oldThreadId.intValue(); // The threadId is specified as an int, so it is safe to truncate
+ synchronized(getMsgListSms()) {
+ Msg msg = getMsgListSms().get(handle);
+ if(msg != null) {
+ msg.threadId = oldThreadId.intValue();
+ /* This will always be the case
+ * The threadId is specified as an int, so it is safe to truncate
+ * TODO: Test that this will trigger a message-shift from Inbox
+ * to old-folder
+ **/
+ /* Spec. states that undelete shall shift the message to Inbox.
+ * Hence we need to trigger a message shift from INBOX to old-folder
+ * after undelete.
+ * We do this by changing the cached folder value to being inbox - hence
+ * the event handler will se the update as the message have been shifted
+ * from INBOX to old-folder. (Errata 5591 clearifies this)
+ * */
+ msg.type = Sms.MESSAGE_TYPE_INBOX;
}
}
updateThreadId(uri, Sms.THREAD_ID, oldThreadId);
} else {
Log.d(TAG, "Message not in deleted folder: handle " + handle
- + " threadId " + threadId);
+ + " threadId " + threadId);
}
res = true;
}
} finally {
- close(c);
+ if (c != null) c.close();
}
-
return res;
}
+ /**
+ *
+ * @param handle
+ * @param type
+ * @param mCurrentFolder
+ * @param uriStr
+ * @param statusValue
+ * @return true is success
+ */
public boolean setMessageStatusDeleted(long handle, TYPE type,
BluetoothMapFolderElement mCurrentFolder, String uriStr, int statusValue) {
boolean res = false;
if (D) Log.d(TAG, "setMessageStatusDeleted: handle " + handle
- + " type " + type + " value " + statusValue);
+ + " type " + type + " value " + statusValue);
if (type == TYPE.EMAIL) {
res = setEmailMessageStatusDelete(mCurrentFolder, uriStr, handle, statusValue);
+ } else if (type == TYPE.IM) {
+ // TODO: to do when deleting IM message
+ if (D) Log.d(TAG, "setMessageStatusDeleted: IM not handled" );
} else {
if (statusValue == BluetoothMapAppParams.STATUS_VALUE_YES) {
if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
@@ -1076,7 +2261,6 @@
}
}
}
-
return res;
}
@@ -1088,23 +2272,30 @@
* @param statusValue
* @return true at success
*/
- public boolean setMessageStatusRead(long handle, TYPE type, String uriStr, int statusValue) throws RemoteException{
+ public boolean setMessageStatusRead(long handle, TYPE type, String uriStr, int statusValue)
+ throws RemoteException{
int count = 0;
if (D) Log.d(TAG, "setMessageStatusRead: handle " + handle
- + " type " + type + " value " + statusValue);
+ + " type " + type + " value " + statusValue);
- /* Approved MAP spec errata 3445 states that read status initiated */
- /* by the MCE shall change the MSE read status. */
-
+ /* Approved MAP spec errata 3445 states that read status initiated
+ * by the MCE shall change the MSE read status. */
if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
- Uri uri = Sms.Inbox.CONTENT_URI;//ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
+ Uri uri = Sms.Inbox.CONTENT_URI;
ContentValues contentValues = new ContentValues();
contentValues.put(Sms.READ, statusValue);
contentValues.put(Sms.SEEN, statusValue);
String where = Sms._ID+"="+handle;
String values = contentValues.toString();
- if (D) Log.d(TAG, " -> SMS Uri: " + uri.toString() + " Where " + where + " values " + values);
+ if (D) Log.d(TAG, " -> SMS Uri: " + uri.toString() +
+ " Where " + where + " values " + values);
+ synchronized(getMsgListSms()) {
+ Msg msg = getMsgListSms().get(handle);
+ if(msg != null) { // This will always be the case
+ msg.flagRead = statusValue;
+ }
+ }
count = mResolver.update(uri, contentValues, where, null);
if (D) Log.d(TAG, " -> "+count +" rows updated!");
@@ -1113,14 +2304,26 @@
if (D) Log.d(TAG, " -> MMS Uri: " + uri.toString());
ContentValues contentValues = new ContentValues();
contentValues.put(Mms.READ, statusValue);
+ synchronized(getMsgListMms()) {
+ Msg msg = getMsgListMms().get(handle);
+ if(msg != null) { // This will always be the case
+ msg.flagRead = statusValue;
+ }
+ }
count = mResolver.update(uri, contentValues, null, null);
if (D) Log.d(TAG, " -> "+count +" rows updated!");
-
- } if (type == TYPE.EMAIL) {
+ } else if (type == TYPE.EMAIL ||
+ type == TYPE.IM) {
Uri uri = mMessageUri;
ContentValues contentValues = new ContentValues();
contentValues.put(BluetoothMapContract.MessageColumns.FLAG_READ, statusValue);
contentValues.put(BluetoothMapContract.MessageColumns._ID, handle);
+ synchronized(getMsgListMsg()) {
+ Msg msg = getMsgListMsg().get(handle);
+ if(msg != null) { // This will always be the case
+ msg.flagRead = statusValue;
+ }
+ }
count = mProviderClient.update(uri, contentValues, null, null);
}
@@ -1143,7 +2346,7 @@
int statusDelivered; // Set to != 0 if a single part deliver fail is received.
public PushMsgInfo(long id, int transparent,
- int retry, String phone, Uri uri) {
+ int retry, String phone, Uri uri) {
this.id = id;
this.transparent = transparent;
this.retry = retry;
@@ -1158,7 +2361,7 @@
}
private Map<Long, PushMsgInfo> mPushMsgList =
- Collections.synchronizedMap(new HashMap<Long, PushMsgInfo>());
+ Collections.synchronizedMap(new HashMap<Long, PushMsgInfo>());
public long pushMessage(BluetoothMapbMessage msg, BluetoothMapFolderElement folderElement,
BluetoothMapAppParams ap, String emailBaseUri)
@@ -1193,12 +2396,12 @@
ParcelFileDescriptor fdOut = null;
Uri uriInsert = Uri.parse(emailBaseUri + BluetoothMapContract.TABLE_MESSAGE);
if (D) Log.d(TAG, "pushMessage - uriInsert= " + uriInsert.toString() +
- ", intoFolder id=" + folderElement.getEmailFolderId());
+ ", intoFolder id=" + folderElement.getFolderId());
- synchronized(mMsgListEmail) {
+ synchronized(getMsgListMsg()) {
// Now insert the empty message into folder
ContentValues values = new ContentValues();
- folderId = folderElement.getEmailFolderId();
+ folderId = folderElement.getFolderId();
values.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId);
Uri uriNew = mProviderClient.insert(uriInsert, values);
if (D) Log.d(TAG, "pushMessage - uriNew= " + uriNew.toString());
@@ -1222,23 +2425,25 @@
} catch (IOException e) {Log.w(TAG, e);}
try {
if(fdOut != null)
- fdOut.close();
+ fdOut.close();
} catch (IOException e) {Log.w(TAG, e);}
}
/* Extract the data for the inserted message, and store in local mirror, to
* avoid sending a NewMessage Event. */
- Msg newMsg = new Msg(handle, folderId);
+ /*TODO: We need to add the new 1.1 parameter as well:-) e.g. read*/
+ Msg newMsg = new Msg(handle, folderId, 1); // TODO: Create define for read-state
newMsg.transparent = (transparent == 1) ? true : false;
- if ( folderId == folderElement.getEmailFolderByName(
- BluetoothMapContract.FOLDER_NAME_OUTBOX).getEmailFolderId() ) {
+ if ( folderId == folderElement.getFolderByName(
+ BluetoothMapContract.FOLDER_NAME_OUTBOX).getFolderId() ) {
newMsg.localInitiatedSend = true;
}
- mMsgListEmail.put(handle, newMsg);
+ getMsgListMsg().put(handle, newMsg);
}
} else { // type SMS_* of MMS
for (BluetoothMapbMessage.vCard recipient : recipientList) {
- if(recipient.getEnvLevel() == 0) // Only send the message to the top level recipient
+ // Only send the message to the top level recipient
+ if(recipient.getEnvLevel() == 0)
{
/* Only send to first address */
String phone = recipient.getFirstPhoneNumber();
@@ -1252,16 +2457,18 @@
* then convert the MMS to type SMS and then proceed
*/
if (msg.getType().equals(TYPE.MMS) &&
- (((BluetoothMapbMessageMms) msg).getTextOnly() == true)) {
- msgBody = ((BluetoothMapbMessageMms) msg).getMessageAsText();
+ (((BluetoothMapbMessageMime) msg).getTextOnly() == true)) {
+ msgBody = ((BluetoothMapbMessageMime) msg).getMessageAsText();
SmsManager smsMng = SmsManager.getDefault();
ArrayList<String> parts = smsMng.divideMessage(msgBody);
int smsParts = parts.size();
if (smsParts <= CONVERT_MMS_TO_SMS_PART_COUNT ) {
- if (D) Log.d(TAG, "pushMessage - converting MMS to SMS, sms parts=" + smsParts );
+ if (D) Log.d(TAG, "pushMessage - converting MMS to SMS, sms parts="
+ + smsParts );
msg.setType(mSmsType);
} else {
- if (D) Log.d(TAG, "pushMessage - MMS text only but to big to convert to SMS");
+ if (D) Log.d(TAG, "pushMessage - MMS text only but to big to " +
+ "convert to SMS");
msgBody = null;
}
@@ -1269,55 +2476,66 @@
if (msg.getType().equals(TYPE.MMS)) {
/* Send message if folder is outbox else just store in draft*/
- handle = sendMmsMessage(folder, phone, (BluetoothMapbMessageMms)msg);
+ handle = sendMmsMessage(folder, phone, (BluetoothMapbMessageMime)msg);
} else if (msg.getType().equals(TYPE.SMS_GSM) ||
msg.getType().equals(TYPE.SMS_CDMA) ) {
/* Add the message to the database */
if(msgBody == null)
msgBody = ((BluetoothMapbMessageSms) msg).getSmsBody();
- /* We need to lock the SMS list while updating the database, to avoid sending
- * events on MCE initiated operation. */
+ /* We need to lock the SMS list while updating the database,
+ * to avoid sending events on MCE initiated operation. */
Uri contentUri = Uri.parse(Sms.CONTENT_URI+ "/" + folder);
Uri uri;
- synchronized(mMsgListSms) {
+ synchronized(getMsgListSms()) {
uri = Sms.addMessageToUri(mResolver, contentUri, phone, msgBody,
- "", System.currentTimeMillis(), read, deliveryReport);
+ "", System.currentTimeMillis(), read, deliveryReport);
if(V) Log.v(TAG, "Sms.addMessageToUri() returned: " + uri);
if (uri == null) {
- if (D) Log.d(TAG, "pushMessage - failure on add to uri " + contentUri);
+ if (D) Log.d(TAG, "pushMessage - failure on add to uri "
+ + contentUri);
return -1;
}
Cursor c = mResolver.query(uri, SMS_PROJECTION_SHORT, null, null, null);
+
+ /* Extract the data for the inserted message, and store in local mirror,
+ * to avoid sending a NewMessage Event. */
try {
- /* Extract the data for the inserted message, and store in local mirror, to
- * avoid sending a NewMessage Event. */
if (c != null && c.moveToFirst()) {
long id = c.getLong(c.getColumnIndex(Sms._ID));
int type = c.getInt(c.getColumnIndex(Sms.TYPE));
int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
- Msg newMsg = new Msg(id, type, threadId);
- mMsgListSms.put(id, newMsg);
+ int readFlag = c.getInt(c.getColumnIndex(Sms.READ));
+ if(V) Log.v(TAG, "add message with id=" + id +
+ " type=" + type + " threadId=" + threadId +
+ " readFlag=" + readFlag + "to mMsgListSms");
+ Msg newMsg = new Msg(id, type, threadId, readFlag);
+ getMsgListSms().put(id, newMsg);
+ c.close();
} else {
- return -1; // This can only happen, if the message is deleted just as it is added
+ Log.w(TAG,"Message: " + uri + " no longer exist!");
+ /* This can only happen, if the message is deleted
+ * just as it is added */
+ return -1;
}
} finally {
- close(c);
+ if (c != null) c.close();
}
handle = Long.parseLong(uri.getLastPathSegment());
/* Send message if folder is outbox */
- if (folder.equals(BluetoothMapContract.FOLDER_NAME_OUTBOX)) {
+ if (folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)) {
PushMsgInfo msgInfo = new PushMsgInfo(handle, transparent,
- retry, phone, uri);
+ retry, phone, uri);
mPushMsgList.put(handle, msgInfo);
sendMessage(msgInfo, msgBody);
if(V) Log.v(TAG, "sendMessage returned...");
- }
- /* sendMessage causes the message to be deleted and reinserted, hence we need to lock
- * the list while this is happening. */
+ } /* else just added to draft */
+
+ /* sendMessage causes the message to be deleted and reinserted,
+ * hence we need to lock the list while this is happening. */
}
} else {
if (D) Log.d(TAG, "pushMessage - failure on type " );
@@ -1331,7 +2549,7 @@
return handle;
}
- public long sendMmsMessage(String folder, String to_address, BluetoothMapbMessageMms msg) {
+ public long sendMmsMessage(String folder, String to_address, BluetoothMapbMessageMime msg) {
/*
*strategy:
*1) parse message into parts
@@ -1346,8 +2564,10 @@
if (folder != null && (folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)
|| folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT))) {
long handle = pushMmsToFolder(Mms.MESSAGE_BOX_DRAFTS, to_address, msg);
- /* if invalid handle (-1) then just return the handle - else continue sending (if folder is outbox) */
- if (BluetoothMapAppParams.INVALID_VALUE_PARAMETER != handle && folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)) {
+ /* if invalid handle (-1) then just return the handle
+ * - else continue sending (if folder is outbox) */
+ if (BluetoothMapAppParams.INVALID_VALUE_PARAMETER != handle &&
+ folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)) {
moveDraftToOutbox(handle);
Intent sendIntent = new Intent("android.intent.action.MMS_SEND_OUTBOX_MSG");
if (D) Log.d(TAG, "broadcasting intent: "+sendIntent.toString());
@@ -1356,33 +2576,36 @@
return handle;
} else {
/* not allowed to push mms to anything but outbox/draft */
- throw new IllegalArgumentException("Cannot push message to other folders than outbox/draft");
+ throw new IllegalArgumentException("Cannot push message to other " +
+ "folders than outbox/draft");
}
}
private void moveDraftToOutbox(long handle) {
/*Move message by changing the msg_box value in the content provider database */
- if (handle != -1) return;
-
- String whereClause = " _id= " + handle;
- Uri uri = Mms.CONTENT_URI;
- Cursor queryResult = mResolver.query(uri, null, whereClause, null, null);
- try {
- if (queryResult != null && queryResult.moveToFirst()) {
- ContentValues data = new ContentValues();
- /* set folder to be outbox */
- data.put(Mms.MESSAGE_BOX, Mms.MESSAGE_BOX_OUTBOX);
- mResolver.update(uri, data, whereClause, null);
- if (D) Log.d(TAG, "Moved draft MMS to outbox");
- } else {
- if (D) Log.d(TAG, "Could not move draft to outbox ");
+ if (handle != -1) {
+ String whereClause = " _id= " + handle;
+ Uri uri = Mms.CONTENT_URI;
+ Cursor queryResult = mResolver.query(uri, null, whereClause, null, null);
+ try {
+ if (queryResult != null) {
+ if (queryResult.getCount() > 0) {
+ queryResult.moveToFirst();
+ ContentValues data = new ContentValues();
+ /* set folder to be outbox */
+ data.put(Mms.MESSAGE_BOX, Mms.MESSAGE_BOX_OUTBOX);
+ mResolver.update(uri, data, whereClause, null);
+ if (D) Log.d(TAG, "moved draft MMS to outbox");
+ }
+ } else {
+ if (D) Log.d(TAG, "Could not move draft to outbox ");
+ }
+ } finally {
+ if (queryResult != null) queryResult.close();
}
- } finally {
- queryResult.close();
}
}
-
- private long pushMmsToFolder(int folder, String to_address, BluetoothMapbMessageMms msg) {
+ private long pushMmsToFolder(int folder, String to_address, BluetoothMapbMessageMime msg) {
/**
* strategy:
* 1) parse msg into parts + header
@@ -1424,7 +2647,8 @@
values.put(Mms.THREAD_ID, Telephony.Threads.getOrCreateThreadId(mContext, recipients));
Uri uri = Mms.CONTENT_URI;
- synchronized (mMsgListMms) {
+ synchronized (getMsgListMms()) {
+
uri = mResolver.insert(uri, values);
if (uri == null) {
@@ -1447,10 +2671,11 @@
Msg newMsg = new Msg(id, type, threadId);
newMsg.localInitiatedSend = true;
- mMsgListMms.put(id, newMsg);
+ getMsgListMms().put(id, newMsg);
+ c.close();
}
} finally {
- close(c);
+ if (c != null) c.close();
}
} // Done adding changes, unlock access to mMsgListMms to allow sending MMS events again
@@ -1459,15 +2684,18 @@
try {
if(msg.getMimeParts() == null) {
- /* Perhaps this message have been deleted, and no longer have any content, but only headers */
+ /* Perhaps this message have been deleted, and no longer have any content,
+ * but only headers */
Log.w(TAG, "No MMS parts present...");
} else {
- if(V) Log.v(TAG, "Adding " + msg.getMimeParts().size() + " parts to the data base.");
- int count = 0;
+ if(V) Log.v(TAG, "Adding " + msg.getMimeParts().size()
+ + " parts to the data base.");
for(MimePart part : msg.getMimeParts()) {
- ++count;
+ int count = 0;
+ count++;
values.clear();
- if(part.mContentType != null && part.mContentType.toUpperCase().contains("TEXT")) {
+ if(part.mContentType != null &&
+ part.mContentType.toUpperCase().contains("TEXT")) {
values.put(Mms.Part.CONTENT_TYPE, "text/plain");
values.put(Mms.Part.CHARSET, 106);
if(part.mPartName != null) {
@@ -1506,8 +2734,8 @@
uri = mResolver.insert(uri, values);
if(V) Log.v(TAG, "Added TEXT part");
- } else if (part.mContentType != null && part.mContentType.toUpperCase().contains("SMIL")){
-
+ } else if (part.mContentType != null &&
+ part.mContentType.toUpperCase().contains("SMIL")){
values.put(Mms.Part.SEQ, -1);
values.put(Mms.Part.CONTENT_TYPE, "application/smil");
if(part.mContentId != null) {
@@ -1536,7 +2764,8 @@
if (V) Log.v(TAG, "Added OTHER part");
}
if (uri != null){
- if (V) Log.v(TAG, "Added part with content-type: "+ part.mContentType + " to Uri: " + uri.toString());
+ if (V) Log.v(TAG, "Added part with content-type: " + part.mContentType
+ + " to Uri: " + uri.toString());
}
}
}
@@ -1676,13 +2905,13 @@
Log.d(TAG, "sendMessage to " + msgInfo.phone);
smsMng.sendMultipartTextMessage(msgInfo.phone, null, parts, sentIntents,
- deliveryIntents);
+ deliveryIntents);
}
private static final String ACTION_MESSAGE_DELIVERY =
- "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY";
+ "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY";
public static final String ACTION_MESSAGE_SENT =
- "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT";
+ "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT";
public static final String EXTRA_MESSAGE_SENT_HANDLE = "HANDLE";
public static final String EXTRA_MESSAGE_SENT_RESULT = "result";
@@ -1738,10 +2967,13 @@
}
if (action.equals(ACTION_MESSAGE_SENT)) {
- int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT, Activity.RESULT_CANCELED);
+ int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT,
+ Activity.RESULT_CANCELED);
msgInfo.partsSent++;
if(result != Activity.RESULT_OK) {
- // If just one of the parts in the message fails, we need to send the entire message again
+ /* If just one of the parts in the message fails, we need to send the
+ * entire message again
+ */
msgInfo.failedSent = true;
}
if(D) Log.d(TAG, "onReceive: msgInfo.partsSent = " + msgInfo.partsSent
@@ -1766,6 +2998,7 @@
status = message.getStatus();
if(status != 0/*0 is success*/) {
msgInfo.statusDelivered = status;
+ if(D) Log.d(TAG, "msgInfo.statusDelivered = " + status);
}
}
if (msgInfo.partsDelivered == msgInfo.parts) {
@@ -1798,7 +3031,7 @@
}
Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msgInfo.id,
- folderSms[Sms.MESSAGE_TYPE_SENT], null, mSmsType);
+ getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType);
sendEvent(evt);
} else {
@@ -1808,7 +3041,7 @@
msgInfo.partsSent = 0; // Reset counter for the retry
msgInfo.failedSent = false;
Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id,
- folderSms[Sms.MESSAGE_TYPE_OUTBOX], null, mSmsType);
+ getSmsFolderName(Sms.MESSAGE_TYPE_OUTBOX), null, mSmsType);
sendEvent(evt);
} else {
if (msgInfo.transparent == 0) {
@@ -1821,15 +3054,15 @@
}
Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id,
- folderSms[Sms.MESSAGE_TYPE_FAILED], null, mSmsType);
+ getSmsFolderName(Sms.MESSAGE_TYPE_FAILED), null, mSmsType);
sendEvent(evt);
}
}
if (delete == true) {
/* Delete from Observer message list to avoid delete notifications */
- synchronized(mMsgListSms) {
- mMsgListSms.remove(msgInfo.id);
+ synchronized(getMsgListSms()) {
+ getMsgListSms().remove(msgInfo.id);
}
/* Delete from DB */
@@ -1849,7 +3082,8 @@
Uri updateUri = ContentUris.withAppendedId(UPDATE_STATUS_URI, messageId);
- if(D) Log.d(TAG, "actionMessageDelivery: uri=" + messageUri + ", status=" + msgInfo.statusDelivered);
+ if(D) Log.d(TAG, "actionMessageDelivery: uri=" + messageUri + ", status="
+ + msgInfo.statusDelivered);
ContentValues contentValues = new ContentValues(2);
@@ -1860,16 +3094,16 @@
Log.d(TAG, "Can't find message for status update: " + messageUri);
}
} finally {
- cursor.close();
+ if (cursor != null) cursor.close();
}
if (msgInfo.statusDelivered == 0) {
Event evt = new Event(EVENT_TYPE_DELEVERY_SUCCESS, msgInfo.id,
- folderSms[Sms.MESSAGE_TYPE_SENT], null, mSmsType);
+ getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType);
sendEvent(evt);
} else {
- Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id,
- folderSms[Sms.MESSAGE_TYPE_SENT], null, mSmsType);
+ Event evt = new Event(EVENT_TYPE_DELIVERY_FAILURE, msgInfo.id,
+ getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType);
sendEvent(evt);
}
@@ -1901,7 +3135,7 @@
} else {
/*if (retry == 1) {
The retry feature only works while connected, else we fail the send,
- * and move the message to failed, to let the user/app resend manually later.
+ * and move the message to failed, to let the user/app resend manually later.
} else */{
if (transparent == 0) {
if (!Sms.moveMessageToFolder(context, uri,
@@ -1926,56 +3160,65 @@
}
private void registerPhoneServiceStateListener() {
- TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ TelephonyManager tm = (TelephonyManager)mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
tm.listen(mPhoneListener, PhoneStateListener.LISTEN_SERVICE_STATE);
}
private void unRegisterPhoneServiceStateListener() {
- TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ TelephonyManager tm = (TelephonyManager)mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
tm.listen(mPhoneListener, PhoneStateListener.LISTEN_NONE);
}
private void resendPendingMessages() {
/* Send pending messages in outbox */
String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX;
- Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, null);
-
+ Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
+ null);
try {
- while (c!= null && c.moveToNext()) {
- long id = c.getLong(c.getColumnIndex(Sms._ID));
- String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
- PushMsgInfo msgInfo = mPushMsgList.get(id);
- if (msgInfo == null || msgInfo.resend == false || msgInfo.sendInProgress == true) {
- continue;
- }
- msgInfo.sendInProgress = true;
- sendMessage(msgInfo, msgBody);
+ if (c != null && c.moveToFirst()) {
+ do {
+ long id = c.getLong(c.getColumnIndex(Sms._ID));
+ String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
+ PushMsgInfo msgInfo = mPushMsgList.get(id);
+ if (msgInfo == null || msgInfo.resend == false ||
+ msgInfo.sendInProgress == true) {
+ continue;
+ }
+ msgInfo.sendInProgress = true;
+ sendMessage(msgInfo, msgBody);
+ } while (c.moveToNext());
}
} finally {
- close(c);
+ if (c != null) c.close();
}
+
+
}
private void failPendingMessages() {
/* Move pending messages from outbox to failed */
String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX;
- Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, null);
- if (c == null) return;
-
+ Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
+ null);
try {
- while (c!= null && c.moveToNext()) {
- long id = c.getLong(c.getColumnIndex(Sms._ID));
- String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
- PushMsgInfo msgInfo = mPushMsgList.get(id);
- if (msgInfo == null || msgInfo.resend == false) {
- continue;
- }
- Sms.moveMessageToFolder(mContext, msgInfo.uri,
- Sms.MESSAGE_TYPE_FAILED, 0);
+ if (c != null && c.moveToFirst()) {
+ do {
+ long id = c.getLong(c.getColumnIndex(Sms._ID));
+ String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
+ PushMsgInfo msgInfo = mPushMsgList.get(id);
+ if (msgInfo == null || msgInfo.resend == false) {
+ continue;
+ }
+ Sms.moveMessageToFolder(mContext, msgInfo.uri,
+ Sms.MESSAGE_TYPE_FAILED, 0);
+ } while (c.moveToNext());
}
} finally {
- close(c);
+ if (c != null) c.close();
}
+
}
private void removeDeletedMessages() {
diff --git a/src/com/android/bluetooth/map/BluetoothMapConvoContactElement.java b/src/com/android/bluetooth/map/BluetoothMapConvoContactElement.java
new file mode 100644
index 0000000..8ee9728
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapConvoContactElement.java
@@ -0,0 +1,348 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.android.bluetooth.map;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+
+import android.util.Log;
+
+import com.android.bluetooth.SignedLongLong;
+
+public class BluetoothMapConvoContactElement
+ implements Comparable<BluetoothMapConvoContactElement> {
+
+ public static final long CONTACT_ID_TYPE_SMS_MMS = 1;
+ public static final long CONTACT_ID_TYPE_EMAIL = 2;
+ public static final long CONTACT_ID_TYPE_IM = 3;
+
+ private static final String XML_ATT_PRIORITY = "priority";
+ private static final String XML_ATT_PRESENCE_STATUS = "presence_status";
+ private static final String XML_ATT_PRESENCE_AVAILABILITY = "presence_availability";
+ private static final String XML_ATT_X_BT_UID = "x_bt_uid";
+ private static final String XML_ATT_LAST_ACTIVITY = "last_activity";
+ private static final String XML_ATT_CHAT_STATE = "chat_state";
+ private static final String XML_ATT_NAME = "name";
+ private static final String XML_ATT_DISPLAY_NAME = "display_name";
+ private static final String XML_ATT_UCI = "x_bt_uci";
+ protected static final String XML_TAG_CONVOCONTACT = "convocontact";
+ private static final String TAG = "BluetoothMapConvoContactElement";
+ private static final boolean D = false;
+ private static final boolean V = false;
+
+ private String mUci = null;
+ private String mName = null;
+ private String mDisplayName = null;
+ private String mPresenceStatus = null;
+ private int mPresenceAvailability = -1;
+ private int mPriority = -1;
+ private long mLastActivity = -1;
+ private SignedLongLong mBtUid = null;
+ private int mChatState = -1;
+
+ public static BluetoothMapConvoContactElement createFromMapContact(MapContact contact,
+ String address) {
+ BluetoothMapConvoContactElement newElement = new BluetoothMapConvoContactElement();
+ newElement.mUci = address;
+ // TODO: For now we use the ID as BT-UID
+ newElement.mBtUid = new SignedLongLong(contact.getId(),0);
+ newElement.mDisplayName = contact.getName();
+ return newElement;
+ }
+
+ public BluetoothMapConvoContactElement(String uci, String name, String displayName,
+ String presenceStatus, int presenceAvailability, long lastActivity, int chatState,
+ int priority, String btUid) {
+ this.mUci = uci;
+ this.mName = name;
+ this.mDisplayName = displayName;
+ this.mPresenceStatus = presenceStatus;
+ this.mPresenceAvailability = presenceAvailability;
+ this.mLastActivity = lastActivity;
+ this.mChatState = chatState;
+ this.mPresenceStatus = presenceStatus;
+ this.mPriority = priority;
+ if(btUid != null) {
+ try {
+ this.mBtUid = SignedLongLong.fromString(btUid);
+ } catch (UnsupportedEncodingException e) {
+ Log.w(TAG,e);
+ }
+ }
+ }
+
+ public BluetoothMapConvoContactElement() {
+ // TODO Auto-generated constructor stub
+ }
+
+ public String getPresenceStatus() {
+ return mPresenceStatus;
+ }
+
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.mDisplayName = displayName;
+ }
+
+ public void setPresenceStatus(String presenceStatus) {
+ this.mPresenceStatus = presenceStatus;
+ }
+
+ public int getPresenceAvailability() {
+ return mPresenceAvailability;
+ }
+
+ public void setPresenceAvailability(int presenceAvailability) {
+ this.mPresenceAvailability = presenceAvailability;
+ }
+
+ public int getPriority() {
+ return mPriority;
+ }
+
+ public void setPriority(int priority) {
+ this.mPriority = priority;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void setName(String name) {
+ this.mName = name;
+ }
+
+ public String getBtUid() {
+ return mBtUid.toHexString();
+ }
+
+ public void setBtUid(SignedLongLong btUid) {
+ this.mBtUid = btUid;
+ }
+
+ public int getChatState() {
+ return mChatState;
+ }
+
+ public void setChatState(int chatState) {
+ this.mChatState = chatState;
+ }
+
+ public void setChatState(String chatState) {
+ this.mChatState = Integer.valueOf(chatState);
+ }
+
+
+ public String getLastActivityString() {
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+ Date date = new Date(mLastActivity);
+ return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+ }
+
+ public void setLastActivity(long dateTime) {
+ this.mLastActivity = dateTime;
+ }
+
+ public void setLastActivity(String lastActivity) throws ParseException {
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+ Date date = format.parse(lastActivity);
+ this.mLastActivity = date.getTime();
+ }
+
+ public void setContactId(String uci) {
+ this.mUci = uci;
+ }
+
+ public String getContactId(){
+ return mUci;
+ }
+
+ public int compareTo(BluetoothMapConvoContactElement e) {
+ if (this.mLastActivity < e.mLastActivity) {
+ return 1;
+ } else if (this.mLastActivity > e.mLastActivity) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ /* Encode the MapConvoContactElement into the StringBuilder reference.
+ * Here we have taken the choice not to report empty attributes, to reduce the
+ * amount of data to be transfered over BT. */
+ public void encode(XmlSerializer xmlConvoElement)
+ throws IllegalArgumentException, IllegalStateException, IOException
+ {
+ // construct the XML tag for a single contact in the convolisting element.
+ xmlConvoElement.startTag(null, XML_TAG_CONVOCONTACT);
+ if(mUci != null) {
+ xmlConvoElement.attribute(null, XML_ATT_UCI, mUci);
+ }
+ if(mDisplayName != null) {
+ xmlConvoElement.attribute(null, XML_ATT_DISPLAY_NAME,
+ BluetoothMapUtils.stripInvalidChars(mDisplayName));
+ }
+ if(mName != null) {
+ xmlConvoElement.attribute(null, XML_ATT_NAME,
+ BluetoothMapUtils.stripInvalidChars(mName));
+ }
+ if(mChatState != -1) {
+ xmlConvoElement.attribute(null, XML_ATT_CHAT_STATE, String.valueOf(mChatState));
+ }
+ if(mLastActivity != -1) {
+ xmlConvoElement.attribute(null, XML_ATT_LAST_ACTIVITY,
+ this.getLastActivityString());
+ }
+ if(mBtUid != null) {
+ xmlConvoElement.attribute(null, XML_ATT_X_BT_UID, mBtUid.toHexString());
+ }
+ if(mPresenceAvailability != -1) {
+ xmlConvoElement.attribute(null, XML_ATT_PRESENCE_AVAILABILITY,
+ String.valueOf(mPresenceAvailability));
+ }
+ if(mPresenceStatus != null) {
+ xmlConvoElement.attribute(null, XML_ATT_PRESENCE_STATUS, mPresenceStatus);
+ }
+ if(mPriority != -1) {
+ xmlConvoElement.attribute(null, XML_ATT_PRIORITY, String.valueOf(mPriority));
+ }
+
+ xmlConvoElement.endTag(null, XML_TAG_CONVOCONTACT);
+ }
+
+
+ /**
+ * Call this function to create a BluetoothMapConvoContactElement. Will consume the end-tag.
+ * @param parser must point into XML_TAG_CONVERSATION tag, hence attributes can be read.
+ * @return
+ * @throws IOException
+ * @throws XmlPullParserException
+ */
+ public static BluetoothMapConvoContactElement createFromXml(XmlPullParser parser)
+ throws ParseException, XmlPullParserException, IOException {
+ int count = parser.getAttributeCount();
+ BluetoothMapConvoContactElement newElement;
+ if(count<1) {
+ throw new IllegalArgumentException(XML_TAG_CONVOCONTACT +
+ " is not decorated with attributes");
+ }
+ newElement = new BluetoothMapConvoContactElement();
+ for (int i = 0; i<count; i++) {
+ String attributeName = parser.getAttributeName(i).trim();
+ String attributeValue = parser.getAttributeValue(i);
+ if(attributeName.equalsIgnoreCase(XML_ATT_UCI)) {
+ newElement.mUci = attributeValue;
+ } else if(attributeName.equalsIgnoreCase(XML_ATT_NAME)) {
+ newElement.mName = attributeValue;
+ } else if(attributeName.equalsIgnoreCase(XML_ATT_DISPLAY_NAME)) {
+ newElement.mDisplayName = attributeValue;
+ } else if(attributeName.equalsIgnoreCase(XML_ATT_CHAT_STATE)) {
+ newElement.setChatState(attributeValue);
+ } else if(attributeName.equalsIgnoreCase(XML_ATT_LAST_ACTIVITY)) {
+ newElement.setLastActivity(attributeValue);
+ } else if(attributeName.equalsIgnoreCase(XML_ATT_X_BT_UID)) {
+ newElement.setBtUid(SignedLongLong.fromString(attributeValue));
+ } else if(attributeName.equalsIgnoreCase(XML_ATT_PRESENCE_AVAILABILITY)) {
+ newElement.mPresenceAvailability = Integer.parseInt(attributeValue);
+ } else if(attributeName.equalsIgnoreCase(XML_ATT_PRESENCE_STATUS)) {
+ newElement.setPresenceStatus(attributeValue);
+ } else if(attributeName.equalsIgnoreCase(XML_ATT_PRIORITY)) {
+ newElement.setPriority(Integer.parseInt(attributeValue));
+ } else {
+ if(D) Log.i(TAG,"Unknown XML attribute: " + parser.getAttributeName(i));
+ }
+ }
+ parser.nextTag(); // Consume the end-tag
+ return newElement;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ BluetoothMapConvoContactElement other = (BluetoothMapConvoContactElement) obj;
+/* As we use equals only for test, we don't compare auto assigned values
+ * if (mBtUid == null) {
+ if (other.mBtUid != null) {
+ return false;
+ }
+ } else if (!mBtUid.equals(other.mBtUid)) {
+ return false;
+ }*/
+ if (mChatState != other.mChatState) {
+ return false;
+ }
+ if (mDisplayName == null) {
+ if (other.mDisplayName != null) {
+ return false;
+ }
+ } else if (!mDisplayName.equals(other.mDisplayName)) {
+ return false;
+ }
+/* As we use equals only for test, we don't compare auto assigned values
+ * if (mId == null) {
+ if (other.mId != null) {
+ return false;
+ }
+ } else if (!mId.equals(other.mId)) {
+ return false;
+ }*/
+ if (mLastActivity != other.mLastActivity) {
+ return false;
+ }
+ if (mName == null) {
+ if (other.mName != null) {
+ return false;
+ }
+ } else if (!mName.equals(other.mName)) {
+ return false;
+ }
+ if (mPresenceAvailability != other.mPresenceAvailability) {
+ return false;
+ }
+ if (mPresenceStatus == null) {
+ if (other.mPresenceStatus != null) {
+ return false;
+ }
+ } else if (!mPresenceStatus.equals(other.mPresenceStatus)) {
+ return false;
+ }
+ if (mPriority != other.mPriority) {
+ return false;
+ }
+ return true;
+ }
+
+}
+
+
diff --git a/src/com/android/bluetooth/map/BluetoothMapConvoListing.java b/src/com/android/bluetooth/map/BluetoothMapConvoListing.java
new file mode 100644
index 0000000..cda95a6
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapConvoListing.java
@@ -0,0 +1,224 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.android.bluetooth.map;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+public class BluetoothMapConvoListing {
+ private boolean hasUnread = false;
+ private static final String TAG = "BluetoothMapConvoListing";
+ private static final boolean D = BluetoothMapService.DEBUG;
+ private static final String XML_TAG = "MAP-convo-listing";
+
+ private List<BluetoothMapConvoListingElement> mList;
+
+ public BluetoothMapConvoListing(){
+ mList = new ArrayList<BluetoothMapConvoListingElement>();
+ }
+ public void add(BluetoothMapConvoListingElement element) {
+ mList.add(element);
+ /* update info regarding whether the list contains unread conversations */
+ if (element.getReadBool())
+ {
+ hasUnread = true;
+ }
+ }
+
+ /**
+ * Used to fetch the number of BluetoothMapConvoListingElement elements in the list.
+ * @return the number of elements in the list.
+ */
+ public int getCount() {
+ if(mList != null)
+ {
+ return mList.size();
+ }
+ return 0;
+ }
+
+ /**
+ * does the list contain any unread messages
+ * @return true if unread messages have been added to the list, else false
+ */
+ public boolean hasUnread()
+ {
+ return hasUnread;
+ }
+
+
+ /**
+ * returns the entire list as a list
+ * @return list
+ */
+ public List<BluetoothMapConvoListingElement> getList(){
+ return mList;
+ }
+
+ /**
+ * Encode the list of BluetoothMapMessageListingElement(s) into a UTF-8
+ * formatted XML-string in a trimmed byte array
+ *
+ * @return a reference to the encoded byte array.
+ * @throws UnsupportedEncodingException
+ * if UTF-8 encoding is unsupported on the platform.
+ */
+ public byte[] encode() throws UnsupportedEncodingException {
+ StringWriter sw = new StringWriter();
+ XmlSerializer xmlConvoElement = new FastXmlSerializer();
+ try {
+ xmlConvoElement.setOutput(sw);
+ xmlConvoElement.startDocument("UTF-8", true);
+ xmlConvoElement.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
+ true);
+ xmlConvoElement.startTag(null, XML_TAG);
+ xmlConvoElement.attribute(null, "version", "1.0");
+ // Do the XML encoding of list
+ for (BluetoothMapConvoListingElement element : mList) {
+ element.encode(xmlConvoElement); // Append the list element
+ }
+ xmlConvoElement.endTag(null, XML_TAG);
+ xmlConvoElement.endDocument();
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, e);
+ } catch (IllegalStateException e) {
+ Log.w(TAG, e);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+ return sw.toString().getBytes("UTF-8");
+ }
+
+ public void sort() {
+ Collections.sort(mList);
+ }
+
+ public void segment(int count, int offset) {
+ count = Math.min(count, mList.size() - offset);
+ if (count > 0) {
+ mList = mList.subList(offset, offset + count);
+ if(mList == null) {
+ mList = new ArrayList<BluetoothMapConvoListingElement>(); // Return an empty list
+ }
+ } else {
+ if(offset > mList.size()) {
+ mList = new ArrayList<BluetoothMapConvoListingElement>();
+ Log.d(TAG, "offset greater than list size. Returning empty list");
+ } else {
+ mList = mList.subList(offset, mList.size());
+ }
+ }
+ }
+
+ public void appendFromXml(InputStream xmlDocument)
+ throws XmlPullParserException, IOException, ParseException {
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ int type;
+ parser.setInput(xmlDocument, "UTF-8");
+
+ // First find the folder-listing
+ while((type=parser.next()) != XmlPullParser.END_TAG
+ && type != XmlPullParser.END_DOCUMENT ) {
+ // Skip until we get a start tag
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ // Skip until we get a folder-listing tag
+ String name = parser.getName();
+ if(!name.equalsIgnoreCase(XML_TAG)) {
+ if(D) Log.i(TAG,"Unknown XML tag: " + name);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ readConversations(parser);
+ }
+ } finally {
+ xmlDocument.close();
+ }
+ }
+
+ /**
+ * Parses folder elements, and add to mSubFolders.
+ * @param parser the Xml Parser currently pointing to an folder-listing tag.
+ * @throws XmlPullParserException
+ * @throws IOException
+ * @throws
+ */
+ private void readConversations(XmlPullParser parser)
+ throws XmlPullParserException, IOException, ParseException {
+ int type;
+ if(D) Log.i(TAG,"readConversations(): ");
+ while((type=parser.next()) != XmlPullParser.END_TAG
+ && type != XmlPullParser.END_DOCUMENT ) {
+ // Skip until we get a start tag
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ // Skip until we get a folder-listing tag
+ String name = parser.getName();
+ if(name.trim().equalsIgnoreCase(BluetoothMapConvoListingElement.XML_TAG_CONVERSATION)
+ == false) {
+ if(D) Log.i(TAG,"Unknown XML tag: " + name);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ // Add a single conversation
+ add(BluetoothMapConvoListingElement.createFromXml(parser));
+ }
+ }
+
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ BluetoothMapConvoListing other = (BluetoothMapConvoListing) obj;
+ if (hasUnread != other.hasUnread) {
+ return false;
+ }
+ if (mList == null) {
+ if (other.mList != null) {
+ return false;
+ }
+ } else if (!mList.equals(other.mList)) {
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java b/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java
new file mode 100644
index 0000000..4947aa4
--- /dev/null
+++ b/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java
@@ -0,0 +1,390 @@
+/*
+* Copyright (C) 2013 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.android.bluetooth.map;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.util.Log;
+
+import com.android.bluetooth.SignedLongLong;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.internal.util.XmlUtils;
+
+public class BluetoothMapConvoListingElement
+ implements Comparable<BluetoothMapConvoListingElement> {
+
+ public static final String XML_TAG_CONVERSATION = "conversation";
+ private static final String XML_ATT_LAST_ACTIVITY = "last_activity";
+ private static final String XML_ATT_NAME = "name";
+ private static final String XML_ATT_ID = "id";
+ private static final String XML_ATT_READ = "readstatus";
+ private static final String XML_ATT_VERSION_COUNTER = "version_counter";
+ private static final String XML_ATT_SUMMARY = "summary";
+ private static final String TAG = "BluetoothMapConvoListingElement";
+ private static final boolean D = BluetoothMapService.DEBUG;
+ private static final boolean V = BluetoothMapService.VERBOSE;
+
+ private SignedLongLong mId = null;
+ private String mName = ""; //title of the conversation #REQUIRED, but allowed empty
+ private long mLastActivity = -1;
+ private boolean mRead = false;
+ private boolean mReportRead = false; // TODO: Is this needed? - false means UNKNOWN
+ private List<BluetoothMapConvoContactElement> mContacts;
+ private long mVersionCounter = -1;
+ private int mCursorIndex = 0;
+ private TYPE mType = null;
+ private String mSummary = null;
+
+ // Used only to keep track of changes to convoListVersionCounter;
+ private String mSmsMmsContacts = null;
+
+ public int getCursorIndex() {
+ return mCursorIndex;
+ }
+
+ public void setCursorIndex(int cursorIndex) {
+ this.mCursorIndex = cursorIndex;
+ if(D) Log.d(TAG, "setCursorIndex: " + cursorIndex);
+ }
+
+ public long getVersionCounter(){
+ return mVersionCounter;
+ }
+
+ public void setVersionCounter(long vcount){
+ if(D) Log.d(TAG, "setVersionCounter: " + vcount);
+ this.mVersionCounter = vcount;
+ }
+
+ public void incrementVersionCounter() {
+ mVersionCounter++;
+ }
+
+ private void setVersionCounter(String vcount){
+ if(D) Log.d(TAG, "setVersionCounter: " + vcount);
+ try {
+ this.mVersionCounter = Long.parseLong(vcount);
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "unable to parse XML versionCounter:" + vcount);
+ mVersionCounter = -1;
+ }
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void setName(String name) {
+ if(D) Log.d(TAG, "setName: " + name);
+ this.mName = name;
+ }
+
+ public TYPE getType() {
+ return mType;
+ }
+
+ public void setType(TYPE type) {
+ this.mType = type;
+ }
+
+ public List<BluetoothMapConvoContactElement> getContacts() {
+ return mContacts;
+ }
+
+ public void setContacts(List<BluetoothMapConvoContactElement> contacts) {
+ this.mContacts = contacts;
+ }
+
+ public void addContact(BluetoothMapConvoContactElement contact){
+ if(mContacts == null)
+ mContacts = new ArrayList<BluetoothMapConvoContactElement>();
+ mContacts.add(contact);
+ }
+
+ public void removeContact(BluetoothMapConvoContactElement contact){
+ mContacts.remove(contact);
+ }
+
+ public void removeContact(int index){
+ mContacts.remove(index);
+ }
+
+
+ public long getLastActivity() {
+ return mLastActivity;
+ }
+
+ public String getLastActivityString() {
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+ Date date = new Date(mLastActivity);
+ return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+ }
+
+ public void setLastActivity(long last) {
+ if(D) Log.d(TAG, "setLastActivity: " + last);
+ this.mLastActivity = last;
+ }
+
+ public void setLastActivity(String lastActivity)throws ParseException {
+ // TODO: Encode with time-zone if MCE requests it
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+ Date date = format.parse(lastActivity);
+ this.mLastActivity = date.getTime();
+ }
+
+ public String getRead() {
+ if(mReportRead == false) {
+ return "UNKNOWN";
+ }
+ return (mRead?"READ":"UNREAD");
+ }
+
+ public boolean getReadBool() {
+ return mRead;
+ }
+
+ public void setRead(boolean read, boolean reportRead) {
+ this.mRead = read;
+ if(D) Log.d(TAG, "setRead: " + read);
+ this.mReportRead = reportRead;
+ }
+
+ private void setRead(String value) {
+ if(value.trim().equalsIgnoreCase("yes")) {
+ mRead = true;
+ } else {
+ mRead = false;
+ }
+ mReportRead = true;
+ }
+
+ /**
+ * Set the conversation ID
+ * @param type 0 if the thread ID is valid across all message types in the instance - else
+ * use one of the CONVO_ID_xxx types.
+ * @param threadId the conversation ID
+ */
+ public void setConvoId(long type, long threadId) {
+ this.mId = new SignedLongLong(threadId,type);
+ if(D) Log.d(TAG, "setConvoId: " + threadId + " type:" + type);
+ }
+
+ public String getConvoId(){
+ return mId.toHexString();
+ }
+
+ public long getCpConvoId() {
+ return mId.getLeastSignificantBits();
+ }
+
+ public void setSummary(String summary) {
+ mSummary = summary;
+ }
+
+ public String getFullSummary() {
+ return mSummary;
+ }
+
+ /* Get a valid UTF-8 string of maximum 256 bytes */
+ private String getSummary() {
+ if(mSummary != null) {
+ try {
+ return new String(BluetoothMapUtils.truncateUtf8StringToBytearray(mSummary, 256),
+ "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // This cannot happen on an Android platform - UTF-8 is mandatory
+ Log.e(TAG,"Missing UTF-8 support on platform", e);
+ }
+ }
+ return null;
+ }
+
+ public String getSmsMmsContacts() {
+ return mSmsMmsContacts;
+ }
+
+ public void setSmsMmsContacts(String smsMmsContacts) {
+ mSmsMmsContacts = smsMmsContacts;
+ }
+
+ public int compareTo(BluetoothMapConvoListingElement e) {
+ if (this.mLastActivity < e.mLastActivity) {
+ return 1;
+ } else if (this.mLastActivity > e.mLastActivity) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ /* Encode the MapMessageListingElement into the StringBuilder reference.
+ * Here we have taken the choice not to report empty attributes, to reduce the
+ * amount of data to be transfered over BT. */
+ public void encode(XmlSerializer xmlConvoElement)
+ throws IllegalArgumentException, IllegalStateException, IOException
+ {
+
+ // contruct the XML tag for a single conversation in the convolisting
+ xmlConvoElement.startTag(null, XML_TAG_CONVERSATION);
+ xmlConvoElement.attribute(null, XML_ATT_ID, mId.toHexString());
+ if(mName != null) {
+ xmlConvoElement.attribute(null, XML_ATT_NAME,
+ BluetoothMapUtils.stripInvalidChars(mName));
+ }
+ if(mLastActivity != -1) {
+ xmlConvoElement.attribute(null, XML_ATT_LAST_ACTIVITY,
+ getLastActivityString());
+ }
+ // Even though this is implied, the value "UNKNOWN" kind of indicated it is required.
+ if(mReportRead == true) {
+ xmlConvoElement.attribute(null, XML_ATT_READ, getRead());
+ }
+ if(mVersionCounter != -1) {
+ xmlConvoElement.attribute(null, XML_ATT_VERSION_COUNTER,
+ Long.toString(getVersionCounter()));
+ }
+ if(mSummary != null) {
+ xmlConvoElement.attribute(null, XML_ATT_SUMMARY, getSummary());
+ }
+ if(mContacts != null){
+ for(BluetoothMapConvoContactElement contact:mContacts){
+ contact.encode(xmlConvoElement);
+ }
+ }
+ xmlConvoElement.endTag(null, XML_TAG_CONVERSATION);
+
+ }
+
+ /**
+ * Consumes a conversation tag. It is expected that the parser is beyond the start-tag event,
+ * with the name "conversation".
+ * @param parser
+ * @return
+ * @throws XmlPullParserException
+ * @throws IOException
+ */
+ public static BluetoothMapConvoListingElement createFromXml(XmlPullParser parser)
+ throws XmlPullParserException, IOException, ParseException {
+ BluetoothMapConvoListingElement newElement = new BluetoothMapConvoListingElement();
+ int count = parser.getAttributeCount();
+ int type;
+ for (int i = 0; i<count; i++) {
+ String attributeName = parser.getAttributeName(i).trim();
+ String attributeValue = parser.getAttributeValue(i);
+ if(attributeName.equalsIgnoreCase(XML_ATT_ID)) {
+ newElement.mId = SignedLongLong.fromString(attributeValue);
+ } else if(attributeName.equalsIgnoreCase(XML_ATT_NAME)) {
+ newElement.mName = attributeValue;
+ } else if(attributeName.equalsIgnoreCase(XML_ATT_LAST_ACTIVITY)) {
+ newElement.setLastActivity(attributeValue);
+ } else if(attributeName.equalsIgnoreCase(XML_ATT_READ)) {
+ newElement.setRead(attributeValue);
+ } else if(attributeName.equalsIgnoreCase(XML_ATT_VERSION_COUNTER)) {
+ newElement.setVersionCounter(attributeValue);
+ } else if(attributeName.equalsIgnoreCase(XML_ATT_SUMMARY)) {
+ newElement.setSummary(attributeValue);
+ } else {
+ if(D) Log.i(TAG,"Unknown XML attribute: " + parser.getAttributeName(i));
+ }
+ }
+
+ // Now determine if we get an end-tag, or a new start tag for contacts
+ while((type=parser.next()) != XmlPullParser.END_TAG
+ && type != XmlPullParser.END_DOCUMENT ) {
+ // Skip until we get a start tag
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ // Skip until we get a convocontact tag
+ String name = parser.getName().trim();
+ if(name.equalsIgnoreCase(BluetoothMapConvoContactElement.XML_TAG_CONVOCONTACT)){
+ newElement.addContact(BluetoothMapConvoContactElement.createFromXml(parser));
+ } else {
+ if(D) Log.i(TAG,"Unknown XML tag: " + name);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ }
+ // As we have extracted all attributes, we should expect an end-tag
+ // parser.nextTag(); // consume the end-tag
+ // TODO: Is this needed? - we should already be at end-tag, as this is the top condition
+
+ return newElement;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ BluetoothMapConvoListingElement other = (BluetoothMapConvoListingElement) obj;
+ if (mContacts == null) {
+ if (other.mContacts != null) {
+ return false;
+ }
+ } else if (!mContacts.equals(other.mContacts)) {
+ return false;
+ }
+ /* As we use equals only for test, we don't compare auto assigned values
+ * if (mId == null) {
+ if (other.mId != null) {
+ return false;
+ }
+ } else if (!mId.equals(other.mId)) {
+ return false;
+ } */
+
+ if (mLastActivity != other.mLastActivity) {
+ return false;
+ }
+ if (mName == null) {
+ if (other.mName != null) {
+ return false;
+ }
+ } else if (!mName.equals(other.mName)) {
+ return false;
+ }
+ if (mRead != other.mRead) {
+ return false;
+ }
+ return true;
+ }
+
+/* @Override
+ public boolean equals(Object o) {
+
+ return true;
+ };
+ */
+
+}
+
+
diff --git a/src/com/android/bluetooth/map/BluetoothMapEmailAppObserver.java b/src/com/android/bluetooth/map/BluetoothMapEmailAppObserver.java
deleted file mode 100644
index 38d0444..0000000
--- a/src/com/android/bluetooth/map/BluetoothMapEmailAppObserver.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
-* Copyright (C) 2014 Samsung System LSI
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-package com.android.bluetooth.map;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.Set;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import com.android.bluetooth.mapapi.BluetoothMapContract;
-import android.util.Log;
-
-/**
- * Class to construct content observers for for email applications on the system.
- *
- *
- */
-
-public class BluetoothMapEmailAppObserver{
-
- private static final String TAG = "BluetoothMapEmailAppObserver";
-
- private static final boolean D = BluetoothMapService.DEBUG;
- private static final boolean V = BluetoothMapService.VERBOSE;
- /* */
- private LinkedHashMap<BluetoothMapEmailSettingsItem, ArrayList<BluetoothMapEmailSettingsItem>> mFullList;
- private LinkedHashMap<String,ContentObserver> mObserverMap = new LinkedHashMap<String,ContentObserver>();
- private ContentResolver mResolver;
- private Context mContext;
- private BroadcastReceiver mReceiver;
- private PackageManager mPackageManager = null;
- BluetoothMapEmailSettingsLoader mLoader;
- BluetoothMapService mMapService = null;
-
- public BluetoothMapEmailAppObserver(final Context context, BluetoothMapService mapService) {
- mContext = context;
- mMapService = mapService;
- mResolver = context.getContentResolver();
- mLoader = new BluetoothMapEmailSettingsLoader(mContext);
- mFullList = mLoader.parsePackages(false); /* Get the current list of apps */
- createReceiver();
- initObservers();
- }
-
-
- private BluetoothMapEmailSettingsItem getApp(String packageName) {
- if(V) Log.d(TAG, "getApp(): Looking for " + packageName);
- for(BluetoothMapEmailSettingsItem app:mFullList.keySet()){
- if(V) Log.d(TAG, " Comparing: " + app.getPackageName());
- if(app.getPackageName().equals(packageName)) {
- if(V) Log.d(TAG, " found " + app.mBase_uri_no_account);
- return app;
- }
- }
- if(V) Log.d(TAG, " NOT FOUND!");
- return null;
- }
-
- private void handleAccountChanges(String packageNameWithProvider) {
-
- if(D)Log.d(TAG,"handleAccountChanges (packageNameWithProvider: "+packageNameWithProvider+"\n");
- String packageName = packageNameWithProvider.replaceFirst("\\.[^\\.]+$", "");
- BluetoothMapEmailSettingsItem app = getApp(packageName);
- if(app != null) {
- ArrayList<BluetoothMapEmailSettingsItem> newAccountList = mLoader.parseAccounts(app);
- ArrayList<BluetoothMapEmailSettingsItem> oldAccountList = mFullList.get(app);
- ArrayList<BluetoothMapEmailSettingsItem> addedAccountList =
- (ArrayList<BluetoothMapEmailSettingsItem>)newAccountList.clone();
- ArrayList<BluetoothMapEmailSettingsItem> removedAccountList = mFullList.get(app); // Same as oldAccountList.clone
-
- mFullList.put(app, newAccountList);
- for(BluetoothMapEmailSettingsItem newAcc: newAccountList){
- for(BluetoothMapEmailSettingsItem oldAcc: oldAccountList){
- if(newAcc.getId() == oldAcc.getId()){
- // For each match remove from both removed and added lists
- removedAccountList.remove(oldAcc);
- addedAccountList.remove(newAcc);
- if(!newAcc.getName().equals(oldAcc.getName()) && newAcc.mIsChecked){
- // Name Changed and the acc is visible - Change Name in SDP record
- mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED);
- if(V)Log.d(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED");
- }
- if(newAcc.mIsChecked != oldAcc.mIsChecked) {
- // Visibility changed
- if(newAcc.mIsChecked){
- // account added - create SDP record
- mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
- if(V)Log.d(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_ADDED isChecked changed");
- } else {
- // account removed - remove SDP record
- mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
- if(V)Log.d(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED isChecked changed");
- }
- }
- break;
- }
- }
- }
- // Notify on any removed accounts
- for(BluetoothMapEmailSettingsItem removedAcc: removedAccountList){
- mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
- if(V)Log.d(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " + removedAcc);
- }
- // Notify on any new accounts
- for(BluetoothMapEmailSettingsItem addedAcc: addedAccountList){
- mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
- if(V)Log.d(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " + addedAcc);
- }
-
- } else {
- Log.e(TAG, "Received change notification on package not registered for notifications!");
-
- }
- }
-
- /**
- * Adds a new content observer to the list of content observers.
- * The key for the observer is the uri as string
- * @param uri uri for the package that supports MAP email
- */
-
- public void registerObserver(BluetoothMapEmailSettingsItem app) {
- Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
- if (V) Log.d(TAG, "registerObserver for URI "+uri.toString()+"\n");
- ContentObserver observer = new ContentObserver(new Handler()) {
- @Override
- public void onChange(boolean selfChange) {
- onChange(selfChange, null);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId()
- + " Uri: " + uri + " selfchange: " + selfChange);
- if(uri != null) {
- handleAccountChanges(uri.getHost());
- } else {
- Log.e(TAG, "Unable to handle change as the URI is NULL!");
- }
-
- }
- };
- mObserverMap.put(uri.toString(), observer);
- mResolver.registerContentObserver(uri, true, observer);
- }
-
- public void unregisterObserver(BluetoothMapEmailSettingsItem app) {
- Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
- if (V) Log.d(TAG, "unregisterObserver("+uri.toString()+")\n");
- mResolver.unregisterContentObserver(mObserverMap.get(uri.toString()));
- mObserverMap.remove(uri.toString());
- }
-
- private void initObservers(){
- if(D)Log.d(TAG,"initObservers()");
- for(BluetoothMapEmailSettingsItem app: mFullList.keySet()){
- registerObserver(app);
- }
- }
-
- private void deinitObservers(){
- if(D)Log.d(TAG,"deinitObservers()");
- for(BluetoothMapEmailSettingsItem app: mFullList.keySet()){
- unregisterObserver(app);
- }
- }
-
- private void createReceiver(){
- if(D)Log.d(TAG,"createReceiver()\n");
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- intentFilter.addDataScheme("package");
- mReceiver= new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if(D)Log.d(TAG,"onReceive\n");
- String action = intent.getAction();
- if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
- Uri data = intent.getData();
- String packageName = data.getEncodedSchemeSpecificPart();
- if(D)Log.d(TAG,"The installed package is: "+ packageName);
- // PackageInfo pInfo = getPackageInfo(packageName);
- ResolveInfo rInfo = mPackageManager.resolveActivity(intent, 0);
- BluetoothMapEmailSettingsItem app = mLoader.createAppItem(rInfo, false);
- if(app != null) {
- registerObserver(app);
- // Add all accounts to mFullList
- ArrayList<BluetoothMapEmailSettingsItem> newAccountList = mLoader.parseAccounts(app);
- mFullList.put(app, newAccountList);
- }
- }
- else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
-
- Uri data = intent.getData();
- String packageName = data.getEncodedSchemeSpecificPart();
- if(D)Log.d(TAG,"The removed package is: "+ packageName);
- BluetoothMapEmailSettingsItem app = getApp(packageName);
- /* Find the object and remove from fullList */
- if(app != null) {
- unregisterObserver(app);
- mFullList.remove(app);
- }
- }
- }
- };
- mContext.registerReceiver(mReceiver,new IntentFilter(Intent.ACTION_PACKAGE_ADDED));
- }
- private void removeReceiver(){
- if(D)Log.d(TAG,"removeReceiver()\n");
- mContext.unregisterReceiver(mReceiver);
- }
- private PackageInfo getPackageInfo(String packageName){
- mPackageManager = mContext.getPackageManager();
- try {
- return mPackageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA|PackageManager.GET_SERVICES);
- } catch (NameNotFoundException e) {
- Log.e(TAG,"Error getting package metadata", e);
- }
- return null;
- }
-
- /**
- * Method to get a list of the accounts (across all apps) that are set to be shared
- * through MAP.
- * @return Arraylist<BluetoothMapEmailSettingsItem> containing all enabled accounts
- */
- public ArrayList<BluetoothMapEmailSettingsItem> getEnabledAccountItems(){
- if(D)Log.d(TAG,"getEnabledAccountItems()\n");
- ArrayList<BluetoothMapEmailSettingsItem> list = new ArrayList<BluetoothMapEmailSettingsItem>();
- for(BluetoothMapEmailSettingsItem app:mFullList.keySet()){
- ArrayList<BluetoothMapEmailSettingsItem> accountList = mFullList.get(app);
- for(BluetoothMapEmailSettingsItem acc: accountList){
- if(acc.mIsChecked) {
- list.add(acc);
- }
- }
- }
- return list;
- }
-
- /**
- * Method to get a list of the accounts (across all apps).
- * @return Arraylist<BluetoothMapEmailSettingsItem> containing all accounts
- */
- public ArrayList<BluetoothMapEmailSettingsItem> getAllAccountItems(){
- if(D)Log.d(TAG,"getAllAccountItems()\n");
- ArrayList<BluetoothMapEmailSettingsItem> list = new ArrayList<BluetoothMapEmailSettingsItem>();
- for(BluetoothMapEmailSettingsItem app:mFullList.keySet()){
- ArrayList<BluetoothMapEmailSettingsItem> accountList = mFullList.get(app);
- list.addAll(accountList);
- }
- return list;
- }
-
-
- /**
- * Cleanup all resources - must be called to avoid leaks.
- */
- public void shutdown() {
- deinitObservers();
- removeReceiver();
- }
-}
diff --git a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsLoader.java b/src/com/android/bluetooth/map/BluetoothMapEmailSettingsLoader.java
deleted file mode 100644
index c6a6ca7..0000000
--- a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsLoader.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
-* Copyright (C) 2014 Samsung System LSI
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-
-package com.android.bluetooth.map;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-
-import com.android.bluetooth.map.BluetoothMapEmailSettingsItem;
-
-
-
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
-import com.android.bluetooth.mapapi.BluetoothMapContract;
-import android.text.format.DateUtils;
-import android.util.Log;
-
-
-public class BluetoothMapEmailSettingsLoader {
- private static final String TAG = "BluetoothMapEmailSettingsLoader";
- private static final boolean D = BluetoothMapService.DEBUG;
- private static final boolean V = BluetoothMapService.VERBOSE;
- private Context mContext = null;
- private PackageManager mPackageManager = null;
- private ContentResolver mResolver;
- private int mAccountsEnabledCount = 0;
- private ContentProviderClient mProviderClient = null;
- private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
-
- public BluetoothMapEmailSettingsLoader(Context ctx)
- {
- mContext = ctx;
- }
-
- /**
- * Method to look through all installed packages system-wide and find those that contain the
- * BTMAP meta-tag in their manifest file. For each app the list of accounts are fetched using
- * the method parseAccounts().
- * @return LinkedHashMap with the packages as keys(BluetoothMapEmailSettingsItem) and
- * values as ArrayLists of BluetoothMapEmailSettingsItems.
- */
- public LinkedHashMap<BluetoothMapEmailSettingsItem,
- ArrayList<BluetoothMapEmailSettingsItem>> parsePackages(boolean includeIcon) {
-
- LinkedHashMap<BluetoothMapEmailSettingsItem, ArrayList<BluetoothMapEmailSettingsItem>> groups =
- new LinkedHashMap<BluetoothMapEmailSettingsItem,ArrayList<BluetoothMapEmailSettingsItem>>();
- Intent searchIntent = new Intent(BluetoothMapContract.PROVIDER_INTERFACE);
- // reset the counter every time this method is called.
- mAccountsEnabledCount=0;
- // find all installed packages and filter out those that do not support Map Email.
- // this is done by looking for a apps with content providers containing the intent-filter for
- // android.content.action.BTMAP_SHARE in the manifest file.
- mPackageManager = mContext.getPackageManager();
- List<ResolveInfo> resInfos = mPackageManager
- .queryIntentContentProviders(searchIntent, 0);
-
- if (resInfos != null) {
- if(D) Log.d(TAG,"Found " + resInfos.size() + " applications");
- for (ResolveInfo rInfo : resInfos) {
- // We cannot rely on apps that have been force-stopped in the application settings menu.
- if ((rInfo.providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) == 0) {
- BluetoothMapEmailSettingsItem app = createAppItem(rInfo, includeIcon);
- if (app != null){
- ArrayList<BluetoothMapEmailSettingsItem> accounts = parseAccounts(app);
- // we do not want to list apps without accounts
- if(accounts.size() >0)
- {
- // we need to make sure that the "select all" checkbox is checked if all accounts in the list are checked
- app.mIsChecked = true;
- for (BluetoothMapEmailSettingsItem acc: accounts)
- {
- if(!acc.mIsChecked)
- {
- app.mIsChecked = false;
- break;
- }
- }
- groups.put(app, accounts);
- }
- }
- } else {
- if(D)Log.d(TAG,"Ignoring force-stopped authority "+ rInfo.providerInfo.authority +"\n");
- }
- }
- }
- else {
- if(D) Log.d(TAG,"Found no applications");
- }
- return groups;
- }
-
- public BluetoothMapEmailSettingsItem createAppItem(ResolveInfo rInfo,
- boolean includeIcon) {
- String provider = rInfo.providerInfo.authority;
- if(provider != null) {
- String name = rInfo.loadLabel(mPackageManager).toString();
- if(D)Log.d(TAG,rInfo.providerInfo.packageName + " - " + name + " - meta-data(provider = " + provider+")\n");
- BluetoothMapEmailSettingsItem app = new BluetoothMapEmailSettingsItem(
- "0",
- name,
- rInfo.providerInfo.packageName,
- provider,
- (includeIcon == false)? null : rInfo.loadIcon(mPackageManager));
- return app;
- }
-
- return null;
- }
-
-
- /**
- * Method for getting the accounts under a given contentprovider from a package.
- * This
- * @param app The parent app object
- * @return An ArrayList of BluetoothMapEmailSettingsItems containing all the accounts from the app
- */
- public ArrayList<BluetoothMapEmailSettingsItem> parseAccounts(BluetoothMapEmailSettingsItem app) {
- Cursor c = null;
- if(D) Log.d(TAG,"Adding app "+app.getPackageName());
- ArrayList<BluetoothMapEmailSettingsItem> children = new ArrayList<BluetoothMapEmailSettingsItem>();
- // Get the list of accounts from the email apps content resolver (if possible
- mResolver = mContext.getContentResolver();
- try{
- mProviderClient = mResolver.acquireUnstableContentProviderClient(Uri.parse(app.mBase_uri_no_account));
- if (mProviderClient == null) {
- throw new RemoteException("Failed to acquire provider for " + app.getPackageName());
- }
- mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
-
- Uri uri = Uri.parse(app.mBase_uri_no_account + "/" + BluetoothMapContract.TABLE_ACCOUNT);
- c = mProviderClient.query(uri, BluetoothMapContract.BT_ACCOUNT_PROJECTION, null, null,
- BluetoothMapContract.AccountColumns._ID+" DESC");
- while (c != null && c.moveToNext()) {
- BluetoothMapEmailSettingsItem child = new BluetoothMapEmailSettingsItem(
- String.valueOf((c.getInt(c.getColumnIndex(BluetoothMapContract.AccountColumns._ID)))),
- c.getString(c.getColumnIndex(BluetoothMapContract.AccountColumns.ACCOUNT_DISPLAY_NAME)) ,
- app.getPackageName(),
- app.getProviderAuthority(),
- null);
-
- child.mIsChecked = (c.getInt(c.getColumnIndex(BluetoothMapContract.AccountColumns.FLAG_EXPOSE))!=0);
- /*update the account counter so we can make sure that not to many accounts are checked. */
- if(child.mIsChecked)
- {
- mAccountsEnabledCount++;
- }
- children.add(child);
- }
- } catch (RemoteException e){
- if(D)Log.d(TAG,"Could not establish ContentProviderClient for "+app.getPackageName()+
- " - returning empty account list" );
- } finally {
- if (c != null) c.close();
- }
- return children;
- }
- /**
- * Gets the number of enabled accounts in total across all supported apps.
- * NOTE that this method should not be called before the parsePackages method
- * has been successfully called.
- * @return number of enabled accounts
- */
- public int getAccountsEnabledCount() {
- if(D)Log.d(TAG,"Enabled Accounts count:"+ mAccountsEnabledCount);
- return mAccountsEnabledCount;
- }
-}
diff --git a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
index 3175eae..8b08fd8 100644
--- a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
@@ -1,5 +1,5 @@
/*
-* Copyright (C) 2014 Samsung System LSI
+* Copyright (C) 2015 Samsung System LSI
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -15,31 +15,39 @@
package com.android.bluetooth.map;
-import android.util.Log;
-import org.xmlpull.v1.XmlSerializer;
-
-import com.android.internal.util.FastXmlSerializer;
-
import java.io.IOException;
+import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
-import java.util.Map.Entry;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
/**
* Class to contain a single folder element representation.
*
*/
-public class BluetoothMapFolderElement {
+public class BluetoothMapFolderElement implements Comparable<BluetoothMapFolderElement>{
private String mName;
private BluetoothMapFolderElement mParent = null;
+ private long mFolderId = -1;
private boolean mHasSmsMmsContent = false;
+ private boolean mHasImContent = false;
private boolean mHasEmailContent = false;
- private long mEmailFolderId = -1;
+
+ private boolean mIgnore = false;
+
private HashMap<String, BluetoothMapFolderElement> mSubFolders;
private static final boolean D = BluetoothMapService.DEBUG;
@@ -53,6 +61,14 @@
mSubFolders = new HashMap<String, BluetoothMapFolderElement>();
}
+ public void setIngore(boolean ignore) {
+ mIgnore = ignore;
+ }
+
+ public boolean shouldIgnore() {
+ return mIgnore;
+ }
+
public String getName() {
return mName;
}
@@ -61,24 +77,29 @@
return mHasSmsMmsContent;
}
- public long getEmailFolderId(){
- return mEmailFolderId;
+ public long getFolderId(){
+ return mFolderId;
}
public boolean hasEmailContent(){
return mHasEmailContent;
}
- public void setEmailFolderId(long emailFolderId) {
- this.mEmailFolderId = emailFolderId;
+ public void setFolderId(long folderId) {
+ this.mFolderId = folderId;
}
-
public void setHasSmsMmsContent(boolean hasSmsMmsContent) {
this.mHasSmsMmsContent = hasSmsMmsContent;
}
-
public void setHasEmailContent(boolean hasEmailContent) {
this.mHasEmailContent = hasEmailContent;
}
+ public void setHasImContent(boolean hasImContent) {
+ this.mHasImContent = hasImContent;
+ }
+
+ public boolean hasImContent(){
+ return mHasImContent;
+ }
/**
* Fetch the parent folder.
@@ -106,38 +127,38 @@
}
- public BluetoothMapFolderElement getEmailFolderByName(String name) {
+ public BluetoothMapFolderElement getFolderByName(String name) {
BluetoothMapFolderElement folderElement = this.getRoot();
folderElement = folderElement.getSubFolder("telecom");
folderElement = folderElement.getSubFolder("msg");
folderElement = folderElement.getSubFolder(name);
- if (folderElement != null && folderElement.getEmailFolderId() == -1 )
+ if (folderElement != null && folderElement.getFolderId() == -1 )
folderElement = null;
return folderElement;
}
- public BluetoothMapFolderElement getEmailFolderById(long id) {
- return getEmailFolderById(id, this);
+ public BluetoothMapFolderElement getFolderById(long id) {
+ return getFolderById(id, this);
}
- public static BluetoothMapFolderElement getEmailFolderById(long id,
+ public static BluetoothMapFolderElement getFolderById(long id,
BluetoothMapFolderElement folderStructure) {
if(folderStructure == null) {
return null;
}
- return findEmailFolderById(id, folderStructure.getRoot());
+ return findFolderById(id, folderStructure.getRoot());
}
- private static BluetoothMapFolderElement findEmailFolderById(long id,
+ private static BluetoothMapFolderElement findFolderById(long id,
BluetoothMapFolderElement folder) {
- if(folder.getEmailFolderId() == id) {
+ if(folder.getFolderId() == id) {
return folder;
}
/* Else */
for(BluetoothMapFolderElement subFolder : folder.mSubFolders.values().toArray(
new BluetoothMapFolderElement[folder.mSubFolders.size()]))
{
- BluetoothMapFolderElement ret = findEmailFolderById(id, subFolder);
+ BluetoothMapFolderElement ret = findFolderById(id, subFolder);
if(ret != null) {
return ret;
}
@@ -148,7 +169,7 @@
/**
* Fetch the root folder.
- * @return the parent folder or null if we are at the root folder.
+ * @return the root folder.
*/
public BluetoothMapFolderElement getRoot() {
BluetoothMapFolderElement rootFolder = this;
@@ -165,10 +186,12 @@
public BluetoothMapFolderElement addFolder(String name){
name = name.toLowerCase(Locale.US);
BluetoothMapFolderElement newFolder = mSubFolders.get(name);
- if(D) Log.i(TAG,"addFolder():" + name);
if(newFolder == null) {
+ if(D) Log.i(TAG,"addFolder():" + name);
newFolder = new BluetoothMapFolderElement(name, this);
mSubFolders.put(name, newFolder);
+ } else {
+ if(D) Log.i(TAG,"addFolder():" + name + " already added");
}
return newFolder;
}
@@ -179,35 +202,37 @@
* @return the added folder element.
*/
public BluetoothMapFolderElement addSmsMmsFolder(String name){
- name = name.toLowerCase(Locale.US);
- BluetoothMapFolderElement newFolder = mSubFolders.get(name);
- if(D) Log.i(TAG,"addSmsMmsFolder():" + name);
- if(newFolder == null) {
- newFolder = new BluetoothMapFolderElement(name, this);
- mSubFolders.put(name, newFolder);
- }
+ if(D) Log.i(TAG,"addSmsMmsFolder()");
+ BluetoothMapFolderElement newFolder = addFolder(name);
newFolder.setHasSmsMmsContent(true);
return newFolder;
}
/**
+ * Add a im folder.
+ * @param name the name of the folder to add.
+ * @return the added folder element.
+ */
+ public BluetoothMapFolderElement addImFolder(String name, long idFolder){
+ if(D) Log.i(TAG,"addImFolder() id = " + idFolder);
+ BluetoothMapFolderElement newFolder = addFolder(name);
+ newFolder.setHasImContent(true);
+ newFolder.setFolderId(idFolder);
+ return newFolder;
+ }
+
+ /**
* Add an Email folder.
* @param name the name of the folder to add.
* @return the added folder element.
*/
public BluetoothMapFolderElement addEmailFolder(String name, long emailFolderId){
- name = name.toLowerCase();
- BluetoothMapFolderElement newFolder = mSubFolders.get(name);
- if(V) Log.v(TAG,"addEmailFolder(): name = " + name
- + "id = " + emailFolderId);
- if(newFolder == null) {
- newFolder = new BluetoothMapFolderElement(name, this);
- mSubFolders.put(name, newFolder);
- }
- newFolder.setEmailFolderId(emailFolderId);
+ if(V) Log.v(TAG,"addEmailFolder() id = " + emailFolderId);
+ BluetoothMapFolderElement newFolder = addFolder(name);
+ newFolder.setFolderId(emailFolderId);
+ newFolder.setHasEmailContent(true);
return newFolder;
}
-
/**
* Fetch the number of sub folders.
* @return returns the number of sub folders.
@@ -244,7 +269,7 @@
xmlMsgElement.startDocument("UTF-8", true);
xmlMsgElement.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
xmlMsgElement.startTag(null, "folder-listing");
- xmlMsgElement.attribute(null, "version", "1.0");
+ xmlMsgElement.attribute(null, "version", BluetoothMapUtils.MAP_V10_STR);
for(i = offset; i<stopIndex; i++)
{
xmlMsgElement.startTag(null, "folder");
@@ -265,4 +290,119 @@
}
return sw.toString().getBytes("UTF-8");
}
+
+ /* The functions below are useful for implementing a MAP client, reusing the object.
+ * Currently they are only used for test purposes.
+ * */
+
+ /**
+ * Append sub folders from an XML document as specified in the MAP specification.
+ * Attributes will be inherited from parent folder - with regards to message types in the
+ * folder.
+ * @param xmlDocument - InputStream with the document
+ *
+ * @throws XmlPullParserException
+ * @throws IOException
+ */
+ public void appendSubfolders(InputStream xmlDocument)
+ throws XmlPullParserException, IOException {
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ int type;
+ parser.setInput(xmlDocument, "UTF-8");
+
+ // First find the folder-listing
+ while((type=parser.next()) != XmlPullParser.END_TAG
+ && type != XmlPullParser.END_DOCUMENT ) {
+ // Skip until we get a start tag
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ // Skip until we get a folder-listing tag
+ String name = parser.getName();
+ if(!name.equalsIgnoreCase("folder-listing")) {
+ if(D) Log.i(TAG,"Unknown XML tag: " + name);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ readFolders(parser);
+ }
+ } finally {
+ xmlDocument.close();
+ }
+ }
+
+ /**
+ * Parses folder elements, and add to mSubFolders.
+ * @param parser the Xml Parser currently pointing to an folder-listing tag.
+ * @throws XmlPullParserException
+ * @throws IOException
+ */
+ public void readFolders(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int type;
+ if(D) Log.i(TAG,"readFolders(): ");
+ while((type=parser.next()) != XmlPullParser.END_TAG
+ && type != XmlPullParser.END_DOCUMENT ) {
+ // Skip until we get a start tag
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ // Skip until we get a folder-listing tag
+ String name = parser.getName();
+ if(name.trim().equalsIgnoreCase("folder") == false) {
+ if(D) Log.i(TAG,"Unknown XML tag: " + name);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ int count = parser.getAttributeCount();
+ for (int i = 0; i<count; i++) {
+ if(parser.getAttributeName(i).trim().equalsIgnoreCase("name")) {
+ // We found a folder, append to sub folders.
+ BluetoothMapFolderElement element =
+ addFolder(parser.getAttributeValue(i).trim());
+ element.setHasEmailContent(mHasEmailContent);
+ element.setHasImContent(mHasImContent);
+ element.setHasSmsMmsContent(mHasSmsMmsContent);
+ } else {
+ if(D) Log.i(TAG,"Unknown XML attribute: " + parser.getAttributeName(i));
+ }
+ }
+ parser.nextTag();
+ }
+ }
+
+ /**
+ * Recursive compare of all folder names
+ */
+ @Override
+ public int compareTo(BluetoothMapFolderElement another) {
+ if(another == null) return 1;
+ int ret = mName.compareToIgnoreCase(another.mName);
+ // TODO: Do we want to add compare of folder type?
+ if(ret == 0) {
+ ret = mSubFolders.size() - another.mSubFolders.size();
+ if(ret == 0) {
+ // Compare all sub folder elements (will do nothing if mSubFolders is empty)
+ for(BluetoothMapFolderElement subfolder : mSubFolders.values()) {
+ BluetoothMapFolderElement subfolderAnother =
+ another.mSubFolders.get(subfolder.getName());
+ if(subfolderAnother == null) {
+ if(D) Log.i(TAG, subfolder.getFullPath() + " not in another");
+ return 1;
+ }
+ ret = subfolder.compareTo(subfolderAnother);
+ if(ret != 0) {
+ if(D) Log.i(TAG, subfolder.getFullPath() + " filed compareTo()");
+ return ret;
+ }
+ }
+ } else {
+ if(D) Log.i(TAG, "mSubFolders.size(): " + mSubFolders.size() +
+ " another.mSubFolders.size(): " + another.mSubFolders.size());
+ }
+ } else {
+ if(D) Log.i(TAG, "mName: " + mName + " another.mName: " + another.mName);
+ }
+ return ret;
+ }
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
index 449b934..66e6d3b 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
@@ -15,12 +15,18 @@
package com.android.bluetooth.map;
import java.io.IOException;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
import javax.obex.ServerSession;
import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.IObexConnectionHandler;
import com.android.bluetooth.ObexServerSockets;
+import com.android.bluetooth.map.BluetoothMapContentObserver.Msg;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.sdp.SdpManager;
import android.bluetooth.BluetoothAdapter;
@@ -35,7 +41,8 @@
import android.util.Log;
public class BluetoothMapMasInstance implements IObexConnectionHandler {
- private static final String TAG = "BluetoothMapMasInstance";
+ private final String TAG;
+ private static volatile int sInstanceCounter = 0;
private static final boolean D = BluetoothMapService.DEBUG;
private static final boolean V = BluetoothMapService.VERBOSE;
@@ -44,6 +51,7 @@
private static final int SDP_MAP_MSG_TYPE_SMS_GSM = 0x02;
private static final int SDP_MAP_MSG_TYPE_SMS_CDMA = 0x04;
private static final int SDP_MAP_MSG_TYPE_MMS = 0x08;
+ private static final int SDP_MAP_MSG_TYPE_IM = 0x10;
private static final int SDP_MAP_MAS_VERSION = 0x0102;
@@ -51,30 +59,46 @@
private static final int SDP_MAP_MAS_FEATURES = 0x0000007F;
private ServerSession mServerSession = null;
-
// The handle to the socket registration with SDP
private ObexServerSockets mServerSockets = null;
private int mSdpHandle = -1;
// The actual incoming connection handle
private BluetoothSocket mConnSocket = null;
-
- private BluetoothDevice mRemoteDevice = null; // The remote connected device
-
+ // The remote connected device
+ private BluetoothDevice mRemoteDevice = null;
private BluetoothAdapter mAdapter;
private volatile boolean mInterrupted; // Used to interrupt socket accept thread
+ private volatile boolean mShutdown = false; // Used to interrupt socket accept thread
private Handler mServiceHandler = null; // MAP service message handler
private BluetoothMapService mMapService = null; // Handle to the outer MAP service
private Context mContext = null; // MAP service context
private BluetoothMnsObexClient mMnsClient = null; // Shared MAP MNS client
- private BluetoothMapEmailSettingsItem mAccount = null; //
- private String mBaseEmailUri = null; // Email client base URI for this instance
+ private BluetoothMapAccountItem mAccount = null; //
+ private String mBaseUri = null; // Client base URI for this instance
private int mMasInstanceId = -1;
private boolean mEnableSmsMms = false;
BluetoothMapContentObserver mObserver;
+ private AtomicLong mDbIndetifier = new AtomicLong();
+ private AtomicLong mFolderVersionCounter = new AtomicLong(0);
+ private AtomicLong mSmsMmsConvoListVersionCounter = new AtomicLong(0);
+ private AtomicLong mImEmailConvoListVersionCounter = new AtomicLong(0);
+
+ private Map<Long, Msg> mMsgListSms=null;
+ private Map<Long, Msg> mMsgListMms=null;
+ private Map<Long, Msg> mMsgListMsg=null;
+
+ private Map<String, BluetoothMapConvoContactElement> mContactList;
+
+ private HashMap<Long,BluetoothMapConvoListingElement> mSmsMmsConvoList =
+ new HashMap<Long, BluetoothMapConvoListingElement>();
+
+ private HashMap<Long,BluetoothMapConvoListingElement> mImEmailConvoList =
+ new HashMap<Long, BluetoothMapConvoListingElement>();
+
private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
public static final String TYPE_SMS_MMS_STR = "SMS/MMS";
@@ -90,35 +114,143 @@
*/
public BluetoothMapMasInstance (BluetoothMapService mapService,
Context context,
- BluetoothMapEmailSettingsItem account,
+ BluetoothMapAccountItem account,
int masId,
boolean enableSmsMms) {
+ TAG = "BluetoothMapMasInstance" + sInstanceCounter++;
mMapService = mapService;
mServiceHandler = mapService.getHandler();
mContext = context;
mAccount = account;
if(account != null) {
- mBaseEmailUri = account.mBase_uri;
+ mBaseUri = account.mBase_uri;
}
mMasInstanceId = masId;
mEnableSmsMms = enableSmsMms;
init();
}
+ /* Needed only for test */
+ protected BluetoothMapMasInstance() {
+ TAG = "BluetoothMapMasInstance" + sInstanceCounter++;
+ }
+
@Override
public String toString() {
- return "MasId: " + mMasInstanceId + " Uri:" + mBaseEmailUri + " SMS/MMS:" + mEnableSmsMms;
+ return "MasId: " + mMasInstanceId + " Uri:" + mBaseUri + " SMS/MMS:" + mEnableSmsMms;
}
private void init() {
mAdapter = BluetoothAdapter.getDefaultAdapter();
}
- public int getMasId() {
+ /**
+ * The data base identifier is used by connecting MCE devices to evaluate if cached data
+ * is still valid, hence only update this value when something actually invalidates the data.
+ * Situations where this must be called:
+ * - MAS ID's vs. server channels are scrambled (As neither MAS ID, name or server channels)
+ * can be used by a client to uniquely identify a specific message database - except MAS id 0
+ * we should change this value if the server channel is changed.
+ * - If a MAS instance folderVersionCounter roles over - will not happen before a long
+ * is too small to hold a unix time-stamp, hence is not handled.
+ */
+ private void updateDbIdentifier(){
+ mDbIndetifier.set(Calendar.getInstance().getTime().getTime());
+ }
+
+ /**
+ * update the time stamp used for FOLDER version counter.
+ * Call once when a content provider notification caused applicable changes to the
+ * list of messages.
+ */
+ /* package */ void updateFolderVersionCounter() {
+ mFolderVersionCounter.incrementAndGet();
+ }
+
+ /**
+ * update the CONVO LIST version counter.
+ * Call once when a content provider notification caused applicable changes to the
+ * list of contacts, or when an update is manually triggered.
+ */
+ /* package */ void updateSmsMmsConvoListVersionCounter() {
+ mSmsMmsConvoListVersionCounter.incrementAndGet();
+ }
+
+ /* package */ void updateImEmailConvoListVersionCounter() {
+ mImEmailConvoListVersionCounter.incrementAndGet();
+ }
+
+ /* package */ Map<Long, Msg> getMsgListSms() {
+ return mMsgListSms;
+ }
+
+ /* package */ void setMsgListSms(Map<Long, Msg> msgListSms) {
+ mMsgListSms = msgListSms;
+ }
+
+ /* package */ Map<Long, Msg> getMsgListMms() {
+ return mMsgListMms;
+ }
+
+ /* package */ void setMsgListMms(Map<Long, Msg> msgListMms) {
+ mMsgListMms = msgListMms;
+ }
+
+ /* package */ Map<Long, Msg> getMsgListMsg() {
+ return mMsgListMsg;
+ }
+
+ /* package */ void setMsgListMsg(Map<Long, Msg> msgListMsg) {
+ mMsgListMsg = msgListMsg;
+ }
+
+ /* package */ Map<String, BluetoothMapConvoContactElement> getContactList() {
+ return mContactList;
+ }
+
+ /* package */ void setContactList(Map<String, BluetoothMapConvoContactElement> contactList) {
+ mContactList = contactList;
+ }
+
+ HashMap<Long,BluetoothMapConvoListingElement> getSmsMmsConvoList() {
+ return mSmsMmsConvoList;
+ }
+
+ void setSmsMmsConvoList(HashMap<Long,BluetoothMapConvoListingElement> smsMmsConvoList) {
+ mSmsMmsConvoList = smsMmsConvoList;
+ }
+
+ HashMap<Long,BluetoothMapConvoListingElement> getImEmailConvoList() {
+ return mImEmailConvoList;
+ }
+
+ void setImEmailConvoList(HashMap<Long,BluetoothMapConvoListingElement> imEmailConvoList) {
+ mImEmailConvoList = imEmailConvoList;
+ }
+
+ /* package*/
+ int getMasId() {
return mMasInstanceId;
}
- public void startRfcommSocketListener() {
+ /* package*/
+ long getDbIdentifier() {
+ return mDbIndetifier.get();
+ }
+
+ /* package*/
+ long getFolderVersionCounter() {
+ return mFolderVersionCounter.get();
+ }
+
+ /* package */
+ long getCombinedConvoListVersionCounter() {
+ long combinedVersionCounter = mSmsMmsConvoListVersionCounter.get();
+ combinedVersionCounter += mImEmailConvoListVersionCounter.get();
+ return combinedVersionCounter;
+ }
+
+ synchronized public void startRfcommSocketListener() {
if (D) Log.d(TAG, "Map Service startRfcommSocketListener");
if (mServerSession != null) {
@@ -145,11 +277,17 @@
Log.e(TAG, "Failed to start the listeners");
return;
}
- if(mSdpHandle > 0) {
+ if(mSdpHandle >= 0) {
SdpManager.getDefaultManager().removeSdpRecord(mSdpHandle);
+ if(V) Log.d(TAG, "Removing SDP record for MAS instance: " + mMasInstanceId +
+ " Object reference: " + this + "SDP handle: " + mSdpHandle);
}
mSdpHandle = createMasSdpRecord(mServerSockets.getRfcommChannel(),
mServerSockets.getL2capPsm());
+ // Here we might have changed crucial data, hence reset DB identifier
+ if(V) Log.d(TAG, "Creating new SDP record for MAS instance: " + mMasInstanceId +
+ " Object reference: " + this + "SDP handle: " + mSdpHandle);
+ updateDbIdentifier();
}
}
@@ -164,17 +302,26 @@
if(mEnableSmsMms) {
masName = TYPE_SMS_MMS_STR;
messageTypeFlags |= SDP_MAP_MSG_TYPE_SMS_GSM |
-// SDP_MAP_MSG_TYPE_SMS_CDMA|
+ SDP_MAP_MSG_TYPE_SMS_CDMA|
SDP_MAP_MSG_TYPE_MMS;
}
- if(mBaseEmailUri != null) {
+ if(mBaseUri != null) {
if(mEnableSmsMms) {
- masName += "/" + TYPE_EMAIL_STR;
+ if(mAccount.getType() == TYPE.EMAIL) {
+ masName += "/" + TYPE_EMAIL_STR;
+ } else if(mAccount.getType() == TYPE.IM) {
+ masName += "/" + TYPE_IM_STR;
+ }
} else {
masName = mAccount.getName();
}
- messageTypeFlags |= SDP_MAP_MSG_TYPE_EMAIL;
+
+ if(mAccount.getType() == TYPE.EMAIL) {
+ messageTypeFlags |= SDP_MAP_MSG_TYPE_EMAIL;
+ } else if(mAccount.getType() == TYPE.IM) {
+ messageTypeFlags |= SDP_MAP_MSG_TYPE_IM;
+ }
}
return SdpManager.getDefaultManager().createMapMasRecord(masName,
@@ -210,7 +357,7 @@
mapServer = new BluetoothMapObexServer(mServiceHandler,
mContext,
mObserver,
- mMasInstanceId,
+ this,
mAccount,
mEnableSmsMms);
@@ -317,9 +464,12 @@
@Override
public synchronized void onAcceptFailed() {
mServerSockets = null; // Will cause a new to be created when calling start.
- Log.e(TAG,"Failed to accept incomming connection - restarting");
- startRfcommSocketListener();
-
+ if(mShutdown) {
+ Log.e(TAG,"Failed to accept incomming connection - " + "shutdown");
+ } else {
+ Log.e(TAG,"Failed to accept incomming connection - " + "restarting");
+ startRfcommSocketListener();
+ }
}
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
index 6a486c6..b904c36 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
@@ -32,13 +32,13 @@
private static final String TAG = "BluetoothMapMessageListing";
private static final boolean D = BluetoothMapService.DEBUG;
- private List<BluetoothMapMessageListingElement> list;
+ private List<BluetoothMapMessageListingElement> mList;
public BluetoothMapMessageListing(){
- list = new ArrayList<BluetoothMapMessageListingElement>();
+ mList = new ArrayList<BluetoothMapMessageListingElement>();
}
public void add(BluetoothMapMessageListingElement element) {
- list.add(element);
+ mList.add(element);
/* update info regarding whether the list contains unread messages */
if (element.getReadBool())
{
@@ -51,9 +51,9 @@
* @return the number of elements in the list.
*/
public int getCount() {
- if(list != null)
+ if(mList != null)
{
- return list.size();
+ return mList.size();
}
return 0;
}
@@ -73,18 +73,22 @@
* @return list
*/
public List<BluetoothMapMessageListingElement> getList(){
- return list;
+ return mList;
}
/**
* Encode the list of BluetoothMapMessageListingElement(s) into a UTF-8
* formatted XML-string in a trimmed byte array
*
+ * @param version the version as a string.
+ * Set the listing version to e.g. "1.0" or "1.1".
+ * To make this future proof, no check is added to validate the value, hence be careful.
* @return a reference to the encoded byte array.
* @throws UnsupportedEncodingException
* if UTF-8 encoding is unsupported on the platform.
*/
- public byte[] encode(boolean includeThreadId) throws UnsupportedEncodingException {
+ // TODO: Remove includeThreadId when MAP-IM is adopted
+ public byte[] encode(boolean includeThreadId, String version) throws UnsupportedEncodingException {
StringWriter sw = new StringWriter();
XmlSerializer xmlMsgElement = new FastXmlSerializer();
try {
@@ -92,9 +96,9 @@
xmlMsgElement.startDocument("UTF-8", true);
xmlMsgElement.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
xmlMsgElement.startTag(null, "MAP-msg-listing");
- xmlMsgElement.attribute(null, "version", "1.0");
+ xmlMsgElement.attribute(null, "version", version);
// Do the XML encoding of list
- for (BluetoothMapMessageListingElement element : list) {
+ for (BluetoothMapMessageListingElement element : mList) {
element.encode(xmlMsgElement, includeThreadId); // Append the list element
}
xmlMsgElement.endTag(null, "MAP-msg-listing");
@@ -110,22 +114,22 @@
}
public void sort() {
- Collections.sort(list);
+ Collections.sort(mList);
}
public void segment(int count, int offset) {
- count = Math.min(count, list.size() - offset);
+ count = Math.min(count, mList.size() - offset);
if (count > 0) {
- list = list.subList(offset, offset + count);
- if(list == null) {
- list = new ArrayList<BluetoothMapMessageListingElement>(); // Return an empty list
+ mList = mList.subList(offset, offset + count);
+ if(mList == null) {
+ mList = new ArrayList<BluetoothMapMessageListingElement>(); // Return an empty list
}
} else {
- if(offset > list.size()) {
- list = new ArrayList<BluetoothMapMessageListingElement>();
+ if(offset > mList.size()) {
+ mList = new ArrayList<BluetoothMapMessageListingElement>();
Log.d(TAG, "offset greater than list size. Returning empty list");
} else {
- list = list.subList(offset, list.size());
+ mList = mList.subList(offset, mList.size());
}
}
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
index 04788f2..ec4005b 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
@@ -44,15 +44,21 @@
private String mRecipientName = null;
private String mRecipientAddressing = null;
private TYPE mType = null;
+ private boolean mMsgTypeAppParamSet = false;
private int mSize = -1;
private String mText = null;
private String mReceptionStatus = null;
+ private String mDeliveryStatus = null;
private int mAttachmentSize = -1;
private String mPriority = null;
private boolean mRead = false;
private String mSent = null;
private String mProtect = null;
+ private String mFolderType = null;
private String mThreadId = null;
+ private String mThreadName = null;
+ private String mAttachmentMimeTypes = null;
+
private boolean mReportRead = false;
private int mCursorIndex = 0;
@@ -77,6 +83,8 @@
}
public String getDateTimeString() {
+ /* TODO: if the feature bit mask of the client supports it, add the time-zone
+ * (as for MSETime) */
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
Date date = new Date(mDateTime);
return format.format(date); // Format to YYYYMMDDTHHMMSS local time
@@ -138,7 +146,8 @@
return mType;
}
- public void setType(TYPE type) {
+ public void setType(TYPE type, boolean appParamSet) {
+ this.mMsgTypeAppParamSet = appParamSet;
this.mType = type;
}
@@ -166,6 +175,14 @@
this.mReceptionStatus = receptionStatus;
}
+ public String getDeliveryStatus() {
+ return mDeliveryStatus;
+ }
+
+ public void setDeliveryStatus(String deliveryStatus) {
+ this.mDeliveryStatus = deliveryStatus;
+ }
+
public int getAttachmentSize() {
return mAttachmentSize;
}
@@ -174,6 +191,14 @@
this.mAttachmentSize = attachmentSize;
}
+ public String getAttachmentMimeTypes() {
+ return mAttachmentMimeTypes;
+ }
+
+ public void setAttachmentMimeTypes(String attachmentMimeTypes) {
+ this.mAttachmentMimeTypes = attachmentMimeTypes;
+ }
+
public String getPriority() {
return mPriority;
}
@@ -188,7 +213,6 @@
public boolean getReadBool() {
return mRead;
}
-
public void setRead(boolean read, boolean reportRead) {
this.mRead = read;
this.mReportRead = reportRead;
@@ -210,12 +234,28 @@
this.mProtect = protect;
}
- public void setThreadId(long threadId) {
+ public void setThreadId(long threadId, TYPE type) {
if(threadId != -1) {
- this.mThreadId = BluetoothMapUtils.getLongAsString(threadId);
+ this.mThreadId = BluetoothMapUtils.getMapConvoHandle(threadId, type);
}
}
+ public String getThreadName() {
+ return mThreadName;
+ }
+
+ public void setThreadName(String name) {
+ this.mThreadName = name;
+ }
+
+ public String getFolderType() {
+ return mFolderType;
+ }
+
+ public void setFolderType(String folderType) {
+ this.mFolderType = folderType;
+ }
+
public int compareTo(BluetoothMapMessageListingElement e) {
if (this.mDateTime < e.mDateTime) {
return 1;
@@ -226,54 +266,35 @@
}
}
- /**
- * Strip away any illegal XML characters, that would otherwise cause the
- * xml serializer to throw an exception.
- * Examples of such characters are the emojis used on Android.
- * @param text The string to validate
- * @return the same string if valid, otherwise a new String stripped for
- * any illegal characters
- */
- private static String stripInvalidChars(String text) {
- char out[] = new char[text.length()];
- int i, o, l;
- for(i=0, o=0, l=text.length(); i<l; i++){
- char c = text.charAt(i);
- if((c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd)) {
- out[o++] = c;
- } // Else we skip the character
- }
-
- if(i==o) {
- return text;
- } else { // We removed some characters, create the new string
- return new String(out,0,o);
- }
- }
-
/* Encode the MapMessageListingElement into the StringBuilder reference.
* */
- public void encode(XmlSerializer xmlMsgElement, boolean includeThreadId) throws IllegalArgumentException, IllegalStateException, IOException
+ public void encode(XmlSerializer xmlMsgElement, boolean includeThreadId)
+ throws IllegalArgumentException, IllegalStateException, IOException
{
-
// contruct the XML tag for a single msg in the msglisting
xmlMsgElement.startTag(null, "msg");
- xmlMsgElement.attribute(null, "handle", BluetoothMapUtils.getMapHandle(mCpHandle, mType));
- if(mSubject != null)
- xmlMsgElement.attribute(null, "subject", stripInvalidChars(mSubject));
+ xmlMsgElement.attribute(null, "handle",
+ BluetoothMapUtils.getMapHandle(mCpHandle, mType));
+ if(mSubject != null){
+ String stripped = BluetoothMapUtils.stripInvalidChars(mSubject);
+ xmlMsgElement.attribute(null, "subject",
+ stripped.substring(0, stripped.length() < 256 ? stripped.length() : 256));
+ }
if(mDateTime != 0)
xmlMsgElement.attribute(null, "datetime", this.getDateTimeString());
if(mSenderName != null)
- xmlMsgElement.attribute(null, "sender_name", stripInvalidChars(mSenderName));
+ xmlMsgElement.attribute(null, "sender_name",
+ BluetoothMapUtils.stripInvalidChars(mSenderName));
if(mSenderAddressing != null)
xmlMsgElement.attribute(null, "sender_addressing", mSenderAddressing);
if(mReplytoAddressing != null)
xmlMsgElement.attribute(null, "replyto_addressing",mReplytoAddressing);
if(mRecipientName != null)
- xmlMsgElement.attribute(null, "recipient_name", stripInvalidChars(mRecipientName));
+ xmlMsgElement.attribute(null, "recipient_name",
+ BluetoothMapUtils.stripInvalidChars(mRecipientName));
if(mRecipientAddressing != null)
xmlMsgElement.attribute(null, "recipient_addressing", mRecipientAddressing);
- if(mType != null)
+ if(mMsgTypeAppParamSet == true)
xmlMsgElement.attribute(null, "type", mType.name());
if(mSize != -1)
xmlMsgElement.attribute(null, "size", Integer.toString(mSize));
@@ -281,8 +302,13 @@
xmlMsgElement.attribute(null, "text", mText);
if(mReceptionStatus != null)
xmlMsgElement.attribute(null, "reception_status", mReceptionStatus);
+ if(mDeliveryStatus != null)
+ xmlMsgElement.attribute(null, "delivery_status", mDeliveryStatus);
if(mAttachmentSize != -1)
- xmlMsgElement.attribute(null, "attachment_size", Integer.toString(mAttachmentSize));
+ xmlMsgElement.attribute(null, "attachment_size",
+ Integer.toString(mAttachmentSize));
+ if(mAttachmentMimeTypes != null)
+ xmlMsgElement.attribute(null, "attachment_mime_types", mAttachmentMimeTypes);
if(mPriority != null)
xmlMsgElement.attribute(null, "priority", mPriority);
if(mReportRead)
@@ -292,7 +318,11 @@
if(mProtect != null)
xmlMsgElement.attribute(null, "protected", mProtect);
if(mThreadId != null && includeThreadId == true)
- xmlMsgElement.attribute(null, "thread_id", mThreadId);
+ xmlMsgElement.attribute(null, "conversation_id", mThreadId);
+ if(mThreadName != null && includeThreadId == true)
+ xmlMsgElement.attribute(null, "conversation_name", mThreadName);
+ if(mFolderType != null )
+ xmlMsgElement.attribute(null, "folder_type", mFolderType);
xmlMsgElement.endTag(null, "msg");
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
index f0cdb2d..b5e4e03 100644
--- a/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -1,5 +1,5 @@
/*
-* Copyright (C) 2014 Samsung System LSI
+* Copyright (C) 2015 Samsung System LSI
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -22,13 +22,15 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.ParcelUuid;
import android.os.RemoteException;
-import com.android.bluetooth.mapapi.BluetoothMapContract;
import android.text.format.DateUtils;
import android.util.Log;
-import com.android.bluetooth.map.BluetoothMapUtils;
+import com.android.bluetooth.SignedLongLong;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.bluetooth.map.BluetoothMapMasInstance;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
import java.io.IOException;
import java.io.InputStream;
@@ -65,38 +67,47 @@
(byte)0xB0, (byte)0xDE, (byte)0x08, (byte)0x00,
(byte)0x20, (byte)0x0C, (byte)0x9A, (byte)0x66
};
-
+ public static final ParcelUuid MAP =
+ ParcelUuid.fromString("00001134-0000-1000-8000-00805F9B34FB");
+ public static final ParcelUuid MNS =
+ ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
+ public static final ParcelUuid MAS =
+ ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB");
/* Message types */
- private static final String TYPE_GET_FOLDER_LISTING = "x-obex/folder-listing";
- private static final String TYPE_GET_MESSAGE_LISTING = "x-bt/MAP-msg-listing";
- private static final String TYPE_MESSAGE = "x-bt/message";
- private static final String TYPE_SET_MESSAGE_STATUS = "x-bt/messageStatus";
- private static final String TYPE_SET_NOTIFICATION_REGISTRATION = "x-bt/MAP-NotificationRegistration";
- private static final String TYPE_MESSAGE_UPDATE = "x-bt/MAP-messageUpdate";
+ private static final String TYPE_GET_FOLDER_LISTING = "x-obex/folder-listing";
+ private static final String TYPE_GET_MESSAGE_LISTING = "x-bt/MAP-msg-listing";
+ private static final String TYPE_GET_CONVO_LISTING = "x-bt/MAP-convo-listing";
+ private static final String TYPE_MESSAGE = "x-bt/message";
+ private static final String TYPE_SET_MESSAGE_STATUS = "x-bt/messageStatus";
+ private static final String TYPE_SET_NOTIFICATION_REGISTRATION
+ = "x-bt/MAP-NotificationRegistration";
+ private static final String TYPE_MESSAGE_UPDATE = "x-bt/MAP-messageUpdate";
+ private static final String TYPE_GET_MAS_INSTANCE_INFORMATION
+ = "x-bt/MASInstanceInformation";
+ private static final String TYPE_SET_OWNER_STATUS = "x-bt/participant";
+ private static final String TYPE_SET_NOTIFICATION_FILTER
+ = "x-bt/MAP-notification-filter";
+
+ private static final int MAS_INSTANCE_INFORMATION_LENGTH = 200;
private BluetoothMapFolderElement mCurrentFolder;
-
private BluetoothMapContentObserver mObserver = null;
-
private Handler mCallback = null;
-
private Context mContext;
-
private boolean mIsAborted = false;
-
BluetoothMapContent mOutContent;
-
- private String mBaseEmailUriString = null;
+ private String mBaseUriString = null;
private long mAccountId = 0;
- private BluetoothMapEmailSettingsItem mAccount = null;
+ private BluetoothMapAccountItem mAccount = null;
private Uri mEmailFolderUri = null;
-
private int mMasId = 0;
+ private BluetoothMapMasInstance mMasInstance; // TODO: change to interface?
// updated during connect if remote has alternative value
private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
-
private boolean mEnableSmsMms = false;
private boolean mThreadIdSupport = false; // true if peer supports threadId in msg listing
+ // Defaults message version is 1.0 but 1.1+ if feature bit is set
+ private String mMessageVersion = BluetoothMapUtils.MAP_V10_STR;
private String mAuthority;
private ContentResolver mResolver;
private ContentProviderClient mProviderClient = null;
@@ -104,8 +115,8 @@
public BluetoothMapObexServer(Handler callback,
Context context,
BluetoothMapContentObserver observer,
- int masId,
- BluetoothMapEmailSettingsItem account,
+ BluetoothMapMasInstance mas,
+ BluetoothMapAccountItem account,
boolean enableSmsMms) throws RemoteException {
super();
mCallback = callback;
@@ -113,18 +124,22 @@
mObserver = observer;
mEnableSmsMms = enableSmsMms;
mAccount = account;
- mMasId = masId;
+ mMasId = mas.getMasId();
+ mMasInstance = mas;
+ mRemoteFeatureMask = mMasInstance.getRemoteFeatureMask();
if(account != null && account.getProviderAuthority() != null) {
mAccountId = account.getAccountId();
mAuthority = account.getProviderAuthority();
mResolver = mContext.getContentResolver();
if (D) Log.d(TAG, "BluetoothMapObexServer(): accountId=" + mAccountId);
- mBaseEmailUriString = account.mBase_uri + "/";
- if (D) Log.d(TAG, "BluetoothMapObexServer(): emailBaseUri=" + mBaseEmailUriString);
- mEmailFolderUri = BluetoothMapContract.buildFolderUri(mAuthority,
- Long.toString(mAccountId));
- if (D) Log.d(TAG, "BluetoothMapObexServer(): mEmailFolderUri=" + mEmailFolderUri);
+ mBaseUriString = account.mBase_uri + "/";
+ if (D) Log.d(TAG, "BluetoothMapObexServer(): baseUri=" + mBaseUriString);
+ if (account.getType() == TYPE.EMAIL) {
+ mEmailFolderUri = BluetoothMapContract.buildFolderUri(mAuthority,
+ Long.toString(mAccountId));
+ if (D) Log.d(TAG, "BluetoothMapObexServer(): mEmailFolderUri=" + mEmailFolderUri);
+ }
mProviderClient = acquireUnstableContentProviderOrThrow();
}
@@ -132,7 +147,7 @@
mCurrentFolder to root folder */
mObserver.setFolderStructure(mCurrentFolder.getRoot());
- mOutContent = new BluetoothMapContent(mContext, mBaseEmailUriString);
+ mOutContent = new BluetoothMapContent(mContext, mAccount, mMasInstance);
}
@@ -140,7 +155,8 @@
*
*/
private ContentProviderClient acquireUnstableContentProviderOrThrow() throws RemoteException{
- ContentProviderClient providerClient = mResolver.acquireUnstableContentProviderClient(mAuthority);
+ ContentProviderClient providerClient =
+ mResolver.acquireUnstableContentProviderClient(mAuthority);
if (providerClient == null) {
throw new RemoteException("Failed to acquire provider for " + mAuthority);
}
@@ -152,20 +168,42 @@
* Build the default minimal folder structure, as defined in the MAP specification.
*/
private void buildFolderStructure() throws RemoteException{
- mCurrentFolder = new BluetoothMapFolderElement("root", null); // This will be the root element
+ mCurrentFolder = new BluetoothMapFolderElement("root", null);//This will be the root element
+ mCurrentFolder.setHasSmsMmsContent(mEnableSmsMms);
+ boolean hasIM = false;
+ boolean hasEmail = false;
+ if (mAccount != null) {
+ if (mAccount.getType() == TYPE.IM)
+ hasIM = true;
+ if( mAccount.getType() == TYPE.EMAIL)
+ hasEmail = true;
+ }
+ mCurrentFolder.setHasImContent(hasIM);
+ mCurrentFolder.setHasEmailContent(hasEmail);
+
BluetoothMapFolderElement tmpFolder;
tmpFolder = mCurrentFolder.addFolder("telecom"); // root/telecom
+ tmpFolder.setHasSmsMmsContent(mEnableSmsMms);
+ tmpFolder.setHasImContent(hasIM);
+ tmpFolder.setHasEmailContent(hasEmail);
+
tmpFolder = tmpFolder.addFolder("msg"); // root/telecom/msg
+ tmpFolder.setHasSmsMmsContent(mEnableSmsMms);
+ tmpFolder.setHasImContent(hasIM);
+ tmpFolder.setHasEmailContent(hasEmail);
- addBaseFolders(tmpFolder); // Add the mandatory folders
-
+ // Add the mandatory folders
+ addBaseFolders(tmpFolder);
if(mEnableSmsMms) {
addSmsMmsFolders(tmpFolder);
}
- if(mEmailFolderUri != null) {
+ if(hasEmail) {
if (D) Log.d(TAG, "buildFolderStructure(): " + mEmailFolderUri.toString());
addEmailFolders(tmpFolder);
}
+ if (hasIM) {
+ addImFolders(tmpFolder);
+ }
}
/**
@@ -173,19 +211,18 @@
* @param root
*/
private void addBaseFolders(BluetoothMapFolderElement root) {
- root.addFolder(BluetoothMapContract.FOLDER_NAME_INBOX); // root/telecom/msg/inbox
+ root.addFolder(BluetoothMapContract.FOLDER_NAME_INBOX); // root/telecom/msg/inbox
root.addFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX);
root.addFolder(BluetoothMapContract.FOLDER_NAME_SENT);
root.addFolder(BluetoothMapContract.FOLDER_NAME_DELETED);
}
-
/**
* Add
* @param root
*/
private void addSmsMmsFolders(BluetoothMapFolderElement root) {
- root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_INBOX); // root/telecom/msg/inbox
+ root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_INBOX); // root/telecom/msg/inbox
root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX);
root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_SENT);
root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_DELETED);
@@ -193,17 +230,32 @@
}
+ private void addImFolders(BluetoothMapFolderElement root) throws RemoteException {
+ // Select all parent folders
+ root.addImFolder(BluetoothMapContract.FOLDER_NAME_INBOX,
+ BluetoothMapContract.FOLDER_ID_INBOX); // root/telecom/msg/inbox
+ root.addImFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX,
+ BluetoothMapContract.FOLDER_ID_OUTBOX);
+ root.addImFolder(BluetoothMapContract.FOLDER_NAME_SENT,
+ BluetoothMapContract.FOLDER_ID_SENT);
+ root.addImFolder(BluetoothMapContract.FOLDER_NAME_DELETED,
+ BluetoothMapContract.FOLDER_ID_DELETED);
+ root.addImFolder(BluetoothMapContract.FOLDER_NAME_DRAFT,
+ BluetoothMapContract.FOLDER_ID_DRAFT);
+ }
+
/**
* Recursively adds folders based on the folders in the email content provider.
* Add a content observer? - to refresh the folder list if any change occurs.
- * Consider simply deleting the entire table, and then rebuild using buildFolderStructure()
+ * Consider simply deleting the entire table, and then rebuild using
+ * buildFolderStructure()
* WARNING: there is no way to notify the client about these changes - hence
* we need to either keep the folder structure constant, disconnect or fail anything
* referring to currentFolder.
* It is unclear what to set as current folder to be able to go one level up...
* The best solution would be to keep the folder structure constant during a connection.
* @param folder the parent folder to which subFolders needs to be added. The
- * folder.getEmailFolderId() will be used to query sub-folders.
+ * folder.getFolderId() will be used to query sub-folders.
* Use a parentFolder with id -1 to get all folders from root.
*/
private void addEmailFolders(BluetoothMapFolderElement parentFolder) throws RemoteException {
@@ -211,15 +263,22 @@
BluetoothMapFolderElement newFolder;
String where = BluetoothMapContract.FolderColumns.PARENT_FOLDER_ID +
- " = " + parentFolder.getEmailFolderId();
+ " = " + parentFolder.getFolderId();
Cursor c = mProviderClient.query(mEmailFolderUri,
BluetoothMapContract.BT_FOLDER_PROJECTION, where, null, null);
try {
- while (c != null && c.moveToNext()) {
- String name = c.getString(c.getColumnIndex(BluetoothMapContract.FolderColumns.NAME));
- long id = c.getLong(c.getColumnIndex(BluetoothMapContract.FolderColumns._ID));
- newFolder = parentFolder.addEmailFolder(name, id);
- addEmailFolders(newFolder); // Use recursion to add any sub folders
+ if (c != null) {
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ String name = c.getString(c.getColumnIndex(
+ BluetoothMapContract.FolderColumns.NAME));
+ long id = c.getLong(c.getColumnIndex(BluetoothMapContract.FolderColumns._ID));
+ newFolder = parentFolder.addEmailFolder(name, id);
+ addEmailFolders(newFolder); // Use recursion to add any sub folders
+ }
+
+ } else {
+ if (D) Log.d(TAG, "addEmailFolders(): no elements found");
}
} finally {
if (c != null) c.close();
@@ -232,11 +291,22 @@
return true;
}
+ public int getRemoteFeatureMask() {
+ return mRemoteFeatureMask;
+ }
+
+ public void setRemoteFeatureMask(int mRemoteFeatureMask) {
+ if(D) Log.d(TAG, "setRemoteFeatureMask() " + Integer.toHexString(mRemoteFeatureMask));
+ this.mRemoteFeatureMask = mRemoteFeatureMask;
+ this.mOutContent.setRemoteFeatureMask(mRemoteFeatureMask);
+ }
+
@Override
public int onConnect(final HeaderSet request, HeaderSet reply) {
if (D) Log.d(TAG, "onConnect():");
if (V) logHeader(request);
mThreadIdSupport = false; // Always assume not supported at new connect.
+ mMessageVersion = BluetoothMapUtils.MAP_V10_STR;//always assume version 1.0 to start with
notifyUpdateWakeLock();
Long threadedMailKey = null;
try {
@@ -283,6 +353,16 @@
return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
}
+ if((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)
+ == BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) {
+ mThreadIdSupport = true;
+ }
+
+ if((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT)
+ == BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT){
+ mMessageVersion = BluetoothMapUtils.MAP_V11_STR;
+ }
+
if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " +
"MSG_SESSION_ESTABLISHED msg.");
@@ -329,8 +409,8 @@
try {
request = op.getReceivedHeader();
+ if (V) logHeader(request);
type = (String)request.getHeader(HeaderSet.TYPE);
-
name = (String)request.getHeader(HeaderSet.NAME);
appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
if(appParamRaw != null)
@@ -347,11 +427,19 @@
+ appParams.getNotificationStatus());
}
return mObserver.setNotificationRegistration(appParams.getNotificationStatus());
- }else if(type.equals(TYPE_SET_MESSAGE_STATUS)) {
+ }else if(type.equals(TYPE_SET_NOTIFICATION_FILTER)) {
if(V) {
- Log.d(TAG,"TYPE_SET_MESSAGE_STATUS: StatusIndicator: "
- + appParams.getStatusIndicator()
- + ", StatusValue: " + appParams.getStatusValue());
+ Log.d(TAG,"TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: "
+ + appParams.getNotificationFilter());
+ }
+ mObserver.setNotificationFilter(appParams.getNotificationFilter());
+ return ResponseCodes.OBEX_HTTP_OK;
+ } else if(type.equals(TYPE_SET_MESSAGE_STATUS)) {
+ if(V) {
+ Log.d(TAG,"TYPE_SET_MESSAGE_STATUS: " +
+ "StatusIndicator: " + appParams.getStatusIndicator()
+ + ", StatusValue: " + appParams.getStatusValue()
+ + ", ExtentedData: " + "" ); // TODO: appParams.getExtendedImData());
}
return setMessageStatus(name, appParams);
} else if (type.equals(TYPE_MESSAGE)) {
@@ -360,8 +448,19 @@
+ ", retry: " + appParams.getRetry()
+ ", charset: " + appParams.getCharset());
}
- return pushMessage(op, name, appParams);
+ return pushMessage(op, name, appParams, mMessageVersion);
+ } else if (type.equals(TYPE_SET_OWNER_STATUS)) {
+ if(V) {
+ Log.d(TAG,"TYPE_SET_OWNER_STATUS:" +
+ " PresenceAvailability " + appParams.getPresenceAvailability() +
+ ", PresenceStatus: " + appParams.getPresenceStatus() +
+ ", LastActivity: " + appParams.getLastActivityString() +
+ ", ChatStatus: " + appParams.getChatState() +
+ ", ChatStatusConvoId: " + appParams.getChatStateConvoIdString());
+ }
+ return setOwnerStatus(name, appParams);
}
+
} catch (RemoteException e){
//reload the providerClient and return error
try {
@@ -388,18 +487,18 @@
private int updateInbox() throws RemoteException{
if (mAccount != null) {
- BluetoothMapFolderElement inboxFolder = mCurrentFolder.getEmailFolderByName(
+ BluetoothMapFolderElement inboxFolder = mCurrentFolder.getFolderByName(
BluetoothMapContract.FOLDER_NAME_INBOX);
if (inboxFolder != null) {
long accountId = mAccountId;
if (D) Log.d(TAG,"updateInbox inbox=" + inboxFolder.getName() + "id="
- + inboxFolder.getEmailFolderId());
+ + inboxFolder.getFolderId());
final Bundle extras = new Bundle(2);
if (accountId != -1) {
if (D) Log.d(TAG,"updateInbox accountId=" + accountId);
extras.putLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID,
- inboxFolder.getEmailFolderId());
+ inboxFolder.getFolderId());
extras.putLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, accountId);
} else {
// Only error code allowed on an UpdateInbox is OBEX_HTTP_NOT_IMPLEMENTED,
@@ -408,11 +507,12 @@
return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
}
- Uri emailUri = Uri.parse(mBaseEmailUriString);
+ Uri emailUri = Uri.parse(mBaseUriString);
if (D) Log.d(TAG,"updateInbox in: " + emailUri.toString());
try {
if (D) Log.d(TAG,"updateInbox call()...");
- Bundle myBundle = mProviderClient.call(BluetoothMapContract.METHOD_UPDATE_FOLDER, null, extras);
+ Bundle myBundle = mProviderClient.call(
+ BluetoothMapContract.METHOD_UPDATE_FOLDER, null, extras);
if (myBundle != null)
return ResponseCodes.OBEX_HTTP_OK;
else {
@@ -445,13 +545,16 @@
+ folderElement.getName());
} else {
folderElement = mCurrentFolder.getSubFolder(folderName);
- if(D) Log.d(TAG, "Folder name: " + folderName + " resulted in this element: "
- + folderElement.getName());
+ if (folderElement != null) {
+ if(D) Log.d(TAG, "Folder name: " + folderName +
+ " resulted in this element: " + folderElement.getName());
+ }
}
return folderElement;
}
- private int pushMessage(final Operation op, String folderName, BluetoothMapAppParams appParams) {
+ private int pushMessage(final Operation op, String folderName,
+ BluetoothMapAppParams appParams, String messageVersion) {
if(appParams.getCharset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
if(D) Log.d(TAG, "pushMessage: Missing charset - unable to decode message content. " +
"appParams.getCharset() = " + appParams.getCharset());
@@ -466,8 +569,8 @@
} else {
folderName = folderElement.getName();
}
- if (!folderName.equals(BluetoothMapContract.FOLDER_NAME_OUTBOX) &&
- !folderName.equals(BluetoothMapContract.FOLDER_NAME_DRAFT)) {
+ if (!folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX) &&
+ !folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) {
if(D) Log.d(TAG, "pushMessage: Is only allowed to outbox and draft. " +
"folderName=" + folderName);
return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
@@ -481,6 +584,7 @@
bMsgStream = op.openInputStream();
// Decode the messageBody
message = BluetoothMapbMessage.parse(bMsgStream, appParams.getCharset());
+ message.setVersionString(messageVersion);
// Send message
if (mObserver == null || message == null) {
// Should not happen except at shutdown.
@@ -488,14 +592,16 @@
return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
}
- if ((message.getType().equals(TYPE.EMAIL) && (folderElement.getEmailFolderId() == -1)) ||
- ((message.getType().equals(TYPE.SMS_GSM) || message.getType().equals(TYPE.SMS_CDMA) ||
- message.getType().equals(TYPE.MMS)) && !folderElement.hasSmsMmsContent()) ) {
+ if ((message.getType().equals(TYPE.EMAIL) && (folderElement.getFolderId() == -1))
+ || ((message.getType().equals(TYPE.SMS_GSM) ||
+ message.getType().equals(TYPE.SMS_CDMA) ||
+ message.getType().equals(TYPE.MMS))
+ && !folderElement.hasSmsMmsContent()) ) {
if(D) Log.w(TAG, "Wrong message type recieved" );
return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
}
- long handle = mObserver.pushMessage(message, folderElement, appParams, mBaseEmailUriString);
+ long handle = mObserver.pushMessage(message, folderElement, appParams, mBaseUriString);
if (D) Log.d(TAG, "pushMessage handle: " + handle);
if (handle < 0) {
if(D) Log.w(TAG, "Message handle not created" );
@@ -503,7 +609,8 @@
}
HeaderSet replyHeaders = new HeaderSet();
String handleStr = BluetoothMapUtils.getMapHandle(handle, message.getType());
- if (D) Log.d(TAG, "handleStr: " + handleStr + " message.getType(): " + message.getType());
+ if (D) Log.d(TAG, "handleStr: " + handleStr + " message.getType(): "
+ + message.getType());
replyHeaders.setHeader(HeaderSet.NAME, handleStr);
op.sendHeaders(replyHeaders);
@@ -513,6 +620,7 @@
mProviderClient = acquireUnstableContentProviderOrThrow();
}catch (RemoteException e2){
//should not happen
+ if(D) Log.w(TAG, "acquireUnstableContentProviderOrThrow FAILED");
}
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
} catch (IllegalArgumentException e) {
@@ -542,23 +650,27 @@
private int setMessageStatus(String msgHandle, BluetoothMapAppParams appParams) {
int indicator = appParams.getStatusIndicator();
int value = appParams.getStatusValue();
+ String extendedData = ""; // TODO: appParams.getExtendedImData();
+
long handle;
BluetoothMapUtils.TYPE msgType;
- if(indicator == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
- value == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
- msgHandle == null) {
+ if (msgHandle == null) {
+ return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+ } else if ((indicator == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
+ value == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) &&
+ extendedData == null) {
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
}
if (mObserver == null) {
- if(D) Log.d(TAG, "Error: no mObserver!");
+ if(D) Log.e(TAG, "Error: no mObserver!");
return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
}
try {
handle = BluetoothMapUtils.getCpHandle(msgHandle);
msgType = BluetoothMapUtils.getMsgTypeFromHandle(msgHandle);
- if(D)Log.d(TAG,"setMessageStatus. Handle:" + handle+", MsgType: "+ msgType);
+ if(D) Log.d(TAG,"setMessageStatus. Handle:" + handle+", MsgType: "+ msgType);
} catch (NumberFormatException e) {
Log.w(TAG, "Wrongly formatted message handle: " + msgHandle);
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
@@ -566,23 +678,89 @@
if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_DELETED) {
if (!mObserver.setMessageStatusDeleted(handle, msgType, mCurrentFolder,
- mBaseEmailUriString, value)) {
+ mBaseUriString, value)) {
+ if(D) Log.w(TAG,"setMessageStatusDeleted failed");
return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
}
- } else /* BluetoothMapAppParams.STATUS_INDICATOR_READ */ {
- try{
- if (!mObserver.setMessageStatusRead(handle, msgType, mBaseEmailUriString, value)) {
- if(D)Log.d(TAG,"not able to update the message");
- return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
- }
- }catch(RemoteException e) {
- if(D) Log.e(TAG,"Error in setMessageStatusRead()", e);
+ } else if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_READ) {
+ try {
+ if (!mObserver.setMessageStatusRead(handle, msgType, mBaseUriString, value)) {
+ if(D) Log.w(TAG,"not able to update the message");
+ return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+ }
+ } catch (RemoteException e) {
+ if(D) Log.w(TAG,"Error in setMessageStatusRead()", e);
return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
}
}
+ if (extendedData != null) {
+
+ }
+
return ResponseCodes.OBEX_HTTP_OK;
}
+ private int setOwnerStatus(String msgHandle, BluetoothMapAppParams appParams)
+ throws RemoteException{
+ // This does only work for IM
+ if (mAccount != null && mAccount.getType() == BluetoothMapUtils.TYPE.IM) {
+ final Bundle extras = new Bundle(5);
+
+ int presenceState = appParams.getPresenceAvailability();
+ String presenceStatus = appParams.getPresenceStatus();
+ long lastActivity = appParams.getLastActivity();
+ int chatState = appParams.getChatState();
+ String chatStatusConvoId = appParams.getChatStateConvoIdString();
+
+ if(presenceState == BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
+ presenceStatus == null &&
+ lastActivity == BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
+ chatState == BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
+ chatStatusConvoId == null) {
+ return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+ }
+
+ if(presenceState != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
+ extras.putInt(BluetoothMapContract.EXTRA_PRESENCE_STATE, presenceState);
+ }
+ if (presenceStatus != null){
+ extras.putString(BluetoothMapContract.EXTRA_PRESENCE_STATUS, presenceStatus);
+ }
+ if (lastActivity != BluetoothMapAppParams.INVALID_VALUE_PARAMETER){
+ extras.putLong(BluetoothMapContract.EXTRA_LAST_ACTIVE, lastActivity);
+ }
+ if (chatState != BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
+ chatStatusConvoId != null){
+ extras.putInt(BluetoothMapContract.EXTRA_CHAT_STATE, chatState);
+ extras.putString(BluetoothMapContract.EXTRA_CONVERSATION_ID, chatStatusConvoId);
+ }
+
+ Uri uri = Uri.parse(mBaseUriString);
+ if (D) Log.d(TAG,"setOwnerStatus in: " + uri.toString());
+ try {
+ if (D) Log.d(TAG,"setOwnerStatus call()...");
+ Bundle myBundle = mProviderClient.call(
+ BluetoothMapContract.METHOD_SET_OWNER_STATUS, null, extras);
+ if (myBundle != null)
+ return ResponseCodes.OBEX_HTTP_OK;
+ else {
+ if (D) Log.d(TAG,"setOwnerStatus call failed");
+ return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
+ }
+ } catch (RemoteException e){
+ mProviderClient = acquireUnstableContentProviderOrThrow();
+ return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+ } catch (NullPointerException e) {
+ if(D) Log.e(TAG, "setOwnerStatus - if uri or method is null", e);
+ return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+ } catch (IllegalArgumentException e) {
+ if(D) Log.e(TAG, "setOwnerStatus - if uri is not known", e);
+ return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+ }
+ }
+ return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+ }
+
@Override
public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
final boolean create) {
@@ -601,8 +779,9 @@
}
if (V) logHeader(request);
- if (D) Log.d(TAG, "onSetPath name is " + folderName + " backup: " + backup
- + " create: " + create);
+ if (D) Log.d(TAG, "onSetPath name is " + folderName +
+ " backup: " + backup +
+ " create: " + create);
if(backup == true){
if(mCurrentFolder.getParent() != null)
@@ -655,13 +834,13 @@
try {
request = op.getReceivedHeader();
type = (String)request.getHeader(HeaderSet.TYPE);
- name = (String)request.getHeader(HeaderSet.NAME);
+
appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
if(appParamRaw != null)
appParams = new BluetoothMapAppParams(appParamRaw);
if (V) logHeader(request);
- if (D) Log.d(TAG, "OnGet type is " + type + " name is " + name);
+ if (D) Log.d(TAG, "OnGet type is " + type );
if (type == null) {
if (V) Log.d(TAG, "type is null?" + type);
@@ -670,32 +849,68 @@
if (type.equals(TYPE_GET_FOLDER_LISTING)) {
if (V && appParams != null) {
- Log.d(TAG,"TYPE_GET_FOLDER_LISTING: MaxListCount = " + appParams.getMaxListCount() +
- ", ListStartOffset = " + appParams.getStartOffset());
+ Log.d(TAG,"TYPE_GET_FOLDER_LISTING: MaxListCount = "
+ + appParams.getMaxListCount()
+ + ", ListStartOffset = " + appParams.getStartOffset());
}
- return sendFolderListingRsp(op, appParams); // Block until all packets have been send.
+ // Block until all packets have been send.
+ return sendFolderListingRsp(op, appParams);
} else if (type.equals(TYPE_GET_MESSAGE_LISTING)){
+ name = (String)request.getHeader(HeaderSet.NAME);
if (V && appParams != null) {
- Log.d(TAG,"TYPE_GET_MESSAGE_LISTING: MaxListCount = " + appParams.getMaxListCount() +
- ", ListStartOffset = " + appParams.getStartOffset());
- Log.d(TAG,"SubjectLength = " + appParams.getSubjectLength() + ", ParameterMask = " +
- appParams.getParameterMask());
- Log.d(TAG,"FilterMessageType = " + appParams.getFilterMessageType() +
- ", FilterPeriodBegin = " + appParams.getFilterPeriodBegin());
- Log.d(TAG,"FilterPeriodEnd = " + appParams.getFilterPeriodBegin() +
- ", FilterReadStatus = " + appParams.getFilterReadStatus());
+ Log.d(TAG,"TYPE_GET_MESSAGE_LISTING: folder name is: " + name +
+ ", MaxListCount = " + appParams.getMaxListCount() +
+ ", ListStartOffset = " + appParams.getStartOffset());
+ Log.d(TAG,"SubjectLength = " + appParams.getSubjectLength() +
+ ", ParameterMask = " + appParams.getParameterMask());
+ Log.d(TAG,"FilterMessageType = " + appParams.getFilterMessageType() );
+ Log.d(TAG,"FilterPeriodBegin = " + appParams.getFilterPeriodBeginString() +
+ ", FilterPeriodEnd = " + appParams.getFilterPeriodEndString() +
+ ", FilterReadStatus = " + appParams.getFilterReadStatus());
Log.d(TAG,"FilterRecipient = " + appParams.getFilterRecipient() +
- ", FilterOriginator = " + appParams.getFilterOriginator());
+ ", FilterOriginator = " + appParams.getFilterOriginator());
Log.d(TAG,"FilterPriority = " + appParams.getFilterPriority());
+ long tmpLong = appParams.getFilterMsgHandle();
+ Log.d(TAG,"FilterMsgHandle = " + (
+ (tmpLong == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) ? "" :
+ Long.toHexString(tmpLong)));
+ SignedLongLong tmpLongLong = appParams.getFilterConvoId();
+ Log.d(TAG,"FilterConvoId = " + ((tmpLongLong == null) ? "" :
+ Long.toHexString(tmpLongLong.getLeastSignificantBits()) ) );
}
- return sendMessageListingRsp(op, appParams, name); // Block until all packets have been send.
- } else if (type.equals(TYPE_MESSAGE)){
+ // Block until all packets have been send.
+ return sendMessageListingRsp(op, appParams, name);
+
+ } else if (type.equals(TYPE_GET_CONVO_LISTING)){
+ name = (String)request.getHeader(HeaderSet.NAME);
+ if (V && appParams != null) {
+ Log.d(TAG,"TYPE_GET_CONVO_LISTING: name is" + name +
+ ", MaxListCount = " + appParams.getMaxListCount() +
+ ", ListStartOffset = " + appParams.getStartOffset());
+ Log.d(TAG,"FilterLastActivityBegin = "+appParams.getFilterLastActivityBegin());
+ Log.d(TAG,"FilterLastActivityEnd = " + appParams.getFilterLastActivityEnd());
+ Log.d(TAG,"FilterReadStatus = " + appParams.getFilterReadStatus());
+ Log.d(TAG,"FilterRecipient = " + appParams.getFilterRecipient());
+ }
+ // Block until all packets have been send.
+ return sendConvoListingRsp(op, appParams,name);
+ } else if (type.equals(TYPE_GET_MAS_INSTANCE_INFORMATION)) {
if(V && appParams != null) {
- Log.d(TAG,"TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() +
+ Log.d(TAG,"TYPE_MESSAGE (GET): MASInstandeId = "
+ + appParams.getMasInstanceId());
+ }
+ // Block until all packets have been send.
+ return sendMASInstanceInformationRsp(op, appParams);
+ } else if (type.equals(TYPE_MESSAGE)){
+ name = (String)request.getHeader(HeaderSet.NAME);
+ if(V && appParams != null) {
+ Log.d(TAG,"TYPE_MESSAGE (GET): name is" + name +
+ ", Attachment = " + appParams.getAttachment() +
", Charset = " + appParams.getCharset() +
", FractionRequest = " + appParams.getFractionRequest());
}
- return sendGetMessageRsp(op, name, appParams); // Block until all packets have been send.
+ // Block until all packets have been send.
+ return sendGetMessageRsp(op, name, appParams, mMessageVersion);
} else {
Log.w(TAG, "unknown type request: " + type);
return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
@@ -735,7 +950,9 @@
* @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
* {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
*/
- private int sendMessageListingRsp(Operation op, BluetoothMapAppParams appParams, String folderName){
+ private int sendMessageListingRsp(Operation op,
+ BluetoothMapAppParams appParams,
+ String folderName){
OutputStream outStream = null;
byte[] outBytes = null;
int maxChunkSize, bytesToWrite, bytesWritten = 0, listSize;
@@ -749,13 +966,34 @@
appParams.setStartOffset(0);
}
- BluetoothMapFolderElement folderToList = getFolderElementFromName(folderName);
- if(folderToList == null) {
- Log.w(TAG,"sendMessageListingRsp: folderToList == null - sending OBEX_HTTP_BAD_REQUEST");
- return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ /* MAP Spec 1.3 introduces the following
+ * Messagehandle filtering:
+ * msgListing (messageHandle=X) -> other allowed filters: parametereMask, subjectMaxLength
+ * ConversationID filtering:
+ * msgListing (convoId empty) -> should work as normal msgListing in valid folders
+ * msgListing (convoId=0, no other filters) -> should return all messages in all folders
+ * msgListing (convoId=N, other filters) -> should return all messages in conversationID=N
+ * according to filters requested
+ */
+ BluetoothMapFolderElement folderToList = null;
+ if (appParams.getFilterMsgHandle() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER
+ || appParams.getFilterConvoId() != null) {
+ // If messageHandle or convoId filtering ignore folder
+ Log.v(TAG,"sendMessageListingRsp: ignore folder ");
+ folderToList = mCurrentFolder.getRoot();
+ folderToList.setIngore(true);
+ } else {
+ folderToList = getFolderElementFromName(folderName);
+ if(folderToList == null) {
+ Log.w(TAG,"sendMessageListingRsp: folderToList == "+
+ "null-sending OBEX_HTTP_BAD_REQUEST");
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ Log.v(TAG,"sendMessageListingRsp: has sms " + folderToList.hasSmsMmsContent() +
+ "has email " + folderToList.hasEmailContent() +
+ "has IM " + folderToList.hasImContent() );
}
- // Check to see if we only need to send the size - hence no need to encode.
try {
// Open the OBEX body stream
outStream = op.openOutputStream();
@@ -766,29 +1004,46 @@
if(appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
appParams.setStartOffset(0);
+ // Check to see if we only need to send the size - hence no need to encode.
if(appParams.getMaxListCount() != 0) {
outList = mOutContent.msgListing(folderToList, appParams);
// Generate the byte stream
outAppParams.setMessageListingSize(outList.getCount());
- outBytes = outList.encode(mThreadIdSupport); // Include thread ID for clients that supports it.
+ String version;
+ if(0 < (mRemoteFeatureMask &
+ BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)) {
+ version = BluetoothMapUtils.MAP_V11_STR;
+ } else {
+ version = BluetoothMapUtils.MAP_V10_STR;
+ }
+ /* This will only set the version, the bit must also be checked before adding any
+ * 1.1 bits to the listing. */
+ outBytes = outList.encode(mThreadIdSupport, version);
hasUnread = outList.hasUnread();
- }
- else {
+ } else {
listSize = mOutContent.msgListingSize(folderToList, appParams);
hasUnread = mOutContent.msgListingHasUnread(folderToList, appParams);
outAppParams.setMessageListingSize(listSize);
op.noBodyHeader();
}
-
+ folderToList.setIngore(false);
// Build the application parameter header
-
// let the peer know if there are unread messages in the list
if(hasUnread) {
outAppParams.setNewMessage(1);
}else{
outAppParams.setNewMessage(0);
}
-
+ if ((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_DATABASE_INDENTIFIER_BIT)
+ == BluetoothMapUtils.MAP_FEATURE_DATABASE_INDENTIFIER_BIT ) {
+ outAppParams.setDatabaseIdentifier(0, mMasInstance.getDbIdentifier());
+ }
+ if((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT)
+ == BluetoothMapUtils.MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT) {
+ // Force update of version counter if needed
+ mObserver.refreshFolderVersionCounter();
+ outAppParams.setFolderVerCounter(mMasInstance.getFolderVersionCounter(), 0);
+ }
outAppParams.setMseTime(Calendar.getInstance().getTime().getTime());
replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
op.sendHeaders(replyHeaders);
@@ -803,7 +1058,8 @@
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
} catch (IllegalArgumentException e) {
- Log.w(TAG,"sendMessageListingRsp: IllegalArgumentException - sending OBEX_HTTP_BAD_REQUEST", e);
+ Log.w(TAG,"sendMessageListingRsp: IllegalArgumentException"+
+ " - sending OBEX_HTTP_BAD_REQUEST", e);
if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
@@ -823,7 +1079,167 @@
if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
}
if(bytesWritten != outBytes.length && !mIsAborted) {
- Log.w(TAG,"sendMessageListingRsp: bytesWritten != outBytes.length - sending OBEX_HTTP_BAD_REQUEST");
+ Log.w(TAG,"sendMessageListingRsp: bytesWritten != outBytes.length" +
+ " - sending OBEX_HTTP_BAD_REQUEST");
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ } else {
+ if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
+ }
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ /**
+ * Update the {@link BluetoothMapAppParams} object message type filter mask to only contain
+ * message types supported by this mas instance.
+ * Could the folder be used in stead?
+ * @param appParams Reference to the object to update
+ * @param overwrite True: The msgType will be overwritten to match the message types supported
+ * by this MAS instance. False: any unsupported message types will be masked out.
+ */
+ private void setMsgTypeFilterParams(BluetoothMapAppParams appParams, boolean overwrite) {
+ int masFilterMask = 0;
+ if(!mEnableSmsMms) {
+ masFilterMask |= BluetoothMapAppParams.FILTER_NO_SMS_CDMA;
+ masFilterMask |= BluetoothMapAppParams.FILTER_NO_SMS_GSM;
+ masFilterMask |= BluetoothMapAppParams.FILTER_NO_MMS;
+ }
+ if(mAccount==null){
+ masFilterMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
+ masFilterMask |= BluetoothMapAppParams.FILTER_NO_IM;
+ } else {
+ if(!(mAccount.getType() == BluetoothMapUtils.TYPE.EMAIL)) {
+ masFilterMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
+ }
+ if(!(mAccount.getType() == BluetoothMapUtils.TYPE.IM)) {
+ masFilterMask |= BluetoothMapAppParams.FILTER_NO_IM;
+ }
+ }
+ if(overwrite) {
+ appParams.setFilterMessageType(masFilterMask);
+ } else {
+ int newMask = appParams.getFilterMessageType();
+ if(newMask == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
+ appParams.setFilterMessageType(newMask);
+ } else {
+ newMask |= masFilterMask;
+ appParams.setFilterMessageType(newMask);
+ }
+ }
+ }
+
+ /**
+ * Generate and send the Conversation listing response based on an application
+ * parameter header. This function call will block until complete or aborted
+ * by the peer. Fragmentation of packets larger than the obex packet size
+ * will be handled by this function.
+ *
+ * @param op
+ * The OBEX operation.
+ * @param appParams
+ * The application parameter header
+ * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
+ * {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
+ */
+ private int sendConvoListingRsp(Operation op,
+ BluetoothMapAppParams appParams,
+ String folderName){
+ OutputStream outStream = null;
+ byte[] outBytes = null;
+ int maxChunkSize, bytesToWrite, bytesWritten = 0;
+ //boolean hasUnread = false;
+ HeaderSet replyHeaders = new HeaderSet();
+ BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
+ BluetoothMapConvoListing outList;
+ if(appParams == null){
+ appParams = new BluetoothMapAppParams();
+ appParams.setMaxListCount(1024);
+ appParams.setStartOffset(0);
+ }
+ // As the app parameters do not carry which message types to list, we set the filter here
+ // to all message types supported by this instance.
+ setMsgTypeFilterParams(appParams, true);
+
+ // Check to see if we only need to send the size - hence no need to encode.
+ try {
+ // Open the OBEX body stream
+ outStream = op.openOutputStream();
+
+ if(appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+ appParams.setMaxListCount(1024);
+
+ if(appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+ appParams.setStartOffset(0);
+
+ if(appParams.getMaxListCount() != 0) {
+ outList = mOutContent.convoListing(appParams, false);
+ outAppParams.setConvoListingSize(outList.getCount());
+ // Generate the byte stream
+ outBytes = outList.encode(); // Include thread ID for clients that supports it.
+ // hasUnread = outList.hasUnread();
+ if(D) Log.d(TAG, "outBytes size:"+ outBytes.length);
+ } else {
+ outList = mOutContent.convoListing(appParams, true);
+ outAppParams.setConvoListingSize(outList.getCount());
+ if(mEnableSmsMms) {
+ mOutContent.refreshSmsMmsConvoVersions();
+ }
+ if(mAccount != null) {
+ mOutContent.refreshImEmailConvoVersions();
+ }
+ // Force update of version counter if needed
+ mObserver.refreshConvoListVersionCounter();
+ if(0 < (mRemoteFeatureMask &
+ BluetoothMapUtils.MAP_FEATURE_CONVERSATION_VERSION_COUNTER_BIT)) {
+ outAppParams.setConvoListingVerCounter(
+ mMasInstance.getCombinedConvoListVersionCounter(), 0);
+ }
+ op.noBodyHeader();
+ }
+ if(D) Log.d(TAG, "outList size:"+ outList.getCount()
+ + " MaxListCount: "+appParams.getMaxListCount());
+ outList = null; // We don't need it anymore - we might as well give it up for GC
+ outAppParams.setDatabaseIdentifier(0, mMasInstance.getDbIdentifier());
+
+ // Build the application parameter header
+ // The MseTime is not in the CR - but I think it is missing.
+ outAppParams.setMseTime(Calendar.getInstance().getTime().getTime());
+ replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
+ op.sendHeaders(replyHeaders);
+
+ } catch (IOException e) {
+ Log.w(TAG,"sendConvoListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
+ if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
+ if(mIsAborted == true) {
+ if(D) Log.d(TAG, "sendConvoListingRsp Operation Aborted");
+ return ResponseCodes.OBEX_HTTP_OK;
+ } else {
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG,"sendConvoListingRsp: IllegalArgumentException" +
+ " - sending OBEX_HTTP_BAD_REQUEST", e);
+ if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+
+ maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
+ if(outBytes != null) {
+ try {
+ while (bytesWritten < outBytes.length && mIsAborted == false) {
+ bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
+ outStream.write(outBytes, bytesWritten, bytesToWrite);
+ bytesWritten += bytesToWrite;
+ }
+ } catch (IOException e) {
+ if(D) Log.w(TAG,e);
+ // We were probably aborted or disconnected
+ } finally {
+ if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
+ }
+ if(bytesWritten != outBytes.length && !mIsAborted) {
+ Log.w(TAG,"sendConvoListingRsp: bytesWritten != outBytes.length" +
+ " - sending OBEX_HTTP_BAD_REQUEST");
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
} else {
@@ -882,7 +1298,8 @@
op.sendHeaders(replyHeaders);
} catch (IOException e1) {
- Log.w(TAG,"sendFolderListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
+ Log.w(TAG,"sendFolderListingRsp: IOException" +
+ " - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
if(mIsAborted == true) {
if(D) Log.d(TAG, "sendFolderListingRsp Operation Aborted");
@@ -891,7 +1308,8 @@
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
} catch (IllegalArgumentException e1) {
- Log.w(TAG,"sendFolderListingRsp: IllegalArgumentException - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
+ Log.w(TAG,"sendFolderListingRsp: IllegalArgumentException" +
+ " - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
}
@@ -911,7 +1329,7 @@
if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
}
if(V)
- Log.v(TAG,"sendFolderList sent " + bytesWritten + " bytes out of "+ outBytes.length);
+ Log.v(TAG,"sendFolderList sent " + bytesWritten+" bytes out of "+ outBytes.length);
if(bytesWritten == outBytes.length || mIsAborted)
return ResponseCodes.OBEX_HTTP_OK;
else
@@ -922,32 +1340,130 @@
}
/**
- * Generate and send the get message response based on an application
- * parameter header and a handle.
+ * Generate and send the get MAS Instance Information response based on an MAS Instance
*
* @param op
* The OBEX operation.
* @param appParams
* The application parameter header
- * @param handle
- * The handle of the requested message
* @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
* {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
*/
- private int sendGetMessageRsp(Operation op, String handle, BluetoothMapAppParams appParams){
+ private int sendMASInstanceInformationRsp(Operation op, BluetoothMapAppParams appParams){
+
+ OutputStream outStream = null;
+ byte[] outBytes = null;
+ String outString = null;
+ int maxChunkSize, bytesToWrite, bytesWritten = 0;
+
+ try {
+ if(mMasId == appParams.getMasInstanceId()) {
+ if(mAccount != null) {
+ if(mAccount.getType() == TYPE.EMAIL) {
+ outString = (mAccount.getName() != null) ? mAccount.getName() :
+ BluetoothMapMasInstance.TYPE_EMAIL_STR;
+ } else if(mAccount.getType() == TYPE.IM){
+ outString = mAccount.getUciFull();
+ if(outString == null) {
+ String uci = mAccount.getUci();
+ // TODO: Do we need to align this with HF/PBAP
+ StringBuilder sb =
+ new StringBuilder(uci == null ? 5 : 5 + uci.length());
+ sb.append("un");
+ if(mMasId < 10) {
+ sb.append("00");
+ } else if(mMasId < 100) {
+ sb.append("0");
+ }
+ sb.append(mMasId);
+ if(uci != null) {
+ sb.append(":").append(uci);
+ }
+ outString = sb.toString();
+ }
+ }
+ } else {
+ outString = BluetoothMapMasInstance.TYPE_SMS_MMS_STR;
+ // TODO: Add phone number if possible
+ }
+ } else {
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+
+ /* Ensure byte array max length is 200 containing valid UTF-8 characters */
+ outBytes = BluetoothMapUtils.truncateUtf8StringToBytearray(outString,
+ MAS_INSTANCE_INFORMATION_LENGTH);
+
+ // Open the OBEX body stream
+ outStream = op.openOutputStream();
+
+ } catch (IOException e) {
+ Log.w(TAG,"sendMASInstanceInformationRsp: IOException" +
+ " - sending OBEX_HTTP_BAD_REQUEST", e);
+ if(mIsAborted == true) {
+ if(D) Log.d(TAG, "sendMASInstanceInformationRsp Operation Aborted");
+ return ResponseCodes.OBEX_HTTP_OK;
+ } else {
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ }
+
+ maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
+
+ if(outBytes != null) {
+ try {
+ while (bytesWritten < outBytes.length && mIsAborted == false) {
+ bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
+ outStream.write(outBytes, bytesWritten, bytesToWrite);
+ bytesWritten += bytesToWrite;
+ }
+ } catch (IOException e) {
+ // We were probably aborted or disconnected
+ } finally {
+ if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
+ }
+ if(V)
+ Log.v(TAG,"sendMASInstanceInformationRsp sent " + bytesWritten +
+ " bytes out of "+ outBytes.length);
+ if(bytesWritten == outBytes.length || mIsAborted)
+ return ResponseCodes.OBEX_HTTP_OK;
+ else
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ /**
+ * Generate and send the get message response based on an application
+ * parameter header and a handle.
+ *
+ * @param op
+ * The OBEX operation.
+ * @param handle
+ * The handle of the requested message
+ * @param appParams
+ * The application parameter header
+ * @param version
+ * The string representation of the version number(i.e. "1.0")
+ * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
+ * {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
+ */
+ private int sendGetMessageRsp(Operation op, String handle,
+ BluetoothMapAppParams appParams, String version){
OutputStream outStream = null;
byte[] outBytes = null;
int maxChunkSize, bytesToWrite, bytesWritten = 0;
try {
- outBytes = mOutContent.getMessage(handle, appParams, mCurrentFolder);
+ outBytes = mOutContent.getMessage(handle, appParams, mCurrentFolder, version);
outStream = op.openOutputStream();
// If it is a fraction request of Email message, set header before responding
- if ((BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.EMAIL)) &&
+ if ((BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.EMAIL)||
+ (BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.IM))) &&
(appParams.getFractionRequest() ==
BluetoothMapAppParams.FRACTION_REQUEST_FIRST)) {
- BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();;
+ BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
HeaderSet replyHeaders = new HeaderSet();
outAppParams.setFractionDeliver(BluetoothMapAppParams.FRACTION_DELIVER_LAST);
// Build and set the application parameter header
@@ -1000,6 +1516,67 @@
return ResponseCodes.OBEX_HTTP_OK;
}
+ @Override
+ public int onDelete(HeaderSet request, HeaderSet reply) {
+ if(D) Log.v(TAG, "onDelete() " + request.toString());
+ mIsAborted = false;
+ notifyUpdateWakeLock();
+ String type, name;
+ byte[] appParamRaw;
+ BluetoothMapAppParams appParams = null;
+
+ /* TODO: If this is to be placed here, we need to cleanup - e.g. the exception handling */
+ try {
+ type = (String)request.getHeader(HeaderSet.TYPE);
+
+ name = (String)request.getHeader(HeaderSet.NAME);
+ appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
+ if(appParamRaw != null)
+ appParams = new BluetoothMapAppParams(appParamRaw);
+ if(D) Log.d(TAG,"type = " + type + ", name = " + name);
+ if(type.equals(TYPE_SET_NOTIFICATION_FILTER)) {
+ if(V) {
+ Log.d(TAG,"TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: "
+ + appParams.getNotificationFilter());
+ }
+ mObserver.setNotificationFilter(appParams.getNotificationFilter());
+ return ResponseCodes.OBEX_HTTP_OK;
+ } else if (type.equals(TYPE_SET_OWNER_STATUS)) {
+ if(V) {
+ Log.d(TAG,"TYPE_SET_OWNER_STATUS:" +
+ " PresenceAvailability " + appParams.getPresenceAvailability() +
+ ", PresenceStatus: " + appParams.getPresenceStatus() +
+ ", LastActivity: " + appParams.getLastActivityString() +
+ ", ChatStatus: " + appParams.getChatState() +
+ ", ChatStatusConvoId: " + appParams.getChatStateConvoIdString());
+ }
+ return setOwnerStatus(name, appParams);
+ }
+
+ } catch (RemoteException e){
+ //reload the providerClient and return error
+ try {
+ mProviderClient = acquireUnstableContentProviderOrThrow();
+ }catch (RemoteException e2){
+ //should not happen
+ }
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }catch (Exception e) {
+
+ if(D) {
+ Log.e(TAG, "Exception occured while handling request",e);
+ } else {
+ Log.e(TAG, "Exception occured while handling request");
+ }
+ if(mIsAborted) {
+ return ResponseCodes.OBEX_HTTP_OK;
+ } else {
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ }
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+
private void notifyUpdateWakeLock() {
if(mCallback != null) {
Message msg = Message.obtain(mCallback);
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
index 7e58b5b..29e08a5 100755
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -61,9 +61,9 @@
* DEBUG log: "setprop log.tag.BluetoothMapService VERBOSE"
*/
- public static final boolean DEBUG = true;
+ public static final boolean DEBUG = true; //FIXME set to false;
- public static final boolean VERBOSE = false;
+ public static final boolean VERBOSE = true; //FIXME set to false;
/**
* Intent indicating timeout for user confirmation, which is sent to
@@ -74,8 +74,8 @@
private static final int USER_CONFIRM_TIMEOUT_VALUE = 25000;
/** Intent indicating that the email settings activity should be opened*/
- public static final String ACTION_SHOW_MAPS_EMAIL_SETTINGS =
- "android.btmap.intent.action.SHOW_MAPS_EMAIL_SETTINGS";
+ public static final String ACTION_SHOW_MAPS_SETTINGS =
+ "android.btmap.intent.action.SHOW_MAPS_SETTINGS";
public static final int MSG_SERVERSESSION_CLOSE = 5000;
@@ -123,16 +123,16 @@
private SparseArray<BluetoothMapMasInstance> mMasInstances =
new SparseArray<BluetoothMapMasInstance>(1);
/* mMasInstanceMap: A list of the active MasInstances with the key being the account */
- private HashMap<BluetoothMapEmailSettingsItem, BluetoothMapMasInstance> mMasInstanceMap =
- new HashMap<BluetoothMapEmailSettingsItem, BluetoothMapMasInstance>(1);
+ private HashMap<BluetoothMapAccountItem, BluetoothMapMasInstance> mMasInstanceMap =
+ new HashMap<BluetoothMapAccountItem, BluetoothMapMasInstance>(1);
private BluetoothDevice mRemoteDevice = null; // The remote connected device - protect access
- private ArrayList<BluetoothMapEmailSettingsItem> mEnabledAccounts = null;
+ private ArrayList<BluetoothMapAccountItem> mEnabledAccounts = null;
private static String sRemoteDeviceName = null;
private int mState;
- private BluetoothMapEmailAppObserver mAppObserver = null;
+ private BluetoothMapAppObserver mAppObserver = null;
private AlarmManager mAlarmManager = null;
private boolean mIsWaitingAuthorization = false;
@@ -157,6 +157,7 @@
}
+
private final void closeService() {
if (DEBUG) Log.d(TAG, "MAP Service closeService in");
@@ -397,7 +398,8 @@
};
private void onConnectHandler(int masId) {
- if (mIsWaitingAuthorization == true || mRemoteDevice == null) {
+ if (mIsWaitingAuthorization == true || mRemoteDevice == null
+ || mSdpSearchInitiated == true) {
return;
}
BluetoothMapMasInstance masInst = mMasInstances.get(masId);
@@ -546,7 +548,7 @@
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
- filter.addAction(ACTION_SHOW_MAPS_EMAIL_SETTINGS);
+ filter.addAction(ACTION_SHOW_MAPS_SETTINGS);
filter.addAction(USER_CONFIRM_TIMEOUT_ACTION);
// We need two filters, since Type only applies to the ACTION_MESSAGE_SENT
@@ -565,7 +567,7 @@
Log.w(TAG,"Unable to register map receiver",e);
}
mAdapter = BluetoothAdapter.getDefaultAdapter();
- mAppObserver = new BluetoothMapEmailAppObserver(this, this);
+ mAppObserver = new BluetoothMapAppObserver(this, this);
mEnabledAccounts = mAppObserver.getEnabledAccountItems();
// Uses mEnabledAccounts, hence getEnabledAccountItems() must be called before this.
@@ -610,14 +612,14 @@
boolean changed = false;
if(getState() == BluetoothMap.STATE_DISCONNECTED) {
- ArrayList<BluetoothMapEmailSettingsItem> newAccountList =
+ ArrayList<BluetoothMapAccountItem> newAccountList =
mAppObserver.getEnabledAccountItems();
- ArrayList<BluetoothMapEmailSettingsItem> newAccounts = null;
- ArrayList<BluetoothMapEmailSettingsItem> removedAccounts = null;
- newAccounts = new ArrayList<BluetoothMapEmailSettingsItem>();
+ ArrayList<BluetoothMapAccountItem> newAccounts = null;
+ ArrayList<BluetoothMapAccountItem> removedAccounts = null;
+ newAccounts = new ArrayList<BluetoothMapAccountItem>();
removedAccounts = mEnabledAccounts; // reuse the current enabled list, to track removed
// accounts
- for(BluetoothMapEmailSettingsItem account: newAccountList) {
+ for(BluetoothMapAccountItem account: newAccountList) {
if(!removedAccounts.remove(account)) {
newAccounts.add(account);
}
@@ -625,7 +627,7 @@
if(removedAccounts != null) {
/* Remove all disabled/removed accounts */
- for(BluetoothMapEmailSettingsItem account : removedAccounts) {
+ for(BluetoothMapAccountItem account : removedAccounts) {
BluetoothMapMasInstance masInst = mMasInstanceMap.remove(account);
if(DEBUG)Log.d(TAG," Removing account: " + account + " masInst = " + masInst);
if(masInst != null) {
@@ -638,7 +640,7 @@
if(newAccounts != null) {
/* Add any newly created accounts */
- for(BluetoothMapEmailSettingsItem account : newAccounts) {
+ for(BluetoothMapAccountItem account : newAccounts) {
if(DEBUG)Log.d(TAG," Adding account: " + account);
int masId = getNextMasId();
BluetoothMapMasInstance newInst =
@@ -659,7 +661,7 @@
mEnabledAccounts = newAccountList;
if(VERBOSE) {
Log.d(TAG," Enabled accounts:");
- for(BluetoothMapEmailSettingsItem account : mEnabledAccounts) {
+ for(BluetoothMapAccountItem account : mEnabledAccounts) {
Log.d(TAG, " " + account);
}
Log.d(TAG," Active MAS instances:");
@@ -718,7 +720,7 @@
mMasInstanceMap.put(null, smsMmsInst);
// get list of accounts already set to be visible through MAP
- for(BluetoothMapEmailSettingsItem account : mEnabledAccounts) {
+ for(BluetoothMapAccountItem account : mEnabledAccounts) {
masId++; // SMS/MMS is masId=0, increment before adding next
BluetoothMapMasInstance newInst =
new BluetoothMapMasInstance(this,
@@ -781,6 +783,9 @@
setUserTimeoutAlarm();
} else if (mPermission == BluetoothDevice.ACCESS_REJECTED) {
cancelConnection = true;
+ } else if(mPermission == BluetoothDevice.ACCESS_ALLOWED) {
+ mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
+ mSdpSearchInitiated = true;
}
} else if (!mRemoteDevice.equals(remoteDevice)) {
Log.w(TAG, "Unexpected connection from a second Remote Device received. name: " +
@@ -894,6 +899,7 @@
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.d(TAG, "onReceive");
String action = intent.getAction();
+ if (DEBUG) Log.d(TAG, "onReceive: " + action);
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR);
@@ -944,6 +950,7 @@
+ result);
}
}
+
mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
mSdpSearchInitiated = true;
} else {
@@ -961,7 +968,7 @@
sendConnectCancelMessage();
}
} else if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)){
- Log.v(TAG, "Received ACTION_SDP_RECORD.");
+// Log.v(TAG, "Received ACTION_SDP_RECORD.");
ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
Log.v(TAG, "Received UUID: " + uuid.toString());
Log.v(TAG, "expected UUID: " +
@@ -982,10 +989,10 @@
}
sendConnectMessage(-1); // -1 indicates all MAS instances
}
- } else if (action.equals(ACTION_SHOW_MAPS_EMAIL_SETTINGS)) {
- Log.v(TAG, "Received ACTION_SHOW_MAPS_EMAIL_SETTINGS.");
+ } else if (action.equals(ACTION_SHOW_MAPS_SETTINGS)) {
+ Log.v(TAG, "Received ACTION_SHOW_MAPS_SETTINGS.");
- Intent in = new Intent(context, BluetoothMapEmailSettings.class);
+ Intent in = new Intent(context, BluetoothMapSettings.class);
in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(in);
} else if (action.equals(BluetoothMapContentObserver.ACTION_MESSAGE_SENT)) {
@@ -1151,11 +1158,11 @@
println(sb, "mAccountChanged: " + mAccountChanged);
println(sb, "mBluetoothMnsObexClient: " + mBluetoothMnsObexClient);
println(sb, "mMasInstanceMap:");
- for (BluetoothMapEmailSettingsItem key : mMasInstanceMap.keySet()) {
+ for (BluetoothMapAccountItem key : mMasInstanceMap.keySet()) {
println(sb, " " + key + " : " + mMasInstanceMap.get(key));
}
println(sb, "mEnabledAccounts:");
- for (BluetoothMapEmailSettingsItem account : mEnabledAccounts) {
+ for (BluetoothMapAccountItem account : mEnabledAccounts) {
println(sb, " " + account);
}
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapEmailSettings.java b/src/com/android/bluetooth/map/BluetoothMapSettings.java
similarity index 63%
rename from src/com/android/bluetooth/map/BluetoothMapEmailSettings.java
rename to src/com/android/bluetooth/map/BluetoothMapSettings.java
index ae9cc32..e02552f 100644
--- a/src/com/android/bluetooth/map/BluetoothMapEmailSettings.java
+++ b/src/com/android/bluetooth/map/BluetoothMapSettings.java
@@ -1,5 +1,5 @@
/*
-* Copyright (C) 2014 Samsung System LSI
+* Copyright (C) 2015 Samsung System LSI
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -15,7 +15,7 @@
package com.android.bluetooth.map;
import com.android.bluetooth.R;
-import com.android.bluetooth.map.BluetoothMapEmailSettingsItem;
+import com.android.bluetooth.map.BluetoothMapAccountItem;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -25,30 +25,32 @@
import android.widget.ExpandableListView;
-public class BluetoothMapEmailSettings extends Activity {
+public class BluetoothMapSettings extends Activity {
- private static final String TAG = "BluetoothMapEmailSettings";
+ private static final String TAG = "BluetoothMapSettings";
private static final boolean D = BluetoothMapService.DEBUG;
private static final boolean V = BluetoothMapService.VERBOSE;
- BluetoothMapEmailSettingsLoader mLoader = new BluetoothMapEmailSettingsLoader(this);
- LinkedHashMap<BluetoothMapEmailSettingsItem,ArrayList<BluetoothMapEmailSettingsItem>> mGroups;
+ BluetoothMapAccountLoader mLoader = new BluetoothMapAccountLoader(this);
+ LinkedHashMap<BluetoothMapAccountItem,ArrayList<BluetoothMapAccountItem>> mGroups;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/* set UI */
- setContentView(R.layout.bluetooth_map_email_settings);
+ setContentView(R.layout.bluetooth_map_settings);
/* create structure for list of groups + items*/
mGroups = mLoader.parsePackages(true);
/* update expandable listview with correct items */
- ExpandableListView listView = (ExpandableListView) findViewById(R.id.bluetooth_map_email_settings_list_view);
+ ExpandableListView listView =
+ (ExpandableListView) findViewById(R.id.bluetooth_map_settings_list_view);
- BluetoothMapEmailSettingsAdapter adapter = new BluetoothMapEmailSettingsAdapter(this,listView, mGroups, mLoader.getAccountsEnabledCount());
+ BluetoothMapSettingsAdapter adapter = new BluetoothMapSettingsAdapter(this,
+ listView, mGroups, mLoader.getAccountsEnabledCount());
listView.setAdapter(adapter);
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsAdapter.java b/src/com/android/bluetooth/map/BluetoothMapSettingsAdapter.java
similarity index 70%
rename from src/com/android/bluetooth/map/BluetoothMapEmailSettingsAdapter.java
rename to src/com/android/bluetooth/map/BluetoothMapSettingsAdapter.java
index feca9c0..2a751f2 100644
--- a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsAdapter.java
+++ b/src/com/android/bluetooth/map/BluetoothMapSettingsAdapter.java
@@ -45,29 +45,29 @@
import android.widget.TextView;
import android.widget.Toast;
import android.widget.CompoundButton;
-import com.android.bluetooth.map.BluetoothMapEmailSettingsItem;
-import com.android.bluetooth.map.BluetoothMapEmailSettingsLoader;
-public class BluetoothMapEmailSettingsAdapter extends BaseExpandableListAdapter {
+import com.android.bluetooth.map.BluetoothMapAccountItem;
+import com.android.bluetooth.map.BluetoothMapAccountLoader;
+public class BluetoothMapSettingsAdapter extends BaseExpandableListAdapter {
private static final boolean D = BluetoothMapService.DEBUG;
private static final boolean V = BluetoothMapService.VERBOSE;
- private static final String TAG = "BluetoothMapEmailSettingsAdapter";
+ private static final String TAG = "BluetoothMapSettingsAdapter";
private boolean mCheckAll = true;
public LayoutInflater mInflater;
public Activity mActivity;
/*needed to prevent random checkbox toggles due to item reuse */
ArrayList<Boolean> mPositionArray;
- private LinkedHashMap<BluetoothMapEmailSettingsItem,
- ArrayList<BluetoothMapEmailSettingsItem>> mProupList;
- private ArrayList<BluetoothMapEmailSettingsItem> mMainGroup;
+ private LinkedHashMap<BluetoothMapAccountItem,
+ ArrayList<BluetoothMapAccountItem>> mProupList;
+ private ArrayList<BluetoothMapAccountItem> mMainGroup;
private int[] mGroupStatus;
/* number of accounts possible to share */
private int mSlotsLeft = 10;
- public BluetoothMapEmailSettingsAdapter(Activity act,
+ public BluetoothMapSettingsAdapter(Activity act,
ExpandableListView listView,
- LinkedHashMap<BluetoothMapEmailSettingsItem,
- ArrayList<BluetoothMapEmailSettingsItem>> groupsList,
+ LinkedHashMap<BluetoothMapAccountItem,
+ ArrayList<BluetoothMapAccountItem>> groupsList,
int enabledAccountsCounts) {
mActivity = act;
this.mProupList = groupsList;
@@ -78,24 +78,25 @@
listView.setOnGroupExpandListener(new OnGroupExpandListener() {
public void onGroupExpand(int groupPosition) {
- BluetoothMapEmailSettingsItem group = mMainGroup.get(groupPosition);
+ BluetoothMapAccountItem group = mMainGroup.get(groupPosition);
if (mProupList.get(group).size() > 0)
mGroupStatus[groupPosition] = 1;
}
});
- mMainGroup = new ArrayList<BluetoothMapEmailSettingsItem>();
- for (Map.Entry<BluetoothMapEmailSettingsItem, ArrayList<BluetoothMapEmailSettingsItem>> mapEntry : mProupList.entrySet()) {
+ mMainGroup = new ArrayList<BluetoothMapAccountItem>();
+ for (Map.Entry<BluetoothMapAccountItem,
+ ArrayList<BluetoothMapAccountItem>> mapEntry : mProupList.entrySet()) {
mMainGroup.add(mapEntry.getKey());
}
}
@Override
- public BluetoothMapEmailSettingsItem getChild(int groupPosition, int childPosition) {
- BluetoothMapEmailSettingsItem item = mMainGroup.get(groupPosition);
+ public BluetoothMapAccountItem getChild(int groupPosition, int childPosition) {
+ BluetoothMapAccountItem item = mMainGroup.get(groupPosition);
return mProupList.get(item).get(childPosition);
}
- private ArrayList<BluetoothMapEmailSettingsItem> getChild(BluetoothMapEmailSettingsItem group) {
+ private ArrayList<BluetoothMapAccountItem> getChild(BluetoothMapAccountItem group) {
return mProupList.get(group);
}
@@ -111,49 +112,53 @@
final ChildHolder holder;
if (convertView == null) {
- convertView = mInflater.inflate(R.layout.bluetooth_map_email_settings_account_item, null);
+ convertView = mInflater.inflate(R.layout.bluetooth_map_settings_account_item, null);
holder = new ChildHolder();
- holder.cb = (CheckBox) convertView.findViewById(R.id.bluetooth_map_email_settings_item_check);
- holder.title = (TextView) convertView.findViewById(R.id.bluetooth_map_email_settings_item_text_view);
+ holder.cb = (CheckBox) convertView.findViewById(R.id.bluetooth_map_settings_item_check);
+ holder.title =
+ (TextView) convertView.findViewById(R.id.bluetooth_map_settings_item_text_view);
convertView.setTag(holder);
} else {
holder = (ChildHolder) convertView.getTag();
}
- final BluetoothMapEmailSettingsItem child = getChild(groupPosition, childPosition);
+ final BluetoothMapAccountItem child = getChild(groupPosition, childPosition);
holder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
- BluetoothMapEmailSettingsItem parentGroup = (BluetoothMapEmailSettingsItem)getGroup(groupPosition);
+ BluetoothMapAccountItem parentGroup =
+ (BluetoothMapAccountItem)getGroup(groupPosition);
boolean oldIsChecked = child.mIsChecked; // needed to prevent updates on UI redraw
child.mIsChecked = isChecked;
if (isChecked) {
- ArrayList<BluetoothMapEmailSettingsItem> childList = getChild(parentGroup);
+ ArrayList<BluetoothMapAccountItem> childList = getChild(parentGroup);
int childIndex = childList.indexOf(child);
boolean isAllChildClicked = true;
if(mSlotsLeft-childList.size() >=0){
for (int i = 0; i < childList.size(); i++) {
if (i != childIndex) {
- BluetoothMapEmailSettingsItem siblings = childList.get(i);
+ BluetoothMapAccountItem siblings = childList.get(i);
if (!siblings.mIsChecked) {
isAllChildClicked = false;
- BluetoothMapEmailSettingsDataHolder.mCheckedChilds.put(child.getName(),
- parentGroup.getName());
+ BluetoothMapSettingsDataHolder.mCheckedChilds.put(
+ child.getName(), parentGroup.getName());
break;
}
}
}
}else {
- showWarning(mActivity.getString(R.string.bluetooth_map_email_settings_no_account_slots_left));
+ showWarning(mActivity.getString(
+ R.string.bluetooth_map_settings_no_account_slots_left));
isAllChildClicked = false;
child.mIsChecked = false;
}
if (isAllChildClicked) {
parentGroup.mIsChecked = true;
- if(!(BluetoothMapEmailSettingsDataHolder.mCheckedChilds.containsKey(child.getName())==true)){
- BluetoothMapEmailSettingsDataHolder.mCheckedChilds.put(child.getName(),
+ if(!(BluetoothMapSettingsDataHolder.mCheckedChilds.containsKey(
+ child.getName())==true)){
+ BluetoothMapSettingsDataHolder.mCheckedChilds.put(child.getName(),
parentGroup.getName());
}
mCheckAll = false;
@@ -164,10 +169,10 @@
if (parentGroup.mIsChecked) {
parentGroup.mIsChecked = false;
mCheckAll = false;
- BluetoothMapEmailSettingsDataHolder.mCheckedChilds.remove(child.getName());
+ BluetoothMapSettingsDataHolder.mCheckedChilds.remove(child.getName());
} else {
mCheckAll = true;
- BluetoothMapEmailSettingsDataHolder.mCheckedChilds.remove(child.getName());
+ BluetoothMapSettingsDataHolder.mCheckedChilds.remove(child.getName());
}
// child.isChecked =false;
}
@@ -182,7 +187,7 @@
holder.cb.setChecked(child.mIsChecked);
holder.title.setText(child.getName());
- if(D)Log.i("childs are", BluetoothMapEmailSettingsDataHolder.mCheckedChilds.toString());
+ if(D)Log.i("childs are", BluetoothMapSettingsDataHolder.mCheckedChilds.toString());
return convertView;
}
@@ -191,12 +196,12 @@
@Override
public int getChildrenCount(int groupPosition) {
- BluetoothMapEmailSettingsItem item = mMainGroup.get(groupPosition);
+ BluetoothMapAccountItem item = mMainGroup.get(groupPosition);
return mProupList.get(item).size();
}
@Override
- public BluetoothMapEmailSettingsItem getGroup(int groupPosition) {
+ public BluetoothMapAccountItem getGroup(int groupPosition) {
return mMainGroup.get(groupPosition);
}
@@ -227,18 +232,20 @@
final GroupHolder holder;
if (convertView == null) {
- convertView = mInflater.inflate(R.layout.bluetooth_map_email_settings_account_group, null);
+ convertView = mInflater.inflate(R.layout.bluetooth_map_settings_account_group, null);
holder = new GroupHolder();
- holder.cb = (CheckBox) convertView.findViewById(R.id.bluetooth_map_email_settings_group_checkbox);
+ holder.cb =
+ (CheckBox) convertView.findViewById(R.id.bluetooth_map_settings_group_checkbox);
holder.imageView = (ImageView) convertView
- .findViewById(R.id.bluetooth_map_email_settings_group_icon);
- holder.title = (TextView) convertView.findViewById(R.id.bluetooth_map_email_settings_group_text_view);
+ .findViewById(R.id.bluetooth_map_settings_group_icon);
+ holder.title =
+ (TextView) convertView.findViewById(R.id.bluetooth_map_settings_group_text_view);
convertView.setTag(holder);
} else {
holder = (GroupHolder) convertView.getTag();
}
- final BluetoothMapEmailSettingsItem groupItem = getGroup(groupPosition);
+ final BluetoothMapAccountItem groupItem = getGroup(groupPosition);
holder.imageView.setImageDrawable(groupItem.getIcon());
@@ -249,8 +256,8 @@
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if (mCheckAll) {
- ArrayList<BluetoothMapEmailSettingsItem> childItem = getChild(groupItem);
- for (BluetoothMapEmailSettingsItem children : childItem)
+ ArrayList<BluetoothMapAccountItem> childItem = getChild(groupItem);
+ for (BluetoothMapAccountItem children : childItem)
{
boolean oldIsChecked = children.mIsChecked;
if(mSlotsLeft >0){
@@ -259,7 +266,8 @@
updateAccount(children);
}
}else {
- showWarning(mActivity.getString(R.string.bluetooth_map_email_settings_no_account_slots_left));
+ showWarning(mActivity.getString(
+ R.string.bluetooth_map_settings_no_account_slots_left));
isChecked = false;
}
}
@@ -303,13 +311,14 @@
public TextView title;
public CheckBox cb;
}
- public void updateAccount(BluetoothMapEmailSettingsItem account) {
+ public void updateAccount(BluetoothMapAccountItem account) {
updateSlotCounter(account.mIsChecked);
- if(D)Log.d(TAG,"Updating account settings for "+account.getName() +". Value is:"+account.mIsChecked);
+ if(D)Log.d(TAG,"Updating account settings for "
+ +account.getName() +". Value is:"+account.mIsChecked);
ContentResolver mResolver = mActivity.getContentResolver();
Uri uri = Uri.parse(account.mBase_uri_no_account+"/"+BluetoothMapContract.TABLE_ACCOUNT);
ContentValues values = new ContentValues();
- values.put(BluetoothMapContract.AccountColumns.FLAG_EXPOSE, ((account.mIsChecked)?1:0)); // get title
+ values.put(BluetoothMapContract.AccountColumns.FLAG_EXPOSE, ((account.mIsChecked)?1:0));
values.put(BluetoothMapContract.AccountColumns._ID, account.getId()); // get title
mResolver.update(uri, values, null ,null);
@@ -325,9 +334,10 @@
if (mSlotsLeft <=0)
{
- text = mActivity.getString(R.string.bluetooth_map_email_settings_no_account_slots_left);
+ text = mActivity.getString(R.string.bluetooth_map_settings_no_account_slots_left);
}else {
- text= mActivity.getString(R.string.bluetooth_map_email_settings_count) + " "+ String.valueOf(mSlotsLeft);
+ text= mActivity.getString(R.string.bluetooth_map_settings_count)
+ + " "+ String.valueOf(mSlotsLeft);
}
int duration = Toast.LENGTH_SHORT;
diff --git a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsDataHolder.java b/src/com/android/bluetooth/map/BluetoothMapSettingsDataHolder.java
similarity index 93%
rename from src/com/android/bluetooth/map/BluetoothMapEmailSettingsDataHolder.java
rename to src/com/android/bluetooth/map/BluetoothMapSettingsDataHolder.java
index 104e2af..e33d705 100644
--- a/src/com/android/bluetooth/map/BluetoothMapEmailSettingsDataHolder.java
+++ b/src/com/android/bluetooth/map/BluetoothMapSettingsDataHolder.java
@@ -17,7 +17,7 @@
import java.util.HashMap;
-public class BluetoothMapEmailSettingsDataHolder {
+public class BluetoothMapSettingsDataHolder {
public static HashMap<String, String> mCheckedChilds = new HashMap<String, String>();
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapUtils.java b/src/com/android/bluetooth/map/BluetoothMapUtils.java
index fb1e7e8..3e3ace1 100644
--- a/src/com/android/bluetooth/map/BluetoothMapUtils.java
+++ b/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -14,15 +14,30 @@
*/
package com.android.bluetooth.map;
+import android.database.Cursor;
+import android.util.Base64;
import android.util.Log;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Date;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
/**
* Various utility methods and generic defines that can be used throughout MAPS
*/
public class BluetoothMapUtils {
- private static final String TAG = "MapUtils";
+ private static final String TAG = "BluetoothMapUtils";
private static final boolean D = BluetoothMapService.DEBUG;
private static final boolean V = BluetoothMapService.VERBOSE;
/* We use the upper 4 bits for the type mask.
@@ -30,24 +45,96 @@
* in stead of a bit to indicate the message type. Then 4
* bit can be use for 16 different message types.
*/
- private static final long HANDLE_TYPE_MASK = (((long)0xf)<<56);
- private static final long HANDLE_TYPE_MMS_MASK = (((long)0x1)<<56);
- private static final long HANDLE_TYPE_EMAIL_MASK = (((long)0x2)<<56);
- private static final long HANDLE_TYPE_SMS_GSM_MASK = (((long)0x4)<<56);
- private static final long HANDLE_TYPE_SMS_CDMA_MASK = (((long)0x8)<<56);
+ private static final long HANDLE_TYPE_MASK = (((long)0xff)<<56);
+ private static final long HANDLE_TYPE_MMS_MASK = (((long)0x01)<<56);
+ private static final long HANDLE_TYPE_EMAIL_MASK = (((long)0x02)<<56);
+ private static final long HANDLE_TYPE_SMS_GSM_MASK = (((long)0x04)<<56);
+ private static final long HANDLE_TYPE_SMS_CDMA_MASK = (((long)0x08)<<56);
+ private static final long HANDLE_TYPE_IM_MASK = (((long)0x10)<<56);
+ public static final long CONVO_ID_TYPE_SMS_MMS = 1;
+ public static final long CONVO_ID_TYPE_EMAIL_IM= 2;
- static final int MAP_FEATURE_DEFAULT_BITMASK = 0x0000001F;
+ // MAP supported feature bit - included from MAP Spec 1.2
+ static final int MAP_FEATURE_DEFAULT_BITMASK = 0x0000001F;
+
+ static final int MAP_FEATURE_NOTIFICATION_REGISTRATION_BIT = 1 << 0;
+ static final int MAP_FEATURE_NOTIFICATION_BIT = 1 << 1;
+ static final int MAP_FEATURE_BROWSING_BIT = 1 << 2;
+ static final int MAP_FEATURE_UPLOADING_BIT = 1 << 3;
+ static final int MAP_FEATURE_DELETE_BIT = 1 << 4;
+ static final int MAP_FEATURE_INSTANCE_INFORMATION_BIT = 1 << 5;
+ static final int MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT = 1 << 6;
+ static final int MAP_FEATURE_EVENT_REPORT_V12_BIT = 1 << 7;
+ static final int MAP_FEATURE_MESSAGE_FORMAT_V11_BIT = 1 << 8;
+ static final int MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT = 1 << 9;
+ static final int MAP_FEATURE_PERSISTENT_MESSAGE_HANDLE_BIT = 1 << 10;
+ static final int MAP_FEATURE_DATABASE_INDENTIFIER_BIT = 1 << 11;
+ static final int MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT = 1 << 12;
+ static final int MAP_FEATURE_CONVERSATION_VERSION_COUNTER_BIT = 1 << 13;
+ static final int MAP_FEATURE_PARTICIPANT_PRESENCE_CHANGE_BIT = 1 << 14;
+ static final int MAP_FEATURE_PARTICIPANT_CHAT_STATE_CHANGE_BIT = 1 << 15;
+
+ static final int MAP_FEATURE_PBAP_CONTACT_CROSS_REFERENCE_BIT = 1 << 16;
+ static final int MAP_FEATURE_NOTIFICATION_FILTERING_BIT = 1 << 17;
+ static final int MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT = 1 << 18;
+
+ static final String MAP_V10_STR = "1.0";
+ static final String MAP_V11_STR = "1.1";
+ static final String MAP_V12_STR = "1.2";
+
+ // Event Report versions
+ static final int MAP_EVENT_REPORT_V10 = 10; // MAP spec 1.1
+ static final int MAP_EVENT_REPORT_V11 = 11; // MAP spec 1.2
+ static final int MAP_EVENT_REPORT_V12 = 12; // MAP spec 1.3 'to be' incl. IM
+
+ // Message Format versions
+ static final int MAP_MESSAGE_FORMAT_V10 = 10; // MAP spec below 1.3
+ static final int MAP_MESSAGE_FORMAT_V11 = 11; // MAP spec 1.3
+
+ // Message Listing Format versions
+ static final int MAP_MESSAGE_LISTING_FORMAT_V10 = 10; // MAP spec below 1.3
+ static final int MAP_MESSAGE_LISTING_FORMAT_V11 = 11; // MAP spec 1.3
/**
* This enum is used to convert from the bMessage type property to a type safe
* type. Hence do not change the names of the enum values.
*/
public enum TYPE{
+ NONE,
EMAIL,
SMS_GSM,
SMS_CDMA,
- MMS
+ MMS,
+ IM
+ }
+
+ static public String getDateTimeString(long timestamp) {
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+ Date date = new Date(timestamp);
+ return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+ }
+
+
+ public static void printCursor(Cursor c) {
+ if (D) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("\nprintCursor:\n");
+ for(int i = 0; i < c.getColumnCount(); i++) {
+ if(c.getColumnName(i).equals(BluetoothMapContract.MessageColumns.DATE) ||
+ c.getColumnName(i).equals(
+ BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY) ||
+ c.getColumnName(i).equals(BluetoothMapContract.ChatStatusColumns.LAST_ACTIVE) ||
+ c.getColumnName(i).equals(BluetoothMapContract.PresenceColumns.LAST_ONLINE) ){
+ sb.append(" ").append(c.getColumnName(i)).append(" : ").append(
+ getDateTimeString(c.getLong(i))).append("\n");
+ } else {
+ sb.append(" ").append(c.getColumnName(i)).append(" : ").append(
+ c.getString(i)).append("\n");
+ }
+ }
+ Log.d(TAG, sb.toString());
+ }
}
public static String getLongAsString(long v) {
@@ -69,6 +156,88 @@
}
/**
+ * Converts a hex-string to a long - please mind that Java has no unsigned data types, hence
+ * any value passed to this function, which has the upper bit set, will return a negative value.
+ * The bitwise content of the variable will however be the same.
+ * Will ignore any white-space characters as well as '-' seperators
+ * @param valueStr a hexstring - NOTE: shall not contain any "0x" prefix.
+ * @return
+ * @throws UnsupportedEncodingException if "US-ASCII" charset is not supported,
+ * NullPointerException if a null pointer is passed to the function,
+ * NumberFormatException if the string contains invalid characters.
+ *
+ */
+ public static long getLongFromString(String valueStr) throws UnsupportedEncodingException {
+ if(valueStr == null) throw new NullPointerException();
+ if(V) Log.i(TAG, "getLongFromString(): converting: " + valueStr);
+ byte[] nibbles;
+ nibbles = valueStr.getBytes("US-ASCII");
+ if(V) Log.i(TAG, " byte values: " + Arrays.toString(nibbles));
+ byte c;
+ int count = 0;
+ int length = nibbles.length;
+ long value = 0;
+ for(int i = 0; i != length; i++) {
+ c = nibbles[i];
+ if(c >= '0' && c <= '9') {
+ c -= '0';
+ } else if(c >= 'A' && c <= 'F') {
+ c -= ('A'-10);
+ } else if(c >= 'a' && c <= 'f') {
+ c -= ('a'-10);
+ } else if(c <= ' ' || c == '-') {
+ if(V)Log.v(TAG, "Skipping c = '" + new String(new byte[]{ (byte)c }, "US-ASCII")
+ + "'");
+ continue; // Skip any whitespace and '-' (which is used for UUIDs)
+ } else {
+ throw new NumberFormatException("Invalid character:" + c);
+ }
+ value = value << 4; // The last nibble shall not be shifted
+ value += c;
+ count++;
+ if(count > 16) throw new NullPointerException("String to large - count: " + count);
+ }
+ if(V) Log.i(TAG, " length: " + count);
+ return value;
+ }
+ private static final int LONG_LONG_LENGTH = 32;
+ public static String getLongLongAsString(long vLow, long vHigh) {
+ char[] result = new char[LONG_LONG_LENGTH];
+ int v1 = (int) (vLow & 0xffffffff);
+ int v2 = (int) ((vLow>>32) & 0xffffffff);
+ int v3 = (int) (vHigh & 0xffffffff);
+ int v4 = (int) ((vHigh>>32) & 0xffffffff);
+ int c,d,i;
+ // Handle the lower bytes
+ for (i = 0; i < 8; i++) {
+ c = v2 & 0x0f;
+ c += (c < 10) ? '0' : ('A'-10);
+ d = v4 & 0x0f;
+ d += (d < 10) ? '0' : ('A'-10);
+ result[23 - i] = (char) c;
+ result[7 - i] = (char) d;
+ v2 >>= 4;
+ v4 >>= 4;
+ c = v1 & 0x0f;
+ c += (c < 10) ? '0' : ('A'-10);
+ d = v3 & 0x0f;
+ d += (d < 10) ? '0' : ('A'-10);
+ result[31 - i] = (char)c;
+ result[15 - i] = (char)d;
+ v1 >>= 4;
+ v3 >>= 4;
+ }
+ // Remove any leading 0's
+ for(i = 0; i < LONG_LONG_LENGTH; i++) {
+ if(result[i] != '0') {
+ break;
+ }
+ }
+ return new String(result, i, LONG_LONG_LENGTH-i);
+ }
+
+
+ /**
* Convert a Content Provider handle and a Messagetype into a unique handle
* @param cpHandle content provider handle
* @param messageType message type (TYPE_MMS/TYPE_SMS_GSM/TYPE_SMS_CDMA/TYPE_EMAIL)
@@ -78,7 +247,6 @@
String mapHandle = "-1";
switch(messageType)
{
-
case MMS:
mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_MMS_MASK);
break;
@@ -91,8 +259,37 @@
case EMAIL:
mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_EMAIL_MASK);
break;
- default:
- throw new IllegalArgumentException("Message type not supported");
+ case IM:
+ mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_IM_MASK);
+ break;
+ default:
+ throw new IllegalArgumentException("Message type not supported");
+ }
+ return mapHandle;
+
+ }
+
+ /**
+ * Convert a Content Provider handle and a Messagetype into a unique handle
+ * @param cpHandle content provider handle
+ * @param messageType message type (TYPE_MMS/TYPE_SMS_GSM/TYPE_SMS_CDMA/TYPE_EMAIL)
+ * @return String Formatted Map Handle
+ */
+ public static String getMapConvoHandle(long cpHandle, TYPE messageType){
+ String mapHandle = "-1";
+ switch(messageType)
+ {
+ case MMS:
+ case SMS_GSM:
+ case SMS_CDMA:
+ mapHandle = getLongLongAsString(cpHandle, CONVO_ID_TYPE_SMS_MMS);
+ break;
+ case EMAIL:
+ case IM:
+ mapHandle = getLongLongAsString(cpHandle, CONVO_ID_TYPE_EMAIL_IM);
+ break;
+ default:
+ throw new IllegalArgumentException("Message type not supported");
}
return mapHandle;
@@ -138,8 +335,281 @@
return TYPE.SMS_GSM;
if((cpHandle & HANDLE_TYPE_SMS_CDMA_MASK) != 0)
return TYPE.SMS_CDMA;
+ if((cpHandle & HANDLE_TYPE_IM_MASK) != 0)
+ return TYPE.IM;
throw new IllegalArgumentException("Message type not found in handle string.");
}
+
+ /**
+ * TODO: Is this still needed after changing to another XML encoder? It should escape illegal
+ * characters.
+ * Strip away any illegal XML characters, that would otherwise cause the
+ * xml serializer to throw an exception.
+ * Examples of such characters are the emojis used on Android.
+ * @param text The string to validate
+ * @return the same string if valid, otherwise a new String stripped for
+ * any illegal characters. If a null pointer is passed an empty string will be returned.
+ */
+ static public String stripInvalidChars(String text) {
+ if(text == null) {
+ return "";
+ }
+ char out[] = new char[text.length()];
+ int i, o, l;
+ for(i=0, o=0, l=text.length(); i<l; i++){
+ char c = text.charAt(i);
+ if((c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd)) {
+ out[o++] = c;
+ } // Else we skip the character
+ }
+
+ if(i==o) {
+ return text;
+ } else { // We removed some characters, create the new string
+ return new String(out,0,o);
+ }
+ }
+
+ /**
+ * Truncate UTF-8 string encoded byte array to desired length
+ * @param utf8String String to convert to bytes array h
+ * @param length Max length of byte array returned including null termination
+ * @return byte array containing valid utf8 characters with max length
+ * @throws UnsupportedEncodingException
+ */
+ static public byte[] truncateUtf8StringToBytearray(String utf8String, int maxLength)
+ throws UnsupportedEncodingException {
+
+ byte[] utf8Bytes = null;
+ try {
+ utf8Bytes = utf8String.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG,"truncateUtf8StringToBytearray: getBytes exception ", e);
+ throw e;
+ }
+
+ if (utf8Bytes.length > (maxLength - 1)) {
+ /* if 'continuation' byte is in place 200,
+ * then strip previous bytes until utf-8 start byte is found */
+ if ( (utf8Bytes[maxLength - 1] & 0xC0) == 0x80 ) {
+ for (int i = maxLength - 2; i >= 0; i--) {
+ if ((utf8Bytes[i] & 0xC0) == 0xC0) {
+ /* first byte in utf-8 character found,
+ * now copy i - 1 bytes to outBytes and add null termination */
+ utf8Bytes = Arrays.copyOf(utf8Bytes, i+1);
+ utf8Bytes[i] = 0;
+ break;
+ }
+ }
+ } else {
+ /* copy bytes to outBytes and null terminate */
+ utf8Bytes = Arrays.copyOf(utf8Bytes, maxLength);
+ utf8Bytes[maxLength - 1] = 0;
+ }
+ }
+ return utf8Bytes;
+ }
+ private static Pattern p = Pattern.compile("=\\?(.+?)\\?(.)\\?(.+?(?=\\?=))\\?=");
+
+ /**
+ * Method for converting quoted printable og base64 encoded string from headers.
+ * @param in the string with encoding
+ * @return decoded string if success - else the same string as was as input.
+ */
+ static public String stripEncoding(String in){
+ String str = null;
+ if(in.contains("=?") && in.contains("?=")){
+ String encoding;
+ String charset;
+ String encodedText;
+ String match;
+ Matcher m = p.matcher(in);
+ while(m.find()){
+ match = m.group(0);
+ charset = m.group(1);
+ encoding = m.group(2);
+ encodedText = m.group(3);
+ Log.v(TAG, "Matching:" + match +"\nCharset: "+charset +"\nEncoding : " +encoding
+ + "\nText: " + encodedText);
+ if(encoding.equalsIgnoreCase("Q")){
+ //quoted printable
+ Log.d(TAG,"StripEncoding: Quoted Printable string : " + encodedText);
+ str = new String(quotedPrintableToUtf8(encodedText,charset));
+ in = in.replace(match, str);
+ }else if(encoding.equalsIgnoreCase("B")){
+ // base64
+ try{
+
+ Log.d(TAG,"StripEncoding: base64 string : " + encodedText);
+ str = new String(Base64.decode(encodedText.getBytes(charset),
+ Base64.DEFAULT), charset);
+ Log.d(TAG,"StripEncoding: decoded string : " + str);
+ in = in.replace(match, str);
+ }catch(UnsupportedEncodingException e){
+ Log.e(TAG, "stripEncoding: Unsupported charset: " + charset);
+ }catch (IllegalArgumentException e){
+ Log.e(TAG,"stripEncoding: string not encoded as base64: " +encodedText);
+ }
+ }else{
+ Log.e(TAG, "stripEncoding: Hit unknown encoding: "+encoding);
+ }
+ }
+ }
+ return in;
+ }
+
+
+ /**
+ * Convert a quoted-printable encoded string to a UTF-8 string:
+ * - Remove any soft line breaks: "=<CRLF>"
+ * - Convert all "=xx" to the corresponding byte
+ * @param text quoted-printable encoded UTF-8 text
+ * @return decoded UTF-8 string
+ */
+ public static byte[] quotedPrintableToUtf8(String text, String charset) {
+ byte[] output = new byte[text.length()]; // We allocate for the worst case memory need
+ byte[] input = null;
+ try {
+ input = text.getBytes("US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ /* This cannot happen as "US-ASCII" is supported for all Java implementations */ }
+
+ if(input == null){
+ return "".getBytes();
+ }
+
+ int in, out, stopCnt = input.length-2; // Leave room for peaking the next two bytes
+
+ /* Algorithm:
+ * - Search for token, copying all non token chars
+ * */
+ for(in=0, out=0; in < stopCnt; in++){
+ byte b0 = input[in];
+ if(b0 == '=') {
+ byte b1 = input[++in];
+ byte b2 = input[++in];
+ if(b1 == '\r' && b2 == '\n') {
+ continue; // soft line break, remove all tree;
+ }
+ if(((b1 >= '0' && b1 <= '9') || (b1 >= 'A' && b1 <= 'F')
+ || (b1 >= 'a' && b1 <= 'f'))
+ && ((b2 >= '0' && b2 <= '9') || (b2 >= 'A' && b2 <= 'F')
+ || (b2 >= 'a' && b2 <= 'f'))) {
+ if(V)Log.v(TAG, "Found hex number: " + String.format("%c%c", b1, b2));
+ if(b1 <= '9') b1 = (byte) (b1 - '0');
+ else if (b1 <= 'F') b1 = (byte) (b1 - 'A' + 10);
+ else if (b1 <= 'f') b1 = (byte) (b1 - 'a' + 10);
+
+ if(b2 <= '9') b2 = (byte) (b2 - '0');
+ else if (b2 <= 'F') b2 = (byte) (b2 - 'A' + 10);
+ else if (b2 <= 'f') b2 = (byte) (b2 - 'a' + 10);
+
+ if(V)Log.v(TAG, "Resulting nibble values: " +
+ String.format("b1=%x b2=%x", b1, b2));
+
+ output[out++] = (byte)(b1<<4 | b2); // valid hex char, append
+ if(V)Log.v(TAG, "Resulting value: " + String.format("0x%2x", output[out-1]));
+ continue;
+ }
+ Log.w(TAG, "Received wrongly quoted printable encoded text. " +
+ "Continuing at best effort...");
+ /* If we get a '=' without either a hex value or CRLF following, just add it and
+ * rewind the in counter. */
+ output[out++] = b0;
+ in -= 2;
+ continue;
+ } else {
+ output[out++] = b0;
+ continue;
+ }
+ }
+
+ // Just add any remaining characters. If they contain any encoding, it is invalid,
+ // and best effort would be just to display the characters.
+ while (in < input.length) {
+ output[out++] = input[in++];
+ }
+
+ String result = null;
+ // Figure out if we support the charset, else fall back to UTF-8, as this is what
+ // the MAP specification suggest to use, and is compatible with US-ASCII.
+ if(charset == null){
+ charset = "UTF-8";
+ } else {
+ charset = charset.toUpperCase();
+ try {
+ if(Charset.isSupported(charset) == false) {
+ charset = "UTF-8";
+ }
+ } catch (IllegalCharsetNameException e) {
+ Log.w(TAG, "Received unknown charset: " + charset + " - using UTF-8.");
+ charset = "UTF-8";
+ }
+ }
+ try{
+ result = new String(output, 0, out, charset);
+ } catch (UnsupportedEncodingException e) {
+ /* This cannot happen unless Charset.isSupported() is out of sync with String */
+ try{
+ result = new String(output, 0, out, "UTF-8");
+ } catch (UnsupportedEncodingException e2) {/* This cannot happen */}
+ }
+ return result.getBytes(); /* return the result as "UTF-8" bytes */
+ }
+
+ /**
+ * Encodes an array of bytes into an array of quoted-printable 7-bit characters.
+ * Unsafe characters are escaped.
+ * Simplified version of encoder from QuetedPrintableCodec.java (Apache external)
+ *
+ * @param bytes
+ * array of bytes to be encoded
+ * @return UTF-8 string containing quoted-printable characters
+ */
+
+ private static byte ESCAPE_CHAR = '=';
+ private static byte TAB = 9;
+ private static byte SPACE = 32;
+
+ public static final String encodeQuotedPrintable(byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ }
+
+ BitSet printable = new BitSet(256);
+ // alpha characters
+ for (int i = 33; i <= 60; i++) {
+ printable.set(i);
+ }
+ for (int i = 62; i <= 126; i++) {
+ printable.set(i);
+ }
+ printable.set(TAB);
+ printable.set(SPACE);
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ for (int i = 0; i < bytes.length; i++) {
+ int b = bytes[i];
+ if (b < 0) {
+ b = 256 + b;
+ }
+ if (printable.get(b)) {
+ buffer.write(b);
+ } else {
+ buffer.write(ESCAPE_CHAR);
+ char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
+ char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
+ buffer.write(hex1);
+ buffer.write(hex2);
+ }
+ }
+ try {
+ return buffer.toString("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ //cannot happen
+ return "";
+ }
+ }
+
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessage.java b/src/com/android/bluetooth/map/BluetoothMapbMessage.java
index 54b72cb..9a77568 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessage.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessage.java
@@ -36,7 +36,7 @@
protected static final boolean D = BluetoothMapService.DEBUG;
protected static final boolean V = BluetoothMapService.VERBOSE;
- private static final String VERSION = "VERSION:1.0";
+ private String mVersionString = "VERSION:1.0";
public static int INVALID_VALUE = -1;
@@ -68,6 +68,8 @@
private String[] mPhoneNumbers = {};
private String[] mEmailAddresses = {};
private int mEnvLevel = 0;
+ private String[] mBtUcis = {};
+ private String[] mBtUids = {};
/**
* Construct a version 3.0 vCard
@@ -110,15 +112,25 @@
* @param name Structured name
* @param formattedName Formatted name
* @param phoneNumbers a String[] of phone numbers
- * @param emailAddresses a String[] of email addresses
+ * @param emailAddresses a String[] of email addresses if available, else null
+ * @param btUids a String[] of X-BT-UIDs if available, else null
+ * @param btUcis a String[] of X-BT-UCIs if available, else null
*/
- public vCard(String name, String formattedName, String[] phoneNumbers, String[] emailAddresses) {
+ public vCard(String name, String formattedName,
+ String[] phoneNumbers,
+ String[] emailAddresses,
+ String[] btUids,
+ String[] btUcis) {
this.mVersion = "3.0";
this.mName = (name != null) ? name : "";
this.mFormattedName = (formattedName != null) ? formattedName : "";
setPhoneNumbers(phoneNumbers);
- if (emailAddresses != null)
+ if (emailAddresses != null) {
this.mEmailAddresses = emailAddresses;
+ }
+ if (btUcis != null) {
+ this.mBtUcis = btUcis;
+ }
}
/**
@@ -140,11 +152,16 @@
mPhoneNumbers = new String[numbers.length];
for(int i = 0, n = numbers.length; i < n; i++){
String networkNumber = PhoneNumberUtils.extractNetworkPortion(numbers[i]);
- /* extractNetworkPortion can return N if the number is a service "number" = a string
- * with the a name in (i.e. "Some-Tele-company" would return N because of the N in compaNy)
+ /* extractNetworkPortion can return N if the number is a service
+ * "number" = a string with the a name in (i.e. "Some-Tele-company" would
+ * return N because of the N in compaNy)
* Hence we need to check if the number is actually a string with alpha chars.
* */
- Boolean alpha = PhoneNumberUtils.stripSeparators(numbers[i]).matches("[0-9]*[a-zA-Z]+[0-9]*");
+ String strippedNumber = PhoneNumberUtils.stripSeparators(numbers[i]);
+ Boolean alpha = false;
+ if(strippedNumber != null){
+ alpha = strippedNumber.matches("[0-9]*[a-zA-Z]+[0-9]*");
+ }
if(networkNumber != null && networkNumber.length() > 1 && !alpha) {
mPhoneNumbers[i] = networkNumber;
} else {
@@ -175,6 +192,19 @@
} else
return null;
}
+ public String getFirstBtUci() {
+ if(mBtUcis.length > 0) {
+ return mBtUcis[0];
+ } else
+ return null;
+ }
+
+ public String getFirstBtUid() {
+ if(mBtUids.length > 0) {
+ return mBtUids[0];
+ } else
+ return null;
+ }
public void encode(StringBuilder sb)
{
@@ -194,13 +224,22 @@
{
sb.append("EMAIL:").append(emailAddress).append("\r\n");
}
+ for(String btUid : mBtUids)
+ {
+ sb.append("X-BT-UID:").append(btUid).append("\r\n");
+ }
+ for(String btUci : mBtUcis)
+ {
+ sb.append("X-BT-UCI:").append(btUci).append("\r\n");
+ }
sb.append("END:VCARD").append("\r\n");
}
/**
- * Parse a vCard from a BMgsReader, where a line containing "BEGIN:VCARD" have just been read.
+ * Parse a vCard from a BMgsReader, where a line containing "BEGIN:VCARD"
+ * have just been read.
* @param reader
- * @param mOriginator
+ * @param envLevel
* @return
*/
public static vCard parseVcard(BMsgReader reader, int envLevel) {
@@ -208,10 +247,12 @@
String name = null;
ArrayList<String> phoneNumbers = null;
ArrayList<String> emailAddresses = null;
+ ArrayList<String> btUids = null;
+ ArrayList<String> btUcis = null;
String[] parts;
String line = reader.getLineEnforce();
- while(!line.contains("END:VCARD")) {
+ while(!line.contains("END:VCARD")){
line = line.trim();
if(line.startsWith("N:")){
parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':'
@@ -233,7 +274,8 @@
String[] subParts = parts[1].split("[^\\\\];");
if(phoneNumbers == null)
phoneNumbers = new ArrayList<String>(1);
- phoneNumbers.add(subParts[subParts.length-1]); // only keep actual phone number
+ // only keep actual phone number
+ phoneNumbers.add(subParts[subParts.length-1]);
} else {}
// Empty phone number - ignore
}
@@ -243,15 +285,40 @@
String[] subParts = parts[1].split("[^\\\\];");
if(emailAddresses == null)
emailAddresses = new ArrayList<String>(1);
- emailAddresses.add(subParts[subParts.length-1]); // only keep actual email address
+ // only keep actual email address
+ emailAddresses.add(subParts[subParts.length-1]);
} else {}
// Empty email address entry - ignore
}
+ else if(line.startsWith("X-BT-UCI:")){
+ parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
+ if(parts.length == 2) {
+ String[] subParts = parts[1].split("[^\\\\];");
+ if(btUcis == null)
+ btUcis = new ArrayList<String>(1);
+ btUcis.add(subParts[subParts.length-1]); // only keep actual UCI
+ } else {}
+ // Empty UCIentry - ignore
+ }
+ else if(line.startsWith("X-BT-UID:")){
+ parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
+ if(parts.length == 2) {
+ String[] subParts = parts[1].split("[^\\\\];");
+ if(btUids == null)
+ btUids = new ArrayList<String>(1);
+ btUids.add(subParts[subParts.length-1]); // only keep actual UID
+ } else {}
+ // Empty UID entry - ignore
+ }
+
+
line = reader.getLineEnforce();
}
return new vCard(name, formattedName,
- phoneNumbers == null? null : phoneNumbers.toArray(new String[phoneNumbers.size()]),
- emailAddresses == null ? null : emailAddresses.toArray(new String[emailAddresses.size()]),
+ phoneNumbers == null?
+ null : phoneNumbers.toArray(new String[phoneNumbers.size()]),
+ emailAddresses == null ?
+ null : emailAddresses.toArray(new String[emailAddresses.size()]),
envLevel);
}
};
@@ -267,9 +334,9 @@
int readByte;
/* TODO: Actually the vCard spec. allows to break lines by using a newLine
- * followed by a white space character(space or tab). Not sure this is a good idea to implement
- * as the Bluetooth MAP spec. illustrates vCards using tab alignment, hence actually
- * showing an invalid vCard format...
+ * followed by a white space character(space or tab). Not sure this is a good idea to
+ * implement as the Bluetooth MAP spec. illustrates vCards using tab alignment,
+ * hence actually showing an invalid vCard format...
* If we read such a folded line, the folded part will be skipped in the parser
* UPDATE: Check if we actually do unfold before parsing the input stream
*/
@@ -345,7 +412,8 @@
if(line == null || subString == null){
throw new IllegalArgumentException("Line or substring is null");
}else if(!line.toUpperCase().contains(subString.toUpperCase()))
- throw new IllegalArgumentException("Expected \"" + subString + "\" in: \"" + line + "\"");
+ throw new IllegalArgumentException("Expected \"" + subString + "\" in: \""
+ + line + "\"");
}
/**
@@ -358,23 +426,26 @@
public void expect(String subString, String subString2) throws IllegalArgumentException{
String line = getLine();
if(!line.toUpperCase().contains(subString.toUpperCase()))
- throw new IllegalArgumentException("Expected \"" + subString + "\" in: \"" + line + "\"");
+ throw new IllegalArgumentException("Expected \"" + subString + "\" in: \""
+ + line + "\"");
if(!line.toUpperCase().contains(subString2.toUpperCase()))
- throw new IllegalArgumentException("Expected \"" + subString + "\" in: \"" + line + "\"");
+ throw new IllegalArgumentException("Expected \"" + subString + "\" in: \""
+ + line + "\"");
}
/**
* Read a part of the bMessage as raw data.
* @param length the number of bytes to read
- * @return the byte[] containing the number of bytes or null if an error occurs or EOF is reached
- * before length bytes have been read.
+ * @return the byte[] containing the number of bytes or null if an error occurs or EOF is
+ * reached before length bytes have been read.
*/
public byte[] getDataBytes(int length) {
byte[] data = new byte[length];
try {
int bytesRead;
int offset=0;
- while ((bytesRead = mInStream.read(data, offset, length-offset)) != (length - offset)) {
+ while ((bytesRead = mInStream.read(data, offset, length-offset))
+ != (length - offset)) {
if(bytesRead == -1)
return null;
offset += bytesRead;
@@ -391,7 +462,19 @@
}
- public static BluetoothMapbMessage parse(InputStream bMsgStream, int appParamCharset) throws IllegalArgumentException{
+ public String getVersionString() {
+ return mVersionString;
+ }
+ /**
+ * Set the version string for VCARD
+ * @param version the actual number part of the version string i.e. 1.0
+ * */
+ public void setVersionString(String version) {
+ this.mVersionString = "VERSION:"+version;
+ }
+
+ public static BluetoothMapbMessage parse(InputStream bMsgStream,
+ int appParamCharset) throws IllegalArgumentException{
BMsgReader reader;
String line = "";
BluetoothMapbMessage newBMsg = null;
@@ -400,10 +483,11 @@
TYPE type = null;
String folder = null;
- /* This section is used for debug. It will write the incoming message to a file on the SD-card,
- * hence should only be used for test/debug.
+ /* This section is used for debug. It will write the incoming message to a file on the
+ * SD-card, hence should only be used for test/debug.
* If an error occurs, it will result in a OBEX_HTTP_PRECON_FAILED to be send to the client,
- * even though the message might be formatted correctly, hence only enable this code for test. */
+ * even though the message might be formatted correctly, hence only enable this code for
+ * test. */
if(V) {
/* Read the entire stream into a file on the SD card*/
File sdCard = Environment.getExternalStorageDirectory();
@@ -415,7 +499,8 @@
int writtenLen = 0;
try {
- outStream = new FileOutputStream(file, false); /* overwrite if it does already exist */
+ /* overwrite if it does already exist */
+ outStream = new FileOutputStream(file, false);
byte[] buffer = new byte[4*1024];
int len = 0;
@@ -428,7 +513,8 @@
} catch (IOException e) {
Log.e(TAG,"Failed to copy the received message",e);
if(writtenLen != 0)
- failed = true; /* We failed to write the complete file, hence the received message is lost... */
+ failed = true; /* We failed to write the complete file,
+ hence the received message is lost... */
} finally {
if(outStream != null)
try {
@@ -443,7 +529,7 @@
}
if (outStream == null) {
- /* We failed to create the the log-file, just continue using the original bMsgStream. */
+ /* We failed to create the log-file, just continue using the original bMsgStream. */
} else {
/* overwrite the bMsgStream using the file written to the SD-Card */
try {
@@ -456,7 +542,8 @@
bMsgStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
Log.e(TAG,"Failed to open the bMessage file", e);
- throw new IllegalArgumentException(); /* terminate this function with an error. */
+ /* terminate this function with an error */
+ throw new IllegalArgumentException();
}
}
Log.i(TAG, "The incoming bMessage have been dumped to " + file.getAbsolutePath());
@@ -464,7 +551,7 @@
reader = new BMsgReader(bMsgStream);
reader.expect("BEGIN:BMSG");
- reader.expect("VERSION","1.0");
+ reader.expect("VERSION");
line = reader.getLineEnforce();
// Parse the properties - which end with either a VCARD or a BENV
@@ -483,14 +570,24 @@
throw new IllegalArgumentException("Missing value for 'STATUS': " + line);
}
}
+ if(line.contains("EXTENDEDDATA")){
+ String arg[] = line.split(":");
+ if (arg != null && arg.length == 2) {
+ String value = arg[1].trim();
+ //FIXME what should we do with this
+ Log.i(TAG,"We got extended data with: "+value);
+ }
+ }
if(line.contains("TYPE")) {
String arg[] = line.split(":");
if (arg != null && arg.length == 2) {
String value = arg[1].trim();
- type = TYPE.valueOf(value); // Will throw IllegalArgumentException if value is wrong
+ /* Will throw IllegalArgumentException if value is wrong */
+ type = TYPE.valueOf(value);
if(appParamCharset == BluetoothMapAppParams.CHARSET_NATIVE
&& type != TYPE.SMS_CDMA && type != TYPE.SMS_GSM) {
- throw new IllegalArgumentException("Native appParamsCharset only supported for SMS");
+ throw new IllegalArgumentException("Native appParamsCharset "
+ +"only supported for SMS");
}
switch(type) {
case SMS_CDMA:
@@ -498,11 +595,14 @@
newBMsg = new BluetoothMapbMessageSms();
break;
case MMS:
- newBMsg = new BluetoothMapbMessageMms();
+ newBMsg = new BluetoothMapbMessageMime();
break;
case EMAIL:
newBMsg = new BluetoothMapbMessageEmail();
break;
+ case IM:
+ newBMsg = new BluetoothMapbMessageMime();
+ break;
default:
break;
}
@@ -520,7 +620,8 @@
line = reader.getLineEnforce();
}
if(newBMsg == null)
- throw new IllegalArgumentException("Missing bMessage TYPE: - unable to parse body-content");
+ throw new IllegalArgumentException("Missing bMessage TYPE: "+
+ "- unable to parse body-content");
newBMsg.setType(type);
newBMsg.mAppParamCharset = appParamCharset;
if(folder != null)
@@ -539,10 +640,11 @@
} else
throw new IllegalArgumentException("Bmessage has no BEGIN:BENV - line:" + line);
- /* TODO: Do we need to validate the END:* tags? They are only needed if someone puts additional info
- * below the END:MSG - in which case we don't handle it.
- * We need to parse the message based on the length field, to ensure MAP 1.0 compatibility,
- * since this spec. do not suggest to escape the end-tag if it occurs inside the message text.
+ /* TODO: Do we need to validate the END:* tags? They are only needed if someone puts
+ * additional info below the END:MSG - in which case we don't handle it.
+ * We need to parse the message based on the length field, to ensure MAP 1.0
+ * compatibility, since this spec. do not suggest to escape the end-tag if it
+ * occurs inside the message text.
*/
try {
@@ -640,10 +742,12 @@
/* PTS has a bug regarding the message length, and sets it 2 bytes too short, hence
* using the length field to determine the amount of data to read, might not be the
* best solution.
- * Since errata ???(bluetooth.org is down at the moment) introduced escaping of END:MSG
- * in the actual message content, it is now safe to use the END:MSG tag as terminator,
- * and simply ignore the length field.*/
- byte[] rawData = reader.getDataBytes(mBMsgLength - (line.getBytes().length + 2)); // 2 added to compensate for the removed \r\n
+ * Since errata ???(bluetooth.org is down at the moment) introduced escaping of
+ * END:MSG in the actual message content, it is now safe to use the END:MSG tag
+ * as terminator, and simply ignore the length field.*/
+
+ /* 2 added to compensate for the removed \r\n */
+ byte[] rawData = reader.getDataBytes(mBMsgLength - (line.getBytes().length + 2));
String data;
try {
data = new String(rawData, "UTF-8");
@@ -665,7 +769,8 @@
* 1) split on "\r\nEND:MSG\r\n"
* 2) delete "BEGIN:MSG\r\n" for each msg
* 3) replace any occurrence of "\END:MSG" with "END:MSG"
- * 4) based on charset from application properties either store as String[] or decode to raw PDUs
+ * 4) based on charset from application properties either store as String[] or
+ * decode to raw PDUs
* */
String messages[] = data.split("\r\nEND:MSG\r\n");
parseMsgInit();
@@ -745,12 +850,25 @@
* @param phoneNumbers
* @param emailAddresses
*/
- public void addOriginator(String name, String formattedName, String[] phoneNumbers, String[] emailAddresses) {
+ public void addOriginator(String name, String formattedName,
+ String[] phoneNumbers,
+ String[] emailAddresses,
+ String[] btUids,
+ String[] btUcis) {
if(mOriginator == null)
mOriginator = new ArrayList<vCard>();
- mOriginator.add(new vCard(name, formattedName, phoneNumbers, emailAddresses));
+ mOriginator.add(new vCard(name, formattedName, phoneNumbers,
+ emailAddresses, btUids, btUcis));
}
+
+ public void addOriginator(String[] btUcis, String[] btUids) {
+ if(mOriginator == null)
+ mOriginator = new ArrayList<vCard>();
+ mOriginator.add(new vCard(null,null,null,null,btUids, btUcis));
+ }
+
+
/** Add a version 2.1 vCard with only a name.
*
* @param name e.g. Bonde;Casper
@@ -772,11 +890,20 @@
this.mRecipient = new ArrayList<vCard>();
this.mRecipient.add(recipient);
}
-
- public void addRecipient(String name, String formattedName, String[] phoneNumbers, String[] emailAddresses) {
+ public void addRecipient(String[] btUcis, String[] btUids) {
if(mRecipient == null)
mRecipient = new ArrayList<vCard>();
- mRecipient.add(new vCard(name, formattedName, phoneNumbers, emailAddresses));
+ mRecipient.add(new vCard(null,null,null,null,btUids, btUcis));
+ }
+ public void addRecipient(String name, String formattedName,
+ String[] phoneNumbers,
+ String[] emailAddresses,
+ String[] btUids,
+ String[] btUcis) {
+ if(mRecipient == null)
+ mRecipient = new ArrayList<vCard>();
+ mRecipient.add(new vCard(name, formattedName, phoneNumbers,
+ emailAddresses,btUids, btUcis));
}
public void addRecipient(String name, String[] phoneNumbers, String[] emailAddresses) {
@@ -786,10 +913,10 @@
}
/**
- * Convert a byte[] of data to a hex string representation, converting each nibble to the corresponding
- * hex char.
- * NOTE: There is not need to escape instances of "\r\nEND:MSG" in the binary data represented as a string
- * as only the characters [0-9] and [a-f] is used.
+ * Convert a byte[] of data to a hex string representation, converting each nibble to the
+ * corresponding hex char.
+ * NOTE: There is not need to escape instances of "\r\nEND:MSG" in the binary data represented
+ * as a string as only the characters [0-9] and [a-f] is used.
* @param pduData the byte-array of data.
* @param scAddressData the byte-array of the encoded sc-Address.
* @return the resulting string.
@@ -803,8 +930,10 @@
for(int i = 0; i < pduData.length; i++) {
out.append(Integer.toString((pduData[i] >> 4) & 0x0f,16)); // MS-nibble first
out.append(Integer.toString( pduData[i] & 0x0f,16));
- /*out.append(Integer.toHexString(data[i]));*/ /* This is the same as above, but does not include the needed 0's
- e.g. it converts the value 3 to "3" and not "03" */
+ /*out.append(Integer.toHexString(data[i]));*/ /* This is the same as above, but does not
+ * include the needed 0's
+ * e.g. it converts the value 3 to "3"
+ * and not "03" */
}
return out.toString();
}
@@ -820,7 +949,8 @@
if(D) Log.d(TAG,"Decoding binary data: START:" + data + ":END");
for(int i = 0, j = 0, n = out.length; i < n; i++)
{
- value = data.substring(j++, ++j); // same as data.substring(2*i, 2*i+1+1) - substring() uses end-1 for last index
+ value = data.substring(j++, ++j);
+ // same as data.substring(2*i, 2*i+1+1) - substring() uses end-1 for last index
out[i] = (byte)(Integer.valueOf(value, 16) & 0xff);
}
if(D) {
@@ -839,13 +969,18 @@
StringBuilder sb = new StringBuilder(256);
byte[] msgStart, msgEnd;
sb.append("BEGIN:BMSG").append("\r\n");
- sb.append(VERSION).append("\r\n");
+
+ sb.append(mVersionString).append("\r\n");
sb.append("STATUS:").append(mStatus).append("\r\n");
sb.append("TYPE:").append(mType.name()).append("\r\n");
if(mFolder.length() > 512)
- sb.append("FOLDER:").append(mFolder.substring(mFolder.length()-512, mFolder.length())).append("\r\n");
+ sb.append("FOLDER:").append(
+ mFolder.substring(mFolder.length()-512, mFolder.length())).append("\r\n");
else
sb.append("FOLDER:").append(mFolder).append("\r\n");
+ if(!mVersionString.contains("1.0")){
+ sb.append("EXTENDEDDATA:").append("\r\n");
+ }
if(mOriginator != null){
for(vCard element : mOriginator)
element.encode(sb);
@@ -887,7 +1022,8 @@
try {
- ByteArrayOutputStream stream = new ByteArrayOutputStream(msgStart.length + msgEnd.length + length);
+ ByteArrayOutputStream stream = new ByteArrayOutputStream(
+ msgStart.length + msgEnd.length + length);
stream.write(msgStart);
for (byte[] fragment : bodyFragments) {
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageMms.java b/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
similarity index 79%
rename from src/com/android/bluetooth/map/BluetoothMapbMessageMms.java
rename to src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
index 8c9a39d..20147b9 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessageMms.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
@@ -29,19 +29,21 @@
import android.util.Base64;
import android.util.Log;
-public class BluetoothMapbMessageMms extends BluetoothMapbMessage {
+public class BluetoothMapbMessageMime extends BluetoothMapbMessage {
public static class MimePart {
- public long mId = INVALID_VALUE; /* The _id from the content provider, can be used to sort the parts if needed */
- public String mContentType = null; /* The mime type, e.g. text/plain */
+ public long mId = INVALID_VALUE; /* The _id from the content provider, can be used to
+ * sort the parts if needed */
+ public String mContentType = null; /* The mime type, e.g. text/plain */
public String mContentId = null;
public String mContentLocation = null;
public String mContentDisposition = null;
- public String mPartName = null; /* e.g. text_1.txt*/
- public String mCharsetName = null; /* This seems to be a number e.g. 106 for UTF-8 CharacterSets
- holds a method for the mapping. */
+ public String mPartName = null; /* e.g. text_1.txt*/
+ public String mCharsetName = null; /* This seems to be a number e.g. 106 for UTF-8
+ CharacterSets holds a method for the mapping. */
public String mFileName = null; /* Do not seem to be used */
- public byte[] mData = null; /* The raw un-encoded data e.g. the raw jpeg data or the text.getBytes("utf-8") */
+ public byte[] mData = null; /* The raw un-encoded data e.g. the raw
+ * jpeg data or the text.getBytes("utf-8") */
String getDataAsString() {
@@ -73,7 +75,8 @@
return result;
}
- public void encode(StringBuilder sb, String boundaryTag, boolean last) throws UnsupportedEncodingException {
+ public void encode(StringBuilder sb, String boundaryTag, boolean last)
+ throws UnsupportedEncodingException {
sb.append("--").append(boundaryTag).append("\r\n");
if(mContentType != null)
sb.append("Content-Type: ").append(mContentType);
@@ -93,11 +96,20 @@
if(mContentType != null &&
(mContentType.toUpperCase().contains("TEXT") ||
mContentType.toUpperCase().contains("SMIL") )) {
- sb.append("Content-Transfer-Encoding: 8BIT\r\n\r\n"); // Add the header split empty line
- sb.append(new String(mData,"UTF-8")).append("\r\n");
+ String text = new String(mData,"UTF-8");
+ if(text.getBytes().length == text.getBytes("UTF-8").length){
+ /* Add the header split empty line */
+ sb.append("Content-Transfer-Encoding: 8BIT\r\n\r\n");
+ }else {
+ /* Add the header split empty line */
+ sb.append("Content-Transfer-Encoding: Quoted-Printable\r\n\r\n");
+ text = BluetoothMapUtils.encodeQuotedPrintable(mData);
+ }
+ sb.append(text).append("\r\n");
}
else {
- sb.append("Content-Transfer-Encoding: Base64\r\n\r\n"); // Add the header split empty line
+ /* Add the header split empty line */
+ sb.append("Content-Transfer-Encoding: Base64\r\n\r\n");
sb.append(Base64.encodeToString(mData, Base64.DEFAULT)).append("\r\n");
}
}
@@ -108,7 +120,11 @@
public void encodePlainText(StringBuilder sb) throws UnsupportedEncodingException {
if(mContentType != null && mContentType.toUpperCase().contains("TEXT")) {
- sb.append(new String(mData,"UTF-8")).append("\r\n");
+ String text = new String(mData, "UTF-8");
+ if(text.getBytes().length != text.getBytes("UTF-8").length){
+ text = BluetoothMapUtils.encodeQuotedPrintable(mData);
+ }
+ sb.append(text).append("\r\n");
} else if(mContentType != null && mContentType.toUpperCase().contains("/SMIL")) {
/* Skip the smil.xml, as no-one knows what it is. */
} else {
@@ -168,7 +184,7 @@
}
public MimePart addMimePart() {
if(parts == null)
- parts = new ArrayList<BluetoothMapbMessageMms.MimePart>();
+ parts = new ArrayList<BluetoothMapbMessageMime.MimePart>();
MimePart newPart = new MimePart();
parts.add(newPart);
return newPart;
@@ -352,8 +368,9 @@
*/
/* If we are to use US-ASCII anyway, here is the code for it for base64.
if (subject != null){
- // Use base64 encoding for the subject, as it may contain non US-ASCII characters or other
- // illegal (RFC822 header), and android do not seem to have encoders/decoders for quoted-printables
+ // Use base64 encoding for the subject, as it may contain non US-ASCII characters or
+ // other illegal (RFC822 header), and android do not seem to have encoders/decoders
+ // for quoted-printables
sb.append("Subject:").append("=?utf-8?B?");
sb.append(Base64.encodeToString(subject.getBytes("utf-8"), Base64.DEFAULT));
sb.append("?=\r\n");
@@ -384,9 +401,11 @@
if(messageId != null)
sb.append("Message-Id: ").append(messageId).append("\r\n");
if(contentType != null)
- sb.append("Content-Type: ").append(contentType).append("; boundary=").append(getBoundary()).append("\r\n");
+ sb.append("Content-Type: ").append(
+ contentType).append("; boundary=").append(getBoundary()).append("\r\n");
}
- sb.append("\r\n"); // If no headers exists, we still need two CRLF, hence keep it out of the if above.
+ // If no headers exists, we still need two CRLF, hence keep it out of the if above.
+ sb.append("\r\n");
}
/* Notes on MMS
@@ -419,16 +438,16 @@
* */
/**
- * Encode the bMessage as a MMS
+ * Encode the bMessage as a Mime message(MMS/IM)
* @return
* @throws UnsupportedEncodingException
*/
- public byte[] encodeMms() throws UnsupportedEncodingException
+ public byte[] encodeMime() throws UnsupportedEncodingException
{
ArrayList<byte[]> bodyFragments = new ArrayList<byte[]>();
StringBuilder sb = new StringBuilder();
int count = 0;
- String mmsBody;
+ String mimeBody;
encoding = "8BIT"; // The encoding used
@@ -436,7 +455,9 @@
if(parts != null) {
if(getIncludeAttachments() == false) {
for(MimePart part : parts) {
- part.encodePlainText(sb); /* We call encode on all parts, to include a tag, where an attachment is missing. */
+ /* We call encode on all parts, to include a tag,
+ * where an attachment is missing. */
+ part.encodePlainText(sb);
}
} else {
for(MimePart part : parts) {
@@ -446,10 +467,11 @@
}
}
- mmsBody = sb.toString();
+ mimeBody = sb.toString();
- if(mmsBody != null) {
- String tmpBody = mmsBody.replaceAll("END:MSG", "/END\\:MSG"); // Replace any occurrences of END:MSG with \END:MSG
+ if(mimeBody != null) {
+ // Replace any occurrences of END:MSG with \END:MSG
+ String tmpBody = mimeBody.replaceAll("END:MSG", "/END\\:MSG");
bodyFragments.add(tmpBody.getBytes("UTF-8"));
} else {
bodyFragments.add(new byte[0]);
@@ -465,7 +487,7 @@
* @return Null if the entire string were e-mail headers. The part of the string in which
* no headers were found.
*/
- private String parseMmsHeaders(String hdrPart) {
+ private String parseMimeHeaders(String hdrPart) {
String[] headers = hdrPart.split("\r\n");
if(D) Log.d(TAG,"Header count=" + headers.length);
String header;
@@ -474,9 +496,10 @@
for(int i = 0, c = headers.length; i < c; i++) {
header = headers[i];
if(D) Log.d(TAG,"Header[" + i + "]: " + header);
- /* We need to figure out if any headers are present, in cases where devices do not follow the e-mail RFCs.
- * Skip empty lines, and then parse headers until a non-header line is found, at which point we treat the
- * remaining as plain text.
+ /* We need to figure out if any headers are present, in cases where devices do
+ * not follow the e-mail RFCs.
+ * Skip empty lines, and then parse headers until a non-header line is found,
+ * at which point we treat the remaining as plain text.
*/
if(header.trim() == "")
continue;
@@ -498,22 +521,27 @@
* This happens when sending the MMS.
*/
if(headerType.contains("FROM")) {
+ headerValue = BluetoothMapUtils.stripEncoding(headerValue);
Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
from = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
} else if(headerType.contains("TO")) {
+ headerValue = BluetoothMapUtils.stripEncoding(headerValue);
Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
to = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
} else if(headerType.contains("CC")) {
+ headerValue = BluetoothMapUtils.stripEncoding(headerValue);
Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
cc = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
} else if(headerType.contains("BCC")) {
+ headerValue = BluetoothMapUtils.stripEncoding(headerValue);
Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
bcc = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
} else if(headerType.contains("REPLY-TO")) {
+ headerValue = BluetoothMapUtils.stripEncoding(headerValue);
Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
replyTo = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
} else if(headerType.contains("SUBJECT")) { // Other headers
- subject = headerValue;
+ subject = BluetoothMapUtils.stripEncoding(headerValue);
} else if(headerType.contains("MESSAGE-ID")) {
messageId = headerValue;
} else if(headerType.contains("DATE")) {
@@ -530,7 +558,8 @@
if(contentTypeParts[j].contains("boundary")) {
boundary = contentTypeParts[j].split("boundary[\\s]*=", 2)[1].trim();
// removing quotes from boundary string
- if ((boundary.charAt(0) == '\"') && (boundary.charAt(boundary.length()-1) == '\"'))
+ if ((boundary.charAt(0) == '\"')
+ && (boundary.charAt(boundary.length()-1) == '\"'))
boundary = boundary.substring(1, boundary.length()-1);
if(D) Log.d(TAG,"Boundary tag=" + boundary);
} else if(contentTypeParts[j].contains("charset")) {
@@ -546,21 +575,23 @@
return null;
}
- private void parseMmsMimePart(String partStr) {
+ private void parseMimePart(String partStr) {
String[] parts = partStr.split("\r\n\r\n", 2); // Split the header from the body
MimePart newPart = addMimePart();
String partEncoding = encoding; /* Use the overall encoding as default */
String body;
String[] headers = parts[0].split("\r\n");
- if(D) Log.d(TAG, "parseMmsMimePart: headers count=" + headers.length);
+ if(D) Log.d(TAG, "parseMimePart: headers count=" + headers.length);
if(parts.length != 2) {
body = partStr;
} else {
for(String header : headers) {
// Skip empty lines(the \r\n after the boundary tag) and endBoundary tags
- if((header.length() == 0) || (header.trim().isEmpty()) || header.trim().equals("--"))
+ if((header.length() == 0)
+ || (header.trim().isEmpty())
+ || header.trim().equals("--"))
continue;
String[] headerParts = header.split(":",2);
@@ -568,7 +599,7 @@
if(D) Log.w(TAG, "part-Header not formatted correctly: ");
continue;
}
- if(D) Log.d(TAG, "parseMmsMimePart: header=" + header);
+ if(D) Log.d(TAG, "parseMimePart: header=" + header);
String headerType = headerParts[0].toUpperCase();
String headerValue = headerParts[1].trim();
if(headerType.contains("CONTENT-TYPE")) {
@@ -600,7 +631,8 @@
newPart.mContentDisposition = headerValue;
}
else {
- if(D) Log.w(TAG,"Skipping unknown part-header: " + headerType + " (" + header + ")");
+ if(D) Log.w(TAG,"Skipping unknown part-header: " + headerType
+ + " (" + header + ")");
}
}
body = parts[1];
@@ -615,7 +647,7 @@
newPart.mData = decodeBody(body, partEncoding, newPart.mCharsetName);
}
- private void parseMmsMimeBody(String body) {
+ private void parseMimeBody(String body) {
MimePart newPart = addMimePart();
newPart.mCharsetName = mCharset;
newPart.mData = decodeBody(body, encoding, mCharset);
@@ -625,7 +657,7 @@
if(encoding != null && encoding.toUpperCase().contains("BASE64")) {
return Base64.decode(body, Base64.DEFAULT);
} else if(encoding != null && encoding.toUpperCase().contains("QUOTED-PRINTABLE")) {
- return quotedPrintableToUtf8(body, charset);
+ return BluetoothMapUtils.quotedPrintableToUtf8(body, charset);
}else{
// TODO: handle other encoding types? - here we simply store the string data as bytes
try {
@@ -638,7 +670,7 @@
return null;
}
- private void parseMms(String message) {
+ private void parseMime(String message) {
/* Overall strategy for decoding:
* 1) split on first empty line to extract the header
* 2) unfold and parse headers
@@ -658,11 +690,11 @@
}
else
{
- remaining = parseMmsHeaders(messageParts[0]);
+ remaining = parseMimeHeaders(messageParts[0]);
// If we have some text not being a header, add it to the message body.
if(remaining != null) {
messageBody = remaining + messageParts[1];
- if(D) Log.d(TAG, "parseMms remaining=" + remaining );
+ if(D) Log.d(TAG, "parseMime remaining=" + remaining );
} else {
messageBody = messageParts[1];
}
@@ -671,7 +703,7 @@
if(boundary == null)
{
// If the boundary is not set, handle as non-multi-part
- parseMmsMimeBody(messageBody);
+ parseMimeBody(messageBody);
setTextOnly(true);
if(contentType == null)
contentType = "text/plain";
@@ -685,111 +717,17 @@
for(int i = 1; i < mimeParts.length - 1; i++) {
String part = mimeParts[i];
if (part != null && (part.length() > 0))
- parseMmsMimePart(part);
+ parseMimePart(part);
}
}
}
- /**
- * Convert a quoted-printable encoded string to a UTF-8 string:
- * - Remove any soft line breaks: "=<CRLF>"
- * - Convert all "=xx" to the corresponding byte
- * @param text quoted-printable encoded UTF-8 text
- * @return decoded UTF-8 string
- */
- public static byte[] quotedPrintableToUtf8(String text, String charset) {
- byte[] output = new byte[text.length()]; // We allocate for the worst case memory need
- byte[] input = null;
- try {
- input = text.getBytes("US-ASCII");
- } catch (UnsupportedEncodingException e) {
- /* This cannot happen as "US-ASCII" is supported for all Java implementations */ }
-
- if(input == null){
- return "".getBytes();
- }
-
- int in, out, stopCnt = input.length-2; // Leave room for peaking the next two bytes
-
- /* Algorithm:
- * - Search for token, copying all non token chars
- * */
- for(in=0, out=0; in < stopCnt; in++){
- byte b0 = input[in];
- if(b0 == '=') {
- byte b1 = input[++in];
- byte b2 = input[++in];
- if(b1 == '\r' && b2 == '\n') {
- continue; // soft line break, remove all tree;
- }
- if(((b1 >= '0' && b1 <= '9') || (b1 >= 'A' && b1 <= 'F') || (b1 >= 'a' && b1 <= 'f')) &&
- ((b2 >= '0' && b2 <= '9') || (b2 >= 'A' && b2 <= 'F') || (b2 >= 'a' && b2 <= 'f'))) {
- if(V)Log.v(TAG, "Found hex number: " + String.format("%c%c", b1, b2));
- if(b1 <= '9') b1 = (byte) (b1 - '0');
- else if (b1 <= 'F') b1 = (byte) (b1 - 'A' + 10);
- else if (b1 <= 'f') b1 = (byte) (b1 - 'a' + 10);
-
- if(b2 <= '9') b2 = (byte) (b2 - '0');
- else if (b2 <= 'F') b2 = (byte) (b2 - 'A' + 10);
- else if (b2 <= 'f') b2 = (byte) (b2 - 'a' + 10);
-
- if(V)Log.v(TAG, "Resulting nibble values: " + String.format("b1=%x b2=%x", b1, b2));
-
- output[out++] = (byte)(b1<<4 | b2); // valid hex char, append
- if(V)Log.v(TAG, "Resulting value: " + String.format("0x%2x", output[out-1]));
- continue;
- }
- Log.w(TAG, "Received wrongly quoted printable encoded text. Continuing at best effort...");
- /* If we get a '=' without either a hex value or CRLF following, just add it and
- * rewind the in counter. */
- output[out++] = b0;
- in -= 2;
- continue;
- } else {
- output[out++] = b0;
- continue;
- }
- }
-
- // Just add any remaining characters. If they contain any encoding, it is invalid,
- // and best effort would be just to display the characters.
- while (in < input.length) {
- output[out++] = input[in++];
- }
-
- String result = null;
- // Figure out if we support the charset, else fall back to UTF-8, as this is what
- // the MAP specification suggest to use, and is compatible with US-ASCII.
- if(charset == null){
- charset = "UTF-8";
- } else {
- charset = charset.toUpperCase();
- try {
- if(Charset.isSupported(charset) == false) {
- charset = "UTF-8";
- }
- } catch (IllegalCharsetNameException e) {
- Log.w(TAG, "Received unknown charset: " + charset + " - using UTF-8.");
- charset = "UTF-8";
- }
- }
- try{
- result = new String(output, 0, out, charset);
- } catch (UnsupportedEncodingException e) {
- /* This cannot happen unless Charset.isSupported() is out of sync with String */
- try{
- result = new String(output, 0, out, "UTF-8");
- } catch (UnsupportedEncodingException e2) {/* This cannot happen */}
- }
- return result.getBytes(); /* return the result as "UTF-8" bytes */
- }
-
/* Notes on SMIL decoding (from http://tools.ietf.org/html/rfc2557):
* src="filename.jpg" refers to a part with Content-Location: filename.jpg
* src="cid:1234@hest.net" refers to a part with Content-ID:<1234@hest.net>*/
@Override
public void parseMsgPart(String msgPart) {
- parseMms(msgPart);
+ parseMime(msgPart);
}
@@ -801,7 +739,7 @@
@Override
public byte[] encode() throws UnsupportedEncodingException {
- return encodeMms();
+ return encodeMime();
}
}
diff --git a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
index 7df31da..6c12018 100644
--- a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
+++ b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
@@ -259,7 +259,7 @@
}
mConnected = connected;
}
- synchronized (this) {
+ synchronized (this) {
mWaitingForRemote = false;
}
}
diff --git a/src/com/android/bluetooth/map/MapContact.java b/src/com/android/bluetooth/map/MapContact.java
new file mode 100644
index 0000000..ce2da98
--- /dev/null
+++ b/src/com/android/bluetooth/map/MapContact.java
@@ -0,0 +1,62 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.android.bluetooth.map;
+
+import com.android.bluetooth.SignedLongLong;
+
+
+/**
+ * Local representation of an Android contact
+ */
+public class MapContact {
+ private final String mName;
+ private final long mId;
+
+ private MapContact(long id, String name) {
+ mId = id;
+ mName = name;
+ }
+
+ public static MapContact create(long id, String name){
+ return new MapContact(id, name);
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ public String getXBtUidString() {
+ if(mId > 0) {
+ return BluetoothMapUtils.getLongLongAsString(mId, 0);
+ }
+ return null;
+ }
+
+ public SignedLongLong getXBtUid() {
+ if(mId > 0) {
+ return new SignedLongLong(mId, 0);
+ }
+ return null;
+ }
+
+ @Override
+ public String toString(){
+ return mName;
+ }
+}
diff --git a/src/com/android/bluetooth/map/SmsMmsContacts.java b/src/com/android/bluetooth/map/SmsMmsContacts.java
new file mode 100644
index 0000000..f43a270
--- /dev/null
+++ b/src/com/android/bluetooth/map/SmsMmsContacts.java
@@ -0,0 +1,195 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.android.bluetooth.map;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.regex.Pattern;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.Telephony.CanonicalAddressesColumns;
+import android.provider.Telephony.MmsSms;
+import android.util.Log;
+
+/**
+ * Use these functions when extracting data for listings. It caches frequently used data to
+ * speed up building large listings - e.g. before applying filtering.
+ */
+@TargetApi(19)
+public class SmsMmsContacts {
+
+ private static final String TAG = "SmsMmsContacts";
+
+ private HashMap<Long,String> mPhoneNumbers = null;
+ private final HashMap<String,MapContact> mNames = new HashMap<String, MapContact>(10);
+
+ private static final Uri ADDRESS_URI =
+ MmsSms.CONTENT_URI.buildUpon().appendPath("canonical-addresses").build();
+
+ private static final String[] ADDRESS_PROJECTION = { CanonicalAddressesColumns._ID,
+ CanonicalAddressesColumns.ADDRESS };
+ private static final int COL_ADDR_ID =
+ Arrays.asList(ADDRESS_PROJECTION).indexOf(CanonicalAddressesColumns._ID);
+ private static final int COL_ADDR_ADDR =
+ Arrays.asList(ADDRESS_PROJECTION).indexOf(CanonicalAddressesColumns.ADDRESS);
+
+ private static final String[] CONTACT_PROJECTION = {Contacts._ID, Contacts.DISPLAY_NAME};
+ private static final String CONTACT_SEL_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
+ private static final int COL_CONTACT_ID =
+ Arrays.asList(CONTACT_PROJECTION).indexOf(Contacts._ID);
+ private static final int COL_CONTACT_NAME =
+ Arrays.asList(CONTACT_PROJECTION).indexOf(Contacts.DISPLAY_NAME);
+
+ /**
+ * Get a contacts phone number based on the canonical addresses id of the contact.
+ * (The ID listed in the Threads table.)
+ * @param resolver the ContantResolver to be used.
+ * @param id the id of the contact, as listed in the Threads table
+ * @return the phone number of the contact - or null if id does not exist.
+ */
+ public String getPhoneNumber(ContentResolver resolver, long id) {
+ String number;
+ if(mPhoneNumbers != null && (number = mPhoneNumbers.get(id)) != null) {
+ return number;
+ }
+ fillPhoneCache(resolver);
+ return mPhoneNumbers.get(id);
+ }
+
+ public static String getPhoneNumberUncached(ContentResolver resolver, long id) {
+ String where = CanonicalAddressesColumns._ID + " = " + id;
+ Cursor c = resolver.query(ADDRESS_URI, ADDRESS_PROJECTION, where, null, null);
+ try {
+ if (c != null) {
+ if(c.moveToPosition(0)) {
+ return c.getString(COL_ADDR_ADDR);
+ }
+ }
+ Log.e(TAG, "query failed");
+ } finally {
+ if(c != null) c.close();
+ }
+ return null;
+ }
+
+ /**
+ * Clears the local cache. Call after a listing is complete, to avoid using invalid data.
+ */
+ public void clearCache() {
+ if(mPhoneNumbers != null) mPhoneNumbers.clear();
+ if(mNames != null) mNames.clear();
+ }
+
+ /**
+ * Refreshes the cache, by clearing all cached values and fill the cache with the result of
+ * a new query.
+ * @param resolver the ContantResolver to be used.
+ */
+ private void fillPhoneCache(ContentResolver resolver){
+ Cursor c = resolver.query(ADDRESS_URI, ADDRESS_PROJECTION, null, null, null);
+ if(mPhoneNumbers == null) {
+ int size = 0;
+ if(c != null)
+ {
+ size = c.getCount();
+ }
+ mPhoneNumbers = new HashMap<Long, String>(size);
+ } else {
+ mPhoneNumbers.clear();
+ }
+ try {
+ if (c != null) {
+ long id;
+ String addr;
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ id = c.getLong(COL_ADDR_ID);
+ addr = c.getString(COL_ADDR_ADDR);
+ mPhoneNumbers.put(id, addr);
+ }
+ } else {
+ Log.e(TAG, "query failed");
+ }
+ } finally {
+ if(c != null) c.close();
+ }
+ }
+
+ public MapContact getContactNameFromPhone(String phone, ContentResolver resolver) {
+ return getContactNameFromPhone(phone, resolver, null);
+ }
+ /**
+ * Lookup a contacts name in the Android Contacts database.
+ * @param phone the phone number of the contact
+ * @param resolver the ContentResolver to use.
+ * @return the name of the contact or null, if no contact was found.
+ */
+ public MapContact getContactNameFromPhone(String phone, ContentResolver resolver,
+ String contactNameFilter) {
+ MapContact contact = mNames.get(phone);
+
+ if(contact != null){
+ if(contact.getId() < 0) {
+ return null;
+ }
+ if(contactNameFilter == null) {
+ return contact;
+ }
+ // Validate filter
+ String searchString = contactNameFilter.replace("*", ".*");
+ searchString = ".*" + searchString + ".*";
+ Pattern p = Pattern.compile(Pattern.quote(searchString), Pattern.CASE_INSENSITIVE);
+ if(p.matcher(contact.getName()).find()) {
+ return contact;
+ }
+ return null;
+ }
+
+ // TODO: Should we change to extract both formatted name, and display name?
+
+ Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,Uri.encode(phone));
+ String selection = CONTACT_SEL_VISIBLE;
+ String[] selectionArgs = null;
+ if(contactNameFilter != null) {
+ selection += "AND " + ContactsContract.Contacts.DISPLAY_NAME + " like ?";
+ selectionArgs = new String[]{"%" + contactNameFilter.replace("*", "%") + "%"};
+ }
+
+ Cursor c = resolver.query(uri, CONTACT_PROJECTION, selection, selectionArgs, null);
+ try {
+ if (c != null && c.getCount() >= 1) {
+ c.moveToFirst();
+ long id = c.getLong(COL_CONTACT_ID);
+ String name = c.getString(COL_CONTACT_NAME);
+ contact = MapContact.create(id, name);
+ mNames.put(phone, contact);
+ } else {
+ contact = MapContact.create(-1, null);
+ mNames.put(phone, contact);
+ contact = null;
+ }
+ } finally {
+ if (c != null) c.close();
+ }
+ return contact;
+ }
+}
diff --git a/tests/Android.mk b/tests/Android.mk
index ca30b08..612cb99 100755
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -5,7 +5,7 @@
LOCAL_MODULE_TAGS := optional
LOCAL_CERTIFICATE := platform
-LOCAL_JAVA_LIBRARIES := javax.obex android.test.runner
+LOCAL_JAVA_LIBRARIES := javax.obex android.test.runner telephony-common mms-common
LOCAL_STATIC_JAVA_LIBRARIES := com.android.emailcommon
# Include all test java files.
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 657c858..cf128db 100755
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -11,6 +11,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
diff --git a/tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java b/tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java
index 9e318c6..15e1bdc 100644
--- a/tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java
+++ b/tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java
@@ -1,102 +1,887 @@
package com.android.bluetooth.tests;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.ParcelFileDescriptor;
+import android.provider.BaseColumns;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.Threads;
import android.test.AndroidTestCase;
import android.util.Log;
-import android.database.Cursor;
-import android.content.Context;
-import android.content.ContentResolver;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
+import com.android.bluetooth.map.BluetoothMapMasInstance;
+import com.android.bluetooth.map.BluetoothMapAccountItem;
+import com.android.bluetooth.map.BluetoothMapAccountLoader;
+import com.android.bluetooth.map.BluetoothMapAppParams;
import com.android.bluetooth.map.BluetoothMapContent;
-import com.android.bluetooth.map.BluetoothMapContentObserver;
+import com.android.bluetooth.map.BluetoothMapFolderElement;
+import com.android.bluetooth.map.BluetoothMapMessageListing;
+import com.android.bluetooth.map.BluetoothMapUtils;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.bluetooth.map.MapContact;
+import com.android.bluetooth.map.SmsMmsContacts;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
-import com.android.emailcommon.provider.EmailContent;
-import com.android.emailcommon.provider.EmailContent.Message;
-import com.android.emailcommon.provider.EmailContent.MessageColumns;
-import com.android.emailcommon.provider.EmailContent.SyncColumns;
-
+@TargetApi(19)
public class BluetoothMapContentTest extends AndroidTestCase {
+
private static final String TAG = "BluetoothMapContentTest";
private static final boolean D = true;
- private static final boolean V = true;
private Context mContext;
private ContentResolver mResolver;
+ private SmsMmsContacts mContacts = new SmsMmsContacts();
- static final String[] EMAIL_PROJECTION = new String[] {
- EmailContent.RECORD_ID,
- MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
- MessageColumns.SUBJECT, MessageColumns.FLAG_READ,
- MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS,
- SyncColumns.SERVER_ID, MessageColumns.DRAFT_INFO,
- MessageColumns.MESSAGE_ID, MessageColumns.MAILBOX_KEY,
- MessageColumns.ACCOUNT_KEY, MessageColumns.FROM_LIST,
- MessageColumns.TO_LIST, MessageColumns.CC_LIST,
- MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST,
- SyncColumns.SERVER_TIMESTAMP, MessageColumns.MEETING_INFO,
- MessageColumns.SNIPPET, MessageColumns.PROTOCOL_SEARCH_INFO,
- MessageColumns.THREAD_TOPIC
+ private BluetoothMapFolderElement mCurrentFolder;
+ private BluetoothMapAccountItem mAccount = null;
+
+ private static final int MAS_ID = 0;
+ private static final int REMOTE_FEATURE_MASK = 0x07FFFFFF;
+ private static final BluetoothMapMasInstance mMasInstance =
+ new MockMasInstance(MAS_ID, REMOTE_FEATURE_MASK);
+
+
+ private Uri mEmailUri = null;
+ private Uri mEmailMessagesUri = null;
+ private Uri mEmailFolderUri = null;
+ private Uri mEmailAccountUri = null;
+
+ static final String[] EMAIL_ACCOUNT_PROJECTION = new String[] {
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.ACCOUNT_ID,
};
+ private void printAccountInfo(Cursor c) {
+ if (D) Log.d(TAG, BluetoothMapContract.MessageColumns.ACCOUNT_ID + " : " +
+ c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.ACCOUNT_ID)) );
+ }
+
+ static final String[] BT_MESSAGE_ID_PROJECTION = new String[] {
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.DATE,
+ };
+
+ static final String[] BT_MESSAGE_PROJECTION = BluetoothMapContract.BT_MESSAGE_PROJECTION;
+
+ static final String[] BT_ACCOUNT_PROJECTION = BluetoothMapContract.BT_ACCOUNT_PROJECTION;
+
+ static final String[] BT_FOLDER_PROJECTION = BluetoothMapContract.BT_FOLDER_PROJECTION;
+
+ BluetoothMapAccountLoader loader;
+ LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> mFullList;
+
+ public BluetoothMapContentTest() {
+ super();
+ }
+
+ private void initTestSetup(){
+ mContext = this.getContext();
+ mResolver = mContext.getContentResolver();
+
+ // find enabled account
+ loader = new BluetoothMapAccountLoader(mContext);
+ mFullList = loader.parsePackages(false);
+ String accountId = getEnabledAccount();
+ Uri tmpEmailUri = Uri.parse("content://com.android.email.bluetoothprovider/");
+
+ mEmailUri = Uri.withAppendedPath(tmpEmailUri, accountId + "/");
+ mEmailMessagesUri = Uri.parse(mEmailUri + BluetoothMapContract.TABLE_MESSAGE);
+ mEmailFolderUri = Uri.parse(mEmailUri + BluetoothMapContract.TABLE_FOLDER);
+ mEmailAccountUri = Uri.parse(tmpEmailUri + BluetoothMapContract.TABLE_ACCOUNT);
+
+ buildFolderStructure();
+
+ }
+
+ public String getEnabledAccount(){
+ if(D)Log.d(TAG,"getEnabledAccountItems()\n");
+ String account = null;
+ for(BluetoothMapAccountItem app:mFullList.keySet()){
+ ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
+ for(BluetoothMapAccountItem acc: accountList){
+ mAccount = acc;
+ account = acc.getId();
+ break;
+ }
+ }
+ return account;
+ }
+
+ private void buildFolderStructure(){
+ mCurrentFolder = new BluetoothMapFolderElement("root", null); // This will be the root element
+ BluetoothMapFolderElement tmpFolder;
+ tmpFolder = mCurrentFolder.addFolder("telecom"); // root/telecom
+ tmpFolder = tmpFolder.addFolder("msg"); // root/telecom/msg
+ if(mEmailFolderUri != null) {
+ addEmailFolders(tmpFolder);
+ }
+ }
+
+ private void addEmailFolders(BluetoothMapFolderElement parentFolder) {
+ BluetoothMapFolderElement newFolder;
+ String where = BluetoothMapContract.FolderColumns.PARENT_FOLDER_ID +
+ " = " + parentFolder.getFolderId();
+ Cursor c = mContext.getContentResolver().query(mEmailFolderUri,
+ BluetoothMapContract.BT_FOLDER_PROJECTION, where, null, null);
+ if (c != null) {
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ String name = c.getString(c.getColumnIndex(BluetoothMapContract.FolderColumns.NAME));
+ long id = c.getLong(c.getColumnIndex(BluetoothMapContract.FolderColumns._ID));
+ newFolder = parentFolder.addEmailFolder(name, id);
+ addEmailFolders(newFolder); // Use recursion to add any sub folders
+ }
+ c.close();
+ } else {
+ if (D) Log.d(TAG, "addEmailFolders(): no elements found");
+ }
+ }
+
+ private BluetoothMapFolderElement getInbox() {
+ BluetoothMapFolderElement tmpFolderElement = null;
+
+ tmpFolderElement = mCurrentFolder.getSubFolder("telecom");
+ tmpFolderElement = tmpFolderElement.getSubFolder("msg");
+ tmpFolderElement = tmpFolderElement.getSubFolder("inbox");
+ return tmpFolderElement;
+ }
+
+ private BluetoothMapFolderElement getOutbox() {
+ BluetoothMapFolderElement tmpFolderElement = null;
+
+ tmpFolderElement = mCurrentFolder.getSubFolder("telecom");
+ tmpFolderElement = tmpFolderElement.getSubFolder("msg");
+ tmpFolderElement = tmpFolderElement.getSubFolder("outbox");
+ return tmpFolderElement;
+ }
+
+
private String getDateTimeString(long timestamp) {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
Date date = new Date(timestamp);
return format.format(date); // Format to YYYYMMDDTHHMMSS local time
}
- private void printEmail(Cursor c) {
- if (D) Log.d(TAG, "printEmail " +
- c.getLong(c.getColumnIndex(EmailContent.RECORD_ID)) +
- "\n " + MessageColumns.DISPLAY_NAME + " : " + c.getString(c.getColumnIndex(MessageColumns.DISPLAY_NAME)) +
- "\n " + MessageColumns.TIMESTAMP + " : " + getDateTimeString(c.getLong(c.getColumnIndex(MessageColumns.TIMESTAMP))) +
- "\n " + MessageColumns.SUBJECT + " : " + c.getString(c.getColumnIndex(MessageColumns.SUBJECT)) +
- "\n " + MessageColumns.FLAG_READ + " : " + c.getString(c.getColumnIndex(MessageColumns.FLAG_READ)) +
- "\n " + MessageColumns.FLAG_ATTACHMENT + " : " + c.getInt(c.getColumnIndex(MessageColumns.FLAG_ATTACHMENT)) +
- "\n " + MessageColumns.FLAGS + " : " + c.getInt(c.getColumnIndex(MessageColumns.FLAGS)) +
- "\n " + SyncColumns.SERVER_ID + " : " + c.getInt(c.getColumnIndex(SyncColumns.SERVER_ID)) +
- "\n " + MessageColumns.DRAFT_INFO + " : " + c.getInt(c.getColumnIndex(MessageColumns.DRAFT_INFO)) +
- "\n " + MessageColumns.MESSAGE_ID + " : " + c.getInt(c.getColumnIndex(MessageColumns.MESSAGE_ID)) +
- "\n " + MessageColumns.MAILBOX_KEY + " : " + c.getInt(c.getColumnIndex(MessageColumns.MAILBOX_KEY)) +
- "\n " + MessageColumns.ACCOUNT_KEY + " : " + c.getInt(c.getColumnIndex(MessageColumns.ACCOUNT_KEY)) +
- "\n " + MessageColumns.FROM_LIST + " : " + c.getString(c.getColumnIndex(MessageColumns.FROM_LIST)) +
- "\n " + MessageColumns.TO_LIST + " : " + c.getString(c.getColumnIndex(MessageColumns.TO_LIST)) +
- "\n " + MessageColumns.CC_LIST + " : " + c.getString(c.getColumnIndex(MessageColumns.CC_LIST)) +
- "\n " + MessageColumns.BCC_LIST + " : " + c.getString(c.getColumnIndex(MessageColumns.BCC_LIST)) +
- "\n " + MessageColumns.REPLY_TO_LIST + " : " + c.getString(c.getColumnIndex(MessageColumns.REPLY_TO_LIST)) +
- "\n " + SyncColumns.SERVER_TIMESTAMP + " : " + getDateTimeString(c.getLong(c.getColumnIndex(SyncColumns.SERVER_TIMESTAMP))) +
- "\n " + MessageColumns.MEETING_INFO + " : " + c.getString(c.getColumnIndex(MessageColumns.MEETING_INFO)) +
- "\n " + MessageColumns.SNIPPET + " : " + c.getString(c.getColumnIndex(MessageColumns.SNIPPET)) +
- "\n " + MessageColumns.PROTOCOL_SEARCH_INFO + " : " + c.getString(c.getColumnIndex(MessageColumns.PROTOCOL_SEARCH_INFO)) +
- "\n " + MessageColumns.THREAD_TOPIC + " : " + c.getString(c.getColumnIndex(MessageColumns.THREAD_TOPIC)));
+ private void printCursor(Cursor c) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("\nprintCursor:\n");
+ for(int i = 0; i < c.getColumnCount(); i++) {
+ if(c.getColumnName(i).equals(BluetoothMapContract.MessageColumns.DATE)){
+ sb.append(" ").append(c.getColumnName(i))
+ .append(" : ").append(getDateTimeString(c.getLong(i))).append("\n");
+ } else {
+ sb.append(" ").append(c.getColumnName(i))
+ .append(" : ").append(c.getString(i)).append("\n");
+ }
+ }
+ Log.d(TAG, sb.toString());
}
- public void dumpEmailMessageTable() {
- Log.d(TAG, "**** Dump of email message table ****");
+ private void dumpMessageContent(Cursor c) {
+ long id = c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns._ID));
+ Uri uri = Uri.parse(mEmailMessagesUri + "/" + id
+ + "/" + BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS);
+ FileInputStream is = null;
+ ParcelFileDescriptor fd = null;
+ int count;
+ try {
+ fd = mResolver.openFileDescriptor(uri, "r");
+ is = new FileInputStream(fd.getFileDescriptor());
+ byte[] buffer = new byte[1024];
- Cursor c = mResolver.query(Message.CONTENT_URI,
- EMAIL_PROJECTION, null, null, "_id DESC");
+ while((count = is.read(buffer)) != -1) {
+ Log.d(TAG, new String(buffer,0, count));
+ }
+
+
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, e);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+ finally {
+ try {
+ if(is != null)
+ is.close();
+ } catch (IOException e) {}
+ try {
+ if(fd != null)
+ fd.close();
+ } catch (IOException e) {}
+ }
+ }
+
+ /**
+ * Create a new message in the database outbox, based on the content of c.
+ * @param c
+ */
+ private void writeMessageContent(Cursor c) {
+ long id = c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns._ID));
+ Uri uri = Uri.parse(mEmailMessagesUri + "/" + id + "/"
+ + BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS);
+ FileInputStream is = null;
+ ParcelFileDescriptor fd = null;
+ FileOutputStream os = null;
+ ParcelFileDescriptor fdOut = null;
+
+ ContentValues newMessage = new ContentValues();
+ BluetoothMapFolderElement outFolder = getOutbox();
+ newMessage.put(BluetoothMapContract.MessageColumns.FOLDER_ID, outFolder.getFolderId());
+ // Now insert the empty message into outbox (Maybe it should be draft first, and then a move?)
+ // TODO: Examine if we need to set some additional flags, e.g. visable?
+ Uri uriOut = mResolver.insert(mEmailMessagesUri, newMessage);
+ int count;
+ try {
+ fd = mResolver.openFileDescriptor(uri, "r");
+ is = new FileInputStream(fd.getFileDescriptor());
+ fdOut = mResolver.openFileDescriptor(uri, "w");
+ os = new FileOutputStream(fdOut.getFileDescriptor());
+ byte[] buffer = new byte[1024];
+
+ while((count = is.read(buffer)) != -1) {
+ Log.d(TAG, new String(buffer,0, count));
+ os.write(buffer, 0, count);
+ }
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, e);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+ finally {
+ try {
+ if(is != null)
+ is.close();
+ } catch (IOException e) {}
+ try {
+ if(fd != null)
+ fd.close();
+ } catch (IOException e) {}
+ try {
+ if(os != null)
+ os.close();
+ } catch (IOException e) {}
+ try {
+ if(fdOut != null)
+ fdOut.close();
+ } catch (IOException e) {}
+ }
+ }
+
+ private void writeMessage(Cursor c) {
+ Log.d(TAG, "c.getCount() = " + c.getCount());
+ c.moveToPosition(-1);
+ if (c.moveToNext()) {
+ writeMessageContent(c);
+ }
+ c.close();
+ }
+
+
+ private void dumpCursor(Cursor c) {
+ Log.d(TAG, "c.getCount() = " + c.getCount());
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ printCursor(c);
+ }
+ c.close();
+ }
+
+ private void callBluetoothProvider() {
+ Log.d(TAG, "**** Test call into email provider ****");
+ int accountId = 0;
+ int mailboxId = 0;
+
+ Log.d(TAG, "contentUri = " + mEmailMessagesUri);
+
+ Cursor c = mResolver.query(mEmailMessagesUri, EMAIL_ACCOUNT_PROJECTION,
+ null, null, "_id DESC");
if (c != null) {
Log.d(TAG, "c.getCount() = " + c.getCount());
c.moveToPosition(-1);
while (c.moveToNext()) {
- printEmail(c);
+ printAccountInfo(c);
+ mailboxId = c.getInt(c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.FOLDER_ID));
+ accountId = c.getInt(c.getColumnIndex(
+ BluetoothMapContract.MessageColumns.ACCOUNT_ID));
}
+ c.close();
} else {
Log.d(TAG, "query failed");
- c.close();
+ }
+
+ final Bundle extras = new Bundle(2);
+ /* TODO: find mailbox from DB */
+ extras.putLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID, mailboxId);
+ extras.putLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, accountId);
+ Bundle myBundle = mResolver.call(mEmailUri, BluetoothMapContract.METHOD_UPDATE_FOLDER,
+ null, extras);
+ }
+
+
+ public void testMsgListing() {
+ initTestSetup();
+ BluetoothMapContent mBtMapContent = new BluetoothMapContent(mContext, mAccount,
+ mMasInstance);
+ BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+ Log.d(TAG, "**** testMsgListing **** ");
+ BluetoothMapFolderElement fe = getInbox();
+
+ if (fe != null) {
+ if (D) Log.d(TAG, "folder name=" + fe.getName());
+
+ appParams.setFilterMessageType(0x0B);
+ appParams.setMaxListCount(1024);
+ appParams.setStartOffset(0);
+
+ BluetoothMapMessageListing msgListing = mBtMapContent.msgListing(fe, appParams);
+ int listCount = msgListing.getCount();
+ int msgListingSize = mBtMapContent.msgListingSize(fe, appParams);
+
+ if (listCount == msgListingSize) {
+ Log.d(TAG, "testMsgListing - " + listCount );
+ }
+ else {
+ Log.d(TAG, "testMsgListing - None");
+ }
+ }
+ else {
+ Log.d(TAG, "testMsgListing - failed ");
+ }
+
+ }
+
+ public void testMsgListingUnread() {
+ initTestSetup();
+ BluetoothMapContent mBtMapContent = new BluetoothMapContent(mContext, mAccount,
+ mMasInstance);
+ BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+ Log.d(TAG, "**** testMsgListingUnread **** ");
+ BluetoothMapFolderElement fe = getInbox();
+
+ if (fe != null) {
+
+ appParams.setFilterReadStatus(0x01);
+ appParams.setFilterMessageType(0x0B);
+ appParams.setMaxListCount(1024);
+ appParams.setStartOffset(0);
+
+ BluetoothMapMessageListing msgListing = mBtMapContent.msgListing(fe, appParams);
+
+ int listCount = msgListing.getCount();
+ if (msgListing.getCount() > 0) {
+ Log.d(TAG, "testMsgListingUnread - " + listCount );
+ }
+ else {
+ Log.d(TAG, "testMsgListingUnread - None");
+ }
+ }
+ else {
+ Log.d(TAG, "testMsgListingUnread - getInbox failed ");
}
}
- public BluetoothMapContentTest() {
- super();
+ public void testMsgListingWithOriginator() {
+ initTestSetup();
+ BluetoothMapContent mBtMapContent = new BluetoothMapContent(mContext, mAccount,
+ mMasInstance);
+ BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+ Log.d(TAG, "**** testMsgListingUnread **** ");
+ BluetoothMapFolderElement fe = getInbox();
+
+ if (fe != null) {
+
+ appParams.setFilterOriginator("*scsc.*");
+ appParams.setFilterMessageType(0x0B);
+ appParams.setMaxListCount(1024);
+ appParams.setStartOffset(0);
+
+ BluetoothMapMessageListing msgListing = mBtMapContent.msgListing(fe, appParams);
+
+ int listCount = msgListing.getCount();
+ if (msgListing.getCount() > 0) {
+ Log.d(TAG, "testMsgListingWithOriginator - " + listCount );
+ }
+ else {
+ Log.d(TAG, "testMsgListingWithOriginator - None");
+ }
+ } else {
+ Log.d(TAG, "testMsgListingWithOriginator - getInbox failed ");
+ }
+ }
+
+ public void testGetMessages() {
+ initTestSetup();
+ BluetoothMapContent mBtMapContent = new BluetoothMapContent(mContext, mAccount,
+ mMasInstance);
+ BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+ Log.d(TAG, "**** testGetMessages **** ");
+ BluetoothMapFolderElement fe = getInbox();
+
+ if (fe != null) {
+ appParams.setAttachment(0);
+ appParams.setCharset(BluetoothMapContent.MAP_MESSAGE_CHARSET_UTF8);
+
+ //get message handles
+ Cursor c = mResolver.query(mEmailMessagesUri, BT_MESSAGE_ID_PROJECTION,
+ null, null, "_id DESC");
+ if (c != null) {
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ Long id = c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns._ID));
+ String handle = BluetoothMapUtils.getMapHandle(id, TYPE.EMAIL);
+ try {
+ // getMessage
+ byte[] bytes = mBtMapContent.getMessage(handle, appParams, fe, "1.1");
+ Log.d(TAG, "testGetMessages id=" + id + ", handle=" + handle +
+ ", length=" + bytes.length );
+ String testPrint = new String(bytes);
+ Log.d(TAG, "testGetMessage (only dump first part):\n" + testPrint );
+ } catch (UnsupportedEncodingException e) {
+ Log.w(TAG, e);
+ } finally {
+
+ }
+ }
+ } else {
+ Log.d(TAG, "testGetMessages - no cursor ");
+ }
+ } else {
+ Log.d(TAG, "testGetMessages - getInbox failed ");
+ }
+
+ }
+
+ public void testDumpAccounts() {
+ initTestSetup();
+ Log.d(TAG, "**** testDumpAccounts **** \n from: " + mEmailAccountUri.toString());
+ Cursor c = mResolver.query(mEmailAccountUri, BT_ACCOUNT_PROJECTION, null, null, "_id DESC");
+ if (c != null) {
+ dumpCursor(c);
+ } else {
+ Log.d(TAG, "query failed");
+ }
+ Log.w(TAG, "testDumpAccounts(): ThreadId: " + Thread.currentThread().getId());
+
+ }
+
+ public void testAccountUpdate() {
+ initTestSetup();
+ Log.d(TAG, "**** testAccountUpdate **** \n of: " + mEmailAccountUri.toString());
+ Cursor c = mResolver.query(mEmailAccountUri, BT_ACCOUNT_PROJECTION, null, null, "_id DESC");
+
+ if (c != null) {
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ printCursor(c);
+ Long id = c.getLong(c.getColumnIndex(BluetoothMapContract.AccountColumns._ID));
+ int exposeFlag = c.getInt(
+ c.getColumnIndex(BluetoothMapContract.AccountColumns.FLAG_EXPOSE));
+ String where = BluetoothMapContract.AccountColumns._ID + " = " + id;
+ ContentValues values = new ContentValues();
+ if(exposeFlag == 1) {
+ values.put(BluetoothMapContract.AccountColumns.FLAG_EXPOSE, (int) 0);
+ } else {
+ values.put(BluetoothMapContract.AccountColumns.FLAG_EXPOSE, (int) 1);
+ }
+ Log.i(TAG, "Calling update() with selection: " + where +
+ "values(exposeFlag): " +
+ values.getAsInteger(BluetoothMapContract.AccountColumns.FLAG_EXPOSE));
+ mResolver.update(mEmailAccountUri, values, where, null);
+ }
+ c.close();
+ }
+
}
public void testDumpMessages() {
+ initTestSetup();
+
+ if (D) Log.d(TAG, "**** testDumpMessages **** \n uri=" + mEmailMessagesUri.toString());
+ BluetoothMapFolderElement fe = getInbox();
+ if (fe != null)
+ {
+ String where ="";
+ //where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + fe.getEmailFolderId();
+ Cursor c = mResolver.query(mEmailMessagesUri, BT_MESSAGE_PROJECTION,
+ where, null, "_id DESC");
+ if (c != null) {
+ dumpCursor(c);
+ } else {
+ if (D) Log.d(TAG, "query failed");
+ }
+ if (D) Log.w(TAG, "dumpMessage(): ThreadId: " + Thread.currentThread().getId());
+ } else {
+ if (D) Log.w(TAG, "dumpMessage(): ThreadId: " + Thread.currentThread().getId());
+ }
+ }
+
+ public void testDumpMessageContent() {
+ initTestSetup();
+
+ Log.d(TAG, "**** testDumpMessageContent **** from: " + mEmailMessagesUri.toString());
+// BluetoothMapFolderElement fe = getInbox();
+// String where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + fe.getEmailFolderId();
+// where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + " = 0";
+
+ Cursor c = mResolver.query(mEmailMessagesUri, BT_MESSAGE_PROJECTION, null, null, "_id DESC");
+ if (c != null && c.moveToNext()) {
+ dumpMessageContent(c);
+ } else {
+ Log.d(TAG, "query failed");
+ }
+ Log.w(TAG, "dumpMessage(): ThreadId: " + Thread.currentThread().getId());
+ }
+
+ public void testWriteMessageContent() {
+ initTestSetup();
+ Log.d(TAG, "**** testWriteMessageContent **** from: " + mEmailMessagesUri.toString());
+ BluetoothMapFolderElement fe = getInbox();
+ String where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + fe.getFolderId();
+// where += " AND " + BluetoothMapContract.MessageColumns.HIGH_PRIORITY + " = 0";
+ Cursor c = mResolver.query(mEmailMessagesUri, BT_MESSAGE_PROJECTION, where, null, "_id DESC");
+ if (c != null) {
+ writeMessage(c);
+ } else {
+ Log.d(TAG, "query failed");
+ }
+ Log.w(TAG, "writeMessage(): ThreadId: " + Thread.currentThread().getId());
+ }
+
+ /*
+ * Handle test cases
+ */
+ private static final long HANDLE_TYPE_SMS_CDMA_MASK = (((long)0x1)<<60);
+
+ public void testHandle() {
+ String handleStr = null;
+ Debug.startMethodTracing("str_format");
+ for(long i = 0; i < 10000; i++) {
+ handleStr = String.format("%016X",(i | HANDLE_TYPE_SMS_CDMA_MASK));
+ }
+ Debug.stopMethodTracing();
+ Debug.startMethodTracing("getHandleString");
+ for(long i = 0; i < 10000; i++) {
+ handleStr = BluetoothMapUtils.getLongAsString(i | HANDLE_TYPE_SMS_CDMA_MASK);
+ }
+ Debug.stopMethodTracing();
+ }
+
+ /*
+ * Folder test cases
+ */
+
+ public void testDumpEmailFolders() {
+ initTestSetup();
+ Debug.startMethodTracing();
+ String where = null;
+ Cursor c = mResolver.query(mEmailFolderUri, BT_FOLDER_PROJECTION, where, null, "_id DESC");
+ if (c != null) {
+ dumpCursor(c);
+ c.close();
+ } else {
+ Log.d(TAG, "query failed");
+ }
+ Debug.stopMethodTracing();
+ }
+
+ public void testFolderPath() {
+ initTestSetup();
+ Log.d(TAG, "**** testFolderPath **** ");
+ BluetoothMapFolderElement fe = getInbox();
+ BluetoothMapFolderElement folder = fe.getFolderById(fe.getFolderId());
+ if(folder == null) {
+ Log.d(TAG, "**** testFolderPath unable to find the folder with id: " +
+ fe.getFolderId());
+ }
+ else {
+ Log.d(TAG, "**** testFolderPath found the folder with id: " +
+ fe.getFolderId() + "\nFull path: " +
+ folder.getFullPath());
+ }
+ }
+
+ public void testFolderElement() {
+ Log.d(TAG, "**** testFolderElement **** ");
+ BluetoothMapFolderElement fe = new BluetoothMapFolderElement("root", null);
+ fe = fe.addEmailFolder("MsG", 1);
+ fe.addEmailFolder("Outbox", 100);
+ fe.addEmailFolder("Sent", 200);
+ BluetoothMapFolderElement inbox = fe.addEmailFolder("Inbox", 300);
+ fe.addEmailFolder("Draft", 400);
+ fe.addEmailFolder("Deleted", 500);
+ inbox.addEmailFolder("keep", 301);
+ inbox.addEmailFolder("private", 302);
+ inbox.addEmailFolder("junk", 303);
+
+ BluetoothMapFolderElement folder = fe.getFolderById(400);
+ assertEquals("draft", folder.getName());
+ assertEquals("private", fe.getFolderById(302).getName());
+ assertEquals("junk", fe.getRoot().getFolderById(303).getName());
+ assertEquals("msg/inbox/keep", fe.getFolderById(301).getFullPath());
+ }
+
+ /*
+ * SMS test cases
+ */
+ public void testAddSmsEntries() {
+ int count = 1000;
mContext = this.getContext();
mResolver = mContext.getContentResolver();
- dumpEmailMessageTable();
+ ContentValues values[] = new ContentValues[count];
+ long date = System.currentTimeMillis();
+ Log.i(TAG, "Preparing messages...");
+ for (int x=0;x<count;x++){
+ //if (D) Log.d(TAG, "*** Adding dummy sms #"+x);
+
+ ContentValues item = new ContentValues(4);
+ item.put("address", "1234");
+ item.put("body", "test message "+x);
+ item.put("date", date);
+ item.put("read", "0");
+
+ values[x] = item;
+ // Uri mUri = mResolver.insert(Uri.parse("content://sms"), item);
+ }
+ Log.i(TAG, "Starting bulk insert...");
+ mResolver.bulkInsert(Uri.parse("content://sms"), values);
+ Log.i(TAG, "Bulk insert done.");
}
+
+ public void testAddSms() {
+ mContext = this.getContext();
+ mResolver = mContext.getContentResolver();
+ if (D) Log.d(TAG, "*** Adding dummy sms #");
+
+ ContentValues item = new ContentValues();
+ item.put("address", "1234");
+ item.put("body", "test message");
+ item.put("date", System.currentTimeMillis());
+ item.put("read", "0");
+
+ Uri mUri = mResolver.insert(Uri.parse("content://sms"), item);
+ }
+
+ public void testServiceSms() {
+ mContext = this.getContext();
+ mResolver = mContext.getContentResolver();
+ if (D) Log.d(TAG, "*** Adding dummy sms #");
+
+ ContentValues item = new ContentValues();
+ item.put("address", "C-Bonde");
+ item.put("body", "test message");
+ item.put("date", System.currentTimeMillis());
+ item.put("read", "0");
+
+ Uri mUri = mResolver.insert(Uri.parse("content://sms"), item);
+ }
+
+ /*
+ * MMS content test cases
+ */
+ public static final int MMS_FROM = 0x89;
+ public static final int MMS_TO = 0x97;
+ public static final int MMS_BCC = 0x81;
+ public static final int MMS_CC = 0x82;
+
+ private void printMmsAddr(long id) {
+ final String[] projection = null;
+ String selection = new String("msg_id=" + id);
+ String uriStr = String.format("content://mms/%d/addr", id);
+ Uri uriAddress = Uri.parse(uriStr);
+ Cursor c = mResolver.query(uriAddress, projection, selection, null, null);
+
+ if (c.moveToFirst()) {
+ do {
+ String add = c.getString(c.getColumnIndex("address"));
+ Integer type = c.getInt(c.getColumnIndex("type"));
+ if (type == MMS_TO) {
+ if (D) Log.d(TAG, " recipient: " + add + " (type: " + type + ")");
+ } else if (type == MMS_FROM) {
+ if (D) Log.d(TAG, " originator: " + add + " (type: " + type + ")");
+ } else {
+ if (D) Log.d(TAG, " address other: " + add + " (type: " + type + ")");
+ }
+ printCursor(c);
+
+ } while(c.moveToNext());
+ }
+ }
+
+ private void printMmsPartImage(long partid) {
+ String uriStr = String.format("content://mms/part/%d", partid);
+ Uri uriAddress = Uri.parse(uriStr);
+ int ch;
+ StringBuffer sb = new StringBuffer("");
+ InputStream is = null;
+
+ try {
+ is = mResolver.openInputStream(uriAddress);
+
+ while ((ch = is.read()) != -1) {
+ sb.append((char)ch);
+ }
+ if (D) Log.d(TAG, sb.toString());
+
+ } catch (IOException e) {
+ // do nothing for now
+ e.printStackTrace();
+ }
+ }
+
+ private void printMmsParts(long id) {
+ final String[] projection = null;
+ String selection = new String("mid=" + id);
+ String uriStr = String.format("content://mms/%d/part", id);
+ Uri uriAddress = Uri.parse(uriStr);
+ Cursor c = mResolver.query(uriAddress, projection, selection, null, null);
+
+ if (c.moveToFirst()) {
+ int i = 0;
+ do {
+ if (D) Log.d(TAG, " part " + i++);
+ printCursor(c);
+
+ /* if (ct.equals("image/jpeg")) { */
+ /* printMmsPartImage(partid); */
+ /* } */
+ } while(c.moveToNext());
+ }
+ }
+
+ public void dumpMmsTable() {
+ mContext = this.getContext();
+ mResolver = mContext.getContentResolver();
+
+ if (D) Log.d(TAG, "**** Dump of mms table ****");
+ Cursor c = mResolver.query(Mms.CONTENT_URI,
+ null, null, null, "_id DESC");
+ if (c != null) {
+ if (D) Log.d(TAG, "c.getCount() = " + c.getCount());
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ Log.d(TAG,"Message:");
+ printCursor(c);
+ long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
+ Log.d(TAG,"Address:");
+ printMmsAddr(id);
+ Log.d(TAG,"Parts:");
+ printMmsParts(id);
+ }
+ c.close();
+ } else {
+ Log.d(TAG, "query failed");
+ }
+ }
+
+ /**
+ * This dumps the thread database.
+ * Interesting how useful this is.
+ * - DATE is described to be the creation date of the thread. But it actually
+ * contains the time-date of the last activity of the thread.
+ * - RECIPIENTS is a list of the contacts related to the thread. The number can
+ * be found for both MMS and SMS in the "canonical-addresses" table.
+ * - The READ column tells if the thread have been read. (read = 1: no unread messages)
+ * - The snippet is a small piece of text from the last message, and could be used as thread
+ * name. Please however note that if we do this, the version-counter should change each
+ * time a message is added to the thread. But since it changes the read attribute and
+ * last activity, it changes anyway.
+ * -
+ */
+
+
+ public void dumpThreadsTable() {
+ mContext = this.getContext();
+ mResolver = mContext.getContentResolver();
+ mContacts.clearCache();
+ Uri uri = Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build();
+
+ if (D) Log.d(TAG, "**** Dump of Threads table ****\nUri: " + uri);
+ Cursor c = mResolver.query(uri,
+ null, null, null, "_id DESC");
+ if (c != null) {
+ if (D) Log.d(TAG, "c.getCount() = " + c.getCount());
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ Log.d(TAG,"Threads:");
+ printCursor(c);
+ String ids = c.getString(c.getColumnIndex(Threads.RECIPIENT_IDS));
+ Log.d(TAG,"Address:");
+ printAddresses(ids);
+/* Log.d(TAG,"Parts:");
+ printMmsParts(id);*/
+ }
+ c.close();
+ } else {
+ Log.d(TAG, "query failed");
+ }
+ }
+
+ /**
+ * This test shows the content of the canonicalAddresses table.
+ * Conclusion:
+ * The _id column matches the id's from the RECIPIENT_IDS column
+ * in the Threads table, hence are to be used to map from an id to
+ * a phone number, which then can be matched to a contact.
+ */
+ public void dumpCanAddrTable() {
+ mContext = this.getContext();
+ mResolver = mContext.getContentResolver();
+ Uri uri = Uri.parse("content://mms-sms/canonical-addresses");
+ uri = MmsSms.CONTENT_URI.buildUpon().appendPath("canonical-addresses").build();
+ dumpUri(uri);
+ }
+
+ public void dumpUri(Uri uri) {
+ if (D) Log.d(TAG, "**** Dump of table ****\nUri: " + uri);
+ Cursor c = mResolver.query(uri, null, null, null, null);
+ if (c != null) {
+ if (D) Log.d(TAG, "c.getCount() = " + c.getCount());
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ Log.d(TAG,"Entry: " + c.getPosition());
+ printCursor(c);
+ }
+ c.close();
+ } else {
+ Log.d(TAG, "query failed");
+ }
+ }
+
+ private void printAddresses(String idsStr) {
+ String[] ids = idsStr.split(" ");
+ for (String id : ids) {
+ long longId;
+ try {
+ longId = Long.parseLong(id);
+ String addr = mContacts.getPhoneNumber(mResolver, longId);
+ MapContact contact = mContacts.getContactNameFromPhone(addr, mResolver);
+ Log.d(TAG, " id " + id + ": " + addr + " - " + contact.getName()
+ + " X-BT-UID: " + contact.getXBtUidString());
+ } catch (NumberFormatException ex) {
+ // skip this id
+ continue;
+ }
+ }
+ }
+
}
diff --git a/tests/src/com/android/bluetooth/tests/BluetoothMapIMContentTest.java b/tests/src/com/android/bluetooth/tests/BluetoothMapIMContentTest.java
new file mode 100644
index 0000000..2d100e4
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/BluetoothMapIMContentTest.java
@@ -0,0 +1,171 @@
+package com.android.bluetooth.tests;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+import com.android.bluetooth.mapapi.BluetoothMapContract.ConversationColumns;
+
+//import info.guardianproject.otr.app.im.provider.Imps;
+//import info.guardianproject.otr.app.im.provider.ImpsBluetoothProvider;
+
+public class BluetoothMapIMContentTest extends AndroidTestCase {
+ private static final String TAG = "BluetoothMapIMContentTest";
+
+ private static final boolean D = true;
+ private static final boolean V = true;
+
+ private Context mContext;
+ private ContentResolver mResolver;
+
+ private String getDateTimeString(long timestamp) {
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+ Date date = new Date(timestamp);
+ return format.format(date); // Format to YYYYMMDDTHHMMSS local time
+ }
+
+ private void printCursor(Cursor c) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("\nprintCursor:\n");
+ for(int i = 0; i < c.getColumnCount(); i++) {
+ if(c.getColumnName(i).equals(BluetoothMapContract.MessageColumns.DATE) ||
+ c.getColumnName(i).equals(BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY) ||
+ c.getColumnName(i).equals(BluetoothMapContract.ChatStatusColumns.LAST_ACTIVE) ||
+ c.getColumnName(i).equals(BluetoothMapContract.PresenceColumns.LAST_ONLINE) ){
+ sb.append(" ").append(c.getColumnName(i)).append(" : ").append(getDateTimeString(c.getLong(i))).append("\n");
+ } else {
+ sb.append(" ").append(c.getColumnName(i)).append(" : ").append(c.getString(i)).append("\n");
+ }
+ }
+ Log.d(TAG, sb.toString());
+ }
+
+ private void dumpImMessageTable() {
+ Log.d(TAG, "**** Dump of im message table ****");
+
+ Cursor c = mResolver.query(
+ BluetoothMapContract.buildMessageUri("info.guardianproject.otr.app.im.provider.bluetoothprovider"),
+ BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, null, null, "_id DESC");
+ if (c != null) {
+ Log.d(TAG, "c.getCount() = " + c.getCount());
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ printCursor(c);
+ }
+ } else {
+ Log.d(TAG, "query failed");
+ c.close();
+ }
+
+ }
+
+ private void insertImMessage( ) {
+ Log.d(TAG, "**** Insert message in im message table ****");
+ ContentValues cv = new ContentValues();
+ cv.put(BluetoothMapContract.MessageColumns.BODY, "This is a test to insert a message");
+ cv.put(BluetoothMapContract.MessageColumns.DATE, System.currentTimeMillis());
+ cv.put(BluetoothMapContract.MessageColumns.THREAD_ID, 2);
+ Uri uri = BluetoothMapContract.buildMessageUri("info.guardianproject.otr.app.im.provider.bluetoothprovider");
+ Uri uriWithId = mResolver.insert(uri, cv);
+ if (uriWithId != null) {
+ Log.d(TAG, "uriWithId = " + uriWithId.toString());
+ } else {
+ Log.d(TAG, "query failed");
+ }
+
+ }
+
+ private void dumpImConversationTable() {
+ Log.d(TAG, "**** Dump of conversation message table ****");
+
+ Uri uri = BluetoothMapContract.buildConversationUri(
+ "info.guardianproject.otr.app.im.provider.bluetoothprovider", "1");
+ uri = uri.buildUpon().appendQueryParameter(BluetoothMapContract.FILTER_ORIGINATOR_SUBSTRING,
+ "asp").build();
+
+ Cursor convo = mResolver.query(
+ uri,
+ BluetoothMapContract.BT_CONVERSATION_PROJECTION, null, null,
+ null);
+
+ if (convo != null) {
+ Log.d(TAG, "c.getCount() = " + convo.getCount());
+
+ while(convo.moveToNext()) {
+ printCursor(convo);
+ }
+ convo.close();
+ } else {
+ Log.d(TAG, "query failed");
+ }
+ }
+
+
+ private void dumpImContactsTable() {
+ Log.d(TAG, "**** Dump of contacts message table ****");
+ Cursor cContact = mResolver.query(
+ BluetoothMapContract.buildConvoContactsUri("info.guardianproject.otr.app.im.provider.bluetoothprovider","1"),
+ BluetoothMapContract.BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION, null, null, "_id DESC");
+
+ if (cContact != null && cContact.moveToFirst()) {
+ Log.d(TAG, "c.getCount() = " + cContact.getCount());
+ do {
+ printCursor(cContact);
+ } while(cContact.moveToNext());
+
+ } else {
+ Log.d(TAG, "query failed");
+ cContact.close();
+ }
+ }
+
+ private void dumpImAccountsTable() {
+ Log.d(TAG, "**** Dump of accounts table ****");
+ Cursor cContact = mResolver.query(
+ BluetoothMapContract.buildAccountUri("info.guardianproject.otr.app.im.provider.bluetoothprovider"),
+ BluetoothMapContract.BT_ACCOUNT_PROJECTION, null, null, "_id DESC");
+
+ if (cContact != null && cContact.moveToFirst()) {
+ Log.d(TAG, "c.getCount() = " + cContact.getCount());
+ do {
+ printCursor(cContact);
+ } while(cContact.moveToNext());
+
+ } else {
+ Log.d(TAG, "query failed");
+ cContact.close();
+ }
+ }
+
+
+ public BluetoothMapIMContentTest() {
+ super();
+ }
+
+ public void testDumpMessages() {
+ mContext = this.getContext();
+ mResolver = mContext.getContentResolver();
+ dumpImMessageTable();
+ dumpImConversationTable();
+ dumpImContactsTable();
+ dumpImAccountsTable();
+
+ insertImMessage();
+
+ }
+
+ public void testDumpConversations() {
+ mContext = this.getContext();
+ mResolver = mContext.getContentResolver();
+ dumpImConversationTable();
+ }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/bluetooth/tests/BluetoothMapUtilsTest.java b/tests/src/com/android/bluetooth/tests/BluetoothMapUtilsTest.java
new file mode 100644
index 0000000..788b36f
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/BluetoothMapUtilsTest.java
@@ -0,0 +1,116 @@
+
+package com.android.bluetooth.tests;
+
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.util.Base64;
+
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import com.android.bluetooth.SignedLongLong;
+import com.android.bluetooth.map.BluetoothMapUtils;
+
+public class BluetoothMapUtilsTest extends AndroidTestCase {
+ private static final String TAG = "BluetoothMapUtilsTest";
+
+ private static final boolean D = true;
+ private static final boolean V = true;
+ private static final String encText1 = "=?UTF-8?b?w6bDuMOlw4bDmMOF?="; //æøåÆØÅ base64
+ private static final String encText2 = "=?UTF-8?B?w6bDuMOlw4bDmMOF?="; //æøåÆØÅ base64
+ private static final String encText3 = "=?UTF-8?q?=C3=A6=C3=B8=C3=A5=C3=86=C3=98=C3=85?="; //æøåÆØÅ QP
+ private static final String encText4 = "=?UTF-8?Q?=C3=A6=C3=B8=C3=A5=C3=86=C3=98=C3=85?="; //æøåÆØÅ QP
+ private static final String encText5 = "=?UTF-8?B?=C3=A6=C3=B8=C3=A5=C3=86=C3=98=C3=85?="; //QP in base64 string - should not compute
+ private static final String encText6 = "=?UTF-8?Q?w6bDuMOlw4bDmMOF?="; //æøåÆØÅ base64 in QP stirng - should not compute
+ private static final String encText7 = "this is a split =?UTF-8?Q?=C3=A6=C3=B8=C3=A5 ###123?= with more =?UTF-8?Q?=C3=A6=C3=B8=C3=A5 ###123?= inside"; // mix of QP and normal
+ private static final String encText8 = "this is a split =?UTF-8?B?w6bDuMOlICMjIzEyMw==?= with more =?UTF-8?Q?=C3=A6=C3=B8=C3=A5 ###123?= inside"; // mix of normal, QP and Base64
+ private static final String encText9 = "=?UTF-8?Q??=";
+ private static final String encText10 = "=?UTF-8?Q??=";
+ private static final String encText11 = "=?UTF-8?Q??=";
+
+ private static final String decText1 = "æøåÆØÅ";
+ private static final String decText2 = "æøåÆØÅ";
+ private static final String decText3 = "æøåÆØÅ";
+ private static final String decText4 = "æøåÆØÅ";
+ private static final String decText5 = encText5;
+ private static final String decText6 = "w6bDuMOlw4bDmMOF";
+ private static final String decText7 = "this is a split æøå ###123 with more æøå ###123 inside";
+ private static final String decText8 = "this is a split æøå ###123 with more æøå ###123 inside";
+
+ public BluetoothMapUtilsTest() {
+ super();
+
+ }
+
+
+ public void testEncoder(){
+ assertTrue(BluetoothMapUtils.stripEncoding(encText1).equals(decText1));
+ assertTrue(BluetoothMapUtils.stripEncoding(encText2).equals(decText2));
+ assertTrue(BluetoothMapUtils.stripEncoding(encText3).equals(decText3));
+ assertTrue(BluetoothMapUtils.stripEncoding(encText4).equals(decText4));
+ assertTrue(BluetoothMapUtils.stripEncoding(encText5).equals(decText5));
+ assertTrue(BluetoothMapUtils.stripEncoding(encText6).equals(decText6));
+ Log.i(TAG,"##############################enc7:" +
+ BluetoothMapUtils.stripEncoding(encText7));
+ assertTrue(BluetoothMapUtils.stripEncoding(encText7).equals(decText7));
+ assertTrue(BluetoothMapUtils.stripEncoding(encText8).equals(decText8));
+ }
+
+ public void testXBtUid() throws UnsupportedEncodingException {
+ {
+ SignedLongLong expected = new SignedLongLong(0x12345678L, 0x90abcdefL);
+ /* this will cause an exception, since the value is too big... */
+ SignedLongLong value;
+ value = SignedLongLong.fromString("90abcdef0000000012345678");
+ assertTrue("expected: " + expected + " value = " + value,
+ 0 == value.compareTo(expected));
+ assertEquals("expected: " + expected + " value = " + value,
+ expected.toHexString(), value.toHexString());
+ Log.i(TAG,"Succesfully compared : " + value);
+ }
+ {
+ SignedLongLong expected = new SignedLongLong(0x12345678L, 0xfedcba9890abcdefL);
+ /* this will cause an exception, since the value is too big... */
+ SignedLongLong value;
+ value = SignedLongLong.fromString("fedcba9890abcdef0000000012345678");
+ assertTrue("expected: " + expected + " value = " + value,
+ 0 == value.compareTo(expected));
+ assertEquals("expected: " + expected + " value = " + value,
+ expected.toHexString(), value.toHexString());
+ Log.i(TAG,"Succesfully compared : " + value);
+ }
+ {
+ SignedLongLong expected = new SignedLongLong(0x12345678L, 0);
+ SignedLongLong value = SignedLongLong.fromString("000012345678");
+ assertTrue("expected: " + expected + " value = " + value,
+ 0 == value.compareTo(expected));
+ assertEquals("expected: " + expected + " value = " + value,
+ expected.toHexString(), value.toHexString());
+ Log.i(TAG,"Succesfully compared : " + value);
+ }
+ {
+ SignedLongLong expected = new SignedLongLong(0x12345678L, 0);
+ SignedLongLong value = SignedLongLong.fromString("12345678");
+ assertTrue("expected: " + expected + " value = " + value,
+ 0 == value.compareTo(expected));
+ assertEquals("expected: " + expected + " value = " + value,
+ expected.toHexString(), value.toHexString());
+ Log.i(TAG,"Succesfully compared : " + value);
+ }
+ {
+ SignedLongLong expected = new SignedLongLong(0x123456789abcdef1L, 0x9L);
+ SignedLongLong value = SignedLongLong.fromString("0009123456789abcdef1");
+ assertTrue("expected: " + expected + " value = " + value,
+ 0 == value.compareTo(expected));
+ assertEquals("expected: " + expected + " value = " + value,
+ expected.toHexString(), value.toHexString());
+ Log.i(TAG,"Succesfully compared : " + value);
+ }
+ {
+ long expected = 0x123456789abcdefL;
+ long value = BluetoothMapUtils.getLongFromString(" 1234 5678 9abc-def");
+ assertTrue("expected: " + expected + " value = " + value, value == expected);
+ }
+ }
+}
diff --git a/tests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java b/tests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java
index 1fcec4e..8724e9f 100755
--- a/tests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java
+++ b/tests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java
@@ -8,22 +8,19 @@
import java.util.Calendar;
import java.util.Date;
+import org.apache.http.message.BasicHeaderElement;
import org.apache.http.message.BasicHeaderValueFormatter;
-import android.preference.PreferenceFragment;
import android.test.AndroidTestCase;
import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
import com.android.bluetooth.map.BluetoothMapAppParams;
-import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.map.BluetoothMapSmsPdu;
+import com.android.bluetooth.map.BluetoothMapUtils;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.map.BluetoothMapbMessage;
-import com.android.bluetooth.map.BluetoothMapbMessageMms;
+import com.android.bluetooth.map.BluetoothMapbMessageMime;
import com.android.bluetooth.map.BluetoothMapbMessageSms;
-import org.apache.http.message.BasicHeaderValueFormatter;
-import org.apache.http.message.BasicHeaderElement;
/***
*
@@ -58,6 +55,15 @@
"EMAIL:casper@email.add\r\n" +
"EMAIL:bonde@email.add\r\n" +
"END:VCARD\r\n" +
+ "BEGIN:VCARD\r\n" +
+ "VERSION:3.0\r\n" +
+ "FN:Casper Bonde\r\n" +
+ "N:Bonde,Casper\r\n" +
+ "TEL:+4512345678\r\n" +
+ "TEL:+4587654321\r\n" +
+ "EMAIL:casper@email.add\r\n" +
+ "EMAIL:bonde@email.add\r\n" +
+ "END:VCARD\r\n" +
"BEGIN:BENV\r\n" +
"BEGIN:VCARD\r\n" +
"VERSION:3.0\r\n" +
@@ -68,6 +74,15 @@
"EMAIL:casper@email.add\r\n" +
"EMAIL:bonde@email.add\r\n" +
"END:VCARD\r\n" +
+ "BEGIN:VCARD\r\n" +
+ "VERSION:3.0\r\n" +
+ "FN:Jens Hansen\r\n" +
+ "N:\r\n" +
+ "TEL:+4512345678\r\n" +
+ "TEL:+4587654321\r\n" +
+ "EMAIL:casper@email.add\r\n" +
+ "EMAIL:bonde@email.add\r\n" +
+ "END:VCARD\r\n" +
"BEGIN:BBODY\r\n" +
"CHARSET:UTF-8\r\n" +
"LENGTH:45\r\n" +
@@ -81,8 +96,10 @@
String encoded;
String[] phone = {"+4512345678", "+4587654321"};
String[] email = {"casper@email.add", "bonde@email.add"};
- msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email);
- msg.addRecipient("", "Jens Hansen", phone, email);
+ msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email, null, null);
+ msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email, null, null);
+ msg.addRecipient("", "Jens Hansen", phone, email, null, null);
+ msg.addRecipient("", "Jens Hansen", phone, email, null, null);
msg.setFolder("inbox");
msg.setSmsBody("This is a short message");
msg.setStatus(false);
@@ -182,8 +199,8 @@
String encoded;
String[] phone = {"00498912345678", "+4587654321"};
String[] email = {"casper@email.add", "bonde@email.add"};
- msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email);
- msg.addRecipient("", "Jens Hansen", phone, email);
+ msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email, null, null);
+ msg.addRecipient("", "Jens Hansen", phone, email, null, null);
msg.setFolder("inbox");
/* TODO: extract current time, and build the expected string */
msg.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus("Let's go fishing!", "00498912345678", date.getTime()));
@@ -252,8 +269,8 @@
String encoded;
String[] phone = {"00498912345678", "+4587654321"};
String[] email = {"casper@email.add", "bonde@email.add"};
- msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email);
- msg.addRecipient("", "Jens Hansen", phone, email);
+ msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email, null, null);
+ msg.addRecipient("", "Jens Hansen", phone, email, null, null);
msg.setFolder("outbox");
/* TODO: extract current time, and build the expected string */
msg.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus("Let's go fishing!", "00498912345678"));
@@ -421,7 +438,7 @@
* Test encoding of a simple MMS text message (UTF8). This validates most parameters.
*/
public void testMmsEncodeText() {
- BluetoothMapbMessageMms msg = new BluetoothMapbMessageMms();
+ BluetoothMapbMessageMime msg = new BluetoothMapbMessageMime ();
String str1 =
"BEGIN:BMSG\r\n" +
"VERSION:1.0\r\n" +
@@ -449,9 +466,16 @@
"END:VCARD\r\n" +
"BEGIN:BBODY\r\n" +
"CHARSET:UTF-8\r\n" +
- "LENGTH:45\r\n" +
+ "LENGTH:184\r\n" +
"BEGIN:MSG\r\n" +
- "This is a short message\r\n" +
+ "From: \"Jørn Hansen\" <bonde@email.add>;\r\n" +
+ "To: \"Jørn Hansen\" <bonde@email.add>;\r\n" +
+ "Cc: Jens Hansen <bonde@email.add>;\r\n" +
+ "\r\n" +
+ "This is a short message\r\n" +
+ "\r\n" +
+ "<partNameimage>\r\n" +
+ "\r\n" +
"END:MSG\r\n" +
"END:BBODY\r\n" +
"END:BENV\r\n" +
@@ -460,14 +484,14 @@
String encoded;
String[] phone = {"+4512345678", "+4587654321"};
String[] email = {"casper@email.add", "bonde@email.add"};
- msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email);
- msg.addRecipient("", "Jørn Hansen", phone, email);
+ msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email, null, null);
+ msg.addRecipient("", "Jørn Hansen", phone, email, null, null);
msg.setFolder("inbox");
msg.setIncludeAttachments(false);
msg.addTo("Jørn Hansen", "bonde@email.add");
msg.addCc("Jens Hansen", "bonde@email.add");
msg.addFrom("Jørn Hansen", "bonde@email.add");
- BluetoothMapbMessageMms.MimePart part = msg.addMimePart();
+ BluetoothMapbMessageMime .MimePart part = msg.addMimePart();
part.mPartName = "partNameText";
part.mContentType ="dsfajfdlk/text/asdfafda";
try {
@@ -485,6 +509,8 @@
msg.setStatus(false);
msg.setType(TYPE.MMS);
+ msg.updateCharset();
+
try {
encoded = new String(msg.encode());
if(D) Log.d(TAG, encoded);
@@ -501,5 +527,56 @@
if(D) Log.i(TAG, "The encoded header: " + headerStr);
}
+ public void testQuotedPrintable() {
+ testQuotedPrintableIso8859_1();
+ testQuotedPrintableUTF_8();
+ }
+
+ public void testQuotedPrintableIso8859_1() {
+ String charset = "iso-8859-1";
+ String input = "Hello, here are some danish letters: =E6=F8=E5.\r\n" +
+ "Please check that you are able to remove soft " +
+ "line breaks and handle '=3D' =\r\ncharacters within the text. \r\n" +
+ "Just a sequence of non optimal characters to make " +
+ "it complete: !\"#$@[\\]^{|}=\r\n~\r\n\r\n" +
+ "Thanks\r\n" +
+ "Casper";
+ String expected = "Hello, here are some danish letters: æøå.\r\n" +
+ "Please check that you are able to remove soft " +
+ "line breaks and handle '=' characters within the text. \r\n" +
+ "Just a sequence of non optimal characters to make " +
+ "it complete: !\"#$@[\\]^{|}~\r\n\r\n" +
+ "Thanks\r\n" +
+ "Casper";
+ String output;
+ output = new String(BluetoothMapUtils.quotedPrintableToUtf8(input, charset));
+ if(D) Log.d(TAG, "\nExpected: \n" + expected);
+ if(D) Log.d(TAG, "\nOutput: \n" + output);
+ assertTrue(output.equals(expected));
+ }
+
+ public void testQuotedPrintableUTF_8() {
+ String charset = "utf-8";
+ String input = "Hello, here are some danish letters: =C3=A6=C3=B8=C3=A5.\r\n" +
+ "Please check that you are able to remove soft " +
+ "line breaks and handle '=3D' =\r\ncharacters within the text. \r\n" +
+ "Just a sequence of non optimal characters to make " +
+ "it complete: !\"#$@[\\]^{|}=\r\n~\r\n\r\n" +
+ "Thanks\r\n" +
+ "Casper";
+ String expected = "Hello, here are some danish letters: æøå.\r\n" +
+ "Please check that you are able to remove soft " +
+ "line breaks and handle '=' characters within the text. \r\n" +
+ "Just a sequence of non optimal characters to make " +
+ "it complete: !\"#$@[\\]^{|}~\r\n\r\n" +
+ "Thanks\r\n" +
+ "Casper";
+ String output;
+ output = new String(BluetoothMapUtils.quotedPrintableToUtf8(input, charset));
+ if(D) Log.d(TAG, "\nExpected: \n" + expected);
+ if(D) Log.d(TAG, "\nOutput: \n" + output);
+ assertTrue(output.equals(expected));
+ }
+
}
diff --git a/tests/src/com/android/bluetooth/tests/BluetoothTestUtils.java b/tests/src/com/android/bluetooth/tests/BluetoothTestUtils.java
new file mode 100644
index 0000000..4ef89e1
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/BluetoothTestUtils.java
@@ -0,0 +1,54 @@
+package com.android.bluetooth.tests;
+
+import android.annotation.TargetApi;
+import android.bluetooth.BluetoothAdapter;
+import android.os.Build;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+
+@TargetApi(Build.VERSION_CODES.ECLAIR)
+public class BluetoothTestUtils extends AndroidTestCase {
+
+ protected static String TAG = "BluetoothTestUtils";
+ protected static final boolean D = true;
+
+ static final int POLL_TIME = 500;
+ static final int ENABLE_TIMEOUT = 5000;
+
+ /** Helper to turn BT on.
+ * This method will either fail on an assert, or return with BT turned on.
+ * Behavior of getState() and isEnabled() are validated along the way.
+ */
+ public static void enableBt(BluetoothAdapter adapter) {
+ if (adapter.getState() == BluetoothAdapter.STATE_ON) {
+ assertTrue(adapter.isEnabled());
+ return;
+ }
+ assertEquals(BluetoothAdapter.STATE_OFF, adapter.getState());
+ assertFalse(adapter.isEnabled());
+ adapter.enable();
+ for (int i=0; i<ENABLE_TIMEOUT/POLL_TIME; i++) {
+ int state = adapter.getState();
+ switch (state) {
+ case BluetoothAdapter.STATE_ON:
+ assertTrue(adapter.isEnabled());
+ Log.i(TAG, "Bluetooth enabled...");
+ return;
+ case BluetoothAdapter.STATE_OFF:
+ Log.i(TAG, "STATE_OFF: Still waiting for enable to begin...");
+ break;
+ default:
+ Log.i(TAG, "Status is: " + state);
+ assertEquals(BluetoothAdapter.STATE_TURNING_ON, adapter.getState());
+ assertFalse(adapter.isEnabled());
+ break;
+ }
+ try {
+ Thread.sleep(POLL_TIME);
+ } catch (InterruptedException e) {}
+ }
+ fail("enable() timeout");
+ }
+
+}
diff --git a/tests/src/com/android/bluetooth/tests/ISeqStepAction.java b/tests/src/com/android/bluetooth/tests/ISeqStepAction.java
new file mode 100644
index 0000000..db66af2
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/ISeqStepAction.java
@@ -0,0 +1,13 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+
+public interface ISeqStepAction {
+
+ void execute(SeqStep step, HeaderSet request, Operation op)
+ throws IOException;
+
+}
diff --git a/tests/src/com/android/bluetooth/tests/ISeqStepValidator.java b/tests/src/com/android/bluetooth/tests/ISeqStepValidator.java
new file mode 100644
index 0000000..5819f06
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/ISeqStepValidator.java
@@ -0,0 +1,15 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+
+/**
+ * Interface to validate test step result
+ */
+public interface ISeqStepValidator {
+ boolean validate(SeqStep step, HeaderSet response, Operation op)
+ throws IOException;
+
+}
diff --git a/tests/src/com/android/bluetooth/tests/ITestSequenceBuilder.java b/tests/src/com/android/bluetooth/tests/ITestSequenceBuilder.java
new file mode 100644
index 0000000..3314fd6
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/ITestSequenceBuilder.java
@@ -0,0 +1,11 @@
+package com.android.bluetooth.tests;
+
+public interface ITestSequenceBuilder {
+
+ /**
+ * Add steps to a sequencer
+ * @param sequencer The sequencer the steps will be added to.
+ */
+ public void build(TestSequencer sequencer);
+
+}
diff --git a/tests/src/com/android/bluetooth/tests/ITestSequenceConfigurator.java b/tests/src/com/android/bluetooth/tests/ITestSequenceConfigurator.java
new file mode 100644
index 0000000..d260d8b
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/ITestSequenceConfigurator.java
@@ -0,0 +1,17 @@
+package com.android.bluetooth.tests;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+
+import javax.obex.ServerRequestHandler;
+
+public interface ITestSequenceConfigurator {
+
+ /** Use this to customize a serverRequestHandler
+ * @param sequence A reference to the sequence to handle
+ * @param stopLatch a reference to a latch that must be count down, when test completes.
+ * @return Reference to the ServerRequestHandler.
+ */
+ public ServerRequestHandler getObexServer(ArrayList<SeqStep> sequence,
+ CountDownLatch stopLatch);
+}
diff --git a/tests/src/com/android/bluetooth/tests/MapObexLevelTest.java b/tests/src/com/android/bluetooth/tests/MapObexLevelTest.java
new file mode 100644
index 0000000..fb5de24
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/MapObexLevelTest.java
@@ -0,0 +1,286 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+import javax.obex.ServerRequestHandler;
+
+import junit.framework.Assert;
+import android.annotation.TargetApi;
+import android.bluetooth.BluetoothServerSocket;
+import android.bluetooth.BluetoothSocket;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.os.Build;
+import android.os.RemoteException;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.bluetooth.BluetoothObexTransport;
+import com.android.bluetooth.tests.TestSequencer.OPTYPE;
+
+@TargetApi(Build.VERSION_CODES.KITKAT)
+public class MapObexLevelTest extends AndroidTestCase implements ITestSequenceConfigurator {
+ protected static String TAG = "MapObexLevelTest";
+ protected static final boolean D = true;
+ protected static final boolean TRACE = false;
+ protected static final boolean DELAY_PASS_30_SEC = true;
+
+ // 128 bit UUID for MAP MAS
+ static final byte[] MAS_TARGET = new byte[] {
+ (byte)0xBB, (byte)0x58, (byte)0x2B, (byte)0x40,
+ (byte)0x42, (byte)0x0C, (byte)0x11, (byte)0xDB,
+ (byte)0xB0, (byte)0xDE, (byte)0x08, (byte)0x00,
+ (byte)0x20, (byte)0x0C, (byte)0x9A, (byte)0x66
+ };
+
+ // 128 bit UUID for MAP MNS
+ static final byte[] MNS_TARGET = new byte[] {
+ (byte)0xBB, (byte)0x58, (byte)0x2B, (byte)0x41,
+ (byte)0x42, (byte)0x0C, (byte)0x11, (byte)0xDB,
+ (byte)0xB0, (byte)0xDE, (byte)0x08, (byte)0x00,
+ (byte)0x20, (byte)0x0C, (byte)0x9A, (byte)0x66
+ };
+
+ /* Message types */
+ static final String TYPE_GET_FOLDER_LISTING = "x-obex/folder-listing";
+ static final String TYPE_GET_MESSAGE_LISTING = "x-bt/MAP-msg-listing";
+ static final String TYPE_GET_CONVO_LISTING = "x-bt/MAP-convo-listing";
+ static final String TYPE_MESSAGE = "x-bt/message";
+ static final String TYPE_SET_MESSAGE_STATUS = "x-bt/messageStatus";
+ static final String TYPE_SET_NOTIFICATION_REGISTRATION = "x-bt/MAP-NotificationRegistration";
+ static final String TYPE_MESSAGE_UPDATE = "x-bt/MAP-messageUpdate";
+ static final String TYPE_GET_MAS_INSTANCE_INFORMATION = "x-bt/MASInstanceInformation";
+
+ public void testFolder() {
+ testLocalSockets(new buildFolderTestSeq());
+ }
+
+ public void testFolderServer() {
+ testServer(new buildFolderTestSeq());
+ }
+
+ public void testFolderClient() {
+ testClient(new buildFolderTestSeq());
+ }
+
+ protected class buildFolderTestSeq implements ITestSequenceBuilder {
+ @Override
+ public void build(TestSequencer sequencer) {
+ addConnectStep(sequencer);
+
+ MapStepsFolder.addGoToMsgFolderSteps(sequencer);
+
+ // MAP DISCONNECT Step
+ addDisconnectStep(sequencer);
+ }
+ }
+
+
+ public void testConvo() {
+ testLocalSockets(new buildConvoTestSeq());
+ }
+
+ public void testConvoServer() {
+ testServer(new buildConvoTestSeq());
+ }
+
+ public void testConvoClient() {
+ testClient(new buildConvoTestSeq());
+ }
+
+ class buildConvoTestSeq implements ITestSequenceBuilder {
+ @Override
+ public void build(TestSequencer sequencer) {
+ addConnectStep(sequencer);
+
+ MapStepsFolder.addGoToMsgFolderSteps(sequencer);
+
+ MapStepsConvo.addConvoListingSteps(sequencer);
+
+ // MAP DISCONNECT Step
+ addDisconnectStep(sequencer);
+ }
+ }
+
+ /**
+ * Run the test sequence using a local socket on a single device.
+ * Throughput around 4000 kbyte/s - with a larger OBEX package size.
+ *
+ * Downside: Unable to get a BT-snoop file...
+ */
+ protected void testLocalSockets(ITestSequenceBuilder builder) {
+ mContext = this.getContext();
+ MapTestData.init(mContext);
+ Log.i(TAG,"Setting up sockets...");
+
+ try {
+ /* Create and interconnect local pipes for transport */
+ LocalServerSocket serverSock = new LocalServerSocket("com.android.bluetooth.tests.sock");
+ LocalSocket clientSock = new LocalSocket();
+ LocalSocket acceptSock;
+
+ clientSock.connect(serverSock.getLocalSocketAddress());
+
+ acceptSock = serverSock.accept();
+
+ /* Create the OBEX transport objects to wrap the pipes - enable SRM */
+ ObexPipeTransport clientTransport = new ObexPipeTransport(clientSock.getInputStream(),
+ clientSock.getOutputStream(), true);
+ ObexPipeTransport serverTransport = new ObexPipeTransport(acceptSock.getInputStream(),
+ acceptSock.getOutputStream(), true);
+
+ TestSequencer sequencer = new TestSequencer(clientTransport, serverTransport, this);
+
+ builder.build(sequencer);
+
+ //Debug.startMethodTracing("ObexTrace");
+ assertTrue(sequencer.run(mContext));
+ //Debug.stopMethodTracing();
+
+ clientSock.close();
+ acceptSock.close();
+ serverSock.close();
+ } catch (IOException e) {
+ Log.e(TAG, "IOException", e);
+ }
+ }
+
+ /**
+ * Server side of a dual device test using a Bluetooth Socket.
+ * Enables the possibility to get a BT-snoop file.
+ * If you need the btsnoop from the device which completes the test with success
+ * you need to add a delay after the test ends, and fetch the file before this delay
+ * expires. When the test completes, the Bluetooth subsystem will be restarted, causing
+ * a new bt-snoop to overwrite the one used in test.
+ */
+ public void testServer(ITestSequenceBuilder builder) {
+ mContext = this.getContext();
+ MapTestData.init(mContext);
+ Log.i(TAG,"Setting up sockets...");
+
+ try {
+ /* This will turn on BT and create a server socket on which accept can be called. */
+ BluetoothServerSocket serverSocket=ObexTest.createServerSocket(BluetoothSocket.TYPE_L2CAP, true);
+
+ Log.i(TAG, "Waiting for client to connect...");
+ BluetoothSocket socket = serverSocket.accept();
+ Log.i(TAG, "Client connected...");
+
+ BluetoothObexTransport serverTransport = new BluetoothObexTransport(socket);
+
+ TestSequencer sequencer = new TestSequencer(null, serverTransport, this);
+
+ builder.build(sequencer);
+
+ //Debug.startMethodTracing("ObexTrace");
+ assertTrue(sequencer.run(mContext));
+ //Debug.stopMethodTracing();
+
+ serverSocket.close();
+ socket.close();
+ } catch (IOException e) {
+ Log.e(TAG, "IOException", e);
+ }
+ if(DELAY_PASS_30_SEC) {
+ Log.i(TAG, "\n\n\nTest done - please fetch logs within 30 seconds...\n\n\n");
+ try {
+ Thread.sleep(30000);
+ } catch (InterruptedException e) {}
+ }
+ }
+
+ /**
+ * Server side of a dual device test using a Bluetooth Socket.
+ * Enables the possibility to get a BT-snoop file.
+ * If you need the btsnoop from the device which completes the test with success
+ * you need to add a delay after the test ends, and fetch the file before this delay
+ * expires. When the test completes, the Bluetooth subsystem will be restarted, causing
+ * a new bt-snoop to overwrite the one used in test.
+ */
+ public void testClient(ITestSequenceBuilder builder) {
+ mContext = this.getContext();
+ MapTestData.init(mContext);
+ Log.i(TAG, "Setting up sockets...");
+
+ try {
+ /* This will turn on BT and connect */
+ BluetoothSocket clientSock =
+ ObexTest.connectClientSocket(BluetoothSocket.TYPE_L2CAP, true, mContext);
+
+ BluetoothObexTransport clientTransport = new BluetoothObexTransport(clientSock);
+
+ TestSequencer sequencer = new TestSequencer(clientTransport, null, this);
+
+ builder.build(sequencer);
+
+ //Debug.startMethodTracing("ObexTrace");
+ assertTrue(sequencer.run(mContext));
+ //Debug.stopMethodTracing();
+
+ clientSock.close();
+ } catch (IOException e) {
+ Log.e(TAG, "IOException", e);
+ }
+ if(DELAY_PASS_30_SEC) {
+ Log.i(TAG, "\n\n\nTest done - please fetch logs within 30 seconds...\n\n\n");
+ try {
+ Thread.sleep(30000);
+ } catch (InterruptedException e) {}
+ }
+ }
+
+ protected void addConnectStep(TestSequencer sequencer) {
+ SeqStep step;
+
+ // MAP CONNECT Step
+ step = sequencer.addStep(OPTYPE.CONNECT, null);
+ HeaderSet hs = new HeaderSet();
+ hs.setHeader(HeaderSet.TARGET, MAS_TARGET);
+ step.mReqHeaders = hs;
+ step.mValidator = new MapConnectValidator();
+ //step.mServerPreAction = new MapAddSmsMessages(); // could take in parameters
+ }
+
+ protected void addDisconnectStep(TestSequencer sequencer) {
+ sequencer.addStep(OPTYPE.DISCONNECT, ObexTest.getResponsecodevalidator());
+ }
+
+ /* Functions to validate results */
+
+ private class MapConnectValidator implements ISeqStepValidator {
+ @Override
+ public boolean validate(SeqStep step, HeaderSet response, Operation notUsed)
+ throws IOException {
+ Assert.assertNotNull(response);
+ byte[] who = (byte[])response.getHeader(HeaderSet.WHO);
+ Assert.assertNotNull(who);
+ Assert.assertTrue(Arrays.equals(who, MAS_TARGET));
+ Assert.assertNotNull(response.getHeader(HeaderSet.CONNECTION_ID));
+ return true;
+ }
+ }
+
+ /**
+ * This is the function creating the Obex Server to be used in this class.
+ * Here we use a mocked version of the MapObexServer class
+ */
+ @Override
+ public ServerRequestHandler getObexServer(ArrayList<SeqStep> sequence,
+ CountDownLatch stopLatch) {
+ try {
+ return new MapObexTestServer(mContext, sequence, stopLatch);
+ } catch (RemoteException e) {
+ Log.e(TAG, "exception", e);
+ fail("Unable to create MapObexTestServer");
+ }
+ return null;
+ }
+
+
+}
+
diff --git a/tests/src/com/android/bluetooth/tests/MapObexTestServer.java b/tests/src/com/android/bluetooth/tests/MapObexTestServer.java
new file mode 100644
index 0000000..4fb6ba6
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/MapObexTestServer.java
@@ -0,0 +1,188 @@
+package com.android.bluetooth.tests;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+import javax.obex.ResponseCodes;
+
+import junit.framework.Assert;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.bluetooth.map.BluetoothMapAccountItem;
+import com.android.bluetooth.map.BluetoothMapContentObserver;
+import com.android.bluetooth.map.BluetoothMapMasInstance;
+import com.android.bluetooth.map.BluetoothMapObexServer;
+import com.android.bluetooth.map.BluetoothMapUtils;
+import com.android.bluetooth.map.BluetoothMnsObexClient;
+
+public class MapObexTestServer extends BluetoothMapObexServer {
+
+ private static final String TAG = "MapObexTestServer";
+ private static final boolean V = true;
+
+ ArrayList<SeqStep> mSequence;
+ CountDownLatch mStopLatch;
+
+ ObexTestDataHandler mDataHandler;
+ int mOperationIndex = 0;
+
+ /* This needs to be static, as calling the super-constructor must be the first step.
+ * Alternatively add the account as constructor parameter, and create a builder
+ * function - factory pattern. */
+// private static BluetoothMapAccountItem mAccountMock = new BluetoothMapAccountItem("1",
+// "TestAccount",
+// "do.not.exist.package.name.and.never.used.anyway:-)",
+// "info.guardianproject.otr.app.im.provider.bluetoothprovider",
+// null,
+// BluetoothMapUtils.TYPE.IM,
+// null,
+// null);
+ private static BluetoothMapAccountItem mAccountMock = null;
+
+ /* MAP Specific instance variables
+ private final BluetoothMapContentObserver mObserver = null;
+ private final BluetoothMnsObexClient mMnsClient = null;*/
+
+ /* Test values, consider gathering somewhere else */
+ private static final int MAS_ID = 0;
+ private static final int REMOTE_FEATURE_MASK = 0x07FFFFFF;
+ private static final BluetoothMapMasInstance mMasInstance =
+ new MockMasInstance(MAS_ID, REMOTE_FEATURE_MASK);
+
+ public MapObexTestServer(final Context context, ArrayList<SeqStep> sequence,
+ CountDownLatch stopLatch) throws RemoteException {
+
+ super(null, context,
+ new BluetoothMapContentObserver(context,
+ new BluetoothMnsObexClient(
+ BluetoothAdapter.getDefaultAdapter().
+ getRemoteDevice("12:23:34:45:56:67"), null, null),
+ /* TODO: this will not work for single device test... */
+ mMasInstance,
+ mAccountMock, /* Account */
+ true) /* Enable SMS/MMS*/,
+ mMasInstance,
+ mAccountMock /* Account */,
+ true /* SMS/MMS enabled*/);
+ mSequence = sequence;
+ mDataHandler = new ObexTestDataHandler("(Server)");
+ mStopLatch = stopLatch;
+ }
+
+ /* OBEX operation handlers */
+ @Override
+ public int onConnect(HeaderSet request, HeaderSet reply) {
+ Log.i(TAG,"onConnect()");
+ int index;
+ int result = ResponseCodes.OBEX_HTTP_OK;
+ try {
+ index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
+ mOperationIndex = index;
+ SeqStep step = mSequence.get(mOperationIndex);
+ Assert.assertNotNull("invalid step index!", step);
+ if(step.mServerPreAction != null) {
+ step.mServerPreAction.execute(step, request, null);
+ }
+ result = super.onConnect(request, reply);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in onConnect - aborting...", e);
+ result = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ // A read from null will produce exception to end the test.
+ }
+ return result;
+ }
+
+ @Override
+ public void onDisconnect(HeaderSet request, HeaderSet reply) {
+ Log.i(TAG,"onDisconnect()");
+ /* TODO: validate request headers, and set response headers */
+ int index;
+ int result = ResponseCodes.OBEX_HTTP_OK;
+ try {
+ index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
+ mOperationIndex = index;
+ SeqStep step = mSequence.get(mOperationIndex);
+ Assert.assertNotNull("invalid step index!", step);
+ if(step.mServerPreAction != null) {
+ step.mServerPreAction.execute(step, request, null);
+ }
+ super.onDisconnect(request, reply);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in onDisconnect - aborting...", e);
+ result = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ // A read from null will produce exception to end the test.
+ }
+ if(mOperationIndex >= (mSequence.size()-1)) {
+ /* End of test, signal test runner thread */
+ Log.i(TAG, "Sending latch close signal...");
+ mStopLatch.countDown();
+ } else {
+ Log.i(TAG, "Got disconnect with mOperationCounter = " + mOperationIndex);
+ }
+ reply.responseCode = result;
+ }
+
+ @Override
+ public int onPut(Operation operation) {
+ Log.i(TAG,"onPut()");
+ int result = ResponseCodes.OBEX_HTTP_OK;
+ try{
+ HeaderSet reqHeaders = operation.getReceivedHeader();
+ int index = ((Long)reqHeaders.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
+ mOperationIndex = index;
+ SeqStep step = mSequence.get(mOperationIndex);
+ Assert.assertNotNull("invalid step index!", step);
+ if(step.mServerPreAction != null) {
+ step.mServerPreAction.execute(step, reqHeaders, operation);
+ }
+ super.onPut(operation);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in onPut - aborting...", e);
+ result = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ // A read from null will produce exception to end the test.
+ }
+ if(result == ResponseCodes.OBEX_HTTP_OK) {
+ Log.i(TAG, "OBEX-HANDLER: operation complete success");
+ } else {
+ Log.e(TAG, "OBEX-HANDLER: operation complete FAILED!");
+ }
+ return result;
+ }
+
+ @Override
+ public int onGet(Operation operation) {
+ Log.i(TAG,"onGet()");
+ int result = ResponseCodes.OBEX_HTTP_OK;
+ try{
+ HeaderSet reqHeaders = operation.getReceivedHeader();
+ int index = ((Long)reqHeaders.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
+ mOperationIndex = index;
+ SeqStep step = mSequence.get(mOperationIndex);
+ Assert.assertNotNull("invalid step index!", step);
+ if(step.mServerPreAction != null) {
+ step.mServerPreAction.execute(step, reqHeaders, operation);
+ }
+ super.onGet(operation);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in onGet - aborting...", e);
+ result = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ // A read from null will produce exception to end the test.
+ }
+ if(result == ResponseCodes.OBEX_HTTP_OK) {
+ Log.i(TAG, "OBEX-HANDLER: operation complete success");
+ } else {
+ Log.e(TAG, "OBEX-HANDLER: operation complete FAILED!");
+ }
+ return result;
+ }
+
+
+
+}
+
diff --git a/tests/src/com/android/bluetooth/tests/MapStepsConvo.java b/tests/src/com/android/bluetooth/tests/MapStepsConvo.java
new file mode 100644
index 0000000..f4288bb
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/MapStepsConvo.java
@@ -0,0 +1,240 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+import java.util.Arrays;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+import javax.obex.ResponseCodes;
+
+import junit.framework.Assert;
+import android.util.Log;
+
+import com.android.bluetooth.map.BluetoothMapAppParams;
+import com.android.bluetooth.map.BluetoothMapConvoListing;
+import com.android.bluetooth.map.BluetoothMapConvoListingElement;
+import com.android.bluetooth.map.BluetoothMapFolderElement;
+import com.android.bluetooth.tests.TestSequencer.OPTYPE;
+
+public class MapStepsConvo {
+ private static final String TAG = "MapStepsConvo";
+
+
+ protected static void addConvoListingSteps(TestSequencer sequencer) {
+ SeqStep step;
+ final int count = 5;
+
+ // TODO: As we use the default message database, these tests will fail if the
+ // database has any content.
+ // To cope with this for now, the validation is disabled.
+
+ /* Request the number of messages */
+ step = addConvoListingStep(sequencer,
+ 0 /*maxListCount*/,
+ -1 /*listStartOffset*/,
+ null /*activityBegin*/,
+ null /*activityEnd*/,
+ -1 /*readStatus*/,
+ null /*recipient*/,
+ new MapConvoListValidator()
+ /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+ /* Add messages and contacts for the entire sequence of tests */
+ step.mServerPreAction = new MapTestData.MapAddSmsMessages(count);
+
+ /* Request the XML for all conversations */
+ step = addConvoListingStep(sequencer,
+ -1 /*maxListCount*/,
+ -1 /*listStartOffset*/,
+ null /*activityBegin*/,
+ null /*activityEnd*/,
+ -1 /*readStatus*/,
+ null /*recipient*/,
+ /*nearly impossible to validate due to auto assigned ID values*/
+ new MapConvoListValidator()
+ /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+
+ step = addConvoListingStep(sequencer,
+ 2 /*maxListCount*/,
+ -1 /*listStartOffset*/,
+ null /*activityBegin*/,
+ null /*activityEnd*/,
+ -1 /*readStatus*/,
+ null /*recipient*/,
+ /*nearly impossible to validate due to auto assigned ID values*/
+ new MapConvoListValidator()
+ /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+
+ step = addConvoListingStep(sequencer,
+ 2 /*maxListCount*/,
+ 1 /*listStartOffset*/,
+ null /*activityBegin*/,
+ null /*activityEnd*/,
+ -1 /*readStatus*/,
+ null /*recipient*/,
+ /*nearly impossible to validate due to auto assigned ID values*/
+ new MapConvoListValidator()
+ /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+
+ step = addConvoListingStep(sequencer,
+ 3 /*maxListCount*/,
+ 2 /*listStartOffset*/,
+ null /*activityBegin*/,
+ null /*activityEnd*/,
+ -1 /*readStatus*/,
+ null /*recipient*/,
+ /*nearly impossible to validate due to auto assigned ID values*/
+ new MapConvoListValidator()
+ /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+
+ step = addConvoListingStep(sequencer,
+ 5 /*maxListCount*/,
+ 1 /*listStartOffset*/,
+ MapTestData.TEST_ACTIVITY_BEGIN_STRING /*activityBegin*/,
+ null /*activityEnd*/,
+ -1 /*readStatus*/,
+ null /*recipient*/,
+ /*nearly impossible to validate due to auto assigned ID values*/
+ new MapConvoListValidator()
+ /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+
+ step = addConvoListingStep(sequencer,
+ 5 /*maxListCount*/,
+ 0 /*listStartOffset*/,
+ MapTestData.TEST_ACTIVITY_BEGIN_STRING /*activityBegin*/,
+ MapTestData.TEST_ACTIVITY_END_STRING /*activityEnd*/,
+ -1 /*readStatus*/,
+ null /*recipient*/,
+ /*nearly impossible to validate due to auto assigned ID values*/
+ new MapConvoListValidator()
+ /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+
+ step = addConvoListingStep(sequencer,
+ 5 /*maxListCount*/,
+ 1 /*listStartOffset*/,
+ MapTestData.TEST_ACTIVITY_BEGIN_STRING /*activityBegin*/,
+ null /*activityEnd*/,
+ 2/* read only */ /*readStatus*/,
+ null /*recipient*/,
+ /*nearly impossible to validate due to auto assigned ID values*/
+ new MapConvoListValidator()
+ /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/);
+
+ /* TODO: Test the different combinations of filtering */
+ }
+
+ /**
+ * Use -1 or null to omit value in request
+ * @param sequencer
+ * @param maxListCount
+ * @param listStartOffset
+ * @param activityBegin
+ * @param activityEnd
+ * @param readStatus -1 omit value, 0 = no filtering, 1 = get unread only, 2 = get read only,
+ * 3 = 1+2 - hence get none...
+ * @param recipient substring of the recipient name
+ * @param validator
+ * @return a reference to the step added, for further decoration
+ */
+ private static SeqStep addConvoListingStep(TestSequencer sequencer, int maxListCount,
+ int listStartOffset, String activityBegin, String activityEnd,
+ int readStatus, String recipient, ISeqStepValidator validator) {
+ SeqStep step;
+ BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+ try {
+ if(activityBegin != null) {
+ appParams.setFilterLastActivityBegin(activityBegin);
+ }
+ if(activityEnd != null) {
+ appParams.setFilterLastActivityEnd(activityEnd);
+ }
+ if(readStatus != -1) {
+ appParams.setFilterReadStatus(readStatus);
+ }
+ if(recipient != null) {
+ appParams.setFilterRecipient(recipient);
+ }
+ if(maxListCount != -1) {
+ appParams.setMaxListCount(maxListCount);
+ }
+ if(listStartOffset != -1) {
+ appParams.setStartOffset(listStartOffset);
+ }
+ } catch (ParseException e) {
+ Log.e(TAG, "unable to build appParams", e);
+ }
+ step = sequencer.addStep(OPTYPE.GET, null);
+ HeaderSet hs = new HeaderSet();
+ hs.setHeader(HeaderSet.TYPE, MapObexLevelTest.TYPE_GET_CONVO_LISTING);
+ try {
+ hs.setHeader(HeaderSet.APPLICATION_PARAMETER, appParams.EncodeParams());
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, "ERROR", e);
+ Assert.fail();
+ }
+ step.mReqHeaders = hs;
+ step.mValidator = validator;
+ return step;
+ }
+
+ /* Functions to validate results */
+ private static class MapConvoListValidator implements ISeqStepValidator {
+
+ final BluetoothMapConvoListing mExpectedListing;
+ final int mExpectedSize;
+
+ public MapConvoListValidator(BluetoothMapConvoListing listing) {
+ this.mExpectedListing = listing;
+ this.mExpectedSize = -1;
+ }
+
+ public MapConvoListValidator(int convoListingSize) {
+ this.mExpectedListing = null;
+ this.mExpectedSize = convoListingSize;
+ }
+
+ public MapConvoListValidator() {
+ this.mExpectedListing = null;
+ this.mExpectedSize = -1;
+ }
+
+ @Override
+ public boolean validate(SeqStep step, HeaderSet response, Operation op)
+ throws IOException {
+ Assert.assertNotNull(op);
+ op.noBodyHeader();
+ try {
+ // For some odd reason, the request will not be send before we start to read the
+ // reply data, hence we need to do this first?
+ BluetoothMapConvoListing receivedListing = new BluetoothMapConvoListing();
+ receivedListing.appendFromXml(op.openInputStream());
+ response = op.getReceivedHeader();
+ byte[] appParamsRaw = (byte[])response.getHeader(HeaderSet.APPLICATION_PARAMETER);
+ Assert.assertNotNull(appParamsRaw);
+ BluetoothMapAppParams appParams;
+ appParams = new BluetoothMapAppParams(appParamsRaw);
+ Assert.assertNotNull(appParams);
+ Assert.assertNotNull(appParams.getDatabaseIdentifier());
+ Assert.assertNotSame(BluetoothMapAppParams.INVALID_VALUE_PARAMETER,
+ appParams.getConvoListingSize());
+ if(mExpectedSize >= 0) {
+ Assert.assertSame(mExpectedSize, appParams.getConvoListingSize());
+ }
+ if(mExpectedListing != null) {
+ // Recursively compare
+ Assert.assertTrue(mExpectedListing.equals(receivedListing));
+ Assert.assertSame(mExpectedListing.getList().size(),
+ appParams.getConvoListingSize());
+ }
+ int responseCode = op.getResponseCode();
+ Assert.assertEquals(ResponseCodes.OBEX_HTTP_OK, responseCode);
+ op.close();
+ } catch (Exception e) {
+ Log.e(TAG,"",e);
+ Assert.fail();
+ }
+ return true;
+ }
+ }
+}
diff --git a/tests/src/com/android/bluetooth/tests/MapStepsFolder.java b/tests/src/com/android/bluetooth/tests/MapStepsFolder.java
new file mode 100644
index 0000000..11d27dc
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/MapStepsFolder.java
@@ -0,0 +1,159 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+import javax.obex.ResponseCodes;
+
+import junit.framework.Assert;
+import android.util.Log;
+
+import com.android.bluetooth.map.BluetoothMapAppParams;
+import com.android.bluetooth.map.BluetoothMapFolderElement;
+import com.android.bluetooth.tests.TestSequencer.OPTYPE;
+
+public class MapStepsFolder {
+ private final static String TAG = "MapStepsFolder";
+ /**
+ * Request and expect the following folder structure:
+ * root
+ * telecom
+ * msg
+ * inbox
+ * outbox
+ * draft
+ * sent
+ * deleted
+ *
+ * The order in which they occur in the listing will not matter.
+ * @param sequencer
+ */
+ protected static void addGoToMsgFolderSteps(TestSequencer sequencer) {
+ SeqStep step;
+ //BluetoothMapFolderElement rootDir = new BluetoothMapFolderElement("root", null);
+
+ // MAP Get Folder Listing Steps
+ // The telecom folder
+ step = sequencer.addStep(OPTYPE.GET, null);
+ HeaderSet hs = new HeaderSet();
+ hs.setHeader(HeaderSet.TYPE, MapObexLevelTest.TYPE_GET_FOLDER_LISTING);
+ step.mReqHeaders = hs;
+ step.mValidator = new MapBuildFolderStructurValidator(1, null);
+
+ step = sequencer.addStep(OPTYPE.SET_PATH, ObexTest.getResponsecodevalidator());
+ hs = new HeaderSet();
+ hs.setHeader(HeaderSet.NAME, "telecom");
+ step.mReqHeaders = hs;
+ step.mClientPostAction = new MapSetClientFolder("telecom");
+
+
+ // The msg folder
+ step = sequencer.addStep(OPTYPE.GET, null);
+ hs = new HeaderSet();
+ hs.setHeader(HeaderSet.TYPE, MapObexLevelTest.TYPE_GET_FOLDER_LISTING);
+ step.mReqHeaders = hs;
+ step.mValidator = new MapBuildFolderStructurValidator(1, null);
+
+ step = sequencer.addStep(OPTYPE.SET_PATH, ObexTest.getResponsecodevalidator());
+ hs = new HeaderSet();
+ hs.setHeader(HeaderSet.NAME, "msg");
+ step.mReqHeaders = hs;
+ step.mClientPostAction = new MapSetClientFolder("msg");
+
+ // The msg folder
+ step = sequencer.addStep(OPTYPE.GET, null);
+ hs = new HeaderSet();
+ hs.setHeader(HeaderSet.TYPE, MapObexLevelTest.TYPE_GET_FOLDER_LISTING);
+ step.mReqHeaders = hs;
+ step.mValidator = new MapBuildFolderStructurValidator(5, buildDefaultFolderStructure());
+ }
+
+ /**
+ * Sets the current folder on the client, to the folder name specified in the constructor.
+ * TODO: Could be extended to be able to navigate back and forth in the folder structure.
+ */
+ private static class MapSetClientFolder implements ISeqStepAction {
+ final String mFolderName;
+ public MapSetClientFolder(String folderName) {
+ super();
+ this.mFolderName = folderName;
+ }
+ @Override
+ public void execute(SeqStep step, HeaderSet request, Operation op)
+ throws IOException {
+ MapBuildFolderStructurValidator.sCurrentFolder =
+ MapBuildFolderStructurValidator.sCurrentFolder.getSubFolder(mFolderName);
+ Assert.assertNotNull(MapBuildFolderStructurValidator.sCurrentFolder);
+ Log.i(TAG, "MapSetClientFolder(): Current path: " +
+ MapBuildFolderStructurValidator.sCurrentFolder.getFullPath());
+ }
+ }
+
+ /* Functions to validate results */
+ private static class MapBuildFolderStructurValidator implements ISeqStepValidator {
+
+ final int mExpectedListingSize;
+ static BluetoothMapFolderElement sCurrentFolder = null;
+ final BluetoothMapFolderElement mExpectedFolderElement;
+
+ public MapBuildFolderStructurValidator(int mExpectedListingSize,
+ BluetoothMapFolderElement folderElement) {
+ super();
+ if(sCurrentFolder == null) {
+ sCurrentFolder = new BluetoothMapFolderElement("root", null);
+ }
+ this.mExpectedListingSize = mExpectedListingSize;
+ this.mExpectedFolderElement = folderElement;
+ }
+
+
+ @Override
+ public boolean validate(SeqStep step, HeaderSet response, Operation op)
+ throws IOException {
+ Assert.assertNotNull(op);
+ op.noBodyHeader();
+ try {
+ // For some odd reason, the request will not be send before we start to read the
+ // reply data, hence we need to do this first?
+ sCurrentFolder.appendSubfolders(op.openInputStream());
+ response = op.getReceivedHeader();
+ byte[] appParamsRaw = (byte[])response.getHeader(HeaderSet.APPLICATION_PARAMETER);
+ Assert.assertNotNull(appParamsRaw);
+ BluetoothMapAppParams appParams;
+ appParams = new BluetoothMapAppParams(appParamsRaw);
+ Assert.assertNotNull(appParams);
+ if(mExpectedFolderElement != null) {
+ // Recursively compare
+ Assert.assertTrue(mExpectedFolderElement.compareTo(sCurrentFolder.getRoot())
+ == 0);
+ }
+ int responseCode = op.getResponseCode();
+ Assert.assertEquals(ResponseCodes.OBEX_HTTP_OK, responseCode);
+ op.close();
+ } catch (Exception e) {
+ Log.e(TAG,"",e);
+ Assert.fail();
+ }
+ return true;
+ }
+
+ }
+
+
+ private static BluetoothMapFolderElement buildDefaultFolderStructure(){
+ BluetoothMapFolderElement root =
+ new BluetoothMapFolderElement("root", null); // This will be the root element
+ BluetoothMapFolderElement tmpFolder;
+ tmpFolder = root.addFolder("telecom"); // root/telecom
+ tmpFolder = tmpFolder.addFolder("msg"); // root/telecom/msg
+ tmpFolder.addFolder("inbox"); // root/telecom/msg/inbox
+ tmpFolder.addFolder("outbox"); // root/telecom/msg/outbox
+ tmpFolder.addFolder("sent"); // root/telecom/msg/sent
+ tmpFolder.addFolder("deleted"); // root/telecom/msg/deleted
+ tmpFolder.addFolder("draft"); // root/telecom/msg/draft
+ return root;
+ }
+
+
+}
diff --git a/tests/src/com/android/bluetooth/tests/MapTestData.java b/tests/src/com/android/bluetooth/tests/MapTestData.java
new file mode 100644
index 0000000..7cb723b
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/MapTestData.java
@@ -0,0 +1,286 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+import java.util.Date;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.Telephony.Sms;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.bluetooth.map.BluetoothMapConvoContactElement;
+import com.android.bluetooth.map.BluetoothMapConvoListing;
+import com.android.bluetooth.map.BluetoothMapConvoListingElement;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+
+/**
+ * Class to hold test data - both the server side data to insert into the databases, and the
+ * validation data to validate the result, when reading back the data.
+ *
+ * Should be data only, not operation specific functionality (client).
+ *
+ * Please try to keep useful functionality call-able from a test case, to make it possible
+ * to call a single test case to e.g. inject some contacts or messages into the database.
+ *
+ */
+@TargetApi(20)
+public class MapTestData extends AndroidTestCase {
+ private static final String TAG = "MapTestData";
+
+ /* Test validation variables */
+ static final String TEST_CONTACT_NAME = "Jesus Überboss";
+ static final String TEST_CONTACT_PHONE = "55566688";
+ static final String TEST_CONTACT_EMAIL = "boss@the.skyes";
+ static final int TEST_NUM_CONTACTS = 3;
+
+ static final int TEST_ADD_CONTACT_PER_ITERATIONS = 4;
+ /* I do know this function is deprecated, but I'm unable to find a good alternative
+ * except from taking a copy of the Date.UTC function as suggested. */
+ // NOTE: This will only set the data on the message - not the lastActivity on SMS/MMS threads
+ static final long TEST_ACTIVITY_BEGIN = Date.UTC(
+ 2014-1900,
+ 8-1, /* month 0-11*/
+ 22, /*day 1-31 */
+ 22, /*hour*/
+ 15, /*minute*/
+ 20 /*second*/);
+
+ static final String TEST_ACTIVITY_BEGIN_STRING = "20150102T150047";
+ static final String TEST_ACTIVITY_END_STRING = "20160102T150047";
+
+ static final int TEST_ACTIVITY_INTERVAL = 5*60*1000; /*ms*/
+
+ static Context sContext = null;
+ public static void init(Context context){
+ sContext = context;
+ }
+ /**
+ * Adds messages to the SMS message database.
+ */
+ public static class MapAddSmsMessages implements ISeqStepAction {
+ int mCount;
+ /**
+ *
+ * @param count the number of iterations to execute
+ */
+ public MapAddSmsMessages(int count) {
+ mCount = count;
+ }
+
+ @Override
+ public void execute(SeqStep step, HeaderSet request, Operation op)
+ throws IOException {
+ int count = mCount; // Number of messages in each conversation
+ ContentResolver resolver = sContext.getContentResolver();
+
+ // Insert some messages
+ insertTestMessages(resolver, step.index, count);
+
+ // Cleanup if needed to avoid duplicates
+ deleteTestContacts(resolver);
+
+ // And now add the contacts
+ setupTestContacts(resolver);
+ }
+ }
+
+ /**
+ * TODO: Only works for filter on TEST_CONTACT_NAME
+ * @param maxCount
+ * @param offset
+ * @param filterContact
+ * @param read
+ * @param reportRead
+ * @param msgCount
+ * @return
+ */
+ public static BluetoothMapConvoListing getConvoListingReference(int maxCount, int offset,
+ boolean filterContact, boolean read, boolean reportRead, int msgCount){
+ BluetoothMapConvoListing list = new BluetoothMapConvoListing();
+ BluetoothMapConvoListingElement element;
+ BluetoothMapConvoContactElement contact;
+ element = new BluetoothMapConvoListingElement();
+ element.setRead(read, reportRead);
+ element.setVersionCounter(0);
+ contact = new BluetoothMapConvoContactElement();
+ contact.setName(TEST_CONTACT_NAME);
+ contact.setLastActivity(TEST_ACTIVITY_BEGIN +
+ msgCount*TEST_ADD_CONTACT_PER_ITERATIONS*TEST_ACTIVITY_INTERVAL);
+ element.addContact(contact);
+ list.add(element);
+ return null;
+ }
+
+ public static void insertTestMessages(ContentResolver resolver, int tag, int count) {
+ ContentValues values[] = new ContentValues[count*4]; // 4 messages/iteration
+ long date = TEST_ACTIVITY_BEGIN;
+ Log.i(TAG, "Preparing messages... with data = " + date);
+
+ for (int x = 0;x < count;x++){
+ /* NOTE: Update TEST_ADD_CONTACT_PER_ITERATIONS if more messages are added */
+ ContentValues item = new ContentValues(5);
+ item.put("address", "98765432");
+ item.put("body", "test message " + x + " step index: " + tag);
+ item.put("date", date+=TEST_ACTIVITY_INTERVAL);
+ item.put("read", "0");
+ if(x%2 == 0) {
+ item.put("type", Sms.MESSAGE_TYPE_INBOX);
+ } else {
+ item.put("type", Sms.MESSAGE_TYPE_SENT);
+ }
+ values[x] = item;
+
+ item = new ContentValues(5);
+ item.put("address", "23456780");
+ item.put("body", "test message " + x + " step index: " + tag);
+ item.put("date", date += TEST_ACTIVITY_INTERVAL);
+ item.put("read", "0");
+ if(x%2 == 0) {
+ item.put("type", Sms.MESSAGE_TYPE_INBOX);
+ } else {
+ item.put("type", Sms.MESSAGE_TYPE_SENT);
+ }
+ values[count+x] = item;
+
+ item = new ContentValues(5);
+ item.put("address", "+4523456780");
+ item.put("body", "test message "+x+" step index: " + tag);
+ item.put("date", date += TEST_ACTIVITY_INTERVAL);
+ item.put("read", "0");
+ if(x%2 == 0) {
+ item.put("type", Sms.MESSAGE_TYPE_INBOX);
+ } else {
+ item.put("type", Sms.MESSAGE_TYPE_SENT);
+ }
+ values[2*count+x] = item;
+
+ /* This is the message used for test */
+ item = new ContentValues(5);
+ item.put("address", TEST_CONTACT_PHONE);
+ item.put("body", "test message "+x+" step index: " + tag);
+ item.put("date", date += TEST_ACTIVITY_INTERVAL);
+ item.put("read", "0");
+ if(x%2 == 0) {
+ item.put("type", Sms.MESSAGE_TYPE_INBOX);
+ } else {
+ item.put("type", Sms.MESSAGE_TYPE_SENT);
+ }
+ values[3*count+x] = item;
+ }
+
+ Log.i(TAG, "Starting bulk insert...");
+ resolver.bulkInsert(Uri.parse("content://sms"), values);
+ Log.i(TAG, "Bulk insert done.");
+ }
+
+ /**
+ * Insert a few contacts in the main contact database, using a test account.
+ */
+ public static void setupTestContacts(ContentResolver resolver){
+ /*TEST_NUM_CONTACTS must be updated if this function is changed */
+ insertContact(resolver, "Hans Hansen", "98765432", "hans@hansens.global");
+ insertContact(resolver, "Helle Børgesen", "23456780", "hb@gmail.com");
+ insertContact(resolver, TEST_CONTACT_NAME, TEST_CONTACT_PHONE, TEST_CONTACT_EMAIL);
+ }
+
+ /**
+ * Helper function to insert a contact
+ * @param name
+ * @param phone
+ * @param email
+ */
+ private static void insertContact(ContentResolver resolver, String name, String phone, String email) {
+ // Get the account info
+ //Cursor c = resolver.query(uri, projection, selection, selectionArgs, sortOrder)
+ ContentValues item = new ContentValues(3);
+ item.put(ContactsContract.RawContacts.ACCOUNT_TYPE, "test_account");
+ item.put(ContactsContract.RawContacts.ACCOUNT_NAME, "MAP account");
+ Uri uri = resolver.insert(ContactsContract.RawContacts.CONTENT_URI, item);
+ Log.i(TAG, "Inserted RawContact: " + uri);
+ long rawId = Long.parseLong(uri.getLastPathSegment());
+
+ //Now add contact information
+ item = new ContentValues(3);
+ item.put(ContactsContract.Data.RAW_CONTACT_ID, rawId);
+ item.put(ContactsContract.Data.MIMETYPE,
+ ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
+ item.put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
+ name);
+ resolver.insert(ContactsContract.Data.CONTENT_URI, item);
+
+ if(phone != null) {
+ item = new ContentValues(3);
+ item.put(ContactsContract.Data.RAW_CONTACT_ID, rawId);
+ item.put(ContactsContract.Data.MIMETYPE,
+ ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
+ item.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);
+ resolver.insert(ContactsContract.Data.CONTENT_URI, item);
+ }
+
+ if(email != null) {
+ item = new ContentValues(3);
+ item.put(ContactsContract.Data.RAW_CONTACT_ID, rawId);
+ item.put(ContactsContract.Data.MIMETYPE,
+ ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
+ item.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);
+ resolver.insert(ContactsContract.Data.CONTENT_URI, item);
+ }
+ }
+
+ /**
+ * Delete all contacts belonging to the test_account.
+ */
+ public static void deleteTestContacts(ContentResolver resolver){
+ resolver.delete(ContactsContract.RawContacts.CONTENT_URI,
+ ContactsContract.RawContacts.ACCOUNT_TYPE + "=\"test_account\"", null);
+ }
+
+ /****************************************************************************
+ * Small test cases to trigger the functionality without running a sequence.
+ ****************************************************************************/
+ /**
+ * Insert a few contacts in the main contact database, using a test account.
+ */
+ public void testInsertMessages() {
+ ContentResolver resolver = mContext.getContentResolver();
+ insertTestMessages(resolver, 1234, 10);
+ }
+
+ public void testInsert1000Messages() {
+ ContentResolver resolver = mContext.getContentResolver();
+ insertTestMessages(resolver, 1234, 1000);
+ }
+
+ /**
+ * Insert a few contacts in the main contact database, using a test account.
+ */
+ public void testSetupContacts() {
+ ContentResolver resolver = mContext.getContentResolver();
+ setupTestContacts(resolver);
+ }
+
+ /**
+ * Delete all contacts belonging to the test_account.
+ */
+ public void testDeleteTestContacts() {
+ ContentResolver resolver = mContext.getContentResolver();
+ deleteTestContacts(resolver);
+ }
+
+ public void testSetup1000Contacts() {
+ ContentResolver resolver = mContext.getContentResolver();
+ for(int i = 0; i < 1000; i++) {
+ insertContact(resolver, "Hans Hansen " + i,
+ "98765431" + i, "hans" + i + "@hansens.global");
+ }
+ }
+
+}
diff --git a/tests/src/com/android/bluetooth/tests/MockMasInstance.java b/tests/src/com/android/bluetooth/tests/MockMasInstance.java
new file mode 100644
index 0000000..07de8e4
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/MockMasInstance.java
@@ -0,0 +1,30 @@
+package com.android.bluetooth.tests;
+
+import com.android.bluetooth.map.BluetoothMapMasInstance;
+import junit.framework.Assert;
+
+public class MockMasInstance extends BluetoothMapMasInstance {
+
+ private final int mMasId;
+ private final int mRemoteFeatureMask;
+
+ public MockMasInstance(int masId, int remoteFeatureMask) {
+ super();
+ this.mMasId = masId;
+ this.mRemoteFeatureMask = remoteFeatureMask;
+ }
+
+ public int getMasId() {
+ return mMasId;
+ }
+
+ @Override
+ public int getRemoteFeatureMask() {
+ return mRemoteFeatureMask;
+ }
+
+ @Override
+ public void restartObexServerSession() {
+ Assert.fail("restartObexServerSession() should not occur");
+ }
+}
diff --git a/tests/src/com/android/bluetooth/tests/ObexPipeTransport.java b/tests/src/com/android/bluetooth/tests/ObexPipeTransport.java
index 3bb9809..28625d5 100644
--- a/tests/src/com/android/bluetooth/tests/ObexPipeTransport.java
+++ b/tests/src/com/android/bluetooth/tests/ObexPipeTransport.java
@@ -26,12 +26,12 @@
import javax.obex.ObexTransport;
public class ObexPipeTransport implements ObexTransport {
- PipedInputStream mInStream;
- PipedOutputStream mOutStream;
+ InputStream mInStream;
+ OutputStream mOutStream;
boolean mEnableSrm;
- public ObexPipeTransport(PipedInputStream inStream,
- PipedOutputStream outStream, boolean enableSrm) {
+ public ObexPipeTransport(InputStream inStream,
+ OutputStream outStream, boolean enableSrm) {
mInStream = inStream;
mOutStream = outStream;
mEnableSrm = enableSrm;
@@ -74,12 +74,12 @@
return true;
}
- public int getMaxTxPacketSize() {
- return 15432;
+ public int getMaxTransmitPacketSize() {
+ return 3*15432;
}
- public int getMaxRxPacketSize() {
- return 23450;
+ public int getMaxReceivePacketSize() {
+ return 2*23450;
}
@Override
@@ -88,3 +88,4 @@
}
}
+
diff --git a/tests/src/com/android/bluetooth/tests/ObexTest.java b/tests/src/com/android/bluetooth/tests/ObexTest.java
index ad45522..b90ea14 100644
--- a/tests/src/com/android/bluetooth/tests/ObexTest.java
+++ b/tests/src/com/android/bluetooth/tests/ObexTest.java
@@ -22,41 +22,35 @@
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
-import javax.obex.ClientSession;
import javax.obex.HeaderSet;
-import javax.obex.ObexPacket;
import javax.obex.ObexTransport;
import javax.obex.Operation;
import javax.obex.ResponseCodes;
-import javax.obex.ServerSession;
+import javax.obex.ServerRequestHandler;
+import junit.framework.Assert;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothUuid;
+import android.bluetooth.SdpMasRecord;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.nfc.cardemulation.OffHostApduService;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
import android.os.Build;
import android.os.Debug;
-import android.os.Handler;
-import android.os.Handler.Callback;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
import android.os.ParcelUuid;
import android.test.AndroidTestCase;
import android.util.Log;
import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.sdp.SdpManager;
-import com.android.bluetooth.sdp.SdpMasRecord;
-import com.android.bluetooth.tests.ObexTest.TestSequencer.OPTYPE;
-import com.android.bluetooth.tests.ObexTest.TestSequencer.SeqStep;
+import com.android.bluetooth.tests.TestSequencer.OPTYPE;
/**
* Test either using the reference ril without a modem, or using a RIL implementing the
@@ -64,42 +58,47 @@
*
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
-public class ObexTest extends AndroidTestCase {
+public class ObexTest extends AndroidTestCase implements ITestSequenceConfigurator {
protected static String TAG = "ObexTest";
protected static final boolean D = true;
protected static final boolean TRACE = false;
protected static final boolean DELAY_PASS_30_SEC = false;
public static final long PROGRESS_INTERVAL_MS = 1000;
private static final ObexTestParams defaultParams =
- new ObexTestParams(2*8092, 0, 2*1024*1024/10);
+ new ObexTestParams(2*8092, 0, 2*1024*1024);
private static final ObexTestParams throttle100Params =
- new ObexTestParams(2*8092, 100000, 2*1024*1024/10);
+ new ObexTestParams(2*8092, 100000, 1024*1024);
private static final ObexTestParams smallParams =
new ObexTestParams(2*8092, 0, 2*1024);
private static final ObexTestParams hugeParams =
- new ObexTestParams(2*8092, 0, 100*1024*1024/1000);
+ new ObexTestParams(2*8092, 0, 100*1024*1024);
- private static final int SMALL_OPERATION_COUNT = 1000/100;
+ private static final int SMALL_OPERATION_COUNT = 1000;
private static final int CONNECT_OPERATION_COUNT = 4500;
private static final int L2CAP_PSM = 29; /* If SDP is not used */
private static final int RFCOMM_CHANNEL = 29; /* If SDP is not used */
- public static final String SERVER_ADDRESS = "10:68:3F:5E:F9:2E";
+ //public static final String SERVER_ADDRESS = "10:68:3F:5E:F9:2E";
+ public static final String SERVER_ADDRESS = "F8:CF:C5:A8:70:7E";
private static final String SDP_SERVER_NAME = "Samsung Server";
private static final String SDP_CLIENT_NAME = "Samsung Client";
- private static final long SDP_FEATURES = 0x87654321L; /* 32 bit */
+ private static final long SDP_FEATURES = 0x87654321L; /* 32 bit */
private static final int SDP_MSG_TYPES = 0xf1; /* 8 bit */
private static final int SDP_MAS_ID = 0xCA; /* 8 bit */
private static final int SDP_VERSION = 0xF0C0; /* 16 bit */
public static final ParcelUuid SDP_UUID_OBEX_MAS = BluetoothUuid.MAS;
private static int sSdpHandle = -1;
+ private static final ObexTestDataHandler sDataHandler = new ObexTestDataHandler("(Client)");
+ private static final ISeqStepValidator sResponseCodeValidator = new ResponseCodeValidator();
+ private static final ISeqStepValidator sDataValidator = new DataValidator();
+
private enum SequencerType {
SEQ_TYPE_PAYLOAD,
@@ -109,10 +108,6 @@
private Context mContext = null;
private int mChannelType = 0;
- public static final int STEP_INDEX_HEADER = 0xF1; /*0xFE*/
- private static final int ENABLE_TIMEOUT = 5000;
- private static final int POLL_TIME = 500;
-
public ObexTest() {
super();
}
@@ -121,6 +116,8 @@
* Test that a connection can be established.
* WARNING: The performance of the pipe implementation is not good. I'm only able to get a
* throughput of around 220 kbyte/sec - less that when using Bluetooth :-)
+ * UPDATE: Did a local socket implementation below to replace this...
+ * This has a throughput of more than 4000 kbyte/s
*/
public void testLocalPipes() {
mContext = this.getContext();
@@ -147,58 +144,97 @@
TestSequencer sequencer = createBtPayloadTestSequence(clientTransport, serverTransport);
//Debug.startMethodTracing("ObexTrace");
- assertTrue(sequencer.run());
+ assertTrue(sequencer.run(mContext));
//Debug.stopMethodTracing();
} catch (IOException e) {
Log.e(TAG, "IOException", e);
}
}
+ /**
+ * Run the test sequence using a local socket.
+ * Throughput around 4000 kbyte/s - with a larger OBEX package size.
+ */
+ public void testLocalSockets() {
+ mContext = this.getContext();
+ System.out.println("Setting up sockets...");
+
+ try {
+ /* Create and interconnect local pipes for transport */
+ LocalServerSocket serverSock = new LocalServerSocket("com.android.bluetooth.tests.sock");
+ LocalSocket clientSock = new LocalSocket();
+ LocalSocket acceptSock;
+
+ clientSock.connect(serverSock.getLocalSocketAddress());
+
+ acceptSock = serverSock.accept();
+
+ /* Create the OBEX transport objects to wrap the pipes - enable SRM */
+ ObexPipeTransport clientTransport = new ObexPipeTransport(clientSock.getInputStream(),
+ clientSock.getOutputStream(), true);
+ ObexPipeTransport serverTransport = new ObexPipeTransport(acceptSock.getInputStream(),
+ acceptSock.getOutputStream(), true);
+
+ TestSequencer sequencer = createBtPayloadTestSequence(clientTransport, serverTransport);
+
+ //Debug.startMethodTracing("ObexTrace");
+ assertTrue(sequencer.run(mContext));
+ //Debug.stopMethodTracing();
+
+ clientSock.close();
+ acceptSock.close();
+ serverSock.close();
+ } catch (IOException e) {
+ Log.e(TAG, "IOException", e);
+ }
+ }
+
/* Create a sequence of put/get operations with different payload sizes */
private TestSequencer createBtPayloadTestSequence(ObexTransport clientTransport,
ObexTransport serverTransport)
throws IOException {
- TestSequencer sequencer = new TestSequencer(clientTransport, serverTransport);
+ TestSequencer sequencer = new TestSequencer(clientTransport, serverTransport, this);
SeqStep step;
- step = sequencer.addStep(OPTYPE.CONNECT);
+ step = sequencer.addStep(OPTYPE.CONNECT, sResponseCodeValidator);
+ if(false){
- step = sequencer.addStep(OPTYPE.PUT);
+ step = sequencer.addStep(OPTYPE.PUT, sDataValidator);
step.mParams = defaultParams;
step.mUseSrm = true;
- step = sequencer.addStep(OPTYPE.GET);
+ step = sequencer.addStep(OPTYPE.GET, sDataValidator);
step.mParams = defaultParams;
step.mUseSrm = true;
-if(true){
- step = sequencer.addStep(OPTYPE.PUT);
+
+ step = sequencer.addStep(OPTYPE.PUT, sDataValidator);
step.mParams = throttle100Params;
step.mUseSrm = true;
- step = sequencer.addStep(OPTYPE.GET);
+ step = sequencer.addStep(OPTYPE.GET, sDataValidator);
step.mParams = throttle100Params;
step.mUseSrm = true;
for(int i=0; i<SMALL_OPERATION_COUNT; i++){
- step = sequencer.addStep(OPTYPE.PUT);
+ step = sequencer.addStep(OPTYPE.PUT, sDataValidator);
step.mParams = smallParams;
step.mUseSrm = true;
- step = sequencer.addStep(OPTYPE.GET);
+ step = sequencer.addStep(OPTYPE.GET, sDataValidator);
step.mParams = smallParams;
step.mUseSrm = true;
}
+}
- step = sequencer.addStep(OPTYPE.PUT);
+ step = sequencer.addStep(OPTYPE.PUT, sDataValidator);
step.mParams = hugeParams;
step.mUseSrm = true;
- step = sequencer.addStep(OPTYPE.GET);
+ step = sequencer.addStep(OPTYPE.GET, sDataValidator);
step.mParams = hugeParams;
step.mUseSrm = true;
- }
- step = sequencer.addStep(OPTYPE.DISCONNECT);
+ step = sequencer.addStep(OPTYPE.DISCONNECT, sResponseCodeValidator);
return sequencer;
}
@@ -206,24 +242,98 @@
private TestSequencer createBtConnectTestSequence(ObexTransport clientTransport,
ObexTransport serverTransport)
throws IOException {
- TestSequencer sequencer = new TestSequencer(clientTransport, serverTransport);
+ TestSequencer sequencer = new TestSequencer(clientTransport, serverTransport, this);
SeqStep step;
- step = sequencer.addStep(OPTYPE.CONNECT);
+ step = sequencer.addStep(OPTYPE.CONNECT, sResponseCodeValidator);
- step = sequencer.addStep(OPTYPE.PUT);
+ step = sequencer.addStep(OPTYPE.PUT, sDataValidator);
step.mParams = smallParams;
step.mUseSrm = true;
- step = sequencer.addStep(OPTYPE.GET);
+ step = sequencer.addStep(OPTYPE.GET, sDataValidator);
step.mParams = smallParams;
step.mUseSrm = true;
- step = sequencer.addStep(OPTYPE.DISCONNECT);
+ step = sequencer.addStep(OPTYPE.DISCONNECT, sResponseCodeValidator);
return sequencer;
}
+
+ /**
+ * Use this validator to validate operation response codes. E.g. for OBEX CONNECT and
+ * DISCONNECT operations.
+ * Expects HeaderSet to be valid, and Operation to be null.
+ */
+ public static ISeqStepValidator getResponsecodevalidator() {
+ return sResponseCodeValidator;
+ }
+
+ /**
+ * Use this validator to validate (and read/write data) for OBEX PUT and GET operations.
+ * Expects Operation to be valid, and HeaderSet to be null.
+ */
+ public static ISeqStepValidator getDatavalidator() {
+ return sDataValidator;
+ }
+
+ /**
+ * Use this validator to validate operation response codes. E.g. for OBEX CONNECT and
+ * DISCONNECT operations.
+ * Expects HeaderSet to be valid, and Operation to be null.
+ */
+ private static class ResponseCodeValidator implements ISeqStepValidator {
+
+ protected static boolean validateHeaderSet(HeaderSet headers, HeaderSet expected)
+ throws IOException {
+ if(headers.getResponseCode() != ResponseCodes.OBEX_HTTP_OK) {
+ Log.e(TAG,"Wrong ResponseCode: " + headers.getResponseCode());
+ Assert.assertTrue(false);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean validate(SeqStep step, HeaderSet response, Operation op)
+ throws IOException {
+ if(response == null) {
+ if(op.getResponseCode() != ResponseCodes.OBEX_HTTP_OK) {
+ Log.e(TAG,"Wrong ResponseCode: " + op.getResponseCode());
+ Assert.assertTrue(false);
+ return false;
+ }
+ return true;
+ }
+ return validateHeaderSet(response, step.mResHeaders);
+ }
+ }
+
+ /**
+ * Use this validator to validate (and read/write data) for OBEX PUT and GET operations.
+ * Expects Operation to ve valid, and HeaderSet to be null.
+ */
+ private static class DataValidator implements ISeqStepValidator {
+ @Override
+ public boolean validate(SeqStep step, HeaderSet notUsed, Operation op)
+ throws IOException {
+ Assert.assertNotNull(op);
+ if(step.mType == OPTYPE.GET) {
+ op.noBodyHeader();
+ sDataHandler.readData(op.openDataInputStream(), step.mParams);
+ } else if (step.mType == OPTYPE.PUT) {
+ sDataHandler.writeData(op.openDataOutputStream(), step.mParams);
+ }
+ int responseCode = op.getResponseCode();
+ Log.i(TAG, "response code: " + responseCode);
+ HeaderSet response = op.getReceivedHeader();
+ ResponseCodeValidator.validateHeaderSet(response, step.mResHeaders);
+ op.close();
+ return true;
+ }
+ }
+
public void testBtServerL2cap() {
testBtServer(BluetoothSocket.TYPE_L2CAP, false, SequencerType.SEQ_TYPE_PAYLOAD);
}
@@ -276,7 +386,7 @@
SequencerType.SEQ_TYPE_CONNECT_DISCONNECT);
try {
// We give the server 100ms to allow adding SDP record
- Thread.sleep(100);
+ Thread.sleep(150);
} catch (InterruptedException e) {
Log.e(TAG,"Exception while waiting...",e);
}
@@ -303,7 +413,7 @@
SequencerType.SEQ_TYPE_CONNECT_DISCONNECT);
try {
// We give the server 100ms to allow adding SDP record
- Thread.sleep(100);
+ Thread.sleep(250);
} catch (InterruptedException e) {
Log.e(TAG,"Exception while waiting...",e);
}
@@ -327,16 +437,17 @@
Log.e(TAG,"No Bluetooth Device!");
assertTrue(false);
}
- enableBt(bt);
+ BluetoothTestUtils.enableBt(bt);
BluetoothServerSocket serverSocket=null;
if(type == BluetoothSocket.TYPE_L2CAP) {
if(useSdp == true) {
- serverSocket = bt.listenUsingL2capOn(L2CAP_PSM);
- } else {
serverSocket = bt.listenUsingL2capOn(
BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP);
+ } else {
+ serverSocket = bt.listenUsingL2capOn(L2CAP_PSM);
}
l2capPsm = serverSocket.getChannel();
+ Log.d(TAG, "L2CAP createde, PSM: " + l2capPsm);
} else if(type == BluetoothSocket.TYPE_RFCOMM) {
if(useSdp == true) {
serverSocket = bt.listenUsingInsecureRfcommOn(
@@ -345,19 +456,25 @@
serverSocket = bt.listenUsingInsecureRfcommOn(RFCOMM_CHANNEL);
}
rfcommChannel = serverSocket.getChannel();
+ Log.d(TAG, "RFCOMM createde, Channel: " + rfcommChannel);
} else {
fail("Invalid transport type!");
}
if(useSdp == true) {
- /* We use the MAP service record to be able to */
+ /* We use the MAP service record to be able to set rfcomm and l2cap channels */
// TODO: We need to free this
- if(sSdpHandle < 0) {
- sSdpHandle = SdpManager.getDefaultManager().createMapMasRecord(SDP_SERVER_NAME,
- SDP_MAS_ID, rfcommChannel, l2capPsm,
- SDP_VERSION, SDP_MSG_TYPES, (int)(SDP_FEATURES & 0xffffffff));
+ if(sSdpHandle >= 0) {
+ SdpManager.getDefaultManager().removeSdpRecord(sSdpHandle);
}
+ Log.d(TAG, "Creating record with rfcomm channel: " + rfcommChannel +
+ " and l2cap channel: " + l2capPsm);
+ sSdpHandle = SdpManager.getDefaultManager().createMapMasRecord(SDP_SERVER_NAME,
+ SDP_MAS_ID, rfcommChannel, l2capPsm,
+ SDP_VERSION, SDP_MSG_TYPES, (int)(SDP_FEATURES & 0xffffffff));
+ } else {
+ Log.d(TAG, "SKIP creation of record with rfcomm channel: " + rfcommChannel +
+ " and l2cap channel: " + l2capPsm);
}
-
return serverSocket;
}
@@ -373,7 +490,7 @@
*/
private void testBtServer(int type, boolean useSdp, SequencerType sequencerType) {
mContext = this.getContext();
- System.out.println("Starting BT Server...");
+ Log.d(TAG,"Starting BT Server...");
if(TRACE) Debug.startMethodTracing("ServerSide");
try {
@@ -398,7 +515,7 @@
}
//Debug.startMethodTracing("ObexTrace");
- assertTrue(sequencer.run());
+ assertTrue(sequencer.run(mContext));
//Debug.stopMethodTracing();
// Same as below... serverTransport.close();
// This is done by the obex server socket.close();
@@ -436,7 +553,7 @@
Log.e(TAG,"No Bluetooth Device!");
assertTrue(false);
}
- enableBt(bt);
+ BluetoothTestUtils.enableBt(bt);
BluetoothDevice serverDevice = bt.getRemoteDevice(SERVER_ADDRESS);
if(useSdp == true) {
@@ -487,9 +604,9 @@
}
//Debug.startMethodTracing("ObexTrace");
- assertTrue(sequencer.run());
+ assertTrue(sequencer.run(mContext));
//Debug.stopMethodTracing();
- // socket.close(); shall be closed by the obex client
+ socket.close(); // Only the streams are closed by the obex client
sequencer.shutdown();
} catch (IOException e) {
@@ -591,336 +708,13 @@
return broadcastReceiver.getMasRecord();
}
- /** Helper to turn BT on.
- * This method will either fail on an assert, or return with BT turned on.
- * Behavior of getState() and isEnabled() are validated along the way.
- */
- public static void enableBt(BluetoothAdapter adapter) {
- if (adapter.getState() == BluetoothAdapter.STATE_ON) {
- assertTrue(adapter.isEnabled());
- return;
- }
- assertEquals(BluetoothAdapter.STATE_OFF, adapter.getState());
- assertFalse(adapter.isEnabled());
- adapter.enable();
- for (int i=0; i<ENABLE_TIMEOUT/POLL_TIME; i++) {
- switch (adapter.getState()) {
- case BluetoothAdapter.STATE_ON:
- assertTrue(adapter.isEnabled());
- return;
- case BluetoothAdapter.STATE_OFF:
- Log.i(TAG, "STATE_OFF: Still waiting for enable to begin...");
- break;
- default:
- assertEquals(BluetoothAdapter.STATE_TURNING_ON, adapter.getState());
- assertFalse(adapter.isEnabled());
- break;
- }
- try {
- Thread.sleep(POLL_TIME);
- } catch (InterruptedException e) {}
- }
- fail("enable() timeout");
- Log.i(TAG, "Bluetooth enabled...");
+ @Override
+ public ServerRequestHandler getObexServer(ArrayList<SeqStep> sequence,
+ CountDownLatch stopLatch) {
+ return new ObexTestServer(sequence, stopLatch);
}
- public static class TestSequencer implements Callback {
-
- private final static int MSG_ID_TIMEOUT = 0x01;
- private final static int TIMEOUT_VALUE = 100*2000; // ms
- private ArrayList<SeqStep> mSequence = null;
- private HandlerThread mHandlerThread = null;
- private Handler mMessageHandler = null;
- private ObexTransport mClientTransport;
- private ObexTransport mServerTransport;
-
- private ClientSession mClientSession;
- private ServerSession mServerSession;
- ObexTestDataHandler mDataHandler;
-
- public enum OPTYPE {CONNECT, PUT, GET, DISCONNECT};
- public class SeqStep {
- /**
- * Test step class to define the operations to be tested.
- * Some of the data in these test steps will be modified during
- * test - e.g. the HeaderSets will be modified to enable SRM
- * and/or carry test information
- */
- /* Operation type - Connect, Get, Put etc. */
- public OPTYPE mType;
- /* The headers to send in the request - and validate on server side */
- public HeaderSet mReqHeaders = null;
- /* The headers to send in the response - and validate on client side */
- public HeaderSet mResHeaders = null;
- /* Use SRM */
- public boolean mUseSrm = false;
- /* The amount of data to include in the body */
- public ObexTestParams mParams = null;
- /* The offset into the data where the un-pause signal is to be sent */
- public int mUnPauseOffset = -1;
- /* The offset into the data where the Abort request is to be sent */
- public int mAbortOffset = -1;
- /* The side to perform Abort */
- public boolean mServerSideAbout = false;
- /* The ID of the test step */
- private int mId;
+}
- /* Arrays to hold expected sequence of request/response packets. */
- public ArrayList<ObexPacket> mRequestPackets = null;
- public ArrayList<ObexPacket> mResponsePackets = null;
-
- public int index = 0; /* requests with same index are executed in parallel
- (without waiting for a response) */
-
- public SeqStep(OPTYPE type) {
- mRequestPackets = new ArrayList<ObexPacket>();
- mResponsePackets = new ArrayList<ObexPacket>();
- mType = type;
- }
-
- /* TODO: Consider to build these automatically based on the operations
- * to be performed. Validate using utility functions - not strict
- * binary compare.*/
- public void addObexPacketSet(ObexPacket request, ObexPacket response) {
- mRequestPackets.add(request);
- mResponsePackets.add(response);
- }
- }
-
- public TestSequencer(ObexTransport clientTransport, ObexTransport serverTransport)
- throws IOException {
- /* Setup the looper thread to handle messages */
-// mHandlerThread = new HandlerThread("TestTimeoutHandler",
-// android.os.Process.THREAD_PRIORITY_BACKGROUND);
-// mHandlerThread.start();
-// Looper testLooper = mHandlerThread.getLooper();
-// mMessageHandler = new Handler(testLooper, this);
- mClientTransport = clientTransport;
- mServerTransport = serverTransport;
-
- //TODO: fix looper cleanup on server - crash after 464 iterations - related to prepare?
-
- /* Initialize members */
- mSequence = new ArrayList<SeqStep>();
- mDataHandler = new ObexTestDataHandler("(Client)");
- }
-
- /**
- * Add a test step to the sequencer.
- * @param type the OBEX operation to perform.
- * @return the created step, which can be decorated before execution.
- */
- public SeqStep addStep(OPTYPE type) {
- SeqStep newStep = new SeqStep(type);
- mSequence.add(newStep);
- return newStep;
- }
-
- /**
- * Add a sub-step to a sequencer step. All requests added to the same index will be send to
- * the SapServer in the order added before listening for the response.
- * The response order is not validated - hence for each response received the entire list of
- * responses in the step will be searched for a match.
- * @param index the index returned from addStep() to which the sub-step is to be added.
- * @param request The request to send to the SAP server
- * @param response The response to EXPECT from the SAP server
-
- public void addSubStep(int index, SapMessage request, SapMessage response) {
- SeqStep step = sequence.get(index);
- step.add(request, response);
- }*/
-
-
- /**
- * Run the sequence.
- * Validate the response is either the expected response or one of the expected events.
- *
- * @return true when done - asserts at error/fail
- */
- public boolean run() throws IOException {
- CountDownLatch stopLatch = new CountDownLatch(1);
-
- /* TODO:
- * First create sequencer to validate using BT-snoop
- * 1) Create the transports (this could include a validation sniffer on each side)
- * 2) Create a server thread with a link to the transport
- * 3) execute the client operation
- * 4) validate response
- *
- * On server:
- * 1) validate the request contains the expected content
- * 2) send response.
- * */
-
- /* Create the server */
- if(mServerTransport != null) {
- mServerSession = new ServerSession(mServerTransport, new ObexTestServer(mSequence,
- stopLatch), null);
- }
-
- /* Create the client */
- if(mClientTransport != null) {
- mClientSession = new ClientSession(mClientTransport);
-
- for(SeqStep step : mSequence) {
- long stepIndex = mSequence.indexOf(step);
-
- Log.i(TAG, "Executing step " + stepIndex + " of type: " + step.mType);
-
- switch(step.mType) {
- case CONNECT: {
- HeaderSet reqHeaders = step.mReqHeaders;
- if(reqHeaders == null) {
- reqHeaders = new HeaderSet();
- }
- reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
- HeaderSet response = mClientSession.connect(reqHeaders);
- validateHeaderSet(response, step.mResHeaders);
- break;
- }
- case GET:{
- HeaderSet reqHeaders = step.mReqHeaders;
- if(reqHeaders == null) {
- reqHeaders = new HeaderSet();
- }
- reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
- Operation op = mClientSession.get(reqHeaders);
- if(op != null) {
- op.noBodyHeader();
- mDataHandler.readData(op.openDataInputStream(), step.mParams);
- int responseCode = op.getResponseCode();
- Log.i(TAG, "response code: " + responseCode);
- HeaderSet response = op.getReceivedHeader();
- validateHeaderSet(response, step.mResHeaders);
- op.close();
- }
- break;
- }
- case PUT: {
- HeaderSet reqHeaders = step.mReqHeaders;
- if(reqHeaders == null) {
- reqHeaders = new HeaderSet();
- }
- reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
- Operation op = mClientSession.put(reqHeaders);
- if(op != null) {
- mDataHandler.writeData(op.openDataOutputStream(), step.mParams);
- int responseCode = op.getResponseCode();
- Log.i(TAG, "response code: " + responseCode);
- HeaderSet response = op.getReceivedHeader();
- validateHeaderSet(response, step.mResHeaders);
- op.close();
- }
- break;
- }
- case DISCONNECT: {
- Log.i(TAG,"Requesting disconnect...");
- HeaderSet reqHeaders = step.mReqHeaders;
- if(reqHeaders == null) {
- reqHeaders = new HeaderSet();
- }
- reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
- try{
- HeaderSet response = mClientSession.disconnect(reqHeaders);
- Log.i(TAG,"Received disconnect response...");
- // For some reason this returns -1 -> EOS
- // Maybe increase server timeout.
- validateHeaderSet(response, step.mResHeaders);
- } catch (IOException e) {
- Log.e(TAG, "Error getting response code", e);
- }
- break;
- }
- default:
- assertTrue("Unknown type: " + step.mType, false);
- break;
-
- }
- }
- mClientSession.close();
- }
- /* All done, close down... */
- if(mServerSession != null) {
- boolean interrupted = false;
- do {
- try {
- interrupted = false;
- Log.i(TAG,"Waiting for stopLatch signal...");
- stopLatch.await();
- } catch (InterruptedException e) {
- Log.w(TAG,e);
- interrupted = true;
- }
- } while (interrupted == true);
- Log.i(TAG,"stopLatch signal received closing down...");
- try {
- interrupted = false;
- Log.i(TAG," Sleep 50ms to allow disconnect signal to be send before closing.");
- Thread.sleep(50);
- } catch (InterruptedException e) {
- Log.w(TAG,e);
- interrupted = true;
- }
- mServerSession.close();
- }
- // this will close the I/O streams as well.
- return true;
- }
-
- public void shutdown() {
-// mMessageHandler.removeCallbacksAndMessages(null);
-// mMessageHandler.quit();
-// mMessageHandler = null;
- }
-
-
- void validateHeaderSet(HeaderSet headers, HeaderSet expected) throws IOException {
- /* TODO: Implement and assert if different */
- if(headers.getResponseCode() != ResponseCodes.OBEX_HTTP_OK) {
- Log.e(TAG,"Wrong ResponseCode: " + headers.getResponseCode());
- assertTrue(false);
- }
- }
-
-// private void startTimer() {
-// Message timeoutMessage = mMessageHandler.obtainMessage(MSG_ID_TIMEOUT);
-// mMessageHandler.sendMessageDelayed(timeoutMessage, TIMEOUT_VALUE);
-// }
-//
-// private void stopTimer() {
-// mMessageHandler.removeMessages(MSG_ID_TIMEOUT);
-// }
-
- @Override
- public boolean handleMessage(Message msg) {
-
- Log.i(TAG,"Handling message ID: " + msg.what);
-
- switch(msg.what) {
- case MSG_ID_TIMEOUT:
- Log.w(TAG, "Timeout occured!");
-/* try {
- //inStream.close();
- } catch (IOException e) {
- Log.e(TAG, "failed to close inStream", e);
- }
- try {
- //outStream.close();
- } catch (IOException e) {
- Log.e(TAG, "failed to close outStream", e);
- }*/
- break;
- default:
- /* Message not handled */
- return false;
- }
- return true; // Message handles
- }
-
-
-
- }
-
-}
\ No newline at end of file
diff --git a/tests/src/com/android/bluetooth/tests/ObexTestServer.java b/tests/src/com/android/bluetooth/tests/ObexTestServer.java
index b7df65c..1efe56d 100644
--- a/tests/src/com/android/bluetooth/tests/ObexTestServer.java
+++ b/tests/src/com/android/bluetooth/tests/ObexTestServer.java
@@ -13,8 +13,6 @@
import android.util.Log;
-import com.android.bluetooth.tests.ObexTest.TestSequencer.SeqStep;
-
public class ObexTestServer extends ServerRequestHandler {
private static final String TAG = "ObexTestServer";
@@ -40,7 +38,7 @@
int index;
int result = ResponseCodes.OBEX_HTTP_OK;
try {
- index = ((Long)request.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue();
+ index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
mOperationIndex = index;
} catch (IOException e) {
Log.e(TAG, "Exception in onConnect - aborting...");
@@ -58,7 +56,7 @@
int index;
int result = ResponseCodes.OBEX_HTTP_OK;
try {
- index = ((Long)request.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue();
+ index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
mOperationIndex = index;
} catch (IOException e) {
Log.e(TAG, "Exception in onDisconnect...");
@@ -89,7 +87,7 @@
try{
inStream = operation.openInputStream();
HeaderSet reqHeaders = operation.getReceivedHeader();
- int index = ((Long)reqHeaders.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue();
+ int index = ((Long)reqHeaders.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
mOperationIndex = index;
mDataHandler.readData(inStream, mSequence.get(index).mParams);
} catch (IOException e) {
@@ -121,7 +119,7 @@
try{
outStream = operation.openOutputStream();
HeaderSet reqHeaders = operation.getReceivedHeader();
- int index = ((Long)reqHeaders.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue();
+ int index = ((Long)reqHeaders.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
mOperationIndex = index;
mDataHandler.writeData(outStream, mSequence.get(index).mParams);
} catch (IOException e) {
diff --git a/tests/src/com/android/bluetooth/tests/SdpManagerTest.java b/tests/src/com/android/bluetooth/tests/SdpManagerTest.java
index c722072..2b7310d 100644
--- a/tests/src/com/android/bluetooth/tests/SdpManagerTest.java
+++ b/tests/src/com/android/bluetooth/tests/SdpManagerTest.java
@@ -34,8 +34,8 @@
public static final int SDP_RECORD_COUNT = 12; /* Maximum number of records to create */
public static final int SDP_ITERATIONS = 2000;
- public static final String SDP_SERVER_NAME = "SDP test Server";
- public static final String SDP_CLIENT_NAME = "SDP test Client";
+ public static final String SDP_SERVER_NAME = "SDP test server";
+ public static final String SDP_CLIENT_NAME = "SDP test client";
public static final long SDP_FEATURES = 0x87654321L; /* 32 bit */
public static final int SDP_MSG_TYPES = 0xf1; /* 8 bit */
@@ -51,7 +51,7 @@
Log.e(TAG,"No Bluetooth Device!");
assertTrue(false);
}
- ObexTest.enableBt(bt);
+ BluetoothTestUtils.enableBt(bt);
mManager = SdpManager.getDefaultManager();
addRemoveRecords(SDP_RECORD_COUNT);
}
@@ -62,7 +62,7 @@
Log.e(TAG,"No Bluetooth Device!");
assertTrue(false);
}
- ObexTest.enableBt(bt);
+ BluetoothTestUtils.enableBt(bt);
mManager = SdpManager.getDefaultManager();
int handles[] = new int[SDP_RECORD_COUNT];
@@ -175,7 +175,7 @@
mClientSession = new ClientSession(clientTransport);
{ // Connect
HeaderSet reqHeaders = new HeaderSet();
- reqHeaders.setHeader(ObexTest.STEP_INDEX_HEADER, (long)0);
+ reqHeaders.setHeader(TestSequencer.STEP_INDEX_HEADER, (long)0);
HeaderSet response = mClientSession.connect(reqHeaders);
assertEquals(response.responseCode, ResponseCodes.OBEX_HTTP_OK);
}
@@ -186,7 +186,7 @@
{ // get operation to trigger SDP search on peer device
HeaderSet reqHeaders = new HeaderSet();
- reqHeaders.setHeader(ObexTest.STEP_INDEX_HEADER, (long)iteration);
+ reqHeaders.setHeader(TestSequencer.STEP_INDEX_HEADER, (long)iteration);
reqHeaders.setHeader(HeaderSet.COUNT, (long)count);
reqHeaders.setHeader(HeaderSet.NAME, uuids_str);
Operation op = mClientSession.get(reqHeaders);
@@ -201,7 +201,7 @@
}
{ // disconnect to end test
HeaderSet reqHeaders = new HeaderSet();
- reqHeaders.setHeader(ObexTest.STEP_INDEX_HEADER, 0L); // signals end of test
+ reqHeaders.setHeader(TestSequencer.STEP_INDEX_HEADER, 0L); // signals end of test
HeaderSet response = mClientSession.disconnect(reqHeaders);
assertEquals(response.responseCode, ResponseCodes.OBEX_HTTP_OK);
}
diff --git a/tests/src/com/android/bluetooth/tests/SdpManagerTestServer.java b/tests/src/com/android/bluetooth/tests/SdpManagerTestServer.java
index 2151e56..19e01be 100644
--- a/tests/src/com/android/bluetooth/tests/SdpManagerTestServer.java
+++ b/tests/src/com/android/bluetooth/tests/SdpManagerTestServer.java
@@ -1,9 +1,6 @@
package com.android.bluetooth.tests;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
@@ -13,9 +10,12 @@
import javax.obex.ServerRequestHandler;
import junit.framework.Assert;
-
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothUuid;
+import android.bluetooth.SdpMasRecord;
+import android.bluetooth.SdpMnsRecord;
+import android.bluetooth.SdpOppOpsRecord;
+import android.bluetooth.SdpPseRecord;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -25,11 +25,6 @@
import com.android.bluetooth.btservice.AbstractionLayer;
import com.android.bluetooth.sdp.SdpManager;
-import com.android.bluetooth.sdp.SdpMasRecord;
-import com.android.bluetooth.sdp.SdpMnsRecord;
-import com.android.bluetooth.sdp.SdpOppOpsRecord;
-import com.android.bluetooth.sdp.SdpPseRecord;
-import com.android.bluetooth.tests.ObexTest.TestSequencer.SeqStep;
/**
* We use an OBEX server to execute SDP search operations, and validate results.
@@ -64,7 +59,7 @@
int index;
int result = ResponseCodes.OBEX_HTTP_OK;
try {
- index = ((Long)request.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue();
+ index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
mOperationIndex = index;
} catch (IOException e) {
Log.e(TAG, "Exception in onConnect - aborting...");
@@ -80,7 +75,7 @@
int index;
int result = ResponseCodes.OBEX_HTTP_OK;
try {
- index = ((Long)request.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue();
+ index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
mOperationIndex = index;
} catch (IOException e) {
Log.e(TAG, "Exception in onDisconnect...");
@@ -122,7 +117,7 @@
mResult = ResponseCodes.OBEX_HTTP_OK;
try{
HeaderSet reqHeaders = operation.getReceivedHeader();
- int index = ((Long)reqHeaders.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue();
+ int index = ((Long)reqHeaders.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue();
mOperationIndex = index;
/* Get the expected number of records to read. */
int count = ((Long)reqHeaders.getHeader(HeaderSet.COUNT)).intValue();
diff --git a/tests/src/com/android/bluetooth/tests/SeqStep.java b/tests/src/com/android/bluetooth/tests/SeqStep.java
new file mode 100644
index 0000000..3f6772a
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/SeqStep.java
@@ -0,0 +1,88 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import javax.obex.HeaderSet;
+import javax.obex.ObexPacket;
+import javax.obex.Operation;
+
+import junit.framework.Assert;
+
+import com.android.bluetooth.tests.TestSequencer.OPTYPE;
+
+public class SeqStep {
+ /**
+ * Test step class to define the operations to be tested.
+ * Some of the data in these test steps will be modified during
+ * test - e.g. the HeaderSets will be modified to enable SRM
+ * and/or carry test information
+ */
+ /* Operation type - Connect, Get, Put etc. */
+ public OPTYPE mType;
+ /* The headers to send in the request - and validate on server side */
+ public HeaderSet mReqHeaders = null;
+ /* The headers to send in the response - and validate on client side */
+ public HeaderSet mResHeaders = null;
+ /* Use SRM */
+ public boolean mUseSrm = false;
+ /* The amount of data to include in the body */
+ public ObexTestParams mParams = null;
+ /* The offset into the data where the un-pause signal is to be sent */
+ public int mUnPauseOffset = -1;
+ /* The offset into the data where the Abort request is to be sent */
+ public int mAbortOffset = -1;
+ /* The side to perform Abort */
+ public boolean mServerSideAbout = false;
+ /* The ID of the test step */
+ private int mId;
+
+ public boolean mSetPathBackup = false; /* bit 0 in flags */
+ public boolean mSetPathCreate = false; /* Inverse of bit 1 in flags */
+
+
+ public ISeqStepValidator mValidator = null;
+ public ISeqStepAction mServerPreAction = null;
+ public ISeqStepAction mClientPostAction = null;
+
+ /* Arrays to hold expected sequence of request/response packets. */
+ public ArrayList<ObexPacket> mRequestPackets = null;
+ public ArrayList<ObexPacket> mResponsePackets = null;
+
+ public int index = 0; /* requests with same index are executed in parallel
+ (without waiting for a response) */
+
+ public SeqStep(OPTYPE type) {
+ mRequestPackets = new ArrayList<ObexPacket>();
+ mResponsePackets = new ArrayList<ObexPacket>();
+ mType = type;
+ }
+
+ public boolean validate(HeaderSet response, Operation op) throws IOException {
+ Assert.assertNotNull(mValidator);
+ return mValidator.validate(this, response, op);
+ }
+
+ public void serverPreAction(HeaderSet request, Operation op) throws IOException {
+ if(mServerPreAction != null) {
+ mServerPreAction.execute(this, request, op);
+ }
+ }
+
+ public void clientPostAction(HeaderSet response, Operation op) throws IOException {
+ if(mClientPostAction != null) {
+ mClientPostAction.execute(this, response, op);
+ }
+ }
+
+
+ /* TODO: Consider to build these automatically based on the operations
+ * to be performed. Validate using utility functions - not strict
+ * binary compare.
+ *
+ * OR simply remove!*/
+ public void addObexPacketSet(ObexPacket request, ObexPacket response) {
+ mRequestPackets.add(request);
+ mResponsePackets.add(response);
+ }
+}
diff --git a/tests/src/com/android/bluetooth/tests/TestSequencer.java b/tests/src/com/android/bluetooth/tests/TestSequencer.java
new file mode 100644
index 0000000..fb0dcba
--- /dev/null
+++ b/tests/src/com/android/bluetooth/tests/TestSequencer.java
@@ -0,0 +1,284 @@
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ObexTransport;
+import javax.obex.Operation;
+import javax.obex.ServerSession;
+
+import junit.framework.Assert;
+
+import android.content.Context;
+import android.hardware.camera2.impl.GetCommand;
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.PowerManager;
+import android.util.Log;
+
+public class TestSequencer implements Callback {
+ protected static String TAG = "TestSequencer";
+ protected static final boolean D = true;
+
+ private final static int MSG_ID_TIMEOUT = 0x01;
+ private final static int TIMEOUT_VALUE = 100*2000; // ms
+ private ArrayList<SeqStep> mSequence = null;
+ private HandlerThread mHandlerThread = null;
+ private Handler mMessageHandler = null;
+ private ObexTransport mClientTransport;
+ private ObexTransport mServerTransport;
+
+ private ClientSession mClientSession = null;
+ private ServerSession mServerSession = null;
+ public static final int STEP_INDEX_HEADER = 0xF1; /*0xFE*/
+
+ public enum OPTYPE {CONNECT, PUT, GET, SET_PATH, DISCONNECT};
+
+ private ITestSequenceConfigurator mConfigurator = null;
+
+ public TestSequencer(ObexTransport clientTransport, ObexTransport serverTransport,
+ ITestSequenceConfigurator configurator)
+ throws IOException {
+ /* Setup the looper thread to handle timeout messages */
+// mHandlerThread = new HandlerThread("TestTimeoutHandler",
+// android.os.Process.THREAD_PRIORITY_BACKGROUND);
+// mHandlerThread.start();
+// Looper testLooper = mHandlerThread.getLooper();
+// mMessageHandler = new Handler(testLooper, this);
+ //TODO: fix looper cleanup on server - crash after 464 iterations - related to prepare?
+
+ mClientTransport = clientTransport;
+ mServerTransport = serverTransport;
+
+ /* Initialize members */
+ mSequence = new ArrayList<SeqStep>();
+ mConfigurator = configurator;
+ Assert.assertNotNull(configurator);
+ }
+
+ /**
+ * Add a test step to the sequencer.
+ * @param type the OBEX operation to perform.
+ * @return the created step, which can be decorated before execution.
+ */
+ public SeqStep addStep(OPTYPE type, ISeqStepValidator validator) {
+ SeqStep newStep = new SeqStep(type);
+ newStep.mValidator = validator;
+ mSequence.add(newStep);
+ return newStep;
+ }
+
+ /**
+ * Add a sub-step to a sequencer step. All requests added to the same index will be send to
+ * the SapServer in the order added before listening for the response.
+ * The response order is not validated - hence for each response received the entire list of
+ * responses in the step will be searched for a match.
+ * @param index the index returned from addStep() to which the sub-step is to be added.
+ * @param request The request to send to the SAP server
+ * @param response The response to EXPECT from the SAP server
+
+ public void addSubStep(int index, SapMessage request, SapMessage response) {
+ SeqStep step = sequence.get(index);
+ step.add(request, response);
+ }*/
+
+
+ /**
+ * Run the sequence.
+ * Validate the response is either the expected response or one of the expected events.
+ *
+ * @return true when done - asserts at error/fail
+ */
+ public boolean run(Context context) throws IOException {
+ CountDownLatch stopLatch = new CountDownLatch(1);
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ //wl.acquire();
+ try {
+ /* TODO:
+ * First create sequencer to validate using BT-snoop
+ * 1) Create the transports (this could include a validation sniffer on each side)
+ * 2) Create a server thread with a link to the transport
+ * 3) execute the client operation
+ * 4) validate response
+ *
+ * On server:
+ * 1) validate the request contains the expected content
+ * 2) send response.
+ * */
+
+ /* Create the server */
+ if(mServerTransport != null) {
+ mServerSession = new ServerSession(mServerTransport,
+ mConfigurator.getObexServer(mSequence, stopLatch) , null);
+ }
+
+ /* Create the client */
+ if(mClientTransport != null) {
+ mClientSession = new ClientSession(mClientTransport);
+
+ for(SeqStep step : mSequence) {
+ long stepIndex = mSequence.indexOf(step);
+
+ Log.i(TAG, "Executing step " + stepIndex + " of type: " + step.mType);
+
+ switch(step.mType) {
+ case CONNECT: {
+ HeaderSet reqHeaders = step.mReqHeaders;
+ if(reqHeaders == null) {
+ reqHeaders = new HeaderSet();
+ }
+ reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
+ HeaderSet response = mClientSession.connect(reqHeaders);
+ step.validate(response, null);
+ step.clientPostAction(response, null);
+ break;
+ }
+ case GET:{
+ HeaderSet reqHeaders = step.mReqHeaders;
+ if(reqHeaders == null) {
+ reqHeaders = new HeaderSet();
+ }
+ reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
+ Log.i(TAG, " Starting operation...");
+ Operation op = mClientSession.get(reqHeaders);
+ Log.i(TAG, " Operation done...");
+ step.validate(null, op);
+ step.clientPostAction(null, op);
+ break;
+ }
+ case PUT: {
+ HeaderSet reqHeaders = step.mReqHeaders;
+ if(reqHeaders == null) {
+ reqHeaders = new HeaderSet();
+ }
+ reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
+ Operation op = mClientSession.put(reqHeaders);
+ step.validate(null, op);
+ step.clientPostAction(null, op);
+ break;
+ }
+ case SET_PATH: {
+ HeaderSet reqHeaders = step.mReqHeaders;
+ if(reqHeaders == null) {
+ reqHeaders = new HeaderSet();
+ }
+ reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
+ try{
+ HeaderSet response = mClientSession.setPath(reqHeaders,
+ step.mSetPathBackup, step.mSetPathCreate);;
+ Log.i(TAG,"Received setPath response...");
+ step.validate(response, null);
+ step.clientPostAction(response, null);
+ } catch (IOException e) {
+ Log.e(TAG, "Error getting response code", e);
+ }
+ break;
+ }
+ case DISCONNECT: {
+ Log.i(TAG,"Requesting disconnect...");
+ HeaderSet reqHeaders = step.mReqHeaders;
+ if(reqHeaders == null) {
+ reqHeaders = new HeaderSet();
+ }
+ reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex);
+ try{
+ HeaderSet response = mClientSession.disconnect(reqHeaders);
+ Log.i(TAG,"Received disconnect response...");
+ step.validate(response, null);
+ step.clientPostAction(response, null);
+ } catch (IOException e) {
+ Log.e(TAG, "Error getting response code", e);
+ }
+ break;
+ }
+ default:
+ Assert.assertTrue("Unknown type: " + step.mType, false);
+ break;
+
+ }
+ }
+ mClientSession.close();
+ }
+ /* All done, close down... */
+ if(mServerSession != null) {
+ boolean interrupted = false;
+ do {
+ try {
+ interrupted = false;
+ Log.i(TAG,"Waiting for stopLatch signal...");
+ stopLatch.await();
+ } catch (InterruptedException e) {
+ Log.w(TAG,e);
+ interrupted = true;
+ }
+ } while (interrupted == true);
+ Log.i(TAG,"stopLatch signal received closing down...");
+ try {
+ interrupted = false;
+ Log.i(TAG," Sleep 50ms to allow disconnect signal to be send before closing.");
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ Log.w(TAG,e);
+ interrupted = true;
+ }
+ mServerSession.close();
+ }
+ // this will close the I/O streams as well.
+ } finally {
+ //wl.release();
+ }
+ return true;
+ }
+
+ public void shutdown() {
+// mMessageHandler.removeCallbacksAndMessages(null);
+// mMessageHandler.quit();
+// mMessageHandler = null;
+ }
+
+
+// private void startTimer() {
+// Message timeoutMessage = mMessageHandler.obtainMessage(MSG_ID_TIMEOUT);
+// mMessageHandler.sendMessageDelayed(timeoutMessage, TIMEOUT_VALUE);
+// }
+//
+// private void stopTimer() {
+// mMessageHandler.removeMessages(MSG_ID_TIMEOUT);
+// }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+
+ Log.i(TAG,"Handling message ID: " + msg.what);
+
+ switch(msg.what) {
+ case MSG_ID_TIMEOUT:
+ Log.w(TAG, "Timeout occured!");
+/* try {
+ //inStream.close();
+ } catch (IOException e) {
+ Log.e(TAG, "failed to close inStream", e);
+ }
+ try {
+ //outStream.close();
+ } catch (IOException e) {
+ Log.e(TAG, "failed to close outStream", e);
+ }*/
+ break;
+ default:
+ /* Message not handled */
+ return false;
+ }
+ return true; // Message handles
+ }
+
+
+
+}
+