Merge "liblog: remove android_lookupEventTag"
diff --git a/healthd/Android.bp b/healthd/Android.bp
index 65eaedd..b3de9c4 100644
--- a/healthd/Android.bp
+++ b/healthd/Android.bp
@@ -243,17 +243,44 @@
cc_test {
name: "libhealthd_charger_test",
- srcs: ["AnimationParser_test.cpp"],
- shared_libs: [
- "liblog",
- "libbase",
- "libcutils",
+ defaults: ["charger_defaults"],
+ srcs: [
+ "AnimationParser_test.cpp",
+ "healthd_mode_charger_test.cpp"
],
static_libs: [
- "libhealthd_charger",
+ "libgmock",
],
test_suites: [
"general-tests",
"device-tests",
],
+ data: [
+ ":libhealthd_charger_test_data",
+ ],
+ require_root: true,
+}
+
+// /system/etc/res/images/charger/battery_fail.png
+prebuilt_etc {
+ name: "system_core_charger_res_images_battery_fail.png",
+ src: "images/battery_fail.png",
+ relative_install_path: "res/images/charger",
+ filename: "battery_fail.png",
+}
+
+// /system/etc/res/images/charger/battery_scale.png
+prebuilt_etc {
+ name: "system_core_charger_res_images_battery_scale.png",
+ src: "images/battery_scale.png",
+ relative_install_path: "res/images/charger",
+ filename: "battery_scale.png",
+}
+
+phony {
+ name: "charger_res_images",
+ required: [
+ "system_core_charger_res_images_battery_fail.png",
+ "system_core_charger_res_images_battery_scale.png",
+ ],
}
diff --git a/healthd/Android.mk b/healthd/Android.mk
deleted file mode 100644
index 4b09cf8..0000000
--- a/healthd/Android.mk
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2013 The Android Open Source Project
-
-LOCAL_PATH := $(call my-dir)
-
-ifeq ($(strip $(BOARD_CHARGER_NO_UI)),true)
-LOCAL_CHARGER_NO_UI := true
-endif
-
-### charger_res_images ###
-ifneq ($(strip $(LOCAL_CHARGER_NO_UI)),true)
-define _add-charger-image
-include $$(CLEAR_VARS)
-LOCAL_MODULE := system_core_charger_res_images_$(notdir $(1))
-LOCAL_MODULE_STEM := $(notdir $(1))
-_img_modules += $$(LOCAL_MODULE)
-LOCAL_SRC_FILES := $1
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $$(TARGET_ROOT_OUT)/res/images/charger
-include $$(BUILD_PREBUILT)
-endef
-
-_img_modules :=
-_images :=
-$(foreach _img, $(call find-subdir-subdir-files, "images", "*.png"), \
- $(eval $(call _add-charger-image,$(_img))))
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := charger_res_images
-LOCAL_MODULE_TAGS := optional
-LOCAL_REQUIRED_MODULES := $(_img_modules)
-include $(BUILD_PHONY_PACKAGE)
-
-_add-charger-image :=
-_img_modules :=
-endif # LOCAL_CHARGER_NO_UI
diff --git a/healthd/animation.h b/healthd/animation.h
index d02d7a7..c2d5f1c 100644
--- a/healthd/animation.h
+++ b/healthd/animation.h
@@ -18,6 +18,7 @@
#define HEALTHD_ANIMATION_H
#include <inttypes.h>
+
#include <string>
class GRSurface;
@@ -52,20 +53,11 @@
// - When treating paths as relative paths, it adds ".png" suffix.
// - When treating paths as absolute paths, it doesn't add the suffix. Hence, the suffix
// is added here.
- void set_resource_root(const std::string& root) {
- if (!animation_file.empty()) {
- animation_file = root + animation_file + ".png";
- }
- if (!fail_file.empty()) {
- fail_file = root + fail_file + ".png";
- }
- if (!text_clock.font_file.empty()) {
- text_clock.font_file = root + text_clock.font_file + ".png";
- }
- if (!text_percent.font_file.empty()) {
- text_percent.font_file = root + text_percent.font_file + ".png";
- }
- }
+ // If |backup_root| is provided, additionally check if file under |root| is accessbile or not.
+ // If not accessbile, use |backup_root| instead.
+ // Require that |root| starts and ends with "/". If |backup_root| is provided, require that
+ // |backup_root| starts and ends with "/".
+ void set_resource_root(const std::string& root, const std::string& backup_root = "");
std::string animation_file;
std::string fail_file;
diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
index 386ba1a..04a99a3 100644
--- a/healthd/healthd_mode_charger.cpp
+++ b/healthd/healthd_mode_charger.cpp
@@ -33,7 +33,9 @@
#include <optional>
#include <android-base/file.h>
+#include <android-base/logging.h>
#include <android-base/macros.h>
+#include <android-base/strings.h>
#include <linux/netlink.h>
#include <sys/socket.h>
@@ -58,6 +60,7 @@
#include <health2impl/Health.h>
#include <healthd/healthd.h>
+using std::string_literals::operator""s;
using namespace android;
using android::hardware::Return;
using android::hardware::health::GetHealthServiceOrDefault;
@@ -103,7 +106,13 @@
namespace android {
-// Resources in /product/etc/res overrides resources in /res.
+// Legacy animation resources are loaded from this directory.
+static constexpr const char* legacy_animation_root = "/res/images/";
+
+// Built-in animation resources are loaded from this directory.
+static constexpr const char* system_animation_root = "/system/etc/res/images/";
+
+// Resources in /product/etc/res overrides resources in /res and /system/etc/res.
// If the device is using the Generic System Image (GSI), resources may exist in
// both paths.
static constexpr const char* product_animation_desc_path =
@@ -625,6 +634,12 @@
batt_anim_.set_resource_root(product_animation_root);
} else if (base::ReadFileToString(animation_desc_path, &content)) {
parse_success = parse_animation_desc(content, &batt_anim_);
+ // Fallback resources always exist in system_animation_root. On legacy devices with an old
+ // ramdisk image, resources may be overridden under root. For example,
+ // /res/images/charger/battery_fail.png may not be the same as
+ // system/core/healthd/images/battery_fail.png in the source tree, but is a device-specific
+ // image. Hence, load from /res, and fall back to /system/etc/res.
+ batt_anim_.set_resource_root(legacy_animation_root, system_animation_root);
} else {
LOGW("Could not open animation description at %s\n", animation_desc_path);
parse_success = false;
@@ -633,13 +648,13 @@
if (!parse_success) {
LOGW("Could not parse animation description. Using default animation.\n");
batt_anim_ = BASE_ANIMATION;
- batt_anim_.animation_file.assign("charger/battery_scale");
+ batt_anim_.animation_file.assign(system_animation_root + "charger/battery_scale.png"s);
InitDefaultAnimationFrames();
batt_anim_.frames = owned_frames_.data();
batt_anim_.num_frames = owned_frames_.size();
}
if (batt_anim_.fail_file.empty()) {
- batt_anim_.fail_file.assign("charger/battery_fail");
+ batt_anim_.fail_file.assign(system_animation_root + "charger/battery_fail.png"s);
}
LOGV("Animation Description:\n");
@@ -678,10 +693,11 @@
InitAnimation();
- ret = res_create_display_surface(batt_anim_.fail_file.c_str(), &surf_unknown_);
+ ret = CreateDisplaySurface(batt_anim_.fail_file.c_str(), &surf_unknown_);
if (ret < 0) {
LOGE("Cannot load custom battery_fail image. Reverting to built in: %d\n", ret);
- ret = res_create_display_surface("charger/battery_fail", &surf_unknown_);
+ ret = CreateDisplaySurface((system_animation_root + "charger/battery_fail.png"s).c_str(),
+ &surf_unknown_);
if (ret < 0) {
LOGE("Cannot load built in battery_fail image\n");
surf_unknown_ = NULL;
@@ -692,8 +708,8 @@
int scale_count;
int scale_fps; // Not in use (charger/battery_scale doesn't have FPS text
// chunk). We are using hard-coded frame.disp_time instead.
- ret = res_create_multi_display_surface(batt_anim_.animation_file.c_str(), &scale_count,
- &scale_fps, &scale_frames);
+ ret = CreateMultiDisplaySurface(batt_anim_.animation_file.c_str(), &scale_count, &scale_fps,
+ &scale_frames);
if (ret < 0) {
LOGE("Cannot load battery_scale image\n");
batt_anim_.num_frames = 0;
@@ -722,6 +738,43 @@
boot_min_cap_ = config->boot_min_cap;
}
+int Charger::CreateDisplaySurface(const std::string& name, GRSurface** surface) {
+ return res_create_display_surface(name.c_str(), surface);
+}
+
+int Charger::CreateMultiDisplaySurface(const std::string& name, int* frames, int* fps,
+ GRSurface*** surface) {
+ return res_create_multi_display_surface(name.c_str(), frames, fps, surface);
+}
+
+void set_resource_root_for(const std::string& root, const std::string& backup_root,
+ std::string* value) {
+ if (value->empty()) {
+ return;
+ }
+
+ std::string new_value = root + *value + ".png";
+ // If |backup_root| is provided, additionally check whether the file under |root| is
+ // accessible or not. If not accessible, fallback to file under |backup_root|.
+ if (!backup_root.empty() && access(new_value.data(), F_OK) == -1) {
+ new_value = backup_root + *value + ".png";
+ }
+
+ *value = new_value;
+}
+
+void animation::set_resource_root(const std::string& root, const std::string& backup_root) {
+ CHECK(android::base::StartsWith(root, "/") && android::base::EndsWith(root, "/"))
+ << "animation root " << root << " must start and end with /";
+ CHECK(backup_root.empty() || (android::base::StartsWith(backup_root, "/") &&
+ android::base::EndsWith(backup_root, "/")))
+ << "animation backup root " << backup_root << " must start and end with /";
+ set_resource_root_for(root, backup_root, &animation_file);
+ set_resource_root_for(root, backup_root, &fail_file);
+ set_resource_root_for(root, backup_root, &text_clock.font_file);
+ set_resource_root_for(root, backup_root, &text_percent.font_file);
+}
+
} // namespace android
int healthd_charger_main(int argc, char** argv) {
diff --git a/healthd/healthd_mode_charger.h b/healthd/healthd_mode_charger.h
index 6e569ee..6f9ae8c 100644
--- a/healthd/healthd_mode_charger.h
+++ b/healthd/healthd_mode_charger.h
@@ -53,6 +53,11 @@
// HalHealthLoop overrides
void OnHealthInfoChanged(const HealthInfo_2_1& health_info) override;
+ // Allowed to be mocked for testing.
+ virtual int CreateDisplaySurface(const std::string& name, GRSurface** surface);
+ virtual int CreateMultiDisplaySurface(const std::string& name, int* frames, int* fps,
+ GRSurface*** surface);
+
private:
void InitDefaultAnimationFrames();
void UpdateScreenState(int64_t now);
diff --git a/healthd/healthd_mode_charger_test.cpp b/healthd/healthd_mode_charger_test.cpp
new file mode 100644
index 0000000..f444f66
--- /dev/null
+++ b/healthd/healthd_mode_charger_test.cpp
@@ -0,0 +1,181 @@
+/*
+ * 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.
+ */
+
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <health/utils.h>
+
+#include "healthd_mode_charger.h"
+
+using android::hardware::Return;
+using android::hardware::health::InitHealthdConfig;
+using std::string_literals::operator""s;
+using testing::_;
+using testing::Invoke;
+using testing::NiceMock;
+using testing::StrEq;
+using testing::Test;
+
+namespace android {
+
+// A replacement to ASSERT_* to be used in a forked process. When the condition is not met,
+// print a gtest message, then exit abnormally.
+class ChildAssertHelper : public std::stringstream {
+ public:
+ ChildAssertHelper(bool res, const char* expr, const char* file, int line) : res_(res) {
+ (*this) << file << ":" << line << ": `" << expr << "` evaluates to false\n";
+ }
+ ~ChildAssertHelper() {
+ EXPECT_TRUE(res_) << str();
+ if (!res_) exit(EX_SOFTWARE);
+ }
+
+ private:
+ bool res_;
+ DISALLOW_COPY_AND_ASSIGN(ChildAssertHelper);
+};
+#define CHILD_ASSERT_TRUE(expr) ChildAssertHelper(expr, #expr, __FILE__, __LINE__)
+
+// Run |test_body| in a chroot jail in a forked process. |subdir| is a sub-directory in testdata.
+// Within |test_body|,
+// - non-fatal errors may be reported using EXPECT_* macro as usual.
+// - fatal errors must be reported using CHILD_ASSERT_TRUE macro. ASSERT_* must not be used.
+void ForkTest(const std::string& subdir, const std::function<void(void)>& test_body) {
+ pid_t pid = fork();
+ ASSERT_GE(pid, 0) << "Fork fails: " << strerror(errno);
+ if (pid == 0) {
+ // child
+ CHILD_ASSERT_TRUE(
+ chroot((android::base::GetExecutableDirectory() + "/" + subdir).c_str()) != -1)
+ << "Failed to chroot to " << subdir << ": " << strerror(errno);
+ test_body();
+ // EXPECT_* macros may set the HasFailure bit without calling exit(). Set exit status
+ // accordingly.
+ exit(::testing::Test::HasFailure() ? EX_SOFTWARE : EX_OK);
+ }
+ // parent
+ int status;
+ ASSERT_NE(-1, waitpid(pid, &status, 0)) << "waitpid() fails: " << strerror(errno);
+ ASSERT_TRUE(WIFEXITED(status)) << "Test fails, waitpid() returns " << status;
+ ASSERT_EQ(EX_OK, WEXITSTATUS(status)) << "Test fails, child process returns " << status;
+}
+
+class MockHealth : public android::hardware::health::V2_1::IHealth {
+ MOCK_METHOD(Return<::android::hardware::health::V2_0::Result>, registerCallback,
+ (const sp<::android::hardware::health::V2_0::IHealthInfoCallback>& callback));
+ MOCK_METHOD(Return<::android::hardware::health::V2_0::Result>, unregisterCallback,
+ (const sp<::android::hardware::health::V2_0::IHealthInfoCallback>& callback));
+ MOCK_METHOD(Return<::android::hardware::health::V2_0::Result>, update, ());
+ MOCK_METHOD(Return<void>, getChargeCounter, (getChargeCounter_cb _hidl_cb));
+ MOCK_METHOD(Return<void>, getCurrentNow, (getCurrentNow_cb _hidl_cb));
+ MOCK_METHOD(Return<void>, getCurrentAverage, (getCurrentAverage_cb _hidl_cb));
+ MOCK_METHOD(Return<void>, getCapacity, (getCapacity_cb _hidl_cb));
+ MOCK_METHOD(Return<void>, getEnergyCounter, (getEnergyCounter_cb _hidl_cb));
+ MOCK_METHOD(Return<void>, getChargeStatus, (getChargeStatus_cb _hidl_cb));
+ MOCK_METHOD(Return<void>, getStorageInfo, (getStorageInfo_cb _hidl_cb));
+ MOCK_METHOD(Return<void>, getDiskStats, (getDiskStats_cb _hidl_cb));
+ MOCK_METHOD(Return<void>, getHealthInfo, (getHealthInfo_cb _hidl_cb));
+ MOCK_METHOD(Return<void>, getHealthConfig, (getHealthConfig_cb _hidl_cb));
+ MOCK_METHOD(Return<void>, getHealthInfo_2_1, (getHealthInfo_2_1_cb _hidl_cb));
+ MOCK_METHOD(Return<void>, shouldKeepScreenOn, (shouldKeepScreenOn_cb _hidl_cb));
+};
+
+class TestCharger : public Charger {
+ public:
+ // Inherit constructor.
+ using Charger::Charger;
+ // Expose protected functions to be used in tests.
+ void Init(struct healthd_config* config) override { Charger::Init(config); }
+ MOCK_METHOD(int, CreateDisplaySurface, (const std::string& name, GRSurface** surface));
+ MOCK_METHOD(int, CreateMultiDisplaySurface,
+ (const std::string& name, int* frames, int* fps, GRSurface*** surface));
+};
+
+// Intentionally leak TestCharger instance to avoid calling ~HealthLoop() because ~HealthLoop()
+// should never be called. But still verify expected calls upon destruction.
+class VerifiedTestCharger {
+ public:
+ VerifiedTestCharger(TestCharger* charger) : charger_(charger) {
+ testing::Mock::AllowLeak(charger_);
+ }
+ TestCharger& operator*() { return *charger_; }
+ TestCharger* operator->() { return charger_; }
+ ~VerifiedTestCharger() { testing::Mock::VerifyAndClearExpectations(charger_); }
+
+ private:
+ TestCharger* charger_;
+};
+
+// Do not use SetUp and TearDown of a test suite, as they will be invoked in the parent process, not
+// the child process. In particular, if the test suite contains mocks, they will not be verified in
+// the child process. Instead, create mocks within closures in each tests.
+void ExpectChargerResAt(const std::string& root) {
+ sp<NiceMock<MockHealth>> health(new NiceMock<MockHealth>());
+ VerifiedTestCharger charger(new NiceMock<TestCharger>(health));
+
+ // Only one frame in all testdata/**/animation.txt
+ GRSurface* multi[] = {nullptr};
+
+ EXPECT_CALL(*charger, CreateDisplaySurface(StrEq(root + "charger/battery_fail.png"), _))
+ .WillRepeatedly(Invoke([](const auto&, GRSurface** surface) {
+ *surface = nullptr;
+ return 0;
+ }));
+ EXPECT_CALL(*charger,
+ CreateMultiDisplaySurface(StrEq(root + "charger/battery_scale.png"), _, _, _))
+ .WillRepeatedly(Invoke([&](const auto&, int* frames, int* fps, GRSurface*** surface) {
+ *frames = arraysize(multi);
+ *fps = 60; // Unused fps value
+ *surface = multi;
+ return 0;
+ }));
+ struct healthd_config healthd_config;
+ InitHealthdConfig(&healthd_config);
+ charger->Init(&healthd_config);
+};
+
+// Test that if resources does not exist in /res or in /product/etc/res, load from /system.
+TEST(ChargerLoadAnimationRes, Empty) {
+ ForkTest("empty", std::bind(&ExpectChargerResAt, "/system/etc/res/images/"));
+}
+
+// Test loading everything from /res
+TEST(ChargerLoadAnimationRes, Legacy) {
+ ForkTest("legacy", std::bind(&ExpectChargerResAt, "/res/images/"));
+}
+
+// Test loading animation text from /res but images from /system if images does not exist under
+// /res.
+TEST(ChargerLoadAnimationRes, LegacyTextSystemImages) {
+ ForkTest("legacy_text_system_images",
+ std::bind(&ExpectChargerResAt, "/system/etc/res/images/"));
+}
+
+// Test loading everything from /product
+TEST(ChargerLoadAnimationRes, Product) {
+ ForkTest("product", std::bind(&ExpectChargerResAt, "/product/etc/res/images/"));
+}
+
+} // namespace android
diff --git a/healthd/testdata/Android.bp b/healthd/testdata/Android.bp
new file mode 100644
index 0000000..110c79a
--- /dev/null
+++ b/healthd/testdata/Android.bp
@@ -0,0 +1,20 @@
+//
+// 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.
+//
+
+filegroup {
+ name: "libhealthd_charger_test_data",
+ srcs: ["**/*.*"],
+}
diff --git a/healthd/testdata/empty/ensure_directory_creation.txt b/healthd/testdata/empty/ensure_directory_creation.txt
new file mode 100644
index 0000000..36ceff4
--- /dev/null
+++ b/healthd/testdata/empty/ensure_directory_creation.txt
@@ -0,0 +1 @@
+File is placed to ensure directory is created on the device.
diff --git a/healthd/testdata/legacy/res/images/charger/battery_fail.png b/healthd/testdata/legacy/res/images/charger/battery_fail.png
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/healthd/testdata/legacy/res/images/charger/battery_fail.png
diff --git a/healthd/testdata/legacy/res/images/charger/battery_scale.png b/healthd/testdata/legacy/res/images/charger/battery_scale.png
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/healthd/testdata/legacy/res/images/charger/battery_scale.png
diff --git a/healthd/testdata/legacy/res/values/charger/animation.txt b/healthd/testdata/legacy/res/values/charger/animation.txt
new file mode 100644
index 0000000..0753336
--- /dev/null
+++ b/healthd/testdata/legacy/res/values/charger/animation.txt
@@ -0,0 +1,9 @@
+# Sample Animation file for testing.
+
+# animation: num_cycles, first_frame_repeats, animation_file
+animation: 2 1 charger/battery_scale
+
+fail: charger/battery_fail
+
+# frame: disp_time min_level max_level
+frame: 15 0 100
diff --git a/healthd/testdata/legacy_text_system_images/res/values/charger/animation.txt b/healthd/testdata/legacy_text_system_images/res/values/charger/animation.txt
new file mode 100644
index 0000000..0753336
--- /dev/null
+++ b/healthd/testdata/legacy_text_system_images/res/values/charger/animation.txt
@@ -0,0 +1,9 @@
+# Sample Animation file for testing.
+
+# animation: num_cycles, first_frame_repeats, animation_file
+animation: 2 1 charger/battery_scale
+
+fail: charger/battery_fail
+
+# frame: disp_time min_level max_level
+frame: 15 0 100
diff --git a/healthd/testdata/product/product/etc/res/images/charger/battery_fail.png b/healthd/testdata/product/product/etc/res/images/charger/battery_fail.png
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/healthd/testdata/product/product/etc/res/images/charger/battery_fail.png
diff --git a/healthd/testdata/product/product/etc/res/images/charger/battery_scale.png b/healthd/testdata/product/product/etc/res/images/charger/battery_scale.png
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/healthd/testdata/product/product/etc/res/images/charger/battery_scale.png
diff --git a/healthd/testdata/product/product/etc/res/values/charger/animation.txt b/healthd/testdata/product/product/etc/res/values/charger/animation.txt
new file mode 100644
index 0000000..0753336
--- /dev/null
+++ b/healthd/testdata/product/product/etc/res/values/charger/animation.txt
@@ -0,0 +1,9 @@
+# Sample Animation file for testing.
+
+# animation: num_cycles, first_frame_repeats, animation_file
+animation: 2 1 charger/battery_scale
+
+fail: charger/battery_fail
+
+# frame: disp_time min_level max_level
+frame: 15 0 100