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

Commit e6977064 authored by Vance Yu's avatar Vance Yu Committed by Jayden Kim
Browse files

Add Bluetooth LE advertising logger

There is no useful information for a BLE advertiser in bugreports.
We faced some related issues so add a logger to record information for investigating issues.

Bug: 220646951
Bug: 242230899
Tag: #stability
Test: adb shell dumpsys bluetooth_manager
Change-Id: Ib1937c8d18f155c5daffc97138dcdc149cc3460e
parent 6566877c
Loading
Loading
Loading
Loading
+40 −7
Original line number Original line Diff line number Diff line
@@ -157,10 +157,18 @@ class AdvertiseManager {
        if (status == 0) {
        if (status == 0) {
            entry.setValue(
            entry.setValue(
                    new AdvertiserInfo(advertiserId, entry.getValue().deathRecipient, callback));
                    new AdvertiserInfo(advertiserId, entry.getValue().deathRecipient, callback));

            mService.mAdvertiserMap.setAdvertiserIdByRegId(regId, advertiserId);
        } else {
        } else {
            IBinder binder = entry.getKey();
            IBinder binder = entry.getKey();
            binder.unlinkToDeath(entry.getValue().deathRecipient, 0);
            binder.unlinkToDeath(entry.getValue().deathRecipient, 0);
            mAdvertisers.remove(binder);
            mAdvertisers.remove(binder);

            AppAdvertiseStats stats = mService.mAdvertiserMap.getAppAdvertiseStatsById(regId);
            if (stats != null) {
                stats.recordAdvertiseStop();
            }
            mService.mAdvertiserMap.removeAppAdvertiseStats(regId);
        }
        }


        callback.onAdvertisingSetStarted(advertiserId, txPower, status);
        callback.onAdvertisingSetStarted(advertiserId, txPower, status);
@@ -209,8 +217,13 @@ class AdvertiseManager {
            if (DBG) {
            if (DBG) {
                Log.d(TAG, "startAdvertisingSet() - reg_id=" + cbId + ", callback: " + binder);
                Log.d(TAG, "startAdvertisingSet() - reg_id=" + cbId + ", callback: " + binder);
            }
            }
        startAdvertisingSetNative(parameters, advDataBytes, scanResponseBytes, periodicParameters,

                periodicDataBytes, duration, maxExtAdvEvents, cbId);
            mService.mAdvertiserMap.add(cbId, callback, mService);
            mService.mAdvertiserMap.recordAdvertiseStart(cbId, parameters, advertiseData,
                    scanResponse, periodicParameters, periodicData, duration, maxExtAdvEvents);

            startAdvertisingSetNative(parameters, advDataBytes, scanResponseBytes,
                    periodicParameters, periodicDataBytes, duration, maxExtAdvEvents, cbId);


        } catch (IllegalArgumentException e) {
        } catch (IllegalArgumentException e) {
            try {
            try {
@@ -276,6 +289,8 @@ class AdvertiseManager {
        } catch (RemoteException e) {
        } catch (RemoteException e) {
            Log.i(TAG, "error sending onAdvertisingSetStopped callback", e);
            Log.i(TAG, "error sending onAdvertisingSetStopped callback", e);
        }
        }

        mService.mAdvertiserMap.recordAdvertiseStop(advertiserId);
    }
    }


    void enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents) {
    void enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents) {
@@ -285,6 +300,9 @@ class AdvertiseManager {
            return;
            return;
        }
        }
        enableAdvertisingSetNative(advertiserId, enable, duration, maxExtAdvEvents);
        enableAdvertisingSetNative(advertiserId, enable, duration, maxExtAdvEvents);

        mService.mAdvertiserMap.enableAdvertisingSet(advertiserId,
                enable, duration, maxExtAdvEvents);
    }
    }


    void setAdvertisingData(int advertiserId, AdvertiseData data) {
    void setAdvertisingData(int advertiserId, AdvertiseData data) {
@@ -297,6 +315,8 @@ class AdvertiseManager {
        try {
        try {
            setAdvertisingDataNative(advertiserId,
            setAdvertisingDataNative(advertiserId,
                    AdvertiseHelper.advertiseDataToBytes(data, deviceName));
                    AdvertiseHelper.advertiseDataToBytes(data, deviceName));

            mService.mAdvertiserMap.setAdvertisingData(advertiserId, data);
        } catch (IllegalArgumentException e) {
        } catch (IllegalArgumentException e) {
            try {
            try {
                onAdvertisingDataSet(advertiserId,
                onAdvertisingDataSet(advertiserId,
@@ -317,6 +337,8 @@ class AdvertiseManager {
        try {
        try {
            setScanResponseDataNative(advertiserId,
            setScanResponseDataNative(advertiserId,
                    AdvertiseHelper.advertiseDataToBytes(data, deviceName));
                    AdvertiseHelper.advertiseDataToBytes(data, deviceName));

            mService.mAdvertiserMap.setScanResponseData(advertiserId, data);
        } catch (IllegalArgumentException e) {
        } catch (IllegalArgumentException e) {
            try {
            try {
                onScanResponseDataSet(advertiserId,
                onScanResponseDataSet(advertiserId,
@@ -334,6 +356,8 @@ class AdvertiseManager {
            return;
            return;
        }
        }
        setAdvertisingParametersNative(advertiserId, parameters);
        setAdvertisingParametersNative(advertiserId, parameters);

        mService.mAdvertiserMap.setAdvertisingParameters(advertiserId, parameters);
    }
    }


    void setPeriodicAdvertisingParameters(int advertiserId,
    void setPeriodicAdvertisingParameters(int advertiserId,
@@ -344,6 +368,8 @@ class AdvertiseManager {
            return;
            return;
        }
        }
        setPeriodicAdvertisingParametersNative(advertiserId, parameters);
        setPeriodicAdvertisingParametersNative(advertiserId, parameters);

        mService.mAdvertiserMap.setPeriodicAdvertisingParameters(advertiserId, parameters);
    }
    }


    void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) {
    void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) {
@@ -356,6 +382,8 @@ class AdvertiseManager {
        try {
        try {
            setPeriodicAdvertisingDataNative(advertiserId,
            setPeriodicAdvertisingDataNative(advertiserId,
                    AdvertiseHelper.advertiseDataToBytes(data, deviceName));
                    AdvertiseHelper.advertiseDataToBytes(data, deviceName));

            mService.mAdvertiserMap.setPeriodicAdvertisingData(advertiserId, data);
        } catch (IllegalArgumentException e) {
        } catch (IllegalArgumentException e) {
            try {
            try {
                onPeriodicAdvertisingDataSet(advertiserId,
                onPeriodicAdvertisingDataSet(advertiserId,
@@ -473,6 +501,11 @@ class AdvertiseManager {


        IAdvertisingSetCallback callback = entry.getValue().callback;
        IAdvertisingSetCallback callback = entry.getValue().callback;
        callback.onPeriodicAdvertisingEnabled(advertiserId, enable, status);
        callback.onPeriodicAdvertisingEnabled(advertiserId, enable, status);

        AppAdvertiseStats stats = mService.mAdvertiserMap.getAppAdvertiseStatsById(advertiserId);
        if (stats != null) {
            stats.onPeriodicAdvertiseEnabled(enable);
        }
    }
    }


    static {
    static {
+384 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2021 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.gatt;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertisingSetParameters;
import android.bluetooth.le.PeriodicAdvertisingParameters;
import android.os.ParcelUuid;
import android.os.SystemClock;
import android.util.SparseArray;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * ScanStats class helps keep track of information about scans
 * on a per application basis.
 * @hide
 */
/*package*/ class AppAdvertiseStats {
    private static final String TAG = AppAdvertiseStats.class.getSimpleName();

    static final DateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss");

    static final String[] PHY_LE_STRINGS = {"LE_1M", "LE_2M", "LE_CODED"};

    // ContextMap here is needed to grab Apps and Connections
    ContextMap mContextMap;

    // GattService is needed to add scan event protos to be dumped later
    GattService mGattService;

    class AppAdvertiserData {
        public boolean includeDeviceName = false;
        public boolean includeTxPowerLevel = false;
        public SparseArray<byte[]> manufacturerData;
        public Map<ParcelUuid, byte[]> serviceData;
        public List<ParcelUuid> serviceUuids;
        AppAdvertiserData(boolean includeDeviceName, boolean includeTxPowerLevel,
                SparseArray<byte[]> manufacturerData, Map<ParcelUuid, byte[]> serviceData,
                List<ParcelUuid> serviceUuids) {
            this.includeDeviceName = includeDeviceName;
            this.includeTxPowerLevel = includeTxPowerLevel;
            this.manufacturerData = manufacturerData;
            this.serviceData = serviceData;
            this.serviceUuids = serviceUuids;
        }
    }

    class AppAdvertiserRecord {
        public long startTime = 0;
        public long stopTime = 0;
        public int duration = 0;
        public int maxExtendedAdvertisingEvents = 0;
        AppAdvertiserRecord(long startTime) {
            this.startTime = startTime;
        }
    }

    private int mAppUid;
    private String mAppName;
    private int mId;
    private boolean mAdvertisingEnabled = false;
    private boolean mPeriodicAdvertisingEnabled = false;
    private int mPrimaryPhy = BluetoothDevice.PHY_LE_1M;
    private int mSecondaryPhy = BluetoothDevice.PHY_LE_1M;
    private int mInterval = 0;
    private int mTxPowerLevel = 0;
    private boolean mLegacy = false;
    private boolean mAnonymous = false;
    private boolean mConnectable = false;
    private boolean mScannable = false;
    private AppAdvertiserData mAdvertisingData = null;
    private AppAdvertiserData mScanResponseData = null;
    private AppAdvertiserData mPeriodicAdvertisingData = null;
    private boolean mPeriodicIncludeTxPower = false;
    private int mPeriodicInterval = 0;
    public LinkedList<AppAdvertiserRecord> mAdvertiserRecords =
            new LinkedList<AppAdvertiserRecord>();

    AppAdvertiseStats(int appUid, int id, String name, ContextMap map, GattService service) {
        this.mAppUid = appUid;
        this.mId = id;
        this.mAppName = name;
        this.mContextMap = map;
        this.mGattService = service;
    }

    void recordAdvertiseStart(AdvertisingSetParameters parameters,
            AdvertiseData advertiseData, AdvertiseData scanResponse,
            PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData,
            int duration, int maxExtAdvEvents) {
        mAdvertisingEnabled = true;
        AppAdvertiserRecord record = new AppAdvertiserRecord(SystemClock.elapsedRealtime());
        record.duration = duration;
        record.maxExtendedAdvertisingEvents = maxExtAdvEvents;
        mAdvertiserRecords.add(record);
        if (parameters != null) {
            mPrimaryPhy = parameters.getPrimaryPhy();
            mSecondaryPhy = parameters.getSecondaryPhy();
            mInterval = parameters.getInterval();
            mTxPowerLevel = parameters.getTxPowerLevel();
            mLegacy = parameters.isLegacy();
            mAnonymous = parameters.isAnonymous();
            mConnectable = parameters.isConnectable();
            mScannable = parameters.isScannable();
        }

        if (advertiseData != null) {
            mAdvertisingData = new AppAdvertiserData(advertiseData.getIncludeDeviceName(),
                    advertiseData.getIncludeTxPowerLevel(),
                    advertiseData.getManufacturerSpecificData(),
                    advertiseData.getServiceData(),
                    advertiseData.getServiceUuids());
        }

        if (scanResponse != null) {
            mScanResponseData = new AppAdvertiserData(scanResponse.getIncludeDeviceName(),
                    scanResponse.getIncludeTxPowerLevel(),
                    scanResponse.getManufacturerSpecificData(),
                    scanResponse.getServiceData(),
                    scanResponse.getServiceUuids());
        }

        if (periodicData != null) {
            mPeriodicAdvertisingData = new AppAdvertiserData(
                    periodicData.getIncludeDeviceName(),
                    periodicData.getIncludeTxPowerLevel(),
                    periodicData.getManufacturerSpecificData(),
                    periodicData.getServiceData(),
                    periodicData.getServiceUuids());
        }

        if (periodicParameters != null) {
            mPeriodicAdvertisingEnabled = true;
            mPeriodicIncludeTxPower = periodicParameters.getIncludeTxPower();
            mPeriodicInterval = periodicParameters.getInterval();
        }
    }

    void recordAdvertiseStop() {
        mAdvertisingEnabled = false;
        mPeriodicAdvertisingEnabled = false;
        AppAdvertiserRecord record = mAdvertiserRecords.getLast();
        record.stopTime = SystemClock.elapsedRealtime();
    }

    void enableAdvertisingSet(boolean enable, int duration, int maxExtAdvEvents) {
        AppAdvertiserRecord lastRecord = mAdvertiserRecords.getLast();
        if (mAdvertisingEnabled) {
            //if the advertisingSet have not been disabled, skip enabling.
            if (lastRecord.stopTime != 0) {
                AppAdvertiserRecord record = new AppAdvertiserRecord(SystemClock.elapsedRealtime());
                record.duration = duration;
                record.maxExtendedAdvertisingEvents = maxExtAdvEvents;
                mAdvertiserRecords.add(record);
                if (mAdvertiserRecords.size() > 5) {
                    mAdvertiserRecords.removeFirst();
                }
            }
        } else {
            if (lastRecord.stopTime == 0) {
                lastRecord.stopTime = SystemClock.elapsedRealtime();
            }
        }
    }

    void setAdvertisingData(AdvertiseData data) {
        if (mAdvertisingData == null) {
            mAdvertisingData = new AppAdvertiserData(data.getIncludeDeviceName(),
                    data.getIncludeTxPowerLevel(),
                    data.getManufacturerSpecificData(),
                    data.getServiceData(),
                    data.getServiceUuids());
        } else if (data != null) {
            mAdvertisingData.includeDeviceName = data.getIncludeDeviceName();
            mAdvertisingData.includeTxPowerLevel = data.getIncludeTxPowerLevel();
            mAdvertisingData.manufacturerData = data.getManufacturerSpecificData();
            mAdvertisingData.serviceData = data.getServiceData();
            mAdvertisingData.serviceUuids = data.getServiceUuids();
        }
    }

    void setScanResponseData(AdvertiseData data) {
        if (mScanResponseData == null) {
            mScanResponseData = new AppAdvertiserData(data.getIncludeDeviceName(),
                    data.getIncludeTxPowerLevel(),
                    data.getManufacturerSpecificData(),
                    data.getServiceData(),
                    data.getServiceUuids());
        } else if (data != null) {
            mScanResponseData.includeDeviceName = data.getIncludeDeviceName();
            mScanResponseData.includeTxPowerLevel = data.getIncludeTxPowerLevel();
            mScanResponseData.manufacturerData = data.getManufacturerSpecificData();
            mScanResponseData.serviceData = data.getServiceData();
            mScanResponseData.serviceUuids = data.getServiceUuids();
        }
    }

    void setAdvertisingParameters(AdvertisingSetParameters parameters) {
        if (parameters != null) {
            mPrimaryPhy = parameters.getPrimaryPhy();
            mSecondaryPhy = parameters.getSecondaryPhy();
            mInterval = parameters.getInterval();
            mTxPowerLevel = parameters.getTxPowerLevel();
            mLegacy = parameters.isLegacy();
            mAnonymous = parameters.isAnonymous();
            mConnectable = parameters.isConnectable();
            mScannable = parameters.isScannable();
        }
    }

    void setPeriodicAdvertisingParameters(PeriodicAdvertisingParameters parameters) {
        if (parameters != null) {
            mPeriodicIncludeTxPower = parameters.getIncludeTxPower();
            mPeriodicInterval = parameters.getInterval();
        }
    }

    void setPeriodicAdvertisingData(AdvertiseData data) {
        if (mPeriodicAdvertisingData == null) {
            mPeriodicAdvertisingData = new AppAdvertiserData(data.getIncludeDeviceName(),
                    data.getIncludeTxPowerLevel(),
                    data.getManufacturerSpecificData(),
                    data.getServiceData(),
                    data.getServiceUuids());
        } else if (data != null) {
            mPeriodicAdvertisingData.includeDeviceName = data.getIncludeDeviceName();
            mPeriodicAdvertisingData.includeTxPowerLevel = data.getIncludeTxPowerLevel();
            mPeriodicAdvertisingData.manufacturerData = data.getManufacturerSpecificData();
            mPeriodicAdvertisingData.serviceData = data.getServiceData();
            mPeriodicAdvertisingData.serviceUuids = data.getServiceUuids();
        }
    }

    void onPeriodicAdvertiseEnabled(boolean enable) {
        mPeriodicAdvertisingEnabled = enable;
    }

    void setId(int id) {
        this.mId = id;
    }

    private static String printByteArrayInHex(byte[] data) {
        final StringBuilder hex = new StringBuilder();
        for (byte b : data) {
            hex.append(String.format("%02x", b));
        }
        return hex.toString();
    }

    private static void dumpAppAdvertiserData(StringBuilder sb, AppAdvertiserData advData) {
        sb.append("\n          └Include Device Name                          : "
                + advData.includeDeviceName);
        sb.append("\n          └Include Tx Power Level                       : "
                + advData.includeTxPowerLevel);

        if (advData.manufacturerData.size() > 0) {
            sb.append("\n          └Manufacturer Data                            : ");
            for (int i = 0; i < advData.manufacturerData.size(); i++) {
                sb.append("\n            [" + Integer.toHexString(advData.manufacturerData.keyAt(i))
                        + ", " + printByteArrayInHex(advData.manufacturerData.valueAt(i)) + "]");
            }
        }

        if (!advData.serviceData.isEmpty()) {
            sb.append("\n          └Service Data(UUID, length of data)           : ");
            for (ParcelUuid uuid : advData.serviceData.keySet()) {
                sb.append("\n            [" + uuid + ", "
                        + advData.serviceData.get(uuid).length + "]");
            }
        }

        if (!advData.serviceUuids.isEmpty()) {
            sb.append("\n          └Service Uuids                                : \n            "
                    + advData.serviceUuids.toString());
        }
    }

    private static String dumpPhyString(int phy) {
        if (phy > PHY_LE_STRINGS.length) {
            return Integer.toString(phy);
        } else {
            return PHY_LE_STRINGS[phy - 1];
        }
    }

    private static void dumpAppAdvertiseStats(StringBuilder sb, AppAdvertiseStats stats) {
        sb.append("\n      └Advertising:");
        sb.append("\n        └Interval(0.625ms)                              : "
                + stats.mInterval);
        sb.append("\n        └TX POWER(dbm)                                  : "
                + stats.mTxPowerLevel);
        sb.append("\n        └Primary Phy                                    : "
                + dumpPhyString(stats.mPrimaryPhy));
        sb.append("\n        └Secondary Phy                                  : "
                + dumpPhyString(stats.mSecondaryPhy));
        sb.append("\n        └Legacy                                         : "
                + stats.mLegacy);
        sb.append("\n        └Anonymous                                      : "
                + stats.mAnonymous);
        sb.append("\n        └Connectable                                    : "
                + stats.mConnectable);
        sb.append("\n        └Scannable                                      : "
                + stats.mScannable);

        if (stats.mAdvertisingData != null) {
            sb.append("\n        └Advertise Data:");
            dumpAppAdvertiserData(sb, stats.mAdvertisingData);
        }

        if (stats.mScanResponseData != null) {
            sb.append("\n        └Scan Response:");
            dumpAppAdvertiserData(sb, stats.mScanResponseData);
        }

        if (stats.mPeriodicInterval > 0) {
            sb.append("\n      └Periodic Advertising Enabled                     : "
                    + stats.mPeriodicAdvertisingEnabled);
            sb.append("\n        └Periodic Include TxPower                       : "
                    + stats.mPeriodicIncludeTxPower);
            sb.append("\n        └Periodic Interval(1.25ms)                      : "
                    + stats.mPeriodicInterval);
        }

        if (stats.mPeriodicAdvertisingData != null) {
            sb.append("\n        └Periodic Advertise Data:");
            dumpAppAdvertiserData(sb, stats.mPeriodicAdvertisingData);
        }

        sb.append("\n");
    }

    static void dumpToString(StringBuilder sb, AppAdvertiseStats stats) {
        long currentTime = System.currentTimeMillis();
        long currentRealTime = SystemClock.elapsedRealtime();

        sb.append("\n    " + stats.mAppName);
        sb.append("\n     Advertising ID                                     : "
                + stats.mId);
        for (int i = 0; i < stats.mAdvertiserRecords.size(); i++) {
            AppAdvertiserRecord record = stats.mAdvertiserRecords.get(i);
            Date timestamp = new Date(currentTime - currentRealTime
                    + record.startTime);

            sb.append("\n      " + (i + 1) + ":");
            sb.append("\n        └Start time                                     : "
                    + DATE_FORMAT.format(timestamp));
            if (record.stopTime == 0) {
                sb.append("\n        └Elapsed time                                   : "
                        + (currentRealTime - record.startTime) + "ms");
            } else {
                Date stopTimestamp = new Date(currentTime - currentRealTime
                        + record.stopTime);
                sb.append("\n        └Stop time                                      : "
                        + DATE_FORMAT.format(stopTimestamp));
            }
            sb.append("\n        └Duration(10ms unit)                            : "
                    + record.duration);
            sb.append("\n        └Maximum number of extended advertising events  : "
                    + record.maxExtendedAdvertisingEvents);
        }

        dumpAppAdvertiseStats(sb, stats);
    }
}
+183 −0

File changed.

Preview size limit exceeded, changes collapsed.

+11 −0
Original line number Original line Diff line number Diff line
@@ -221,6 +221,13 @@ public class GattService extends ProfileService {


    ScannerMap mScannerMap = new ScannerMap();
    ScannerMap mScannerMap = new ScannerMap();


    /**
     * List of our registered advertisers.
     */
    class AdvertiserMap extends ContextMap<IAdvertisingSetCallback, Void> {}

    AdvertiserMap mAdvertiserMap = new AdvertiserMap();

    /**
    /**
     * List of our registered clients.
     * List of our registered clients.
     */
     */
@@ -343,6 +350,7 @@ public class GattService extends ProfileService {
        }
        }
        setGattService(null);
        setGattService(null);
        mScannerMap.clear();
        mScannerMap.clear();
        mAdvertiserMap.clear();
        mClientMap.clear();
        mClientMap.clear();
        mServerMap.clear();
        mServerMap.clear();
        mHandleMap.clear();
        mHandleMap.clear();
@@ -4657,6 +4665,9 @@ public class GattService extends ProfileService {
        sb.append("GATT Scanner Map\n");
        sb.append("GATT Scanner Map\n");
        mScannerMap.dump(sb);
        mScannerMap.dump(sb);


        sb.append("GATT Advertiser Map\n");
        mAdvertiserMap.dumpAdvertiser(sb);

        sb.append("GATT Client Map\n");
        sb.append("GATT Client Map\n");
        mClientMap.dump(sb);
        mClientMap.dump(sb);