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