MediaMetrics: Add AnalyticsActions and AnalyticsState

Factor out common analytics code from audio specifics.

Allow for saving and clearing analytics state if audioserver crashes.

Test: atest mediametrics_tests
Test: instrumented check on audioserver restart
Bug: 138583596
Change-Id: I1073f3ef95f44a383a7f14b0c2ea6f978a84ee24
diff --git a/services/mediametrics/AnalyticsActions.h b/services/mediametrics/AnalyticsActions.h
new file mode 100644
index 0000000..5568c91
--- /dev/null
+++ b/services/mediametrics/AnalyticsActions.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2020 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 <media/MediaMetricsItem.h>
+#include <mutex>
+
+namespace android::mediametrics {
+
+/**
+ * AnalyticsActions consists of a map of pairs <trigger, action> which
+ * are evaluated for a given incoming MediaMetrics item.
+ *
+ * A vector of Actions are returned from getActionsForItem() which
+ * should be executed outside of any locks.
+ *
+ * Mediametrics assumes weak consistency, which is fine as the analytics database
+ * is generally strictly increasing in size (until gc removes values that are
+ * supposedly no longer needed).
+ */
+
+class AnalyticsActions {
+public:
+
+    using Elem = mediametrics::Item::Prop::Elem;
+    /**
+     * Trigger: a pair consisting of
+     * std::string: A wildcard url specifying a property in the item,
+     *              where '*' indicates 0 or more arbitrary characters
+     *              for the item key match.
+     * Elem: A value that needs to match exactly.
+     *
+     * Trigger is used in a map sort;  default less with std::string as primary key.
+     * The wildcard accepts a string with '*' as being 0 or more arbitrary
+     * characters for the item key match.  A wildcard is preferred over general
+     * regexp for simple fast lookup.
+     *
+     * TODO: incorporate a regexp option.
+     */
+    using Trigger = std::pair<std::string, Elem>;
+
+    /**
+     * Function: The function to be executed.
+     */
+    using Function = std::function<
+            void(const std::shared_ptr<const mediametrics::Item>& item)>;
+
+    /**
+     * Action:  An action to execute.  This is a shared pointer to Function.
+     */
+    using Action = std::shared_ptr<Function>;
+
+    /**
+     * Adds a new action.
+     *
+     * \param url references a property in the item with wildcards
+     * \param value references a value (cast to Elem automatically)
+     *              so be careful of the type.  It must be one of
+     *              the types acceptable to Elem.
+     * \param action is a function or lambda to execute if the url matches value
+     *               in the item.
+     */
+    template <typename T, typename U, typename A>
+    void addAction(T&& url, U&& value, A&& action) {
+        std::lock_guard l(mLock);
+        mFilters[ { std::forward<T>(url), std::forward<U>(value) } ]
+                = std::forward<A>(action);
+    }
+
+    // TODO: remove an action.
+
+    /**
+     * Get all the actions triggered for a particular item.
+     *
+     * \param item to be analyzed for actions.
+     */
+    std::vector<Action>
+    getActionsForItem(const std::shared_ptr<const mediametrics::Item>& item) {
+        std::vector<Action> actions;
+        std::lock_guard l(mLock);
+
+        // Essentially the code looks like this:
+        /*
+        for (auto &[trigger, action] : mFilters) {
+            if (isMatch(trigger, item)) {
+                actions.push_back(action);
+            }
+        }
+        */
+
+        // Optimization: there should only be one match for a non-wildcard url.
+        auto it = mFilters.upper_bound( {item->getKey(), std::monostate{} });
+        if (it != mFilters.end()) {
+            const auto &[trigger, action] = *it;
+            if (isMatch(trigger, item)) {
+                actions.push_back(action);
+            }
+        }
+
+        // Optimization: for wildcard URLs we go backwards until there is no
+        // match with the prefix before the wildcard.
+        while (it != mFilters.begin()) {  // this walks backwards, cannot start at begin.
+            const auto &[trigger, action] = *--it;  // look backwards
+            int ret = isWildcardMatch(trigger, item);
+            if (ret == mediametrics::Item::RECURSIVE_WILDCARD_CHECK_MATCH_FOUND) {
+                actions.push_back(action);    // match found.
+            } else if (ret == mediametrics::Item::RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD) {
+                break;                        // no match before wildcard.
+            }
+            // a wildcard was encountered when matching prefix, so we should check again.
+        }
+        return actions;
+    }
+
+private:
+
+    static inline bool isMatch(const Trigger& trigger,
+            const std::shared_ptr<const mediametrics::Item>& item) {
+        const auto& [key, elem] = trigger;
+        if (!startsWith(key, item->getKey())) return false;
+        // The trigger key is in format (item key).propName, so + 1 skips '.' delimeter.
+        const char *propName = key.c_str() + item->getKey().size() + 1;
+        return item->hasPropElem(propName, elem);
+    }
+
+    static inline int isWildcardMatch(const Trigger& trigger,
+            const std::shared_ptr<const mediametrics::Item>& item) {
+        const auto& [key, elem] = trigger;
+        return item->recursiveWildcardCheckElem(key.c_str(), elem);
+    }
+
+    mutable std::mutex mLock;
+    std::map<Trigger, Action> mFilters; // GUARDED_BY mLock
+};
+
+} // namespace android::mediametrics
diff --git a/services/mediametrics/AnalyticsState.h b/services/mediametrics/AnalyticsState.h
new file mode 100644
index 0000000..4711bb6
--- /dev/null
+++ b/services/mediametrics/AnalyticsState.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2020 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 "TimeMachine.h"
+#include "TransactionLog.h"
+
+namespace android::mediametrics {
+
+/**
+ * AnalyticsState consists of a TimeMachine and TransactionLog for a set
+ * of MediaMetrics Items.
+ *
+ * One can add new Items with the submit() method.
+ *
+ * The AnalyticsState may be cleared or duplicated to preserve state after crashes
+ * in services are detected.
+ *
+ * As its members may not be moveable due to mutexes, we use this encapsulation
+ * with a shared pointer in order to save it or duplicate it.
+ */
+class AnalyticsState {
+public:
+    /**
+     * Returns success if AnalyticsState accepts the item.
+     *
+     * A trusted source can create a new key, an untrusted source
+     * can only modify the key if the uid will match that authorized
+     * on the existing key.
+     *
+     * \param item the item to be submitted.
+     * \param isTrusted whether the transaction comes from a trusted source.
+     *        In this case, a trusted source is verified by binder
+     *        UID to be a system service by MediaMetrics service.
+     *        Do not use true if you haven't really checked!
+     *
+     * \return NO_ERROR on success or
+     *         PERMISSION_DENIED if the item cannot be put into the AnalyticsState.
+     */
+    status_t submit(const std::shared_ptr<const mediametrics::Item>& item, bool isTrusted) {
+        return mTimeMachine.put(item, isTrusted) ?: mTransactionLog.put(item);
+    }
+
+    /**
+     * Returns a pair consisting of the dump string, and the number of lines in the string.
+     *
+     * The number of lines in the returned pair is used as an optimization
+     * for subsequent line limiting.
+     *
+     * The TimeMachine and the TransactionLog are dumped separately under
+     * different locks, so may not be 100% consistent with the last data
+     * delivered.
+     *
+     * \param lines the maximum number of lines in the string returned.
+     */
+    std::pair<std::string, int32_t> dump(int32_t lines = INT32_MAX) const {
+        std::stringstream ss;
+        int32_t ll = lines;
+
+        if (ll > 0) {
+            ss << "TransactionLog:\n";
+            --ll;
+        }
+        if (ll > 0) {
+            auto [s, l] = mTransactionLog.dump(ll);
+            ss << s;
+            ll -= l;
+        }
+        if (ll > 0) {
+            ss << "TimeMachine:\n";
+            --ll;
+        }
+        if (ll > 0) {
+            auto [s, l] = mTimeMachine.dump(ll);
+            ss << s;
+            ll -= l;
+        }
+        return { ss.str(), lines - ll };
+    }
+
+    /**
+     * Clears the AnalyticsState.
+     */
+    void clear() {
+        mTimeMachine.clear();
+        mTransactionLog.clear();
+    }
+
+private:
+    // Note: TimeMachine and TransactionLog are individually locked.
+    // Access to these objects under multiple threads will be weakly synchronized,
+    // which is acceptable as modifications only increase the history (or with GC,
+    // eliminates very old history).
+
+    TimeMachine    mTimeMachine;
+    TransactionLog mTransactionLog;
+};
+
+} // namespace android::mediametrics
diff --git a/services/mediametrics/AudioAnalytics.cpp b/services/mediametrics/AudioAnalytics.cpp
index fffe517..f7ee051 100644
--- a/services/mediametrics/AudioAnalytics.cpp
+++ b/services/mediametrics/AudioAnalytics.cpp
@@ -27,6 +27,22 @@
 AudioAnalytics::AudioAnalytics()
 {
     ALOGD("%s", __func__);
+
+    // Add action to save AnalyticsState if audioserver is restarted.
+    // This triggers on an item of "audio.flinger"
+    // with a property "event" set to "AudioFlinger" (the constructor).
+    mActions.addAction(
+        "audio.flinger.event",
+        std::string("AudioFlinger"),
+        std::make_shared<AnalyticsActions::Function>(
+            [this](const std::shared_ptr<const android::mediametrics::Item> &){
+                ALOGW("Audioflinger() constructor event detected");
+                mPreviousAnalyticsState.set(std::make_shared<AnalyticsState>(
+                        *mAnalyticsState.get()));
+                // Note: get returns shared_ptr temp, whose lifetime is extended
+                // to end of full expression.
+                mAnalyticsState->clear();  // TODO: filter the analytics state.
+            }));
 }
 
 AudioAnalytics::~AudioAnalytics()
@@ -37,11 +53,14 @@
 status_t AudioAnalytics::submit(
         const std::shared_ptr<const mediametrics::Item>& item, bool isTrusted)
 {
-    if (startsWith(item->getKey(), "audio.")) {
-        return mTimeMachine.put(item, isTrusted)
-                ?: mTransactionLog.put(item);
-    }
-    return BAD_VALUE;
+    if (!startsWith(item->getKey(), "audio.")) return BAD_VALUE;
+    status_t status = mAnalyticsState->submit(item, isTrusted);
+    if (status != NO_ERROR) return status;  // may not be permitted.
+
+    // Only if the item was successfully submitted (permission)
+    // do we check triggered actions.
+    checkActions(item);
+    return NO_ERROR;
 }
 
 std::pair<std::string, int32_t> AudioAnalytics::dump(int32_t lines) const
@@ -50,24 +69,29 @@
     int32_t ll = lines;
 
     if (ll > 0) {
-        ss << "TransactionLog:\n";
-        --ll;
-    }
-    if (ll > 0) {
-        auto [s, l] = mTransactionLog.dump(ll);
+        auto [s, l] = mAnalyticsState->dump(ll);
         ss << s;
         ll -= l;
     }
     if (ll > 0) {
-        ss << "TimeMachine:\n";
+        ss << "Prior audioserver state:\n";
         --ll;
     }
     if (ll > 0) {
-        auto [s, l] = mTimeMachine.dump(ll);
+        auto [s, l] = mPreviousAnalyticsState->dump(ll);
         ss << s;
         ll -= l;
     }
     return { ss.str(), lines - ll };
 }
 
+void AudioAnalytics::checkActions(const std::shared_ptr<const mediametrics::Item>& item)
+{
+    auto actions = mActions.getActionsForItem(item); // internally locked.
+    // Execute actions with no lock held.
+    for (const auto& action : actions) {
+        (*action)(item);
+    }
+}
+
 } // namespace android
diff --git a/services/mediametrics/AudioAnalytics.h b/services/mediametrics/AudioAnalytics.h
index b931258..be885ec 100644
--- a/services/mediametrics/AudioAnalytics.h
+++ b/services/mediametrics/AudioAnalytics.h
@@ -16,8 +16,9 @@
 
 #pragma once
 
-#include "TimeMachine.h"
-#include "TransactionLog.h"
+#include "AnalyticsActions.h"
+#include "AnalyticsState.h"
+#include "Wrap.h"
 
 namespace android::mediametrics {
 
@@ -27,7 +28,6 @@
     AudioAnalytics();
     ~AudioAnalytics();
 
-    // TODO: update with conditions for keys.
     /**
      * Returns success if AudioAnalytics recognizes item.
      *
@@ -42,6 +42,10 @@
      *        In this case, a trusted source is verified by binder
      *        UID to be a system service by MediaMetrics service.
      *        Do not use true if you haven't really checked!
+     *
+     * \return NO_ERROR on success,
+     *         PERMISSION_DENIED if the item cannot be put into the AnalyticsState,
+     *         BAD_VALUE if the item key does not start with "audio.".
      */
     status_t submit(const std::shared_ptr<const mediametrics::Item>& item, bool isTrusted);
 
@@ -60,9 +64,22 @@
     std::pair<std::string, int32_t> dump(int32_t lines = INT32_MAX) const;
 
 private:
-    // The following are locked internally
-    TimeMachine mTimeMachine;
-    TransactionLog mTransactionLog;
+
+    /**
+     * Checks for any pending actions for a particular item.
+     *
+     * \param item to check against the current AnalyticsActions.
+     */
+    void checkActions(const std::shared_ptr<const mediametrics::Item>& item);
+
+    // Actions is individually locked
+    AnalyticsActions mActions;
+
+    // AnalyticsState is individually locked, and we use SharedPtrWrap
+    // to allow safe access even if the shared pointer changes underneath.
+
+    SharedPtrWrap<AnalyticsState> mAnalyticsState;
+    SharedPtrWrap<AnalyticsState> mPreviousAnalyticsState;
 };
 
 } // namespace android::mediametrics
diff --git a/services/mediametrics/MediaMetricsService.cpp b/services/mediametrics/MediaMetricsService.cpp
index b5bdd6f..3b95f7a 100644
--- a/services/mediametrics/MediaMetricsService.cpp
+++ b/services/mediametrics/MediaMetricsService.cpp
@@ -291,7 +291,11 @@
         }
         // TODO: maybe consider a better way of dumping audio analytics info.
         constexpr int32_t linesToDump = 1000;
-        result.append(mAudioAnalytics.dump(linesToDump).first.c_str());
+        auto [ dumpString, lines ] = mAudioAnalytics.dump(linesToDump);
+        result.append(dumpString.c_str());
+        if (lines == linesToDump) {
+            result.append("-- some lines may be truncated --\n");
+        }
     }
 
     write(fd, result.string(), result.size());
diff --git a/services/mediametrics/Wrap.h b/services/mediametrics/Wrap.h
new file mode 100644
index 0000000..99963fb
--- /dev/null
+++ b/services/mediametrics/Wrap.h
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2020 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 <memory>
+#include <mutex>
+
+namespace android::mediametrics {
+
+/**
+ * Wraps a shared-ptr for which member access through operator->() behaves
+ * as if the shared-ptr is atomically copied and then (without a lock) -> called.
+ *
+ * See related C++ 20:
+ * https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic2
+ *
+ * EXAMPLE:
+ *
+ * SharedPtrWrap<T> t{};
+ *
+ * thread1() {
+ *   t->func();  // safely executes either the original t or the one created by thread2.
+ * }
+ *
+ * thread2() {
+ *  t.set(std::make_shared<T>()); // overwrites the original t.
+ * }
+ */
+template <typename T>
+class SharedPtrWrap {
+    mutable std::mutex mLock;
+    std::shared_ptr<T> mPtr;
+
+public:
+    template <typename... Args>
+    explicit SharedPtrWrap(Args&&... args)
+        : mPtr(std::make_shared<T>(std::forward<Args>(args)...))
+    {}
+
+    /**
+     * Gets the current shared pointer.  This must return a value, not a reference.
+     *
+     * For compatibility with existing shared_ptr, we do not pass back a
+     * shared_ptr<const T> for the const getter.
+     */
+    std::shared_ptr<T> get() const {
+        std::lock_guard lock(mLock);
+        return mPtr;
+    }
+
+    /**
+     * Sets the current shared pointer, returning the previous shared pointer.
+     */
+    std::shared_ptr<T> set(std::shared_ptr<T> ptr) { // pass by value as we use swap.
+        std::lock_guard lock(mLock);
+        std::swap(ptr, mPtr);
+        return ptr;
+    }
+
+    /**
+     * Returns a shared pointer value representing T at the instant of time when
+     * the call executes. The lifetime of the shared pointer will
+     * be extended as we are returning an instance of the shared_ptr
+     * not a reference to it.  The destructor to the returned shared_ptr
+     * will be called sometime after the expression including the member function or
+     * the member variable is evaluated. Do not change to a reference!
+     */
+
+    // For compatibility with existing shared_ptr, we do not pass back a
+    // shared_ptr<const T> for the const operator pointer access.
+    std::shared_ptr<T> operator->() const {
+        return get();
+    }
+    /**
+     * We do not overload operator*() as the reference is not stable if the
+     * lock is not held.
+     */
+};
+
+/**
+ * Wraps member access to the class T by a lock.
+ *
+ * The object T is constructed within the LockWrap to guarantee
+ * locked access at all times.  When T's methods are accessed through ->,
+ * a monitor style lock is obtained to prevent multiple threads from executing
+ * methods in the object T at the same time.
+ * Suggested by Kevin R.
+ *
+ * EXAMPLE:
+ *
+ * // Accumulator class which is very slow, requires locking for multiple threads.
+ *
+ * class Accumulator {
+ *   int32_t value_ = 0;
+ * public:
+ *   void add(int32_t incr) {
+ *     const int32_t temp = value_;
+ *     sleep(0);  // yield
+ *     value_ = temp + incr;
+ *   }
+ *   int32_t get() { return value_; }
+ * };
+ *
+ * // We use LockWrap on Accumulator to have safe multithread access.
+ * android::mediametrics::LockWrap<Accumulator> a{}; // locked accumulator succeeds
+ *
+ * // Conversely, the following line fails:
+ * // auto a = std::make_shared<Accumulator>(); // this fails, only 50% adds atomic.
+ *
+ * constexpr size_t THREADS = 100;
+ * constexpr size_t ITERATIONS = 10;
+ * constexpr int32_t INCREMENT = 1;
+ *
+ * // Test by generating multiple threads, all adding simultaneously.
+ * std::vector<std::future<void>> threads(THREADS);
+ * for (size_t i = 0; i < THREADS; ++i) {
+ *     threads.push_back(std::async(std::launch::async, [&] {
+ *         for (size_t j = 0; j < ITERATIONS; ++j) {
+ *             a->add(INCREMENT);  // add needs locked access here.
+ *         }
+ *     }));
+ * }
+ * threads.clear();
+ *
+ * // If the add operations are not atomic, value will be smaller than expected.
+ * ASSERT_EQ(INCREMENT * THREADS * ITERATIONS, (size_t)a->get());
+ *
+ */
+template <typename T>
+class LockWrap {
+    /**
+      * Holding class that keeps the pointer and the lock.
+      *
+      * We return this holding class from operator->() to keep the lock until the
+      * method function or method variable access is completed.
+      */
+    class LockedPointer {
+        friend LockWrap;
+        LockedPointer(T *t, std::mutex *lock)
+            : mT(t), mLock(*lock) {}
+        T* const mT;
+        std::lock_guard<std::mutex> mLock;
+    public:
+        const T* operator->() const {
+            return mT;
+        }
+        T* operator->() {
+            return mT;
+        }
+    };
+
+    mutable std::mutex mLock;
+    mutable T mT;
+
+public:
+    template <typename... Args>
+    explicit LockWrap(Args&&... args) : mT(std::forward<Args>(args)...) {}
+
+    const LockedPointer operator->() const {
+        return LockedPointer(&mT, &mLock);
+    }
+    LockedPointer operator->() {
+        return LockedPointer(&mT, &mLock);
+    }
+
+    // @TestApi
+    bool isLocked() const {
+        if (mLock.try_lock()) {
+            mLock.unlock();
+            return false; // we were able to get the lock.
+        }
+        return true; // we were NOT able to get the lock.
+    }
+};
+
+} // namespace android::mediametrics
diff --git a/services/mediametrics/tests/mediametrics_tests.cpp b/services/mediametrics/tests/mediametrics_tests.cpp
index 90a8565..dea625c 100644
--- a/services/mediametrics/tests/mediametrics_tests.cpp
+++ b/services/mediametrics/tests/mediametrics_tests.cpp
@@ -52,6 +52,117 @@
   ASSERT_EQ(true, check);
 }
 
+TEST(mediametrics_tests, shared_ptr_wrap) {
+  // Test shared pointer wrap with simple access
+  android::mediametrics::SharedPtrWrap<std::string> s("123");
+  ASSERT_EQ('1', s->at(0));
+  ASSERT_EQ('2', s->at(1));
+  s->push_back('4');
+  ASSERT_EQ('4', s->at(3));
+
+  const android::mediametrics::SharedPtrWrap<std::string> s2("345");
+  ASSERT_EQ('3', s2->operator[](0));  // s2[0] == '3'
+  // we allow modification through a const shared pointer wrap
+  // for compatibility with shared_ptr.
+  s2->push_back('6');
+  ASSERT_EQ('6', s2->operator[](3));  // s2[3] == '6'
+
+  android::mediametrics::SharedPtrWrap<std::string> s3("");
+  s3.set(std::make_shared<std::string>("abc"));
+  ASSERT_EQ('b', s3->operator[](1)); // s2[1] = 'b';
+
+  // Use Thunk to check whether the destructor was called prematurely
+  // when setting the shared ptr wrap in the middle of a method.
+
+  class Thunk {
+    std::function<void(int)> mF;
+    const int mFinal;
+
+    public:
+      Thunk(decltype(mF) f, int final) : mF(f), mFinal(final) {}
+      ~Thunk() { mF(mFinal); }
+      void thunk(int value) { mF(value); }
+  };
+
+  int counter = 0;
+  android::mediametrics::SharedPtrWrap<Thunk> s4(
+    [&](int value) {
+      s4.set(std::make_shared<Thunk>([](int){}, 0)); // recursively set s4 while in s4.
+      ++counter;
+      ASSERT_EQ(value, counter);  // on thunk() value is 1, on destructor this is 2.
+    }, 2);
+
+  // This will fail if the shared ptr wrap doesn't hold a ref count during method access.
+  s4->thunk(1);
+}
+
+TEST(mediametrics_tests, lock_wrap) {
+  // Test lock wrap with simple access
+  android::mediametrics::LockWrap<std::string> s("123");
+  ASSERT_EQ('1', s->at(0));
+  ASSERT_EQ('2', s->at(1));
+  s->push_back('4');
+  ASSERT_EQ('4', s->at(3));
+
+  const android::mediametrics::LockWrap<std::string> s2("345");
+  ASSERT_EQ('3', s2->operator[](0));  // s2[0] == '3'
+  // note: we can't modify s2 due to const, s2->push_back('6');
+
+  android::mediametrics::LockWrap<std::string> s3("");
+  s3->operator=("abc");
+  ASSERT_EQ('b', s3->operator[](1)); // s2[1] = 'b';
+
+  // Use Thunk to check whether we have the lock when calling a method through LockWrap.
+
+  class Thunk {
+    std::function<void()> mF;
+
+    public:
+      Thunk(decltype(mF) f) : mF(f) {}
+      void thunk() { mF(); }
+  };
+
+  android::mediametrics::LockWrap<Thunk> s4([&]{
+    ASSERT_EQ(true, s4.isLocked()); // we must be locked when thunk() is called.
+  });
+
+  // This will fail if we are not locked during method access.
+  s4->thunk();
+}
+
+TEST(mediametrics_tests, lock_wrap_multithread) {
+  class Accumulator {
+    int32_t value_ = 0;
+  public:
+    void add(int32_t incr) {
+      const int32_t temp = value_;
+      sleep(0);  // yield
+      value_ = temp + incr;
+    }
+    int32_t get() { return value_; }
+  };
+
+  android::mediametrics::LockWrap<Accumulator> a{}; // locked accumulator succeeds
+  // auto a = std::make_shared<Accumulator>(); // this fails, only 50% adds atomic.
+
+  constexpr size_t THREADS = 100;
+  constexpr size_t ITERATIONS = 10;
+  constexpr int32_t INCREMENT = 1;
+
+  std::vector<std::future<void>> threads(THREADS);
+  for (size_t i = 0; i < THREADS; ++i) {
+    threads.push_back(std::async(std::launch::async, [&] {
+        for (size_t j = 0; j < ITERATIONS; ++j) {
+          a->add(INCREMENT);
+        }
+      }));
+  }
+  threads.clear();
+
+  // If the add operations are not atomic, value will be smaller than expected.
+  ASSERT_EQ(INCREMENT * THREADS * ITERATIONS, (size_t)a->get());
+}
+
 TEST(mediametrics_tests, instantiate) {
   sp mediaMetrics = new MediaMetricsService();
   status_t status;
@@ -592,6 +703,61 @@
   ASSERT_EQ((size_t)2, transactionLog.size());
 }
 
+TEST(mediametrics_tests, analytics_actions) {
+  mediametrics::AnalyticsActions analyticsActions;
+  bool action1 = false;
+  bool action2 = false;
+  bool action3 = false;
+  bool action4 = false;
+
+  // check to see whether various actions have been matched.
+  analyticsActions.addAction(
+      "audio.flinger.event",
+      std::string("AudioFlinger"),
+      std::make_shared<mediametrics::AnalyticsActions::Function>(
+          [&](const std::shared_ptr<const android::mediametrics::Item> &) {
+            action1 = true;
+          }));
+
+  analyticsActions.addAction(
+      "audio.*.event",
+      std::string("AudioFlinger"),
+      std::make_shared<mediametrics::AnalyticsActions::Function>(
+          [&](const std::shared_ptr<const android::mediametrics::Item> &) {
+            action2 = true;
+          }));
+
+  analyticsActions.addAction("audio.fl*n*g*r.event",
+      std::string("AudioFlinger"),
+      std::make_shared<mediametrics::AnalyticsActions::Function>(
+          [&](const std::shared_ptr<const android::mediametrics::Item> &) {
+            action3 = true;
+          }));
+
+  analyticsActions.addAction("audio.fl*gn*r.event",
+      std::string("AudioFlinger"),
+      std::make_shared<mediametrics::AnalyticsActions::Function>(
+          [&](const std::shared_ptr<const android::mediametrics::Item> &) {
+            action4 = true;
+          }));
+
+  // make a test item
+  auto item = std::make_shared<mediametrics::Item>("audio.flinger");
+  (*item).set("event", "AudioFlinger");
+
+  // get the actions and execute them
+  auto actions = analyticsActions.getActionsForItem(item);
+  for (const auto& action : actions) {
+    action->operator()(item);
+  }
+
+  // The following should match.
+  ASSERT_EQ(true, action1);
+  ASSERT_EQ(true, action2);
+  ASSERT_EQ(true, action3);
+  ASSERT_EQ(false, action4); // audio.fl*gn*r != audio.flinger
+}
+
 TEST(mediametrics_tests, audio_analytics_permission) {
   auto item = std::make_shared<mediametrics::Item>("audio.1");
   (*item).set("one", (int32_t)1)
@@ -614,14 +780,14 @@
 
   // TODO: Verify contents of AudioAnalytics.
   // Currently there is no getter API in AudioAnalytics besides dump.
-  ASSERT_EQ(4, audioAnalytics.dump(1000).second /* lines */);
+  ASSERT_EQ(9, audioAnalytics.dump(1000).second /* lines */);
 
   ASSERT_EQ(NO_ERROR, audioAnalytics.submit(item, true /* isTrusted */));
   // untrusted entities can add to an existing key
   ASSERT_EQ(NO_ERROR, audioAnalytics.submit(item2, false /* isTrusted */));
 
   // Check that we have some info in the dump.
-  ASSERT_LT(4, audioAnalytics.dump(1000).second /* lines */);
+  ASSERT_LT(9, audioAnalytics.dump(1000).second /* lines */);
 }
 
 TEST(mediametrics_tests, audio_analytics_dump) {