blob: 8b3ecede08507a25c25c2736611fa647281ad03e [file] [log] [blame]
/*
* 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.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
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.Bundle;
import android.os.Handler;
import android.provider.Telephony.Mms;
import android.provider.Telephony.Sms;
import android.telephony.SubscriptionInfo;
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.Arrays;
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;
@Mock
private SubscriptionInfo mMockSubscription;
@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);
when(mMockContext.getSystemServiceName(SubscriptionManager.class))
.thenReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
when(mMockSubscriptionManager.getActiveSubscriptionInfoList())
.thenReturn(Arrays.asList(mMockSubscription));
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.cleanUp();
Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size());
Assert.assertEquals(0, mMockThreadContentProvider.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.cleanUp();
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.cleanUp();
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.cleanUp();
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);
}
/**
* Test to validate that some poorly formatted messages don't crash.
*/
@Test
public void testStoreBadMessage() {
mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
mTestMessage1 = new Bmessage();
mTestMessage1.setBodyContent("HelloWorld");
mTestMessage1.setType(Bmessage.Type.SMS_GSM);
mTestMessage1.setFolder("telecom/msg/sent");
mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp);
mTestMessage2 = new Bmessage();
mTestMessage2.setBodyContent("HelloWorld");
mTestMessage2.setType(Bmessage.Type.MMS);
mTestMessage2.setFolder("telecom/msg/inbox");
mMapClientContent.storeMessage(mTestMessage2, mTestMessage2Handle, mTestMessage1Timestamp);
}
/**
* Test to validate that an exception in the Subscription manager won't crash Bluetooth during
* disconnect.
*/
@Test
public void testCleanUpRemoteException() {
mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
doThrow(java.lang.NullPointerException.class).when(mMockSubscriptionManager)
.removeSubscriptionInfoRecord(any(), anyInt());
mMapClientContent.cleanUp();
}
/**
* Test to validate old subscriptions are removed at startup.
*/
@Test
public void testCleanUpAtStartup() {
MapClientContent.clearAllContent(mMockContext);
verify(mMockSubscriptionManager, never()).removeSubscriptionInfoRecord(any(), anyInt());
when(mMockSubscription.getSubscriptionType())
.thenReturn(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
MapClientContent.clearAllContent(mMockContext);
verify(mMockSubscriptionManager).removeSubscriptionInfoRecord(any(),
eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
}
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.equals(Sms.CONTENT_URI) || uri.equals(Mms.CONTENT_URI)) {
mContentValues.clear();
}
return 1;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
Log.i(TAG, "URI = " + uri);
if (uri.equals(Mms.Inbox.CONTENT_URI)) uri = Mms.CONTENT_URI;
Uri returnUri = Uri.withAppendedPath(uri, String.valueOf(mContentValues.size() + 1));
//only store top level message parts
if (uri.equals(Sms.Inbox.CONTENT_URI) || uri.equals(Mms.CONTENT_URI)) {
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;
}
@Override
public int update(Uri uri, ContentValues values, Bundle extras) {
return 0;
}
}
}