StatsEventCompat

StatsEventCompat is a wrapper library that toggles between the old
logging scheme and the new logging scheme. It was designed particularly
for the DNS Resolver module and should not be used by others.

We will merge libstatspush_compat and libstatssocket_q in a future CL.

Test: m libstatspush_compat
Test: bit libstatspush_compat_test:* (passes on Q and R)
Bug: 145534143

Change-Id: Idf35ccb6669798166475f08b2fbab40534b5db19
diff --git a/libstats/push_compat/Android.bp b/libstats/push_compat/Android.bp
new file mode 100644
index 0000000..9353990
--- /dev/null
+++ b/libstats/push_compat/Android.bp
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2019 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.
+//
+
+// =========================================================================
+// Native library that toggles between the old and new statsd socket
+// protocols. This library should only be used by DNS resolver or other
+// native modules on Q that log pushed atoms to statsd.
+// =========================================================================
+cc_defaults {
+    name: "libstatspush_compat_defaults",
+    srcs: ["StatsEventCompat.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    header_libs: ["libstatssocket_headers"],
+    static_libs: [
+        "libbase",
+        "liblog",
+        "libstatssocket_q",
+        "libutils"
+    ],
+}
+
+cc_library {
+    name: "libstatspush_compat",
+    defaults: ["libstatspush_compat_defaults"],
+    export_include_dirs: ["include"],
+    static_libs: ["libgtest_prod"],
+}
+
+cc_test {
+    name: "libstatspush_compat_test",
+    defaults: ["libstatspush_compat_defaults"],
+    test_suites: ["device_tests"],
+    srcs: [
+        "tests/StatsEventCompat_test.cpp",
+    ],
+    static_libs: ["libgmock"],
+}
+
diff --git a/libstats/push_compat/StatsEventCompat.cpp b/libstats/push_compat/StatsEventCompat.cpp
new file mode 100644
index 0000000..edfa070
--- /dev/null
+++ b/libstats/push_compat/StatsEventCompat.cpp
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include "include/StatsEventCompat.h"
+#include <android-base/properties.h>
+#include <android/api-level.h>
+#include <android/log.h>
+#include <dlfcn.h>
+#include <utils/SystemClock.h>
+
+using android::base::GetProperty;
+
+const static int kStatsEventTag = 1937006964;
+
+/* Checking ro.build.version.release is fragile, as the release field is
+ * an opaque string without structural guarantees. However, testing confirms
+ * that on Q devices, the property is "10," and on R, it is "R." Until
+ * android_get_device_api_level() is updated, this is the only solution.
+ *
+ * TODO(b/146019024): migrate to android_get_device_api_level()
+ */
+const bool StatsEventCompat::mPlatformAtLeastR =
+        GetProperty("ro.build.version.codename", "") == "R" ||
+        android_get_device_api_level() > __ANDROID_API_Q__;
+
+// definitions of static class variables
+bool StatsEventCompat::mAttemptedLoad = false;
+struct stats_event_api_table* StatsEventCompat::mStatsEventApi = nullptr;
+std::mutex StatsEventCompat::mLoadLock;
+
+StatsEventCompat::StatsEventCompat() : mEventQ(kStatsEventTag) {
+    // guard loading because StatsEventCompat might be called from multithreaded
+    // environment
+    {
+        std::lock_guard<std::mutex> lg(mLoadLock);
+        if (!mAttemptedLoad) {
+            void* handle = dlopen("libstatssocket.so", RTLD_NOW);
+            if (handle) {
+                mStatsEventApi = (struct stats_event_api_table*)dlsym(handle, "table");
+            } else {
+                ALOGE("dlopen failed: %s\n", dlerror());
+            }
+        }
+        mAttemptedLoad = true;
+    }
+
+    if (mStatsEventApi) {
+        mEventR = mStatsEventApi->obtain();
+    } else if (!mPlatformAtLeastR) {
+        mEventQ << android::elapsedRealtimeNano();
+    }
+}
+
+StatsEventCompat::~StatsEventCompat() {
+    if (mStatsEventApi) mStatsEventApi->release(mEventR);
+}
+
+void StatsEventCompat::setAtomId(int32_t atomId) {
+    if (mStatsEventApi) {
+        mStatsEventApi->set_atom_id(mEventR, (uint32_t)atomId);
+    } else if (!mPlatformAtLeastR) {
+        mEventQ << atomId;
+    }
+}
+
+void StatsEventCompat::writeInt32(int32_t value) {
+    if (mStatsEventApi) {
+        mStatsEventApi->write_int32(mEventR, value);
+    } else if (!mPlatformAtLeastR) {
+        mEventQ << value;
+    }
+}
+
+void StatsEventCompat::writeInt64(int64_t value) {
+    if (mStatsEventApi) {
+        mStatsEventApi->write_int64(mEventR, value);
+    } else if (!mPlatformAtLeastR) {
+        mEventQ << value;
+    }
+}
+
+void StatsEventCompat::writeFloat(float value) {
+    if (mStatsEventApi) {
+        mStatsEventApi->write_float(mEventR, value);
+    } else if (!mPlatformAtLeastR) {
+        mEventQ << value;
+    }
+}
+
+void StatsEventCompat::writeBool(bool value) {
+    if (mStatsEventApi) {
+        mStatsEventApi->write_bool(mEventR, value);
+    } else if (!mPlatformAtLeastR) {
+        mEventQ << value;
+    }
+}
+
+void StatsEventCompat::writeByteArray(const char* buffer, size_t length) {
+    if (mStatsEventApi) {
+        mStatsEventApi->write_byte_array(mEventR, (const uint8_t*)buffer, length);
+    } else if (!mPlatformAtLeastR) {
+        mEventQ.AppendCharArray(buffer, length);
+    }
+}
+
+void StatsEventCompat::writeString(const char* value) {
+    if (value == nullptr) value = "";
+
+    if (mStatsEventApi) {
+        mStatsEventApi->write_string8(mEventR, value);
+    } else if (!mPlatformAtLeastR) {
+        mEventQ << value;
+    }
+}
+
+void StatsEventCompat::writeAttributionChain(const int32_t* uids, size_t numUids,
+                                             const vector<const char*>& tags) {
+    if (mStatsEventApi) {
+        mStatsEventApi->write_attribution_chain(mEventR, (const uint32_t*)uids, tags.data(),
+                                                (uint8_t)numUids);
+    } else if (!mPlatformAtLeastR) {
+        mEventQ.begin();
+        for (size_t i = 0; i < numUids; i++) {
+            mEventQ.begin();
+            mEventQ << uids[i];
+            const char* tag = tags[i] ? tags[i] : "";
+            mEventQ << tag;
+            mEventQ.end();
+        }
+        mEventQ.end();
+    }
+}
+
+void StatsEventCompat::writeKeyValuePairs(const map<int, int32_t>& int32Map,
+                                          const map<int, int64_t>& int64Map,
+                                          const map<int, const char*>& stringMap,
+                                          const map<int, float>& floatMap) {
+    if (mStatsEventApi) {
+        vector<struct key_value_pair> pairs;
+
+        for (const auto& it : int32Map) {
+            pairs.push_back({.key = it.first, .valueType = INT32_TYPE, .int32Value = it.second});
+        }
+        for (const auto& it : int64Map) {
+            pairs.push_back({.key = it.first, .valueType = INT64_TYPE, .int64Value = it.second});
+        }
+        for (const auto& it : stringMap) {
+            pairs.push_back({.key = it.first, .valueType = STRING_TYPE, .stringValue = it.second});
+        }
+        for (const auto& it : floatMap) {
+            pairs.push_back({.key = it.first, .valueType = FLOAT_TYPE, .floatValue = it.second});
+        }
+
+        mStatsEventApi->write_key_value_pairs(mEventR, pairs.data(), (uint8_t)pairs.size());
+    }
+
+    else if (!mPlatformAtLeastR) {
+        mEventQ.begin();
+        writeKeyValuePairMap(int32Map);
+        writeKeyValuePairMap(int64Map);
+        writeKeyValuePairMap(stringMap);
+        writeKeyValuePairMap(floatMap);
+        mEventQ.end();
+    }
+}
+
+template <class T>
+void StatsEventCompat::writeKeyValuePairMap(const map<int, T>& keyValuePairMap) {
+    for (const auto& it : keyValuePairMap) {
+        mEventQ.begin();
+        mEventQ << it.first;
+        mEventQ << it.second;
+        mEventQ.end();
+    }
+}
+
+// explicitly specify which types we're going to use
+template void StatsEventCompat::writeKeyValuePairMap<int32_t>(const map<int, int32_t>&);
+template void StatsEventCompat::writeKeyValuePairMap<int64_t>(const map<int, int64_t>&);
+template void StatsEventCompat::writeKeyValuePairMap<float>(const map<int, float>&);
+template void StatsEventCompat::writeKeyValuePairMap<const char*>(const map<int, const char*>&);
+
+void StatsEventCompat::addBoolAnnotation(uint8_t annotationId, bool value) {
+    if (mStatsEventApi) mStatsEventApi->add_bool_annotation(mEventR, annotationId, value);
+    // Don't do anything if on Q.
+}
+
+void StatsEventCompat::addInt32Annotation(uint8_t annotationId, int32_t value) {
+    if (mStatsEventApi) mStatsEventApi->add_int32_annotation(mEventR, annotationId, value);
+    // Don't do anything if on Q.
+}
+
+int StatsEventCompat::writeToSocket() {
+    if (mStatsEventApi) {
+        mStatsEventApi->build(mEventR);
+        return mStatsEventApi->write(mEventR);
+    }
+
+    if (!mPlatformAtLeastR) return mEventQ.write(LOG_ID_STATS);
+
+    // We reach here only if we're on R, but libstatspush_compat was unable to
+    // be loaded using dlopen.
+    return -ENOLINK;
+}
+
+bool StatsEventCompat::usesNewSchema() {
+    return mStatsEventApi != nullptr;
+}
diff --git a/libstats/push_compat/include/StatsEventCompat.h b/libstats/push_compat/include/StatsEventCompat.h
new file mode 100644
index 0000000..a8cde68
--- /dev/null
+++ b/libstats/push_compat/include/StatsEventCompat.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 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 <gtest/gtest_prod.h>
+#include <map>
+#include <mutex>
+#include <vector>
+#include "stats_event.h"
+#include "stats_event_list.h"
+
+using std::map;
+using std::vector;
+
+class StatsEventCompat {
+  public:
+    StatsEventCompat();
+    ~StatsEventCompat();
+
+    void setAtomId(int32_t atomId);
+    void writeInt32(int32_t value);
+    void writeInt64(int64_t value);
+    void writeFloat(float value);
+    void writeBool(bool value);
+    void writeByteArray(const char* buffer, size_t length);
+    void writeString(const char* value);
+
+    // Pre-condition: numUids == tags.size()
+    void writeAttributionChain(const int32_t* uids, size_t numUids,
+                               const vector<const char*>& tags);
+
+    void writeKeyValuePairs(const map<int, int32_t>& int32Map, const map<int, int64_t>& int64Map,
+                            const map<int, const char*>& stringMap,
+                            const map<int, float>& floatMap);
+
+    void addBoolAnnotation(uint8_t annotationId, bool value);
+    void addInt32Annotation(uint8_t annotationId, int32_t value);
+
+    int writeToSocket();
+
+  private:
+    // static member variables
+    const static bool mPlatformAtLeastR;
+    static bool mAttemptedLoad;
+    static std::mutex mLoadLock;
+    static struct stats_event_api_table* mStatsEventApi;
+
+    // non-static member variables
+    struct stats_event* mEventR = nullptr;
+    stats_event_list mEventQ;
+
+    template <class T>
+    void writeKeyValuePairMap(const map<int, T>& keyValuePairMap);
+
+    bool usesNewSchema();
+    FRIEND_TEST(StatsEventCompatTest, TestDynamicLoading);
+};
diff --git a/libstats/push_compat/tests/StatsEventCompat_test.cpp b/libstats/push_compat/tests/StatsEventCompat_test.cpp
new file mode 100644
index 0000000..2be24ec
--- /dev/null
+++ b/libstats/push_compat/tests/StatsEventCompat_test.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include "include/StatsEventCompat.h"
+#include <android-base/properties.h>
+#include <android/api-level.h>
+#include <gtest/gtest.h>
+
+using android::base::GetProperty;
+
+/* Checking ro.build.version.release is fragile, as the release field is
+ * an opaque string without structural guarantees. However, testing confirms
+ * that on Q devices, the property is "10," and on R, it is "R." Until
+ * android_get_device_api_level() is updated, this is the only solution.
+ *
+ *
+ * TODO(b/146019024): migrate to android_get_device_api_level()
+ */
+const static bool mPlatformAtLeastR = GetProperty("ro.build.version.release", "") == "R" ||
+                                      android_get_device_api_level() > __ANDROID_API_Q__;
+
+TEST(StatsEventCompatTest, TestDynamicLoading) {
+    StatsEventCompat event;
+    EXPECT_EQ(mPlatformAtLeastR, event.usesNewSchema());
+}
diff --git a/libstats/socket/Android.bp b/libstats/socket/Android.bp
index beb009c..bd3d9ae 100644
--- a/libstats/socket/Android.bp
+++ b/libstats/socket/Android.bp
@@ -41,3 +41,9 @@
         "liblog",
     ],
 }
+
+cc_library_headers {
+    name: "libstatssocket_headers",
+    export_include_dirs: ["include"],
+    host_supported: true,
+}
diff --git a/libstats/socket/include/stats_event.h b/libstats/socket/include/stats_event.h
index 6a33d54..e7117d2 100644
--- a/libstats/socket/include/stats_event.h
+++ b/libstats/socket/include/stats_event.h
@@ -85,7 +85,7 @@
 // The build function can be called multiple times without error. If the event
 // has been built before, this function is a no-op.
 void stats_event_build(struct stats_event* event);
-void stats_event_write(struct stats_event* event);
+int stats_event_write(struct stats_event* event);
 void stats_event_release(struct stats_event* event);
 
 void stats_event_set_atom_id(struct stats_event* event, uint32_t atomId);
@@ -98,7 +98,7 @@
 void stats_event_write_byte_array(struct stats_event* event, const uint8_t* buf, size_t numBytes);
 
 // Buf must be null-terminated.
-void stats_event_write_string8(struct stats_event* event, const char* buf);
+void stats_event_write_string8(struct stats_event* event, const char* value);
 
 // Tags must be null-terminated.
 void stats_event_write_attribution_chain(struct stats_event* event, const uint32_t* uids,
@@ -127,9 +127,33 @@
                                       int32_t value);
 
 uint32_t stats_event_get_atom_id(struct stats_event* event);
+// Size is an output parameter.
 uint8_t* stats_event_get_buffer(struct stats_event* event, size_t* size);
 uint32_t stats_event_get_errors(struct stats_event* event);
 
+// This table is used by StatsEventCompat to access the stats_event API.
+struct stats_event_api_table {
+    struct stats_event* (*obtain)(void);
+    void (*build)(struct stats_event*);
+    int (*write)(struct stats_event*);
+    void (*release)(struct stats_event*);
+    void (*set_atom_id)(struct stats_event*, uint32_t);
+    void (*write_int32)(struct stats_event*, int32_t);
+    void (*write_int64)(struct stats_event*, int64_t);
+    void (*write_float)(struct stats_event*, float);
+    void (*write_bool)(struct stats_event*, bool);
+    void (*write_byte_array)(struct stats_event*, const uint8_t*, size_t);
+    void (*write_string8)(struct stats_event*, const char*);
+    void (*write_attribution_chain)(struct stats_event*, const uint32_t*, const char* const*,
+                                    uint8_t);
+    void (*write_key_value_pairs)(struct stats_event*, struct key_value_pair*, uint8_t);
+    void (*add_bool_annotation)(struct stats_event*, uint8_t, bool);
+    void (*add_int32_annotation)(struct stats_event*, uint8_t, int32_t);
+    uint32_t (*get_atom_id)(struct stats_event*);
+    uint8_t* (*get_buffer)(struct stats_event*, size_t*);
+    uint32_t (*get_errors)(struct stats_event*);
+};
+
 #ifdef __cplusplus
 }
 #endif  // __CPLUSPLUS
diff --git a/libstats/socket/stats_event.c b/libstats/socket/stats_event.c
index ef887e3..4098434 100644
--- a/libstats/socket/stats_event.c
+++ b/libstats/socket/stats_event.c
@@ -193,12 +193,12 @@
     append_byte_array(event, buf, numBytes);
 }
 
-// Buf is assumed to be encoded using UTF8
-void stats_event_write_string8(struct stats_event* event, const char* buf) {
+// Value is assumed to be encoded using UTF8
+void stats_event_write_string8(struct stats_event* event, const char* value) {
     if (event->errors) return;
 
     start_field(event, STRING_TYPE);
-    append_string(event, buf);
+    append_string(event, value);
 }
 
 // Tags are assumed to be encoded using UTF8
@@ -320,8 +320,28 @@
     event->built = true;
 }
 
-void stats_event_write(struct stats_event* event) {
+int stats_event_write(struct stats_event* event) {
     stats_event_build(event);
-
-    write_buffer_to_statsd(&event->buf, event->size, event->atomId);
+    return write_buffer_to_statsd(&event->buf, event->size, event->atomId);
 }
+
+struct stats_event_api_table table = {
+        stats_event_obtain,
+        stats_event_build,
+        stats_event_write,
+        stats_event_release,
+        stats_event_set_atom_id,
+        stats_event_write_int32,
+        stats_event_write_int64,
+        stats_event_write_float,
+        stats_event_write_bool,
+        stats_event_write_byte_array,
+        stats_event_write_string8,
+        stats_event_write_attribution_chain,
+        stats_event_write_key_value_pairs,
+        stats_event_add_bool_annotation,
+        stats_event_add_int32_annotation,
+        stats_event_get_atom_id,
+        stats_event_get_buffer,
+        stats_event_get_errors,
+};