Merge "Map Client use content provider for messages"
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java b/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
index 2dbdd1d..1d466c5 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
@@ -46,7 +46,7 @@
* jpeg data or the text.getBytes("utf-8") */
- String getDataAsString() {
+ public String getDataAsString() {
String result = null;
String charset = mCharsetName;
// Figure out if we support the charset, else fall back to UTF-8, as this is what
diff --git a/src/com/android/bluetooth/mapclient/MapClientContent.java b/src/com/android/bluetooth/mapclient/MapClientContent.java
new file mode 100644
index 0000000..57d201f
--- /dev/null
+++ b/src/com/android/bluetooth/mapclient/MapClientContent.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.mapclient;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothMapClient;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Telephony;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.Sms;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.bluetooth.map.BluetoothMapbMessageMime;
+import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
+import com.android.vcard.VCardConstants;
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardProperty;
+
+import com.google.android.mms.pdu.PduHeaders;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+class MapClientContent {
+
+ private static final String INBOX_PATH = "telecom/msg/inbox";
+ private static final String TAG = "MapClientContent";
+ private static final int DEFAULT_CHARSET = 106;
+ private static final int ORIGINATOR_ADDRESS_TYPE = 137;
+ private static final int RECIPIENT_ADDRESS_TYPE = 151;
+
+ final BluetoothDevice mDevice;
+ private final Context mContext;
+ private final Callbacks mCallbacks;
+ private final ContentResolver mResolver;
+ ContentObserver mContentObserver;
+ String mPhoneNumber = null;
+ private int mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private SubscriptionManager mSubscriptionManager;
+ private HashMap<String, Uri> mHandleToUriMap = new HashMap<>();
+ private HashMap<Uri, MessageStatus> mUriToHandleMap = new HashMap<>();
+
+ /**
+ * Callbacks
+ * API to notify about statusChanges as observed from the content provider
+ */
+ interface Callbacks {
+ void onMessageStatusChanged(String handle, int status);
+ }
+
+ /**
+ * MapClientContent manages all interactions between Bluetooth and the messaging provider.
+ *
+ * Changes to the database are mirrored between the remote and local providers, specifically new
+ * messages, changes to read status, and removal of messages.
+ *
+ * context: the context that all content provider interactions are conducted
+ * MceStateMachine: the interface to send outbound updates such as when a message is read
+ * locally
+ * device: the associated Bluetooth device used for associating messages with a subscription
+ */
+ MapClientContent(Context context, Callbacks callbacks,
+ BluetoothDevice device) {
+ mContext = context;
+ mDevice = device;
+ mCallbacks = callbacks;
+ mResolver = mContext.getContentResolver();
+
+ mSubscriptionManager = (SubscriptionManager) mContext
+ .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ mSubscriptionManager
+ .addSubscriptionInfoRecord(device.getAddress(), /*device.getName()*/"TEST", 0,
+ SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
+ SubscriptionInfo info = mSubscriptionManager
+ .getActiveSubscriptionInfoForIcc(mDevice.getAddress());
+ if (info != null) {
+ mSubscriptionId = info.getSubscriptionId();
+ mSubscriptionManager.setDisplayNumber(mPhoneNumber, mSubscriptionId);
+ }
+
+ mContentObserver = new ContentObserver(null) {
+ @Override
+ public boolean deliverSelfNotifications() {
+ return false;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ logV("onChange");
+ findChangeInDatabase();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ logV("onChange" + uri.toString());
+ findChangeInDatabase();
+ }
+ };
+
+ clearMessages();
+ mResolver.registerContentObserver(Sms.CONTENT_URI, true, mContentObserver);
+ mResolver.registerContentObserver(Mms.CONTENT_URI, true, mContentObserver);
+ mResolver.registerContentObserver(MmsSms.CONTENT_URI, true, mContentObserver);
+ }
+
+ private static void logD(String message) {
+ if (MapClientService.DBG) {
+ Log.d(TAG, message);
+ }
+ }
+
+ private static void logV(String message) {
+ if (MapClientService.VDBG) {
+ Log.v(TAG, message);
+ }
+ }
+
+ /**
+ * parseLocalNumber
+ *
+ * Determine the connected phone's number by extracting it from an inbound or outbound mms
+ * message. This number is necessary such that group messages can be displayed correctly.
+ */
+ void parseLocalNumber(Bmessage message) {
+ if (mPhoneNumber != null) {
+ return;
+ }
+ if (INBOX_PATH.equals(message.getFolder())) {
+ ArrayList<VCardEntry> recipients = message.getRecipients();
+ if (recipients != null && !recipients.isEmpty()) {
+ mPhoneNumber = PhoneNumberUtils.extractNetworkPortion(
+ recipients.get(0).getPhoneList().get(0).getNumber());
+ }
+ } else {
+ mPhoneNumber = PhoneNumberUtils.extractNetworkPortion(getOriginatorNumber(message));
+ }
+ logV("Found phone number: " + mPhoneNumber);
+ }
+
+ /**
+ * storeMessage
+ *
+ * Store a message in database with the associated handle and timestamp.
+ * The handle is used to associate the local message with the remote message.
+ */
+ void storeMessage(Bmessage message, String handle, Long timestamp) {
+ switch (message.getType()) {
+ case MMS:
+ storeMms(message, handle, timestamp);
+ return;
+ case SMS_CDMA:
+ case SMS_GSM:
+ storeSms(message, handle, timestamp);
+ return;
+ default:
+ logD("Request to store unsupported message type: " + message.getType());
+ }
+ }
+
+ private void storeSms(Bmessage message, String handle, Long timestamp) {
+ logD("storeSms");
+ logV(message.toString());
+ VCardEntry originator = message.getOriginator();
+ String recipients;
+ if (INBOX_PATH.equals(message.getFolder())) {
+ recipients = getOriginatorNumber(message);
+ } else {
+ recipients = message.getRecipients().get(0).getPhoneList().get(0).getNumber();
+ }
+ logV("Received SMS from Number " + recipients);
+ String messageContent;
+
+ Uri contentUri = INBOX_PATH.equalsIgnoreCase(message.getFolder()) ? Sms.Inbox.CONTENT_URI
+ : Sms.Sent.CONTENT_URI;
+ ContentValues values = new ContentValues();
+ long threadId = getThreadId(message);
+ int readStatus = message.getStatus() == Bmessage.Status.READ ? 1 : 0;
+
+ values.put(Sms.THREAD_ID, threadId);
+ values.put(Sms.ADDRESS, recipients);
+ values.put(Sms.BODY, message.getBodyContent());
+ values.put(Sms.SUBSCRIPTION_ID, mSubscriptionId);
+ values.put(Sms.DATE, timestamp);
+ values.put(Sms.READ, readStatus);
+
+ Uri results = mResolver.insert(contentUri, values);
+ mHandleToUriMap.put(handle, results);
+ mUriToHandleMap.put(results, new MessageStatus(handle, readStatus));
+ logD("Map InsertedThread" + results);
+ }
+
+ /**
+ * deleteMessage
+ * remove a message from the local provider based on a remote change
+ */
+ void deleteMessage(String handle) {
+ logD("deleting handle" + handle);
+ Uri messageToChange = mHandleToUriMap.get(handle);
+ if (messageToChange != null) {
+ mResolver.delete(messageToChange, null);
+ }
+ }
+
+
+ /**
+ * markRead
+ * mark a message read in the local provider based on a remote change
+ */
+ void markRead(String handle) {
+ logD("marking read " + handle);
+ Uri messageToChange = mHandleToUriMap.get(handle);
+ if (messageToChange != null) {
+ ContentValues values = new ContentValues();
+ values.put(Sms.READ, 1);
+ mResolver.update(messageToChange, values, null);
+ }
+ }
+
+ /**
+ * findChangeInDatabase
+ * compare the current state of the local content provider to the expected state and propagate
+ * changes to the remote.
+ */
+ private void findChangeInDatabase() {
+ HashMap<Uri, MessageStatus> originalUriToHandleMap;
+ HashMap<Uri, MessageStatus> duplicateUriToHandleMap;
+
+ originalUriToHandleMap = mUriToHandleMap;
+ duplicateUriToHandleMap = new HashMap<>(originalUriToHandleMap);
+ for (Uri uri : new Uri[]{Mms.CONTENT_URI, Sms.CONTENT_URI}) {
+ Cursor cursor = mResolver.query(uri, null, null, null, null);
+ while (cursor.moveToNext()) {
+ Uri index = Uri
+ .withAppendedPath(uri, cursor.getString(cursor.getColumnIndex("_id")));
+ int readStatus = cursor.getInt(cursor.getColumnIndex(Sms.READ));
+ MessageStatus currentMessage = duplicateUriToHandleMap.remove(index);
+ if (currentMessage != null && currentMessage.mRead != readStatus) {
+ logV(currentMessage.mHandle);
+ currentMessage.mRead = readStatus;
+ mCallbacks.onMessageStatusChanged(currentMessage.mHandle,
+ BluetoothMapClient.READ);
+ }
+ }
+ }
+ for (HashMap.Entry record : duplicateUriToHandleMap.entrySet()) {
+ logV("Deleted " + ((MessageStatus) record.getValue()).mHandle);
+ originalUriToHandleMap.remove(record.getKey());
+ mCallbacks.onMessageStatusChanged(((MessageStatus) record.getValue()).mHandle,
+ BluetoothMapClient.DELETED);
+ }
+ }
+
+ private void storeMms(Bmessage message, String handle, Long timestamp) {
+ logD("storeMms");
+ logV(message.toString());
+ try {
+ parseLocalNumber(message);
+ ContentValues values = new ContentValues();
+ long threadId = getThreadId(message);
+ BluetoothMapbMessageMime mmsBmessage = new BluetoothMapbMessageMime();
+ mmsBmessage.parseMsgPart(message.getBodyContent());
+ int read = message.getStatus() == Bmessage.Status.READ ? 1 : 0;
+ logD("Parsed");
+ values.put(Mms.SUBSCRIPTION_ID, mSubscriptionId);
+ values.put(Mms.THREAD_ID, threadId);
+ values.put(Mms.DATE, timestamp / 1000L);
+ values.put(Mms.TEXT_ONLY, true);
+ values.put(Mms.MESSAGE_BOX, Mms.MESSAGE_BOX_INBOX);
+ values.put(Mms.READ, read);
+ values.put(Mms.SEEN, 0);
+ values.put(Mms.MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ);
+ values.put(Mms.MMS_VERSION, PduHeaders.CURRENT_MMS_VERSION);
+ values.put(Mms.PRIORITY, PduHeaders.PRIORITY_NORMAL);
+ values.put(Mms.READ_REPORT, PduHeaders.VALUE_NO);
+ values.put(Mms.TRANSACTION_ID, "T" + Long.toHexString(System.currentTimeMillis()));
+ values.put(Mms.DELIVERY_REPORT, PduHeaders.VALUE_NO);
+ values.put(Mms.LOCKED, 0);
+ values.put(Mms.CONTENT_TYPE, "application/vnd.wap.multipart.related");
+ values.put(Mms.MESSAGE_CLASS, PduHeaders.MESSAGE_CLASS_PERSONAL_STR);
+ values.put(Mms.MESSAGE_SIZE, mmsBmessage.getSize());
+
+ Uri results = mResolver.insert(Mms.CONTENT_URI, values);
+
+ logD("Map InsertedThread" + results);
+
+ for (MimePart part : mmsBmessage.getMimeParts()) {
+ storeMmsPart(part, results);
+ }
+
+ storeAddressPart(message, results);
+
+ Uri contentUri =
+ INBOX_PATH.equalsIgnoreCase(message.getFolder()) ? Mms.Inbox.CONTENT_URI
+ : Mms.Sent.CONTENT_URI;
+
+ String messageContent = mmsBmessage.getMessageAsText();
+
+ values.put(Mms.Part.CONTENT_TYPE, "plain/text");
+ values.put(Mms.SUBSCRIPTION_ID, mSubscriptionId);
+ mUriToHandleMap.put(results, new MessageStatus(handle, read));
+ } catch (Exception e) {
+ Log.e(TAG, e.toString());
+ throw e;
+ }
+ }
+
+ private Uri storeMmsPart(MimePart messagePart, Uri messageUri) {
+ ContentValues values = new ContentValues();
+ values.put(Mms.Part.CONTENT_TYPE, "text/plain");
+ values.put(Mms.Part.CHARSET, DEFAULT_CHARSET);
+ values.put(Mms.Part.FILENAME, "text_1.txt");
+ values.put(Mms.Part.NAME, "text_1.txt");
+ values.put(Mms.Part.CONTENT_ID, messagePart.mContentId);
+ values.put(Mms.Part.CONTENT_LOCATION, messagePart.mContentLocation);
+ values.put(Mms.Part.TEXT, messagePart.getDataAsString());
+
+ Uri contentUri = Uri.parse(messageUri.toString() + "/part");
+ Uri results = mResolver.insert(contentUri, values);
+ logD("Inserted" + results);
+ return results;
+ }
+
+ private void storeAddressPart(Bmessage message, Uri messageUri) {
+ ContentValues values = new ContentValues();
+ Uri contentUri = Uri.parse(messageUri.toString() + "/addr");
+ String originator = getOriginatorNumber(message);
+ values.put(Mms.Addr.CHARSET, DEFAULT_CHARSET);
+
+ values.put(Mms.Addr.ADDRESS, originator);
+ values.put(Mms.Addr.TYPE, ORIGINATOR_ADDRESS_TYPE);
+ mResolver.insert(contentUri, values);
+
+ Set<String> messageContacts = new ArraySet<>();
+ getRecipientsFromMessage(message, messageContacts);
+ for (String recipient : messageContacts) {
+ values.put(Mms.Addr.ADDRESS, recipient);
+ values.put(Mms.Addr.TYPE, RECIPIENT_ADDRESS_TYPE);
+ mResolver.insert(contentUri, values);
+ }
+ }
+
+ private Uri insertIntoMmsTable(String subject) {
+ ContentValues mmsValues = new ContentValues();
+ mmsValues.put(Mms.TEXT_ONLY, 1);
+ mmsValues.put(Mms.MESSAGE_TYPE, 128);
+ mmsValues.put(Mms.SUBJECT, subject);
+ return mResolver.insert(Mms.CONTENT_URI, mmsValues);
+ }
+
+ /**
+ * clearMessages
+ * clean up the content provider on startup and shutdown
+ */
+ void clearMessages() {
+ mResolver.unregisterContentObserver(mContentObserver);
+ mResolver.delete(Sms.CONTENT_URI, Sms.SUBSCRIPTION_ID + " =? ",
+ new String[]{Integer.toString(mSubscriptionId)});
+ mResolver.delete(Mms.CONTENT_URI, Mms.SUBSCRIPTION_ID + " =? ",
+ new String[]{Integer.toString(mSubscriptionId)});
+ }
+
+ /**
+ * getThreadId
+ * utilize the originator and recipients to obtain the thread id
+ */
+ private long getThreadId(Bmessage message) {
+
+ Set<String> messageContacts = new ArraySet<>();
+ String originator = PhoneNumberUtils.extractNetworkPortion(getOriginatorNumber(message));
+ if (originator != null) {
+ messageContacts.add(originator);
+ }
+ getRecipientsFromMessage(message, messageContacts);
+
+ messageContacts.remove(mPhoneNumber);
+ logV("Contacts = " + messageContacts.toString());
+ return Telephony.Threads.getOrCreateThreadId(mContext, messageContacts);
+ }
+
+ private void getRecipientsFromMessage(Bmessage message, Set<String> messageContacts) {
+ List<VCardEntry> recipients = message.getRecipients();
+ for (VCardEntry recipient : recipients) {
+ List<VCardEntry.PhoneData> phoneData = recipient.getPhoneList();
+ if (phoneData != null && phoneData.size() > 0) {
+ messageContacts
+ .add(PhoneNumberUtils.extractNetworkPortion(phoneData.get(0).getNumber()));
+ }
+ }
+ }
+
+ private String getOriginatorNumber(Bmessage message) {
+ VCardEntry originator = message.getOriginator();
+ if (originator != null) {
+ List<VCardEntry.PhoneData> phoneData = originator.getPhoneList();
+ if (phoneData != null && phoneData.size() > 0) {
+ return phoneData.get(0).getNumber();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * addThreadContactToEntries
+ * utilizing the thread id fill in the appropriate fields of bmsg with the intended recipients
+ */
+ boolean addThreadContactsToEntries(Bmessage bmsg, String thread) {
+ String threadId = Uri.parse(thread).getLastPathSegment();
+
+ logD("MATCHING THREAD" + threadId);
+
+ Cursor cursor = mResolver
+ .query(Uri.withAppendedPath(MmsSms.CONTENT_CONVERSATIONS_URI,
+ threadId + "/recipients"),
+ null, null,
+ null, null);
+
+ if (cursor.moveToNext()) {
+ logD("Columns" + Arrays.toString(cursor.getColumnNames()));
+ logV("CONTACT LIST: " + cursor.getString(cursor.getColumnIndex("recipient_ids")));
+ addRecipientsToEntries(bmsg,
+ cursor.getString(cursor.getColumnIndex("recipient_ids")).split(" "));
+ return true;
+ } else {
+ Log.w(TAG, "Thread Not Found");
+ return false;
+ }
+ }
+
+
+ private void addRecipientsToEntries(Bmessage bmsg, String[] recipients) {
+ logV("CONTACT LIST: " + Arrays.toString(recipients));
+ for (String recipient : recipients) {
+ Cursor cursor = mResolver
+ .query(Uri.parse("content://mms-sms/canonical-address/" + recipient), null,
+ null, null,
+ null);
+ while (cursor.moveToNext()) {
+ String number = cursor.getString(cursor.getColumnIndex(Mms.Addr.ADDRESS));
+ logV("CONTACT number: " + number);
+ VCardEntry destEntry = new VCardEntry();
+ VCardProperty destEntryPhone = new VCardProperty();
+ destEntryPhone.setName(VCardConstants.PROPERTY_TEL);
+ destEntryPhone.addValues(number);
+ destEntry.addProperty(destEntryPhone);
+ bmsg.addRecipient(destEntry);
+ }
+ }
+ }
+
+ /**
+ * MessageStatus
+ *
+ * Helper class to store associations between remote and local provider based on message handle
+ * and read status
+ */
+ class MessageStatus {
+
+ String mHandle;
+ int mRead;
+
+ MessageStatus(String handle, int read) {
+ mHandle = handle;
+ mRead = read;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return ((other instanceof MessageStatus) && ((MessageStatus) other).mHandle
+ .equals(mHandle));
+ }
+ }
+}
diff --git a/src/com/android/bluetooth/mapclient/MapClientService.java b/src/com/android/bluetooth/mapclient/MapClientService.java
index 3c056da..d1bf260 100644
--- a/src/com/android/bluetooth/mapclient/MapClientService.java
+++ b/src/com/android/bluetooth/mapclient/MapClientService.java
@@ -456,8 +456,8 @@
}
private MapClientService getService() {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "MAP call not allowed for non-active user");
+ if (!Utils.checkCaller() && !MapUtils.isSystemUser()) {
+ Log.w(TAG, "MAP call not allowed for non-active and non-system user.");
return null;
}
diff --git a/src/com/android/bluetooth/mapclient/MapUtils.java b/src/com/android/bluetooth/mapclient/MapUtils.java
index c47526a..a26816d 100644
--- a/src/com/android/bluetooth/mapclient/MapUtils.java
+++ b/src/com/android/bluetooth/mapclient/MapUtils.java
@@ -16,6 +16,7 @@
package com.android.bluetooth.mapclient;
import android.os.SystemProperties;
+import android.os.UserHandle;
import com.android.bluetooth.Utils;
import com.android.internal.annotations.VisibleForTesting;
@@ -32,6 +33,10 @@
sMnsService = service;
}
+ static boolean isSystemUser() {
+ return UserHandle.getCallingUserId() == UserHandle.USER_SYSTEM;
+ }
+
static MnsService newMnsServiceInstance(MapClientService mapClientService) {
return (sMnsService == null) ? new MnsService(mapClientService) : sMnsService;
}
diff --git a/src/com/android/bluetooth/mapclient/MceStateMachine.java b/src/com/android/bluetooth/mapclient/MceStateMachine.java
index 5395595..1ca32d8 100644
--- a/src/com/android/bluetooth/mapclient/MceStateMachine.java
+++ b/src/com/android/bluetooth/mapclient/MceStateMachine.java
@@ -81,7 +81,7 @@
* a connection to the Message Access Server is created and a request to enable notification of new
* messages is sent.
*/
-final class MceStateMachine extends StateMachine {
+class MceStateMachine extends StateMachine {
// Messages for events handled by the StateMachine
static final int MSG_MAS_CONNECTED = 1001;
static final int MSG_MAS_DISCONNECTED = 1002;
@@ -110,6 +110,7 @@
private static final String FOLDER_MSG = "msg";
private static final String FOLDER_OUTBOX = "outbox";
private static final String FOLDER_INBOX = "inbox";
+ private static final String FOLDER_SENT = "sent";
private static final String INBOX_PATH = "telecom/msg/inbox";
@@ -123,6 +124,7 @@
private final BluetoothDevice mDevice;
private MapClientService mService;
private MasClient mMasClient;
+ private final MapClientContent mDatabase;
private HashMap<String, Bmessage> mSentMessageLog = new HashMap<>(MAX_MESSAGES);
private HashMap<Bmessage, PendingIntent> mSentReceiptRequested = new HashMap<>(MAX_MESSAGES);
private HashMap<Bmessage, PendingIntent> mDeliveryReceiptRequested =
@@ -189,6 +191,14 @@
mDisconnecting = new Disconnecting();
mConnected = new Connected();
+ MapClientContent.Callbacks callbacks = new MapClientContent.Callbacks(){
+ @Override
+ public void onMessageStatusChanged(String handle, int status) {
+ setMessageStatus(handle, status);
+ }
+ };
+ mDatabase = new MapClientContent(mService, callbacks, mDevice);
+
addState(mDisconnected);
addState(mConnecting);
addState(mDisconnecting);
@@ -272,16 +282,22 @@
for (Uri contact : contacts) {
// Who to send the message to.
- VCardEntry destEntry = new VCardEntry();
- VCardProperty destEntryPhone = new VCardProperty();
if (DBG) {
Log.d(TAG, "Scheme " + contact.getScheme());
}
if (PhoneAccount.SCHEME_TEL.equals(contact.getScheme())) {
- destEntryPhone.setName(VCardConstants.PROPERTY_TEL);
- destEntryPhone.addValues(contact.getSchemeSpecificPart());
- if (DBG) {
- Log.d(TAG, "Sending to phone numbers " + destEntryPhone.getValueList());
+ if (contact.getPath().contains(Telephony.Threads.CONTENT_URI.toString())) {
+ mDatabase.addThreadContactsToEntries(bmsg, contact.getLastPathSegment());
+ } else {
+ VCardEntry destEntry = new VCardEntry();
+ VCardProperty destEntryPhone = new VCardProperty();
+ destEntryPhone.setName(VCardConstants.PROPERTY_TEL);
+ destEntryPhone.addValues(contact.getSchemeSpecificPart());
+ destEntry.addProperty(destEntryPhone);
+ bmsg.addRecipient(destEntry);
+ if (DBG) {
+ Log.d(TAG, "Sending to phone numbers " + destEntryPhone.getValueList());
+ }
}
} else {
if (DBG) {
@@ -289,8 +305,6 @@
}
return false;
}
- destEntry.addProperty(destEntryPhone);
- bmsg.addRecipient(destEntry);
}
// Message of the body.
@@ -511,6 +525,8 @@
mMasClient.makeRequest(new RequestGetFolderListing(0, 0));
mMasClient.makeRequest(new RequestSetPath(false));
mMasClient.makeRequest(new RequestSetNotificationRegistration(true));
+ sendMessage(MSG_GET_MESSAGE_LISTING, FOLDER_SENT);
+ sendMessage(MSG_GET_MESSAGE_LISTING, FOLDER_INBOX);
}
@Override
@@ -551,7 +567,6 @@
// Get latest 50 Unread messages in the last week
MessagesFilter filter = new MessagesFilter();
filter.setMessageType(MapUtils.fetchMessageType());
- filter.setReadStatus(MessagesFilter.READ_STATUS_UNREAD);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, -7);
filter.setPeriod(calendar.getTime(), null);
@@ -607,6 +622,7 @@
@Override
public void exit() {
+ mDatabase.clearMessages();
mPreviousState = BluetoothProfile.STATE_CONNECTED;
}
@@ -636,7 +652,6 @@
+ ", Message handle = " + ev.getHandle());
}
switch (ev.getType()) {
-
case NEW_MESSAGE:
// Infer the timestamp for this message as 'now' and read status false
// instead of getting the message listing data for it
@@ -649,18 +664,23 @@
mMasClient.makeRequest(new RequestGetMessage(ev.getHandle(),
MasClient.CharsetType.UTF_8, false));
break;
-
case DELIVERY_SUCCESS:
case SENDING_SUCCESS:
notifySentMessageStatus(ev.getHandle(), ev.getType());
break;
+ case READ_STATUS_CHANGED:
+ mDatabase.markRead(ev.getHandle());
+ break;
+ case MESSAGE_DELETED:
+ mDatabase.deleteMessage(ev.getHandle());
+ break;
}
}
}
// Sets the specified message status to "read" (from "unread" status, mostly)
private void markMessageRead(RequestGetMessage request) {
- if (DBG) Log.d(TAG, "markMessageRead");
+ if (DBG) Log.d(TAG, "markMessageRead" + request.getHandle());
MessageMetadata metadata = mMessages.get(request.getHandle());
metadata.setRead(true);
mMasClient.makeRequest(new RequestSetMessageStatus(request.getHandle(),
@@ -754,6 +774,8 @@
if (message == null) {
return;
}
+ mDatabase.storeMessage(message, request.getHandle(),
+ mMessages.get(request.getHandle()).getTimestamp());
if (!INBOX_PATH.equalsIgnoreCase(message.getFolder())) {
if (DBG) {
Log.d(TAG, "Ignoring message received in " + message.getFolder() + ".");
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java b/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java
new file mode 100644
index 0000000..0229038
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.mapclient;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothMapClient;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.telephony.SubscriptionManager;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.vcard.VCardConstants;
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardProperty;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class MapClientContentTest {
+
+ private static final String TAG = "MapClientContentTest";
+ private static final int READ = 1;
+
+ private BluetoothAdapter mAdapter;
+ private BluetoothDevice mTestDevice;
+ private Context mTargetContext;
+
+ private Handler mHandler;
+ private Bmessage mTestMessage1;
+ private Bmessage mTestMessage2;
+ private Long mTestMessage1Timestamp = 1234L;
+ private String mTestMessage1Handle = "0001";
+ private String mTestMessage2Handle = "0002";
+
+
+ private VCardEntry mOriginator;
+
+ private ArgumentCaptor<Uri> mUriArgument = ArgumentCaptor.forClass(Uri.class);
+
+ private MapClientContent mMapClientContent;
+
+ @Mock
+ private AdapterService mAdapterService;
+ @Mock
+ private DatabaseManager mDatabaseManager;
+ @Mock
+ private MapClientService mMockMapClientService;
+ @Mock
+ private Context mMockContext;
+ @Mock
+ private MapClientContent.Callbacks mCallbacks;
+
+ private MockContentResolver mMockContentResolver;
+ private FakeContentProvider mMockSmsContentProvider;
+ private FakeContentProvider mMockMmsContentProvider;
+ private FakeContentProvider mMockThreadContentProvider;
+
+ @Mock
+ private SubscriptionManager mMockSubscriptionManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mTargetContext = InstrumentationRegistry.getTargetContext();
+
+ mMockSmsContentProvider = Mockito.spy(new FakeContentProvider(mTargetContext));
+
+ mMockMmsContentProvider = Mockito.spy(new FakeContentProvider(mTargetContext));
+ mMockThreadContentProvider = Mockito.spy(new FakeContentProvider(mTargetContext));
+
+
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
+ mMockContentResolver = Mockito.spy(new MockContentResolver());
+ mMockContentResolver.addProvider("sms", mMockSmsContentProvider);
+ mMockContentResolver.addProvider("mms", mMockMmsContentProvider);
+ mMockContentResolver.addProvider("mms-sms", mMockThreadContentProvider);
+
+ when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
+ when(mMockContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE))
+ .thenReturn(mMockSubscriptionManager);
+ createTestMessages();
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ /**
+ * Test that everything initializes correctly with an empty content provider
+ */
+ @Test
+ public void testCreateMapClientContent() {
+ mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+ verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+ eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+ Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size());
+ }
+
+ /**
+ * Test that a dirty database gets cleaned at startup.
+ */
+ @Test
+ public void testCleanDirtyDatabase() {
+ mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+ mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp);
+ verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+ eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+ Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size());
+ mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+ Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size());
+ }
+
+ /**
+ * Test inserting 2 SMS messages and then clearing out the database.
+ */
+ @Test
+ public void testStoreTwoSMS() {
+ mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+ mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp);
+ verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+ eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+ Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size());
+
+ mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp);
+ Assert.assertEquals(2, mMockSmsContentProvider.mContentValues.size());
+ Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size());
+
+ mMapClientContent.clearMessages();
+ Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size());
+ }
+
+ /**
+ * Test inserting 2 MMS messages and then clearing out the database.
+ */
+ @Test
+ public void testStoreTwoMMS() {
+ mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+ mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp);
+ verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+ eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+ Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size());
+
+ mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp);
+ Assert.assertEquals(2, mMockMmsContentProvider.mContentValues.size());
+
+ mMapClientContent.clearMessages();
+ Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size());
+ }
+
+ /**
+ * Test that SMS and MMS messages end up in their respective databases.
+ */
+ @Test
+ public void testStoreOneSMSOneMMS() {
+ mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+ mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp);
+ verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+ eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+ Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size());
+
+ mMapClientContent.storeMessage(mTestMessage2, mTestMessage2Handle, mTestMessage1Timestamp);
+ Assert.assertEquals(2, mMockMmsContentProvider.mContentValues.size());
+
+ mMapClientContent.clearMessages();
+ Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size());
+ }
+
+ /**
+ * Test read status changed
+ */
+ @Test
+ public void testReadStatusChanged() {
+ mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+ mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp);
+ verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+ eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+ Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size());
+
+ mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp);
+ Assert.assertEquals(2, mMockMmsContentProvider.mContentValues.size());
+
+ mMapClientContent.markRead(mTestMessage1Handle);
+
+ mMapClientContent.clearMessages();
+ Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size());
+ }
+
+ /**
+ * Test read status changed in local provider
+ *
+ * Insert a message, and notify the observer about a change
+ * The cursor is configured to return messages marked as read
+ * Verify that the local change is observed and propagated to the remote
+ */
+ @Test
+ public void testLocalReadStatusChanged() {
+ mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+ mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp);
+ Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size());
+ mMapClientContent.mContentObserver.onChange(false);
+ verify(mCallbacks).onMessageStatusChanged(eq(mTestMessage1Handle),
+ eq(BluetoothMapClient.READ));
+ }
+
+ /**
+ * Test remote message deleted
+ *
+ * Add a message to the database Simulate the message getting
+ * deleted on the phone Verify that the message is deleted locally
+ */
+ @Test
+ public void testMessageDeleted() {
+ mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+ mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp);
+ verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+ eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+ Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size());
+ // attempt to delete an invalid handle, nothing should be removed.
+ mMapClientContent.deleteMessage(mTestMessage2Handle);
+ Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size());
+
+ // delete a valid handle
+ mMapClientContent.deleteMessage(mTestMessage1Handle);
+ Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size());
+ }
+
+ /**
+ * Test read status changed in local provider
+ *
+ * Insert a message, manually remove it and notify the observer about a change
+ * Verify that the local change is observed and propagated to the remote
+ */
+ @Test
+ public void testLocalMessageDeleted() {
+ mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+ mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp);
+ verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+ eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+ Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size());
+ mMockSmsContentProvider.mContentValues.clear();
+ mMapClientContent.mContentObserver.onChange(false);
+ verify(mCallbacks).onMessageStatusChanged(eq(mTestMessage1Handle),
+ eq(BluetoothMapClient.DELETED));
+ }
+
+ /**
+ * Test parse own phone number Attempt to parse your phone number from a received SMS message
+ * and fail Receive an MMS message and successfully parse your phone number
+ */
+ @Test
+ public void testParseNumber() {
+ mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+ Assert.assertNull(mMapClientContent.mPhoneNumber);
+ mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp);
+ Assert.assertNull(mMapClientContent.mPhoneNumber);
+ mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp);
+ Assert.assertEquals("5551212", mMapClientContent.mPhoneNumber);
+ }
+
+ void createTestMessages() {
+ mOriginator = new VCardEntry();
+ VCardProperty property = new VCardProperty();
+ property.setName(VCardConstants.PROPERTY_TEL);
+ property.addValues("555-1212");
+ mOriginator.addProperty(property);
+ mTestMessage1 = new Bmessage();
+ mTestMessage1.setBodyContent("HelloWorld");
+ mTestMessage1.setType(Bmessage.Type.SMS_GSM);
+ mTestMessage1.setFolder("telecom/msg/inbox");
+ mTestMessage1.addOriginator(mOriginator);
+
+ mTestMessage2 = new Bmessage();
+ mTestMessage2.setBodyContent("HelloWorld");
+ mTestMessage2.setType(Bmessage.Type.MMS);
+ mTestMessage2.setFolder("telecom/msg/inbox");
+ mTestMessage2.addOriginator(mOriginator);
+ mTestMessage2.addRecipient(mOriginator);
+ }
+
+ public class FakeContentProvider extends MockContentProvider {
+
+ Map<Uri, ContentValues> mContentValues = new HashMap<>();
+ FakeContentProvider(Context context) {
+ super(context);
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ Log.i(TAG, "Delete " + uri);
+ Log.i(TAG, "Contents" + mContentValues.toString());
+ mContentValues.remove(uri);
+ if (uri.toString().equals("content://sms") || uri.toString().equals("content://mms")) {
+
+ mContentValues.clear();
+ }
+ return 1;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ Log.i(TAG, "URI = " + uri);
+ Uri returnUri = Uri.withAppendedPath(uri, String.valueOf(mContentValues.size() + 1));
+ //only store top level message parts
+ if (uri.toString().equals("content://sms/inbox") || uri.toString()
+ .equals("content://mms")) {
+ Log.i(TAG, "adding content" + values);
+ mContentValues.put(returnUri, values);
+ Log.i(TAG, "ContentSize = " + mContentValues.size());
+ }
+ return returnUri;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ Cursor cursor = Mockito.mock(Cursor.class);
+
+ when(cursor.moveToFirst()).thenReturn(true);
+ when(cursor.moveToNext()).thenReturn(true).thenReturn(false);
+
+ when(cursor.getLong(anyInt())).thenReturn((long) mContentValues.size());
+ when(cursor.getString(anyInt())).thenReturn(String.valueOf(mContentValues.size()));
+ when(cursor.getInt(anyInt())).thenReturn(READ);
+ return cursor;
+ }
+ }
+
+
+}
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java b/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
index 972ad47..b5da1a2 100644
--- a/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
@@ -25,22 +25,31 @@
import android.bluetooth.SdpMasRecord;
import android.content.Context;
import android.content.Intent;
+import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.telephony.SubscriptionManager;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -50,31 +59,56 @@
@MediumTest
@RunWith(AndroidJUnit4.class)
public class MapClientStateMachineTest {
+
private static final String TAG = "MapStateMachineTest";
private static final int ASYNC_CALL_TIMEOUT_MILLIS = 100;
-
+ @Rule
+ public final ServiceTestRule mServiceRule = new ServiceTestRule();
private BluetoothAdapter mAdapter;
private MceStateMachine mMceStateMachine = null;
private BluetoothDevice mTestDevice;
private Context mTargetContext;
-
private Handler mHandler;
-
private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
-
+ @Mock
+ private AdapterService mAdapterService;
+ @Mock
+ private DatabaseManager mDatabaseManager;
@Mock
private MapClientService mMockMapClientService;
-
+ private MockContentResolver mMockContentResolver;
+ private MockContentProvider mMockContentProvider;
@Mock
private MasClient mMockMasClient;
+ @Mock
+ private SubscriptionManager mMockSubscriptionManager;
+
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mTargetContext = InstrumentationRegistry.getTargetContext();
+ mMockContentProvider = new MockContentProvider(mTargetContext) {
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+ };
+ mMockContentResolver = new MockContentResolver();
+
Assume.assumeTrue("Ignore test when MapClientService is not enabled",
mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce));
+ TestUtils.setAdapterService(mAdapterService);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ TestUtils.startService(mServiceRule, MapClientService.class);
+ mMockContentResolver.addProvider("sms", mMockContentProvider);
+ mMockContentResolver.addProvider("mms", mMockContentProvider);
+ mMockContentResolver.addProvider("mms-sms", mMockContentProvider);
+
+ when(mMockMapClientService.getContentResolver()).thenReturn(mMockContentResolver);
+ when(mMockMapClientService.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE))
+ .thenReturn(mMockSubscriptionManager);
doReturn(mTargetContext.getResources()).when(mMockMapClientService).getResources();
@@ -93,13 +127,16 @@
}
@After
- public void tearDown() {
+ public void tearDown() throws Exception {
if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce)) {
return;
}
+
if (mMceStateMachine != null) {
mMceStateMachine.doQuit();
}
+ TestUtils.stopService(mServiceRule, MapClientService.class);
+ TestUtils.clearAdapterService(mAdapterService);
}
/**
@@ -112,8 +149,8 @@
}
/**
- * Test transition from
- * STATE_CONNECTING --> (receive MSG_MAS_DISCONNECTED) --> STATE_DISCONNECTED
+ * Test transition from STATE_CONNECTING --> (receive MSG_MAS_DISCONNECTED) -->
+ * STATE_DISCONNECTED
*/
@Test
public void testStateTransitionFromConnectingToDisconnected() {
@@ -151,9 +188,9 @@
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
}
- /**
- * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED
- * --> (receive MSG_MAS_DISCONNECTED) --> STATE_DISCONNECTED
+ /**
+ * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED -->
+ * (receive MSG_MAS_DISCONNECTED) --> STATE_DISCONNECTED
*/
@Test
public void testStateTransitionFromConnectedWithMasDisconnected() {
@@ -220,7 +257,8 @@
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
- Assert.assertTrue(mMceStateMachine.setMessageStatus("123456789AB", BluetoothMapClient.READ));
+ Assert.assertTrue(
+ mMceStateMachine.setMessageStatus("123456789AB", BluetoothMapClient.READ));
}
private void setupSdpRecordReceipt() {