/* * 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 "UeventListener.h" #include #include #include #include #include #include #include #include #include namespace device { namespace google { namespace crosshatch { 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 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 halves = android::base::Split(product, "="); if (halves.size() != 2) { return; } std::vector 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 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 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=/kernel/q6audio/q6voice_uevent") && !strcmp(mic_break_status, "MIC_BREAK_STATUS=true")) { using ::hardware::google::pixelstats::V1_0::IPixelStats; android::sp 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() : uevent_fd_(-1), is_usb_attached_(false), attached_product_(nullptr) {} /* Thread function for ListenForeverInNewThread. * Exit after kMaxConsecutiveErrors to prevent spinning. */ static void createAndListenForever() { UeventListener listener; constexpr int kMaxConsecutiveErrors = 10; int consecutive_errors = 0; while (1) { if (listener.ProcessUevent()) { consecutive_errors = 0; } else { if (++consecutive_errors >= kMaxConsecutiveErrors) { ALOGE("Too many ProcessUevent errors; exiting UeventListener."); return; } } } } /* Create a UeventListener in a new thread and process uevents forever. */ void UeventListener::ListenForeverInNewThread() { std::thread listenThread(createAndListenForever); listenThread.detach(); } } // namespace crosshatch } // namespace google } // namespace device