Yifan Hong | 830cdb1 | 2021-01-11 20:47:23 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2021 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include "health-impl/Health.h" |
| 18 | |
| 19 | #include <android-base/file.h> |
| 20 | #include <android-base/logging.h> |
| 21 | #include <android/binder_manager.h> |
| 22 | #include <android/binder_process.h> |
| 23 | #include <android/hardware/health/translate-ndk.h> |
| 24 | #include <health/utils.h> |
| 25 | |
| 26 | #include "LinkedCallback.h" |
| 27 | #include "health-convert.h" |
| 28 | |
| 29 | using std::string_literals::operator""s; |
| 30 | |
| 31 | namespace aidl::android::hardware::health { |
| 32 | |
| 33 | namespace { |
| 34 | // Wrap LinkedCallback::OnCallbackDied() into a void(void*). |
| 35 | void OnCallbackDiedWrapped(void* cookie) { |
| 36 | LinkedCallback* linked = reinterpret_cast<LinkedCallback*>(cookie); |
| 37 | linked->OnCallbackDied(); |
| 38 | } |
| 39 | } // namespace |
| 40 | |
| 41 | /* |
| 42 | // If you need to call healthd_board_init, construct the Health instance with |
| 43 | // the healthd_config after calling healthd_board_init: |
| 44 | class MyHealth : public Health { |
| 45 | protected: |
| 46 | MyHealth() : Health(CreateConfig()) {} |
| 47 | private: |
| 48 | static std::unique_ptr<healthd_config> CreateConfig() { |
| 49 | auto config = std::make_unique<healthd_config>(); |
| 50 | ::android::hardware::health::InitHealthdConfig(config.get()); |
| 51 | healthd_board_init(config.get()); |
| 52 | return std::move(config); |
| 53 | } |
| 54 | }; |
| 55 | */ |
| 56 | Health::Health(std::string_view instance_name, std::unique_ptr<struct healthd_config>&& config) |
| 57 | : instance_name_(instance_name), |
| 58 | healthd_config_(std::move(config)), |
| 59 | death_recipient_(AIBinder_DeathRecipient_new(&OnCallbackDiedWrapped)) { |
| 60 | battery_monitor_.init(healthd_config_.get()); |
| 61 | } |
| 62 | |
Yifan Hong | 2d418a2 | 2021-11-12 18:13:51 -0800 | [diff] [blame] | 63 | Health::~Health() {} |
| 64 | |
Yifan Hong | 830cdb1 | 2021-01-11 20:47:23 -0800 | [diff] [blame] | 65 | // |
| 66 | // Getters. |
| 67 | // |
| 68 | |
| 69 | template <typename T> |
| 70 | static ndk::ScopedAStatus GetProperty(::android::BatteryMonitor* monitor, int id, T defaultValue, |
| 71 | T* out) { |
| 72 | *out = defaultValue; |
| 73 | struct ::android::BatteryProperty prop; |
| 74 | ::android::status_t err = monitor->getProperty(static_cast<int>(id), &prop); |
| 75 | if (err == ::android::OK) { |
| 76 | *out = static_cast<T>(prop.valueInt64); |
| 77 | } else { |
| 78 | LOG(DEBUG) << "getProperty(" << id << ")" |
| 79 | << " fails: (" << err << ") " << ::android::statusToString(err); |
| 80 | } |
| 81 | |
| 82 | switch (err) { |
| 83 | case ::android::OK: |
| 84 | return ndk::ScopedAStatus::ok(); |
| 85 | case ::android::NAME_NOT_FOUND: |
| 86 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| 87 | default: |
| 88 | return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 89 | IHealth::STATUS_UNKNOWN, ::android::statusToString(err).c_str()); |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | ndk::ScopedAStatus Health::getChargeCounterUah(int32_t* out) { |
| 94 | return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CHARGE_COUNTER, 0, out); |
| 95 | } |
| 96 | |
| 97 | ndk::ScopedAStatus Health::getCurrentNowMicroamps(int32_t* out) { |
| 98 | return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CURRENT_NOW, 0, out); |
| 99 | } |
| 100 | |
| 101 | ndk::ScopedAStatus Health::getCurrentAverageMicroamps(int32_t* out) { |
| 102 | return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CURRENT_AVG, 0, out); |
| 103 | } |
| 104 | |
| 105 | ndk::ScopedAStatus Health::getCapacity(int32_t* out) { |
| 106 | return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CAPACITY, 0, out); |
| 107 | } |
| 108 | |
| 109 | ndk::ScopedAStatus Health::getEnergyCounterNwh(int64_t* out) { |
| 110 | return GetProperty<int64_t>(&battery_monitor_, ::android::BATTERY_PROP_ENERGY_COUNTER, 0, out); |
| 111 | } |
| 112 | |
| 113 | ndk::ScopedAStatus Health::getChargeStatus(BatteryStatus* out) { |
| 114 | return GetProperty(&battery_monitor_, ::android::BATTERY_PROP_BATTERY_STATUS, |
| 115 | BatteryStatus::UNKNOWN, out); |
| 116 | } |
| 117 | |
Jack Wu | 3356161 | 2022-11-24 12:10:55 +0800 | [diff] [blame] | 118 | ndk::ScopedAStatus Health::setChargingPolicy(BatteryChargingPolicy in_value) { |
| 119 | ::android::status_t err = battery_monitor_.setChargingPolicy(static_cast<int>(in_value)); |
| 120 | |
| 121 | switch (err) { |
| 122 | case ::android::OK: |
| 123 | return ndk::ScopedAStatus::ok(); |
| 124 | case ::android::NAME_NOT_FOUND: |
| 125 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| 126 | case ::android::BAD_VALUE: |
| 127 | return ndk::ScopedAStatus::fromStatus(::android::INVALID_OPERATION); |
| 128 | default: |
| 129 | return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 130 | IHealth::STATUS_UNKNOWN, ::android::statusToString(err).c_str()); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | ndk::ScopedAStatus Health::getChargingPolicy(BatteryChargingPolicy* out) { |
| 135 | return GetProperty(&battery_monitor_, ::android::BATTERY_PROP_CHARGING_POLICY, |
| 136 | BatteryChargingPolicy::DEFAULT, out); |
| 137 | } |
| 138 | |
| 139 | ndk::ScopedAStatus Health::getBatteryHealthData(BatteryHealthData* out) { |
| 140 | if (auto res = |
| 141 | GetProperty<int64_t>(&battery_monitor_, ::android::BATTERY_PROP_MANUFACTURING_DATE, |
| 142 | 0, &out->batteryManufacturingDateSeconds); |
| 143 | !res.isOk()) { |
| 144 | LOG(WARNING) << "Cannot get Manufacturing_date: " << res.getDescription(); |
| 145 | } |
| 146 | if (auto res = GetProperty<int64_t>(&battery_monitor_, ::android::BATTERY_PROP_FIRST_USAGE_DATE, |
| 147 | 0, &out->batteryFirstUsageSeconds); |
| 148 | !res.isOk()) { |
| 149 | LOG(WARNING) << "Cannot get First_usage_date: " << res.getDescription(); |
| 150 | } |
Jack Wu | cbbf24f | 2023-02-18 12:34:19 +0800 | [diff] [blame] | 151 | if (auto res = GetProperty<int64_t>(&battery_monitor_, ::android::BATTERY_PROP_STATE_OF_HEALTH, |
| 152 | 0, &out->batteryStateOfHealth); |
| 153 | !res.isOk()) { |
| 154 | LOG(WARNING) << "Cannot get Battery_state_of_health: " << res.getDescription(); |
| 155 | } |
Jack Wu | 3356161 | 2022-11-24 12:10:55 +0800 | [diff] [blame] | 156 | return ndk::ScopedAStatus::ok(); |
| 157 | } |
| 158 | |
Yifan Hong | 830cdb1 | 2021-01-11 20:47:23 -0800 | [diff] [blame] | 159 | ndk::ScopedAStatus Health::getDiskStats(std::vector<DiskStats>*) { |
| 160 | // This implementation does not support DiskStats. An implementation may extend this |
| 161 | // class and override this function to support disk stats. |
| 162 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| 163 | } |
| 164 | |
| 165 | ndk::ScopedAStatus Health::getStorageInfo(std::vector<StorageInfo>*) { |
| 166 | // This implementation does not support StorageInfo. An implementation may extend this |
| 167 | // class and override this function to support storage info. |
| 168 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| 169 | } |
| 170 | |
| 171 | ndk::ScopedAStatus Health::getHealthInfo(HealthInfo* out) { |
| 172 | battery_monitor_.updateValues(); |
| 173 | |
Yifan Hong | bc84a79 | 2022-03-01 12:24:55 -0800 | [diff] [blame] | 174 | *out = battery_monitor_.getHealthInfo(); |
Yifan Hong | 830cdb1 | 2021-01-11 20:47:23 -0800 | [diff] [blame] | 175 | |
| 176 | // Fill in storage infos; these aren't retrieved by BatteryMonitor. |
| 177 | if (auto res = getStorageInfo(&out->storageInfos); !res.isOk()) { |
| 178 | if (res.getServiceSpecificError() == 0 && |
| 179 | res.getExceptionCode() != EX_UNSUPPORTED_OPERATION) { |
| 180 | return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 181 | IHealth::STATUS_UNKNOWN, |
| 182 | ("getStorageInfo fails: " + res.getDescription()).c_str()); |
| 183 | } |
| 184 | LOG(DEBUG) << "getHealthInfo: getStorageInfo fails with service-specific error, clearing: " |
| 185 | << res.getDescription(); |
| 186 | out->storageInfos = {}; |
| 187 | } |
| 188 | if (auto res = getDiskStats(&out->diskStats); !res.isOk()) { |
| 189 | if (res.getServiceSpecificError() == 0 && |
| 190 | res.getExceptionCode() != EX_UNSUPPORTED_OPERATION) { |
| 191 | return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 192 | IHealth::STATUS_UNKNOWN, |
| 193 | ("getDiskStats fails: " + res.getDescription()).c_str()); |
| 194 | } |
| 195 | LOG(DEBUG) << "getHealthInfo: getDiskStats fails with service-specific error, clearing: " |
| 196 | << res.getDescription(); |
| 197 | out->diskStats = {}; |
| 198 | } |
| 199 | |
| 200 | // A subclass may want to update health info struct before returning it. |
| 201 | UpdateHealthInfo(out); |
| 202 | |
| 203 | return ndk::ScopedAStatus::ok(); |
| 204 | } |
| 205 | |
| 206 | binder_status_t Health::dump(int fd, const char**, uint32_t) { |
| 207 | battery_monitor_.dumpState(fd); |
| 208 | |
| 209 | ::android::base::WriteStringToFd("\ngetHealthInfo -> ", fd); |
| 210 | HealthInfo health_info; |
| 211 | auto res = getHealthInfo(&health_info); |
| 212 | if (res.isOk()) { |
| 213 | ::android::base::WriteStringToFd(health_info.toString(), fd); |
| 214 | } else { |
| 215 | ::android::base::WriteStringToFd(res.getDescription(), fd); |
| 216 | } |
| 217 | |
| 218 | fsync(fd); |
| 219 | return STATUS_OK; |
| 220 | } |
| 221 | |
| 222 | std::optional<bool> Health::ShouldKeepScreenOn() { |
| 223 | if (!healthd_config_->screen_on) { |
| 224 | return std::nullopt; |
| 225 | } |
| 226 | |
| 227 | HealthInfo health_info; |
| 228 | auto res = getHealthInfo(&health_info); |
| 229 | if (!res.isOk()) { |
| 230 | return std::nullopt; |
| 231 | } |
| 232 | |
| 233 | ::android::BatteryProperties props = {}; |
| 234 | convert(health_info, &props); |
| 235 | return healthd_config_->screen_on(&props); |
| 236 | } |
| 237 | |
| 238 | namespace { |
| 239 | bool IsDeadObjectLogged(const ndk::ScopedAStatus& ret) { |
| 240 | if (ret.isOk()) return false; |
| 241 | if (ret.getStatus() == ::STATUS_DEAD_OBJECT) return true; |
| 242 | LOG(ERROR) << "Cannot call healthInfoChanged on callback: " << ret.getDescription(); |
| 243 | return false; |
| 244 | } |
| 245 | } // namespace |
| 246 | |
| 247 | // |
| 248 | // Subclass helpers / overrides |
| 249 | // |
| 250 | |
| 251 | void Health::UpdateHealthInfo(HealthInfo* /* health_info */) { |
| 252 | /* |
| 253 | // Sample code for a subclass to implement this: |
| 254 | // If you need to modify values (e.g. batteryChargeTimeToFullNowSeconds), do it here. |
| 255 | health_info->batteryChargeTimeToFullNowSeconds = calculate_charge_time_seconds(); |
| 256 | |
| 257 | // If you need to call healthd_board_battery_update, modify its signature |
| 258 | // and implementation to operate on HealthInfo directly, then call: |
| 259 | healthd_board_battery_update(health_info); |
| 260 | */ |
| 261 | } |
| 262 | |
| 263 | // |
| 264 | // Methods that handle callbacks. |
| 265 | // |
| 266 | |
| 267 | ndk::ScopedAStatus Health::registerCallback(const std::shared_ptr<IHealthInfoCallback>& callback) { |
| 268 | if (callback == nullptr) { |
| 269 | // For now, this shouldn't happen because argument is not nullable. |
| 270 | return ndk::ScopedAStatus::fromExceptionCode(EX_NULL_POINTER); |
| 271 | } |
| 272 | |
| 273 | { |
| 274 | std::lock_guard<decltype(callbacks_lock_)> lock(callbacks_lock_); |
| 275 | callbacks_.emplace_back(LinkedCallback::Make(ref<Health>(), callback)); |
| 276 | // unlock |
| 277 | } |
| 278 | |
| 279 | HealthInfo health_info; |
| 280 | if (auto res = getHealthInfo(&health_info); !res.isOk()) { |
| 281 | LOG(WARNING) << "Cannot call getHealthInfo: " << res.getDescription(); |
| 282 | // No health info to send, so return early. |
| 283 | return ndk::ScopedAStatus::ok(); |
| 284 | } |
| 285 | |
| 286 | if (auto res = callback->healthInfoChanged(health_info); IsDeadObjectLogged(res)) { |
| 287 | (void)unregisterCallback(callback); |
| 288 | } |
| 289 | return ndk::ScopedAStatus::ok(); |
| 290 | } |
| 291 | |
| 292 | ndk::ScopedAStatus Health::unregisterCallback( |
| 293 | const std::shared_ptr<IHealthInfoCallback>& callback) { |
| 294 | if (callback == nullptr) { |
| 295 | // For now, this shouldn't happen because argument is not nullable. |
| 296 | return ndk::ScopedAStatus::fromExceptionCode(EX_NULL_POINTER); |
| 297 | } |
| 298 | |
| 299 | std::lock_guard<decltype(callbacks_lock_)> lock(callbacks_lock_); |
| 300 | |
| 301 | auto matches = [callback](const auto& linked) { |
Yifan Hong | 6839f67 | 2021-10-28 22:24:35 -0700 | [diff] [blame] | 302 | return linked->callback()->asBinder() == callback->asBinder(); // compares binder object |
Yifan Hong | 830cdb1 | 2021-01-11 20:47:23 -0800 | [diff] [blame] | 303 | }; |
| 304 | auto it = std::remove_if(callbacks_.begin(), callbacks_.end(), matches); |
| 305 | bool removed = (it != callbacks_.end()); |
| 306 | callbacks_.erase(it, callbacks_.end()); // calls unlinkToDeath on deleted callbacks. |
| 307 | return removed ? ndk::ScopedAStatus::ok() |
| 308 | : ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); |
| 309 | } |
| 310 | |
| 311 | // A combination of the HIDL version |
| 312 | // android::hardware::health::V2_1::implementation::Health::update() and |
| 313 | // android::hardware::health::V2_1::implementation::BinderHealth::update() |
| 314 | ndk::ScopedAStatus Health::update() { |
| 315 | HealthInfo health_info; |
| 316 | if (auto res = getHealthInfo(&health_info); !res.isOk()) { |
| 317 | LOG(DEBUG) << "Cannot call getHealthInfo for update(): " << res.getDescription(); |
| 318 | // Propagate service specific errors. If there's none, report unknown error. |
| 319 | if (res.getServiceSpecificError() != 0 || |
| 320 | res.getExceptionCode() == EX_UNSUPPORTED_OPERATION) { |
| 321 | return res; |
| 322 | } |
| 323 | return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 324 | IHealth::STATUS_UNKNOWN, res.getDescription().c_str()); |
| 325 | } |
| 326 | battery_monitor_.logValues(); |
| 327 | OnHealthInfoChanged(health_info); |
| 328 | return ndk::ScopedAStatus::ok(); |
| 329 | } |
| 330 | |
| 331 | void Health::OnHealthInfoChanged(const HealthInfo& health_info) { |
| 332 | // Notify all callbacks |
| 333 | std::unique_lock<decltype(callbacks_lock_)> lock(callbacks_lock_); |
| 334 | // is_dead notifies a callback and return true if it is dead. |
| 335 | auto is_dead = [&](const auto& linked) { |
| 336 | auto res = linked->callback()->healthInfoChanged(health_info); |
| 337 | return IsDeadObjectLogged(res); |
| 338 | }; |
| 339 | auto it = std::remove_if(callbacks_.begin(), callbacks_.end(), is_dead); |
| 340 | callbacks_.erase(it, callbacks_.end()); // calls unlinkToDeath on deleted callbacks. |
| 341 | lock.unlock(); |
| 342 | |
| 343 | // Let HalHealthLoop::OnHealthInfoChanged() adjusts uevent / wakealarm periods |
| 344 | } |
| 345 | |
| 346 | void Health::BinderEvent(uint32_t /*epevents*/) { |
| 347 | if (binder_fd_ >= 0) { |
| 348 | ABinderProcess_handlePolledCommands(); |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | void Health::OnInit(HalHealthLoop* hal_health_loop, struct healthd_config* config) { |
| 353 | LOG(INFO) << instance_name_ << " instance initializing with healthd_config..."; |
| 354 | |
| 355 | // Similar to HIDL's android::hardware::health::V2_1::implementation::HalHealthLoop::Init, |
| 356 | // copy configuration parameters to |config| for HealthLoop (e.g. uevent / wake alarm periods) |
| 357 | *config = *healthd_config_.get(); |
| 358 | |
| 359 | binder_status_t status = ABinderProcess_setupPolling(&binder_fd_); |
| 360 | |
| 361 | if (status == ::STATUS_OK && binder_fd_ >= 0) { |
| 362 | std::shared_ptr<Health> thiz = ref<Health>(); |
| 363 | auto binder_event = [thiz](auto*, uint32_t epevents) { thiz->BinderEvent(epevents); }; |
| 364 | if (hal_health_loop->RegisterEvent(binder_fd_, binder_event, EVENT_NO_WAKEUP_FD) != 0) { |
| 365 | PLOG(ERROR) << instance_name_ << " instance: Register for binder events failed"; |
| 366 | } |
| 367 | } |
| 368 | |
| 369 | std::string health_name = IHealth::descriptor + "/"s + instance_name_; |
| 370 | CHECK_EQ(STATUS_OK, AServiceManager_addService(this->asBinder().get(), health_name.c_str())) |
| 371 | << instance_name_ << ": Failed to register HAL"; |
| 372 | |
| 373 | LOG(INFO) << instance_name_ << ": Hal init done"; |
| 374 | } |
| 375 | |
| 376 | // Unlike hwbinder, for binder, there's no need to explicitly call flushCommands() |
| 377 | // in PrepareToWait(). See b/139697085. |
| 378 | |
| 379 | } // namespace aidl::android::hardware::health |