summaryrefslogtreecommitdiffstats
path: root/lib/mapapi/com
diff options
context:
space:
mode:
authorkschulz <k.schulz@samsung.com>2015-03-17 11:47:46 +0100
committerAndre Eisenbach <eisenbach@google.com>2015-04-10 19:43:37 -0700
commit5a60e47497f21f64e6d79420dc4c56c1907df22a (patch)
tree68a80ce2c49692ab9b50204ba8ee8a6aa238df02 /lib/mapapi/com
parent0dcecb2cfd921916ed586183d64ec9fd250a6e4c (diff)
downloadandroid_packages_apps_Bluetooth-5a60e47497f21f64e6d79420dc4c56c1907df22a.tar.gz
android_packages_apps_Bluetooth-5a60e47497f21f64e6d79420dc4c56c1907df22a.tar.bz2
android_packages_apps_Bluetooth-5a60e47497f21f64e6d79420dc4c56c1907df22a.zip
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
Diffstat (limited to 'lib/mapapi/com')
-rw-r--r--lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java751
-rw-r--r--lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapIMProvider.java689
2 files changed, 1395 insertions, 45 deletions
diff --git a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
index 32018ba95..6a40a27be 100644
--- a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
+++ b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
@@ -21,7 +21,7 @@ import android.net.Uri;
/**
- * 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 @@ import android.net.Uri;
* might provide.</li>
* <li>Folder data set with the folder structure for the messages. Each message is linked to an
* entry in this data set.</li>
+ * <li>Conversation data set with the thread structure of the messages. Each message is linked
+ * to an entry in this data set.</li>
* </ul>
*
* To enable that the Bluetooth Message Access Server can detect the content provider implementing
- * this interface, the {@code provider} tag for the bluetooth related content provider must
+ * this interface, the {@code provider} tag for the Bluetooth related content provider must
* have an intent-filter like the following in the manifest:
* <pre class="prettyprint">&lt;provider android:authorities="[PROVIDER AUTHORITY]"
android:exported="true"
@@ -66,8 +68,10 @@ public final class BluetoothMapContract {
* 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 final class BluetoothMapContract {
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 @@ public final class BluetoothMapContract {
* 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 @@ public final class BluetoothMapContract {
}
/**
* 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 @@ public final class BluetoothMapContract {
}
/**
* 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 @@ public final class BluetoothMapContract {
}
/**
* 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 @@ public final class BluetoothMapContract {
}
/**
* 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 @@ public final class BluetoothMapContract {
}
/**
+ * 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 @@ public final class BluetoothMapContract {
* 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 @@ public final class BluetoothMapContract {
*
* 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 @@ public final class BluetoothMapContract {
* 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 final class BluetoothMapContract {
*/
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 @@ public final class BluetoothMapContract {
/**
* 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 final class BluetoothMapContract {
*/
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 final class BluetoothMapContract {
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 final class BluetoothMapContract {
*/
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 final class BluetoothMapContract {
*/
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 @@ public final class BluetoothMapContract {
MessageColumns._ID,
MessageColumns.DATE,
MessageColumns.SUBJECT,
+ //TODO REMOVE WHEN Parts Table is in place
+ MessageColumns.BODY,
+ MessageColumns.MESSAGE_SIZE,
+ MessageColumns.FOLDER_ID,
MessageColumns.FLAG_READ,
+ MessageColumns.FLAG_PROTECTED,
+ MessageColumns.FLAG_HIGH_PRIORITY,
MessageColumns.FLAG_ATTACHMENT,
- MessageColumns.FOLDER_ID,
- MessageColumns.ACCOUNT_ID,
+ 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 @@ public final class BluetoothMapContract {
};
/**
+ * 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 @@ public final class BluetoothMapContract {
};
+ /**
+ * 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 000000000..d0c642ccd
--- /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);
+ }
+}