health 2.0 vts: add battery current test.
Require the following on battery current:
- skip health info tests if current is 0 (indicates the value is unavailable)
- when charging, positive
- when discharging / not charging, negative
- when unknown, zero
- batteryCurrentAverage should be similar to batteryCurrentNow.
All new tests are executed over
a period of time to avoid effects of fluctuation in
battery current, and ensure battery status is initialized.
Requirements are enforced on devices launching with R
only.
Require the following on battery status:
- if not charging / full / charging, must be connected
- if discharging, must not be connected.
Test: run on coral, tests are skipped
Test: run on cuttlefish, test passes
Bug: 136717180
Change-Id: Ic5a714b830ffeca1dcd000c6cad0fbfe8a8710ed
diff --git a/health/2.0/vts/functional/VtsHalHealthV2_0TargetTest.cpp b/health/2.0/vts/functional/VtsHalHealthV2_0TargetTest.cpp
index 0129883..441e2d7 100644
--- a/health/2.0/vts/functional/VtsHalHealthV2_0TargetTest.cpp
+++ b/health/2.0/vts/functional/VtsHalHealthV2_0TargetTest.cpp
@@ -16,11 +16,15 @@
#define LOG_TAG "health_hidl_hal_test"
+#include <chrono>
#include <mutex>
#include <set>
#include <string>
+#include <thread>
#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android/hardware/health/1.0/types.h>
#include <android/hardware/health/2.0/IHealth.h>
#include <android/hardware/health/2.0/types.h>
#include <gflags/gflags.h>
@@ -32,15 +36,25 @@
using ::testing::AssertionFailure;
using ::testing::AssertionResult;
using ::testing::AssertionSuccess;
+using namespace std::chrono_literals;
DEFINE_bool(force, false, "Force test healthd even when the default instance is present.");
+// Return expr if it is evaluated to false.
+#define TEST_AND_RETURN(expr) \
+ do { \
+ auto res = (expr); \
+ if (!res) return res; \
+ } while (0)
+
namespace android {
namespace hardware {
namespace health {
-namespace V2_0 {
using V1_0::BatteryStatus;
+using V1_0::toString;
+
+namespace V2_0 {
class HealthHidlTest : public ::testing::TestWithParam<std::string> {
public:
@@ -309,6 +323,357 @@
PerInstance, HealthHidlTest,
testing::ValuesIn(android::hardware::getAllHalInstanceNames(IHealth::descriptor)),
android::hardware::PrintInstanceNameToString);
+
+// For battery current tests, value may not be stable if the battery current has fluctuated.
+// Retry in a bit more time (with the following timeout) and consider the test successful if it
+// has succeed once.
+static constexpr auto gBatteryTestTimeout = 1min;
+// Tests on battery current signs are only enforced on devices launching with Android 11.
+static constexpr int64_t gBatteryTestMinShippingApiLevel = 30;
+static constexpr double gCurrentCompareFactor = 0.50;
+
+// Tuple for all IHealth::get* API return values.
+template <typename T>
+struct HalResult {
+ Result result;
+ T value;
+};
+
+// Needs to be called repeatedly within a period of time to ensure values are initialized.
+static AssertionResult IsBatteryCurrentSignCorrect(HalResult<BatteryStatus> status,
+ HalResult<int32_t> current,
+ bool acceptZeroCurrentAsUnknown) {
+ // getChargeStatus / getCurrentNow / getCurrentAverage / getHealthInfo already tested above.
+ // Here, just skip if not ok.
+ if (status.result != Result::SUCCESS) {
+ return AssertionSuccess() << "getChargeStatus / getHealthInfo returned "
+ << toString(status.result) << ", skipping";
+ }
+
+ if (current.result != Result::SUCCESS) {
+ return AssertionSuccess() << "getCurrentNow / getCurrentAverage returned "
+ << toString(current.result) << ", skipping";
+ }
+
+ // For IHealth.getCurrentNow/Average, if current is not available, it is expected that
+ // current.result == Result::NOT_SUPPORTED, which is checked above. Hence, zero current is
+ // not treated as unknown values.
+ // For IHealth.getHealthInfo, if current is not available, health_info.current_* == 0.
+ // Caller of this function provides current.result == Result::SUCCESS. Hence, just skip the
+ // check.
+ if (current.value == 0 && acceptZeroCurrentAsUnknown) {
+ return AssertionSuccess()
+ << "current is 0, which indicates the value may not be available. Skipping.";
+ }
+
+ switch (status.value) {
+ case BatteryStatus::UNKNOWN:
+ if (current.value != 0) {
+ // BatteryStatus may be UNKNOWN initially with a non-zero current value, but
+ // after it is initialized, it should be known.
+ return AssertionFailure()
+ << "BatteryStatus is UNKNOWN but current is not 0. Actual: "
+ << current.value;
+ }
+ break;
+ case BatteryStatus::CHARGING:
+ if (current.value <= 0) {
+ return AssertionFailure()
+ << "BatteryStatus is CHARGING but current is not positive. Actual: "
+ << current.value;
+ }
+ break;
+ case BatteryStatus::NOT_CHARGING:
+ if (current.value > 0) {
+ return AssertionFailure() << "BatteryStatus is " << toString(status.value)
+ << " but current is positive. Actual: " << current.value;
+ }
+ break;
+ case BatteryStatus::DISCHARGING:
+ if (current.value >= 0) {
+ return AssertionFailure()
+ << "BatteryStatus is " << toString(status.value)
+ << " but current is not negative. Actual: " << current.value;
+ }
+ break;
+ case BatteryStatus::FULL:
+ // Battery current may be positive or negative depending on the load.
+ break;
+ default:
+ return AssertionFailure() << "Unknown BatteryStatus " << toString(status.value);
+ }
+
+ return AssertionSuccess() << "BatteryStatus is " << toString(status.value)
+ << " and current has the correct sign: " << current.value;
+}
+
+static AssertionResult IsValueSimilar(int32_t dividend, int32_t divisor, double factor) {
+ auto difference = abs(dividend - divisor);
+ if (difference > factor * abs(divisor)) {
+ return AssertionFailure() << dividend << " and " << divisor << " are not similar.";
+ }
+ return AssertionSuccess() << dividend << " and " << divisor << " are similar.";
+}
+
+static AssertionResult IsBatteryCurrentSimilar(HalResult<BatteryStatus> status,
+ HalResult<int32_t> currentNow,
+ HalResult<int32_t> currentAverage) {
+ if (status.result == Result::SUCCESS && status.value == BatteryStatus::FULL) {
+ // No reason to test on full battery because battery current load fluctuates.
+ return AssertionSuccess() << "Battery is full, skipping";
+ }
+
+ // getCurrentNow / getCurrentAverage / getHealthInfo already tested above. Here, just skip if
+ // not SUCCESS or value 0.
+ if (currentNow.result != Result::SUCCESS || currentNow.value == 0) {
+ return AssertionSuccess() << "getCurrentNow returned " << toString(currentNow.result)
+ << " with value " << currentNow.value << ", skipping";
+ }
+
+ if (currentAverage.result != Result::SUCCESS || currentAverage.value == 0) {
+ return AssertionSuccess() << "getCurrentAverage returned "
+ << toString(currentAverage.result) << " with value "
+ << currentAverage.value << ", skipping";
+ }
+
+ // Check that the two values are similar. Note that the two tests uses a different
+ // divisor to ensure that they are actually pretty similar. For example,
+ // IsValueSimilar(5,10,0.4) returns true, but IsValueSimlar(10,5,0.4) returns false.
+ TEST_AND_RETURN(IsValueSimilar(currentNow.value, currentAverage.value, gCurrentCompareFactor)
+ << " for now vs. average. Check units.");
+ TEST_AND_RETURN(IsValueSimilar(currentAverage.value, currentNow.value, gCurrentCompareFactor)
+ << " for average vs. now. Check units.");
+ return AssertionSuccess() << "currentNow = " << currentNow.value
+ << " and currentAverage = " << currentAverage.value
+ << " are considered similar.";
+}
+
+// Test that f() returns AssertionSuccess() once in a given period of time.
+template <typename Duration, typename Function>
+static AssertionResult SucceedOnce(Duration d, Function f) {
+ AssertionResult result = AssertionFailure() << "Function never evaluated.";
+ auto end = std::chrono::system_clock::now() + d;
+ while (std::chrono::system_clock::now() <= end) {
+ result = f();
+ if (result) {
+ return result;
+ }
+ std::this_thread::sleep_for(2s);
+ }
+ return result;
+}
+
+uint64_t GetShippingApiLevel() {
+ uint64_t api_level = android::base::GetUintProperty<uint64_t>("ro.product.first_api_level", 0);
+ if (api_level != 0) {
+ return api_level;
+ }
+ return android::base::GetUintProperty<uint64_t>("ro.build.version.sdk", 0);
+}
+
+class BatteryTest : public HealthHidlTest {
+ public:
+ void SetUp() override {
+ HealthHidlTest::SetUp();
+
+ auto shippingApiLevel = GetShippingApiLevel();
+ if (shippingApiLevel < gBatteryTestMinShippingApiLevel) {
+ GTEST_SKIP() << "Skipping on devices with first API level " << shippingApiLevel;
+ }
+ }
+};
+
+TEST_P(BatteryTest, InstantCurrentAgainstChargeStatusInHealthInfo) {
+ auto testOnce = [&]() -> AssertionResult {
+ HalResult<HealthInfo> healthInfo;
+ TEST_AND_RETURN(isOk(mHealth->getHealthInfo([&](auto result, const auto& value) {
+ healthInfo = {result, value};
+ })));
+
+ return IsBatteryCurrentSignCorrect(
+ {healthInfo.result, healthInfo.value.legacy.batteryStatus},
+ {healthInfo.result, healthInfo.value.legacy.batteryCurrent},
+ true /* accept zero current as unknown */);
+ };
+ EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+ << "You may want to try again later when current_now becomes stable.";
+}
+
+TEST_P(BatteryTest, AverageCurrentAgainstChargeStatusInHealthInfo) {
+ auto testOnce = [&]() -> AssertionResult {
+ HalResult<HealthInfo> healthInfo;
+ TEST_AND_RETURN(isOk(mHealth->getHealthInfo([&](auto result, const auto& value) {
+ healthInfo = {result, value};
+ })));
+ return IsBatteryCurrentSignCorrect(
+ {healthInfo.result, healthInfo.value.legacy.batteryStatus},
+ {healthInfo.result, healthInfo.value.batteryCurrentAverage},
+ true /* accept zero current as unknown */);
+ };
+
+ EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+ << "You may want to try again later when current_average becomes stable.";
+}
+
+TEST_P(BatteryTest, InstantCurrentAgainstAverageCurrentInHealthInfo) {
+ auto testOnce = [&]() -> AssertionResult {
+ HalResult<HealthInfo> healthInfo;
+ TEST_AND_RETURN(isOk(mHealth->getHealthInfo([&](auto result, const auto& value) {
+ healthInfo = {result, value};
+ })));
+ return IsBatteryCurrentSimilar({healthInfo.result, healthInfo.value.legacy.batteryStatus},
+ {healthInfo.result, healthInfo.value.legacy.batteryCurrent},
+ {healthInfo.result, healthInfo.value.batteryCurrentAverage});
+ };
+
+ EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+ << "You may want to try again later when current_now and current_average becomes "
+ "stable.";
+}
+
+TEST_P(BatteryTest, InstantCurrentAgainstChargeStatusFromHal) {
+ auto testOnce = [&]() -> AssertionResult {
+ HalResult<BatteryStatus> status;
+ HalResult<int32_t> currentNow;
+ TEST_AND_RETURN(isOk(mHealth->getChargeStatus([&](auto result, auto value) {
+ status = {result, value};
+ })));
+ TEST_AND_RETURN(isOk(mHealth->getCurrentNow([&](auto result, auto value) {
+ currentNow = {result, value};
+ })));
+
+ return IsBatteryCurrentSignCorrect(status, currentNow,
+ false /* accept zero current as unknown */);
+ };
+
+ EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+ << "You may want to try again later when current_now becomes stable.";
+}
+
+TEST_P(BatteryTest, AverageCurrentAgainstChargeStatusFromHal) {
+ auto testOnce = [&]() -> AssertionResult {
+ HalResult<BatteryStatus> status;
+ TEST_AND_RETURN(isOk(mHealth->getChargeStatus([&](auto result, auto value) {
+ status = {result, value};
+ })));
+ HalResult<int32_t> currentAverage;
+ TEST_AND_RETURN(isOk(mHealth->getCurrentAverage([&](auto result, auto value) {
+ currentAverage = {result, value};
+ })));
+ return IsBatteryCurrentSignCorrect(status, currentAverage,
+ false /* accept zero current as unknown */);
+ };
+
+ EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+ << "You may want to try again later when current_average becomes stable.";
+}
+
+TEST_P(BatteryTest, InstantCurrentAgainstAverageCurrentFromHal) {
+ auto testOnce = [&]() -> AssertionResult {
+ HalResult<BatteryStatus> status;
+ TEST_AND_RETURN(isOk(mHealth->getChargeStatus([&](auto result, auto value) {
+ status = {result, value};
+ })));
+ HalResult<int32_t> currentNow;
+ TEST_AND_RETURN(isOk(mHealth->getCurrentNow([&](auto result, auto value) {
+ currentNow = {result, value};
+ })));
+ HalResult<int32_t> currentAverage;
+ TEST_AND_RETURN(isOk(mHealth->getCurrentAverage([&](auto result, auto value) {
+ currentAverage = {result, value};
+ })));
+ return IsBatteryCurrentSimilar(status, currentNow, currentAverage);
+ };
+
+ EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+ << "You may want to try again later when current_average becomes stable.";
+}
+
+AssertionResult IsBatteryStatusCorrect(HalResult<BatteryStatus> status,
+ HalResult<HealthInfo> healthInfo) {
+ // getChargetStatus / getHealthInfo is already tested above. Here, just skip if not ok.
+ if (healthInfo.result != Result::SUCCESS) {
+ return AssertionSuccess() << "getHealthInfo returned " << toString(healthInfo.result)
+ << ", skipping";
+ }
+ if (status.result != Result::SUCCESS) {
+ return AssertionSuccess() << "getChargeStatus returned " << toString(status.result)
+ << ", skipping";
+ }
+
+ const auto& batteryInfo = healthInfo.value.legacy;
+ bool isConnected = batteryInfo.chargerAcOnline || batteryInfo.chargerUsbOnline ||
+ batteryInfo.chargerWirelessOnline;
+
+ std::stringstream message;
+ message << "BatteryStatus is " << toString(status.value) << " and "
+ << (isConnected ? "" : "no ")
+ << "power source is connected: ac=" << batteryInfo.chargerAcOnline
+ << ", usb=" << batteryInfo.chargerUsbOnline
+ << ", wireless=" << batteryInfo.chargerWirelessOnline;
+
+ switch (status.value) {
+ case BatteryStatus::UNKNOWN: {
+ // Don't enforce anything on isConnected on unknown battery status.
+ // Battery-less devices must report UNKNOWN battery status, but may report true
+ // or false on isConnected.
+ } break;
+ case BatteryStatus::CHARGING:
+ case BatteryStatus::NOT_CHARGING:
+ case BatteryStatus::FULL: {
+ if (!isConnected) {
+ return AssertionFailure() << message.str();
+ }
+ } break;
+ case BatteryStatus::DISCHARGING: {
+ if (isConnected) {
+ return AssertionFailure() << message.str();
+ }
+ } break;
+ default: {
+ return AssertionFailure() << "Unknown battery status value " << toString(status.value);
+ } break;
+ }
+
+ return AssertionSuccess() << message.str();
+}
+
+TEST_P(BatteryTest, ConnectedAgainstStatusFromHal) {
+ auto testOnce = [&]() -> AssertionResult {
+ HalResult<BatteryStatus> status;
+ TEST_AND_RETURN(isOk(mHealth->getChargeStatus([&](auto result, auto value) {
+ status = {result, value};
+ })));
+ HalResult<HealthInfo> healthInfo;
+ TEST_AND_RETURN(isOk(mHealth->getHealthInfo([&](auto result, const auto& value) {
+ healthInfo = {result, value};
+ })));
+ return IsBatteryStatusCorrect(status, healthInfo);
+ };
+
+ EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+ << "You may want to try again later when battery_status becomes stable.";
+}
+
+TEST_P(BatteryTest, ConnectedAgainstStatusInHealthInfo) {
+ auto testOnce = [&]() -> AssertionResult {
+ HalResult<HealthInfo> healthInfo;
+ TEST_AND_RETURN(isOk(mHealth->getHealthInfo([&](auto result, const auto& value) {
+ healthInfo = {result, value};
+ })));
+ return IsBatteryStatusCorrect({healthInfo.result, healthInfo.value.legacy.batteryStatus},
+ healthInfo);
+ };
+
+ EXPECT_TRUE(SucceedOnce(gBatteryTestTimeout, testOnce))
+ << "You may want to try again later when getHealthInfo becomes stable.";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ PerInstance, BatteryTest,
+ testing::ValuesIn(android::hardware::getAllHalInstanceNames(IHealth::descriptor)),
+ android::hardware::PrintInstanceNameToString);
+
} // namespace V2_0
} // namespace health
} // namespace hardware