Implement restricted metrics changed operation

Very generally follows the pattern of active configs/data fetch
broadcasts.

A few things are still todo:
  - tests
  - resending all pending intents on statsd restart
  - returning the current
  - code optimizations/cleanups

Test: m
Test: atest statsd_test
Bug: 268141944
Ignore-AOSP-First: U feature
Change-Id: I1f8c52c6dc0570630c92d29adb6ce8b14c08c3e7
diff --git a/statsd/src/StatsLogProcessor.cpp b/statsd/src/StatsLogProcessor.cpp
index 106f0f4..4a4a469 100644
--- a/statsd/src/StatsLogProcessor.cpp
+++ b/statsd/src/StatsLogProcessor.cpp
@@ -91,7 +91,9 @@
         const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerManager,
         const sp<AlarmMonitor>& anomalyAlarmMonitor, const sp<AlarmMonitor>& periodicAlarmMonitor,
         const int64_t timeBaseNs, const std::function<bool(const ConfigKey&)>& sendBroadcast,
-        const std::function<bool(const int&, const vector<int64_t>&)>& activateBroadcast)
+        const std::function<bool(const int&, const vector<int64_t>&)>& activateBroadcast,
+        const std::function<void(const ConfigKey&, const string&, const vector<int64_t>&)>&
+                sendRestrictedMetricsBroadcast)
     : mLastTtlTime(0),
       mUidMap(uidMap),
       mPullerManager(pullerManager),
@@ -99,6 +101,7 @@
       mPeriodicAlarmMonitor(periodicAlarmMonitor),
       mSendBroadcast(sendBroadcast),
       mSendActivationBroadcast(activateBroadcast),
+      mSendRestrictedMetricsBroadcast(sendRestrictedMetricsBroadcast),
       mTimeBaseNs(timeBaseNs),
       mLargestTimestampSeen(0),
       mLastTimestampSeen(0) {
@@ -564,6 +567,17 @@
             newMetricsManager->init();
             mUidMap->OnConfigUpdated(key);
             newMetricsManager->refreshTtl(timestampNs);
+            if (mIsRestrictedMetricsEnabled) {
+                if (newMetricsManager->hasRestrictedMetricsDelegate()) {
+                    mSendRestrictedMetricsBroadcast(
+                            key, newMetricsManager->getRestrictedMetricsDelegate(),
+                            newMetricsManager->getAllMetricIds());
+                } else if (it != mMetricsManagers.end() &&
+                           it->second->hasRestrictedMetricsDelegate()) {
+                    mSendRestrictedMetricsBroadcast(key, it->second->getRestrictedMetricsDelegate(),
+                                                    {});
+                }
+            }
             mMetricsManagers[key] = newMetricsManager;
             VLOG("StatsdConfig valid");
         }
@@ -573,12 +587,21 @@
                                                mAnomalyAlarmMonitor, mPeriodicAlarmMonitor);
         if (configValid) {
             mUidMap->OnConfigUpdated(key);
+            if (mIsRestrictedMetricsEnabled && it->second->hasRestrictedMetricsDelegate()) {
+                mSendRestrictedMetricsBroadcast(key, it->second->getRestrictedMetricsDelegate(),
+                                                it->second->getAllMetricIds());
+            }
         }
     }
     if (!configValid) {
         // If there is any error in the config, don't use it.
         // Remove any existing config with the same key.
         ALOGE("StatsdConfig NOT valid");
+        // Send an empty restricted metrics broadcast if the previous config was restricted.
+        if (mIsRestrictedMetricsEnabled && it != mMetricsManagers.end() &&
+            it->second->hasRestrictedMetricsDelegate()) {
+            mSendRestrictedMetricsBroadcast(key, it->second->getRestrictedMetricsDelegate(), {});
+        }
         mMetricsManagers.erase(key);
     }
 }
@@ -800,6 +823,9 @@
         }
         WriteDataToDiskLocked(key, getElapsedRealtimeNs(), getWallClockNs(), CONFIG_REMOVED,
                               NO_TIME_CONSTRAINTS);
+        if (mIsRestrictedMetricsEnabled && it->second->hasRestrictedMetricsDelegate()) {
+            mSendRestrictedMetricsBroadcast(key, it->second->getRestrictedMetricsDelegate(), {});
+        }
         mMetricsManagers.erase(it);
         mUidMap->OnConfigRemoved(key);
     }
diff --git a/statsd/src/StatsLogProcessor.h b/statsd/src/StatsLogProcessor.h
index f08ebf9..cc7ef6d 100644
--- a/statsd/src/StatsLogProcessor.h
+++ b/statsd/src/StatsLogProcessor.h
@@ -36,13 +36,15 @@
 
 class StatsLogProcessor : public ConfigListener, public virtual PackageInfoListener {
 public:
-    StatsLogProcessor(const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerManager,
-                      const sp<AlarmMonitor>& anomalyAlarmMonitor,
-                      const sp<AlarmMonitor>& subscriberTriggerAlarmMonitor,
-                      const int64_t timeBaseNs,
-                      const std::function<bool(const ConfigKey&)>& sendBroadcast,
-                      const std::function<bool(const int&,
-                                               const vector<int64_t>&)>& sendActivationBroadcast);
+    StatsLogProcessor(
+            const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerManager,
+            const sp<AlarmMonitor>& anomalyAlarmMonitor,
+            const sp<AlarmMonitor>& subscriberTriggerAlarmMonitor, const int64_t timeBaseNs,
+            const std::function<bool(const ConfigKey&)>& sendBroadcast,
+            const std::function<bool(const int&, const vector<int64_t>&)>& sendActivationBroadcast,
+            const std::function<void(const ConfigKey&, const string&, const vector<int64_t>&)>&
+                    sendRestrictedMetricsBroadcast);
+
     virtual ~StatsLogProcessor();
 
     void OnLogEvent(LogEvent* event);
@@ -297,6 +299,12 @@
     // are currently active.
     std::function<bool(const int& uid, const vector<int64_t>& configIds)> mSendActivationBroadcast;
 
+    // Function used to send a broadcast if necessary so the receiver can be notified of the
+    // restricted metrics for the given config.
+    std::function<void(const ConfigKey& key, const string& delegatePackage,
+                       const vector<int64_t>& restrictedMetricIds)>
+            mSendRestrictedMetricsBroadcast;
+
     const int64_t mTimeBaseNs;
 
     // Largest timestamp of the events that we have processed.
diff --git a/statsd/src/StatsService.cpp b/statsd/src/StatsService.cpp
index 724cc04..10a958e 100644
--- a/statsd/src/StatsService.cpp
+++ b/statsd/src/StatsService.cpp
@@ -35,6 +35,7 @@
 #include "android-base/stringprintf.h"
 #include "config/ConfigKey.h"
 #include "config/ConfigManager.h"
+#include "flags/FlagProvider.h"
 #include "guardrail/StatsdStats.h"
 #include "stats_log_util.h"
 #include "storage/StorageManager.h"
@@ -163,6 +164,13 @@
                 }
                 VLOG("StatsService::active configs broadcast failed for uid %d", uid);
                 return false;
+            },
+            [this](const ConfigKey& key, const string& delegatePackage,
+                   const vector<int64_t>& restrictedMetrics) {
+                set<string> configPackages = mUidMap->getAppNamesFromUid(key.GetUid(), true);
+                set<int32_t> delegateUids = mUidMap->getAppUid(delegatePackage);
+                mConfigManager->SendRestrictedMetricsBroadcast(configPackages, key.GetId(),
+                                                               delegateUids, restrictedMetrics);
             });
 
     mUidMap->setListener(mProcessor);
@@ -1350,11 +1358,32 @@
     mPullerManager->SetStatsCompanionService(nullptr);
 }
 
-Status StatsService::setRestrictedMetricsChangedOperation(const int64_t configKey,
+Status StatsService::setRestrictedMetricsChangedOperation(const int64_t configId,
                                                           const string& configPackage,
+                                                          const shared_ptr<IPendingIntentRef>& pir,
+                                                          const int32_t callingUid,
                                                           vector<int64_t>* output) {
     ENFORCE_UID(AID_SYSTEM);
-    // query db using configKey and populate output.
+    if (!FlagProvider::getInstance().getBootFlagBool(RESTRICTED_METRICS_FLAG, FLAG_FALSE)) {
+        return Status::ok();
+    }
+    mConfigManager->SetRestrictedMetricsChangedReceiver(configPackage, configId, callingUid, pir);
+    if (output != nullptr) {
+        // TODO(b/269419485): implement getting the current restricted metrics.
+    } else {
+        ALOGW("StatsService::setRestrictedMetricsChangedOperation output was nullptr");
+    }
+    return Status::ok();
+}
+
+Status StatsService::removeRestrictedMetricsChangedOperation(const int64_t configId,
+                                                             const string& configPackage,
+                                                             const int32_t callingUid) {
+    ENFORCE_UID(AID_SYSTEM);
+    if (!FlagProvider::getInstance().getBootFlagBool(RESTRICTED_METRICS_FLAG, FLAG_FALSE)) {
+        return Status::ok();
+    }
+    mConfigManager->RemoveRestrictedMetricsChangedReceiver(configPackage, configId, callingUid);
     return Status::ok();
 }
 
diff --git a/statsd/src/StatsService.h b/statsd/src/StatsService.h
index af94603..1d67633 100644
--- a/statsd/src/StatsService.h
+++ b/statsd/src/StatsService.h
@@ -210,13 +210,24 @@
     virtual Status updateProperties(const std::vector<PropertyParcel>& properties);
 
     /**
-     * Binder call to let clients register the active configs changed operation.
+     * Binder call to let clients register the restricted metrics changed operation for the given
+     * config and calling uid.
      */
     virtual Status setRestrictedMetricsChangedOperation(const int64_t configKey,
                                                         const string& configPackage,
+                                                        const shared_ptr<IPendingIntentRef>& pir,
+                                                        const int32_t callingUid,
                                                         vector<int64_t>* output);
 
     /**
+     * Binder call to remove the restricted metrics changed operation for the specified config
+     * and calling uid.
+     */
+    virtual Status removeRestrictedMetricsChangedOperation(const int64_t configKey,
+                                                           const string& configPackage,
+                                                           const int32_t callingUid);
+
+    /**
      * Binder call to query data in statsd sql store.
      */
     virtual Status querySql(const string& sqlQuery, const int32_t minSqlClientVersion,
diff --git a/statsd/src/config/ConfigKeyWithPackage.h b/statsd/src/config/ConfigKeyWithPackage.h
new file mode 100644
index 0000000..85e95d5
--- /dev/null
+++ b/statsd/src/config/ConfigKeyWithPackage.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::hash;
+using std::string;
+
+/**
+ * A config key that uses a package name instead of a uid. Generally, ConfigKey which uses a uid
+ * should be used. This is currently only used for restricted metrics changed operation.
+ */
+class ConfigKeyWithPackage {
+public:
+    ConfigKeyWithPackage(const string& package, const int64_t id) : mPackage(package), mId(id) {
+    }
+
+    inline string GetPackage() const {
+        return mPackage;
+    }
+    inline int64_t GetId() const {
+        return mId;
+    }
+
+    inline bool operator<(const ConfigKeyWithPackage& that) const {
+        if (mPackage != that.mPackage) {
+            return mPackage < that.mPackage;
+        }
+        return mId < that.mId;
+    };
+
+    inline bool operator==(const ConfigKeyWithPackage& that) const {
+        return mPackage == that.mPackage && mId == that.mId;
+    };
+
+private:
+    string mPackage;
+    int64_t mId;
+};
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/config/ConfigManager.cpp b/statsd/src/config/ConfigManager.cpp
index 570cd45..3a9c69c 100644
--- a/statsd/src/config/ConfigManager.cpp
+++ b/statsd/src/config/ConfigManager.cpp
@@ -37,6 +37,8 @@
 using std::string;
 using std::vector;
 
+using Status = ::ndk::ScopedAStatus;
+
 #define STATS_SERVICE_DIR "/data/misc/stats-service"
 
 using android::base::StringPrintf;
@@ -152,6 +154,61 @@
     }
 }
 
+void ConfigManager::SetRestrictedMetricsChangedReceiver(const string& configPackage,
+                                                        const int64_t configId,
+                                                        const int32_t callingUid,
+                                                        const shared_ptr<IPendingIntentRef>& pir) {
+    lock_guard<mutex> lock(mMutex);
+    ConfigKeyWithPackage configKey(configPackage, configId);
+    mRestrictedMetricsChangedReceivers[configKey][callingUid] = pir;
+}
+
+void ConfigManager::RemoveRestrictedMetricsChangedReceiver(const string& configPackage,
+                                                           const int64_t configId,
+                                                           const int32_t callingUid) {
+    lock_guard<mutex> lock(mMutex);
+    ConfigKeyWithPackage configKey(configPackage, configId);
+    const auto& it = mRestrictedMetricsChangedReceivers.find(configKey);
+    if (it != mRestrictedMetricsChangedReceivers.end()) {
+        it->second.erase(callingUid);
+        if (it->second.empty()) {
+            mRestrictedMetricsChangedReceivers.erase(it);
+        }
+    }
+}
+
+void ConfigManager::SendRestrictedMetricsBroadcast(const set<string>& configPackages,
+                                                   const int64_t configId,
+                                                   const set<int32_t>& delegateUids,
+                                                   const vector<int64_t>& metricIds) {
+    set<shared_ptr<IPendingIntentRef>> intentsToSend;
+    {
+        lock_guard<mutex> lock(mMutex);
+        // Invoke the pending intent for all matching configs, as long as the listening delegates
+        // match the allowed delegate uids specified by the config.
+        for (const string& configPackage : configPackages) {
+            ConfigKeyWithPackage key(configPackage, configId);
+            const auto& it = mRestrictedMetricsChangedReceivers.find(key);
+            if (it != mRestrictedMetricsChangedReceivers.end()) {
+                for (const auto& [delegateUid, pir] : it->second) {
+                    if (delegateUids.find(delegateUid) != delegateUids.end()) {
+                        intentsToSend.insert(pir);
+                    }
+                }
+            }
+        }
+    }
+
+    // Invoke the pending intents without holding the lock.
+    for (const shared_ptr<IPendingIntentRef>& pir : intentsToSend) {
+        Status status = pir->sendRestrictedMetricsChangedBroadcast(metricIds);
+        if (status.isOk()) {
+            VLOG("ConfigManager::SendRestrictedMetricsBroadcast succeeded");
+        }
+        // TODO (b/269419485): handle failures.
+    }
+}
+
 void ConfigManager::RemoveConfig(const ConfigKey& key) {
     vector<sp<ConfigListener>> broadcastList;
     {
@@ -181,6 +238,7 @@
     StorageManager::deleteSuffixedFiles(STATS_SERVICE_DIR, suffix.c_str());
 }
 
+// TODO(b/xxx): consider removing all receivers associated with this uid.
 void ConfigManager::RemoveConfigs(int uid) {
     vector<ConfigKey> removed;
     vector<sp<ConfigListener>> broadcastList;
diff --git a/statsd/src/config/ConfigManager.h b/statsd/src/config/ConfigManager.h
index 9a0f504..57ca00d 100644
--- a/statsd/src/config/ConfigManager.h
+++ b/statsd/src/config/ConfigManager.h
@@ -16,14 +16,15 @@
 
 #pragma once
 
-#include "config/ConfigKey.h"
-#include "config/ConfigListener.h"
-
 #include <aidl/android/os/IPendingIntentRef.h>
+#include <stdio.h>
+
 #include <mutex>
 #include <string>
 
-#include <stdio.h>
+#include "config/ConfigKey.h"
+#include "config/ConfigKeyWithPackage.h"
+#include "config/ConfigListener.h"
 
 using aidl::android::os::IPendingIntentRef;
 using std::shared_ptr;
@@ -113,6 +114,27 @@
                                             const shared_ptr<IPendingIntentRef>& pir);
 
     /**
+     * Sets the pending intent that is notified whenever the list of restricted metrics changes
+     */
+    void SetRestrictedMetricsChangedReceiver(const string& configPackage, const int64_t configId,
+                                             const int32_t callingUid,
+                                             const shared_ptr<IPendingIntentRef>& pir);
+
+    /**
+     * Erase any restricted metrics changed pending intents associated with this config key & uid.
+     */
+    void RemoveRestrictedMetricsChangedReceiver(const string& configPackage, const int64_t configId,
+                                                const int32_t callingUid);
+
+    /**
+     * Sends a restricted metrics broadcast for the valid config keys and delegate package
+     */
+    void SendRestrictedMetricsBroadcast(const std::set<string>& configPackages,
+                                        const int64_t configId,
+                                        const std::set<int32_t>& delegateUids,
+                                        const std::vector<int64_t>& metricIds);
+
+    /**
      * A configuration was removed.
      *
      * Reports this to listeners.
@@ -166,6 +188,13 @@
     std::map<int, shared_ptr<IPendingIntentRef>> mActiveConfigsChangedReceivers;
 
     /**
+     * Each uid can subscribe up to one receiver for a particular config to receive the restricted
+     * metrics for that config. The receiver is specified as IPendingIntentRef.
+     */
+    std::map<ConfigKeyWithPackage, std::map<int32_t, shared_ptr<IPendingIntentRef>>>
+            mRestrictedMetricsChangedReceivers;
+
+    /**
      * The ConfigListeners that will be told about changes.
      */
     std::vector<sp<ConfigListener>> mListeners;
diff --git a/statsd/src/metrics/MetricsManager.cpp b/statsd/src/metrics/MetricsManager.cpp
index b725f72..6c699b7 100644
--- a/statsd/src/metrics/MetricsManager.cpp
+++ b/statsd/src/metrics/MetricsManager.cpp
@@ -839,6 +839,15 @@
     return possibleUids.find(callingUid) != possibleUids.end();
 }
 
+vector<int64_t> MetricsManager::getAllMetricIds() const {
+    vector<int64_t> metricIds;
+    metricIds.reserve(mMetricProducerMap.size());
+    for (const auto& [metricId, _] : mMetricProducerMap) {
+        metricIds.push_back(metricId);
+    }
+    return metricIds;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/metrics/MetricsManager.h b/statsd/src/metrics/MetricsManager.h
index 7062b34..d336db5 100644
--- a/statsd/src/metrics/MetricsManager.h
+++ b/statsd/src/metrics/MetricsManager.h
@@ -168,10 +168,17 @@
         return mRestrictedMetricsDelegatePackageName.has_value();
     }
 
+    inline string getRestrictedMetricsDelegate() const {
+        return hasRestrictedMetricsDelegate() ? mRestrictedMetricsDelegatePackageName.value() : "";
+    }
+
     void enforceRestrictedDataTtls(const int64_t wallClockNs);
 
     bool validateRestrictedMetricsDelegate(const int32_t callingUid);
 
+    // Slow, should not be called in a hotpath.
+    vector<int64_t> getAllMetricIds() const;
+
 private:
     // For test only.
     inline int64_t getTtlEndNs() const { return mTtlEndNs; }