/*
* Copyright (C) 2017 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 LOG_TAG "charger_test"
#include <android/log.h>
#include <chrono>
#include <condition_variable>
#include <fstream>
#include <iostream>
#include <mutex>
#include <streambuf>
#include <string>
#include <thread>
#include <vector>
#include <health2/Health.h>
#define LOG_THIS(fmt, ...) \
ALOGE(fmt, ##__VA_ARGS__); \
printf(fmt "\n", ##__VA_ARGS__);
template <typename T>
class Atomic {
public:
Atomic(T&& init) : mValue(std::move(init)) {}
void set(T&& newVal) {
{
std::lock_guard<std::mutex> lock(mMutex);
mValue = std::move(newVal);
}
mChanged.notify_all();
}
bool waitFor(long ms, const T& expectVal) {
std::unique_lock<std::mutex> lock(mMutex);
return mChanged.wait_for(lock, std::chrono::milliseconds(ms),
[this, &expectVal] { return mValue == expectVal; });
}
private:
std::mutex mMutex;
std::condition_variable mChanged;
T mValue;
};
Atomic<bool>& getUpdateNotifier() {
static Atomic<bool> val(false);
return val;
}
int energyCounter(int64_t* counter) {
*counter = 0xEC12345;
return 0;
}
const char* createFile(const char* path, const char* content) {
std::ofstream stream(path);
if (!stream.is_open()) {
LOG_THIS("Cannot create file %s", path);
return NULL;
}
stream << content << std::endl;
stream.close();
return path;
}
std::string openToString(const char* path) {
std::ifstream stream(path);
if (!stream.is_open()) {
LOG_THIS("Cannot open file %s", path);
return "";
}
return std::string(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
}
int expectContains(const std::string& content, const std::vector<std::string>& fields) {
int status = 0;
for (const auto& field : fields) {
auto pos = content.find(field);
if (pos == std::string::npos) {
LOG_THIS("Cannot find substr '%s'", field.c_str());
status = 1;
}
}
return status;
}
::android::hardware::hidl_handle createHidlHandle(const char* filepath) {
int fd = creat(filepath, S_IRUSR | S_IWUSR);
if (fd < 0) return {};
native_handle_t* nativeHandle = native_handle_create(1, 0);
nativeHandle->data[0] = fd;
::android::hardware::hidl_handle handle;
handle.setTo(nativeHandle, true /* shouldOwn */);
return handle;
}
void healthd_board_init(struct healthd_config* config) {
config->periodic_chores_interval_fast = 60;
config->periodic_chores_interval_slow = 600;
config->batteryStatusPath = createFile("/data/local/tmp/batteryStatus", "Not charging");
config->batteryHealthPath = createFile("/data/local/tmp/batteryHealth", "Unspecified failure");
config->batteryPresentPath = createFile("/data/local/tmp/batteryPresent", "1");
config->batteryCapacityPath = createFile("/data/local/tmp/batteryCapacity", "47");
config->batteryVoltagePath = createFile("/data/local/tmp/batteryVoltage", "45000");
config->batteryTemperaturePath = createFile("/data/local/tmp/batteryTemperature", "987");
config->batteryTechnologyPath = createFile("/data/local/tmp/batteryTechnology", "NiCd");
config->batteryCurrentNowPath = createFile("/data/local/tmp/batteryCurrentNow", "99000");
config->batteryCurrentAvgPath = createFile("/data/local/tmp/batteryCurrentAvg", "98000");
config->batteryChargeCounterPath = createFile("/data/local/tmp/batteryChargeCounter", "600");
config->batteryFullChargePath = createFile("/data/local/tmp/batteryFullCharge", "3515547");
config->batteryCycleCountPath = createFile("/data/local/tmp/batteryCycleCount", "77");
config->energyCounter = energyCounter;
config->boot_min_cap = 50;
config->screen_on = NULL;
}
int healthd_board_battery_update(struct android::BatteryProperties*) {
getUpdateNotifier().set(true /* updated */);
// return 0 to log periodic polled battery status to kernel log
return 0;
}
extern int healthd_charger_main(int argc, char** argv);
int main(int argc, char** argv) {
using android::hardware::health::V2_0::implementation::Health;
const char* dumpFile = "/data/local/tmp/dump.txt";
std::thread bgThread([=] {
healthd_charger_main(argc, argv);
});
// wait for healthd_init to finish
if (!getUpdateNotifier().waitFor(1000 /* wait ms */, true /* updated */)) {
LOG_THIS("Time out.");
exit(1);
}
Health::getImplementation()->debug(createHidlHandle(dumpFile), {} /* options */);
std::string content = openToString(dumpFile);
int status = expectContains(content, {
"status: 4",
"health: 6",
"present: 1",
"level: 47",
"voltage: 45",
"temp: 987",
"current now: 99000",
"current avg: 98000",
"charge counter: 600",
"current now: 99",
"cycle count: 77",
"Full charge: 3515547"
});
if (status == 0) {
LOG_THIS("Test success.");
} else {
LOG_THIS("Actual dump:\n%s", content.c_str());
}
exit(status); // force bgThread to exit
}