| /* |
| * 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); |
| } |
| } |