MediaMetrics: Move package name from uid to service utilities

Allows sharing class with AudioFlinger.

Bug: 129355845
Bug: 138583596
Test: atest mediametrics_tests
Test: adb shell dumpsys media.metrics
Change-Id: I6654dc9456dcd7c93821a8d83dd75d96b658f254
diff --git a/media/utils/Android.bp b/media/utils/Android.bp
index 5047b19..b7037e9 100644
--- a/media/utils/Android.bp
+++ b/media/utils/Android.bp
@@ -29,6 +29,7 @@
         "libc_malloc_debug_backtrace",
     ],
     shared_libs: [
+        "libaudioutils", // for clock.h
         "libbinder",
         "libcutils",
         "liblog",
diff --git a/media/utils/ServiceUtilities.cpp b/media/utils/ServiceUtilities.cpp
index a77311e..a33ed97 100644
--- a/media/utils/ServiceUtilities.cpp
+++ b/media/utils/ServiceUtilities.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "ServiceUtilities"
 
+#include <audio_utils/clock.h>
 #include <binder/AppOpsManager.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
@@ -24,6 +25,7 @@
 
 #include <iterator>
 #include <algorithm>
+#include <pwd.h>
 
 /* When performing permission checks we do not use permission cache for
  * runtime permissions (protection level dangerous) as they may change at
@@ -329,4 +331,128 @@
     }
 }
 
+// How long we hold info before we re-fetch it (24 hours) if we found it previously.
+static constexpr nsecs_t INFO_EXPIRATION_NS = 24 * 60 * 60 * NANOS_PER_SECOND;
+// Maximum info records we retain before clearing everything.
+static constexpr size_t INFO_CACHE_MAX = 1000;
+
+// The original code is from MediaMetricsService.cpp.
+mediautils::UidInfo::Info mediautils::UidInfo::getInfo(uid_t uid)
+{
+    const nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+    struct mediautils::UidInfo::Info info;
+    {
+        std::lock_guard _l(mLock);
+        auto it = mInfoMap.find(uid);
+        if (it != mInfoMap.end()) {
+            info = it->second;
+            ALOGV("%s: uid %d expiration %lld now %lld",
+                    __func__, uid, (long long)info.expirationNs, (long long)now);
+            if (info.expirationNs <= now) {
+                // purge the stale entry and fall into re-fetching
+                ALOGV("%s: entry for uid %d expired, now %lld",
+                        __func__, uid, (long long)now);
+                mInfoMap.erase(it);
+                info.uid = (uid_t)-1;  // this is always fully overwritten
+            }
+        }
+    }
+
+    // if we did not find it in our map, look it up
+    if (info.uid == (uid_t)(-1)) {
+        sp<IServiceManager> sm = defaultServiceManager();
+        sp<content::pm::IPackageManagerNative> package_mgr;
+        if (sm.get() == nullptr) {
+            ALOGE("%s: Cannot find service manager", __func__);
+        } else {
+            sp<IBinder> binder = sm->getService(String16("package_native"));
+            if (binder.get() == nullptr) {
+                ALOGE("%s: Cannot find package_native", __func__);
+            } else {
+                package_mgr = interface_cast<content::pm::IPackageManagerNative>(binder);
+            }
+        }
+
+        // find package name
+        std::string pkg;
+        if (package_mgr != nullptr) {
+            std::vector<std::string> names;
+            binder::Status status = package_mgr->getNamesForUids({(int)uid}, &names);
+            if (!status.isOk()) {
+                ALOGE("%s: getNamesForUids failed: %s",
+                        __func__, status.exceptionMessage().c_str());
+            } else {
+                if (!names[0].empty()) {
+                    pkg = names[0].c_str();
+                }
+            }
+        }
+
+        if (pkg.empty()) {
+            struct passwd pw{}, *result;
+            char buf[8192]; // extra buffer space - should exceed what is
+                            // required in struct passwd_pw (tested),
+                            // and even then this is only used in backup
+                            // when the package manager is unavailable.
+            if (getpwuid_r(uid, &pw, buf, sizeof(buf), &result) == 0
+                    && result != nullptr
+                    && result->pw_name != nullptr) {
+                pkg = result->pw_name;
+            }
+        }
+
+        // strip any leading "shared:" strings that came back
+        if (pkg.compare(0, 7, "shared:") == 0) {
+            pkg.erase(0, 7);
+        }
+
+        // determine how pkg was installed and the versionCode
+        std::string installer;
+        int64_t versionCode = 0;
+        bool notFound = false;
+        if (pkg.empty()) {
+            pkg = std::to_string(uid); // not found
+            notFound = true;
+        } else if (strchr(pkg.c_str(), '.') == nullptr) {
+            // not of form 'com.whatever...'; assume internal
+            // so we don't need to look it up in package manager.
+        } else if (package_mgr.get() != nullptr) {
+            String16 pkgName16(pkg.c_str());
+            binder::Status status = package_mgr->getInstallerForPackage(pkgName16, &installer);
+            if (!status.isOk()) {
+                ALOGE("%s: getInstallerForPackage failed: %s",
+                        __func__, status.exceptionMessage().c_str());
+            }
+
+            // skip if we didn't get an installer
+            if (status.isOk()) {
+                status = package_mgr->getVersionCodeForPackage(pkgName16, &versionCode);
+                if (!status.isOk()) {
+                    ALOGE("%s: getVersionCodeForPackage failed: %s",
+                            __func__, status.exceptionMessage().c_str());
+                }
+            }
+
+            ALOGV("%s: package '%s' installed by '%s' versioncode %lld",
+                    __func__, pkg.c_str(), installer.c_str(), (long long)versionCode);
+        }
+
+        // add it to the map, to save a subsequent lookup
+        std::lock_guard _l(mLock);
+        // first clear if we have too many cached elements.  This would be rare.
+        if (mInfoMap.size() >= INFO_CACHE_MAX) mInfoMap.clear();
+
+        // always overwrite
+        info.uid = uid;
+        info.package = std::move(pkg);
+        info.installer = std::move(installer);
+        info.versionCode = versionCode;
+        info.expirationNs = now + (notFound ? 0 : INFO_EXPIRATION_NS);
+        ALOGV("%s: adding uid %d package '%s' expirationNs: %lld",
+                __func__, uid, info.package.c_str(), (long long)info.expirationNs);
+        mInfoMap[uid] = info;
+    }
+    return info;
+}
+
 } // namespace android
diff --git a/media/utils/include/mediautils/ServiceUtilities.h b/media/utils/include/mediautils/ServiceUtilities.h
index e467058..4925cdb 100644
--- a/media/utils/include/mediautils/ServiceUtilities.h
+++ b/media/utils/include/mediautils/ServiceUtilities.h
@@ -28,6 +28,7 @@
 #include <map>
 #include <optional>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 namespace android {
@@ -118,6 +119,40 @@
     using Packages = std::vector<Package>;
     std::map<uid_t, Packages> mDebugLog;
 };
-}
+
+namespace mediautils {
+
+/**
+ * This class is used to retrieve (and cache) package information
+ * for a given uid.
+ */
+class UidInfo {
+public:
+    struct Info {
+        uid_t uid = -1;           // uid used for lookup.
+        std::string package;      // package name.
+        std::string installer;    // installer for the package (e.g. preload, play store).
+        int64_t versionCode = 0;  // reported version code.
+        int64_t expirationNs = 0; // after this time in SYSTEM_TIME_REALTIME we refetch.
+    };
+
+    /**
+     * Returns the package information for a UID.
+     *
+     * The package name will be the uid if we cannot find the associated name.
+     *
+     * \param uid is the uid of the app or service.
+     */
+    Info getInfo(uid_t uid);
+
+private:
+    std::mutex mLock;
+    // TODO: use concurrent hashmap with striped lock.
+    std::unordered_map<uid_t, Info> mInfoMap; // GUARDED_BY(mLock)
+};
+
+} // namespace mediautils
+
+} // namespace android
 
 #endif // ANDROID_MEDIAUTILS_SERVICEUTILITIES_H
diff --git a/services/mediametrics/Android.bp b/services/mediametrics/Android.bp
index 0840f31..20f346c 100644
--- a/services/mediametrics/Android.bp
+++ b/services/mediametrics/Android.bp
@@ -12,6 +12,7 @@
         "libbinder",
         "liblog",
         "libmediametricsservice",
+        "libmediautils",
         "libutils",
     ],
 
@@ -52,6 +53,7 @@
         "libbinder",
         "liblog",
         "libmediametrics",
+        "libmediautils",
         "libprotobuf-cpp-lite",
         "libstatslog",
         "libutils",
diff --git a/services/mediametrics/MediaMetricsService.cpp b/services/mediametrics/MediaMetricsService.cpp
index bbd13fa..b5bdd6f 100644
--- a/services/mediametrics/MediaMetricsService.cpp
+++ b/services/mediametrics/MediaMetricsService.cpp
@@ -57,6 +57,25 @@
     return (timeNs + NANOS_PER_SECOND / 2) / NANOS_PER_SECOND * NANOS_PER_SECOND;
 }
 
+/* static */
+bool MediaMetricsService::useUidForPackage(
+        const std::string& package, const std::string& installer)
+{
+    if (strchr(package.c_str(), '.') == NULL) {
+        return false;  // not of form 'com.whatever...'; assume internal and ok
+    } else if (strncmp(package.c_str(), "android.", 8) == 0) {
+        return false;  // android.* packages are assumed fine
+    } else if (strncmp(installer.c_str(), "com.android.", 12) == 0) {
+        return false;  // from play store
+    } else if (strncmp(installer.c_str(), "com.google.", 11) == 0) {
+        return false;  // some google source
+    } else if (strcmp(installer.c_str(), "preload") == 0) {
+        return false;  // preloads
+    } else {
+        return true;  // we're not sure where it came from, use uid only.
+    }
+}
+
 MediaMetricsService::MediaMetricsService()
         : mMaxRecords(kMaxRecords),
           mMaxRecordAgeNs(kMaxRecordAgeNs),
@@ -112,14 +131,19 @@
         break;
     }
 
-    // Overwrite package name and version if the caller was untrusted.
-    if (!isTrusted) {
-        mUidInfo.setPkgInfo(item, item->getUid(), true, true);
-    } else if (item->getPkgName().empty()) {
-        // empty, so fill out both parts
-        mUidInfo.setPkgInfo(item, item->getUid(), true, true);
-    } else {
-        // trusted, provided a package, do nothing
+    // Overwrite package name and version if the caller was untrusted or empty
+    if (!isTrusted || item->getPkgName().empty()) {
+        const uid_t uid = item->getUid();
+        mediautils::UidInfo::Info info = mUidInfo.getInfo(uid);
+        if (useUidForPackage(info.package, info.installer)) {
+            // remove uid information of unknown installed packages.
+            // TODO: perhaps this can be done just before uploading to Westworld.
+            item->setPkgName(std::to_string(uid));
+            item->setPkgVersionCode(0);
+        } else {
+            item->setPkgName(info.package);
+            item->setPkgVersionCode(info.versionCode);
+        }
     }
 
     ALOGV("%s: given uid %d; sanitized uid: %d sanitized pkg: %s "
@@ -450,150 +474,4 @@
     return false;
 }
 
-// How long we hold package info before we re-fetch it
-constexpr nsecs_t PKG_EXPIRATION_NS = 30 * 60 * NANOS_PER_SECOND; // 30 minutes
-
-// give me the package name, perhaps going to find it
-// manages its own mutex operations internally
-void MediaMetricsService::UidInfo::setPkgInfo(
-        mediametrics::Item *item, uid_t uid, bool setName, bool setVersion)
-{
-    ALOGV("%s: uid=%d", __func__, uid);
-
-    if (!setName && !setVersion) {
-        return;  // setting nothing? strange
-    }
-
-    const nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
-    struct UidToPkgInfo mapping;
-    {
-        std::lock_guard _l(mUidInfoLock);
-        auto it = mPkgMappings.find(uid);
-        if (it != mPkgMappings.end()) {
-            mapping = it->second;
-            ALOGV("%s: uid %d expiration %lld now %lld",
-                    __func__, uid, (long long)mapping.expiration, (long long)now);
-            if (mapping.expiration <= now) {
-                // purge the stale entry and fall into re-fetching
-                ALOGV("%s: entry for uid %d expired, now %lld",
-                        __func__, uid, (long long)now);
-                mPkgMappings.erase(it);
-                mapping.uid = (uid_t)-1;  // this is always fully overwritten
-            }
-        }
-    }
-
-    // if we did not find it
-    if (mapping.uid == (uid_t)(-1)) {
-        std::string pkg;
-        std::string installer;
-        int64_t versionCode = 0;
-
-        const struct passwd *pw = getpwuid(uid);
-        if (pw) {
-            pkg = pw->pw_name;
-        }
-
-        sp<IServiceManager> sm = defaultServiceManager();
-        sp<content::pm::IPackageManagerNative> package_mgr;
-        if (sm.get() == nullptr) {
-            ALOGE("%s: Cannot find service manager", __func__);
-        } else {
-            sp<IBinder> binder = sm->getService(String16("package_native"));
-            if (binder.get() == nullptr) {
-                ALOGE("%s: Cannot find package_native", __func__);
-            } else {
-                package_mgr = interface_cast<content::pm::IPackageManagerNative>(binder);
-            }
-        }
-
-        if (package_mgr != nullptr) {
-            std::vector<int> uids;
-            std::vector<std::string> names;
-            uids.push_back(uid);
-            binder::Status status = package_mgr->getNamesForUids(uids, &names);
-            if (!status.isOk()) {
-                ALOGE("%s: getNamesForUids failed: %s",
-                        __func__, status.exceptionMessage().c_str());
-            } else {
-                if (!names[0].empty()) {
-                    pkg = names[0].c_str();
-                }
-            }
-        }
-
-        // strip any leading "shared:" strings that came back
-        if (pkg.compare(0, 7, "shared:") == 0) {
-            pkg.erase(0, 7);
-        }
-        // determine how pkg was installed and the versionCode
-        if (pkg.empty()) {
-            pkg = std::to_string(uid); // no name for us to manage
-        } else if (strchr(pkg.c_str(), '.') == NULL) {
-            // not of form 'com.whatever...'; assume internal and ok
-        } else if (strncmp(pkg.c_str(), "android.", 8) == 0) {
-            // android.* packages are assumed fine
-        } else if (package_mgr.get() != nullptr) {
-            String16 pkgName16(pkg.c_str());
-            binder::Status status = package_mgr->getInstallerForPackage(pkgName16, &installer);
-            if (!status.isOk()) {
-                ALOGE("%s: getInstallerForPackage failed: %s",
-                        __func__, status.exceptionMessage().c_str());
-            }
-
-            // skip if we didn't get an installer
-            if (status.isOk()) {
-                status = package_mgr->getVersionCodeForPackage(pkgName16, &versionCode);
-                if (!status.isOk()) {
-                    ALOGE("%s: getVersionCodeForPackage failed: %s",
-                            __func__, status.exceptionMessage().c_str());
-                }
-            }
-
-            ALOGV("%s: package '%s' installed by '%s' versioncode %lld",
-                    __func__, pkg.c_str(), installer.c_str(), (long long)versionCode);
-
-            if (strncmp(installer.c_str(), "com.android.", 12) == 0) {
-                // from play store, we keep info
-            } else if (strncmp(installer.c_str(), "com.google.", 11) == 0) {
-                // some google source, we keep info
-            } else if (strcmp(installer.c_str(), "preload") == 0) {
-                // preloads, we keep the info
-            } else if (installer.c_str()[0] == '\0') {
-                // sideload (no installer); report UID only
-                pkg = std::to_string(uid);
-                versionCode = 0;
-            } else {
-                // unknown installer; report UID only
-                pkg = std::to_string(uid);
-                versionCode = 0;
-            }
-        } else {
-            // unvalidated by package_mgr just send uid.
-            pkg = std::to_string(uid);
-        }
-
-        // add it to the map, to save a subsequent lookup
-        std::lock_guard _l(mUidInfoLock);
-        // always overwrite
-        mapping.uid = uid;
-        mapping.pkg = std::move(pkg);
-        mapping.installer = std::move(installer);
-        mapping.versionCode = versionCode;
-        mapping.expiration = now + PKG_EXPIRATION_NS;
-        ALOGV("%s: adding uid %d pkg '%s' expiration: %lld",
-                __func__, uid, mapping.pkg.c_str(), (long long)mapping.expiration);
-        mPkgMappings[uid] = mapping;
-    }
-
-    if (mapping.uid != (uid_t)(-1)) {
-        if (setName) {
-            item->setPkgName(mapping.pkg);
-        }
-        if (setVersion) {
-            item->setPkgVersionCode(mapping.versionCode);
-        }
-    }
-}
-
 } // namespace android
diff --git a/services/mediametrics/MediaMetricsService.h b/services/mediametrics/MediaMetricsService.h
index b736c30..74a114a 100644
--- a/services/mediametrics/MediaMetricsService.h
+++ b/services/mediametrics/MediaMetricsService.h
@@ -24,6 +24,7 @@
 
 // IMediaMetricsService must include Vector, String16, Errors
 #include <media/IMediaMetricsService.h>
+#include <mediautils/ServiceUtilities.h>
 #include <utils/String8.h>
 
 #include "AudioAnalytics.h"
@@ -62,6 +63,11 @@
      */
     static nsecs_t roundTime(nsecs_t timeNs);
 
+    /**
+     * Returns true if we should use uid for package name when uploading to WestWorld.
+     */
+    static bool useUidForPackage(const std::string& package, const std::string& installer);
+
 protected:
 
     // Internal call where release is true if ownership of item is transferred
@@ -96,27 +102,10 @@
     const size_t mMaxRecordsExpiredAtOnce;
     const int mDumpProtoDefault;
 
-    class UidInfo {
-    public:
-        void setPkgInfo(mediametrics::Item *item, uid_t uid, bool setName, bool setVersion);
-
-    private:
-        std::mutex mUidInfoLock;
-
-        struct UidToPkgInfo {
-            uid_t uid = -1;
-            std::string pkg;
-            std::string installer;
-            int64_t versionCode = 0;
-            nsecs_t expiration = 0;  // TODO: remove expiration.
-        };
-
-        // TODO: use concurrent hashmap with striped lock.
-        std::unordered_map<uid_t, struct UidToPkgInfo> mPkgMappings; // GUARDED_BY(mUidInfoLock)
-    } mUidInfo;  // mUidInfo can be accessed without lock (locked internally)
-
     std::atomic<int64_t> mItemsSubmitted{}; // accessed outside of lock.
 
+    mediautils::UidInfo mUidInfo;  // mUidInfo can be accessed without lock (locked internally)
+
     mediametrics::AudioAnalytics mAudioAnalytics;
 
     std::mutex mLock;
diff --git a/services/mediametrics/tests/Android.bp b/services/mediametrics/tests/Android.bp
index 9eb2d89..bdeda30 100644
--- a/services/mediametrics/tests/Android.bp
+++ b/services/mediametrics/tests/Android.bp
@@ -17,6 +17,7 @@
         "liblog",
         "libmediametrics",
         "libmediametricsservice",
+        "libmediautils",
         "libutils",
     ],
 
diff --git a/services/mediametrics/tests/mediametrics_tests.cpp b/services/mediametrics/tests/mediametrics_tests.cpp
index ccf2d63..90a8565 100644
--- a/services/mediametrics/tests/mediametrics_tests.cpp
+++ b/services/mediametrics/tests/mediametrics_tests.cpp
@@ -89,6 +89,32 @@
   mediaMetrics->dump(fileno(stdout), {} /* args */);
 }
 
+TEST(mediametrics_tests, package_installer_check) {
+  ASSERT_EQ(false, MediaMetricsService::useUidForPackage(
+      "abcd", "installer"));  // ok, package name has no dot.
+  ASSERT_EQ(false, MediaMetricsService::useUidForPackage(
+      "android.com", "installer"));  // ok, package name starts with android
+
+  ASSERT_EQ(false, MediaMetricsService::useUidForPackage(
+      "abc.def", "com.android.foo"));  // ok, installer name starts with com.android
+  ASSERT_EQ(false, MediaMetricsService::useUidForPackage(
+      "123.456", "com.google.bar"));  // ok, installer name starts with com.google
+  ASSERT_EQ(false, MediaMetricsService::useUidForPackage(
+      "r2.d2", "preload"));  // ok, installer name is preload
+
+  ASSERT_EQ(true, MediaMetricsService::useUidForPackage(
+      "abc.def", "installer"));  // unknown installer
+  ASSERT_EQ(true, MediaMetricsService::useUidForPackage(
+      "123.456", "installer")); // unknown installer
+  ASSERT_EQ(true, MediaMetricsService::useUidForPackage(
+      "r2.d2", "preload23"));  // unknown installer
+
+  ASSERT_EQ(true, MediaMetricsService::useUidForPackage(
+      "com.android.foo", "abc.def"));  // unknown installer
+  ASSERT_EQ(true, MediaMetricsService::useUidForPackage(
+      "com.google.bar", "123.456"));  // unknown installer
+}
+
 TEST(mediametrics_tests, item_manipulation) {
   mediametrics::Item item("audiorecord");