1 /*
2 * Copyright (C) 2022 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 #define LOG_TAG "pixelstats: BatteryHealthReporter"
18
19 #include <android-base/file.h>
20 #include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
21 #include <log/log.h>
22 #include <pixelstats/BatteryHealthReporter.h>
23 #include <pixelstats/StatsHelper.h>
24 #include <time.h>
25 #include <utils/Timers.h>
26
27 #include <cinttypes>
28
29 namespace android {
30 namespace hardware {
31 namespace google {
32 namespace pixel {
33
34 using aidl::android::frameworks::stats::IStats;
35 using aidl::android::frameworks::stats::VendorAtom;
36 using aidl::android::frameworks::stats::VendorAtomValue;
37 using android::base::ReadFileToString;
38 using android::base::WriteStringToFile;
39 using android::hardware::google::pixel::PixelAtoms::BatteryHealthStatus;
40 using android::hardware::google::pixel::PixelAtoms::BatteryHealthUsage;
41
42 const int SECONDS_PER_MONTH = 60 * 60 * 24 * 30;
43
BatteryHealthReporter()44 BatteryHealthReporter::BatteryHealthReporter() {}
45
getTimeSecs(void)46 int64_t BatteryHealthReporter::getTimeSecs(void) {
47 return nanoseconds_to_seconds(systemTime(SYSTEM_TIME_BOOTTIME));
48 }
49
reportBatteryHealthStatus(const std::shared_ptr<IStats> & stats_client)50 bool BatteryHealthReporter::reportBatteryHealthStatus(const std::shared_ptr<IStats> &stats_client) {
51 std::string path = kBatteryHealthStatusPath;
52 std::string file_contents, line;
53 std::istringstream ss;
54
55 if (!ReadFileToString(path.c_str(), &file_contents)) {
56 ALOGD("Unsupported path %s - %s", path.c_str(), strerror(errno));
57 return false;
58 }
59
60 ss.str(file_contents);
61
62 while (std::getline(ss, line)) {
63 reportBatteryHealthStatusEvent(stats_client, line.c_str());
64 }
65
66 return true;
67 }
68
reportBatteryHealthStatusEvent(const std::shared_ptr<IStats> & stats_client,const char * line)69 void BatteryHealthReporter::reportBatteryHealthStatusEvent(
70 const std::shared_ptr<IStats> &stats_client, const char *line) {
71 int health_status_stats_fields[] = {
72 BatteryHealthStatus::kHealthAlgorithmFieldNumber,
73 BatteryHealthStatus::kHealthStatusFieldNumber,
74 BatteryHealthStatus::kHealthIndexFieldNumber,
75 BatteryHealthStatus::kHealthCapacityIndexFieldNumber,
76 BatteryHealthStatus::kHealthImpedanceIndexFieldNumber,
77 BatteryHealthStatus::kSwellingCumulativeFieldNumber,
78 BatteryHealthStatus::kHealthFullCapacityFieldNumber,
79 BatteryHealthStatus::kCurrentImpedanceFieldNumber,
80 BatteryHealthStatus::kBatteryAgeFieldNumber,
81 BatteryHealthStatus::kCycleCountFieldNumber,
82 BatteryHealthStatus::kBatteryDisconnectStatusFieldNumber,
83 };
84
85 const int32_t vtier_fields_size = std::size(health_status_stats_fields);
86 static_assert(vtier_fields_size == 11, "Unexpected battery health status fields size");
87 std::vector<VendorAtomValue> values(vtier_fields_size);
88 VendorAtomValue val;
89 int32_t i = 0, fields_size = 0, tmp[vtier_fields_size] = {0};
90
91 // health_algo: health_status, health_index,healh_capacity_index,health_imp_index,
92 // swelling_cumulative,health_full_capacity,current_impedance, battery_age,cycle_count,
93 // bpst_status
94 fields_size = sscanf(line, "%d: %d, %d,%d,%d %d,%d,%d %d,%d, %d", &tmp[0], &tmp[1], &tmp[2],
95 &tmp[3], &tmp[4], &tmp[5], &tmp[6], &tmp[7], &tmp[8], &tmp[9], &tmp[10]);
96 if (fields_size < (vtier_fields_size - 1) || fields_size > vtier_fields_size) {
97 // Whether bpst_status exists or not, it needs to be compatible
98 // If format isn't as expected, then ignore line on purpose
99 return;
100 }
101
102 ALOGD("BatteryHealthStatus: processed %s", line);
103 for (i = 0; i < fields_size; i++) {
104 val.set<VendorAtomValue::intValue>(tmp[i]);
105 values[health_status_stats_fields[i] - kVendorAtomOffset] = val;
106 }
107
108 VendorAtom event = {.reverseDomainName = "",
109 .atomId = PixelAtoms::Atom::kBatteryHealthStatus,
110 .values = std::move(values)};
111 reportVendorAtom(stats_client, event);
112 }
113
reportBatteryHealthUsage(const std::shared_ptr<IStats> & stats_client)114 bool BatteryHealthReporter::reportBatteryHealthUsage(const std::shared_ptr<IStats> &stats_client) {
115 std::string path = kBatteryHealthUsagePath;
116 std::string file_contents, line;
117 std::istringstream ss;
118
119 if (!ReadFileToString(path.c_str(), &file_contents)) {
120 ALOGD("Unsupported path %s - %s", path.c_str(), strerror(errno));
121 return false;
122 }
123
124 ss.str(file_contents);
125
126 // skip first title line
127 if (!std::getline(ss, line)) {
128 ALOGE("Unable to read first line of: %s", path.c_str());
129 return false;
130 }
131
132 while (std::getline(ss, line)) {
133 reportBatteryHealthUsageEvent(stats_client, line.c_str());
134 }
135
136 return true;
137 }
138
reportBatteryHealthUsageEvent(const std::shared_ptr<IStats> & stats_client,const char * line)139 void BatteryHealthReporter::reportBatteryHealthUsageEvent(
140 const std::shared_ptr<IStats> &stats_client, const char *line) {
141 int health_status_stats_fields[] = {
142 BatteryHealthUsage::kTemperatureLimitDeciCFieldNumber,
143 BatteryHealthUsage::kSocLimitFieldNumber,
144 BatteryHealthUsage::kChargeTimeSecsFieldNumber,
145 BatteryHealthUsage::kDischargeTimeSecsFieldNumber,
146 };
147
148 const int32_t vtier_fields_size = std::size(health_status_stats_fields);
149 static_assert(vtier_fields_size == 4, "Unexpected battery health status fields size");
150 std::vector<VendorAtomValue> values(vtier_fields_size);
151 VendorAtomValue val;
152 int32_t i = 0, tmp[vtier_fields_size] = {0};
153
154 // temp/soc charge(s) discharge(s)
155 if (sscanf(line, "%d/%d\t%d\t%d", &tmp[0], &tmp[1], &tmp[2], &tmp[3]) != vtier_fields_size) {
156 /* If format isn't as expected, then ignore line on purpose */
157 return;
158 }
159
160 ALOGD("BatteryHealthUsage: processed %s", line);
161 for (i = 0; i < vtier_fields_size; i++) {
162 val.set<VendorAtomValue::intValue>(tmp[i]);
163 values[health_status_stats_fields[i] - kVendorAtomOffset] = val;
164 }
165
166 VendorAtom event = {.reverseDomainName = "",
167 .atomId = PixelAtoms::Atom::kBatteryHealthUsage,
168 .values = std::move(values)};
169 reportVendorAtom(stats_client, event);
170 }
171
checkAndReportStatus(const std::shared_ptr<IStats> & stats_client)172 void BatteryHealthReporter::checkAndReportStatus(const std::shared_ptr<IStats> &stats_client) {
173 int64_t now = getTimeSecs();
174 if ((report_time_ != 0) && (now - report_time_ < SECONDS_PER_MONTH)) {
175 ALOGD("Do not upload yet. now: %" PRId64 ", pre: %" PRId64, now, report_time_);
176 return;
177 }
178
179 bool successStatus = reportBatteryHealthStatus(stats_client);
180 bool successUsage = reportBatteryHealthUsage(stats_client);
181
182 if (successStatus && successUsage) {
183 report_time_ = now;
184 }
185 }
186
187 } // namespace pixel
188 } // namespace google
189 } // namespace hardware
190 } // namespace android
191