1 /* 2 * Copyright 2018 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.btservice; 18 19 import static org.mockito.ArgumentMatchers.eq; 20 import static org.mockito.Mockito.*; 21 22 import android.bluetooth.BluetoothAdapter; 23 import android.content.Intent; 24 import android.os.Looper; 25 26 import androidx.test.InstrumentationRegistry; 27 import androidx.test.filters.MediumTest; 28 import androidx.test.rule.ServiceTestRule; 29 import androidx.test.runner.AndroidJUnit4; 30 31 import com.android.bluetooth.TestUtils; 32 import com.android.bluetooth.btservice.storage.DatabaseManager; 33 34 import org.junit.After; 35 import org.junit.Assert; 36 import org.junit.Before; 37 import org.junit.Rule; 38 import org.junit.Test; 39 import org.junit.runner.RunWith; 40 import org.mockito.ArgumentCaptor; 41 import org.mockito.Mock; 42 import org.mockito.MockitoAnnotations; 43 import org.mockito.invocation.InvocationOnMock; 44 import org.mockito.stubbing.Answer; 45 46 import java.lang.reflect.InvocationTargetException; 47 import java.util.List; 48 import java.util.concurrent.ConcurrentHashMap; 49 import java.util.concurrent.TimeoutException; 50 51 @MediumTest 52 @RunWith(AndroidJUnit4.class) 53 public class ProfileServiceTest { 54 private static final int PROFILE_START_MILLIS = 1250; 55 private static final int NUM_REPEATS = 5; 56 57 @Rule public final ServiceTestRule mServiceTestRule = new ServiceTestRule(); 58 @Mock private AdapterService mMockAdapterService; 59 @Mock private DatabaseManager mDatabaseManager; 60 61 private Class[] mProfiles; 62 ConcurrentHashMap<String, Boolean> mStartedProfileMap = new ConcurrentHashMap(); 63 setProfileState(Class profile, int state)64 private void setProfileState(Class profile, int state) throws TimeoutException { 65 if (state == BluetoothAdapter.STATE_ON) { 66 mStartedProfileMap.put(profile.getSimpleName(), true); 67 } else if (state == BluetoothAdapter.STATE_OFF) { 68 mStartedProfileMap.put(profile.getSimpleName(), false); 69 } 70 Intent startIntent = new Intent(InstrumentationRegistry.getTargetContext(), profile); 71 startIntent.putExtra(AdapterService.EXTRA_ACTION, 72 AdapterService.ACTION_SERVICE_STATE_CHANGED); 73 startIntent.putExtra(BluetoothAdapter.EXTRA_STATE, state); 74 mServiceTestRule.startService(startIntent); 75 } 76 setAllProfilesState(int state, int invocationNumber)77 private void setAllProfilesState(int state, int invocationNumber) throws TimeoutException { 78 for (Class profile : mProfiles) { 79 setProfileState(profile, state); 80 } 81 ArgumentCaptor<ProfileService> argument = ArgumentCaptor.forClass(ProfileService.class); 82 verify(mMockAdapterService, timeout(PROFILE_START_MILLIS).times( 83 mProfiles.length * invocationNumber)).onProfileServiceStateChanged( 84 argument.capture(), eq(state)); 85 List<ProfileService> argumentProfiles = argument.getAllValues(); 86 for (Class profile : mProfiles) { 87 int matches = 0; 88 for (ProfileService arg : argumentProfiles) { 89 if (arg.getClass().getName().equals(profile.getName())) { 90 matches += 1; 91 } 92 } 93 Assert.assertEquals(invocationNumber, matches); 94 } 95 } 96 97 @Before setUp()98 public void setUp() 99 throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 100 if (Looper.myLooper() == null) { 101 Looper.prepare(); 102 } 103 Assert.assertNotNull(Looper.myLooper()); 104 105 MockitoAnnotations.initMocks(this); 106 when(mMockAdapterService.isStartedProfile(anyString())).thenAnswer(new Answer<Boolean>() { 107 @Override 108 public Boolean answer(InvocationOnMock invocation) throws Throwable { 109 Object[] args = invocation.getArguments(); 110 return mStartedProfileMap.get((String) args[0]); 111 } 112 }); 113 114 mProfiles = Config.getSupportedProfiles(); 115 116 mMockAdapterService.initNative(false /* is_restricted */, 117 false /* is_common_criteria_mode */, 0 /* config_compare_result */, 118 new String[0], false); 119 120 TestUtils.setAdapterService(mMockAdapterService); 121 doReturn(mDatabaseManager).when(mMockAdapterService).getDatabase(); 122 123 Assert.assertNotNull(AdapterService.getAdapterService()); 124 } 125 126 @After tearDown()127 public void tearDown() 128 throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 129 mMockAdapterService.cleanupNative(); 130 TestUtils.clearAdapterService(mMockAdapterService); 131 mMockAdapterService = null; 132 mProfiles = null; 133 } 134 135 /** 136 * Test: Start the Bluetooth services that are configured. 137 * Verify that the same services start. 138 */ 139 @Test testEnableDisable()140 public void testEnableDisable() throws TimeoutException { 141 setAllProfilesState(BluetoothAdapter.STATE_ON, 1); 142 setAllProfilesState(BluetoothAdapter.STATE_OFF, 1); 143 } 144 145 /** 146 * Test: Start the Bluetooth services that are configured twice. 147 * Verify that the services start. 148 */ 149 @Test testEnableDisableTwice()150 public void testEnableDisableTwice() throws TimeoutException { 151 setAllProfilesState(BluetoothAdapter.STATE_ON, 1); 152 setAllProfilesState(BluetoothAdapter.STATE_OFF, 1); 153 setAllProfilesState(BluetoothAdapter.STATE_ON, 2); 154 setAllProfilesState(BluetoothAdapter.STATE_OFF, 2); 155 } 156 157 /** 158 * Test: Start the Bluetooth services that are configured. 159 * Verify that each profile starts and stops. 160 */ 161 @Test testEnableDisableInterleaved()162 public void testEnableDisableInterleaved() throws TimeoutException { 163 for (Class profile : mProfiles) { 164 setProfileState(profile, BluetoothAdapter.STATE_ON); 165 setProfileState(profile, BluetoothAdapter.STATE_OFF); 166 } 167 ArgumentCaptor<ProfileService> starts = ArgumentCaptor.forClass(ProfileService.class); 168 ArgumentCaptor<ProfileService> stops = ArgumentCaptor.forClass(ProfileService.class); 169 int invocationNumber = mProfiles.length; 170 verify(mMockAdapterService, 171 timeout(PROFILE_START_MILLIS).times(invocationNumber)).onProfileServiceStateChanged( 172 starts.capture(), eq(BluetoothAdapter.STATE_ON)); 173 verify(mMockAdapterService, 174 timeout(PROFILE_START_MILLIS).times(invocationNumber)).onProfileServiceStateChanged( 175 stops.capture(), eq(BluetoothAdapter.STATE_OFF)); 176 177 List<ProfileService> startedArguments = starts.getAllValues(); 178 List<ProfileService> stoppedArguments = stops.getAllValues(); 179 Assert.assertEquals(startedArguments.size(), stoppedArguments.size()); 180 for (ProfileService service : startedArguments) { 181 Assert.assertTrue(stoppedArguments.contains(service)); 182 stoppedArguments.remove(service); 183 Assert.assertFalse(stoppedArguments.contains(service)); 184 } 185 } 186 187 /** 188 * Test: Start and stop a single profile repeatedly. 189 * Verify that the profiles start and stop. 190 */ 191 @Test testRepeatedEnableDisableSingly()192 public void testRepeatedEnableDisableSingly() throws TimeoutException { 193 int profileNumber = 0; 194 for (Class profile : mProfiles) { 195 for (int i = 0; i < NUM_REPEATS; i++) { 196 setProfileState(profile, BluetoothAdapter.STATE_ON); 197 ArgumentCaptor<ProfileService> start = 198 ArgumentCaptor.forClass(ProfileService.class); 199 verify(mMockAdapterService, timeout(PROFILE_START_MILLIS).times( 200 NUM_REPEATS * profileNumber + i + 1)).onProfileServiceStateChanged( 201 start.capture(), eq(BluetoothAdapter.STATE_ON)); 202 setProfileState(profile, BluetoothAdapter.STATE_OFF); 203 ArgumentCaptor<ProfileService> stop = ArgumentCaptor.forClass(ProfileService.class); 204 verify(mMockAdapterService, timeout(PROFILE_START_MILLIS).times( 205 NUM_REPEATS * profileNumber + i + 1)).onProfileServiceStateChanged( 206 stop.capture(), eq(BluetoothAdapter.STATE_OFF)); 207 Assert.assertEquals(start.getValue(), stop.getValue()); 208 } 209 profileNumber += 1; 210 } 211 } 212 213 /** 214 * Test: Start and stop a single profile repeatedly and verify that the profile services are 215 * registered and unregistered accordingly. 216 */ 217 @Test testProfileServiceRegisterUnregister()218 public void testProfileServiceRegisterUnregister() throws TimeoutException { 219 int profileNumber = 0; 220 for (Class profile : mProfiles) { 221 for (int i = 0; i < NUM_REPEATS; i++) { 222 setProfileState(profile, BluetoothAdapter.STATE_ON); 223 ArgumentCaptor<ProfileService> start = 224 ArgumentCaptor.forClass(ProfileService.class); 225 verify(mMockAdapterService, timeout(PROFILE_START_MILLIS).times( 226 NUM_REPEATS * profileNumber + i + 1)).addProfile( 227 start.capture()); 228 setProfileState(profile, BluetoothAdapter.STATE_OFF); 229 ArgumentCaptor<ProfileService> stop = ArgumentCaptor.forClass(ProfileService.class); 230 verify(mMockAdapterService, timeout(PROFILE_START_MILLIS).times( 231 NUM_REPEATS * profileNumber + i + 1)).removeProfile( 232 stop.capture()); 233 Assert.assertEquals(start.getValue(), stop.getValue()); 234 } 235 profileNumber += 1; 236 } 237 } 238 } 239