75da4c0eee
Change-Id: I134d6418f1357a4d7ce37358481d6b765d93e1a5
281 lines
10 KiB
C++
281 lines
10 KiB
C++
/*
|
|
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following
|
|
* disclaimer in the documentation and/or other materials provided
|
|
* with the distribution.
|
|
* * Neither the name of The Linux Foundation nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
|
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
#include "battery_listener.h"
|
|
#ifdef LOG_TAG
|
|
#undef LOG_TAG
|
|
#endif
|
|
#define LOG_TAG "LocSvc_BatteryListener"
|
|
#define LOG_NDEBUG 0
|
|
|
|
#include <android/hidl/manager/1.0/IServiceManager.h>
|
|
#include <android/hardware/health/2.1/IHealth.h>
|
|
#include <android/hardware/health/2.1/IHealthInfoCallback.h>
|
|
#include <healthhalutils/HealthHalUtils.h>
|
|
#include <hidl/HidlTransportSupport.h>
|
|
#include <thread>
|
|
#include <log_util.h>
|
|
|
|
using android::hardware::interfacesEqual;
|
|
using android::hardware::Return;
|
|
using android::hardware::Void;
|
|
using android::hardware::health::V1_0::BatteryStatus;
|
|
using android::hardware::health::V2_1::HealthInfo;
|
|
using android::hardware::health::V2_1::IHealthInfoCallback;
|
|
using android::hardware::health::V2_1::IHealth;
|
|
using android::hardware::health::V2_0::Result;
|
|
using android::hidl::manager::V1_0::IServiceManager;
|
|
using namespace std::literals::chrono_literals;
|
|
|
|
static bool sIsBatteryListened = false;
|
|
namespace android {
|
|
|
|
#define GET_HEALTH_SVC_RETRY_CNT 5
|
|
#define GET_HEALTH_SVC_WAIT_TIME_MS 500
|
|
|
|
struct BatteryListenerImpl : public hardware::health::V2_1::IHealthInfoCallback,
|
|
public hardware::hidl_death_recipient {
|
|
typedef std::function<void(bool)> cb_fn_t;
|
|
BatteryListenerImpl(cb_fn_t cb);
|
|
virtual ~BatteryListenerImpl ();
|
|
virtual hardware::Return<void> healthInfoChanged(
|
|
const hardware::health::V2_0::HealthInfo& info);
|
|
virtual hardware::Return<void> healthInfoChanged_2_1(
|
|
const hardware::health::V2_1::HealthInfo& info);
|
|
virtual void serviceDied(uint64_t cookie,
|
|
const wp<hidl::base::V1_0::IBase>& who);
|
|
bool isCharging() {
|
|
std::lock_guard<std::mutex> _l(mLock);
|
|
return statusToBool(mStatus);
|
|
}
|
|
private:
|
|
sp<hardware::health::V2_1::IHealth> mHealth;
|
|
status_t init();
|
|
BatteryStatus mStatus;
|
|
cb_fn_t mCb;
|
|
std::mutex mLock;
|
|
std::condition_variable mCond;
|
|
std::unique_ptr<std::thread> mThread;
|
|
bool mDone;
|
|
bool statusToBool(const BatteryStatus &s) const {
|
|
return (s == BatteryStatus::CHARGING) ||
|
|
(s == BatteryStatus::FULL);
|
|
}
|
|
};
|
|
|
|
status_t BatteryListenerImpl::init()
|
|
{
|
|
int tries = 0;
|
|
|
|
if (mHealth != NULL)
|
|
return INVALID_OPERATION;
|
|
|
|
do {
|
|
mHealth = IHealth::getService();
|
|
if (mHealth != NULL)
|
|
break;
|
|
usleep(GET_HEALTH_SVC_WAIT_TIME_MS * 1000);
|
|
tries++;
|
|
} while(tries < GET_HEALTH_SVC_RETRY_CNT);
|
|
|
|
if (mHealth == NULL) {
|
|
LOC_LOGe("no health service found, retries %d", tries);
|
|
return NO_INIT;
|
|
} else {
|
|
LOC_LOGi("Get health service in %d tries", tries);
|
|
}
|
|
mStatus = BatteryStatus::UNKNOWN;
|
|
auto ret = mHealth->getChargeStatus([&](Result r, BatteryStatus status) {
|
|
if (r != Result::SUCCESS) {
|
|
LOC_LOGe("batterylistener: cannot get battery status");
|
|
return;
|
|
}
|
|
mStatus = status;
|
|
});
|
|
if (!ret.isOk()) {
|
|
LOC_LOGe("batterylistener: get charge status transaction error");
|
|
}
|
|
if (mStatus == BatteryStatus::UNKNOWN) {
|
|
LOC_LOGw("batterylistener: init: invalid battery status");
|
|
}
|
|
mDone = false;
|
|
mThread = std::make_unique<std::thread>([this]() {
|
|
std::unique_lock<std::mutex> l(mLock);
|
|
BatteryStatus local_status = mStatus;
|
|
while (!mDone) {
|
|
if (local_status == mStatus) {
|
|
mCond.wait(l);
|
|
continue;
|
|
}
|
|
local_status = mStatus;
|
|
switch (local_status) {
|
|
// NOT_CHARGING is a special event that indicates, a battery is connected,
|
|
// but not charging. This is seen for approx a second
|
|
// after charger is plugged in. A charging event is eventually received.
|
|
// We must try to avoid an unnecessary cb to HAL
|
|
// only to call it again shortly.
|
|
// An option to deal with this transient event would be to ignore this.
|
|
// Or process this event with a slight delay (i.e cancel this event
|
|
// if a different event comes in within a timeout
|
|
case BatteryStatus::NOT_CHARGING : {
|
|
auto mStatusnot_ncharging =
|
|
[this, local_status]() { return mStatus != local_status; };
|
|
mCond.wait_for(l, 3s, mStatusnot_ncharging);
|
|
if (mStatusnot_ncharging()) // i.e event changed
|
|
break;
|
|
[[clang::fallthrough]]; //explicit fall-through between switch labels
|
|
}
|
|
default:
|
|
bool c = statusToBool(local_status);
|
|
LOC_LOGi("healthInfo cb thread: cb %s", c ? "CHARGING" : "NOT CHARGING");
|
|
l.unlock();
|
|
mCb(c);
|
|
l.lock();
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
auto reg = mHealth->registerCallback(this);
|
|
if (!reg.isOk()) {
|
|
LOC_LOGe("Transaction error in registeringCb to HealthHAL death: %s",
|
|
reg.description().c_str());
|
|
}
|
|
|
|
auto linked = mHealth->linkToDeath(this, 0 /* cookie */);
|
|
if (!linked.isOk() || linked == false) {
|
|
LOC_LOGe("Transaction error in linking to HealthHAL death: %s",
|
|
linked.description().c_str());
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
BatteryListenerImpl::BatteryListenerImpl(cb_fn_t cb) :
|
|
mCb(cb)
|
|
{
|
|
init();
|
|
}
|
|
|
|
BatteryListenerImpl::~BatteryListenerImpl()
|
|
{
|
|
{
|
|
std::lock_guard<std::mutex> _l(mLock);
|
|
if (mHealth != NULL)
|
|
mHealth->unregisterCallback(this);
|
|
auto r = mHealth->unlinkToDeath(this);
|
|
if (!r.isOk() || r == false) {
|
|
LOC_LOGe("Transaction error in unregister to HealthHAL death: %s",
|
|
r.description().c_str());
|
|
}
|
|
}
|
|
mDone = true;
|
|
mThread->join();
|
|
}
|
|
|
|
void BatteryListenerImpl::serviceDied(uint64_t cookie __unused,
|
|
const wp<hidl::base::V1_0::IBase>& who)
|
|
{
|
|
{
|
|
std::lock_guard<std::mutex> _l(mLock);
|
|
if (mHealth == NULL || !interfacesEqual(mHealth, who.promote())) {
|
|
LOC_LOGe("health not initialized or unknown interface died");
|
|
return;
|
|
}
|
|
LOC_LOGi("health service died, reinit");
|
|
mDone = true;
|
|
}
|
|
mHealth = NULL;
|
|
mCond.notify_one();
|
|
mThread->join();
|
|
std::lock_guard<std::mutex> _l(mLock);
|
|
init();
|
|
}
|
|
|
|
// this callback seems to be a SYNC callback and so
|
|
// waits for return before next event is issued.
|
|
// therefore we need not have a queue to process
|
|
// NOT_CHARGING and CHARGING concurrencies.
|
|
// Replace single var by a list if this assumption is broken
|
|
Return<void> BatteryListenerImpl::healthInfoChanged(
|
|
const hardware::health::V2_0::HealthInfo& info) {
|
|
LOC_LOGv("healthInfoChanged: %d", info.legacy.batteryStatus);
|
|
std::unique_lock<std::mutex> l(mLock);
|
|
if (info.legacy.batteryStatus != mStatus) {
|
|
mStatus = info.legacy.batteryStatus;
|
|
mCond.notify_one();
|
|
}
|
|
return Void();
|
|
}
|
|
|
|
Return<void> BatteryListenerImpl::healthInfoChanged_2_1(
|
|
const hardware::health::V2_1::HealthInfo& info) {
|
|
LOC_LOGv("healthInfoChanged_2_1: %d", info.legacy.legacy.batteryStatus);
|
|
healthInfoChanged(info.legacy);
|
|
return Void();
|
|
}
|
|
|
|
static sp<BatteryListenerImpl> batteryListener;
|
|
|
|
bool batteryPropertiesListenerIsCharging() {
|
|
return batteryListener->isCharging();
|
|
}
|
|
|
|
status_t batteryPropertiesListenerInit(BatteryListenerImpl::cb_fn_t cb) {
|
|
batteryListener = new BatteryListenerImpl(cb);
|
|
bool isCharging = batteryPropertiesListenerIsCharging();
|
|
LOC_LOGv("charging status: %s charging", isCharging ? "" : "not");;
|
|
if (isCharging) {
|
|
cb(isCharging);
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t batteryPropertiesListenerDeinit() {
|
|
batteryListener.clear();
|
|
return OK;
|
|
}
|
|
|
|
} // namespace android
|
|
|
|
void loc_extn_battery_properties_listener_init(battery_status_change_fn_t fn) {
|
|
LOC_LOGv("loc_extn_battery_properties_listener_init entry");
|
|
if (!sIsBatteryListened) {
|
|
std::thread t1(android::batteryPropertiesListenerInit,
|
|
[=](bool charging) { fn(charging); });
|
|
t1.detach();
|
|
sIsBatteryListened = true;
|
|
}
|
|
}
|
|
|
|
void loc_extn_battery_properties_listener_deinit() {
|
|
android::batteryPropertiesListenerDeinit();
|
|
}
|
|
|
|
bool loc_extn_battery_properties_is_charging() {
|
|
return android::batteryPropertiesListenerIsCharging();
|
|
}
|