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);