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