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