Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 341c1cf5 authored by Palash Ahuja's avatar Palash Ahuja Committed by Automerger Merge Worker
Browse files

Merge "[BluetoothMetrics] RDI Changes inside RFCOMM proto" into main am: b84fc7bc am: c656fbd9

parents 1ff7d5fa c656fbd9
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -942,6 +942,7 @@ public class AdapterService extends Service {
        int metricId = getMetricId(device);
        long currentTime = System.nanoTime();
        long endToEndLatencyNanos = currentTime - socketCreationTimeNanos;
        byte[] remoteDeviceInfoBytes = MetricsLogger.getInstance().getRemoteDeviceInfoProto(device);
        BluetoothStatsLog.write(
                BluetoothStatsLog.BLUETOOTH_RFCOMM_CONNECTION_ATTEMPTED,
                metricId,
@@ -952,7 +953,7 @@ public class AdapterService extends Service {
                resultCode,
                isSerialPort,
                appUid,
                new byte[0]);
                remoteDeviceInfoBytes);
    }

    @RequiresPermission(
+103 −35
Original line number Diff line number Diff line
@@ -18,14 +18,17 @@ package com.android.bluetooth.btservice;
import static com.android.bluetooth.BtRestrictedStatsLog.RESTRICTED_BLUETOOTH_DEVICE_NAME_REPORTED;

import android.app.AlarmManager;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.os.Build;
import android.os.SystemClock;
import android.util.Log;
import android.util.proto.ProtoOutputStream;

import androidx.annotation.RequiresApi;

import com.android.bluetooth.BluetoothMetricsProto.BluetoothLog;
import com.android.bluetooth.BluetoothMetricsProto.BluetoothRemoteDeviceInformation;
import com.android.bluetooth.BluetoothMetricsProto.ProfileConnectionStats;
import com.android.bluetooth.BluetoothMetricsProto.ProfileId;
import com.android.bluetooth.BluetoothStatsLog;
@@ -34,6 +37,7 @@ import com.android.bluetooth.Utils;
import com.android.modules.utils.build.SdkLevel;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ascii;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

@@ -48,9 +52,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * Class of Bluetooth Metrics
 */
/** Class of Bluetooth Metrics */
public class MetricsLogger {
    private static final String TAG = "BluetoothMetricsLogger";
    private static final String BLOOMFILTER_PATH = "/data/misc/bluetooth";
@@ -68,11 +70,12 @@ public class MetricsLogger {
    private Context mContext = null;
    private AlarmManager mAlarmManager = null;
    private boolean mInitialized = false;
    static final private Object mLock = new Object();
    private static final Object sLock = new Object();
    private BloomFilter<byte[]> mBloomFilter = null;
    protected boolean mBloomFilterInitialized = false;

    private AlarmManager.OnAlarmListener mOnAlarmListener = new AlarmManager.OnAlarmListener () {
    private AlarmManager.OnAlarmListener mOnAlarmListener =
            new AlarmManager.OnAlarmListener() {
                @Override
                public void onAlarm() {
                    drainBufferedCounters();
@@ -82,7 +85,7 @@ public class MetricsLogger {

    public static MetricsLogger getInstance() {
        if (sInstance == null) {
            synchronized (mLock) {
            synchronized (sLock) {
                if (sInstance == null) {
                    sInstance = new MetricsLogger();
                }
@@ -99,7 +102,7 @@ public class MetricsLogger {
    @VisibleForTesting
    public static void setInstanceForTesting(MetricsLogger instance) {
        Utils.enforceInstrumentationTestMode();
        synchronized (mLock) {
        synchronized (sLock) {
            Log.d(TAG, "setInstanceForTesting(), set to " + instance);
            sInstance = instance;
        }
@@ -122,11 +125,14 @@ public class MetricsLogger {
            mBloomFilterInitialized = true;
        } catch (IOException e1) {
            Log.w(TAG, "MetricsLogger can't read the BloomFilter file.");
            byte[] bloomfilterData = DeviceBloomfilterGenerator.hexStringToByteArray(
            byte[] bloomfilterData =
                    DeviceBloomfilterGenerator.hexStringToByteArray(
                            DeviceBloomfilterGenerator.BLOOM_FILTER_DEFAULT);
            try {
                mBloomFilter = BloomFilter.readFrom(
                        new ByteArrayInputStream(bloomfilterData), Funnels.byteArrayFunnel());
                mBloomFilter =
                        BloomFilter.readFrom(
                                new ByteArrayInputStream(bloomfilterData),
                                Funnels.byteArrayFunnel());
                mBloomFilterInitialized = true;
                Log.i(TAG, "The default bloomfilter is used");
                return true;
@@ -169,7 +175,7 @@ public class MetricsLogger {
        }
        long total = 0;

        synchronized (mLock) {
        synchronized (sLock) {
            if (mCounters.containsKey(key)) {
                total = mCounters.get(key);
            }
@@ -184,9 +190,9 @@ public class MetricsLogger {
    }

    /**
     * Log profile connection event by incrementing an internal counter for that profile.
     * This log persists over adapter enable/disable and only get cleared when metrics are
     * dumped or when Bluetooth process is killed.
     * Log profile connection event by incrementing an internal counter for that profile. This log
     * persists over adapter enable/disable and only get cleared when metrics are dumped or when
     * Bluetooth process is killed.
     *
     * @param profileId Bluetooth profile that is connected at this event
     */
@@ -197,15 +203,15 @@ public class MetricsLogger {
    }

    /**
     * Dump collected metrics into proto using a builder.
     * Clean up internal data after the dump.
     * Dump collected metrics into proto using a builder. Clean up internal data after the dump.
     *
     * @param metricsBuilder proto builder for {@link BluetoothLog}
     */
    public static void dumpProto(BluetoothLog.Builder metricsBuilder) {
        synchronized (sProfileConnectionCounts) {
            sProfileConnectionCounts.forEach(
                    (key, value) -> metricsBuilder.addProfileConnectionStats(
                    (key, value) ->
                            metricsBuilder.addProfileConnectionStats(
                                    ProfileConnectionStats.newBuilder()
                                            .setProfileId(key)
                                            .setNumTimesConnected(value)
@@ -236,14 +242,13 @@ public class MetricsLogger {
            Log.w(TAG, "count is not larger than 0. count: " + count + " key: " + key);
            return false;
        }
        BluetoothStatsLog.write(
                BluetoothStatsLog.BLUETOOTH_CODE_PATH_COUNTER, key, count);
        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_CODE_PATH_COUNTER, key, count);
        return true;
    }

    protected void drainBufferedCounters() {
        Log.i(TAG, "drainBufferedCounters().");
        synchronized (mLock) {
        synchronized (sLock) {
            // send mCounters to statsd
            for (int key : mCounters.keySet()) {
                count(key, mCounters.get(key));
@@ -265,10 +270,75 @@ public class MetricsLogger {
        mBloomFilterInitialized = false;
        return true;
    }

    protected void cancelPendingDrain() {
        mAlarmManager.cancel(mOnAlarmListener);
    }

    private void writeFieldIfNotNull(
            ProtoOutputStream proto,
            long fieldType,
            long fieldCount,
            long fieldNumber,
            Object value) {
        if (value != null) {
            try {
                if (fieldType == ProtoOutputStream.FIELD_TYPE_STRING) {
                    proto.write(fieldType | fieldCount | fieldNumber, value.toString());
                }

                if (fieldType == ProtoOutputStream.FIELD_TYPE_INT32) {
                    proto.write(fieldType | fieldCount | fieldNumber, (Integer) value);
                }
            } catch (Exception e) {
                Log.e(TAG, "Error writing field " + fieldNumber + ": " + e.getMessage());
            }
        }
    }

    /**
     * Retrieves a byte array containing serialized remote device information for the specified
     * BluetoothDevice. This data can be used for remote device identification and logging.
     *
     * @param device The BluetoothDevice for which to retrieve device information.
     * @return A byte array containing the serialized remote device information.
     */
    public byte[] getRemoteDeviceInfoProto(BluetoothDevice device) {
        ProtoOutputStream proto = new ProtoOutputStream();

        // write Allowlisted Device Name Hash
        writeFieldIfNotNull(
                proto,
                ProtoOutputStream.FIELD_TYPE_STRING,
                ProtoOutputStream.FIELD_COUNT_SINGLE,
                BluetoothRemoteDeviceInformation.ALLOWLISTED_DEVICE_NAME_HASH_FIELD_NUMBER,
                getAllowlistedDeviceNameHash(device.getName()));

        // write COD
        writeFieldIfNotNull(
                proto,
                ProtoOutputStream.FIELD_TYPE_INT32,
                ProtoOutputStream.FIELD_COUNT_SINGLE,
                BluetoothRemoteDeviceInformation.CLASS_OF_DEVICE_FIELD_NUMBER,
                device.getBluetoothClass() != null
                        ? device.getBluetoothClass().getClassOfDevice()
                        : null);

        // write OUI
        writeFieldIfNotNull(
                proto,
                ProtoOutputStream.FIELD_TYPE_INT32,
                ProtoOutputStream.FIELD_COUNT_SINGLE,
                BluetoothRemoteDeviceInformation.OUI_FIELD_NUMBER,
                getOui(device));

        return proto.getBytes();
    }

    private int getOui(BluetoothDevice device) {
        return Integer.parseInt(device.getAddress().replace(":", "").substring(0, 6), 16);
    }

    private List<String> getWordBreakdownList(String deviceName) {
        if (deviceName == null) {
            return new ArrayList<String>();
@@ -276,8 +346,7 @@ public class MetricsLogger {
        // remove more than one spaces in a row
        deviceName = deviceName.trim().replaceAll(" +", " ");
        // remove non alphanumeric characters and spaces, and transform to lower cases.
        String[] words = deviceName.replaceAll(
                "[^a-zA-Z0-9 ]", "").toLowerCase().split(" ");
        String[] words = Ascii.toLowerCase(deviceName.replaceAll("[^a-zA-Z0-9 ]", "")).split(" ");

        if (words.length > MAX_WORDS_ALLOWED_IN_DEVICE_NAME) {
            // Validity checking here to avoid excessively long sequences
@@ -343,8 +412,7 @@ public class MetricsLogger {

    protected void statslogBluetoothDeviceNames(int metricId, String matchedString) {
        String sha256 = getSha256String(matchedString);
        Log.d(TAG,
                "Uploading sha256 hash of matched bluetooth device name: " + sha256);
        Log.d(TAG, "Uploading sha256 hash of matched bluetooth device name: " + sha256);
        BluetoothStatsLog.write(
                BluetoothStatsLog.BLUETOOTH_HASHED_DEVICE_NAME_REPORTED, metricId, sha256);
    }
+26 −2
Original line number Diff line number Diff line
@@ -18,15 +18,21 @@ package com.android.bluetooth.btservice;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;

import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.BluetoothMetricsProto.BluetoothLog;
import com.android.bluetooth.BluetoothMetricsProto.BluetoothRemoteDeviceInformation;
import com.android.bluetooth.BluetoothMetricsProto.ProfileConnectionStats;
import com.android.bluetooth.BluetoothMetricsProto.ProfileId;
import com.android.bluetooth.TestUtils;

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import com.google.protobuf.InvalidProtocolBufferException;

import org.junit.After;
import org.junit.Assert;
@@ -87,8 +93,7 @@ public class MetricsLoggerTest {
    private TestableMetricsLogger mTestableMetricsLogger;
    @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Mock
    private AdapterService mMockAdapterService;
    @Mock private AdapterService mMockAdapterService;

    public class TestableMetricsLogger extends MetricsLogger {
        public HashMap<Integer, Long> mTestableCounters = new HashMap<>();
@@ -256,6 +261,25 @@ public class MetricsLoggerTest {
        }
    }

    @Test
    public void testOuiFromBluetoothDevice() {
        BluetoothDevice bluetoothDevice =
                TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);

        byte[] remoteDeviceInformationBytes =
                MetricsLogger.getInstance().getRemoteDeviceInfoProto(bluetoothDevice);

        try {
            BluetoothRemoteDeviceInformation bluetoothRemoteDeviceInformation =
                    BluetoothRemoteDeviceInformation.parseFrom(remoteDeviceInformationBytes);
            int oui = (0 << 16) | (1 << 8) | 2; // OUI from the above mac address
            Assert.assertEquals(bluetoothRemoteDeviceInformation.getOui(), oui);

        } catch (InvalidProtocolBufferException e) {
            Assert.assertNull(e.getMessage()); // test failure here
        }
    }

    @Test
    public void uploadEmptyDeviceName() {
        initTestingBloomfilter();
+20 −1
Original line number Diff line number Diff line
@@ -294,3 +294,22 @@ message HeadsetProfileConnectionStats {
  // Number of times this type of headset profile is connected
  optional int32 num_times_connected = 2;
}

/**
* Encapsulates Remote Device Information. Needs to be kept consistent with
* BluetoothRemoteDeviceInformation
* in frameworks/proto_logging/stats/atoms/bluetooth/bluetooth_extension_atoms.proto
*
* Logged from:
*    packages/modules/Bluetooth
*/
message BluetoothRemoteDeviceInformation {
  // SHA256 hashed Bluetooth device name.
  optional string allowlisted_device_name_hash = 1;

  // Class of Device
  optional int32 class_of_device = 2;

  // The first three bytes of MAC address
  optional int32 oui = 3;
}