Add StateTracker classes

One shared StateManager object will manage all StateTrackers across all configs, maintaining a map of atom ids to StateTrackers.

StateManager is responsible for initializing and removing StateTrackers and notifying them of event or StateListener changes.

Each StateTracker maintains a list of StateListeners and is responsible for tracking state values to primary keys and notifying StateListeners when a state change occurs.

Test: bit statsd_test:*
Bug: 136566566

Change-Id: Icb61c668a01b611aa75caa7bdc2a6801c650b119
diff --git a/cmds/statsd/src/state/StateListener.h b/cmds/statsd/src/state/StateListener.h
new file mode 100644
index 0000000..a31690a
--- /dev/null
+++ b/cmds/statsd/src/state/StateListener.h
@@ -0,0 +1,52 @@
+/*
+ * 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 <utils/RefBase.h>
+
+#include "HashableDimensionKey.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StateListener : public virtual RefBase {
+public:
+    StateListener(){};
+
+    virtual ~StateListener(){};
+
+    /**
+     * Interface for handling a state change.
+     *
+     * The old and new state values map to the original state values.
+     * StateTrackers only track the original state values and are unaware
+     * of higher-level state groups. MetricProducers hold information on
+     * state groups and are responsible for mapping original state values to
+     * the correct state group.
+     *
+     * [atomId]: The id of the state atom
+     * [primaryKey]: The primary field values of the state atom
+     * [oldState]: Previous state value before state change
+     * [newState]: Current state value after state change
+     */
+    virtual void onStateChanged(int atomId, const HashableDimensionKey& primaryKey, int oldState,
+                                int newState) = 0;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateManager.cpp b/cmds/statsd/src/state/StateManager.cpp
new file mode 100644
index 0000000..a3059c5
--- /dev/null
+++ b/cmds/statsd/src/state/StateManager.cpp
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+#define DEBUG false  // STOPSHIP if true
+#include "Log.h"
+
+#include "StateManager.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+StateManager& StateManager::getInstance() {
+    static StateManager sStateManager;
+    return sStateManager;
+}
+
+void StateManager::onLogEvent(const LogEvent& event) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mStateTrackers.find(event.GetTagId()) != mStateTrackers.end()) {
+        mStateTrackers[event.GetTagId()]->onLogEvent(event);
+    }
+}
+
+bool StateManager::registerListener(int stateAtomId, wp<StateListener> listener) {
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    // Check if state tracker already exists
+    if (mStateTrackers.find(stateAtomId) == mStateTrackers.end()) {
+        // Create a new state tracker iff atom is a state atom
+        auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(stateAtomId);
+        if (it != android::util::AtomsInfo::kStateAtomsFieldOptions.end()) {
+            mStateTrackers[stateAtomId] = new StateTracker(stateAtomId, it->second);
+        } else {
+            ALOGE("StateManager cannot register listener, Atom %d is not a state atom",
+                  stateAtomId);
+            return false;
+        }
+    }
+    mStateTrackers[stateAtomId]->registerListener(listener);
+    return true;
+}
+
+void StateManager::unregisterListener(int stateAtomId, wp<StateListener> listener) {
+    std::unique_lock<std::mutex> lock(mMutex);
+
+    // Hold the sp<> until the lock is released so that ~StateTracker() is
+    // not called while the lock is held.
+    sp<StateTracker> toRemove;
+
+    // Unregister listener from correct StateTracker
+    auto it = mStateTrackers.find(stateAtomId);
+    if (it != mStateTrackers.end()) {
+        it->second->unregisterListener(listener);
+
+        // Remove the StateTracker if it has no listeners
+        if (it->second->getListenersCount() == 0) {
+            toRemove = it->second;
+            mStateTrackers.erase(it);
+        }
+    } else {
+        ALOGE("StateManager cannot unregister listener, StateTracker for atom %d does not exist",
+              stateAtomId);
+    }
+    lock.unlock();
+}
+
+int StateManager::getState(int stateAtomId, const HashableDimensionKey& key) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mStateTrackers.find(stateAtomId) != mStateTrackers.end()) {
+        return mStateTrackers[stateAtomId]->getState(key);
+    }
+
+    return StateTracker::kStateUnknown;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateManager.h b/cmds/statsd/src/state/StateManager.h
new file mode 100644
index 0000000..ce60f14
--- /dev/null
+++ b/cmds/statsd/src/state/StateManager.h
@@ -0,0 +1,77 @@
+/*
+ * 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 <utils/Log.h>
+#include <utils/RefBase.h>
+#include "HashableDimensionKey.h"
+
+#include "state/StateListener.h"
+#include "state/StateTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StateManager : public virtual RefBase {
+public:
+    StateManager(){};
+
+    ~StateManager(){};
+
+    // Returns a pointer to the single, shared StateManager object.
+    static StateManager& getInstance();
+
+    // Notifies the correct StateTracker of an event.
+    void onLogEvent(const LogEvent& event);
+
+    // Returns true if stateAtomId is the id of a state atom and notifies the
+    // correct StateTracker to register the listener. If the correct
+    // StateTracker does not exist, a new StateTracker is created.
+    bool registerListener(int stateAtomId, wp<StateListener> listener);
+
+    // Notifies the correct StateTracker to unregister a listener
+    // and removes the tracker if it no longer has any listeners.
+    void unregisterListener(int stateAtomId, wp<StateListener> listener);
+
+    // Queries the correct StateTracker for the state that is mapped to the given
+    // query key.
+    // If the StateTracker doesn't exist, returns StateTracker::kStateUnknown.
+    int getState(int stateAtomId, const HashableDimensionKey& queryKey);
+
+    inline int getStateTrackersCount() {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return mStateTrackers.size();
+    }
+
+    inline int getListenersCount(int stateAtomId) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        if (mStateTrackers.find(stateAtomId) != mStateTrackers.end()) {
+            return mStateTrackers[stateAtomId]->getListenersCount();
+        }
+        return -1;
+    }
+
+private:
+  mutable std::mutex mMutex;
+
+  // Maps state atom ids to StateTrackers
+  std::unordered_map<int, sp<StateTracker>> mStateTrackers;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp
new file mode 100644
index 0000000..5a91950
--- /dev/null
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+#define DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
+#include "stats_util.h"
+
+#include "StateTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+StateTracker::StateTracker(const int atomId,
+                           const util::StateAtomFieldOptions& stateAtomInfo)
+  : mAtomId(atomId),
+    mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)) {
+    // create matcher for each primary field
+    // TODO(tsaichristine): handle when primary field is first uid in chain
+    for (const auto& primary : stateAtomInfo.primaryFields) {
+        Matcher matcher = getSimpleMatcher(atomId, primary);
+        mPrimaryFields.push_back(matcher);
+    }
+
+    // TODO(tsaichristine): set default state, reset state, and nesting
+}
+
+void StateTracker::onLogEvent(const LogEvent& event) {
+    // parse event for primary field values i.e. primary key
+    HashableDimensionKey primaryKey;
+    if (mPrimaryFields.size() > 0) {
+        if (!filterValues(mPrimaryFields, event.getValues(), &primaryKey) ||
+            primaryKey.getValues().size() != mPrimaryFields.size()) {
+            ALOGE("StateTracker error extracting primary key from log event.");
+            handleReset();
+            return;
+        }
+    } else {
+        // atom has no primary fields
+        primaryKey = DEFAULT_DIMENSION_KEY;
+    }
+
+    // parse event for state value
+    Value state;
+    int32_t stateValue;
+    if (!filterValues(mStateField, event.getValues(), &state) || state.getType() != INT) {
+        ALOGE("StateTracker error extracting state from log event. Type: %d", state.getType());
+        handlePartialReset(primaryKey);
+        return;
+    }
+    stateValue = state.int_value;
+
+    if (stateValue == mResetState) {
+        VLOG("StateTracker Reset state: %s", state.toString().c_str());
+        handleReset();
+    }
+
+    // track and update state
+    int32_t oldState = 0;
+    int32_t newState = 0;
+    updateState(primaryKey, stateValue, &oldState, &newState);
+
+    // notify all listeners if state has changed
+    if (oldState != newState) {
+        VLOG("StateTracker updated state");
+        for (auto listener : mListeners) {
+            auto sListener = listener.promote();  // safe access to wp<>
+            if (sListener != nullptr) {
+                sListener->onStateChanged(mAtomId, primaryKey, oldState, newState);
+            }
+        }
+    } else {
+        VLOG("StateTracker NO updated state");
+    }
+}
+
+void StateTracker::registerListener(wp<StateListener> listener) {
+    mListeners.insert(listener);
+}
+
+void StateTracker::unregisterListener(wp<StateListener> listener) {
+    mListeners.erase(listener);
+}
+
+int StateTracker::getState(const HashableDimensionKey& queryKey) const {
+    if (queryKey.getValues().size() == mPrimaryFields.size()) {
+        auto it = mStateMap.find(queryKey);
+        if (it != mStateMap.end()) {
+            return it->second.state;
+        }
+    } else if (queryKey.getValues().size() > mPrimaryFields.size()) {
+        ALOGE("StateTracker query key size > primary key size is illegal");
+    } else {
+        ALOGE("StateTracker query key size < primary key size is not supported");
+    }
+    return mDefaultState;
+}
+
+void StateTracker::handleReset() {
+    VLOG("StateTracker handle reset");
+    for (const auto pair : mStateMap) {
+        for (auto l : mListeners) {
+            auto sl = l.promote();
+            if (sl != nullptr) {
+                sl->onStateChanged(mAtomId, pair.first, pair.second.state, mDefaultState);
+            }
+        }
+    }
+    mStateMap.clear();
+}
+
+void StateTracker::handlePartialReset(const HashableDimensionKey& primaryKey) {
+    VLOG("StateTracker handle partial reset");
+    if (mStateMap.find(primaryKey) != mStateMap.end()) {
+        mStateMap.erase(primaryKey);
+    }
+}
+
+void StateTracker::updateState(const HashableDimensionKey& primaryKey, const int32_t eventState,
+                               int32_t* oldState, int32_t* newState) {
+    // get old state (either current state in map or default state)
+    auto it = mStateMap.find(primaryKey);
+    if (it != mStateMap.end()) {
+        *oldState = it->second.state;
+    } else {
+        *oldState = mDefaultState;
+    }
+
+    // update state map
+    if (eventState == mDefaultState) {
+        // remove (key, state) pair if state returns to default state
+        VLOG("\t StateTracker changed to default state")
+        mStateMap.erase(primaryKey);
+    } else {
+        mStateMap[primaryKey].state = eventState;
+        mStateMap[primaryKey].count = 1;
+    }
+    *newState = eventState;
+
+    // TODO(tsaichristine): support atoms with nested counting
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
new file mode 100644
index 0000000..f22706c
--- /dev/null
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -0,0 +1,95 @@
+/*
+ * 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 <statslog.h>
+#include <utils/RefBase.h>
+#include "HashableDimensionKey.h"
+#include "logd/LogEvent.h"
+
+#include "state/StateListener.h"
+
+#include <unordered_map>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StateTracker : public virtual RefBase {
+public:
+    StateTracker(const int atomId, const util::StateAtomFieldOptions& stateAtomInfo);
+
+    virtual ~StateTracker(){};
+
+    // Updates state map and notifies all listeners if a state change occurs.
+    // Checks if a state change has occurred by getting the state value from
+    // the log event and comparing the old and new states.
+    void onLogEvent(const LogEvent& event);
+
+    // Adds new listeners to set of StateListeners. If a listener is already
+    // registered, it is ignored.
+    void registerListener(wp<StateListener> listener);
+
+    void unregisterListener(wp<StateListener> listener);
+
+    // Returns the state value mapped to the given query key.
+    // If the key isn't mapped to a state or the key size doesn't match the
+    // primary key size, the default state is returned.
+    int getState(const HashableDimensionKey& queryKey) const;
+
+    inline int getListenersCount() const {
+        return mListeners.size();
+    }
+
+    const static int kStateUnknown = -1;
+
+private:
+    struct StateValueInfo {
+        int32_t state;  // state value
+        int count;      // nested count (only used for binary states)
+    };
+
+    const int32_t mAtomId;  // id of the state atom being tracked
+
+    Matcher mStateField;  // matches the atom's exclusive state field
+
+    std::vector<Matcher> mPrimaryFields;  // matches the atom's primary fields
+
+    int32_t mDefaultState = kStateUnknown;
+
+    int32_t mResetState;
+
+    // Maps primary key to state value info
+    std::unordered_map<HashableDimensionKey, StateValueInfo> mStateMap;
+
+    // Set of all StateListeners (objects listening for state changes)
+    std::set<wp<StateListener>> mListeners;
+
+    // Reset all state values in map to default state
+    void handleReset();
+
+    // Reset only the state value mapped to primary key to default state
+    void handlePartialReset(const HashableDimensionKey& primaryKey);
+
+    // Update the StateMap based on the received state value.
+    // Store the old and new states.
+    void updateState(const HashableDimensionKey& primaryKey, const int32_t eventState,
+                     int32_t* oldState, int32_t* newState);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android