1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bluetooth.mapclient; 18 19 import static org.mockito.Mockito.any; 20 import static org.mockito.Mockito.anyInt; 21 import static org.mockito.Mockito.doThrow; 22 import static org.mockito.Mockito.eq; 23 import static org.mockito.Mockito.never; 24 import static org.mockito.Mockito.verify; 25 import static org.mockito.Mockito.when; 26 27 import android.bluetooth.BluetoothAdapter; 28 import android.bluetooth.BluetoothDevice; 29 import android.bluetooth.BluetoothMapClient; 30 import android.content.ContentValues; 31 import android.content.Context; 32 import android.database.Cursor; 33 import android.net.Uri; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.provider.Telephony.Mms; 37 import android.provider.Telephony.Sms; 38 import android.telephony.SubscriptionInfo; 39 import android.telephony.SubscriptionManager; 40 import android.test.mock.MockContentProvider; 41 import android.test.mock.MockContentResolver; 42 import android.util.Log; 43 44 import androidx.test.InstrumentationRegistry; 45 import androidx.test.filters.MediumTest; 46 import androidx.test.runner.AndroidJUnit4; 47 48 import com.android.bluetooth.btservice.AdapterService; 49 import com.android.bluetooth.btservice.storage.DatabaseManager; 50 import com.android.vcard.VCardConstants; 51 import com.android.vcard.VCardEntry; 52 import com.android.vcard.VCardProperty; 53 54 import org.junit.After; 55 import org.junit.Assert; 56 import org.junit.Before; 57 import org.junit.Test; 58 import org.junit.runner.RunWith; 59 import org.mockito.ArgumentCaptor; 60 import org.mockito.Mock; 61 import org.mockito.Mockito; 62 import org.mockito.MockitoAnnotations; 63 64 import java.util.Arrays; 65 import java.util.HashMap; 66 import java.util.Map; 67 68 @MediumTest 69 @RunWith(AndroidJUnit4.class) 70 public class MapClientContentTest { 71 72 private static final String TAG = "MapClientContentTest"; 73 private static final int READ = 1; 74 75 private BluetoothAdapter mAdapter; 76 private BluetoothDevice mTestDevice; 77 private Context mTargetContext; 78 79 private Handler mHandler; 80 private Bmessage mTestMessage1; 81 private Bmessage mTestMessage2; 82 private Long mTestMessage1Timestamp = 1234L; 83 private String mTestMessage1Handle = "0001"; 84 private String mTestMessage2Handle = "0002"; 85 86 87 private VCardEntry mOriginator; 88 89 private ArgumentCaptor<Uri> mUriArgument = ArgumentCaptor.forClass(Uri.class); 90 91 private MapClientContent mMapClientContent; 92 93 @Mock 94 private AdapterService mAdapterService; 95 @Mock 96 private DatabaseManager mDatabaseManager; 97 @Mock 98 private MapClientService mMockMapClientService; 99 @Mock 100 private Context mMockContext; 101 @Mock 102 private MapClientContent.Callbacks mCallbacks; 103 104 private MockContentResolver mMockContentResolver; 105 private FakeContentProvider mMockSmsContentProvider; 106 private FakeContentProvider mMockMmsContentProvider; 107 private FakeContentProvider mMockThreadContentProvider; 108 109 @Mock 110 private SubscriptionManager mMockSubscriptionManager; 111 @Mock 112 private SubscriptionInfo mMockSubscription; 113 114 @Before setUp()115 public void setUp() throws Exception { 116 MockitoAnnotations.initMocks(this); 117 mTargetContext = InstrumentationRegistry.getTargetContext(); 118 119 mMockSmsContentProvider = Mockito.spy(new FakeContentProvider(mTargetContext)); 120 121 mMockMmsContentProvider = Mockito.spy(new FakeContentProvider(mTargetContext)); 122 mMockThreadContentProvider = Mockito.spy(new FakeContentProvider(mTargetContext)); 123 124 125 mAdapter = BluetoothAdapter.getDefaultAdapter(); 126 mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05"); 127 mMockContentResolver = Mockito.spy(new MockContentResolver()); 128 mMockContentResolver.addProvider("sms", mMockSmsContentProvider); 129 mMockContentResolver.addProvider("mms", mMockMmsContentProvider); 130 mMockContentResolver.addProvider("mms-sms", mMockThreadContentProvider); 131 132 when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver); 133 when(mMockContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)) 134 .thenReturn(mMockSubscriptionManager); 135 136 when(mMockSubscriptionManager.getActiveSubscriptionInfoList()) 137 .thenReturn(Arrays.asList(mMockSubscription)); 138 createTestMessages(); 139 140 } 141 142 @After tearDown()143 public void tearDown() throws Exception { 144 } 145 146 /** 147 * Test that everything initializes correctly with an empty content provider 148 */ 149 @Test testCreateMapClientContent()150 public void testCreateMapClientContent() { 151 mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); 152 verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(), 153 eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); 154 Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size()); 155 } 156 157 /** 158 * Test that a dirty database gets cleaned at startup. 159 */ 160 @Test testCleanDirtyDatabase()161 public void testCleanDirtyDatabase() { 162 mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); 163 mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp); 164 verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(), 165 eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); 166 Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size()); 167 mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); 168 Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size()); 169 } 170 171 /** 172 * Test inserting 2 SMS messages and then clearing out the database. 173 */ 174 @Test testStoreTwoSMS()175 public void testStoreTwoSMS() { 176 mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); 177 mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp); 178 verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(), 179 eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); 180 Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size()); 181 182 mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp); 183 Assert.assertEquals(2, mMockSmsContentProvider.mContentValues.size()); 184 Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size()); 185 186 mMapClientContent.cleanUp(); 187 Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size()); 188 Assert.assertEquals(0, mMockThreadContentProvider.mContentValues.size()); 189 } 190 191 /** 192 * Test inserting 2 MMS messages and then clearing out the database. 193 */ 194 @Test testStoreTwoMMS()195 public void testStoreTwoMMS() { 196 mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); 197 mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp); 198 verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(), 199 eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); 200 Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size()); 201 202 mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp); 203 Assert.assertEquals(2, mMockMmsContentProvider.mContentValues.size()); 204 205 mMapClientContent.cleanUp(); 206 Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size()); 207 } 208 209 /** 210 * Test that SMS and MMS messages end up in their respective databases. 211 */ 212 @Test testStoreOneSMSOneMMS()213 public void testStoreOneSMSOneMMS() { 214 mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); 215 mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp); 216 verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(), 217 eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); 218 Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size()); 219 220 mMapClientContent.storeMessage(mTestMessage2, mTestMessage2Handle, mTestMessage1Timestamp); 221 Assert.assertEquals(2, mMockMmsContentProvider.mContentValues.size()); 222 223 mMapClientContent.cleanUp(); 224 Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size()); 225 } 226 227 /** 228 * Test read status changed 229 */ 230 @Test testReadStatusChanged()231 public void testReadStatusChanged() { 232 mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); 233 mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp); 234 verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(), 235 eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); 236 Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size()); 237 238 mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp); 239 Assert.assertEquals(2, mMockMmsContentProvider.mContentValues.size()); 240 241 mMapClientContent.markRead(mTestMessage1Handle); 242 243 mMapClientContent.cleanUp(); 244 Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size()); 245 } 246 247 /** 248 * Test read status changed in local provider 249 * 250 * Insert a message, and notify the observer about a change 251 * The cursor is configured to return messages marked as read 252 * Verify that the local change is observed and propagated to the remote 253 */ 254 @Test testLocalReadStatusChanged()255 public void testLocalReadStatusChanged() { 256 mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); 257 mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp); 258 Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size()); 259 mMapClientContent.mContentObserver.onChange(false); 260 verify(mCallbacks).onMessageStatusChanged(eq(mTestMessage1Handle), 261 eq(BluetoothMapClient.READ)); 262 } 263 264 /** 265 * Test remote message deleted 266 * 267 * Add a message to the database Simulate the message getting 268 * deleted on the phone Verify that the message is deleted locally 269 */ 270 @Test testMessageDeleted()271 public void testMessageDeleted() { 272 mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); 273 mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp); 274 verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(), 275 eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); 276 Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size()); 277 // attempt to delete an invalid handle, nothing should be removed. 278 mMapClientContent.deleteMessage(mTestMessage2Handle); 279 Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size()); 280 281 // delete a valid handle 282 mMapClientContent.deleteMessage(mTestMessage1Handle); 283 Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size()); 284 } 285 286 /** 287 * Test read status changed in local provider 288 * 289 * Insert a message, manually remove it and notify the observer about a change 290 * Verify that the local change is observed and propagated to the remote 291 */ 292 @Test testLocalMessageDeleted()293 public void testLocalMessageDeleted() { 294 mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); 295 mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp); 296 verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(), 297 eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); 298 Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size()); 299 mMockSmsContentProvider.mContentValues.clear(); 300 mMapClientContent.mContentObserver.onChange(false); 301 verify(mCallbacks).onMessageStatusChanged(eq(mTestMessage1Handle), 302 eq(BluetoothMapClient.DELETED)); 303 } 304 305 /** 306 * Test parse own phone number Attempt to parse your phone number from a received SMS message 307 * and fail Receive an MMS message and successfully parse your phone number 308 */ 309 @Test testParseNumber()310 public void testParseNumber() { 311 mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); 312 Assert.assertNull(mMapClientContent.mPhoneNumber); 313 mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp); 314 Assert.assertNull(mMapClientContent.mPhoneNumber); 315 mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp); 316 Assert.assertEquals("5551212", mMapClientContent.mPhoneNumber); 317 } 318 319 /** 320 * Test to validate that some poorly formatted messages don't crash. 321 */ 322 @Test testStoreBadMessage()323 public void testStoreBadMessage() { 324 mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); 325 mTestMessage1 = new Bmessage(); 326 mTestMessage1.setBodyContent("HelloWorld"); 327 mTestMessage1.setType(Bmessage.Type.SMS_GSM); 328 mTestMessage1.setFolder("telecom/msg/sent"); 329 mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp); 330 331 mTestMessage2 = new Bmessage(); 332 mTestMessage2.setBodyContent("HelloWorld"); 333 mTestMessage2.setType(Bmessage.Type.MMS); 334 mTestMessage2.setFolder("telecom/msg/inbox"); 335 mMapClientContent.storeMessage(mTestMessage2, mTestMessage2Handle, mTestMessage1Timestamp); 336 } 337 338 /** 339 * Test to validate that an exception in the Subscription manager won't crash Bluetooth during 340 * disconnect. 341 */ 342 @Test testCleanUpRemoteException()343 public void testCleanUpRemoteException() { 344 mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice); 345 doThrow(java.lang.NullPointerException.class).when(mMockSubscriptionManager) 346 .removeSubscriptionInfoRecord(any(), anyInt()); 347 mMapClientContent.cleanUp(); 348 } 349 350 /** 351 * Test to validate old subscriptions are removed at startup. 352 */ 353 @Test testCleanUpAtStartup()354 public void testCleanUpAtStartup() { 355 MapClientContent.clearAllContent(mMockContext); 356 verify(mMockSubscriptionManager, never()).removeSubscriptionInfoRecord(any(), anyInt()); 357 358 when(mMockSubscription.getSubscriptionType()) 359 .thenReturn(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM); 360 MapClientContent.clearAllContent(mMockContext); 361 verify(mMockSubscriptionManager).removeSubscriptionInfoRecord(any(), 362 eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)); 363 } 364 createTestMessages()365 void createTestMessages() { 366 mOriginator = new VCardEntry(); 367 VCardProperty property = new VCardProperty(); 368 property.setName(VCardConstants.PROPERTY_TEL); 369 property.addValues("555-1212"); 370 mOriginator.addProperty(property); 371 mTestMessage1 = new Bmessage(); 372 mTestMessage1.setBodyContent("HelloWorld"); 373 mTestMessage1.setType(Bmessage.Type.SMS_GSM); 374 mTestMessage1.setFolder("telecom/msg/inbox"); 375 mTestMessage1.addOriginator(mOriginator); 376 377 mTestMessage2 = new Bmessage(); 378 mTestMessage2.setBodyContent("HelloWorld"); 379 mTestMessage2.setType(Bmessage.Type.MMS); 380 mTestMessage2.setFolder("telecom/msg/inbox"); 381 mTestMessage2.addOriginator(mOriginator); 382 mTestMessage2.addRecipient(mOriginator); 383 } 384 385 public class FakeContentProvider extends MockContentProvider { 386 387 Map<Uri, ContentValues> mContentValues = new HashMap<>(); FakeContentProvider(Context context)388 FakeContentProvider(Context context) { 389 super(context); 390 } 391 392 @Override delete(Uri uri, String selection, String[] selectionArgs)393 public int delete(Uri uri, String selection, String[] selectionArgs) { 394 Log.i(TAG, "Delete " + uri); 395 Log.i(TAG, "Contents" + mContentValues.toString()); 396 mContentValues.remove(uri); 397 if (uri.equals(Sms.CONTENT_URI) || uri.equals(Mms.CONTENT_URI)) { 398 mContentValues.clear(); 399 } 400 return 1; 401 } 402 403 @Override insert(Uri uri, ContentValues values)404 public Uri insert(Uri uri, ContentValues values) { 405 Log.i(TAG, "URI = " + uri); 406 if (uri.equals(Mms.Inbox.CONTENT_URI)) uri = Mms.CONTENT_URI; 407 Uri returnUri = Uri.withAppendedPath(uri, String.valueOf(mContentValues.size() + 1)); 408 //only store top level message parts 409 if (uri.equals(Sms.Inbox.CONTENT_URI) || uri.equals(Mms.CONTENT_URI)) { 410 Log.i(TAG, "adding content" + values); 411 mContentValues.put(returnUri, values); 412 Log.i(TAG, "ContentSize = " + mContentValues.size()); 413 } 414 return returnUri; 415 } 416 417 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)418 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 419 String sortOrder) { 420 Cursor cursor = Mockito.mock(Cursor.class); 421 422 when(cursor.moveToFirst()).thenReturn(true); 423 when(cursor.moveToNext()).thenReturn(true).thenReturn(false); 424 425 when(cursor.getLong(anyInt())).thenReturn((long) mContentValues.size()); 426 when(cursor.getString(anyInt())).thenReturn(String.valueOf(mContentValues.size())); 427 when(cursor.getInt(anyInt())).thenReturn(READ); 428 return cursor; 429 } 430 431 @Override update(Uri uri, ContentValues values, Bundle extras)432 public int update(Uri uri, ContentValues values, Bundle extras) { 433 return 0; 434 } 435 } 436 } 437