blob: 812e64abfc5b3afc10f7601203313832d5c5a4b1 [file] [log] [blame]
Yifan Hong830cdb12021-01-11 20:47:23 -08001/*
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
29using std::string_literals::operator""s;
30
31namespace aidl::android::hardware::health {
32
33namespace {
34// Wrap LinkedCallback::OnCallbackDied() into a void(void*).
35void 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:
44class 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*/
56Health::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
67template <typename T>
68static 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
91ndk::ScopedAStatus Health::getChargeCounterUah(int32_t* out) {
92 return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CHARGE_COUNTER, 0, out);
93}
94
95ndk::ScopedAStatus Health::getCurrentNowMicroamps(int32_t* out) {
96 return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CURRENT_NOW, 0, out);
97}
98
99ndk::ScopedAStatus Health::getCurrentAverageMicroamps(int32_t* out) {
100 return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CURRENT_AVG, 0, out);
101}
102
103ndk::ScopedAStatus Health::getCapacity(int32_t* out) {
104 return GetProperty<int32_t>(&battery_monitor_, ::android::BATTERY_PROP_CAPACITY, 0, out);
105}
106
107ndk::ScopedAStatus Health::getEnergyCounterNwh(int64_t* out) {
108 return GetProperty<int64_t>(&battery_monitor_, ::android::BATTERY_PROP_ENERGY_COUNTER, 0, out);
109}
110
111ndk::ScopedAStatus Health::getChargeStatus(BatteryStatus* out) {
112 return GetProperty(&battery_monitor_, ::android::BATTERY_PROP_BATTERY_STATUS,
113 BatteryStatus::UNKNOWN, out);
114}
115
116ndk::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
122ndk::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
128ndk::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
168binder_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
184std::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
200namespace {
201bool 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
213void 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
229ndk::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
254ndk::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 Hong6839f672021-10-28 22:24:35 -0700264 return linked->callback()->asBinder() == callback->asBinder(); // compares binder object
Yifan Hong830cdb12021-01-11 20:47:23 -0800265 };
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()
276ndk::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
293void 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
308void Health::BinderEvent(uint32_t /*epevents*/) {
309 if (binder_fd_ >= 0) {
310 ABinderProcess_handlePolledCommands();
311 }
312}
313
314void 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