/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "pixelstats-wlc" #include #include #include #include #include #include #include #include #include #include /* I set a higher rare limit ti orientation, beacuae user might try to adjust * orientation when start charge **/ #define GOOGLE_PTMC_ID (0x72) #define ID_UNKNOWN (0) #define WLC_VENDOR_REPORT_RATE_LIMIT_MIN_SEC (60 * 60) #define WLC_VENDOR_REPORT_RATE_LIMIT_MAX_COUNT_PER_DAY (10) #define ORIENTATION_REPORT_RATE_LIMIT_MIN_SEC (0) #define ORIENTATION_REPORT_RATE_LIMIT_MAX_COUNT_PER_DAY (10) #define DAY_SECOND (86400) namespace android { namespace hardware { namespace google { namespace pixel { using aidl::android::frameworks::stats::IStats; using aidl::android::frameworks::stats::VendorAtom; using aidl::android::frameworks::stats::VendorAtomValue; using android::base::ReadFileToString; WlcReporter::WlcStatus::WlcStatus() : is_charging(false), check_charger_vendor_id(false), check_charger_vendor_id_scheduled(false), check_vendor_id_attempts(0) {} WlcReporter::ReportRecord::ReportRecord(char const *name_) : name(name_), last_reported_time_in_sec_today(0), last_reported_time_in_sec(0), count_today(0) {} WlcReporter::WlcReporter(const char *ptmc_path) : kWirelessChargerPtmcPath(ptmc_path) {} void WlcReporter::checkAndReport(const std::shared_ptr &stats_client, bool online, const char *ptmc_uevent) { bool wireless_charging = online; bool started_wireless_charging = wireless_charging && !wlc_status_.is_charging; wlc_status_.is_charging = wireless_charging; ALOGV("reportVendorId is_charging: %s, started_wireless_charging: %s", (wireless_charging) ? "true" : "false", (started_wireless_charging) ? "true" : "false"); if (started_wireless_charging) { reportOrientation(stats_client); wlc_status_.check_vendor_id_attempts = 0; if (checkRateLimit(WLC_VENDOR_REPORT_RATE_LIMIT_MIN_SEC, WLC_VENDOR_REPORT_RATE_LIMIT_MAX_COUNT_PER_DAY, &rec_wlc_vendor_)) { wlc_status_.check_charger_vendor_id = true; if (kWirelessChargerPtmcPath != nullptr && strlen(kWirelessChargerPtmcPath) != 0) { scheduleReportVendorId(stats_client); } else { ALOGV("ptmc_path not set"); } } } if (!wireless_charging) { wlc_status_.check_charger_vendor_id = false; } if (wireless_charging) { checkVendorId(stats_client, ptmc_uevent); } } void WlcReporter::checkVendorId(const std::shared_ptr &stats_client, const char *ptmc_uevent) { if (!ptmc_uevent || !wlc_status_.check_charger_vendor_id) { return; } if (reportVendorMayRetry(stats_client, ptmc_uevent)) { wlc_status_.check_charger_vendor_id = false; } } bool WlcReporter::reportVendorMayRetry(const std::shared_ptr &stats_client, const char *ptmc_uevent) { int ptmcId = readPtmcId(ptmc_uevent); if (ptmcId == ID_UNKNOWN) { if (++(wlc_status_.check_vendor_id_attempts) < kMaxVendorIdAttempts) { return false; } /* else if ptmc not ready after retry assume ptmc not supported by charger */ } ALOGV("reportVendorId from Uevent"); reportVendor(stats_client, ptmcId); return true; } void WlcReporter::reportVendor(const std::shared_ptr &stats_client, const int ptmcId) { int vendorCharger = (ptmcId == GOOGLE_PTMC_ID) ? PixelAtoms::WirelessChargingStats::VENDOR_GOOGLE : PixelAtoms::WirelessChargingStats::VENDOR_UNKNOWN; ALOGV("ptmcId: 0x%x; vendorCharger: %d", ptmcId, vendorCharger); VendorAtomValue tmp; tmp.set(vendorCharger); std::vector values(1); values[PixelAtoms::WirelessChargingStats::kChargerVendorFieldNumber - kVendorAtomOffset] = tmp; // Send vendor atom to IStats HAL VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(), .atomId = PixelAtoms::Atom::kWirelessChargingStats, .values = std::move(values)}; const ndk::ScopedAStatus retStat = stats_client->reportVendorAtom(event); if (!retStat.isOk()) { ALOGE("Unable to report WLC_STATS to Stats service"); } return; } int WlcReporter::readPtmcId(const char *ptmc_str) { int id; if (sscanf(ptmc_str, "%x", &id) != 1) { return ID_UNKNOWN; } return id; } void WlcReporter::scheduleReportVendorId(const std::shared_ptr &stats_client) { if (wlc_status_.check_charger_vendor_id_scheduled) { return; } wlc_status_.check_charger_vendor_id_scheduled = true; std::thread taskThread(&WlcReporter::reportInBackground, this, stats_client, kWirelessChargerPtmcPath); taskThread.detach(); } bool WlcReporter::ptmcWaitTimer(int timerfd) { const int kDelaytimeBeforeReadPtmcId = 60; struct itimerspec period; period.it_interval.tv_sec = 0; period.it_interval.tv_nsec = 0; period.it_value.tv_sec = kDelaytimeBeforeReadPtmcId; period.it_value.tv_nsec = 0; if (timerfd_settime(timerfd, 0, &period, nullptr)) { ALOGE("Unable to set timer - %s", strerror(errno)); return false; } int readval; do { char buf[8]; errno = 0; readval = read(timerfd, buf, sizeof(buf)); } while (readval < 0 && errno == EINTR); if (readval < 0) { ALOGE("Timerfd error - %s", strerror(errno)); return false; } return true; } /* * PTMC path use to sore wireless charger vendor id, * and it take some time to get ready after wireless chagre start, * to prevnt busy wait, I use timer and background thread to check PTMC ID **/ void WlcReporter::reportInBackground(const std::shared_ptr &stats_client, const char *ptmc_path) { int timerfd = timerfd_create(CLOCK_BOOTTIME, 0); if (timerfd < 0) { ALOGE("Unable to create timerfd - %s", strerror(errno)); return; } if (ptmcWaitTimer(timerfd)) { if (!wlc_status_.is_charging) { ALOGV("Not charging, skip report vender id"); } else if (!wlc_status_.check_charger_vendor_id) { ALOGV("id reported by uevnt, skip report vender id"); } else { std::string file_contents; if (!ReadFileToString(ptmc_path, &file_contents)) { ALOGE("ReadFileToString %s fail", ptmc_path); } else { int ptmcId = readPtmcId(file_contents.c_str()); ALOGV("reportVendorId from file"); reportVendor(stats_client, ptmcId); } } } wlc_status_.check_charger_vendor_id_scheduled = false; close(timerfd); } /* Reference to frameworks/native/libs/ui/include/ui/DisplayInfo.h * translate orientation value from sensor to enum define in * pixelatoms.proto */ int WlcReporter::translateDeviceOrientationToAtomValue(int orientation) { switch (orientation) { case 0: return PixelAtoms::DeviceOrientation::ORIENTATION_0; case 1: return PixelAtoms::DeviceOrientation::ORIENTATION_90; case 2: return PixelAtoms::DeviceOrientation::ORIENTATION_180; case 3: return PixelAtoms::DeviceOrientation::ORIENTATION_270; default: return PixelAtoms::DeviceOrientation::ORIENTATION_UNKNOWN; } } void WlcReporter::reportOrientation(const std::shared_ptr &stats_client) { ALOGV("reportOrientation"); if (!checkRateLimit(ORIENTATION_REPORT_RATE_LIMIT_MIN_SEC, ORIENTATION_REPORT_RATE_LIMIT_MAX_COUNT_PER_DAY, &rec_orientation_)) { return; } sp orientationCollector = OrientationCollector::createOrientationCollector(); if (orientationCollector != nullptr) { int orientationFromSensor = -1; orientationCollector->pollOrientation(&orientationFromSensor); VendorAtomValue tmp; tmp.set( translateDeviceOrientationToAtomValue(orientationFromSensor)); std::vector values(1); values[PixelAtoms::DeviceOrientation::kOrientationFieldNumber - kVendorAtomOffset] = tmp; VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(), .atomId = PixelAtoms::Atom::kDeviceOrientation, .values = std::move(values)}; const ndk::ScopedAStatus retStat = stats_client->reportVendorAtom(event); if (!retStat.isOk()) { ALOGE("Unable to report Orientation to Stats service"); } orientationCollector->disableOrientationSensor(); } } bool WlcReporter::checkRateLimit(int64_t minSecond, int maxCount, ReportRecord *rec) { if (rec == nullptr) { ALOGE("ReportRecord should not be NULL"); return false; } int64_t now = nanoseconds_to_seconds(systemTime(SYSTEM_TIME_BOOTTIME)); if (rec->last_reported_time_in_sec > 0 && now - rec->last_reported_time_in_sec < minSecond) { ALOGV("%s: Rate limit, min period: %ld", rec->name, minSecond); return false; } if (rec->last_reported_time_in_sec_today == 0) { rec->last_reported_time_in_sec_today = now; ALOGV("%s: reset day time (init)", rec->name); } else if (now - rec->last_reported_time_in_sec_today > DAY_SECOND) { rec->last_reported_time_in_sec_today = now; rec->count_today = 0; ALOGV("%s: reset day time", rec->name); } else if (rec->count_today >= maxCount) { ALOGV("%s: Rate limit, max count: %d", rec->name, maxCount); return false; } (rec->count_today)++; ALOGV("%s: checkRateLimit count: %d", rec->name, rec->count_today); rec->last_reported_time_in_sec = now; return true; } } // namespace pixel } // namespace google } // namespace hardware } // namespace android