BatteryStats: Update to use power.stats HAL

This CL updates BatteryStatsService to use power.stats HAL if available.
power.stats HAL is the new HAL that will be collecting platform and
subsystem power statistics going forward. BatteryStatsService will still
fall back to using the existing power HAL if power.stats HAL is not
implemented.

Bug: 120552155
Test: Disconnected the device from the charger, let battery drain 1-2%,
then ran "adb shell dumpsys batterystats" and visually inspected.
Test: Did the above test with and without power.stats HAL available
Test: Took a bugreport and verified that subsystem stats show up in
Volta Historian.

Change-Id: I5eb63fcbbf87a134bd8c34a11330db8b55df3894
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 6d72191..b4fe837 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -111,6 +111,7 @@
         "android.hardware.light@2.0",
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
+        "android.hardware.power.stats@1.0",
         "android.hardware.tetheroffload.config@1.0",
         "android.hardware.thermal@1.0",
         "android.hardware.tv.cec@1.0",
diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
index 0ff60e4..024760d 100644
--- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp
+++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
@@ -27,9 +27,11 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include <unordered_map>
 
 #include <android/hardware/power/1.0/IPower.h>
 #include <android/hardware/power/1.1/IPower.h>
+#include <android/hardware/power/stats/1.0/IPowerStats.h>
 #include <android/system/suspend/1.0/ISystemSuspend.h>
 #include <android/system/suspend/1.0/ISystemSuspendCallback.h>
 #include <android_runtime/AndroidRuntime.h>
@@ -74,6 +76,33 @@
 static jmethodID jputVoter = NULL;
 static jmethodID jputState = NULL;
 
+std::mutex gPowerHalMutex;
+std::unordered_map<uint32_t, std::string> gPowerStatsHalEntityNames = {};
+std::unordered_map<uint32_t, std::unordered_map<uint32_t, std::string>>
+    gPowerStatsHalStateNames = {};
+std::vector<uint32_t> gPowerStatsHalPlatformIds = {};
+std::vector<uint32_t> gPowerStatsHalSubsystemIds = {};
+sp<android::hardware::power::stats::V1_0::IPowerStats> gPowerStatsHalV1_0 = nullptr;
+std::function<void(JNIEnv*, jobject)> gGetLowPowerStatsImpl = {};
+std::function<jint(JNIEnv*, jobject)> gGetPlatformLowPowerStatsImpl = {};
+std::function<jint(JNIEnv*, jobject)> gGetSubsystemLowPowerStatsImpl = {};
+
+// The caller must be holding gPowerHalMutex.
+static void deinitPowerStatsLocked() {
+    gPowerStatsHalV1_0 = nullptr;
+}
+
+struct PowerHalDeathRecipient : virtual public hardware::hidl_death_recipient {
+    virtual void serviceDied(uint64_t cookie,
+            const wp<android::hidl::base::V1_0::IBase>& who) override {
+        // The HAL just died. Reset all handles to HAL services.
+        std::lock_guard<std::mutex> lock(gPowerHalMutex);
+        deinitPowerStatsLocked();
+    }
+};
+
+sp<PowerHalDeathRecipient> gDeathRecipient = new PowerHalDeathRecipient();
+
 class WakeupCallback : public ISystemSuspendCallback {
 public:
     Return<void> notifyWakeup(bool success) override {
@@ -202,18 +231,291 @@
     return mergedreasonpos - mergedreason;
 }
 
-static void getLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject jrpmStats) {
-    if (jrpmStats == NULL) {
-        jniThrowException(env, "java/lang/NullPointerException",
-                "The rpmstats jni input jobject jrpmStats is null.");
-        return;
+// The caller must be holding gPowerHalMutex.
+static bool checkResultLocked(const Return<void> &ret, const char* function) {
+    if (!ret.isOk()) {
+        ALOGE("%s failed: requested HAL service not available. Description: %s",
+            function, ret.description().c_str());
+        if (ret.isDeadObject()) {
+            deinitPowerStatsLocked();
+        }
+        return false;
     }
-    if (jgetAndUpdatePlatformState == NULL || jgetSubsystem == NULL
-            || jputVoter == NULL || jputState == NULL) {
-        ALOGE("A rpmstats jni jmethodID is null.");
+    return true;
+}
+
+// The caller must be holding gPowerHalMutex.
+// gPowerStatsHalV1_0 must not be null
+static bool initializePowerStats() {
+    using android::hardware::power::stats::V1_0::Status;
+    using android::hardware::power::stats::V1_0::PowerEntityType;
+
+    // Clear out previous content if we are re-initializing
+    gPowerStatsHalEntityNames.clear();
+    gPowerStatsHalStateNames.clear();
+    gPowerStatsHalPlatformIds.clear();
+    gPowerStatsHalSubsystemIds.clear();
+
+    Return<void> ret;
+    ret = gPowerStatsHalV1_0->getPowerEntityInfo([](auto infos, auto status) {
+        if (status != Status::SUCCESS) {
+            ALOGE("Error getting power entity info");
+            return;
+        }
+
+        // construct lookup table of powerEntityId to power entity name
+        // also construct vector of platform and subsystem IDs
+        for (auto info : infos) {
+            gPowerStatsHalEntityNames.emplace(info.powerEntityId, info.powerEntityName);
+            if (info.type == PowerEntityType::POWER_DOMAIN) {
+                gPowerStatsHalPlatformIds.emplace_back(info.powerEntityId);
+            } else {
+                gPowerStatsHalSubsystemIds.emplace_back(info.powerEntityId);
+            }
+        }
+    });
+    if (!checkResultLocked(ret, __func__)) {
+        return false;
+    }
+
+    ret = gPowerStatsHalV1_0->getPowerEntityStateInfo({}, [](auto stateSpaces, auto status) {
+        if (status != Status::SUCCESS) {
+            ALOGE("Error getting state info");
+            return;
+        }
+
+        // construct lookup table of powerEntityId, powerEntityStateId to power entity state name
+        for (auto stateSpace : stateSpaces) {
+            std::unordered_map<uint32_t, std::string> stateNames = {};
+            for (auto state : stateSpace.states) {
+                stateNames.emplace(state.powerEntityStateId,
+                    state.powerEntityStateName);
+            }
+            gPowerStatsHalStateNames.emplace(stateSpace.powerEntityId, stateNames);
+        }
+    });
+    if (!checkResultLocked(ret, __func__)) {
+        return false;
+    }
+
+    return (!gPowerStatsHalEntityNames.empty()) && (!gPowerStatsHalStateNames.empty());
+}
+
+// The caller must be holding gPowerHalMutex.
+static bool getPowerStatsHalLocked() {
+    if (gPowerStatsHalV1_0 == nullptr) {
+        gPowerStatsHalV1_0 = android::hardware::power::stats::V1_0::IPowerStats::getService();
+        if (gPowerStatsHalV1_0 == nullptr) {
+            ALOGE("Unable to get power.stats HAL service.");
+            return false;
+        }
+
+        // Link death recipient to power.stats service handle
+        hardware::Return<bool> linked = gPowerStatsHalV1_0->linkToDeath(gDeathRecipient, 0);
+        if (!linked.isOk()) {
+            ALOGE("Transaction error in linking to power.stats HAL death: %s",
+                    linked.description().c_str());
+            deinitPowerStatsLocked();
+            return false;
+        } else if (!linked) {
+            ALOGW("Unable to link to power.stats HAL death notifications");
+            // We should still continue even though linking failed
+        }
+        return initializePowerStats();
+    }
+    return true;
+}
+
+// The caller must be holding powerHalMutex.
+static void getPowerStatsHalLowPowerData(JNIEnv* env, jobject jrpmStats) {
+    using android::hardware::power::stats::V1_0::Status;
+
+    if (!getPowerStatsHalLocked()) {
+        ALOGE("failed to get low power stats");
         return;
     }
 
+    // Get power entity state residency data
+    bool success = false;
+    Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData({},
+        [&env, &jrpmStats, &success](auto results, auto status) {
+            if (status == Status::NOT_SUPPORTED) {
+                ALOGW("getPowerEntityStateResidencyData is not supported");
+                success = false;
+                return;
+            }
+
+            for (auto result : results) {
+                jobject jsubsystem = env->CallObjectMethod(jrpmStats, jgetSubsystem,
+                    env->NewStringUTF(gPowerStatsHalEntityNames.at(result.powerEntityId).c_str()));
+                if (jsubsystem == NULL) {
+                    ALOGE("The rpmstats jni jobject jsubsystem is null.");
+                    return;
+                }
+                for (auto stateResidency : result.stateResidencyData) {
+
+                    env->CallVoidMethod(jsubsystem, jputState,
+                        env->NewStringUTF(gPowerStatsHalStateNames.at(result.powerEntityId)
+                        .at(stateResidency.powerEntityStateId).c_str()),
+                        stateResidency.totalTimeInStateMs,
+                        stateResidency.totalStateEntryCount);
+                }
+            }
+            success = true;
+        });
+    checkResultLocked(ret, __func__);
+    if (!success) {
+        ALOGE("getPowerEntityStateResidencyData failed");
+    }
+}
+
+static jint getPowerStatsHalPlatformData(JNIEnv* env, jobject outBuf) {
+    using android::hardware::power::stats::V1_0::Status;
+    using hardware::power::stats::V1_0::PowerEntityStateResidencyResult;
+    using hardware::power::stats::V1_0::PowerEntityStateResidencyData;
+
+    if (!getPowerStatsHalLocked()) {
+        ALOGE("failed to get low power stats");
+        return -1;
+    }
+
+    char *output = (char*)env->GetDirectBufferAddress(outBuf);
+    char *offset = output;
+    int remaining = (int)env->GetDirectBufferCapacity(outBuf);
+    int total_added = -1;
+
+    // Get power entity state residency data
+    Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData(
+        gPowerStatsHalPlatformIds,
+        [&offset, &remaining, &total_added](auto results, auto status) {
+            if (status == Status::NOT_SUPPORTED) {
+                ALOGW("getPowerEntityStateResidencyData is not supported");
+                return;
+            }
+
+            for (size_t i = 0; i < results.size(); i++) {
+                const PowerEntityStateResidencyResult& result = results[i];
+
+                for (size_t j = 0; j < result.stateResidencyData.size(); j++) {
+                    const PowerEntityStateResidencyData& stateResidency =
+                        result.stateResidencyData[j];
+                    int added = snprintf(offset, remaining,
+                        "state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " ",
+                        j + 1, gPowerStatsHalStateNames.at(result.powerEntityId)
+                           .at(stateResidency.powerEntityStateId).c_str(),
+                        stateResidency.totalTimeInStateMs,
+                        stateResidency.totalStateEntryCount);
+                    if (added < 0) {
+                        break;
+                    }
+                    if (added > remaining) {
+                        added = remaining;
+                    }
+                    offset += added;
+                    remaining -= added;
+                    total_added += added;
+                }
+                if (remaining <= 0) {
+                    /* rewrite NULL character*/
+                    offset--;
+                    total_added--;
+                    ALOGE("power.stats Hal: buffer not enough");
+                    break;
+                }
+            }
+        });
+    if (!checkResultLocked(ret, __func__)) {
+        return -1;
+    }
+
+    total_added += 1;
+    return total_added;
+}
+
+static jint getPowerStatsHalSubsystemData(JNIEnv* env, jobject outBuf) {
+    using android::hardware::power::stats::V1_0::Status;
+    using hardware::power::stats::V1_0::PowerEntityStateResidencyResult;
+    using hardware::power::stats::V1_0::PowerEntityStateResidencyData;
+
+    if (!getPowerStatsHalLocked()) {
+        ALOGE("failed to get low power stats");
+        return -1;
+    }
+
+    char *output = (char*)env->GetDirectBufferAddress(outBuf);
+    char *offset = output;
+    int remaining = (int)env->GetDirectBufferCapacity(outBuf);
+    int total_added = -1;
+
+    // Get power entity state residency data
+    Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData(
+        gPowerStatsHalSubsystemIds,
+        [&offset, &remaining, &total_added](auto results, auto status) {
+            if (status == Status::NOT_SUPPORTED) {
+                ALOGW("getPowerEntityStateResidencyData is not supported");
+                return;
+            }
+
+            int added = snprintf(offset, remaining, "SubsystemPowerState ");
+            offset += added;
+            remaining -= added;
+            total_added += added;
+
+            for (size_t i = 0; i < results.size(); i++) {
+                const PowerEntityStateResidencyResult& result = results[i];
+                added = snprintf(offset, remaining, "subsystem_%zu name=%s ",
+                        i + 1, gPowerStatsHalEntityNames.at(result.powerEntityId).c_str());
+                if (added < 0) {
+                    break;
+                }
+
+                if (added > remaining) {
+                    added = remaining;
+                }
+
+                offset += added;
+                remaining -= added;
+                total_added += added;
+
+                for (size_t j = 0; j < result.stateResidencyData.size(); j++) {
+                    const PowerEntityStateResidencyData& stateResidency =
+                        result.stateResidencyData[j];
+                    added = snprintf(offset, remaining,
+                        "state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " last entry=%"
+                        PRIu64 " ", j + 1, gPowerStatsHalStateNames.at(result.powerEntityId)
+                           .at(stateResidency.powerEntityStateId).c_str(),
+                        stateResidency.totalTimeInStateMs,
+                        stateResidency.totalStateEntryCount,
+                        stateResidency.lastEntryTimestampMs);
+                    if (added < 0) {
+                        break;
+                    }
+                    if (added > remaining) {
+                        added = remaining;
+                    }
+                    offset += added;
+                    remaining -= added;
+                    total_added += added;
+                }
+                if (remaining <= 0) {
+                    /* rewrite NULL character*/
+                    offset--;
+                    total_added--;
+                    ALOGE("power.stats Hal: buffer not enough");
+                    break;
+                }
+            }
+        });
+    if (!checkResultLocked(ret, __func__)) {
+        return -1;
+    }
+
+    total_added += 1;
+    return total_added;
+}
+
+// The caller must be holding powerHalMutex.
+static void getPowerHalLowPowerData(JNIEnv* env, jobject jrpmStats) {
     sp<IPowerV1_0> powerHalV1_0 = getPowerHalV1_0();
     if (powerHalV1_0 == nullptr) {
         ALOGE("Power Hal not loaded");
@@ -286,17 +588,12 @@
     processPowerHalReturn(ret, "getSubsystemLowPowerStats");
 }
 
-static jint getPlatformLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) {
+static jint getPowerHalPlatformData(JNIEnv* env, jobject outBuf) {
     char *output = (char*)env->GetDirectBufferAddress(outBuf);
     char *offset = output;
     int remaining = (int)env->GetDirectBufferCapacity(outBuf);
     int total_added = -1;
 
-    if (outBuf == NULL) {
-        jniThrowException(env, "java/lang/NullPointerException", "null argument");
-        return -1;
-    }
-
     {
         sp<IPowerV1_0> powerHalV1_0 = getPowerHalV1_0();
         if (powerHalV1_0 == nullptr) {
@@ -365,7 +662,7 @@
     return total_added;
 }
 
-static jint getSubsystemLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) {
+static jint getPowerHalSubsystemData(JNIEnv* env, jobject outBuf) {
     char *output = (char*)env->GetDirectBufferAddress(outBuf);
     char *offset = output;
     int remaining = (int)env->GetDirectBufferCapacity(outBuf);
@@ -374,11 +671,6 @@
     // This is a IPower 1.1 API
     sp<IPowerV1_1> powerHal_1_1 = nullptr;
 
-    if (outBuf == NULL) {
-        jniThrowException(env, "java/lang/NullPointerException", "null argument");
-        return -1;
-    }
-
     {
         // Trying to get 1.1, this will succeed only for devices supporting 1.1
         powerHal_1_1 = getPowerHalV1_1();
@@ -458,6 +750,88 @@
     return total_added;
 }
 
+static void setUpPowerStatsLocked() {
+    // First see if power.stats HAL is available. Fall back to power HAL if
+    // power.stats HAL is unavailable.
+    if (android::hardware::power::stats::V1_0::IPowerStats::getService() != nullptr) {
+        ALOGI("Using power.stats HAL");
+        gGetLowPowerStatsImpl = getPowerStatsHalLowPowerData;
+        gGetPlatformLowPowerStatsImpl = getPowerStatsHalPlatformData;
+        gGetSubsystemLowPowerStatsImpl = getPowerStatsHalSubsystemData;
+    } else if (android::hardware::power::V1_0::IPower::getService() != nullptr) {
+        ALOGI("Using power HAL");
+        gGetLowPowerStatsImpl = getPowerHalLowPowerData;
+        gGetPlatformLowPowerStatsImpl = getPowerHalPlatformData;
+        gGetSubsystemLowPowerStatsImpl = getPowerHalSubsystemData;
+    }
+}
+
+static void getLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject jrpmStats) {
+    if (jrpmStats == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException",
+                "The rpmstats jni input jobject jrpmStats is null.");
+        return;
+    }
+    if (jgetAndUpdatePlatformState == NULL || jgetSubsystem == NULL
+            || jputVoter == NULL || jputState == NULL) {
+        ALOGE("A rpmstats jni jmethodID is null.");
+        return;
+    }
+
+    std::lock_guard<std::mutex> lock(gPowerHalMutex);
+
+    if (!gGetLowPowerStatsImpl) {
+        setUpPowerStatsLocked();
+    }
+
+    if (gGetLowPowerStatsImpl) {
+        return gGetLowPowerStatsImpl(env, jrpmStats);
+    }
+
+    ALOGE("Unable to load Power Hal or power.stats HAL");
+    return;
+}
+
+static jint getPlatformLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) {
+    if (outBuf == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", "null argument");
+        return -1;
+    }
+
+    std::lock_guard<std::mutex> lock(gPowerHalMutex);
+
+    if (!gGetPlatformLowPowerStatsImpl) {
+        setUpPowerStatsLocked();
+    }
+
+    if (gGetPlatformLowPowerStatsImpl) {
+        return gGetPlatformLowPowerStatsImpl(env, outBuf);
+    }
+
+    ALOGE("Unable to load Power Hal or power.stats HAL");
+    return -1;
+}
+
+static jint getSubsystemLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) {
+    if (outBuf == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", "null argument");
+        return -1;
+    }
+
+    std::lock_guard<std::mutex> lock(gPowerHalMutex);
+
+    if (!gGetSubsystemLowPowerStatsImpl) {
+        setUpPowerStatsLocked();
+    }
+
+    if (gGetSubsystemLowPowerStatsImpl) {
+        return gGetSubsystemLowPowerStatsImpl(env, outBuf);
+    }
+
+    ALOGE("Unable to load Power Hal or power.stats HAL");
+    return -1;
+}
+
 static const JNINativeMethod method_table[] = {
     { "nativeWaitWakeup", "(Ljava/nio/ByteBuffer;)I", (void*)nativeWaitWakeup },
     { "getLowPowerStats", "(Lcom/android/internal/os/RpmStats;)V", (void*)getLowPowerStats },