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