| /* |
| * Copyright 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.bluetooth.btservice; |
| |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.Mockito.*; |
| |
| import android.app.AlarmManager; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.IBluetoothCallback; |
| import android.content.Context; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.res.Resources; |
| import android.media.AudioManager; |
| import android.os.Binder; |
| import android.os.Looper; |
| import android.os.PowerManager; |
| import android.os.Process; |
| import android.os.SystemProperties; |
| import android.os.UserManager; |
| import android.support.test.InstrumentationRegistry; |
| import android.support.test.filters.MediumTest; |
| import android.support.test.runner.AndroidJUnit4; |
| import android.test.mock.MockContentResolver; |
| import android.util.ByteStringUtils; |
| import android.util.Log; |
| |
| import com.android.bluetooth.R; |
| import com.android.bluetooth.TestUtils; |
| import com.android.bluetooth.Utils; |
| |
| import com.google.protobuf.ByteString; |
| |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.BeforeClass; |
| import org.junit.Ignore; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| import java.security.InvalidKeyException; |
| import java.security.NoSuchAlgorithmException; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| |
| import javax.crypto.Mac; |
| import javax.crypto.spec.SecretKeySpec; |
| |
| @MediumTest |
| @RunWith(AndroidJUnit4.class) |
| public class AdapterServiceTest { |
| private static final String TAG = AdapterServiceTest.class.getSimpleName(); |
| |
| private AdapterService mAdapterService; |
| |
| private @Mock Context mMockContext; |
| private @Mock ApplicationInfo mMockApplicationInfo; |
| private @Mock AlarmManager mMockAlarmManager; |
| private @Mock Resources mMockResources; |
| private @Mock UserManager mMockUserManager; |
| private @Mock ProfileService mMockGattService; |
| private @Mock ProfileService mMockService; |
| private @Mock ProfileService mMockService2; |
| private @Mock IBluetoothCallback mIBluetoothCallback; |
| private @Mock Binder mBinder; |
| private @Mock AudioManager mAudioManager; |
| |
| private static final int CONTEXT_SWITCH_MS = 100; |
| private static final int ONE_SECOND_MS = 1000; |
| private static final int NATIVE_INIT_MS = 8000; |
| |
| private PowerManager mPowerManager; |
| private PackageManager mMockPackageManager; |
| private MockContentResolver mMockContentResolver; |
| private HashMap<String, HashMap<String, String>> mAdapterConfig; |
| |
| @BeforeClass |
| public static void setupClass() { |
| // Bring native layer up and down to make sure config files are properly loaded |
| if (Looper.myLooper() == null) { |
| Looper.prepare(); |
| } |
| Assert.assertNotNull(Looper.myLooper()); |
| AdapterService adapterService = new AdapterService(); |
| adapterService.initNative(); |
| adapterService.cleanupNative(); |
| HashMap<String, HashMap<String, String>> adapterConfig = TestUtils.readAdapterConfig(); |
| Assert.assertNotNull(adapterConfig); |
| Assert.assertNotNull("metrics salt is null: " + adapterConfig.toString(), |
| getMetricsSalt(adapterConfig)); |
| } |
| |
| @Before |
| public void setUp() throws PackageManager.NameNotFoundException { |
| if (Looper.myLooper() == null) { |
| Looper.prepare(); |
| } |
| Assert.assertNotNull(Looper.myLooper()); |
| |
| InstrumentationRegistry.getInstrumentation().runOnMainSync( |
| () -> mAdapterService = new AdapterService()); |
| mMockPackageManager = mock(PackageManager.class); |
| mMockContentResolver = new MockContentResolver(mMockContext); |
| MockitoAnnotations.initMocks(this); |
| mPowerManager = (PowerManager) InstrumentationRegistry.getTargetContext() |
| .getSystemService(Context.POWER_SERVICE); |
| |
| when(mMockContext.getApplicationInfo()).thenReturn(mMockApplicationInfo); |
| when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver); |
| when(mMockContext.getApplicationContext()).thenReturn(mMockContext); |
| when(mMockContext.getResources()).thenReturn(mMockResources); |
| when(mMockContext.getUserId()).thenReturn(Process.BLUETOOTH_UID); |
| when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); |
| when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager); |
| when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager); |
| when(mMockContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mMockAlarmManager); |
| when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager); |
| |
| when(mMockResources.getBoolean(R.bool.profile_supported_gatt)).thenReturn(true); |
| when(mMockResources.getBoolean(R.bool.profile_supported_pbap)).thenReturn(true); |
| when(mMockResources.getBoolean(R.bool.profile_supported_pan)).thenReturn(true); |
| |
| when(mIBluetoothCallback.asBinder()).thenReturn(mBinder); |
| |
| doReturn(Process.BLUETOOTH_UID).when(mMockPackageManager) |
| .getPackageUidAsUser(any(), anyInt(), anyInt()); |
| |
| when(mMockGattService.getName()).thenReturn("GattService"); |
| when(mMockService.getName()).thenReturn("Service1"); |
| when(mMockService2.getName()).thenReturn("Service2"); |
| |
| // Attach a context to the service for permission checks. |
| mAdapterService.attach(mMockContext, null, null, null, null, null); |
| |
| mAdapterService.onCreate(); |
| mAdapterService.registerCallback(mIBluetoothCallback); |
| |
| Config.init(mMockContext); |
| |
| mAdapterConfig = TestUtils.readAdapterConfig(); |
| Assert.assertNotNull(mAdapterConfig); |
| } |
| |
| @After |
| public void tearDown() { |
| mAdapterService.unregisterCallback(mIBluetoothCallback); |
| mAdapterService.cleanup(); |
| Config.init(InstrumentationRegistry.getTargetContext()); |
| } |
| |
| private void verifyStateChange(int prevState, int currState, int callNumber, int timeoutMs) { |
| try { |
| verify(mIBluetoothCallback, timeout(timeoutMs) |
| .times(callNumber)).onBluetoothStateChange(prevState, currState); |
| } catch (Exception e) { |
| // the mocked onBluetoothStateChange doesn't throw exceptions |
| } |
| } |
| |
| private void doEnable(int invocationNumber, boolean onlyGatt) { |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| |
| final int startServiceCalls = 2 * (onlyGatt ? 1 : 3); // Start and stop GATT + 2 |
| |
| mAdapterService.enable(); |
| |
| verifyStateChange(BluetoothAdapter.STATE_OFF, BluetoothAdapter.STATE_BLE_TURNING_ON, |
| invocationNumber + 1, CONTEXT_SWITCH_MS); |
| |
| // Start GATT |
| verify(mMockContext, timeout(CONTEXT_SWITCH_MS).times( |
| startServiceCalls * invocationNumber + 1)).startService(any()); |
| mAdapterService.addProfile(mMockGattService); |
| mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_ON); |
| |
| verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON, BluetoothAdapter.STATE_BLE_ON, |
| invocationNumber + 1, NATIVE_INIT_MS); |
| |
| mAdapterService.onLeServiceUp(); |
| |
| verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_TURNING_ON, |
| invocationNumber + 1, CONTEXT_SWITCH_MS); |
| |
| if (!onlyGatt) { |
| // Start Mock PBAP and PAN services |
| verify(mMockContext, timeout(ONE_SECOND_MS).times( |
| startServiceCalls * invocationNumber + 3)).startService(any()); |
| mAdapterService.addProfile(mMockService); |
| mAdapterService.addProfile(mMockService2); |
| mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_ON); |
| mAdapterService.onProfileServiceStateChanged(mMockService2, BluetoothAdapter.STATE_ON); |
| } |
| |
| verifyStateChange(BluetoothAdapter.STATE_TURNING_ON, BluetoothAdapter.STATE_ON, |
| invocationNumber + 1, CONTEXT_SWITCH_MS); |
| |
| verify(mMockContext, timeout(CONTEXT_SWITCH_MS).times(2 * invocationNumber + 2)) |
| .sendBroadcast(any(), eq(android.Manifest.permission.BLUETOOTH)); |
| final int scanMode = mAdapterService.getScanMode(); |
| Assert.assertTrue(scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE |
| || scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); |
| Assert.assertTrue(mAdapterService.isEnabled()); |
| } |
| |
| private void doDisable(int invocationNumber, boolean onlyGatt) { |
| Assert.assertTrue(mAdapterService.isEnabled()); |
| |
| final int startServiceCalls = 2 * (onlyGatt ? 1 : 3); // Start and stop GATT + 2 |
| |
| mAdapterService.disable(); |
| |
| verifyStateChange(BluetoothAdapter.STATE_ON, BluetoothAdapter.STATE_TURNING_OFF, |
| invocationNumber + 1, CONTEXT_SWITCH_MS); |
| |
| if (!onlyGatt) { |
| // Stop PBAP and PAN |
| verify(mMockContext, timeout(ONE_SECOND_MS).times( |
| startServiceCalls * invocationNumber + 5)).startService(any()); |
| mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_OFF); |
| mAdapterService.onProfileServiceStateChanged(mMockService2, BluetoothAdapter.STATE_OFF); |
| } |
| |
| verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_BLE_ON, |
| invocationNumber + 1, CONTEXT_SWITCH_MS); |
| |
| mAdapterService.onBrEdrDown(); |
| |
| verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_BLE_TURNING_OFF, |
| invocationNumber + 1, CONTEXT_SWITCH_MS); |
| |
| // Stop GATT |
| verify(mMockContext, timeout(ONE_SECOND_MS).times( |
| startServiceCalls * invocationNumber + startServiceCalls)).startService(any()); |
| mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_OFF); |
| |
| verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, |
| invocationNumber + 1, CONTEXT_SWITCH_MS); |
| |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| } |
| |
| /** |
| * Test: Turn Bluetooth on. |
| * Check whether the AdapterService gets started. |
| */ |
| @Test |
| public void testEnable() { |
| doEnable(0, false); |
| } |
| |
| /** |
| * Test: Turn Bluetooth on/off. |
| * Check whether the AdapterService gets started and stopped. |
| */ |
| @Test |
| public void testEnableDisable() { |
| doEnable(0, false); |
| doDisable(0, false); |
| } |
| |
| /** |
| * Test: Turn Bluetooth on/off with only GATT supported. |
| * Check whether the AdapterService gets started and stopped. |
| */ |
| @Test |
| public void testEnableDisableOnlyGatt() { |
| Context mockContext = mock(Context.class); |
| Resources mockResources = mock(Resources.class); |
| |
| when(mockContext.getApplicationInfo()).thenReturn(mMockApplicationInfo); |
| when(mockContext.getContentResolver()).thenReturn(mMockContentResolver); |
| when(mockContext.getApplicationContext()).thenReturn(mockContext); |
| when(mockContext.getResources()).thenReturn(mockResources); |
| when(mockContext.getUserId()).thenReturn(Process.BLUETOOTH_UID); |
| when(mockContext.getPackageManager()).thenReturn(mMockPackageManager); |
| when(mockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager); |
| when(mockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager); |
| when(mockContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mMockAlarmManager); |
| |
| when(mockResources.getBoolean(R.bool.profile_supported_gatt)).thenReturn(true); |
| |
| Config.init(mockContext); |
| doEnable(0, true); |
| doDisable(0, true); |
| } |
| |
| /** |
| * Test: Don't start GATT |
| * Check whether the AdapterService quits gracefully |
| */ |
| @Test |
| public void testGattStartTimeout() { |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| |
| mAdapterService.enable(); |
| |
| verifyStateChange(BluetoothAdapter.STATE_OFF, BluetoothAdapter.STATE_BLE_TURNING_ON, 1, |
| CONTEXT_SWITCH_MS); |
| |
| // Start GATT |
| verify(mMockContext, timeout(CONTEXT_SWITCH_MS).times(1)).startService(any()); |
| mAdapterService.addProfile(mMockGattService); |
| |
| verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON, |
| BluetoothAdapter.STATE_BLE_TURNING_OFF, 1, |
| AdapterState.BLE_START_TIMEOUT_DELAY + CONTEXT_SWITCH_MS); |
| |
| // Stop GATT |
| verify(mMockContext, timeout(AdapterState.BLE_STOP_TIMEOUT_DELAY + CONTEXT_SWITCH_MS) |
| .times(2)).startService(any()); |
| |
| verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1, |
| CONTEXT_SWITCH_MS); |
| |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| } |
| |
| /** |
| * Test: Don't stop GATT |
| * Check whether the AdapterService quits gracefully |
| */ |
| @Test |
| public void testGattStopTimeout() { |
| doEnable(0, false); |
| Assert.assertTrue(mAdapterService.isEnabled()); |
| |
| mAdapterService.disable(); |
| |
| verifyStateChange(BluetoothAdapter.STATE_ON, BluetoothAdapter.STATE_TURNING_OFF, 1, |
| CONTEXT_SWITCH_MS); |
| |
| // Stop PBAP and PAN |
| verify(mMockContext, timeout(ONE_SECOND_MS).times(5)).startService(any()); |
| mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_OFF); |
| mAdapterService.onProfileServiceStateChanged(mMockService2, BluetoothAdapter.STATE_OFF); |
| |
| verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_BLE_ON, 1, |
| CONTEXT_SWITCH_MS); |
| |
| mAdapterService.onBrEdrDown(); |
| |
| verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_BLE_TURNING_OFF, 1, |
| CONTEXT_SWITCH_MS); |
| |
| // Stop GATT |
| verify(mMockContext, timeout(ONE_SECOND_MS).times(6)).startService(any()); |
| |
| verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1, |
| AdapterState.BLE_STOP_TIMEOUT_DELAY + CONTEXT_SWITCH_MS); |
| |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| } |
| |
| /** |
| * Test: Don't start a classic profile |
| * Check whether the AdapterService quits gracefully |
| */ |
| @Test |
| public void testProfileStartTimeout() { |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| |
| mAdapterService.enable(); |
| |
| verifyStateChange(BluetoothAdapter.STATE_OFF, BluetoothAdapter.STATE_BLE_TURNING_ON, 1, |
| CONTEXT_SWITCH_MS); |
| |
| // Start GATT |
| verify(mMockContext, timeout(CONTEXT_SWITCH_MS).times(1)).startService(any()); |
| mAdapterService.addProfile(mMockGattService); |
| mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_ON); |
| |
| verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON, BluetoothAdapter.STATE_BLE_ON, 1, |
| NATIVE_INIT_MS); |
| |
| mAdapterService.onLeServiceUp(); |
| |
| verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_TURNING_ON, 1, |
| CONTEXT_SWITCH_MS); |
| |
| // Register Mock PBAP and PAN services, only start one |
| verify(mMockContext, timeout(ONE_SECOND_MS).times(3)).startService(any()); |
| mAdapterService.addProfile(mMockService); |
| mAdapterService.addProfile(mMockService2); |
| mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_ON); |
| |
| verifyStateChange(BluetoothAdapter.STATE_TURNING_ON, BluetoothAdapter.STATE_TURNING_OFF, 1, |
| AdapterState.BREDR_START_TIMEOUT_DELAY + CONTEXT_SWITCH_MS); |
| |
| // Stop PBAP and PAN |
| verify(mMockContext, timeout(ONE_SECOND_MS).times(5)).startService(any()); |
| mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_OFF); |
| |
| verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_BLE_ON, 1, |
| CONTEXT_SWITCH_MS); |
| } |
| |
| /** |
| * Test: Don't stop a classic profile |
| * Check whether the AdapterService quits gracefully |
| */ |
| @Test |
| public void testProfileStopTimeout() { |
| doEnable(0, false); |
| |
| Assert.assertTrue(mAdapterService.isEnabled()); |
| |
| mAdapterService.disable(); |
| |
| verifyStateChange(BluetoothAdapter.STATE_ON, BluetoothAdapter.STATE_TURNING_OFF, 1, |
| CONTEXT_SWITCH_MS); |
| |
| // Stop PBAP and PAN |
| verify(mMockContext, timeout(ONE_SECOND_MS).times(5)).startService(any()); |
| mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_OFF); |
| |
| verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF, |
| BluetoothAdapter.STATE_BLE_TURNING_OFF, 1, |
| AdapterState.BREDR_STOP_TIMEOUT_DELAY + CONTEXT_SWITCH_MS); |
| |
| // Stop GATT |
| verify(mMockContext, timeout(ONE_SECOND_MS).times(6)).startService(any()); |
| mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_OFF); |
| |
| verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1, |
| AdapterState.BLE_STOP_TIMEOUT_DELAY + CONTEXT_SWITCH_MS); |
| |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| } |
| |
| /** |
| * Test: Toggle snoop logging setting |
| * Check whether the AdapterService restarts fully |
| */ |
| @Test |
| public void testSnoopLoggingChange() { |
| String snoopSetting = |
| SystemProperties.get(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, ""); |
| SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, "false"); |
| doEnable(0, false); |
| |
| Assert.assertTrue(mAdapterService.isEnabled()); |
| |
| Assert.assertFalse( |
| SystemProperties.getBoolean(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, |
| true)); |
| |
| SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, "true"); |
| |
| mAdapterService.disable(); |
| |
| verifyStateChange(BluetoothAdapter.STATE_ON, BluetoothAdapter.STATE_TURNING_OFF, 1, |
| CONTEXT_SWITCH_MS); |
| |
| // Stop PBAP and PAN |
| verify(mMockContext, timeout(ONE_SECOND_MS).times(5)).startService(any()); |
| mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_OFF); |
| mAdapterService.onProfileServiceStateChanged(mMockService2, BluetoothAdapter.STATE_OFF); |
| |
| verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_BLE_ON, 1, |
| CONTEXT_SWITCH_MS); |
| |
| // Don't call onBrEdrDown(). The Adapter should turn itself off. |
| |
| verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_BLE_TURNING_OFF, 1, |
| CONTEXT_SWITCH_MS); |
| |
| // Stop GATT |
| verify(mMockContext, timeout(ONE_SECOND_MS).times(6)).startService(any()); |
| mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_OFF); |
| |
| verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1, |
| CONTEXT_SWITCH_MS); |
| |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| |
| // Restore earlier setting |
| SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, snoopSetting); |
| } |
| |
| /** |
| * Test: Obfuscate Bluetooth address when Bluetooth is disabled |
| * Check whether the returned value meets expectation |
| */ |
| @Test |
| public void testObfuscateBluetoothAddress_BluetoothDisabled() { |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| byte[] metricsSalt = getMetricsSalt(mAdapterConfig); |
| Assert.assertNotNull(metricsSalt); |
| BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0); |
| ByteString obfuscatedAddress = mAdapterService.obfuscateAddress(device); |
| Assert.assertFalse(obfuscatedAddress.isEmpty()); |
| Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress.toByteArray())); |
| Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device), |
| obfuscatedAddress.toByteArray()); |
| } |
| |
| /** |
| * Test: Obfuscate Bluetooth address when Bluetooth is enabled |
| * Check whether the returned value meets expectation |
| */ |
| @Test |
| public void testObfuscateBluetoothAddress_BluetoothEnabled() { |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| doEnable(0, false); |
| Assert.assertTrue(mAdapterService.isEnabled()); |
| byte[] metricsSalt = getMetricsSalt(mAdapterConfig); |
| Assert.assertNotNull(metricsSalt); |
| BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0); |
| ByteString obfuscatedAddress = mAdapterService.obfuscateAddress(device); |
| Assert.assertFalse(obfuscatedAddress.isEmpty()); |
| Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress.toByteArray())); |
| Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device), |
| obfuscatedAddress.toByteArray()); |
| } |
| |
| /** |
| * Test: Check if obfuscated Bluetooth address stays the same after toggling Bluetooth |
| */ |
| @Test |
| public void testObfuscateBluetoothAddress_PersistentBetweenToggle() { |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| byte[] metricsSalt = getMetricsSalt(mAdapterConfig); |
| Assert.assertNotNull(metricsSalt); |
| BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0); |
| ByteString obfuscatedAddress1 = mAdapterService.obfuscateAddress(device); |
| Assert.assertFalse(obfuscatedAddress1.isEmpty()); |
| Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1.toByteArray())); |
| Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device), |
| obfuscatedAddress1.toByteArray()); |
| // Enable |
| doEnable(0, false); |
| Assert.assertTrue(mAdapterService.isEnabled()); |
| ByteString obfuscatedAddress3 = mAdapterService.obfuscateAddress(device); |
| Assert.assertFalse(obfuscatedAddress3.isEmpty()); |
| Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress3.toByteArray())); |
| Assert.assertArrayEquals(obfuscatedAddress3.toByteArray(), |
| obfuscatedAddress1.toByteArray()); |
| // Disable |
| doDisable(0, false); |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| ByteString obfuscatedAddress4 = mAdapterService.obfuscateAddress(device); |
| Assert.assertFalse(obfuscatedAddress4.isEmpty()); |
| Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress4.toByteArray())); |
| Assert.assertArrayEquals(obfuscatedAddress4.toByteArray(), |
| obfuscatedAddress1.toByteArray()); |
| } |
| |
| /** |
| * Test: Check if obfuscated Bluetooth address stays the same after re-initializing |
| * {@link AdapterService} |
| */ |
| @Test |
| public void testObfuscateBluetoothAddress_PersistentBetweenAdapterServiceInitialization() throws |
| PackageManager.NameNotFoundException { |
| byte[] metricsSalt = getMetricsSalt(mAdapterConfig); |
| Assert.assertNotNull(metricsSalt); |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0); |
| ByteString obfuscatedAddress1 = mAdapterService.obfuscateAddress(device); |
| Assert.assertFalse(obfuscatedAddress1.isEmpty()); |
| Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1.toByteArray())); |
| Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device), |
| obfuscatedAddress1.toByteArray()); |
| tearDown(); |
| setUp(); |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| ByteString obfuscatedAddress2 = mAdapterService.obfuscateAddress(device); |
| Assert.assertFalse(obfuscatedAddress2.isEmpty()); |
| Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2.toByteArray())); |
| Assert.assertArrayEquals(obfuscatedAddress2.toByteArray(), |
| obfuscatedAddress1.toByteArray()); |
| } |
| |
| /** |
| * Test: Verify that obfuscated Bluetooth address changes after factory reset |
| * |
| * There are 4 types of factory reset that we are talking about: |
| * 1. Factory reset all user data from Settings -> Will restart phone |
| * 2. Factory reset WiFi and Bluetooth from Settings -> Will only restart WiFi and BT |
| * 3. Call BluetoothAdapter.factoryReset() -> Will disable Bluetooth and reset config in |
| * memory and disk |
| * 4. Call AdapterService.factoryReset() -> Will only reset config in memory |
| * |
| * We can only use No. 4 here |
| */ |
| @Ignore("AdapterService.factoryReset() does not reload config into memory and hence old salt" |
| + " is still used until next time Bluetooth library is initialized. However Bluetooth" |
| + " cannot be used until Bluetooth process restart any way. Thus it is almost" |
| + " guaranteed that user has to re-enable Bluetooth and hence re-generate new salt" |
| + " after factory reset") |
| @Test |
| public void testObfuscateBluetoothAddress_FactoryReset() { |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0); |
| ByteString obfuscatedAddress1 = mAdapterService.obfuscateAddress(device); |
| Assert.assertFalse(obfuscatedAddress1.isEmpty()); |
| Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1.toByteArray())); |
| mAdapterService.factoryReset(); |
| ByteString obfuscatedAddress2 = mAdapterService.obfuscateAddress(device); |
| Assert.assertFalse(obfuscatedAddress2.isEmpty()); |
| Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2.toByteArray())); |
| Assert.assertFalse(Arrays.equals(obfuscatedAddress2.toByteArray(), |
| obfuscatedAddress1.toByteArray())); |
| doEnable(0, false); |
| ByteString obfuscatedAddress3 = mAdapterService.obfuscateAddress(device); |
| Assert.assertFalse(obfuscatedAddress3.isEmpty()); |
| Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress3.toByteArray())); |
| Assert.assertArrayEquals(obfuscatedAddress3.toByteArray(), |
| obfuscatedAddress2.toByteArray()); |
| mAdapterService.factoryReset(); |
| ByteString obfuscatedAddress4 = mAdapterService.obfuscateAddress(device); |
| Assert.assertFalse(obfuscatedAddress4.isEmpty()); |
| Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress4.toByteArray())); |
| Assert.assertFalse(Arrays.equals(obfuscatedAddress4.toByteArray(), |
| obfuscatedAddress3.toByteArray())); |
| } |
| |
| /** |
| * Test: Verify that obfuscated Bluetooth address changes after factory reset and reloading |
| * native layer |
| */ |
| @Test |
| public void testObfuscateBluetoothAddress_FactoryResetAndReloadNativeLayer() throws |
| PackageManager.NameNotFoundException { |
| byte[] metricsSalt1 = getMetricsSalt(mAdapterConfig); |
| Assert.assertNotNull(metricsSalt1); |
| Assert.assertFalse(mAdapterService.isEnabled()); |
| BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0); |
| ByteString obfuscatedAddress1 = mAdapterService.obfuscateAddress(device); |
| Assert.assertFalse(obfuscatedAddress1.isEmpty()); |
| Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1.toByteArray())); |
| Assert.assertArrayEquals(obfuscateInJava(metricsSalt1, device), |
| obfuscatedAddress1.toByteArray()); |
| mAdapterService.factoryReset(); |
| tearDown(); |
| setUp(); |
| // Cannot verify metrics salt since it is not written to disk until native cleanup |
| ByteString obfuscatedAddress2 = mAdapterService.obfuscateAddress(device); |
| Assert.assertFalse(obfuscatedAddress2.isEmpty()); |
| Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2.toByteArray())); |
| Assert.assertFalse(Arrays.equals(obfuscatedAddress2.toByteArray(), |
| obfuscatedAddress1.toByteArray())); |
| } |
| |
| private static byte[] getMetricsSalt(HashMap<String, HashMap<String, String>> adapterConfig) { |
| HashMap<String, String> metricsSection = adapterConfig.get("Metrics"); |
| if (metricsSection == null) { |
| Log.e(TAG, "Metrics section is null: " + adapterConfig.toString()); |
| return null; |
| } |
| String saltString = metricsSection.get("Salt256Bit"); |
| if (saltString == null) { |
| Log.e(TAG, "Salt256Bit is null: " + metricsSection.toString()); |
| return null; |
| } |
| byte[] metricsSalt = ByteStringUtils.fromHexToByteArray(saltString); |
| if (metricsSalt.length != 32) { |
| Log.e(TAG, "Salt length is not 32 bit, but is " + metricsSalt.length); |
| return null; |
| } |
| return metricsSalt; |
| } |
| |
| private static byte[] obfuscateInJava(byte[] key, BluetoothDevice device) { |
| String algorithm = "HmacSHA256"; |
| try { |
| Mac hmac256 = Mac.getInstance(algorithm); |
| hmac256.init(new SecretKeySpec(key, algorithm)); |
| return hmac256.doFinal(Utils.getByteAddress(device)); |
| } catch (NoSuchAlgorithmException | IllegalStateException | InvalidKeyException exp) { |
| exp.printStackTrace(); |
| return null; |
| } |
| } |
| |
| private static boolean isByteArrayAllZero(byte[] byteArray) { |
| for (byte i : byteArray) { |
| if (i != 0) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |