Loading android/app/src/com/android/bluetooth/btservice/AdapterProperties.java +2 −1 Original line number Diff line number Diff line Loading @@ -763,7 +763,8 @@ class AdapterProperties { if (state == BluetoothProfile.STATE_CONNECTING) { BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_DEVICE_NAME_REPORTED, metricId, device.getName()); MetricsLogger.getInstance().logSanitizedBluetoothDeviceName(metricId, device.getName()); MetricsLogger.getInstance() .logAllowlistedDeviceNameHash(metricId, device.getName(), true); } BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_CONNECTION_STATE_CHANGED, state, 0 /* deprecated */, profile, mService.obfuscateAddress(device), Loading android/app/src/com/android/bluetooth/btservice/AdapterService.java +0 −1 Original line number Diff line number Diff line Loading @@ -941,7 +941,6 @@ public class AdapterService extends Service { long socketCreationTimeNanos, boolean isSerialPort, int appUid) { int metricId = getMetricId(device); long currentTime = System.nanoTime(); long endToEndLatencyNanos = currentTime - socketCreationTimeNanos; Loading android/app/src/com/android/bluetooth/btservice/MetricsLogger.java +72 −35 Original line number Diff line number Diff line Loading @@ -19,21 +19,23 @@ import static com.android.bluetooth.BtRestrictedStatsLog.RESTRICTED_BLUETOOTH_DE import android.app.AlarmManager; import android.content.Context; import android.os.Build; import android.os.SystemClock; import android.util.Log; import androidx.annotation.RequiresApi; import com.android.bluetooth.BluetoothMetricsProto.BluetoothLog; import com.android.bluetooth.BluetoothMetricsProto.ProfileConnectionStats; import com.android.bluetooth.BluetoothMetricsProto.ProfileId; import com.android.bluetooth.BluetoothStatsLog; import com.android.bluetooth.BtRestrictedStatsLog; import com.android.bluetooth.Utils; import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; import com.google.common.annotations.VisibleForTesting; import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; import com.google.common.hash.Hashing; import java.io.ByteArrayInputStream; import java.io.File; Loading @@ -42,7 +44,9 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * Class of Bluetooth Metrics Loading Loading @@ -269,11 +273,10 @@ public class MetricsLogger { mAlarmManager.cancel(mOnAlarmListener); } protected boolean logSanitizedBluetoothDeviceName(int metricId, String deviceName) { if (!mBloomFilterInitialized || deviceName == null) { return false; private List<String> getWordBreakdownList(String deviceName) { if (deviceName == null) { return new ArrayList<String>(); } // remove more than one spaces in a row deviceName = deviceName.trim().replaceAll(" +", " "); // remove non alphanumeric characters and spaces, and transform to lower cases. Loading @@ -282,52 +285,86 @@ public class MetricsLogger { if (words.length > MAX_WORDS_ALLOWED_IN_DEVICE_NAME) { // Validity checking here to avoid excessively long sequences return false; return new ArrayList<String>(); } // find the longest matched substring String matchedString = ""; byte[] matchedSha256 = null; // collect the word breakdown in an arraylist ArrayList<String> wordBreakdownList = new ArrayList<String>(); for (int start = 0; start < words.length; start++) { String toBeMatched = ""; StringBuilder deviceNameCombination = new StringBuilder(); for (int end = start; end < words.length; end++) { toBeMatched += words[end]; // TODO(b/280868296): Refactor to log even if bloom filter isn't initialized. if (SdkLevel.isAtLeastU()) { BtRestrictedStatsLog.write(RESTRICTED_BLUETOOTH_DEVICE_NAME_REPORTED, toBeMatched); deviceNameCombination.append(words[end]); wordBreakdownList.add(deviceNameCombination.toString()); } byte[] sha256 = getSha256(toBeMatched); if (sha256 == null) { continue; } if (mBloomFilter.mightContain(sha256) && toBeMatched.length() > matchedString.length()) { matchedString = toBeMatched; matchedSha256 = sha256; return wordBreakdownList; } @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) private void uploadRestrictedBluetothDeviceName(List<String> wordBreakdownList) { for (String word : wordBreakdownList) { BtRestrictedStatsLog.write(RESTRICTED_BLUETOOTH_DEVICE_NAME_REPORTED, word); } } // upload the sha256 of the longest matched string. if (matchedSha256 == null) { return false; private String getMatchedString(List<String> wordBreakdownList) { if (!mBloomFilterInitialized || wordBreakdownList.isEmpty()) { return ""; } statslogBluetoothDeviceNames( metricId, matchedString, Hashing.sha256().hashString(matchedString, StandardCharsets.UTF_8).toString()); return true; String matchedString = ""; for (String word : wordBreakdownList) { byte[] sha256 = getSha256(word); if (mBloomFilter.mightContain(sha256) && word.length() > matchedString.length()) { matchedString = word; } } return matchedString; } protected void statslogBluetoothDeviceNames(int metricId, String matchedString, String sha256) { protected String getAllowlistedDeviceNameHash(String deviceName) { List<String> wordBreakdownList = getWordBreakdownList(deviceName); String matchedString = getMatchedString(wordBreakdownList); return getSha256String(matchedString); } protected String logAllowlistedDeviceNameHash( int metricId, String deviceName, boolean logRestrictedNames) { List<String> wordBreakdownList = getWordBreakdownList(deviceName); String matchedString = getMatchedString(wordBreakdownList); if (logRestrictedNames) { // Log the restricted bluetooth device name if (SdkLevel.isAtLeastU()) { uploadRestrictedBluetothDeviceName(wordBreakdownList); } } if (!matchedString.isEmpty()) { statslogBluetoothDeviceNames(metricId, matchedString); } return getSha256String(matchedString); } protected void statslogBluetoothDeviceNames(int metricId, String matchedString) { String sha256 = getSha256String(matchedString); Log.d(TAG, "Uploading sha256 hash of matched bluetooth device name: " + sha256); BluetoothStatsLog.write( BluetoothStatsLog.BLUETOOTH_HASHED_DEVICE_NAME_REPORTED, metricId, sha256); } protected static String getSha256String(String name) { if (name.isEmpty()) { return ""; } StringBuilder hexString = new StringBuilder(); byte[] hashBytes = getSha256(name); for (byte b : hashBytes) { hexString.append(String.format("%02x", b)); } return hexString.toString(); } protected static byte[] getSha256(String name) { MessageDigest digest = null; try { Loading android/app/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java +69 −196 Original line number Diff line number Diff line Loading @@ -42,15 +42,48 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Unit tests for {@link MetricsLogger} */ /** Unit tests for {@link MetricsLogger} */ @MediumTest @RunWith(AndroidJUnit4.class) public class MetricsLoggerTest { private static final String TEST_BLOOMFILTER_NAME = "TestBloomfilter"; private static final HashMap<String, String> SANITIZED_DEVICE_NAME_MAP = new HashMap<>(); static { SANITIZED_DEVICE_NAME_MAP.put("AirpoDspro", "airpodspro"); SANITIZED_DEVICE_NAME_MAP.put("AirpoDs-pro", "airpodspro"); SANITIZED_DEVICE_NAME_MAP.put("Someone's AirpoDs", "airpods"); SANITIZED_DEVICE_NAME_MAP.put("Galaxy Buds pro", "galaxybudspro"); SANITIZED_DEVICE_NAME_MAP.put("Someone's AirpoDs", "airpods"); SANITIZED_DEVICE_NAME_MAP.put("Who's Pixel 7", "pixel7"); SANITIZED_DEVICE_NAME_MAP.put("陈的pixel 7手机", "pixel7"); SANITIZED_DEVICE_NAME_MAP.put("pixel 7 pro", "pixel7pro"); SANITIZED_DEVICE_NAME_MAP.put("My Pixel 7 Pro", "pixel7pro"); SANITIZED_DEVICE_NAME_MAP.put("My Pixel 7 PRO", "pixel7pro"); SANITIZED_DEVICE_NAME_MAP.put("My Pixel 7 - PRO", "pixel7pro"); SANITIZED_DEVICE_NAME_MAP.put("My BMW X5", "bmwx5"); SANITIZED_DEVICE_NAME_MAP.put("Jane Doe's Tesla Model--X", "teslamodelx"); SANITIZED_DEVICE_NAME_MAP.put("TESLA of Jane DOE", "tesla"); SANITIZED_DEVICE_NAME_MAP.put("SONY WH-1000XM noise cancelling headsets", "sonywh1000xm"); SANITIZED_DEVICE_NAME_MAP.put("Amazon Echo Dot in Kitchen", "amazonechodot"); SANITIZED_DEVICE_NAME_MAP.put("斯巴鲁 Starlink", "starlink"); SANITIZED_DEVICE_NAME_MAP.put("大黄蜂MyLink", "mylink"); SANITIZED_DEVICE_NAME_MAP.put("Dad's Fitbit Charge 3", "fitbitcharge3"); SANITIZED_DEVICE_NAME_MAP.put("Mike's new Galaxy Buds 2", "galaxybuds2"); SANITIZED_DEVICE_NAME_MAP.put("My third Ford F-150", "fordf150"); SANITIZED_DEVICE_NAME_MAP.put("BOSE QC_35 Noise Cancelling Headsets", "boseqc35"); SANITIZED_DEVICE_NAME_MAP.put("Fitbit versa 3 band", "fitbitversa3"); SANITIZED_DEVICE_NAME_MAP.put("vw atlas", "vwatlas"); SANITIZED_DEVICE_NAME_MAP.put("My volkswagen tiguan", "volkswagentiguan"); SANITIZED_DEVICE_NAME_MAP.put("SomeDevice1", ""); SANITIZED_DEVICE_NAME_MAP.put("Some Device-2", ""); SANITIZED_DEVICE_NAME_MAP.put("abcgfDG gdfg", ""); SANITIZED_DEVICE_NAME_MAP.put("Bluetooth headset", ""); } private TestableMetricsLogger mTestableMetricsLogger; @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); Loading @@ -68,16 +101,13 @@ public class MetricsLoggerTest { } @Override protected void scheduleDrains() { } protected void scheduleDrains() {} @Override protected void cancelPendingDrain() { } protected void cancelPendingDrain() {} @Override protected void statslogBluetoothDeviceNames( int metricId, String matchedString, String sha256) { protected void statslogBluetoothDeviceNames(int metricId, String matchedString) { mTestableDeviceNames.merge(matchedString, 1, Integer::sum); } } Loading @@ -88,8 +118,7 @@ public class MetricsLoggerTest { MetricsLogger.dumpProto(BluetoothLog.newBuilder()); mTestableMetricsLogger = new TestableMetricsLogger(); mTestableMetricsLogger.mBloomFilterInitialized = true; doReturn(null) .when(mMockAdapterService).registerReceiver(any(), any()); doReturn(null).when(mMockAdapterService).registerReceiver(any(), any()); } @After Loading @@ -99,9 +128,7 @@ public class MetricsLoggerTest { mTestableMetricsLogger.close(); } /** * Simple test to verify that profile connection event can be logged, dumped, and cleaned */ /** Simple test to verify that profile connection event can be logged, dumped, and cleaned */ @Test public void testLogProfileConnectionEvent() { MetricsLogger.logProfileConnectionEvent(ProfileId.AVRCP); Loading @@ -119,9 +146,7 @@ public class MetricsLoggerTest { Assert.assertEquals(0, metricsProtoAfterDump.getProfileConnectionStatsCount()); } /** * Test whether multiple profile's connection events can be logged interleaving */ /** Test whether multiple profile's connection events can be logged interleaving */ @Test public void testLogProfileConnectionEventMultipleProfile() { MetricsLogger.logProfileConnectionEvent(ProfileId.AVRCP); Loading @@ -134,11 +159,11 @@ public class MetricsLoggerTest { HashMap<ProfileId, ProfileConnectionStats> profileConnectionCountMap = getProfileUsageStatsMap(metricsProto.getProfileConnectionStatsList()); Assert.assertTrue(profileConnectionCountMap.containsKey(ProfileId.AVRCP)); Assert.assertEquals(2, profileConnectionCountMap.get(ProfileId.AVRCP).getNumTimesConnected()); Assert.assertEquals( 2, profileConnectionCountMap.get(ProfileId.AVRCP).getNumTimesConnected()); Assert.assertTrue(profileConnectionCountMap.containsKey(ProfileId.HEADSET)); Assert.assertEquals(1, profileConnectionCountMap.get(ProfileId.HEADSET).getNumTimesConnected()); Assert.assertEquals( 1, profileConnectionCountMap.get(ProfileId.HEADSET).getNumTimesConnected()); // Verify that MetricsLogger's internal state is cleared after a dump BluetoothLog.Builder metricsBuilderAfterDump = BluetoothLog.newBuilder(); MetricsLogger.dumpProto(metricsBuilderAfterDump); Loading @@ -153,9 +178,7 @@ public class MetricsLoggerTest { return profileUsageStatsMap; } /** * Test add counters and send them to statsd */ /** Test add counters and send them to statsd */ @Test public void testAddAndSendCountersNormalCases() { mTestableMetricsLogger.init(mMockAdapterService); Loading @@ -172,12 +195,9 @@ public class MetricsLoggerTest { mTestableMetricsLogger.cacheCount(2, 5); mTestableMetricsLogger.cacheCount(3, 1); mTestableMetricsLogger.drainBufferedCounters(); Assert.assertEquals( 3L, mTestableMetricsLogger.mTestableCounters.get(1).longValue()); Assert.assertEquals( 10L, mTestableMetricsLogger.mTestableCounters.get(2).longValue()); Assert.assertEquals( 1L, mTestableMetricsLogger.mTestableCounters.get(3).longValue()); Assert.assertEquals(3L, mTestableMetricsLogger.mTestableCounters.get(1).longValue()); Assert.assertEquals(10L, mTestableMetricsLogger.mTestableCounters.get(2).longValue()); Assert.assertEquals(1L, mTestableMetricsLogger.mTestableCounters.get(3).longValue()); } @Test Loading @@ -204,8 +224,7 @@ public class MetricsLoggerTest { mTestableMetricsLogger.cacheCount(2, Long.MAX_VALUE); mTestableMetricsLogger.close(); Assert.assertEquals( 1, mTestableMetricsLogger.mTestableCounters.get(1).longValue()); Assert.assertEquals(1, mTestableMetricsLogger.mTestableCounters.get(1).longValue()); Assert.assertEquals( Long.MAX_VALUE, mTestableMetricsLogger.mTestableCounters.get(2).longValue()); } Loading @@ -226,172 +245,26 @@ public class MetricsLoggerTest { } @Test public void testDeviceNameUploadingDeviceSet1() { initTestingBloomfitler(); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "a b c d e f g h pixel 7"); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "AirpoDspro"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("airpodspro").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "AirpoDs-pro"); Assert.assertEquals(2, mTestableMetricsLogger.mTestableDeviceNames.get("airpodspro").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "Someone's AirpoDs"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("airpods").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "Who's Pixel 7"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("pixel7").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "陈的pixel 7手机"); Assert.assertEquals(2, mTestableMetricsLogger.mTestableDeviceNames.get("pixel7").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(2, "pixel 7 pro"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "My Pixel 7 PRO"); Assert.assertEquals(2, mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "My Pixel 7 PRO"); Assert.assertEquals(3, mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "My Pixel 7 - PRO"); Assert.assertEquals(4, mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "My BMW X5"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("bmwx5").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "Jane Doe's Tesla Model--X"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("teslamodelx").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "TESLA of Jane DOE"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("tesla").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "SONY WH-1000XM noise cancelling headsets"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("sonywh1000xm").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "SONY WH-1000XM4 noise cancelling headsets"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("sonywh1000xm4").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "Amazon Echo Dot in Kitchen"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("amazonechodot").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "斯巴鲁 Starlink"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("starlink").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "大黄蜂MyLink"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("mylink").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "Dad's Fitbit Charge 3"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("fitbitcharge3").intValue()); mTestableMetricsLogger.mTestableDeviceNames.clear(); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, ""); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, " "); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "SomeDevice1"); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "Bluetooth headset"); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(3, "Some Device-2"); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(5, "abcgfDG gdfg"); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); public void testDeviceNameToSha() { initTestingBloomfilter(); for (Map.Entry<String, String> entry : SANITIZED_DEVICE_NAME_MAP.entrySet()) { String deviceName = entry.getKey(); String sha256 = MetricsLogger.getSha256String(entry.getValue()); Assert.assertEquals( sha256, mTestableMetricsLogger.logAllowlistedDeviceNameHash(1, deviceName, true)); } } @Test public void testDeviceNameUploadingDeviceSet2() { initTestingBloomfitler(); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "Galaxy Buds pro"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("galaxybudspro").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "Mike's new Galaxy Buds 2"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("galaxybuds2").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(877, "My third Ford F-150"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("fordf150").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "BOSE QC_35 Noise Cancelling Headsets"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("boseqc35").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "BOSE Quiet Comfort 35 Headsets"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("bosequietcomfort35").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "Fitbit versa 3 band"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("fitbitversa3").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "vw atlas"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("vwatlas").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "My volkswagen tiguan"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("volkswagentiguan").intValue()); mTestableMetricsLogger.mTestableDeviceNames.clear(); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, ""); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, " "); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "weirddevice"); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "" + "My BOSE Quiet Comfort 35 Noise Cancelling Headsets"); // Too long, won't process Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); public void uploadEmptyDeviceName() { initTestingBloomfilter(); Assert.assertEquals("", mTestableMetricsLogger.logAllowlistedDeviceNameHash(1, "", true)); } private void initTestingBloomfitler() { byte[] bloomfilterData = DeviceBloomfilterGenerator.hexStringToByteArray( private void initTestingBloomfilter() { byte[] bloomfilterData = DeviceBloomfilterGenerator.hexStringToByteArray( DeviceBloomfilterGenerator.BLOOM_FILTER_DEFAULT); try { mTestableMetricsLogger.setBloomfilter( Loading Loading
android/app/src/com/android/bluetooth/btservice/AdapterProperties.java +2 −1 Original line number Diff line number Diff line Loading @@ -763,7 +763,8 @@ class AdapterProperties { if (state == BluetoothProfile.STATE_CONNECTING) { BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_DEVICE_NAME_REPORTED, metricId, device.getName()); MetricsLogger.getInstance().logSanitizedBluetoothDeviceName(metricId, device.getName()); MetricsLogger.getInstance() .logAllowlistedDeviceNameHash(metricId, device.getName(), true); } BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_CONNECTION_STATE_CHANGED, state, 0 /* deprecated */, profile, mService.obfuscateAddress(device), Loading
android/app/src/com/android/bluetooth/btservice/AdapterService.java +0 −1 Original line number Diff line number Diff line Loading @@ -941,7 +941,6 @@ public class AdapterService extends Service { long socketCreationTimeNanos, boolean isSerialPort, int appUid) { int metricId = getMetricId(device); long currentTime = System.nanoTime(); long endToEndLatencyNanos = currentTime - socketCreationTimeNanos; Loading
android/app/src/com/android/bluetooth/btservice/MetricsLogger.java +72 −35 Original line number Diff line number Diff line Loading @@ -19,21 +19,23 @@ import static com.android.bluetooth.BtRestrictedStatsLog.RESTRICTED_BLUETOOTH_DE import android.app.AlarmManager; import android.content.Context; import android.os.Build; import android.os.SystemClock; import android.util.Log; import androidx.annotation.RequiresApi; import com.android.bluetooth.BluetoothMetricsProto.BluetoothLog; import com.android.bluetooth.BluetoothMetricsProto.ProfileConnectionStats; import com.android.bluetooth.BluetoothMetricsProto.ProfileId; import com.android.bluetooth.BluetoothStatsLog; import com.android.bluetooth.BtRestrictedStatsLog; import com.android.bluetooth.Utils; import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; import com.google.common.annotations.VisibleForTesting; import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; import com.google.common.hash.Hashing; import java.io.ByteArrayInputStream; import java.io.File; Loading @@ -42,7 +44,9 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * Class of Bluetooth Metrics Loading Loading @@ -269,11 +273,10 @@ public class MetricsLogger { mAlarmManager.cancel(mOnAlarmListener); } protected boolean logSanitizedBluetoothDeviceName(int metricId, String deviceName) { if (!mBloomFilterInitialized || deviceName == null) { return false; private List<String> getWordBreakdownList(String deviceName) { if (deviceName == null) { return new ArrayList<String>(); } // remove more than one spaces in a row deviceName = deviceName.trim().replaceAll(" +", " "); // remove non alphanumeric characters and spaces, and transform to lower cases. Loading @@ -282,52 +285,86 @@ public class MetricsLogger { if (words.length > MAX_WORDS_ALLOWED_IN_DEVICE_NAME) { // Validity checking here to avoid excessively long sequences return false; return new ArrayList<String>(); } // find the longest matched substring String matchedString = ""; byte[] matchedSha256 = null; // collect the word breakdown in an arraylist ArrayList<String> wordBreakdownList = new ArrayList<String>(); for (int start = 0; start < words.length; start++) { String toBeMatched = ""; StringBuilder deviceNameCombination = new StringBuilder(); for (int end = start; end < words.length; end++) { toBeMatched += words[end]; // TODO(b/280868296): Refactor to log even if bloom filter isn't initialized. if (SdkLevel.isAtLeastU()) { BtRestrictedStatsLog.write(RESTRICTED_BLUETOOTH_DEVICE_NAME_REPORTED, toBeMatched); deviceNameCombination.append(words[end]); wordBreakdownList.add(deviceNameCombination.toString()); } byte[] sha256 = getSha256(toBeMatched); if (sha256 == null) { continue; } if (mBloomFilter.mightContain(sha256) && toBeMatched.length() > matchedString.length()) { matchedString = toBeMatched; matchedSha256 = sha256; return wordBreakdownList; } @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) private void uploadRestrictedBluetothDeviceName(List<String> wordBreakdownList) { for (String word : wordBreakdownList) { BtRestrictedStatsLog.write(RESTRICTED_BLUETOOTH_DEVICE_NAME_REPORTED, word); } } // upload the sha256 of the longest matched string. if (matchedSha256 == null) { return false; private String getMatchedString(List<String> wordBreakdownList) { if (!mBloomFilterInitialized || wordBreakdownList.isEmpty()) { return ""; } statslogBluetoothDeviceNames( metricId, matchedString, Hashing.sha256().hashString(matchedString, StandardCharsets.UTF_8).toString()); return true; String matchedString = ""; for (String word : wordBreakdownList) { byte[] sha256 = getSha256(word); if (mBloomFilter.mightContain(sha256) && word.length() > matchedString.length()) { matchedString = word; } } return matchedString; } protected void statslogBluetoothDeviceNames(int metricId, String matchedString, String sha256) { protected String getAllowlistedDeviceNameHash(String deviceName) { List<String> wordBreakdownList = getWordBreakdownList(deviceName); String matchedString = getMatchedString(wordBreakdownList); return getSha256String(matchedString); } protected String logAllowlistedDeviceNameHash( int metricId, String deviceName, boolean logRestrictedNames) { List<String> wordBreakdownList = getWordBreakdownList(deviceName); String matchedString = getMatchedString(wordBreakdownList); if (logRestrictedNames) { // Log the restricted bluetooth device name if (SdkLevel.isAtLeastU()) { uploadRestrictedBluetothDeviceName(wordBreakdownList); } } if (!matchedString.isEmpty()) { statslogBluetoothDeviceNames(metricId, matchedString); } return getSha256String(matchedString); } protected void statslogBluetoothDeviceNames(int metricId, String matchedString) { String sha256 = getSha256String(matchedString); Log.d(TAG, "Uploading sha256 hash of matched bluetooth device name: " + sha256); BluetoothStatsLog.write( BluetoothStatsLog.BLUETOOTH_HASHED_DEVICE_NAME_REPORTED, metricId, sha256); } protected static String getSha256String(String name) { if (name.isEmpty()) { return ""; } StringBuilder hexString = new StringBuilder(); byte[] hashBytes = getSha256(name); for (byte b : hashBytes) { hexString.append(String.format("%02x", b)); } return hexString.toString(); } protected static byte[] getSha256(String name) { MessageDigest digest = null; try { Loading
android/app/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java +69 −196 Original line number Diff line number Diff line Loading @@ -42,15 +42,48 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Unit tests for {@link MetricsLogger} */ /** Unit tests for {@link MetricsLogger} */ @MediumTest @RunWith(AndroidJUnit4.class) public class MetricsLoggerTest { private static final String TEST_BLOOMFILTER_NAME = "TestBloomfilter"; private static final HashMap<String, String> SANITIZED_DEVICE_NAME_MAP = new HashMap<>(); static { SANITIZED_DEVICE_NAME_MAP.put("AirpoDspro", "airpodspro"); SANITIZED_DEVICE_NAME_MAP.put("AirpoDs-pro", "airpodspro"); SANITIZED_DEVICE_NAME_MAP.put("Someone's AirpoDs", "airpods"); SANITIZED_DEVICE_NAME_MAP.put("Galaxy Buds pro", "galaxybudspro"); SANITIZED_DEVICE_NAME_MAP.put("Someone's AirpoDs", "airpods"); SANITIZED_DEVICE_NAME_MAP.put("Who's Pixel 7", "pixel7"); SANITIZED_DEVICE_NAME_MAP.put("陈的pixel 7手机", "pixel7"); SANITIZED_DEVICE_NAME_MAP.put("pixel 7 pro", "pixel7pro"); SANITIZED_DEVICE_NAME_MAP.put("My Pixel 7 Pro", "pixel7pro"); SANITIZED_DEVICE_NAME_MAP.put("My Pixel 7 PRO", "pixel7pro"); SANITIZED_DEVICE_NAME_MAP.put("My Pixel 7 - PRO", "pixel7pro"); SANITIZED_DEVICE_NAME_MAP.put("My BMW X5", "bmwx5"); SANITIZED_DEVICE_NAME_MAP.put("Jane Doe's Tesla Model--X", "teslamodelx"); SANITIZED_DEVICE_NAME_MAP.put("TESLA of Jane DOE", "tesla"); SANITIZED_DEVICE_NAME_MAP.put("SONY WH-1000XM noise cancelling headsets", "sonywh1000xm"); SANITIZED_DEVICE_NAME_MAP.put("Amazon Echo Dot in Kitchen", "amazonechodot"); SANITIZED_DEVICE_NAME_MAP.put("斯巴鲁 Starlink", "starlink"); SANITIZED_DEVICE_NAME_MAP.put("大黄蜂MyLink", "mylink"); SANITIZED_DEVICE_NAME_MAP.put("Dad's Fitbit Charge 3", "fitbitcharge3"); SANITIZED_DEVICE_NAME_MAP.put("Mike's new Galaxy Buds 2", "galaxybuds2"); SANITIZED_DEVICE_NAME_MAP.put("My third Ford F-150", "fordf150"); SANITIZED_DEVICE_NAME_MAP.put("BOSE QC_35 Noise Cancelling Headsets", "boseqc35"); SANITIZED_DEVICE_NAME_MAP.put("Fitbit versa 3 band", "fitbitversa3"); SANITIZED_DEVICE_NAME_MAP.put("vw atlas", "vwatlas"); SANITIZED_DEVICE_NAME_MAP.put("My volkswagen tiguan", "volkswagentiguan"); SANITIZED_DEVICE_NAME_MAP.put("SomeDevice1", ""); SANITIZED_DEVICE_NAME_MAP.put("Some Device-2", ""); SANITIZED_DEVICE_NAME_MAP.put("abcgfDG gdfg", ""); SANITIZED_DEVICE_NAME_MAP.put("Bluetooth headset", ""); } private TestableMetricsLogger mTestableMetricsLogger; @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); Loading @@ -68,16 +101,13 @@ public class MetricsLoggerTest { } @Override protected void scheduleDrains() { } protected void scheduleDrains() {} @Override protected void cancelPendingDrain() { } protected void cancelPendingDrain() {} @Override protected void statslogBluetoothDeviceNames( int metricId, String matchedString, String sha256) { protected void statslogBluetoothDeviceNames(int metricId, String matchedString) { mTestableDeviceNames.merge(matchedString, 1, Integer::sum); } } Loading @@ -88,8 +118,7 @@ public class MetricsLoggerTest { MetricsLogger.dumpProto(BluetoothLog.newBuilder()); mTestableMetricsLogger = new TestableMetricsLogger(); mTestableMetricsLogger.mBloomFilterInitialized = true; doReturn(null) .when(mMockAdapterService).registerReceiver(any(), any()); doReturn(null).when(mMockAdapterService).registerReceiver(any(), any()); } @After Loading @@ -99,9 +128,7 @@ public class MetricsLoggerTest { mTestableMetricsLogger.close(); } /** * Simple test to verify that profile connection event can be logged, dumped, and cleaned */ /** Simple test to verify that profile connection event can be logged, dumped, and cleaned */ @Test public void testLogProfileConnectionEvent() { MetricsLogger.logProfileConnectionEvent(ProfileId.AVRCP); Loading @@ -119,9 +146,7 @@ public class MetricsLoggerTest { Assert.assertEquals(0, metricsProtoAfterDump.getProfileConnectionStatsCount()); } /** * Test whether multiple profile's connection events can be logged interleaving */ /** Test whether multiple profile's connection events can be logged interleaving */ @Test public void testLogProfileConnectionEventMultipleProfile() { MetricsLogger.logProfileConnectionEvent(ProfileId.AVRCP); Loading @@ -134,11 +159,11 @@ public class MetricsLoggerTest { HashMap<ProfileId, ProfileConnectionStats> profileConnectionCountMap = getProfileUsageStatsMap(metricsProto.getProfileConnectionStatsList()); Assert.assertTrue(profileConnectionCountMap.containsKey(ProfileId.AVRCP)); Assert.assertEquals(2, profileConnectionCountMap.get(ProfileId.AVRCP).getNumTimesConnected()); Assert.assertEquals( 2, profileConnectionCountMap.get(ProfileId.AVRCP).getNumTimesConnected()); Assert.assertTrue(profileConnectionCountMap.containsKey(ProfileId.HEADSET)); Assert.assertEquals(1, profileConnectionCountMap.get(ProfileId.HEADSET).getNumTimesConnected()); Assert.assertEquals( 1, profileConnectionCountMap.get(ProfileId.HEADSET).getNumTimesConnected()); // Verify that MetricsLogger's internal state is cleared after a dump BluetoothLog.Builder metricsBuilderAfterDump = BluetoothLog.newBuilder(); MetricsLogger.dumpProto(metricsBuilderAfterDump); Loading @@ -153,9 +178,7 @@ public class MetricsLoggerTest { return profileUsageStatsMap; } /** * Test add counters and send them to statsd */ /** Test add counters and send them to statsd */ @Test public void testAddAndSendCountersNormalCases() { mTestableMetricsLogger.init(mMockAdapterService); Loading @@ -172,12 +195,9 @@ public class MetricsLoggerTest { mTestableMetricsLogger.cacheCount(2, 5); mTestableMetricsLogger.cacheCount(3, 1); mTestableMetricsLogger.drainBufferedCounters(); Assert.assertEquals( 3L, mTestableMetricsLogger.mTestableCounters.get(1).longValue()); Assert.assertEquals( 10L, mTestableMetricsLogger.mTestableCounters.get(2).longValue()); Assert.assertEquals( 1L, mTestableMetricsLogger.mTestableCounters.get(3).longValue()); Assert.assertEquals(3L, mTestableMetricsLogger.mTestableCounters.get(1).longValue()); Assert.assertEquals(10L, mTestableMetricsLogger.mTestableCounters.get(2).longValue()); Assert.assertEquals(1L, mTestableMetricsLogger.mTestableCounters.get(3).longValue()); } @Test Loading @@ -204,8 +224,7 @@ public class MetricsLoggerTest { mTestableMetricsLogger.cacheCount(2, Long.MAX_VALUE); mTestableMetricsLogger.close(); Assert.assertEquals( 1, mTestableMetricsLogger.mTestableCounters.get(1).longValue()); Assert.assertEquals(1, mTestableMetricsLogger.mTestableCounters.get(1).longValue()); Assert.assertEquals( Long.MAX_VALUE, mTestableMetricsLogger.mTestableCounters.get(2).longValue()); } Loading @@ -226,172 +245,26 @@ public class MetricsLoggerTest { } @Test public void testDeviceNameUploadingDeviceSet1() { initTestingBloomfitler(); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "a b c d e f g h pixel 7"); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "AirpoDspro"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("airpodspro").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "AirpoDs-pro"); Assert.assertEquals(2, mTestableMetricsLogger.mTestableDeviceNames.get("airpodspro").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "Someone's AirpoDs"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("airpods").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "Who's Pixel 7"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("pixel7").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "陈的pixel 7手机"); Assert.assertEquals(2, mTestableMetricsLogger.mTestableDeviceNames.get("pixel7").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(2, "pixel 7 pro"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "My Pixel 7 PRO"); Assert.assertEquals(2, mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "My Pixel 7 PRO"); Assert.assertEquals(3, mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "My Pixel 7 - PRO"); Assert.assertEquals(4, mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "My BMW X5"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("bmwx5").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "Jane Doe's Tesla Model--X"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("teslamodelx").intValue()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "TESLA of Jane DOE"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("tesla").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "SONY WH-1000XM noise cancelling headsets"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("sonywh1000xm").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "SONY WH-1000XM4 noise cancelling headsets"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("sonywh1000xm4").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "Amazon Echo Dot in Kitchen"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("amazonechodot").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "斯巴鲁 Starlink"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("starlink").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "大黄蜂MyLink"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("mylink").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "Dad's Fitbit Charge 3"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("fitbitcharge3").intValue()); mTestableMetricsLogger.mTestableDeviceNames.clear(); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, ""); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, " "); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "SomeDevice1"); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "Bluetooth headset"); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(3, "Some Device-2"); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(5, "abcgfDG gdfg"); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); public void testDeviceNameToSha() { initTestingBloomfilter(); for (Map.Entry<String, String> entry : SANITIZED_DEVICE_NAME_MAP.entrySet()) { String deviceName = entry.getKey(); String sha256 = MetricsLogger.getSha256String(entry.getValue()); Assert.assertEquals( sha256, mTestableMetricsLogger.logAllowlistedDeviceNameHash(1, deviceName, true)); } } @Test public void testDeviceNameUploadingDeviceSet2() { initTestingBloomfitler(); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "Galaxy Buds pro"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("galaxybudspro").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "Mike's new Galaxy Buds 2"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("galaxybuds2").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(877, "My third Ford F-150"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("fordf150").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "BOSE QC_35 Noise Cancelling Headsets"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("boseqc35").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "BOSE Quiet Comfort 35 Headsets"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("bosequietcomfort35").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "Fitbit versa 3 band"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("fitbitversa3").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "vw atlas"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("vwatlas").intValue()); mTestableMetricsLogger .logSanitizedBluetoothDeviceName(1, "My volkswagen tiguan"); Assert.assertEquals(1, mTestableMetricsLogger.mTestableDeviceNames.get("volkswagentiguan").intValue()); mTestableMetricsLogger.mTestableDeviceNames.clear(); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, ""); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, " "); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "weirddevice"); Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); mTestableMetricsLogger.logSanitizedBluetoothDeviceName(1, "" + "My BOSE Quiet Comfort 35 Noise Cancelling Headsets"); // Too long, won't process Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty()); public void uploadEmptyDeviceName() { initTestingBloomfilter(); Assert.assertEquals("", mTestableMetricsLogger.logAllowlistedDeviceNameHash(1, "", true)); } private void initTestingBloomfitler() { byte[] bloomfilterData = DeviceBloomfilterGenerator.hexStringToByteArray( private void initTestingBloomfilter() { byte[] bloomfilterData = DeviceBloomfilterGenerator.hexStringToByteArray( DeviceBloomfilterGenerator.BLOOM_FILTER_DEFAULT); try { mTestableMetricsLogger.setBloomfilter( Loading