1 /*
2  * Copyright (C) 2020 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-wlc"
18 
19 #include <android-base/file.h>
20 #include <android-base/strings.h>
21 #include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
22 #include <log/log.h>
23 #include <pixelstats/OrientationCollector.h>
24 #include <pixelstats/WlcReporter.h>
25 #include <sys/timerfd.h>
26 #include <time.h>
27 #include <utils/Timers.h>
28 
29 #include <thread>
30 
31 /* I set a higher rare limit ti orientation, beacuae user might try to adjust
32  * orientation when start charge
33  **/
34 #define GOOGLE_PTMC_ID (0x72)
35 #define ID_UNKNOWN (0)
36 #define WLC_VENDOR_REPORT_RATE_LIMIT_MIN_SEC (60 * 60)
37 #define WLC_VENDOR_REPORT_RATE_LIMIT_MAX_COUNT_PER_DAY (10)
38 #define ORIENTATION_REPORT_RATE_LIMIT_MIN_SEC (0)
39 #define ORIENTATION_REPORT_RATE_LIMIT_MAX_COUNT_PER_DAY (10)
40 #define DAY_SECOND (86400)
41 
42 namespace android {
43 namespace hardware {
44 namespace google {
45 namespace pixel {
46 
47 using aidl::android::frameworks::stats::IStats;
48 using aidl::android::frameworks::stats::VendorAtom;
49 using aidl::android::frameworks::stats::VendorAtomValue;
50 using android::base::ReadFileToString;
51 
WlcStatus()52 WlcReporter::WlcStatus::WlcStatus()
53     : is_charging(false),
54       check_charger_vendor_id(false),
55       check_charger_vendor_id_scheduled(false),
56       check_vendor_id_attempts(0) {}
57 
ReportRecord(char const * name_)58 WlcReporter::ReportRecord::ReportRecord(char const *name_)
59     : name(name_),
60       last_reported_time_in_sec_today(0),
61       last_reported_time_in_sec(0),
62       count_today(0) {}
63 
WlcReporter(const char * ptmc_path)64 WlcReporter::WlcReporter(const char *ptmc_path) : kWirelessChargerPtmcPath(ptmc_path) {}
checkAndReport(const std::shared_ptr<IStats> & stats_client,bool online,const char * ptmc_uevent)65 void WlcReporter::checkAndReport(const std::shared_ptr<IStats> &stats_client, bool online,
66                                  const char *ptmc_uevent) {
67     bool wireless_charging = online;
68     bool started_wireless_charging = wireless_charging && !wlc_status_.is_charging;
69     wlc_status_.is_charging = wireless_charging;
70     ALOGV("reportVendorId is_charging: %s, started_wireless_charging: %s",
71           (wireless_charging) ? "true" : "false", (started_wireless_charging) ? "true" : "false");
72 
73     if (started_wireless_charging) {
74         reportOrientation(stats_client);
75         wlc_status_.check_vendor_id_attempts = 0;
76         if (checkRateLimit(WLC_VENDOR_REPORT_RATE_LIMIT_MIN_SEC,
77                            WLC_VENDOR_REPORT_RATE_LIMIT_MAX_COUNT_PER_DAY, &rec_wlc_vendor_)) {
78             wlc_status_.check_charger_vendor_id = true;
79             if (kWirelessChargerPtmcPath != nullptr && strlen(kWirelessChargerPtmcPath) != 0) {
80                 scheduleReportVendorId(stats_client);
81             } else {
82                 ALOGV("ptmc_path not set");
83             }
84         }
85     }
86     if (!wireless_charging) {
87         wlc_status_.check_charger_vendor_id = false;
88     }
89     if (wireless_charging) {
90         checkVendorId(stats_client, ptmc_uevent);
91     }
92 }
93 
checkVendorId(const std::shared_ptr<IStats> & stats_client,const char * ptmc_uevent)94 void WlcReporter::checkVendorId(const std::shared_ptr<IStats> &stats_client,
95                                 const char *ptmc_uevent) {
96     if (!ptmc_uevent || !wlc_status_.check_charger_vendor_id) {
97         return;
98     }
99     if (reportVendorMayRetry(stats_client, ptmc_uevent)) {
100         wlc_status_.check_charger_vendor_id = false;
101     }
102 }
103 
reportVendorMayRetry(const std::shared_ptr<IStats> & stats_client,const char * ptmc_uevent)104 bool WlcReporter::reportVendorMayRetry(const std::shared_ptr<IStats> &stats_client,
105                                        const char *ptmc_uevent) {
106     int ptmcId = readPtmcId(ptmc_uevent);
107     if (ptmcId == ID_UNKNOWN) {
108         if (++(wlc_status_.check_vendor_id_attempts) < kMaxVendorIdAttempts) {
109             return false;
110         } /* else if ptmc not ready after retry assume ptmc not supported by charger */
111     }
112     ALOGV("reportVendorId from Uevent");
113     reportVendor(stats_client, ptmcId);
114     return true;
115 }
reportVendor(const std::shared_ptr<IStats> & stats_client,const int ptmcId)116 void WlcReporter::reportVendor(const std::shared_ptr<IStats> &stats_client, const int ptmcId) {
117     int vendorCharger = (ptmcId == GOOGLE_PTMC_ID)
118                                 ? PixelAtoms::WirelessChargingStats::VENDOR_GOOGLE
119                                 : PixelAtoms::WirelessChargingStats::VENDOR_UNKNOWN;
120     ALOGV("ptmcId: 0x%x; vendorCharger: %d", ptmcId, vendorCharger);
121     VendorAtomValue tmp;
122     tmp.set<VendorAtomValue::intValue>(vendorCharger);
123 
124     std::vector<VendorAtomValue> values(1);
125     values[PixelAtoms::WirelessChargingStats::kChargerVendorFieldNumber - kVendorAtomOffset] = tmp;
126 
127     // Send vendor atom to IStats HAL
128     VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
129                         .atomId = PixelAtoms::Atom::kWirelessChargingStats,
130                         .values = std::move(values)};
131     const ndk::ScopedAStatus retStat = stats_client->reportVendorAtom(event);
132     if (!retStat.isOk()) {
133         ALOGE("Unable to report WLC_STATS to Stats service");
134     }
135     return;
136 }
137 
readPtmcId(const char * ptmc_str)138 int WlcReporter::readPtmcId(const char *ptmc_str) {
139     int id;
140     if (sscanf(ptmc_str, "%x", &id) != 1) {
141         return ID_UNKNOWN;
142     }
143     return id;
144 }
145 
scheduleReportVendorId(const std::shared_ptr<IStats> & stats_client)146 void WlcReporter::scheduleReportVendorId(const std::shared_ptr<IStats> &stats_client) {
147     if (wlc_status_.check_charger_vendor_id_scheduled) {
148         return;
149     }
150 
151     wlc_status_.check_charger_vendor_id_scheduled = true;
152     std::thread taskThread(&WlcReporter::reportInBackground, this, stats_client,
153                            kWirelessChargerPtmcPath);
154     taskThread.detach();
155 }
156 
ptmcWaitTimer(int timerfd)157 bool WlcReporter::ptmcWaitTimer(int timerfd) {
158     const int kDelaytimeBeforeReadPtmcId = 60;
159     struct itimerspec period;
160     period.it_interval.tv_sec = 0;
161     period.it_interval.tv_nsec = 0;
162     period.it_value.tv_sec = kDelaytimeBeforeReadPtmcId;
163     period.it_value.tv_nsec = 0;
164 
165     if (timerfd_settime(timerfd, 0, &period, nullptr)) {
166         ALOGE("Unable to set timer - %s", strerror(errno));
167         return false;
168     }
169     int readval;
170     do {
171         char buf[8];
172         errno = 0;
173         readval = read(timerfd, buf, sizeof(buf));
174     } while (readval < 0 && errno == EINTR);
175     if (readval < 0) {
176         ALOGE("Timerfd error - %s", strerror(errno));
177         return false;
178     }
179     return true;
180 }
181 
182 /*
183  * PTMC path use to sore wireless charger vendor id,
184  * and it take some time to get ready after wireless chagre start,
185  * to prevnt busy wait, I use timer and background thread to check PTMC ID
186  **/
reportInBackground(const std::shared_ptr<IStats> & stats_client,const char * ptmc_path)187 void WlcReporter::reportInBackground(const std::shared_ptr<IStats> &stats_client,
188                                      const char *ptmc_path) {
189     int timerfd = timerfd_create(CLOCK_BOOTTIME, 0);
190     if (timerfd < 0) {
191         ALOGE("Unable to create timerfd - %s", strerror(errno));
192         return;
193     }
194     if (ptmcWaitTimer(timerfd)) {
195         if (!wlc_status_.is_charging) {
196             ALOGV("Not charging, skip report vender id");
197         } else if (!wlc_status_.check_charger_vendor_id) {
198             ALOGV("id reported by uevnt, skip report vender id");
199         } else {
200             std::string file_contents;
201             if (!ReadFileToString(ptmc_path, &file_contents)) {
202                 ALOGE("ReadFileToString %s fail", ptmc_path);
203             } else {
204                 int ptmcId = readPtmcId(file_contents.c_str());
205                 ALOGV("reportVendorId from file");
206                 reportVendor(stats_client, ptmcId);
207             }
208         }
209     }
210     wlc_status_.check_charger_vendor_id_scheduled = false;
211     close(timerfd);
212 }
213 
214 /*  Reference to frameworks/native/libs/ui/include/ui/DisplayInfo.h
215  *  translate orientation value from sensor to enum define in
216  *  pixelatoms.proto
217  */
translateDeviceOrientationToAtomValue(int orientation)218 int WlcReporter::translateDeviceOrientationToAtomValue(int orientation) {
219     switch (orientation) {
220         case 0:
221             return PixelAtoms::DeviceOrientation::ORIENTATION_0;
222         case 1:
223             return PixelAtoms::DeviceOrientation::ORIENTATION_90;
224         case 2:
225             return PixelAtoms::DeviceOrientation::ORIENTATION_180;
226         case 3:
227             return PixelAtoms::DeviceOrientation::ORIENTATION_270;
228         default:
229             return PixelAtoms::DeviceOrientation::ORIENTATION_UNKNOWN;
230     }
231 }
232 
reportOrientation(const std::shared_ptr<IStats> & stats_client)233 void WlcReporter::reportOrientation(const std::shared_ptr<IStats> &stats_client) {
234     ALOGV("reportOrientation");
235     if (!checkRateLimit(ORIENTATION_REPORT_RATE_LIMIT_MIN_SEC,
236                         ORIENTATION_REPORT_RATE_LIMIT_MAX_COUNT_PER_DAY, &rec_orientation_)) {
237         return;
238     }
239     sp<OrientationCollector> orientationCollector =
240             OrientationCollector::createOrientationCollector();
241     if (orientationCollector != nullptr) {
242         int orientationFromSensor = -1;
243         orientationCollector->pollOrientation(&orientationFromSensor);
244         VendorAtomValue tmp;
245         tmp.set<VendorAtomValue::intValue>(
246                 translateDeviceOrientationToAtomValue(orientationFromSensor));
247 
248         std::vector<VendorAtomValue> values(1);
249         values[PixelAtoms::DeviceOrientation::kOrientationFieldNumber - kVendorAtomOffset] = tmp;
250 
251         VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
252                             .atomId = PixelAtoms::Atom::kDeviceOrientation,
253                             .values = std::move(values)};
254         const ndk::ScopedAStatus retStat = stats_client->reportVendorAtom(event);
255         if (!retStat.isOk()) {
256             ALOGE("Unable to report Orientation to Stats service");
257         }
258         orientationCollector->disableOrientationSensor();
259     }
260 }
261 
checkRateLimit(int64_t minSecond,int maxCount,ReportRecord * rec)262 bool WlcReporter::checkRateLimit(int64_t minSecond, int maxCount, ReportRecord *rec) {
263     if (rec == nullptr) {
264         ALOGE("ReportRecord should not be NULL");
265         return false;
266     }
267     int64_t now = nanoseconds_to_seconds(systemTime(SYSTEM_TIME_BOOTTIME));
268     if (rec->last_reported_time_in_sec > 0 && now - rec->last_reported_time_in_sec < minSecond) {
269         ALOGV("%s: Rate limit, min period: %ld", rec->name, minSecond);
270         return false;
271     }
272     if (rec->last_reported_time_in_sec_today == 0) {
273         rec->last_reported_time_in_sec_today = now;
274         ALOGV("%s: reset day time (init)", rec->name);
275     } else if (now - rec->last_reported_time_in_sec_today > DAY_SECOND) {
276         rec->last_reported_time_in_sec_today = now;
277         rec->count_today = 0;
278         ALOGV("%s: reset day time", rec->name);
279     } else if (rec->count_today >= maxCount) {
280         ALOGV("%s: Rate limit, max count: %d", rec->name, maxCount);
281         return false;
282     }
283     (rec->count_today)++;
284     ALOGV("%s: checkRateLimit count: %d", rec->name, rec->count_today);
285     rec->last_reported_time_in_sec = now;
286     return true;
287 }
288 }  // namespace pixel
289 }  // namespace google
290 }  // namespace hardware
291 }  // namespace android
292