diff --git a/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java b/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java index e02fd6f73f2849beb42c025ce29cb6d8d62d03ed..4ba889dbbcb9c6a083f2d24632643ab8372269c6 100644 --- a/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java +++ b/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java @@ -30,6 +30,7 @@ import android.os.RemoteException; import android.util.Log; import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.gatt.GattService.AdvertiserMap; import java.util.Collections; import java.util.HashMap; @@ -46,6 +47,7 @@ class AdvertiseManager { private final GattService mService; private final AdapterService mAdapterService; + private final AdvertiserMap mAdvertiserMap; private Handler mHandler; Map<IBinder, AdvertiserInfo> mAdvertisers = Collections.synchronizedMap(new HashMap<>()); static int sTempRegistrationId = -1; @@ -53,12 +55,14 @@ class AdvertiseManager { /** * Constructor of {@link AdvertiseManager}. */ - AdvertiseManager(GattService service, AdapterService adapterService) { + AdvertiseManager(GattService service, AdapterService adapterService, + AdvertiserMap advertiserMap) { if (DBG) { Log.d(TAG, "advertise manager created"); } mService = service; mAdapterService = adapterService; + mAdvertiserMap = advertiserMap; } /** @@ -157,10 +161,18 @@ class AdvertiseManager { if (status == 0) { entry.setValue( new AdvertiserInfo(advertiserId, entry.getValue().deathRecipient, callback)); + + mAdvertiserMap.setAdvertiserIdByRegId(regId, advertiserId); } else { IBinder binder = entry.getKey(); binder.unlinkToDeath(entry.getValue().deathRecipient, 0); mAdvertisers.remove(binder); + + AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(regId); + if (stats != null) { + stats.recordAdvertiseStop(); + } + mAdvertiserMap.removeAppAdvertiseStats(regId); } callback.onAdvertisingSetStarted(advertiserId, txPower, status); @@ -181,6 +193,13 @@ class AdvertiseManager { IAdvertisingSetCallback callback = entry.getValue().callback; callback.onAdvertisingEnabled(advertiserId, enable, status); + + if (!enable && status != 0) { + AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(advertiserId); + if (stats != null) { + stats.recordAdvertiseStop(); + } + } } void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData, @@ -203,14 +222,19 @@ class AdvertiseManager { byte[] periodicDataBytes = AdvertiseHelper.advertiseDataToBytes(periodicData, deviceName); - int cbId = --sTempRegistrationId; - mAdvertisers.put(binder, new AdvertiserInfo(cbId, deathRecipient, callback)); + int cbId = --sTempRegistrationId; + mAdvertisers.put(binder, new AdvertiserInfo(cbId, deathRecipient, callback)); - if (DBG) { - Log.d(TAG, "startAdvertisingSet() - reg_id=" + cbId + ", callback: " + binder); - } - startAdvertisingSetNative(parameters, advDataBytes, scanResponseBytes, periodicParameters, - periodicDataBytes, duration, maxExtAdvEvents, cbId); + if (DBG) { + Log.d(TAG, "startAdvertisingSet() - reg_id=" + cbId + ", callback: " + binder); + } + + mAdvertiserMap.add(cbId, callback, mService); + mAdvertiserMap.recordAdvertiseStart(cbId, parameters, advertiseData, + scanResponse, periodicParameters, periodicData, duration, maxExtAdvEvents); + + startAdvertisingSetNative(parameters, advDataBytes, scanResponseBytes, + periodicParameters, periodicDataBytes, duration, maxExtAdvEvents, cbId); } catch (IllegalArgumentException e) { try { @@ -276,6 +300,8 @@ class AdvertiseManager { } catch (RemoteException e) { Log.i(TAG, "error sending onAdvertisingSetStopped callback", e); } + + mAdvertiserMap.recordAdvertiseStop(advertiserId); } void enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents) { @@ -285,6 +311,9 @@ class AdvertiseManager { return; } enableAdvertisingSetNative(advertiserId, enable, duration, maxExtAdvEvents); + + mAdvertiserMap.enableAdvertisingSet(advertiserId, + enable, duration, maxExtAdvEvents); } void setAdvertisingData(int advertiserId, AdvertiseData data) { @@ -297,6 +326,8 @@ class AdvertiseManager { try { setAdvertisingDataNative(advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName)); + + mAdvertiserMap.setAdvertisingData(advertiserId, data); } catch (IllegalArgumentException e) { try { onAdvertisingDataSet(advertiserId, @@ -317,6 +348,8 @@ class AdvertiseManager { try { setScanResponseDataNative(advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName)); + + mAdvertiserMap.setScanResponseData(advertiserId, data); } catch (IllegalArgumentException e) { try { onScanResponseDataSet(advertiserId, @@ -334,6 +367,8 @@ class AdvertiseManager { return; } setAdvertisingParametersNative(advertiserId, parameters); + + mAdvertiserMap.setAdvertisingParameters(advertiserId, parameters); } void setPeriodicAdvertisingParameters(int advertiserId, @@ -344,6 +379,8 @@ class AdvertiseManager { return; } setPeriodicAdvertisingParametersNative(advertiserId, parameters); + + mAdvertiserMap.setPeriodicAdvertisingParameters(advertiserId, parameters); } void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) { @@ -356,6 +393,8 @@ class AdvertiseManager { try { setPeriodicAdvertisingDataNative(advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName)); + + mAdvertiserMap.setPeriodicAdvertisingData(advertiserId, data); } catch (IllegalArgumentException e) { try { onPeriodicAdvertisingDataSet(advertiserId, @@ -473,6 +512,11 @@ class AdvertiseManager { IAdvertisingSetCallback callback = entry.getValue().callback; callback.onPeriodicAdvertisingEnabled(advertiserId, enable, status); + + AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(advertiserId); + if (stats != null) { + stats.onPeriodicAdvertiseEnabled(enable); + } } static { diff --git a/android/app/src/com/android/bluetooth/gatt/AppAdvertiseStats.java b/android/app/src/com/android/bluetooth/gatt/AppAdvertiseStats.java new file mode 100644 index 0000000000000000000000000000000000000000..13241498cfb0ee467a2b6352cfdc446b6620ee47 --- /dev/null +++ b/android/app/src/com/android/bluetooth/gatt/AppAdvertiseStats.java @@ -0,0 +1,385 @@ +/* + * 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.util.SparseArray; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +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(); + + private static DateTimeFormatter sDateFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss") + .withZone(ZoneId.systemDefault()); + + static final String[] PHY_LE_STRINGS = {"LE_1M", "LE_2M", "LE_CODED"}; + static final int UUID_STRING_FILTER_LEN = 8; + + // 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; + + static 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; + } + } + + static class AppAdvertiserRecord { + public Instant startTime = null; + public Instant stopTime = null; + public int duration = 0; + public int maxExtendedAdvertisingEvents = 0; + AppAdvertiserRecord(Instant 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 ArrayList<AppAdvertiserRecord> mAdvertiserRecords = + new ArrayList<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(Instant.now()); + record.duration = duration; + record.maxExtendedAdvertisingEvents = maxExtAdvEvents; + mAdvertiserRecords.add(record); + if (mAdvertiserRecords.size() > 5) { + mAdvertiserRecords.remove(0); + } + + 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 recordAdvertiseStart(int duration, int maxExtAdvEvents) { + recordAdvertiseStart(null, null, null, null, null, duration, maxExtAdvEvents); + } + + void recordAdvertiseStop() { + mAdvertisingEnabled = false; + mPeriodicAdvertisingEnabled = false; + if (!mAdvertiserRecords.isEmpty()) { + AppAdvertiserRecord record = mAdvertiserRecords.get(mAdvertiserRecords.size() - 1); + record.stopTime = Instant.now(); + } + } + + void enableAdvertisingSet(boolean enable, int duration, int maxExtAdvEvents) { + if (enable) { + //if the advertisingSet have not been disabled, skip enabling. + if (!mAdvertisingEnabled) { + recordAdvertiseStart(duration, maxExtAdvEvents); + } + } else { + //if the advertisingSet have not been enabled, skip disabling. + if (mAdvertisingEnabled) { + recordAdvertiseStop(); + } + } + } + + 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 (length of data) : " + + advData.manufacturerData.size()); + } + + if (!advData.serviceData.isEmpty()) { + sb.append("\n â””Service Data(UUID, length of data) : "); + for (ParcelUuid uuid : advData.serviceData.keySet()) { + sb.append("\n [" + uuid.toString().substring(0, UUID_STRING_FILTER_LEN) + + "-xxxx-xxxx-xxxx-xxxxxxxxxxxx, " + + advData.serviceData.get(uuid).length + "]"); + } + } + + if (!advData.serviceUuids.isEmpty()) { + sb.append("\n â””Service Uuids : \n " + + advData.serviceUuids.toString().substring(0, UUID_STRING_FILTER_LEN) + + "-xxxx-xxxx-xxxx-xxxxxxxxxxxx"); + } + } + + 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) { + Instant currentTime = Instant.now(); + + 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); + + sb.append("\n " + (i + 1) + ":"); + sb.append("\n â””Start time : " + + sDateFormat.format(record.startTime)); + if (record.stopTime == null) { + Duration timeElapsed = Duration.between(record.startTime, currentTime); + sb.append("\n â””Elapsed time : " + + timeElapsed.toMillis() + "ms"); + } else { + sb.append("\n â””Stop time : " + + sDateFormat.format(record.stopTime)); + } + sb.append("\n â””Duration(10ms unit) : " + + record.duration); + sb.append("\n â””Maximum number of extended advertising events : " + + record.maxExtendedAdvertisingEvents); + } + + dumpAppAdvertiseStats(sb, stats); + } +} diff --git a/android/app/src/com/android/bluetooth/gatt/ContextMap.java b/android/app/src/com/android/bluetooth/gatt/ContextMap.java index 5f6f115ce7a479ec2eb702504dacd8dbb4930e20..f9c6cd4d44b3b848f57545930f45bae0cd1fefee 100644 --- a/android/app/src/com/android/bluetooth/gatt/ContextMap.java +++ b/android/app/src/com/android/bluetooth/gatt/ContextMap.java @@ -15,6 +15,9 @@ */ package com.android.bluetooth.gatt; +import android.bluetooth.le.AdvertiseData; +import android.bluetooth.le.AdvertisingSetParameters; +import android.bluetooth.le.PeriodicAdvertisingParameters; import android.os.Binder; import android.os.IBinder; import android.os.IInterface; @@ -24,6 +27,10 @@ import android.os.UserHandle; import android.os.WorkSource; import android.util.Log; +import com.android.internal.annotations.GuardedBy; + +import com.google.common.collect.EvictingQueue; + import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -125,6 +132,15 @@ import java.util.UUID; this.appScanStats = appScanStats; } + /** + * Creates a new app context for advertiser. + */ + App(int id, C callback, String name) { + this.id = id; + this.callback = callback; + this.name = name; + } + /** * Link death recipient */ @@ -169,12 +185,22 @@ import java.util.UUID; } /** Our internal application list */ - private List<App> mApps = new ArrayList<App>(); private final Object mAppsLock = new Object(); + @GuardedBy("mAppsLock") + private List<App> mApps = new ArrayList<App>(); /** Internal map to keep track of logging information by app name */ private HashMap<Integer, AppScanStats> mAppScanStats = new HashMap<Integer, AppScanStats>(); + /** Internal map to keep track of logging information by advertise id */ + private final Map<Integer, AppAdvertiseStats> mAppAdvertiseStats = + new HashMap<Integer, AppAdvertiseStats>(); + + private static final int ADVERTISE_STATE_MAX_SIZE = 5; + + private final EvictingQueue<AppAdvertiseStats> mLastAdvertises = + EvictingQueue.create(ADVERTISE_STATE_MAX_SIZE); + /** Internal list of connected devices **/ private Set<Connection> mConnections = new HashSet<Connection>(); private final Object mConnectionsLock = new Object(); @@ -202,6 +228,32 @@ import java.util.UUID; } } + /** + * Add an entry to the application context list for advertiser. + */ + App add(int id, C callback, GattService service) { + int appUid = Binder.getCallingUid(); + String appName = service.getPackageManager().getNameForUid(appUid); + if (appName == null) { + // Assign an app name if one isn't found + appName = "Unknown App (UID: " + appUid + ")"; + } + + synchronized (mAppsLock) { + AppAdvertiseStats appAdvertiseStats = mAppAdvertiseStats.get(id); + if (appAdvertiseStats == null) { + appAdvertiseStats = new AppAdvertiseStats(appUid, id, appName, this, service); + mAppAdvertiseStats.put(id, appAdvertiseStats); + } + App app = getById(appUid); + if (app == null) { + app = new App(appUid, callback, appName); + mApps.add(app); + } + return app; + } + } + /** * Remove the context for a given UUID */ @@ -384,6 +436,105 @@ import java.util.UUID; return mAppScanStats.get(uid); } + /** + * Remove the context for a given application ID. + */ + void removeAppAdvertiseStats(int id) { + synchronized (this) { + mAppAdvertiseStats.remove(id); + } + } + + /** + * Get Logging info by ID + */ + AppAdvertiseStats getAppAdvertiseStatsById(int id) { + synchronized (this) { + return mAppAdvertiseStats.get(id); + } + } + + /** + * update the advertiser ID by the regiseter ID + */ + void setAdvertiserIdByRegId(int regId, int advertiserId) { + synchronized (this) { + AppAdvertiseStats stats = mAppAdvertiseStats.get(regId); + stats.setId(advertiserId); + mAppAdvertiseStats.remove(regId); + mAppAdvertiseStats.put(advertiserId, stats); + } + } + + void recordAdvertiseStart(int id, AdvertisingSetParameters parameters, + AdvertiseData advertiseData, AdvertiseData scanResponse, + PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData, + int duration, int maxExtAdvEvents) { + synchronized (this) { + AppAdvertiseStats stats = mAppAdvertiseStats.get(id); + stats.recordAdvertiseStart(parameters, advertiseData, scanResponse, + periodicParameters, periodicData, duration, maxExtAdvEvents); + } + } + + void recordAdvertiseStop(int id) { + synchronized (this) { + AppAdvertiseStats stats = mAppAdvertiseStats.get(id); + stats.recordAdvertiseStop(); + mAppAdvertiseStats.remove(id); + mLastAdvertises.add(stats); + } + } + + void enableAdvertisingSet(int id, boolean enable, int duration, int maxExtAdvEvents) { + synchronized (this) { + AppAdvertiseStats stats = mAppAdvertiseStats.get(id); + stats.enableAdvertisingSet(enable, duration, maxExtAdvEvents); + } + } + + void setAdvertisingData(int id, AdvertiseData data) { + synchronized (this) { + AppAdvertiseStats stats = mAppAdvertiseStats.get(id); + stats.setAdvertisingData(data); + } + } + + void setScanResponseData(int id, AdvertiseData data) { + synchronized (this) { + AppAdvertiseStats stats = mAppAdvertiseStats.get(id); + stats.setScanResponseData(data); + } + } + + void setAdvertisingParameters(int id, AdvertisingSetParameters parameters) { + synchronized (this) { + AppAdvertiseStats stats = mAppAdvertiseStats.get(id); + stats.setAdvertisingParameters(parameters); + } + } + + void setPeriodicAdvertisingParameters(int id, PeriodicAdvertisingParameters parameters) { + synchronized (this) { + AppAdvertiseStats stats = mAppAdvertiseStats.get(id); + stats.setPeriodicAdvertisingParameters(parameters); + } + } + + void setPeriodicAdvertisingData(int id, AdvertiseData data) { + synchronized (this) { + AppAdvertiseStats stats = mAppAdvertiseStats.get(id); + stats.setPeriodicAdvertisingData(data); + } + } + + void onPeriodicAdvertiseEnabled(int id, boolean enable) { + synchronized (this) { + AppAdvertiseStats stats = mAppAdvertiseStats.get(id); + stats.onPeriodicAdvertiseEnabled(enable); + } + } + /** * Get the device addresses for all connected devices */ @@ -479,7 +630,9 @@ import java.util.UUID; while (i.hasNext()) { App entry = i.next(); entry.unlinkToDeath(); - entry.appScanStats.isRegistered = false; + if (entry.appScanStats != null) { + entry.appScanStats.isRegistered = false; + } i.remove(); } } @@ -487,6 +640,11 @@ import java.util.UUID; synchronized (mConnectionsLock) { mConnections.clear(); } + + synchronized (this) { + mAppAdvertiseStats.clear(); + mLastAdvertises.clear(); + } } /** @@ -516,4 +674,31 @@ import java.util.UUID; appScanStats.dumpToString(sb); } } + + /** + * Logs advertiser debug information. + */ + void dumpAdvertiser(StringBuilder sb) { + synchronized (this) { + if (!mLastAdvertises.isEmpty()) { + sb.append("\n last " + mLastAdvertises.size() + " advertising:"); + for (AppAdvertiseStats stats : mLastAdvertises) { + AppAdvertiseStats.dumpToString(sb, stats); + } + sb.append("\n"); + } + + if (!mAppAdvertiseStats.isEmpty()) { + sb.append(" Total number of ongoing advertising : " + + mAppAdvertiseStats.size()); + sb.append("\n Ongoing advertising:"); + for (Integer key : mAppAdvertiseStats.keySet()) { + AppAdvertiseStats stats = mAppAdvertiseStats.get(key); + AppAdvertiseStats.dumpToString(sb, stats); + } + } + sb.append("\n"); + } + Log.d(TAG, sb.toString()); + } } diff --git a/android/app/src/com/android/bluetooth/gatt/GattService.java b/android/app/src/com/android/bluetooth/gatt/GattService.java index b21a34986767a246ae48bce886fe2b67eb1aa664..4f5db52e071543e8648f86027396ea632dae0a12 100644 --- a/android/app/src/com/android/bluetooth/gatt/GattService.java +++ b/android/app/src/com/android/bluetooth/gatt/GattService.java @@ -221,6 +221,13 @@ public class GattService extends ProfileService { ScannerMap mScannerMap = new ScannerMap(); + /** + * List of our registered advertisers. + */ + static class AdvertiserMap extends ContextMap<IAdvertisingSetCallback, Void> {} + + private AdvertiserMap mAdvertiserMap = new AdvertiserMap(); + /** * List of our registered clients. */ @@ -323,7 +330,7 @@ public class GattService extends ProfileService { mBluetoothAdapterProxy = BluetoothAdapterProxy.getInstance(); mCompanionManager = getSystemService(CompanionDeviceManager.class); mAppOps = getSystemService(AppOpsManager.class); - mAdvertiseManager = new AdvertiseManager(this, mAdapterService); + mAdvertiseManager = new AdvertiseManager(this, mAdapterService, mAdvertiserMap); mAdvertiseManager.start(); mScanManager = new ScanManager(this, mAdapterService, mBluetoothAdapterProxy); @@ -343,6 +350,7 @@ public class GattService extends ProfileService { } setGattService(null); mScannerMap.clear(); + mAdvertiserMap.clear(); mClientMap.clear(); mServerMap.clear(); mHandleMap.clear(); @@ -4664,6 +4672,9 @@ public class GattService extends ProfileService { sb.append("GATT Scanner Map\n"); mScannerMap.dump(sb); + sb.append("GATT Advertiser Map\n"); + mAdvertiserMap.dumpAdvertiser(sb); + sb.append("GATT Client Map\n"); mClientMap.dump(sb);