/* * Copyright (c) 2019, 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" #include #include #include #include #include using android::hardware::interfacesEqual; using android::hardware::Return; using android::hardware::Void; using android::hardware::health::V1_0::BatteryStatus; using android::hardware::health::V1_0::toString; using android::hardware::health::V2_0::get_health_service; using android::hardware::health::V2_0::HealthInfo; using android::hardware::health::V2_0::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_0::IHealthInfoCallback, public hardware::hidl_death_recipient { typedef std::function cb_fn_t; BatteryListenerImpl(cb_fn_t cb); virtual ~BatteryListenerImpl (); virtual hardware::Return healthInfoChanged( const hardware::health::V2_0::HealthInfo& info); virtual void serviceDied(uint64_t cookie, const wp& who); bool isCharging() { std::lock_guard _l(mLock); return statusToBool(mStatus); } private: sp mHealth; status_t init(); BatteryStatus mStatus; cb_fn_t mCb; std::mutex mLock; std::condition_variable mCond; std::unique_ptr 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 = hardware::health::V2_0::get_health_service(); if (mHealth != NULL) break; usleep(GET_HEALTH_SVC_WAIT_TIME_MS * 1000); tries++; } while(tries < GET_HEALTH_SVC_RETRY_CNT); if (mHealth == NULL) { ALOGE("no health service found, retries %d", tries); return NO_INIT; } else { ALOGI("Get health service in %d tries", tries); } mStatus = BatteryStatus::UNKNOWN; auto ret = mHealth->getChargeStatus([&](Result r, BatteryStatus status) { if (r != Result::SUCCESS) { ALOGE("batterylistener: cannot get battery status"); return; } mStatus = status; }); if (!ret.isOk()) ALOGE("batterylistener: get charge status transaction error"); if (mStatus == BatteryStatus::UNKNOWN) ALOGW("batterylistener: init: invalid battery status"); mDone = false; mThread = std::make_unique([this]() { std::unique_lock 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); ALOGI("healthInfo cb thread: cb %s", c ? "CHARGING" : "NOT CHARGING"); l.unlock(); mCb(c); l.lock(); break; } } }); auto reg = mHealth->registerCallback(this); if (!reg.isOk()) { ALOGE("Transaction error in registeringCb to HealthHAL death: %s", reg.description().c_str()); } auto linked = mHealth->linkToDeath(this, 0 /* cookie */); if (!linked.isOk() || linked == false) { ALOGE("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 _l(mLock); if (mHealth != NULL) mHealth->unlinkToDeath(this); auto r = mHealth->unlinkToDeath(this); if (!r.isOk() || r == false) { ALOGE("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& who) { { std::lock_guard _l(mLock); if (mHealth == NULL || !interfacesEqual(mHealth, who.promote())) { ALOGE("health not initialized or unknown interface died"); return; } ALOGI("health service died, reinit"); mDone = true; } mThread->join(); std::lock_guard _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 BatteryListenerImpl::healthInfoChanged( const hardware::health::V2_0::HealthInfo& info) { ALOGV("healthInfoChanged: %d", info.legacy.batteryStatus); std::unique_lock l(mLock); if (info.legacy.batteryStatus != mStatus) { mStatus = info.legacy.batteryStatus; mCond.notify_one(); } return Void(); } static sp batteryListener; status_t batteryPropertiesListenerInit(BatteryListenerImpl::cb_fn_t cb) { ALOGV("batteryPropertiesListenerInit entry"); batteryListener = new BatteryListenerImpl(cb); return NO_ERROR; } status_t batteryPropertiesListenerDeinit() { batteryListener.clear(); return OK; } bool batteryPropertiesListenerIsCharging() { return batteryListener->isCharging(); } } // namespace android void loc_extn_battery_properties_listener_init(battery_status_change_fn_t fn) { ALOGV("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(); }