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 | |
| 63 | // |
| 64 | // Getters. |
| 65 | // |
| 66 | |
| 67 | template <typename T> |
| 68 | static ndk::ScopedAStatus GetProperty(::android::BatteryMonitor* monitor, int id, T defaultValue, |
| 69 | T* out) { |
| 70 | *out = defaultValue; |
| 71 | struct ::android::BatteryProperty prop; |
| 72 | ::android::status_t err = monitor->getProperty(static_cast<int>(id), &prop); |
| 73 | if (err == ::android::OK) { |
| 74 | *out = static_cast<T>(prop.valueInt64); |
| 75 | } else { |
| 76 | LOG(DEBUG) << "getProperty(" << id << ")" |
| 77 | << " fails: (" << err << ") " << ::android::statusToString(err); |
| 78 | } |
| 79 | |
| 80 | switch (err) { |
| 81 | case ::android::OK: |
| 82 | return ndk::ScopedAStatus::ok(); |
| 83 | case ::android::NAME_NOT_FOUND: |
| 84 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| 85 | default: |
| 86 | return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 87 | IHealth::STATUS_UNKNOWN, ::android::statusToString(err).c_str()); |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | ndk::ScopedAStatus Health::getChargeCounterUah(int32_t* out) { |
| 92 | return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CHARGE_COUNTER, 0, out); |
| 93 | } |
| 94 | |
| 95 | ndk::ScopedAStatus Health::getCurrentNowMicroamps(int32_t* out) { |
| 96 | return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CURRENT_NOW, 0, out); |
| 97 | } |
| 98 | |
| 99 | ndk::ScopedAStatus Health::getCurrentAverageMicroamps(int32_t* out) { |
| 100 | return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CURRENT_AVG, 0, out); |
| 101 | } |
| 102 | |
| 103 | ndk::ScopedAStatus Health::getCapacity(int32_t* out) { |
| 104 | return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CAPACITY, 0, out); |
| 105 | } |
| 106 | |
| 107 | ndk::ScopedAStatus Health::getEnergyCounterNwh(int64_t* out) { |
| 108 | return GetProperty<int64_t>(&battery_monitor_, ::android::BATTERY_PROP_ENERGY_COUNTER, 0, out); |
| 109 | } |
| 110 | |
| 111 | ndk::ScopedAStatus Health::getChargeStatus(BatteryStatus* out) { |
| 112 | return GetProperty(&battery_monitor_, ::android::BATTERY_PROP_BATTERY_STATUS, |
| 113 | BatteryStatus::UNKNOWN, out); |
| 114 | } |
| 115 | |
| 116 | ndk::ScopedAStatus Health::getDiskStats(std::vector<DiskStats>*) { |
| 117 | // This implementation does not support DiskStats. An implementation may extend this |
| 118 | // class and override this function to support disk stats. |
| 119 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| 120 | } |
| 121 | |
| 122 | ndk::ScopedAStatus Health::getStorageInfo(std::vector<StorageInfo>*) { |
| 123 | // This implementation does not support StorageInfo. An implementation may extend this |
| 124 | // class and override this function to support storage info. |
| 125 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| 126 | } |
| 127 | |
| 128 | ndk::ScopedAStatus Health::getHealthInfo(HealthInfo* out) { |
| 129 | battery_monitor_.updateValues(); |
| 130 | |
| 131 | // TODO(b/177269435): BatteryMonitor should store AIDL HealthInfo instead. |
| 132 | auto health_info_2_1 = battery_monitor_.getHealthInfo_2_1(); |
| 133 | if (!::android::h2a::translate(health_info_2_1, out)) { |
| 134 | return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 135 | IHealth::STATUS_UNKNOWN, "Cannot translate HIDL HealthInfo to AIDL"); |
| 136 | } |
| 137 | |
| 138 | // Fill in storage infos; these aren't retrieved by BatteryMonitor. |
| 139 | if (auto res = getStorageInfo(&out->storageInfos); !res.isOk()) { |
| 140 | if (res.getServiceSpecificError() == 0 && |
| 141 | res.getExceptionCode() != EX_UNSUPPORTED_OPERATION) { |
| 142 | return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 143 | IHealth::STATUS_UNKNOWN, |
| 144 | ("getStorageInfo fails: " + res.getDescription()).c_str()); |
| 145 | } |
| 146 | LOG(DEBUG) << "getHealthInfo: getStorageInfo fails with service-specific error, clearing: " |
| 147 | << res.getDescription(); |
| 148 | out->storageInfos = {}; |
| 149 | } |
| 150 | if (auto res = getDiskStats(&out->diskStats); !res.isOk()) { |
| 151 | if (res.getServiceSpecificError() == 0 && |
| 152 | res.getExceptionCode() != EX_UNSUPPORTED_OPERATION) { |
| 153 | return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 154 | IHealth::STATUS_UNKNOWN, |
| 155 | ("getDiskStats fails: " + res.getDescription()).c_str()); |
| 156 | } |
| 157 | LOG(DEBUG) << "getHealthInfo: getDiskStats fails with service-specific error, clearing: " |
| 158 | << res.getDescription(); |
| 159 | out->diskStats = {}; |
| 160 | } |
| 161 | |
| 162 | // A subclass may want to update health info struct before returning it. |
| 163 | UpdateHealthInfo(out); |
| 164 | |
| 165 | return ndk::ScopedAStatus::ok(); |
| 166 | } |
| 167 | |
| 168 | binder_status_t Health::dump(int fd, const char**, uint32_t) { |
| 169 | battery_monitor_.dumpState(fd); |
| 170 | |
| 171 | ::android::base::WriteStringToFd("\ngetHealthInfo -> ", fd); |
| 172 | HealthInfo health_info; |
| 173 | auto res = getHealthInfo(&health_info); |
| 174 | if (res.isOk()) { |
| 175 | ::android::base::WriteStringToFd(health_info.toString(), fd); |
| 176 | } else { |
| 177 | ::android::base::WriteStringToFd(res.getDescription(), fd); |
| 178 | } |
| 179 | |
| 180 | fsync(fd); |
| 181 | return STATUS_OK; |
| 182 | } |
| 183 | |
| 184 | std::optional<bool> Health::ShouldKeepScreenOn() { |
| 185 | if (!healthd_config_->screen_on) { |
| 186 | return std::nullopt; |
| 187 | } |
| 188 | |
| 189 | HealthInfo health_info; |
| 190 | auto res = getHealthInfo(&health_info); |
| 191 | if (!res.isOk()) { |
| 192 | return std::nullopt; |
| 193 | } |
| 194 | |
| 195 | ::android::BatteryProperties props = {}; |
| 196 | convert(health_info, &props); |
| 197 | return healthd_config_->screen_on(&props); |
| 198 | } |
| 199 | |
| 200 | namespace { |
| 201 | bool IsDeadObjectLogged(const ndk::ScopedAStatus& ret) { |
| 202 | if (ret.isOk()) return false; |
| 203 | if (ret.getStatus() == ::STATUS_DEAD_OBJECT) return true; |
| 204 | LOG(ERROR) << "Cannot call healthInfoChanged on callback: " << ret.getDescription(); |
| 205 | return false; |
| 206 | } |
| 207 | } // namespace |
| 208 | |
| 209 | // |
| 210 | // Subclass helpers / overrides |
| 211 | // |
| 212 | |
| 213 | void Health::UpdateHealthInfo(HealthInfo* /* health_info */) { |
| 214 | /* |
| 215 | // Sample code for a subclass to implement this: |
| 216 | // If you need to modify values (e.g. batteryChargeTimeToFullNowSeconds), do it here. |
| 217 | health_info->batteryChargeTimeToFullNowSeconds = calculate_charge_time_seconds(); |
| 218 | |
| 219 | // If you need to call healthd_board_battery_update, modify its signature |
| 220 | // and implementation to operate on HealthInfo directly, then call: |
| 221 | healthd_board_battery_update(health_info); |
| 222 | */ |
| 223 | } |
| 224 | |
| 225 | // |
| 226 | // Methods that handle callbacks. |
| 227 | // |
| 228 | |
| 229 | ndk::ScopedAStatus Health::registerCallback(const std::shared_ptr<IHealthInfoCallback>& callback) { |
| 230 | if (callback == nullptr) { |
| 231 | // For now, this shouldn't happen because argument is not nullable. |
| 232 | return ndk::ScopedAStatus::fromExceptionCode(EX_NULL_POINTER); |
| 233 | } |
| 234 | |
| 235 | { |
| 236 | std::lock_guard<decltype(callbacks_lock_)> lock(callbacks_lock_); |
| 237 | callbacks_.emplace_back(LinkedCallback::Make(ref<Health>(), callback)); |
| 238 | // unlock |
| 239 | } |
| 240 | |
| 241 | HealthInfo health_info; |
| 242 | if (auto res = getHealthInfo(&health_info); !res.isOk()) { |
| 243 | LOG(WARNING) << "Cannot call getHealthInfo: " << res.getDescription(); |
| 244 | // No health info to send, so return early. |
| 245 | return ndk::ScopedAStatus::ok(); |
| 246 | } |
| 247 | |
| 248 | if (auto res = callback->healthInfoChanged(health_info); IsDeadObjectLogged(res)) { |
| 249 | (void)unregisterCallback(callback); |
| 250 | } |
| 251 | return ndk::ScopedAStatus::ok(); |
| 252 | } |
| 253 | |
| 254 | ndk::ScopedAStatus Health::unregisterCallback( |
| 255 | const std::shared_ptr<IHealthInfoCallback>& callback) { |
| 256 | if (callback == nullptr) { |
| 257 | // For now, this shouldn't happen because argument is not nullable. |
| 258 | return ndk::ScopedAStatus::fromExceptionCode(EX_NULL_POINTER); |
| 259 | } |
| 260 | |
| 261 | std::lock_guard<decltype(callbacks_lock_)> lock(callbacks_lock_); |
| 262 | |
| 263 | auto matches = [callback](const auto& linked) { |
Yifan Hong | 6839f67 | 2021-10-28 22:24:35 -0700 | [diff] [blame] | 264 | return linked->callback()->asBinder() == callback->asBinder(); // compares binder object |
Yifan Hong | 830cdb1 | 2021-01-11 20:47:23 -0800 | [diff] [blame] | 265 | }; |
| 266 | auto it = std::remove_if(callbacks_.begin(), callbacks_.end(), matches); |
| 267 | bool removed = (it != callbacks_.end()); |
| 268 | callbacks_.erase(it, callbacks_.end()); // calls unlinkToDeath on deleted callbacks. |
| 269 | return removed ? ndk::ScopedAStatus::ok() |
| 270 | : ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); |
| 271 | } |
| 272 | |
| 273 | // A combination of the HIDL version |
| 274 | // android::hardware::health::V2_1::implementation::Health::update() and |
| 275 | // android::hardware::health::V2_1::implementation::BinderHealth::update() |
| 276 | ndk::ScopedAStatus Health::update() { |
| 277 | HealthInfo health_info; |
| 278 | if (auto res = getHealthInfo(&health_info); !res.isOk()) { |
| 279 | LOG(DEBUG) << "Cannot call getHealthInfo for update(): " << res.getDescription(); |
| 280 | // Propagate service specific errors. If there's none, report unknown error. |
| 281 | if (res.getServiceSpecificError() != 0 || |
| 282 | res.getExceptionCode() == EX_UNSUPPORTED_OPERATION) { |
| 283 | return res; |
| 284 | } |
| 285 | return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 286 | IHealth::STATUS_UNKNOWN, res.getDescription().c_str()); |
| 287 | } |
| 288 | battery_monitor_.logValues(); |
| 289 | OnHealthInfoChanged(health_info); |
| 290 | return ndk::ScopedAStatus::ok(); |
| 291 | } |
| 292 | |
| 293 | void Health::OnHealthInfoChanged(const HealthInfo& health_info) { |
| 294 | // Notify all callbacks |
| 295 | std::unique_lock<decltype(callbacks_lock_)> lock(callbacks_lock_); |
| 296 | // is_dead notifies a callback and return true if it is dead. |
| 297 | auto is_dead = [&](const auto& linked) { |
| 298 | auto res = linked->callback()->healthInfoChanged(health_info); |
| 299 | return IsDeadObjectLogged(res); |
| 300 | }; |
| 301 | auto it = std::remove_if(callbacks_.begin(), callbacks_.end(), is_dead); |
| 302 | callbacks_.erase(it, callbacks_.end()); // calls unlinkToDeath on deleted callbacks. |
| 303 | lock.unlock(); |
| 304 | |
| 305 | // Let HalHealthLoop::OnHealthInfoChanged() adjusts uevent / wakealarm periods |
| 306 | } |
| 307 | |
| 308 | void Health::BinderEvent(uint32_t /*epevents*/) { |
| 309 | if (binder_fd_ >= 0) { |
| 310 | ABinderProcess_handlePolledCommands(); |
| 311 | } |
| 312 | } |
| 313 | |
| 314 | void Health::OnInit(HalHealthLoop* hal_health_loop, struct healthd_config* config) { |
| 315 | LOG(INFO) << instance_name_ << " instance initializing with healthd_config..."; |
| 316 | |
| 317 | // Similar to HIDL's android::hardware::health::V2_1::implementation::HalHealthLoop::Init, |
| 318 | // copy configuration parameters to |config| for HealthLoop (e.g. uevent / wake alarm periods) |
| 319 | *config = *healthd_config_.get(); |
| 320 | |
| 321 | binder_status_t status = ABinderProcess_setupPolling(&binder_fd_); |
| 322 | |
| 323 | if (status == ::STATUS_OK && binder_fd_ >= 0) { |
| 324 | std::shared_ptr<Health> thiz = ref<Health>(); |
| 325 | auto binder_event = [thiz](auto*, uint32_t epevents) { thiz->BinderEvent(epevents); }; |
| 326 | if (hal_health_loop->RegisterEvent(binder_fd_, binder_event, EVENT_NO_WAKEUP_FD) != 0) { |
| 327 | PLOG(ERROR) << instance_name_ << " instance: Register for binder events failed"; |
| 328 | } |
| 329 | } |
| 330 | |
| 331 | std::string health_name = IHealth::descriptor + "/"s + instance_name_; |
| 332 | CHECK_EQ(STATUS_OK, AServiceManager_addService(this->asBinder().get(), health_name.c_str())) |
| 333 | << instance_name_ << ": Failed to register HAL"; |
| 334 | |
| 335 | LOG(INFO) << instance_name_ << ": Hal init done"; |
| 336 | } |
| 337 | |
| 338 | // Unlike hwbinder, for binder, there's no need to explicitly call flushCommands() |
| 339 | // in PrepareToWait(). See b/139697085. |
| 340 | |
| 341 | } // namespace aidl::android::hardware::health |