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 android.Manifest.permission.BLUETOOTH_CONNECT; 20 import static org.mockito.Mockito.*; 21 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothMapClient; 25 import android.bluetooth.BluetoothProfile; 26 import android.bluetooth.SdpMasRecord; 27 import android.content.ContentValues; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.database.Cursor; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.Looper; 35 import android.os.Message; 36 import android.provider.Telephony.Sms; 37 import android.telephony.SubscriptionManager; 38 import android.test.mock.MockContentProvider; 39 import android.test.mock.MockContentResolver; 40 import android.util.Log; 41 42 import androidx.test.InstrumentationRegistry; 43 import androidx.test.filters.MediumTest; 44 import androidx.test.rule.ServiceTestRule; 45 import androidx.test.runner.AndroidJUnit4; 46 47 import com.android.bluetooth.R; 48 import com.android.bluetooth.TestUtils; 49 import com.android.bluetooth.btservice.AdapterService; 50 import com.android.bluetooth.btservice.storage.DatabaseManager; 51 import com.android.vcard.VCardConstants; 52 import com.android.vcard.VCardEntry; 53 import com.android.vcard.VCardProperty; 54 55 import org.junit.After; 56 import org.junit.Assert; 57 import org.junit.Assume; 58 import org.junit.Before; 59 import org.junit.Rule; 60 import org.junit.Test; 61 import org.junit.runner.RunWith; 62 import org.mockito.ArgumentCaptor; 63 import org.mockito.Mock; 64 import org.mockito.Mockito; 65 import org.mockito.MockitoAnnotations; 66 67 import java.util.HashMap; 68 import java.util.Map; 69 70 @MediumTest 71 @RunWith(AndroidJUnit4.class) 72 public class MapClientStateMachineTest { 73 74 private static final String TAG = "MapStateMachineTest"; 75 private static final String FOLDER_SENT = "sent"; 76 77 private static final int ASYNC_CALL_TIMEOUT_MILLIS = 100; 78 private static final int DISCONNECT_TIMEOUT = 3000; 79 @Rule 80 public final ServiceTestRule mServiceRule = new ServiceTestRule(); 81 private BluetoothAdapter mAdapter; 82 private MceStateMachine mMceStateMachine = null; 83 private BluetoothDevice mTestDevice; 84 private Context mTargetContext; 85 private Handler mHandler; 86 private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class); 87 @Mock 88 private AdapterService mAdapterService; 89 @Mock 90 private DatabaseManager mDatabaseManager; 91 @Mock 92 private MapClientService mMockMapClientService; 93 private MockContentResolver mMockContentResolver; 94 private MockSmsContentProvider mMockContentProvider; 95 @Mock 96 private MasClient mMockMasClient; 97 98 @Mock 99 private RequestPushMessage mMockRequestPushMessage; 100 101 @Mock 102 private SubscriptionManager mMockSubscriptionManager; 103 104 @Before setUp()105 public void setUp() throws Exception { 106 MockitoAnnotations.initMocks(this); 107 mTargetContext = InstrumentationRegistry.getTargetContext(); 108 mMockContentProvider = new MockSmsContentProvider(); 109 mMockContentResolver = new MockContentResolver(); 110 111 Assume.assumeTrue("Ignore test when MapClientService is not enabled", 112 mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce)); 113 TestUtils.setAdapterService(mAdapterService); 114 when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); 115 doReturn(true, false).when(mAdapterService).isStartedProfile(anyString()); 116 TestUtils.startService(mServiceRule, MapClientService.class); 117 mMockContentResolver.addProvider("sms", mMockContentProvider); 118 mMockContentResolver.addProvider("mms", mMockContentProvider); 119 mMockContentResolver.addProvider("mms-sms", mMockContentProvider); 120 121 when(mMockMapClientService.getContentResolver()).thenReturn(mMockContentResolver); 122 when(mMockMapClientService.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)) 123 .thenReturn(mMockSubscriptionManager); 124 125 doReturn(mTargetContext.getResources()).when(mMockMapClientService).getResources(); 126 127 // This line must be called to make sure relevant objects are initialized properly 128 mAdapter = BluetoothAdapter.getDefaultAdapter(); 129 // Get a device for testing 130 mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05"); 131 132 when(mMockMasClient.makeRequest(any(Request.class))).thenReturn(true); 133 mMceStateMachine = new MceStateMachine(mMockMapClientService, mTestDevice, mMockMasClient); 134 Assert.assertNotNull(mMceStateMachine); 135 if (Looper.myLooper() == null) { 136 Looper.prepare(); 137 } 138 mHandler = new Handler(); 139 } 140 141 @After tearDown()142 public void tearDown() throws Exception { 143 if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce)) { 144 return; 145 } 146 147 if (mMceStateMachine != null) { 148 mMceStateMachine.doQuit(); 149 } 150 TestUtils.stopService(mServiceRule, MapClientService.class); 151 TestUtils.clearAdapterService(mAdapterService); 152 } 153 154 /** 155 * Test that default state is STATE_CONNECTING 156 */ 157 @Test testDefaultConnectingState()158 public void testDefaultConnectingState() { 159 Log.i(TAG, "in testDefaultConnectingState"); 160 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState()); 161 } 162 163 /** 164 * Test transition from STATE_CONNECTING --> (receive MSG_MAS_DISCONNECTED) --> 165 * STATE_DISCONNECTED 166 */ 167 @Test testStateTransitionFromConnectingToDisconnected()168 public void testStateTransitionFromConnectingToDisconnected() { 169 Log.i(TAG, "in testStateTransitionFromConnectingToDisconnected"); 170 setupSdpRecordReceipt(); 171 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_DISCONNECTED); 172 mMceStateMachine.sendMessage(msg); 173 174 // Wait until the message is processed and a broadcast request is sent to 175 // to MapClientService to change 176 // state from STATE_CONNECTING to STATE_DISCONNECTED 177 verify(mMockMapClientService, 178 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast( 179 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), 180 any(Bundle.class)); 181 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState()); 182 } 183 184 /** 185 * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED 186 */ 187 @Test testStateTransitionFromConnectingToConnected()188 public void testStateTransitionFromConnectingToConnected() { 189 Log.i(TAG, "in testStateTransitionFromConnectingToConnected"); 190 191 setupSdpRecordReceipt(); 192 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 193 mMceStateMachine.sendMessage(msg); 194 195 // Wait until the message is processed and a broadcast request is sent to 196 // to MapClientService to change 197 // state from STATE_CONNECTING to STATE_CONNECTED 198 verify(mMockMapClientService, 199 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast( 200 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), 201 any(Bundle.class)); 202 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 203 } 204 205 /** 206 * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED --> 207 * (receive MSG_MAS_DISCONNECTED) --> STATE_DISCONNECTED 208 */ 209 @Test testStateTransitionFromConnectedWithMasDisconnected()210 public void testStateTransitionFromConnectedWithMasDisconnected() { 211 Log.i(TAG, "in testStateTransitionFromConnectedWithMasDisconnected"); 212 213 setupSdpRecordReceipt(); 214 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 215 mMceStateMachine.sendMessage(msg); 216 217 // Wait until the message is processed and a broadcast request is sent to 218 // to MapClientService to change 219 // state from STATE_CONNECTING to STATE_CONNECTED 220 verify(mMockMapClientService, 221 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast( 222 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), 223 any(Bundle.class)); 224 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 225 226 msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_DISCONNECTED); 227 mMceStateMachine.sendMessage(msg); 228 verify(mMockMapClientService, 229 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(4)).sendBroadcast( 230 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), 231 any(Bundle.class)); 232 233 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState()); 234 } 235 236 237 /** 238 * Test receiving an empty event report 239 */ 240 @Test testReceiveEmptyEvent()241 public void testReceiveEmptyEvent() { 242 setupSdpRecordReceipt(); 243 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 244 mMceStateMachine.sendMessage(msg); 245 246 // Wait until the message is processed and a broadcast request is sent to 247 // to MapClientService to change 248 // state from STATE_CONNECTING to STATE_CONNECTED 249 verify(mMockMapClientService, 250 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast( 251 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), 252 any(Bundle.class)); 253 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 254 255 // Send an empty notification event, verify the mMceStateMachine is still connected 256 Message notification = Message.obtain(mHandler, MceStateMachine.MSG_NOTIFICATION); 257 mMceStateMachine.getCurrentState().processMessage(msg); 258 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 259 } 260 261 /** 262 * Test set message status 263 */ 264 @Test testSetMessageStatus()265 public void testSetMessageStatus() { 266 setupSdpRecordReceipt(); 267 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 268 mMceStateMachine.sendMessage(msg); 269 270 // Wait until the message is processed and a broadcast request is sent to 271 // to MapClientService to change 272 // state from STATE_CONNECTING to STATE_CONNECTED 273 verify(mMockMapClientService, 274 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast( 275 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), 276 any(Bundle.class)); 277 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 278 Assert.assertTrue( 279 mMceStateMachine.setMessageStatus("123456789AB", BluetoothMapClient.READ)); 280 } 281 282 283 /** 284 * Test disconnect 285 */ 286 @Test testDisconnect()287 public void testDisconnect() { 288 setupSdpRecordReceipt(); 289 doAnswer(invocation -> { 290 mMceStateMachine.sendMessage(MceStateMachine.MSG_MAS_DISCONNECTED); 291 return null; 292 }).when(mMockMasClient).shutdown(); 293 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 294 mMceStateMachine.sendMessage(msg); 295 296 // Wait until the message is processed and a broadcast request is sent to 297 // to MapClientService to change 298 // state from STATE_CONNECTING to STATE_CONNECTED 299 verify(mMockMapClientService, 300 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast( 301 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), 302 any(Bundle.class)); 303 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 304 305 mMceStateMachine.disconnect(); 306 verify(mMockMapClientService, 307 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(4)).sendBroadcast( 308 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), 309 any(Bundle.class)); 310 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState()); 311 } 312 313 /** 314 * Test disconnect timeout 315 */ 316 @Test testDisconnectTimeout()317 public void testDisconnectTimeout() { 318 setupSdpRecordReceipt(); 319 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 320 mMceStateMachine.sendMessage(msg); 321 322 // Wait until the message is processed and a broadcast request is sent to 323 // to MapClientService to change 324 // state from STATE_CONNECTING to STATE_CONNECTED 325 verify(mMockMapClientService, 326 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast( 327 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), 328 any(Bundle.class)); 329 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 330 331 mMceStateMachine.disconnect(); 332 verify(mMockMapClientService, 333 after(DISCONNECT_TIMEOUT / 2).times(3)).sendBroadcast( 334 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), 335 any(Bundle.class)); 336 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING, mMceStateMachine.getState()); 337 338 verify(mMockMapClientService, 339 timeout(DISCONNECT_TIMEOUT).times(4)).sendBroadcast( 340 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), 341 any(Bundle.class)); 342 Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState()); 343 } 344 345 /** 346 * Test sending a message 347 */ 348 @Test testSendSMSMessage()349 public void testSendSMSMessage() { 350 setupSdpRecordReceipt(); 351 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 352 mMceStateMachine.sendMessage(msg); 353 TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper()); 354 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 355 356 String testMessage = "Hello World!"; 357 Uri[] contacts = new Uri[] {Uri.parse("tel://5551212")}; 358 359 verify(mMockMasClient, times(0)).makeRequest(any(RequestPushMessage.class)); 360 mMceStateMachine.sendMapMessage(contacts, testMessage, null, null); 361 verify(mMockMasClient, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) 362 .makeRequest(any(RequestPushMessage.class)); 363 } 364 365 /** 366 * Test message sent successfully 367 */ 368 @Test testSMSMessageSent()369 public void testSMSMessageSent() { 370 setupSdpRecordReceipt(); 371 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 372 mMceStateMachine.sendMessage(msg); 373 TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper()); 374 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 375 376 String testMessage = "Hello World!"; 377 VCardEntry recipient = new VCardEntry(); 378 VCardProperty property = new VCardProperty(); 379 property.setName(VCardConstants.PROPERTY_TEL); 380 property.addValues("555-1212"); 381 recipient.addProperty(property); 382 Bmessage testBmessage = new Bmessage(); 383 testBmessage.setType(Bmessage.Type.SMS_GSM); 384 testBmessage.setBodyContent(testMessage); 385 testBmessage.addRecipient(recipient); 386 RequestPushMessage testRequest = 387 new RequestPushMessage(FOLDER_SENT, testBmessage, null, false, false); 388 when(mMockRequestPushMessage.getMsgHandle()).thenReturn("12345"); 389 when(mMockRequestPushMessage.getBMsg()).thenReturn(testBmessage); 390 Message msgSent = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED, 391 mMockRequestPushMessage); 392 393 mMceStateMachine.sendMessage(msgSent); 394 TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper()); 395 Assert.assertEquals(1, mMockContentProvider.mInsertOperationCount); 396 } 397 setupSdpRecordReceipt()398 private void setupSdpRecordReceipt() { 399 // Perform first part of MAP connection logic. 400 verify(mMockMapClientService, 401 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendBroadcast( 402 mIntentArgument.capture(), eq(BLUETOOTH_CONNECT), 403 any(Bundle.class)); 404 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState()); 405 406 // Setup receipt of SDP record 407 SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "MasRecord"); 408 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_SDP_DONE, record); 409 mMceStateMachine.sendMessage(msg); 410 } 411 412 private class MockSmsContentProvider extends MockContentProvider { 413 Map<Uri, ContentValues> mContentValues = new HashMap<>(); 414 int mInsertOperationCount = 0; 415 416 @Override delete(Uri uri, String selection, String[] selectionArgs)417 public int delete(Uri uri, String selection, String[] selectionArgs) { 418 return 0; 419 } 420 421 @Override insert(Uri uri, ContentValues values)422 public Uri insert(Uri uri, ContentValues values) { 423 mInsertOperationCount++; 424 return Uri.withAppendedPath(Sms.CONTENT_URI, String.valueOf(mInsertOperationCount)); 425 } 426 427 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)428 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 429 String sortOrder) { 430 Cursor cursor = Mockito.mock(Cursor.class); 431 432 when(cursor.moveToFirst()).thenReturn(true); 433 when(cursor.moveToNext()).thenReturn(true).thenReturn(false); 434 435 when(cursor.getLong(anyInt())).thenReturn((long) mContentValues.size()); 436 when(cursor.getString(anyInt())).thenReturn(String.valueOf(mContentValues.size())); 437 return cursor; 438 } 439 } 440 441 } 442