blob: fc72aae0cc47eeb294855044fb86d2af33798def [file] [log] [blame]
/*
* 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 <android-base/file.h>
#include <android-base/strings.h>
#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
#include <log/log.h>
#include <pixelstats/OrientationCollector.h>
#include <pixelstats/WlcReporter.h>
#include <sys/timerfd.h>
#include <time.h>
#include <utils/Timers.h>
#include <thread>
/* 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<IStats> &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<IStats> &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<IStats> &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<IStats> &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<VendorAtomValue::intValue>(vendorCharger);
std::vector<VendorAtomValue> 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<IStats> &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<IStats> &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<IStats> &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 =
OrientationCollector::createOrientationCollector();
if (orientationCollector != nullptr) {
int orientationFromSensor = -1;
orientationCollector->pollOrientation(&orientationFromSensor);
VendorAtomValue tmp;
tmp.set<VendorAtomValue::intValue>(
translateDeviceOrientationToAtomValue(orientationFromSensor));
std::vector<VendorAtomValue> 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