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     const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
112     if (!ret.isOk())
113         ALOGE("Unable to report BatteryHealthStatus to Stats service");
114 }
115 
reportBatteryHealthUsage(const std::shared_ptr<IStats> & stats_client)116 bool BatteryHealthReporter::reportBatteryHealthUsage(const std::shared_ptr<IStats> &stats_client) {
117     std::string path = kBatteryHealthUsagePath;
118     std::string file_contents, line;
119     std::istringstream ss;
120 
121     if (!ReadFileToString(path.c_str(), &file_contents)) {
122         ALOGD("Unsupported path %s - %s", path.c_str(), strerror(errno));
123         return false;
124     }
125 
126     ss.str(file_contents);
127 
128     // skip first title line
129     if (!std::getline(ss, line)) {
130         ALOGE("Unable to read first line of: %s", path.c_str());
131         return false;
132     }
133 
134     while (std::getline(ss, line)) {
135         reportBatteryHealthUsageEvent(stats_client, line.c_str());
136     }
137 
138     return true;
139 }
140 
reportBatteryHealthUsageEvent(const std::shared_ptr<IStats> & stats_client,const char * line)141 void BatteryHealthReporter::reportBatteryHealthUsageEvent(
142         const std::shared_ptr<IStats> &stats_client, const char *line) {
143     int health_status_stats_fields[] = {
144             BatteryHealthUsage::kTemperatureLimitDeciCFieldNumber,
145             BatteryHealthUsage::kSocLimitFieldNumber,
146             BatteryHealthUsage::kChargeTimeSecsFieldNumber,
147             BatteryHealthUsage::kDischargeTimeSecsFieldNumber,
148     };
149 
150     const int32_t vtier_fields_size = std::size(health_status_stats_fields);
151     static_assert(vtier_fields_size == 4, "Unexpected battery health status fields size");
152     std::vector<VendorAtomValue> values(vtier_fields_size);
153     VendorAtomValue val;
154     int32_t i = 0, tmp[vtier_fields_size] = {0};
155 
156     // temp/soc charge(s) discharge(s)
157     if (sscanf(line, "%d/%d\t%d\t%d", &tmp[0], &tmp[1], &tmp[2], &tmp[3]) != vtier_fields_size) {
158         /* If format isn't as expected, then ignore line on purpose */
159         return;
160     }
161 
162     ALOGD("BatteryHealthUsage: processed %s", line);
163     for (i = 0; i < vtier_fields_size; i++) {
164         val.set<VendorAtomValue::intValue>(tmp[i]);
165         values[health_status_stats_fields[i] - kVendorAtomOffset] = val;
166     }
167 
168     VendorAtom event = {.reverseDomainName = "",
169                         .atomId = PixelAtoms::Atom::kBatteryHealthUsage,
170                         .values = std::move(values)};
171     const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
172     if (!ret.isOk())
173         ALOGE("Unable to report BatteryHealthStatus to Stats service");
174 }
175 
checkAndReportStatus(const std::shared_ptr<IStats> & stats_client)176 void BatteryHealthReporter::checkAndReportStatus(const std::shared_ptr<IStats> &stats_client) {
177     int64_t now = getTimeSecs();
178     if ((report_time_ != 0) && (now - report_time_ < SECONDS_PER_MONTH)) {
179         ALOGD("Do not upload yet. now: %" PRId64 ", pre: %" PRId64, now, report_time_);
180         return;
181     }
182 
183     bool successStatus = reportBatteryHealthStatus(stats_client);
184     bool successUsage = reportBatteryHealthUsage(stats_client);
185 
186     if (successStatus && successUsage) {
187         report_time_ = now;
188     }
189 }
190 
191 }  // namespace pixel
192 }  // namespace google
193 }  // namespace hardware
194 }  // namespace android
195