1 /*
2  * Copyright 2023 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.server.bluetooth;
18 
19 import static android.Manifest.permission.BLUETOOTH_CONNECT;
20 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
21 import static android.Manifest.permission.LOCAL_MAC_ADDRESS;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 
25 import static org.junit.Assert.assertThrows;
26 import static org.mockito.ArgumentMatchers.anyBoolean;
27 import static org.mockito.ArgumentMatchers.eq;
28 import static org.mockito.Mockito.any;
29 import static org.mockito.Mockito.anyInt;
30 import static org.mockito.Mockito.clearInvocations;
31 import static org.mockito.Mockito.doReturn;
32 import static org.mockito.Mockito.eq;
33 import static org.mockito.Mockito.lenient;
34 import static org.mockito.Mockito.mock;
35 import static org.mockito.Mockito.mockingDetails;
36 import static org.mockito.Mockito.spy;
37 import static org.mockito.Mockito.verify;
38 import static org.mockito.Mockito.verifyNoMoreInteractions;
39 import static org.mockito.quality.Strictness.STRICT_STUBS;
40 
41 import android.app.AppOpsManager;
42 import android.app.admin.DevicePolicyManager;
43 import android.bluetooth.IBluetoothManagerCallback;
44 import android.compat.testing.PlatformCompatChangeRule;
45 import android.content.AttributionSource;
46 import android.content.Context;
47 import android.content.ContextWrapper;
48 import android.os.IBinder;
49 import android.os.Process;
50 import android.os.UserManager;
51 
52 import androidx.test.filters.SmallTest;
53 import androidx.test.platform.app.InstrumentationRegistry;
54 import androidx.test.runner.AndroidJUnit4;
55 
56 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
57 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
58 
59 import org.junit.After;
60 import org.junit.Before;
61 import org.junit.Rule;
62 import org.junit.Test;
63 import org.junit.function.ThrowingRunnable;
64 import org.junit.rules.TestRule;
65 import org.junit.runner.RunWith;
66 import org.mockito.Mock;
67 import org.mockito.MockitoAnnotations;
68 import org.mockito.junit.MockitoJUnit;
69 import org.mockito.junit.MockitoRule;
70 
71 import java.util.function.BooleanSupplier;
72 
73 @SmallTest
74 @RunWith(AndroidJUnit4.class)
75 public class BluetoothServiceBinderTest {
76     private static final String TAG = BluetoothServiceBinderTest.class.getSimpleName();
77     private static final String LOG_COMPAT_CHANGE = "android.permission.LOG_COMPAT_CHANGE";
78     private static final String READ_COMPAT_CHANGE_CONFIG =
79             "android.permission.READ_COMPAT_CHANGE_CONFIG";
80 
81     @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(STRICT_STUBS);
82 
83     @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule();
84 
85     @Mock private BluetoothManagerService mManagerService;
86     @Mock private UserManager mUserManager;
87     @Mock private AppOpsManager mAppOpsManager;
88     @Mock private DevicePolicyManager mDevicePolicyManager;
89 
90     private Context mContext =
91             spy(
92                     new ContextWrapper(
93                             InstrumentationRegistry.getInstrumentation().getTargetContext()));
94 
95     private final AttributionSource mSource =
96             spy(new AttributionSource.Builder(Process.myUid()).build());
97 
98     private BluetoothServiceBinder mBinder;
99 
100     @Before
setUp()101     public void setUp() throws Exception {
102         MockitoAnnotations.initMocks(this);
103 
104         lenient().doReturn(TAG).when(mSource).getPackageName();
105 
106         InstrumentationRegistry.getInstrumentation()
107                 .getUiAutomation()
108                 .adoptShellPermissionIdentity(LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG);
109 
110         final String appops = mContext.getSystemServiceName(AppOpsManager.class);
111         final String devicePolicy = mContext.getSystemServiceName(DevicePolicyManager.class);
112         doReturn(mAppOpsManager).when(mContext).getSystemService(eq(appops));
113         doReturn(mDevicePolicyManager).when(mContext).getSystemService(eq(devicePolicy));
114 
115         mBinder = new BluetoothServiceBinder(mManagerService, null, mContext, mUserManager);
116     }
117 
118     @After
tearDown()119     public void tearDown() {
120         InstrumentationRegistry.getInstrumentation()
121                 .getUiAutomation()
122                 .dropShellPermissionIdentity();
123         // Do not call verifyMock here. If the test fails the initial error will be lost
124     }
125 
126     @Test
registerAdapter()127     public void registerAdapter() {
128         assertThrows(NullPointerException.class, () -> mBinder.registerAdapter(null));
129         mBinder.registerAdapter(mock(IBluetoothManagerCallback.class));
130         verify(mManagerService).registerAdapter(any());
131         verifyMock();
132     }
133 
134     @Test
unregisterAdapter()135     public void unregisterAdapter() {
136         assertThrows(NullPointerException.class, () -> mBinder.unregisterAdapter(null));
137         mBinder.unregisterAdapter(mock(IBluetoothManagerCallback.class));
138         verify(mManagerService).unregisterAdapter(any());
139         verifyMock();
140     }
141 
142     @Test
143     @DisableCompatChanges({ChangeIds.RESTRICT_ENABLE_DISABLE})
enableNoRestrictEnable()144     public void enableNoRestrictEnable() {
145         assertThrows(NullPointerException.class, () -> mBinder.enable(null));
146 
147         checkDisabled(() -> mBinder.enable(mSource));
148         checkHardDenied(() -> mBinder.enable(mSource), true);
149         doReturn(true).when(mManagerService).enable(any());
150         checkGranted(() -> mBinder.enable(mSource), true);
151         verify(mUserManager).getProfileParent(any());
152         verify(mManagerService).enable(eq(TAG));
153         verifyMock();
154     }
155 
156     @Test
157     @EnableCompatChanges({ChangeIds.RESTRICT_ENABLE_DISABLE})
enableWithRestrictEnable()158     public void enableWithRestrictEnable() {
159         assertThrows(NullPointerException.class, () -> mBinder.enable(null));
160 
161         checkDisabled(() -> mBinder.enable(mSource));
162         checkHardDenied(() -> mBinder.enable(mSource), true);
163         checkGranted(() -> mBinder.enable(mSource), false);
164         verify(mUserManager).getProfileParent(any());
165         verifyMock();
166 
167         // TODO(b/280518177): add more test around compatChange
168     }
169 
170     @Test
enableNoAutoConnect()171     public void enableNoAutoConnect() {
172         assertThrows(NullPointerException.class, () -> mBinder.enableNoAutoConnect(null));
173 
174         checkDisabled(() -> mBinder.enableNoAutoConnect(mSource));
175         checkHardDenied(() -> mBinder.enableNoAutoConnect(mSource), false);
176 
177         // enableNoAutoConnect is only available for Nfc and will fail otherwise
178         assertThrows(SecurityException.class, () -> mBinder.enableNoAutoConnect(mSource));
179 
180         verify(mUserManager).hasUserRestrictionForUser(eq(UserManager.DISALLOW_BLUETOOTH), any());
181         verify(mAppOpsManager).checkPackage(anyInt(), eq(TAG));
182         verifyMock();
183 
184         // TODO(b/280518177): add test that simulate NFC caller to have a successful case
185     }
186 
187     @Test
188     @DisableCompatChanges({ChangeIds.RESTRICT_ENABLE_DISABLE})
disableNoRestrictEnable()189     public void disableNoRestrictEnable() {
190         assertThrows(NullPointerException.class, () -> mBinder.disable(null, true));
191 
192         assertThrows(SecurityException.class, () -> mBinder.disable(mSource, false));
193 
194         checkDisabled(() -> mBinder.disable(mSource, true));
195         checkHardDenied(() -> mBinder.disable(mSource, true), true);
196         doReturn(true).when(mManagerService).disable(any(), anyBoolean());
197         checkGranted(() -> mBinder.disable(mSource, true), true);
198         verify(mUserManager).getProfileParent(any());
199         verify(mManagerService).disable(eq(TAG), anyBoolean());
200         verifyMock();
201     }
202 
203     @Test
204     @EnableCompatChanges({ChangeIds.RESTRICT_ENABLE_DISABLE})
disableWithRestrictEnable()205     public void disableWithRestrictEnable() {
206         assertThrows(NullPointerException.class, () -> mBinder.disable(null, true));
207 
208         assertThrows(SecurityException.class, () -> mBinder.disable(mSource, false));
209 
210         checkDisabled(() -> mBinder.disable(mSource, true));
211         checkHardDenied(() -> mBinder.disable(mSource, true), true);
212         checkGranted(() -> mBinder.disable(mSource, true), false);
213         verify(mUserManager).getProfileParent(any());
214         verifyMock();
215 
216         // TODO(b/280518177): add more test around compatChange
217     }
218 
219     @Test
getState()220     public void getState() {
221         // TODO(b/280518177): add more test from not System / ...
222         // TODO(b/280518177): add more test when caller is not in foreground
223 
224         mBinder.getState();
225         verify(mManagerService).getState();
226         verify(mUserManager).getProfileParent(any());
227         verifyMock();
228     }
229 
230     @Test
getAddress()231     public void getAddress() {
232         assertThrows(NullPointerException.class, () -> mBinder.getAddress(null));
233 
234         assertThrows(SecurityException.class, () -> mBinder.getAddress(mSource));
235         InstrumentationRegistry.getInstrumentation()
236                 .getUiAutomation()
237                 .adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
238 
239         // TODO(b/280518177): Throws SecurityException and remove DEFAULT_MAC_ADDRESS
240         // assertThrows(SecurityException.class, () -> mBinder.getAddress(mSource));
241         assertThat(mBinder.getAddress(mSource)).isEqualTo("02:00:00:00:00:00");
242         verifyMockForCheckIfCallerIsForegroundUser();
243 
244         InstrumentationRegistry.getInstrumentation()
245                 .getUiAutomation()
246                 .adoptShellPermissionIdentity(BLUETOOTH_CONNECT, LOCAL_MAC_ADDRESS);
247 
248         // TODO(b/280518177): add more test from not System / ...
249         // TODO(b/280518177): add more test when caller is not in foreground
250 
251         doReturn("foo").when(mManagerService).getAddress();
252         assertThat(mBinder.getAddress(mSource)).isEqualTo("foo");
253 
254         verify(mManagerService).getAddress();
255         verifyMockForCheckIfCallerIsForegroundUser();
256     }
257 
258     @Test
getName()259     public void getName() {
260         assertThrows(NullPointerException.class, () -> mBinder.getName(null));
261 
262         assertThrows(SecurityException.class, () -> mBinder.getName(mSource));
263         InstrumentationRegistry.getInstrumentation()
264                 .getUiAutomation()
265                 .adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
266 
267         // TODO(b/280518177): add more test from not System / ...
268         // TODO(b/280518177): add more test when caller is not in foreground
269 
270         doReturn("foo").when(mManagerService).getName();
271         assertThat(mBinder.getName(mSource)).isEqualTo("foo");
272         verify(mManagerService).getName();
273         verifyMockForCheckIfCallerIsForegroundUser();
274     }
275 
276     @Test
onFactoryReset()277     public void onFactoryReset() {
278         assertThrows(NullPointerException.class, () -> mBinder.onFactoryReset(null));
279 
280         assertThrows(SecurityException.class, () -> mBinder.onFactoryReset(mSource));
281         InstrumentationRegistry.getInstrumentation()
282                 .getUiAutomation()
283                 .adoptShellPermissionIdentity(BLUETOOTH_PRIVILEGED);
284 
285         assertThrows(SecurityException.class, () -> mBinder.onFactoryReset(mSource));
286         InstrumentationRegistry.getInstrumentation()
287                 .getUiAutomation()
288                 .adoptShellPermissionIdentity(BLUETOOTH_PRIVILEGED, BLUETOOTH_CONNECT);
289 
290         assertThat(mBinder.onFactoryReset(mSource)).isFalse();
291         verify(mManagerService).onFactoryReset();
292         verifyMock();
293     }
294 
295     @Test
isBleScanAvailable()296     public void isBleScanAvailable() {
297         // No permission needed for this call
298         mBinder.isBleScanAvailable();
299         verify(mManagerService).isBleScanAvailable();
300         verifyMock();
301     }
302 
303     @Test
enableBle()304     public void enableBle() {
305         IBinder token = mock(IBinder.class);
306         assertThrows(NullPointerException.class, () -> mBinder.enableBle(null, token));
307         assertThrows(NullPointerException.class, () -> mBinder.enableBle(mSource, null));
308 
309         checkDisabled(() -> mBinder.enableBle(mSource, token));
310         checkHardDenied(() -> mBinder.enableBle(mSource, token), false);
311         doReturn(true).when(mManagerService).enableBle(eq(TAG), eq(token));
312         checkGranted(() -> mBinder.enableBle(mSource, token), true);
313         verify(mManagerService).enableBle(eq(TAG), eq(token));
314         verifyMock();
315     }
316 
317     @Test
disableBle()318     public void disableBle() {
319         IBinder token = mock(IBinder.class);
320         assertThrows(NullPointerException.class, () -> mBinder.disableBle(null, token));
321         assertThrows(NullPointerException.class, () -> mBinder.disableBle(mSource, null));
322 
323         checkDisabled(() -> mBinder.disableBle(mSource, token));
324         checkHardDenied(() -> mBinder.disableBle(mSource, token), false);
325         doReturn(true).when(mManagerService).disableBle(eq(TAG), eq(token));
326         checkGranted(() -> mBinder.disableBle(mSource, token), true);
327         verify(mManagerService).disableBle(eq(TAG), eq(token));
328         verifyMock();
329     }
330 
331     @Test
isHearingAidProfileSupported()332     public void isHearingAidProfileSupported() {
333         // No permission needed for this call
334         mBinder.isHearingAidProfileSupported();
335         verify(mManagerService).isHearingAidProfileSupported();
336         verifyMock();
337     }
338 
339     @Test
setBtHciSnoopLogMode()340     public void setBtHciSnoopLogMode() {
341         assertThrows(SecurityException.class, () -> mBinder.setBtHciSnoopLogMode(0));
342 
343         InstrumentationRegistry.getInstrumentation()
344                 .getUiAutomation()
345                 .adoptShellPermissionIdentity(BLUETOOTH_PRIVILEGED);
346         assertThat(mBinder.setBtHciSnoopLogMode(0)).isEqualTo(0);
347         verify(mManagerService).setBtHciSnoopLogMode(anyInt());
348         verifyMock();
349     }
350 
351     @Test
getBtHciSnoopLogMode()352     public void getBtHciSnoopLogMode() {
353         assertThrows(SecurityException.class, () -> mBinder.getBtHciSnoopLogMode());
354 
355         InstrumentationRegistry.getInstrumentation()
356                 .getUiAutomation()
357                 .adoptShellPermissionIdentity(BLUETOOTH_PRIVILEGED);
358         assertThat(mBinder.getBtHciSnoopLogMode()).isEqualTo(0);
359         verify(mManagerService).getBtHciSnoopLogMode();
360         verifyMock();
361     }
362 
363     // TODO(b/280518177): Add test for `handleShellCommand` and `dump`
364 
365     // *********************************************************************************************
366     // Utility method used in tests
367 
verifyAndClearMock(Object o)368     private void verifyAndClearMock(Object o) {
369         assertThat(mockingDetails(o).isMock() || mockingDetails(o).isSpy()).isTrue();
370         verifyNoMoreInteractions(o);
371         clearInvocations(o);
372     }
373 
verifyMock()374     private void verifyMock() {
375         verifyAndClearMock(mManagerService);
376         verifyAndClearMock(mUserManager);
377         verifyAndClearMock(mAppOpsManager);
378         verifyAndClearMock(mDevicePolicyManager);
379     }
380 
verifyMockForCheckIfCallerIsForegroundUser()381     private void verifyMockForCheckIfCallerIsForegroundUser() {
382         verify(mUserManager).getProfileParent(any());
383         verifyMock();
384     }
385 
checkDisabled(BooleanSupplier binderCall)386     private void checkDisabled(BooleanSupplier binderCall) {
387         doReturn(true)
388                 .when(mUserManager)
389                 .hasUserRestrictionForUser(eq(UserManager.DISALLOW_BLUETOOTH), any());
390 
391         assertThat(binderCall.getAsBoolean()).isFalse();
392 
393         verify(mUserManager).hasUserRestrictionForUser(eq(UserManager.DISALLOW_BLUETOOTH), any());
394         verifyMock();
395     }
396 
checkHardDenied(ThrowingRunnable binderCall, boolean requireForeground)397     private void checkHardDenied(ThrowingRunnable binderCall, boolean requireForeground) {
398         doReturn(false)
399                 .when(mUserManager)
400                 .hasUserRestrictionForUser(eq(UserManager.DISALLOW_BLUETOOTH), any());
401 
402         assertThrows(SecurityException.class, binderCall);
403 
404         verify(mUserManager).hasUserRestrictionForUser(eq(UserManager.DISALLOW_BLUETOOTH), any());
405         if (requireForeground) {
406             verify(mUserManager).getProfileParent(any());
407         }
408         verify(mAppOpsManager).checkPackage(anyInt(), eq(TAG));
409         verifyMock();
410     }
411 
checkGranted(BooleanSupplier binderCall, boolean expectedResult)412     private void checkGranted(BooleanSupplier binderCall, boolean expectedResult) {
413         InstrumentationRegistry.getInstrumentation()
414                 .getUiAutomation()
415                 .adoptShellPermissionIdentity(
416                         LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG, BLUETOOTH_CONNECT);
417 
418         assertThat(binderCall.getAsBoolean()).isEqualTo(expectedResult);
419 
420         verify(mUserManager).hasUserRestrictionForUser(eq(UserManager.DISALLOW_BLUETOOTH), any());
421         verify(mAppOpsManager).checkPackage(anyInt(), eq(TAG));
422         if (!expectedResult) {
423             verify(mDevicePolicyManager).getDeviceOwnerUser();
424             verify(mDevicePolicyManager).getDeviceOwnerComponentOnAnyUser();
425         }
426     }
427 }
428