blob: 02477b1aede13585487f5375a1839aeaa10c97c6 [file] [log] [blame]
/*
* Copyright (C) 2017 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-uevent"
#include <pixelstats/UeventListener.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <cutils/uevent.h>
#include <hardware/google/pixelstats/1.0/IPixelStats.h>
#include <log/log.h>
#include <utils/StrongPointer.h>
#include <unistd.h>
#include <thread>
namespace android {
namespace hardware {
namespace google {
namespace pixel {
constexpr int32_t UEVENT_MSG_LEN = 2048; // it's 2048 in all other users.
// Report connection & disconnection of devices into the USB-C connector.
void UeventListener::ReportUsbConnectorUevents(const char *power_supply_typec_mode) {
using ::hardware::google::pixelstats::V1_0::IPixelStats;
if (!power_supply_typec_mode) {
// No mode reported -> No reporting.
return;
}
// It's attached if the string *doesn't* match.
int attached = !!strcmp(power_supply_typec_mode, "POWER_SUPPLY_TYPEC_MODE=Nothing attached");
if (attached == is_usb_attached_) {
return;
}
is_usb_attached_ = attached;
android::sp<IPixelStats> client = IPixelStats::tryGetService();
if (!client) {
ALOGE("Unable to connect to PixelStats service");
return;
}
if (attached) {
client->reportUsbConnectorConnected();
usb_connect_time_ = android::base::Timer();
} else {
client->reportUsbConnectorDisconnected(usb_connect_time_.duration().count());
}
}
// Report connection & disconnection of USB audio devices.
void UeventListener::ReportUsbAudioUevents(const char *driver, const char *product,
const char *action) {
// driver is not provided on remove, so it can be NULL. Check in remove branch.
if (!product || !action) {
return;
}
// The PRODUCT of a USB audio device is PRODUCT=VID/PID/VERSION
std::vector<std::string> halves = android::base::Split(product, "=");
if (halves.size() != 2) {
return;
}
std::vector<std::string> vidPidVer = android::base::Split(halves[1], "/");
if (vidPidVer.size() != 3) {
return;
}
// Parse the VID/PID as hex values.
const int kBaseHex = 16;
int32_t vid, pid;
char *vidEnd = NULL, *pidEnd = NULL;
const char *vidC = vidPidVer[0].c_str();
vid = strtol(vidC, &vidEnd, kBaseHex);
if ((vidC == vidEnd) || !vidEnd || (*vidEnd != '\0')) {
return;
}
const char *pidC = vidPidVer[1].c_str();
pid = strtol(pidC, &pidEnd, kBaseHex);
if ((pidC == pidEnd) || !pidEnd || (*pidEnd != '\0')) {
return;
}
/* A uevent is generated for each audio interface - only report the first connected one
* for each device by storing its PRODUCT= string in attached_product_, and clearing
* it on disconnect. This also means we will only report the first USB audio device attached
* to the system. Only attempt to connect to the HAL when reporting an event.
*/
using ::hardware::google::pixelstats::V1_0::IPixelStats;
if (!attached_product_ && !strcmp(action, "ACTION=add")) {
if (!driver || strcmp(driver, "DRIVER=snd-usb-audio")) {
return;
}
usb_audio_connect_time_ = android::base::Timer();
attached_product_ = strdup(product);
android::sp<IPixelStats> client = IPixelStats::tryGetService();
if (!client) {
ALOGE("Couldn't connect to PixelStats service for audio connect");
return;
}
client->reportUsbAudioConnected(vid, pid);
} else if (attached_product_ && !strcmp(action, "ACTION=remove")) {
if (strcmp(attached_product_, product)) {
// Not the expected product detaching.
return;
}
free(attached_product_);
attached_product_ = NULL;
android::sp<IPixelStats> client = IPixelStats::tryGetService();
if (!client) {
ALOGE("Couldn't connect to PixelStats service for audio disconnect");
return;
}
client->reportUsbAudioDisconnected(vid, pid, usb_audio_connect_time_.duration().count());
}
}
void UeventListener::ReportMicBroken(const char *devpath, const char *mic_break_status) {
if (!devpath || !mic_break_status)
return;
if (!strcmp(devpath, ("DEVPATH=" + kAudioUevent).c_str()) &&
!strcmp(mic_break_status, "MIC_BREAK_STATUS=true")) {
using ::hardware::google::pixelstats::V1_0::IPixelStats;
android::sp<IPixelStats> client = IPixelStats::tryGetService();
if (!client) {
ALOGE("Couldn't connect to PixelStats service for mic break");
return;
}
client->reportHardwareFailed(IPixelStats::HardwareType::MICROPHONE, 0,
IPixelStats::HardwareErrorCode::COMPLETE);
}
}
bool UeventListener::ProcessUevent() {
char msg[UEVENT_MSG_LEN + 2];
char *cp;
const char *action, *power_supply_typec_mode, *driver, *product;
const char *mic_break_status;
const char *devpath;
int n;
if (uevent_fd_ < 0) {
uevent_fd_ = uevent_open_socket(64 * 1024, true);
if (uevent_fd_ < 0) {
ALOGE("uevent_init: uevent_open_socket failed\n");
return false;
}
}
n = uevent_kernel_multicast_recv(uevent_fd_, msg, UEVENT_MSG_LEN);
if (n <= 0 || n >= UEVENT_MSG_LEN)
return false;
// Ensure double-null termination of msg.
msg[n] = '\0';
msg[n + 1] = '\0';
action = power_supply_typec_mode = driver = product = NULL;
mic_break_status = devpath = NULL;
/**
* msg is a sequence of null-terminated strings.
* Iterate through and record positions of string/value pairs of interest.
* Double null indicates end of the message. (enforced above).
*/
cp = msg;
while (*cp) {
if (!strncmp(cp, "ACTION=", strlen("ACTION="))) {
action = cp;
} else if (!strncmp(cp, "POWER_SUPPLY_TYPEC_MODE=", strlen("POWER_SUPPLY_TYPEC_MODE="))) {
power_supply_typec_mode = cp;
} else if (!strncmp(cp, "DRIVER=", strlen("DRIVER="))) {
driver = cp;
} else if (!strncmp(cp, "PRODUCT=", strlen("PRODUCT="))) {
product = cp;
} else if (!strncmp(cp, "MIC_BREAK_STATUS=", strlen("MIC_BREAK_STATUS="))) {
mic_break_status = cp;
} else if (!strncmp(cp, "DEVPATH=", strlen("DEVPATH="))) {
devpath = cp;
}
/* advance to after the next \0 */
while (*cp++) {
}
}
/* Process the strings recorded. */
ReportUsbConnectorUevents(power_supply_typec_mode);
ReportUsbAudioUevents(driver, product, action);
ReportMicBroken(devpath, mic_break_status);
return true;
}
UeventListener::UeventListener(const std::string audio_uevent)
: kAudioUevent(audio_uevent),
uevent_fd_(-1),
is_usb_attached_(false),
attached_product_(nullptr) {}
/* Thread function to continuously monitor uevents.
* Exit after kMaxConsecutiveErrors to prevent spinning. */
void UeventListener::ListenForever() {
constexpr int kMaxConsecutiveErrors = 10;
int consecutive_errors = 0;
while (1) {
if (ProcessUevent()) {
consecutive_errors = 0;
} else {
if (++consecutive_errors >= kMaxConsecutiveErrors) {
ALOGE("Too many ProcessUevent errors; exiting UeventListener.");
return;
}
}
}
}
} // namespace pixel
} // namespace google
} // namespace hardware
} // namespace android