Merge Android 14 QPR2 to AOSP main

Bug: 319669529
Merged-In: I8f1863cbde90b5bba929629615d0b11d11961650
Change-Id: I6cb962702e9f1473287e1da429272399b3ca93bf
diff --git a/atrace/OWNERS b/atrace/OWNERS
index 05e234a..a7801e6 100644
--- a/atrace/OWNERS
+++ b/atrace/OWNERS
@@ -1,2 +1,4 @@
 wvw@google.com
-namhyung@google.com
+jenhaochen@google.com
+paillon@google.com
+liumartin@google.com
diff --git a/battery_mitigation/Android.bp b/battery_mitigation/Android.bp
index 1a5d6c6..6b5e122 100644
--- a/battery_mitigation/Android.bp
+++ b/battery_mitigation/Android.bp
@@ -24,6 +24,7 @@
     vendor_available: true,
     srcs: [
         "BatteryMitigation.cpp",
+        "BatteryMitigationService.cpp",
         "MitigationThermalManager.cpp",
     ],
     static_libs: [
diff --git a/battery_mitigation/BatteryMitigation.cpp b/battery_mitigation/BatteryMitigation.cpp
index 6aadaa4..10c944c 100644
--- a/battery_mitigation/BatteryMitigation.cpp
+++ b/battery_mitigation/BatteryMitigation.cpp
@@ -18,7 +18,6 @@
 
 #include <sstream>
 
-#define MAX_BROWNOUT_DATA_AGE_MINUTES 5
 #define ONE_SECOND_IN_US 1000000
 
 namespace android {
@@ -26,9 +25,11 @@
 namespace google {
 namespace pixel {
 
+using android::base::ReadFileToString;
+
 BatteryMitigation::BatteryMitigation(const struct MitigationConfig::Config &cfg) {
-        mThermalMgr = &MitigationThermalManager::getInstance();
-        mThermalMgr->updateConfig(cfg);
+    mThermalMgr = &MitigationThermalManager::getInstance();
+    mThermalMgr->updateConfig(cfg);
 }
 
 bool BatteryMitigation::isMitigationLogTimeValid(std::chrono::system_clock::time_point startTime,
@@ -36,7 +37,7 @@
                                                  const char *const timestampFormat,
                                                  const std::regex pattern) {
     std::string logFile;
-    if (!android::base::ReadFileToString(logFilePath, &logFile)) {
+    if (!ReadFileToString(logFilePath, &logFile)) {
         return false;
     }
     std::istringstream content(logFile);
@@ -66,7 +67,7 @@
             auto delta = epoch_startTime - epoch_logFileTime;
             auto delta_minutes = delta / 60;
 
-            if ((delta_minutes < MAX_BROWNOUT_DATA_AGE_MINUTES) && (delta_minutes >= 0)) {
+            if (delta_minutes >= 0) {
                 return true;
             }
         }
diff --git a/battery_mitigation/BatteryMitigationService.cpp b/battery_mitigation/BatteryMitigationService.cpp
new file mode 100644
index 0000000..3357fdf
--- /dev/null
+++ b/battery_mitigation/BatteryMitigationService.cpp
@@ -0,0 +1,1128 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include <battery_mitigation/BatteryMitigationService.h>
+
+namespace android {
+namespace hardware {
+namespace google {
+namespace pixel {
+
+using android::base::ReadFileToString;
+using android::base::StartsWith;
+using android::hardware::google::pixel::MitigationConfig;
+
+const struct BrownoutStatsCSVFields brownoutStatsCSVFields = {
+        .triggered_time = "triggered_timestamp",
+        .triggered_idx = "triggered_irq",
+        .battery_soc = "battery_soc",
+        .battery_temp = "battery_temp",
+        .battery_cycle = "battery_cycle",
+        .voltage_now = "voltage_now",
+        .current_now = "current_now",
+        .cpu0_freq = "dvfs_channel1",
+        .cpu1_freq = "dvfs_channel2",
+        .cpu2_freq = "dvfs_channel3",
+        .gpu_freq = "dvfs_channel4",
+        .tpu_freq = "dvfs_channel5",
+        .aur_freq = "dvfs_channel6",
+        .odpm_prefix = "odpm_channel_",
+};
+
+BatteryMitigationService::BatteryMitigationService(
+                          const struct MitigationConfig::EventThreadConfig &eventThreadCfg)
+                          :cfg(eventThreadCfg) {
+    initTotalNumericSysfsPaths();
+    initPmicRelated();
+}
+
+BatteryMitigationService::~BatteryMitigationService() {
+    stopEventThread(threadStop, wakeupEventFd, brownoutEventThread);
+    tearDownBrownoutEventThread();
+    stopEventThread(triggerThreadStop, triggeredStateWakeupEventFd, eventThread);
+    tearDownTriggerEventThread();
+}
+
+bool BatteryMitigationService::isBrownoutStatsBinarySupported() {
+    if (access(cfg.TriggeredIdxPath, F_OK) == 0 &&
+        access(cfg.BrownoutStatsPath, F_OK) == 0) {
+        return true;
+    }
+    return false;
+}
+
+bool readSysfsToInt(const std::string &path, int *val) {
+    std::string file_contents;
+
+    if (!ReadFileToString(path, &file_contents)) {
+        return false;
+    } else if (StartsWith(file_contents, "0x")) {
+        if (sscanf(file_contents.c_str(), "0x%x", val) != 1) {
+            return false;
+        }
+    } else if (sscanf(file_contents.c_str(), "%d", val) != 1) {
+        return false;
+    }
+    return true;
+}
+
+bool readSysfsToDouble(const std::string &path, double *val) {
+    std::string file_contents;
+
+    if (!android::base::ReadFileToString(path, &file_contents)) {
+        return false;
+    } else if (sscanf(file_contents.c_str(), "%lf", val) != 1) {
+        return false;
+    }
+    return true;
+}
+
+int getFilesInDir(const char *directory, std::vector<std::string> *files) {
+    std::string content;
+    struct dirent *entry;
+
+    DIR *dir = opendir(directory);
+    if (dir == NULL)
+        return -1;
+
+    files->clear();
+    while ((entry = readdir(dir)) != NULL)
+        files->push_back(entry->d_name);
+    closedir(dir);
+
+    return 0;
+}
+
+void addNumericSysfsStatPathinDir(
+        std::string numericSysfsStatDir,
+        std::vector<MitigationConfig::numericSysfs> totalNumericSysfsStatPaths) {
+    std::vector<std::string> files;
+    if (getFilesInDir(numericSysfsStatDir.c_str(), &files) < 0) {
+        return;
+    }
+    for (auto &file : files) {
+        std::string fullPath = numericSysfsStatDir + file;
+        totalNumericSysfsStatPaths.push_back({file, fullPath});
+    }
+}
+
+void BatteryMitigationService::initTotalNumericSysfsPaths() {
+    totalNumericSysfsStatPaths.assign(cfg.NumericSysfsStatPaths.begin(),
+                                      cfg.NumericSysfsStatPaths.end());
+    for (const auto &sysfsStat : cfg.NumericSysfsStatDirs) {
+        addNumericSysfsStatPathinDir(sysfsStat.path.c_str(), totalNumericSysfsStatPaths);
+    }
+
+    /* Append first available path in PlatformSpecific */
+    for (const auto &sysfsStatList : cfg.PlatformSpecific.NumericSysfsStatPaths) {
+        for (const auto &sysfsStatPath : sysfsStatList.paths) {
+            if (access(sysfsStatPath.c_str(), F_OK) == 0) {
+                totalNumericSysfsStatPaths.push_back({sysfsStatList.name, sysfsStatPath});
+                break;
+            }
+        }
+    }
+    for (const auto &sysfsStatList : cfg.PlatformSpecific.NumericSysfsStatDirs) {
+        for (const auto &sysfsStatPath : sysfsStatList.paths) {
+            if (access(sysfsStatPath.c_str(), F_OK) == 0) {
+                addNumericSysfsStatPathinDir(sysfsStatPath, totalNumericSysfsStatPaths);
+                break;
+            }
+        }
+    }
+}
+
+int BatteryMitigationService::readNumericStats(struct BrownoutStatsExtend *brownoutStatsExtend) {
+    int i = 0;
+
+    if (i >= STATS_MAX_SIZE)
+        return 0;
+
+    for (const auto &sysfsStat : totalNumericSysfsStatPaths) {
+        snprintf(brownoutStatsExtend->numericStats[i].name,
+                 STAT_NAME_SIZE, "%s", sysfsStat.name.c_str());
+        if (!readSysfsToInt(sysfsStat.path,
+            &brownoutStatsExtend->numericStats[i].value)) {
+            continue;
+        }
+        if (++i == STATS_MAX_SIZE) {
+            LOG(DEBUG) << "STATS_MAX_SIZE not enough for NumericStats";
+            break;
+        }
+    }
+
+    return i;
+}
+
+
+void BatteryMitigationService::startBrownoutEventThread() {
+    if (isBrownoutStatsBinarySupported()) {
+        brownoutEventThread = std::thread(&BatteryMitigationService::BrownoutEventThread, this);
+        eventThread = std::thread(&BatteryMitigationService::TriggerEventThread, this);
+    }
+}
+
+void BatteryMitigationService::stopEventThread(std::atomic_bool &thread_stop, int wakeup_event_fd,
+                                               std::thread &event_thread) {
+    if (!thread_stop.load()) {
+        thread_stop.store(true);
+        uint64_t flag = 1;
+        /* wakeup epoll_wait */
+        write(wakeup_event_fd, &flag, sizeof(flag));
+
+        if (event_thread.joinable()) {
+            event_thread.join();
+        }
+    }
+}
+
+void BatteryMitigationService::tearDownTriggerEventThread() {
+    triggerThreadStop.store(true);
+    close(triggeredStateWakeupEventFd);
+    close(triggeredStateEpollFd);
+    LOOP_TRIG_STATS(idx) {
+        close(triggeredStateFd[idx]);
+    }
+}
+
+int getMmapAddr(int &fd, const char *const path, size_t memSize, char **addr) {
+    fd = open(path, O_RDWR | O_CREAT, (mode_t) 0644);
+    if (fd < 0) {
+        return fd;
+    }
+    lseek(fd, memSize - 1, SEEK_SET);
+    write(fd,  "", 1);
+    *addr = (char *)mmap(NULL, memSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+    return 0;
+}
+
+int BatteryMitigationService::initTrigFd() {
+    int ret;
+    struct epoll_event trigEvent[MAX_EVENT];
+    struct epoll_event wakeupEvent;
+
+    LOOP_TRIG_STATS(idx) {
+        triggeredStateFd[idx] = open(cfg.triggeredStatePath[idx], O_RDONLY);
+        if (triggeredStateFd[idx] < 0) {
+            for (int i = idx - 1; i >= 0; i--) {
+                close(triggeredStateFd[i]);
+            }
+            return triggeredStateFd[idx];
+        }
+    }
+
+    triggeredStateEpollFd = epoll_create(MAX_EVENT + 1);
+    if (triggeredStateEpollFd < 0) {
+        LOOP_TRIG_STATS(idx) {
+            close(triggeredStateFd[idx]);
+        }
+        return triggeredStateEpollFd;
+    }
+
+    triggeredStateWakeupEventFd = eventfd(0, 0);
+    if (triggeredStateWakeupEventFd < 0) {
+        close(triggeredStateEpollFd);
+        LOOP_TRIG_STATS(idx) {
+            close(triggeredStateFd[idx]);
+        }
+        return triggeredStateWakeupEventFd;
+    }
+
+    LOOP_TRIG_STATS(i) {
+        trigEvent[i] = epoll_event();
+        trigEvent[i].data.fd = triggeredStateFd[i];
+        trigEvent[i].events = EPOLLET;
+        ret = epoll_ctl(triggeredStateEpollFd, EPOLL_CTL_ADD, triggeredStateFd[i], &trigEvent[i]);
+        if (ret < 0) {
+            close(triggeredStateWakeupEventFd);
+            close(triggeredStateEpollFd);
+            LOOP_TRIG_STATS(idx) {
+                close(triggeredStateFd[idx]);
+            }
+            return ret;
+        }
+    }
+
+    wakeupEvent = epoll_event();
+    wakeupEvent.data.fd = triggeredStateWakeupEventFd;
+    wakeupEvent.events = EPOLLIN | EPOLLWAKEUP;
+    ret = epoll_ctl(triggeredStateEpollFd, EPOLL_CTL_ADD, triggeredStateWakeupEventFd,
+                    &wakeupEvent);
+    if (ret < 0) {
+        close(triggeredStateWakeupEventFd);
+        close(triggeredStateEpollFd);
+        LOOP_TRIG_STATS(idx) {
+            close(triggeredStateFd[idx]);
+        }
+        return ret;
+    }
+
+    return 0;
+}
+
+void BatteryMitigationService::TriggerEventThread() {
+    int requestedFd;
+    char buf[BUF_SIZE];
+    struct epoll_event events[EPOLL_MAXEVENTS];
+    std::smatch match;
+    if (initTrigFd() != 0) {
+        LOG(DEBUG) << "failed to init Trig FD";
+        tearDownTriggerEventThread();
+        return;
+    }
+
+    LOOP_TRIG_STATS(idx) {
+        read(triggeredStateFd[idx], buf, BUF_SIZE);
+    }
+
+    while (!triggerThreadStop.load()) {
+        requestedFd = epoll_wait(triggeredStateEpollFd, events, EPOLL_MAXEVENTS, -1);
+        if (requestedFd <= 0) {
+            /* ensure epoll_wait can sleep in the next loop */
+            LOOP_TRIG_STATS(idx) {
+                read(triggeredStateFd[idx], buf, BUF_SIZE);
+            }
+            continue;
+        }
+        std::string state;
+        for (int i = 0; i < requestedFd; i++) {
+            /* triggeredStateFd[i]: triggeredState event from kernel */
+            /* triggeredStateWakeupEventFd: wakeup epoll_wait to stop thread properly */
+            LOOP_TRIG_STATS(idx) {
+                if (events[i].data.fd == triggeredStateFd[idx]) {
+                    read(triggeredStateFd[idx], buf, BUF_SIZE);
+                    if (ReadFileToString(cfg.triggeredStatePath[idx], &state)) {
+                        size_t pos = state.find("_");
+                        std::string tState = state.substr(0, pos);
+                        std::string tModule = state.substr(pos + 1);
+                        LOG(INFO) << idx << " triggered, current state: " << tState << ". throttle "
+                                  << tModule;
+                        /* b/299700579 launch throttling on targeted module */
+                    }
+                    break;
+                }
+            }
+            /* b/299700579 handle wakeupEvent here if we need to do something after this loop */
+        }
+    }
+}
+
+int BatteryMitigationService::initFd() {
+    int ret;
+    struct epoll_event triggeredIdxEvent, wakeupEvent;
+
+    brownoutStatsFd = open(cfg.BrownoutStatsPath, O_RDONLY);
+    if (brownoutStatsFd < 0) {
+        return brownoutStatsFd;
+    }
+
+    storingFd = open(cfg.StoringPath, O_RDWR | O_CREAT | O_TRUNC, (mode_t) 0644);
+    if (storingFd < 0) {
+        return storingFd;
+    }
+
+    triggeredIdxFd = open(cfg.TriggeredIdxPath, O_RDONLY);
+    if (triggeredIdxFd < 0) {
+        return triggeredIdxFd;
+    }
+
+    triggeredIdxEpollFd = epoll_create(2);
+    if (triggeredIdxEpollFd < 0) {
+        return triggeredIdxEpollFd;
+    }
+
+    wakeupEventFd = eventfd(0, 0);
+    if (wakeupEventFd < 0) {
+        return wakeupEventFd;
+    }
+
+    triggeredIdxEvent = epoll_event();
+    triggeredIdxEvent.data.fd = triggeredIdxFd;
+    triggeredIdxEvent.events = EPOLLERR | EPOLLWAKEUP;
+    ret = epoll_ctl(triggeredIdxEpollFd, EPOLL_CTL_ADD, triggeredIdxFd, &triggeredIdxEvent);
+    if (ret < 0) {
+        return ret;
+    }
+
+    wakeupEvent = epoll_event();
+    wakeupEvent.data.fd = wakeupEventFd;
+    wakeupEvent.events = EPOLLIN | EPOLLWAKEUP;
+    ret = epoll_ctl(triggeredIdxEpollFd, EPOLL_CTL_ADD, wakeupEventFd, &wakeupEvent);
+    if (ret < 0) {
+        return ret;
+    }
+
+    return 0;
+}
+
+void BatteryMitigationService::BrownoutEventThread() {
+    int requestedFd;
+    int triggeredIdx;
+    char buf[BUF_SIZE];
+    struct timeval eventReceivedTime;
+    struct timeval statStoredTime;
+    struct epoll_event events[EPOLL_MAXEVENTS];
+    struct BrownoutStatsExtend brownoutStatsExtend;
+    size_t brownoutStatsSize = sizeof(struct brownout_stats);
+    size_t brownoutStatsExtendSize = sizeof(struct BrownoutStatsExtend);
+    bool stopByEvent = false;
+    /* BrownoutEventThread store multiple brownout event (BROWNOUT_EVENT_BUF_SIZE)
+     * and each event contains several dumps (DUMP_TIMES).
+     */
+    int brownoutEventCounter = 0;
+
+    if (initFd() != 0) {
+        LOG(DEBUG) << "failed to init FD";
+        tearDownBrownoutEventThread();
+        return;
+    }
+
+    /* allow epoll_wait sleep in the first loop */
+    read(triggeredIdxFd, buf, BUF_SIZE);
+
+    while (!threadStop.load()) {
+        requestedFd = epoll_wait(triggeredIdxEpollFd, events, EPOLL_MAXEVENTS, -1);
+        if (requestedFd <= 0) {
+            /* ensure epoll_wait can sleep in the next loop */
+            read(triggeredIdxFd, buf, BUF_SIZE);
+            continue;
+        }
+        /* triggeredIdxFd: brownout event from kernel */
+        /* wakeupEventFd: wakeup epoll_wait to stop thread properly */
+        for (int i = 0; i < requestedFd; i++) {
+            if (events[i].data.fd == triggeredIdxFd) {
+                break;
+            } else {
+                stopByEvent = true;
+            }
+        }
+        if (stopByEvent) {
+            break;
+        }
+
+        /* record brownout event idx and received time */
+        gettimeofday(&eventReceivedTime, NULL);
+        lseek(triggeredIdxFd, 0, SEEK_SET);
+        if (read(triggeredIdxFd, buf, BUF_SIZE) == -1) {
+            continue;
+        }
+        triggeredIdx = atoi(buf);
+        if (triggeredIdx >= TRIGGERED_SOURCE_MAX || triggeredIdx < 0) {
+            continue;
+        }
+
+        /* dump brownout related stats */
+        std::string stats;
+        for (int i = 0; i < DUMP_TIMES; i++) {
+            memset(&brownoutStatsExtend, 0 , brownoutStatsExtendSize);
+
+            /* storing by string due the stats msg too complicate */
+            if (ReadFileToString(cfg.FvpStatsPath, &stats)) {
+                snprintf(brownoutStatsExtend.fvpStats, FVP_STATS_SIZE, "%s", stats.c_str());
+            }
+
+            /* storing numericStats */
+            readNumericStats(&brownoutStatsExtend);
+
+            /* storing brownoutStats */
+            lseek(brownoutStatsFd, 0, SEEK_SET);
+            read(brownoutStatsFd, &brownoutStatsExtend.brownoutStats, brownoutStatsSize);
+            gettimeofday(&statStoredTime, NULL);
+            brownoutStatsExtend.dumpTime = statStoredTime;
+            brownoutStatsExtend.eventReceivedTime = eventReceivedTime;
+            brownoutStatsExtend.eventIdx = triggeredIdx;
+
+            /* write to file */
+            lseek(storingFd,
+                  brownoutStatsExtendSize * (brownoutEventCounter * DUMP_TIMES + i), SEEK_SET);
+            write(storingFd, &brownoutStatsExtend, brownoutStatsExtendSize);
+            fsync(storingFd);
+        }
+
+        if (++brownoutEventCounter == BROWNOUT_EVENT_BUF_SIZE) {
+            brownoutEventCounter = 0;
+        }
+    }
+}
+
+void readLPFPowerBitResolutions(const char *odpmDir, double *bitResolutions) {
+    char path[BUF_SIZE];
+
+    for (int i = 0; i < METER_CHANNEL_MAX; i++) {
+        snprintf(path, BUF_SIZE, "%s/in_power%d_scale", odpmDir, i);
+        if (!readSysfsToDouble(path, &bitResolutions[i])) {
+            bitResolutions[i] = 0;
+        }
+    }
+}
+
+void readLPFChannelNames(const char *odpmEnabledRailsPath, char **lpfChannelNames) {
+    char *line = NULL;
+    size_t len = 0;
+    ssize_t read;
+
+    FILE *fp = fopen(odpmEnabledRailsPath, "r");
+    if (!fp)
+        return;
+
+    int c = 0;
+    while ((read = getline(&line, &len, fp)) != -1 && read != 0) {
+        lpfChannelNames[c] = (char *)malloc(read);
+        if (lpfChannelNames[c] != nullptr) {
+            snprintf(lpfChannelNames[c], read, "%s", line);
+        }
+        if (++c == METER_CHANNEL_MAX)
+            break;
+    }
+    fclose(fp);
+
+    if (line)
+        free(line);
+}
+
+int getMainPmicID(const std::string &mainPmicNamePath, const std::string &subPmicNamePath) {
+    std::string content;
+    int ret = 0;
+    int mainPmicVer, subPmicVer;
+
+    if (!ReadFileToString(mainPmicNamePath, &content)) {
+        LOG(DEBUG) << "Failed to open " << mainPmicNamePath << " set device0 as main pmic";
+        return ret;
+    }
+    /* ODPM pmic name: s2mpgXX-odpm */
+    mainPmicVer = atoi(content.substr(5, 2).c_str());
+
+    if (!ReadFileToString(subPmicNamePath, &content)) {
+        LOG(DEBUG) << "Failed to open " << subPmicNamePath << " set device0 as main pmic";
+        return ret;
+    }
+    subPmicVer = atoi(content.substr(5, 2).c_str());
+
+    if (mainPmicVer > subPmicVer) {
+        ret = 1;
+    }
+
+    return ret;
+}
+
+void freeLpfChannelNames(char **lpfChannelNames) {
+    for (int c = 0; c < METER_CHANNEL_MAX; c++) {
+        free(lpfChannelNames[c]);
+    }
+}
+
+void BatteryMitigationService::tearDownBrownoutEventThread() {
+    close(triggeredIdxFd);
+    close(brownoutStatsFd);
+    close(triggeredIdxEpollFd);
+    close(wakeupEventFd);
+    close(storingFd);
+    threadStop.store(true);
+    freeLpfChannelNames(mainLpfChannelNames);
+    freeLpfChannelNames(subLpfChannelNames);
+
+}
+
+void BatteryMitigationService::initPmicRelated() {
+    mainPmicID = 0;
+    mainPmicID = getMainPmicID(cfg.PmicCommon[mainPmicID].PmicNamePath,
+                               cfg.PmicCommon[subPmicID].PmicNamePath);
+    subPmicID = !mainPmicID;
+
+    /* read odpm resolutions and channel names */
+    readLPFPowerBitResolutions(cfg.PmicCommon[mainPmicID].OdpmDir, mainLpfBitResolutions);
+    readLPFPowerBitResolutions(cfg.PmicCommon[subPmicID].OdpmDir, subLpfBitResolutions);
+    readLPFChannelNames(cfg.PmicCommon[mainPmicID].OdpmEnabledRailsPath, mainLpfChannelNames);
+    readLPFChannelNames(cfg.PmicCommon[subPmicID].OdpmEnabledRailsPath, subLpfChannelNames);
+
+}
+
+void printUTC(FILE *fp, struct timespec time, const char *stat) {
+    if (!fp) {
+        return;
+    }
+    char timeBuff[BUF_SIZE];
+    if (strlen(stat) > 0) {
+        fprintf(fp, "%s: ", stat);
+    }
+    std::strftime(timeBuff, BUF_SIZE, "%Y-%m-%d %H:%M:%S", std::localtime(&time.tv_sec));
+    fprintf(fp, "%s.%09ld",timeBuff, time.tv_nsec);
+}
+
+void printUTC(FILE *fp, timeval time, const char *stat) {
+    if (!fp) {
+        return;
+    }
+    char timeBuff[BUF_SIZE];
+    if (strlen(stat) > 0) {
+        fprintf(fp, "%s: ", stat);
+    }
+    std::strftime(timeBuff, BUF_SIZE, "%Y-%m-%d %H:%M:%S", std::localtime(&time.tv_sec));
+    /* convert usec to nsec */
+    fprintf(fp, "%s.%06ld000",timeBuff, time.tv_usec);
+}
+
+void printODPMInstantDataSummary(FILE *fp, std::vector<odpm_instant_data> &odpmData,
+                                 double *lpfBitResolutions, char **lpfChannelNames) {
+    if (!fp) {
+        return;
+    }
+    std::vector<struct timespec> validTime;
+    std::vector<OdpmInstantPower> instPower[METER_CHANNEL_MAX];
+    std::vector<OdpmInstantPower> instPowerMax;
+    std::vector<OdpmInstantPower> instPowerMin;
+    std::vector<double> instPowerList;
+    std::vector<double> instPowerStd;
+
+    if (odpmData.size() == 0)
+        return;
+
+    /* initial Max, Min, Sum for sorting */
+    struct timespec curTime = odpmData[0].time;
+    validTime.emplace_back(curTime);
+    for (int c = 0; c < METER_CHANNEL_MAX; c++) {
+        double power = lpfBitResolutions[c] * odpmData[0].value[c];
+        instPower[c].emplace_back((OdpmInstantPower){curTime, power});
+        instPowerMax.emplace_back((OdpmInstantPower){curTime, power});
+        instPowerMin.emplace_back((OdpmInstantPower){curTime, power});
+        instPowerList.emplace_back(power);
+    }
+
+    for (auto lpf = (odpmData.begin() + 1); lpf != odpmData.end(); lpf++) {
+        curTime = lpf->time;
+        /* remove duplicate data by checking the odpm instant data dump time */
+        auto it =  std::find_if(validTime.begin(), validTime.end(),
+                                [&_ts = curTime] (const struct timespec &ts) ->
+                                bool {return _ts.tv_sec == ts.tv_sec &&
+                                             _ts.tv_nsec == ts.tv_nsec;});
+        if (it == validTime.end()) {
+            validTime.emplace_back(curTime);
+            for (int c = 0; c < METER_CHANNEL_MAX; c++){
+                double power = lpfBitResolutions[c] * lpf->value[c];
+                instPower[c].emplace_back((OdpmInstantPower){curTime, power});
+                instPowerList[c] += power;
+                if (power > instPowerMax[c].value) {
+                    instPowerMax[c].value = power;
+                    instPowerMax[c].time = curTime;
+                }
+                if (power < instPowerMin[c].value) {
+                    instPowerMin[c].value = power;
+                    instPowerMin[c].time = curTime;
+                }
+            }
+        }
+    }
+
+    int n = validTime.size();
+    for (int c = 0; c < METER_CHANNEL_MAX; c++) {
+        /* sort instant power by time */
+        std::sort(instPower[c].begin(), instPower[c].end(),
+                  [] (const auto &i, const auto &j)
+                  {return i.time.tv_sec < j.time.tv_sec ||
+                          (i.time.tv_sec == j.time.tv_sec &&
+                           i.time.tv_nsec < j.time.tv_nsec);});
+        /* compute std for each channel */
+        double avg = instPowerList[c] / n;
+        double mse = 0;
+        for (int i = 0; i < n; i++) {
+            mse += pow(instPower[c][i].value - avg, 2);
+        }
+        instPowerStd.emplace_back(pow(mse / n, 0.5));
+    }
+
+    /* print Max, Min, Avg, Std */
+    for (int c = 0; c < METER_CHANNEL_MAX; c++) {
+        fprintf(fp, "%s Max: %.2f Min: %.2f Avg: %.2f Std: %.2f\n", lpfChannelNames[c],
+               instPowerMax[c].value,
+               instPowerMin[c].value,
+               instPowerList[c] / n,
+               instPowerStd[c]);
+    }
+    fprintf(fp, "\n");
+
+    /* print time */
+    fprintf(fp, "time ");
+    for (int i = 0; i < n; i++) {
+        printUTC(fp, instPower[0][i].time, "");
+        fprintf(fp, " ");
+    }
+    fprintf(fp, "\n");
+
+    /* print instant power by channel */
+    for (int c = 0; c < METER_CHANNEL_MAX; c++){
+        fprintf(fp, "%s ", lpfChannelNames[c]);
+        for (int i = 0; i < n; i++) {
+            fprintf(fp, "%.2f ", instPower[c][i].value);
+        }
+        fprintf(fp, "\n");
+    }
+
+    fprintf(fp, "\n");
+}
+
+void printLatency(FILE *fp, struct BrownoutStatsExtend *brownoutStatsExtend) {
+    if (!fp) {
+        return;
+    }
+    /* received latency */
+    struct timespec recvLatency;
+    recvLatency.tv_sec = brownoutStatsExtend[0].eventReceivedTime.tv_sec - \
+                         brownoutStatsExtend[0].brownoutStats.triggered_time.tv_sec;
+
+    signed long long temp = brownoutStatsExtend[0].eventReceivedTime.tv_usec * 1000;
+    if (temp >= brownoutStatsExtend[0].brownoutStats.triggered_time.tv_nsec)
+        recvLatency.tv_nsec = brownoutStatsExtend[0].eventReceivedTime.tv_usec * 1000 - \
+                              brownoutStatsExtend[0].brownoutStats.triggered_time.tv_nsec;
+    else
+        recvLatency.tv_nsec = NSEC_PER_SEC - \
+                              brownoutStatsExtend[0].brownoutStats.triggered_time.tv_nsec \
+                              + brownoutStatsExtend[0].eventReceivedTime.tv_usec * 1000;
+
+    /* dump latency */
+    struct timespec dumpLatency;
+    dumpLatency.tv_sec = brownoutStatsExtend[0].dumpTime.tv_sec - \
+                         brownoutStatsExtend[0].eventReceivedTime.tv_sec;
+
+    temp = brownoutStatsExtend[0].dumpTime.tv_usec;
+    if (temp >= brownoutStatsExtend[0].eventReceivedTime.tv_usec)
+        dumpLatency.tv_nsec = (brownoutStatsExtend[0].dumpTime.tv_usec - \
+                                brownoutStatsExtend[0].eventReceivedTime.tv_usec) * 1000;
+    else
+        dumpLatency.tv_nsec = NSEC_PER_SEC - \
+                              brownoutStatsExtend[0].eventReceivedTime.tv_usec * 1000 +	\
+                              brownoutStatsExtend[0].dumpTime.tv_usec * 1000;
+
+    /* total latency */
+    struct timespec totalLatency;
+    totalLatency.tv_sec = brownoutStatsExtend[0].dumpTime.tv_sec - \
+                          brownoutStatsExtend[0].brownoutStats.triggered_time.tv_sec;
+    temp = brownoutStatsExtend[0].dumpTime.tv_usec * 1000;
+    if (temp >= brownoutStatsExtend[0].brownoutStats.triggered_time.tv_nsec)
+        totalLatency.tv_nsec = brownoutStatsExtend[0].dumpTime.tv_usec * 1000 - \
+            brownoutStatsExtend[0].brownoutStats.triggered_time.tv_nsec;
+    else
+        totalLatency.tv_nsec = NSEC_PER_SEC - \
+                               brownoutStatsExtend[0].brownoutStats.triggered_time.tv_nsec + \
+                               brownoutStatsExtend[0].dumpTime.tv_usec * 1000;
+
+    fprintf(fp, "recvLatency %ld.%09ld\n", recvLatency.tv_sec, recvLatency.tv_nsec);
+    fprintf(fp, "dumpLatency %ld.%09ld\n", dumpLatency.tv_sec, dumpLatency.tv_nsec);
+    fprintf(fp, "totalLatency %ld.%09ld\n\n", totalLatency.tv_sec, totalLatency.tv_nsec);
+
+}
+
+void BatteryMitigationService::printBrownoutStatsExtendSummary(
+                               FILE *fp, struct BrownoutStatsExtend *brownoutStatsExtend) {
+    if (!fp) {
+        return;
+    }
+    std::vector<odpm_instant_data> odpmData[PMIC_NUM];
+
+    /* print out the triggered_time in first dump */
+    printUTC(fp, brownoutStatsExtend[0].brownoutStats.triggered_time, "triggered_time");
+    fprintf(fp, "\n");
+    fprintf(fp, "triggered_idx: %d\n", brownoutStatsExtend[0].brownoutStats.triggered_idx);
+    printLatency(fp, brownoutStatsExtend);
+
+    /* skip time invalid odpm instant data */
+    for (int i = 0; i < DUMP_TIMES; i++) {
+        for (int d = 0; d < DATA_LOGGING_LEN; d++) {
+            if (brownoutStatsExtend[i].brownoutStats.main_odpm_instant_data[d].time.tv_sec != 0) {
+                odpmData[mainPmicID].emplace_back(brownoutStatsExtend[i].brownoutStats.main_odpm_instant_data[d]);
+            }
+            if (brownoutStatsExtend[i].brownoutStats.sub_odpm_instant_data[d].time.tv_sec != 0) {
+                odpmData[subPmicID].emplace_back(brownoutStatsExtend[i].brownoutStats.sub_odpm_instant_data[d]);
+            }
+        }
+    }
+
+    printODPMInstantDataSummary(fp,
+                                odpmData[mainPmicID], mainLpfBitResolutions, mainLpfChannelNames);
+    printODPMInstantDataSummary(fp,
+                                odpmData[subPmicID], subLpfBitResolutions, subLpfChannelNames);
+
+}
+
+void printOdpmInstantData(FILE *fp, struct odpm_instant_data odpmInstantData) {
+    if (!fp) {
+        return;
+    }
+    if (odpmInstantData.time.tv_sec == 0 &&
+        odpmInstantData.time.tv_nsec == 0) {
+        return;
+    }
+    printUTC(fp, odpmInstantData.time, "");
+    fprintf(fp, " ");
+    for (int i = 0; i < METER_CHANNEL_MAX; i++){
+        fprintf(fp, "%d ", odpmInstantData.value[i]);
+    }
+    fprintf(fp, "\n");
+}
+
+void parseFvpStats(const char *stats, std::vector<numericStat> &result) {
+    std::string fvpStats(stats);
+    std::string line;
+    std::istringstream iss(fvpStats);
+    size_t pos;
+    while (getline(iss, line, '\n')) {
+        if (line.find("time_ns") == std::string::npos) {
+            if (line.find("cur_freq:") == std::string::npos) {
+                result.emplace_back();
+                snprintf(result.back().name, STAT_NAME_SIZE, "%s", line.c_str());
+                continue;
+            } else if (result.size() > 0 && (pos = line.find(" ")) != std::string::npos) {
+                sscanf(line.substr(pos, line.size()).c_str(), "%d", &result.back().value);
+            }
+        }
+    }
+
+}
+
+void printBrownoutStatsExtendRaw(FILE *fp, struct BrownoutStatsExtend *brownoutStatsExtend) {
+    if (!fp) {
+        return;
+    }
+    printUTC(fp, brownoutStatsExtend->brownoutStats.triggered_time, "triggered_time");
+    fprintf(fp, "\n");
+    fprintf(fp, "triggered_idx: %d\n", brownoutStatsExtend->brownoutStats.triggered_idx);
+
+    fprintf(fp, "main_odpm_instant_data:\n");
+    for (int d = 0; d < DATA_LOGGING_LEN; d++) {
+        printOdpmInstantData(fp, brownoutStatsExtend->brownoutStats.main_odpm_instant_data[d]);
+    }
+    fprintf(fp, "sub_odpm_instant_data:\n");
+    for (int d = 0; d < DATA_LOGGING_LEN; d++) {
+        printOdpmInstantData(fp, brownoutStatsExtend->brownoutStats.sub_odpm_instant_data[d]);
+    }
+    fprintf(fp, "mitigation_state:\n");
+    for (int d = 0; d < DATA_LOGGING_LEN; d++) {
+        fprintf(fp, "%d ", brownoutStatsExtend->brownoutStats.triggered_state[d]);
+    }
+
+    fprintf(fp, "\n");
+    fprintf(fp, "fvp_stats:\n");
+    std::vector<numericStat> fvpStats;
+    parseFvpStats(brownoutStatsExtend->fvpStats, fvpStats);
+    for (const auto &fvpStat : fvpStats) {
+        fprintf(fp, "%s_freq: %d\n", fvpStat.name, fvpStat.value);
+    }
+    for (int i = 0; i < STATS_MAX_SIZE; i++) {
+        if (strlen(brownoutStatsExtend->numericStats[i].name) > 0)
+            fprintf(fp, "%s: %d\n", brownoutStatsExtend->numericStats[i].name,
+                               brownoutStatsExtend->numericStats[i].value);
+    }
+    printUTC(fp, brownoutStatsExtend->eventReceivedTime, "eventReceivedTime");
+    fprintf(fp, "\n");
+    printUTC(fp, brownoutStatsExtend->dumpTime, "dumpTime");
+    fprintf(fp, "\n");
+    fprintf(fp, "eventIdx: %d\n", brownoutStatsExtend->eventIdx);
+}
+
+bool getValueFromNumericStats(const char *statName, int *value,
+                              struct numericStat *numericStats) {
+    for (int i = 0; i < STATS_MAX_SIZE; i++) {
+        if (strcmp(numericStats[i].name, statName) == 0) {
+            *value = numericStats[i].value;
+            return true;
+        }
+    }
+    return false;
+}
+
+void setMaxNumericStat(const char *statName, int *maxValue,
+                       struct numericStat *numericStats) {
+    int statValue;
+    if (getValueFromNumericStats(statName,
+                                 &statValue,
+                                 numericStats) &&
+        statValue > *maxValue) {
+        *maxValue = statValue;
+    }
+}
+
+void setMinNumericStat(const char *statName, int *minValue,
+                       struct numericStat *numericStats) {
+    int statValue;
+    if (getValueFromNumericStats(statName,
+                                 &statValue,
+                                 numericStats) &&
+        statValue < *minValue) {
+        *minValue = statValue;
+    }
+}
+
+/* fvp_stats constins MIF, CL0-2, TPU, AUR */
+void setMinNumericStat(const char *statName, int *minValue,
+                       std::vector<numericStat> &fvpStats) {
+    auto it = std::find_if(fvpStats.begin(), fvpStats.end(),
+                           [&name = statName] (const struct numericStat &ns) ->
+                           bool {return strcmp(ns.name, name) == 0;});
+    if (it != fvpStats.end()) {
+        if (*minValue > (*it).value) {
+            *minValue = (*it).value;
+        }
+    }
+}
+
+void initBrownoutStatsCSVRow(struct BrownoutStatsCSVRow *row) {
+    memset(row, 0, sizeof(struct BrownoutStatsCSVRow));
+    row->min_battery_soc = INT_MAX;
+    row->min_battery_cycle = INT_MAX;
+    row->min_voltage_now = INT_MAX;
+    row->min_cpu0_freq = INT_MAX;
+    row->min_cpu1_freq = INT_MAX;
+    row->min_cpu2_freq = INT_MAX;
+    row->min_gpu_freq = INT_MAX;
+    row->min_tpu_freq = INT_MAX;
+    row->min_aur_freq = INT_MAX;
+}
+
+bool readBrownoutStatsExtend(const char *storingPath,
+                             struct BrownoutStatsExtend *brownoutStatsExtends) {
+    int fd = open(storingPath, O_RDONLY);
+    if (fd < 0) {
+        LOG(DEBUG) << "Failed to open " << storingPath;
+        return false;
+    }
+
+    size_t logFileSize = lseek(fd, 0, SEEK_END);
+    char *logFileAddr = (char *) mmap(NULL, logFileSize, PROT_READ, MAP_PRIVATE, fd, 0);
+    close(fd);
+
+    memcpy(brownoutStatsExtends, logFileAddr, logFileSize);
+    munmap(logFileAddr, logFileSize);
+
+    return true;
+}
+
+void BatteryMitigationService::getBrownoutStatsCSVRow(
+                               struct BrownoutStatsExtend *brownoutStatsExtendPerEvent,
+                               struct BrownoutStatsCSVRow *row) {
+    initBrownoutStatsCSVRow(row);
+    for (int i = 0; i < DUMP_TIMES; i++) {
+        if (i == 0) {
+            /* triggered_time */
+            row->triggered_time = brownoutStatsExtendPerEvent[0].brownoutStats.triggered_time;
+            /* triggered_idx */
+            row->triggered_idx = brownoutStatsExtendPerEvent[0].brownoutStats.triggered_idx;
+        }
+        double power;
+        for (int d = 0; d < DATA_LOGGING_LEN; d++) {
+            for (int c = 0; c < METER_CHANNEL_MAX; c++) {
+                /* max_main_odpm_instant_power */
+                power =
+                  brownoutStatsExtendPerEvent[i].brownoutStats.main_odpm_instant_data[d].value[c] *
+                  mainLpfBitResolutions[c];
+                if (power > row->max_main_odpm_instant_power[c]) {
+                    row->max_main_odpm_instant_power[c] = power;
+                }
+                /* max_sub_odpm_instant_power */
+                power =
+                  brownoutStatsExtendPerEvent[i].brownoutStats.sub_odpm_instant_data[d].value[c] *
+                  subLpfBitResolutions[c];
+                if (power > row->max_sub_odpm_instant_power[c]) {
+                    row->max_sub_odpm_instant_power[c] = power;
+                }
+            }
+        }
+        /* min_battery_soc */
+        setMinNumericStat("battery_soc",
+                          &row->min_battery_soc,
+                          brownoutStatsExtendPerEvent[i].numericStats);
+        /* max_battery_temp */
+        setMaxNumericStat("battery_temp",
+                          &row->max_battery_temp,
+                          brownoutStatsExtendPerEvent[i].numericStats);
+        /* min_battery_cycle */
+        setMinNumericStat("battery_cycle",
+                          &row->min_battery_cycle,
+                          brownoutStatsExtendPerEvent[i].numericStats);
+        /* min_voltage_now */
+        setMinNumericStat("voltage_now",
+                          &row->min_voltage_now,
+                          brownoutStatsExtendPerEvent[i].numericStats);
+        /* max_current_now */
+        setMaxNumericStat("current_now",
+                          &row->max_current_now,
+                          brownoutStatsExtendPerEvent[i].numericStats);
+        /* min_cpu0_freq */
+        setMinNumericStat("cpu0_freq",
+                          &row->min_cpu0_freq,
+                          brownoutStatsExtendPerEvent[i].numericStats);
+        /* min_cpu1_freq */
+        setMinNumericStat("cpu1_freq",
+                          &row->min_cpu1_freq,
+                          brownoutStatsExtendPerEvent[i].numericStats);
+        /* min_cpu2_freq */
+        setMinNumericStat("cpu2_freq",
+                          &row->min_cpu2_freq,
+                          brownoutStatsExtendPerEvent[i].numericStats);
+        /* min_gpu_freq */
+        setMinNumericStat("gpu_freq",
+                          &row->min_gpu_freq,
+                          brownoutStatsExtendPerEvent[i].numericStats);
+
+        std::vector<numericStat> fvpStats;
+        parseFvpStats(brownoutStatsExtendPerEvent[i].fvpStats, fvpStats);
+        /* min_tpu_freq */
+        setMinNumericStat("TPU", &row->min_tpu_freq, fvpStats);
+        /* min_aur_freq */
+        setMinNumericStat("AUR", &row->min_aur_freq, fvpStats);
+    }
+
+}
+
+bool BatteryMitigationService::genLastmealCSV(const char *parsedMealCSVPath) {
+    if (access(cfg.StoringPath, F_OK) != 0) {
+        LOG(DEBUG) << "Failed to access " << cfg.StoringPath;
+        return false;
+    }
+
+    struct BrownoutStatsExtend brownoutStatsExtends[BROWNOUT_EVENT_BUF_SIZE][DUMP_TIMES];
+    if (!readBrownoutStatsExtend(cfg.StoringPath, brownoutStatsExtends[0])) {
+        return false;
+    }
+
+    std::vector<struct BrownoutStatsCSVRow> rows;
+    for (int e = 0; e < BROWNOUT_EVENT_BUF_SIZE; e++) {
+        if (brownoutStatsExtends[e][0].brownoutStats.triggered_time.tv_sec != 0) {
+            rows.emplace_back();
+            getBrownoutStatsCSVRow(brownoutStatsExtends[e], &rows.back());
+        }
+    }
+    /* sort rows by time */
+    std::sort(rows.begin(), rows.end(),
+              [] (const auto &i, const auto &j)
+              {return i.triggered_time.tv_sec < j.triggered_time.tv_sec ||
+                      (i.triggered_time.tv_sec == j.triggered_time.tv_sec &&
+                       i.triggered_time.tv_nsec < j.triggered_time.tv_nsec);});
+
+    FILE *fp = nullptr;
+    fp = fopen(parsedMealCSVPath, "w");
+    if (!fp)
+        return false;
+
+    /* print csv fields */
+    fprintf(fp, "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,", brownoutStatsCSVFields.triggered_time,
+            brownoutStatsCSVFields.triggered_idx, brownoutStatsCSVFields.battery_soc,
+            brownoutStatsCSVFields.battery_temp, brownoutStatsCSVFields.battery_cycle,
+            brownoutStatsCSVFields.voltage_now, brownoutStatsCSVFields.current_now,
+            brownoutStatsCSVFields.cpu0_freq, brownoutStatsCSVFields.cpu1_freq,
+            brownoutStatsCSVFields.cpu2_freq, brownoutStatsCSVFields.gpu_freq,
+            brownoutStatsCSVFields.tpu_freq, brownoutStatsCSVFields.aur_freq);
+    for (int c = 1; c <= METER_CHANNEL_MAX; c++) {
+        fprintf(fp, "%s%02d,", brownoutStatsCSVFields.odpm_prefix, c);
+    }
+    for (int c = 1; c <= METER_CHANNEL_MAX; c++) {
+        fprintf(fp, "%s%02d,", brownoutStatsCSVFields.odpm_prefix, c + METER_CHANNEL_MAX);
+    }
+    fprintf(fp, "\n");
+
+    /* print csv rows */
+    for (auto row = rows.begin(); row != rows.end(); row++) {
+        printUTC(fp, row->triggered_time, "");
+        fprintf(fp, ",");
+        fprintf(fp, "%d,", row->triggered_idx);
+        fprintf(fp, "%d,", row->min_battery_soc);
+        fprintf(fp, "%d,", row->max_battery_temp);
+        fprintf(fp, "%d,", row->min_battery_cycle);
+        fprintf(fp, "%d,", row->min_voltage_now);
+        fprintf(fp, "%d,", row->max_current_now);
+        fprintf(fp, "%d,", row->min_cpu0_freq);
+        fprintf(fp, "%d,", row->min_cpu1_freq);
+        fprintf(fp, "%d,", row->min_cpu2_freq);
+        fprintf(fp, "%d,", row->min_gpu_freq);
+        fprintf(fp, "%d,", row->min_tpu_freq);
+        fprintf(fp, "%d,", row->min_aur_freq);
+        for (int c = 0; c < METER_CHANNEL_MAX; c++) {
+            fprintf(fp, "%d,", int(row->max_main_odpm_instant_power[c] * 1000));
+        }
+        for (int c = 0; c < METER_CHANNEL_MAX; c++) {
+            fprintf(fp, "%d,", int(row->max_sub_odpm_instant_power[c] * 1000));
+        }
+        fprintf(fp, "\n");
+    }
+    fclose(fp);
+
+    return true;
+}
+
+bool BatteryMitigationService::isTimeValid(const char *storingPath,
+                                           std::chrono::system_clock::time_point startTime) {
+    struct BrownoutStatsExtend brownoutStatsExtends[BROWNOUT_EVENT_BUF_SIZE][DUMP_TIMES];
+    if (!readBrownoutStatsExtend(storingPath, brownoutStatsExtends[0])) {
+        return false;
+    }
+    time_t sec =
+        std::chrono::time_point_cast<std::chrono::seconds>(startTime).time_since_epoch().count();
+
+    for (int e = 0; e < BROWNOUT_EVENT_BUF_SIZE; e++) {
+        if (brownoutStatsExtends[e][0].dumpTime.tv_sec == 0 &&
+            brownoutStatsExtends[e][0].dumpTime.tv_usec == 0) {
+            continue;
+        } else if (brownoutStatsExtends[e][0].dumpTime.tv_sec < sec) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool BatteryMitigationService::parseBrownoutStatsExtend(FILE *fp) {
+    if (!fp) {
+        return false;
+    }
+    struct BrownoutStatsExtend brownoutStatsExtends[BROWNOUT_EVENT_BUF_SIZE][DUMP_TIMES];
+    if (!readBrownoutStatsExtend(cfg.StoringPath, brownoutStatsExtends[0])) {
+        return false;
+    }
+
+    for (int e = 0; e < BROWNOUT_EVENT_BUF_SIZE; e++) {
+        if (brownoutStatsExtends[e][0].dumpTime.tv_sec == 0 &&
+            brownoutStatsExtends[e][0].dumpTime.tv_usec == 0) {
+            continue;
+        }
+        printBrownoutStatsExtendSummary(fp, brownoutStatsExtends[e]);
+        fprintf(fp, "=== RAW ===\n");
+        for (int i = 0; i < DUMP_TIMES; i++) {
+            fprintf(fp, "=== Dump %d-%d ===\n", e, i);
+            printBrownoutStatsExtendRaw(fp, &brownoutStatsExtends[e][i]);
+            fprintf(fp, "=============\n\n");
+        }
+    }
+    return true;
+}
+
+bool BatteryMitigationService::genParsedMeal(const char *parsedMealPath) {
+    if (access(cfg.StoringPath, F_OK) != 0) {
+        LOG(DEBUG) << "Failed to access " << cfg.StoringPath;
+        return false;
+    }
+
+    FILE *fp = nullptr;
+    fp = fopen(parsedMealPath, "w");
+    if (!fp || !parseBrownoutStatsExtend(fp)) {
+        return false;
+    }
+    fclose(fp);
+
+    return true;
+}
+
+}  // namespace pixel
+}  // namespace google
+}  // namespace hardware
+}  // namespace android
diff --git a/battery_mitigation/include/battery_mitigation/BatteryMitigation.h b/battery_mitigation/include/battery_mitigation/BatteryMitigation.h
index d3ce433..de22b9a 100644
--- a/battery_mitigation/include/battery_mitigation/BatteryMitigation.h
+++ b/battery_mitigation/include/battery_mitigation/BatteryMitigation.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+
 #include <utils/RefBase.h>
 
 #include "MitigationThermalManager.h"
diff --git a/battery_mitigation/include/battery_mitigation/BatteryMitigationService.h b/battery_mitigation/include/battery_mitigation/BatteryMitigationService.h
new file mode 100644
index 0000000..9c69714
--- /dev/null
+++ b/battery_mitigation/include/battery_mitigation/BatteryMitigationService.h
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include <android-base/chrono_utils.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <errno.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/system_properties.h>
+#include <unistd.h>
+#include <utils/RefBase.h>
+
+#include <algorithm>
+#include <cmath>
+#include <map>
+#include <regex>
+#include <thread>
+
+#include "MitigationConfig.h"
+
+#define MIN_SUPPORTED_PLATFORM   2 /* CDT */
+#define MAX_SUPPORTED_PLATFORM   5
+#define NSEC_PER_SEC             1000000000L
+#define BROWNOUT_EVENT_BUF_SIZE  10
+#define DUMP_TIMES               12
+#define EPOLL_MAXEVENTS 12
+#define BUF_SIZE                 128
+#define FVP_STATS_SIZE 4096
+#define STAT_NAME_SIZE           48
+#define STATS_MAX_SIZE           64
+#define PMIC_NUM 2
+#define LOOP_TRIG_STATS(idx) for (int idx = 0; idx < MAX_EVENT; idx++)
+
+namespace android {
+namespace hardware {
+namespace google {
+namespace pixel {
+
+using ::android::sp;
+using android::hardware::google::pixel::MitigationConfig;
+
+struct numericStat {
+    char name[STAT_NAME_SIZE];
+    int value;
+};
+
+struct OdpmInstantPower {
+    struct timespec time;
+    double value;
+};
+
+struct BrownoutStatsCSVFields {
+    const char *const triggered_time;
+    const char *const triggered_idx;
+    const char *const battery_soc;
+    const char *const battery_temp;
+    const char *const battery_cycle;
+    const char *const voltage_now;
+    const char *const current_now;
+    const char *const cpu0_freq;
+    const char *const cpu1_freq;
+    const char *const cpu2_freq;
+    const char *const gpu_freq;
+    const char *const tpu_freq;
+    const char *const aur_freq;
+    const char *const odpm_prefix;
+};
+
+struct BrownoutStatsCSVRow {
+    struct timespec triggered_time;
+    int triggered_idx;
+    int min_battery_soc;
+    int max_battery_temp;
+    int min_battery_cycle;
+    int min_voltage_now;
+    int max_current_now;
+    int min_cpu0_freq;
+    int min_cpu1_freq;
+    int min_cpu2_freq;
+    int min_gpu_freq;
+    int min_tpu_freq;
+    int min_aur_freq;
+
+    double max_main_odpm_instant_power[METER_CHANNEL_MAX];
+    double max_sub_odpm_instant_power[METER_CHANNEL_MAX];
+};
+
+struct BrownoutStatsExtend {
+    struct brownout_stats brownoutStats;
+    char fvpStats[FVP_STATS_SIZE];
+    struct numericStat numericStats[STATS_MAX_SIZE];
+    timeval eventReceivedTime;
+    timeval dumpTime;
+    unsigned int eventIdx;
+};
+
+class BatteryMitigationService : public RefBase {
+  public:
+    BatteryMitigationService(const struct MitigationConfig::EventThreadConfig &eventThreadCfg);
+    ~BatteryMitigationService();
+
+    void startBrownoutEventThread();
+    void stopEventThread(std::atomic_bool &thread_stop, int wakeup_event_fd,
+                         std::thread &event_thread);
+    bool isBrownoutStatsBinarySupported();
+    bool isPlatformSupported();
+    bool isTimeValid(const char*, std::chrono::system_clock::time_point);
+    bool genParsedMeal(const char*);
+    bool genLastmealCSV(const char*);
+  private:
+    struct MitigationConfig::EventThreadConfig cfg;
+
+    int storingFd;
+    int triggeredStateFd[MAX_EVENT];
+    int triggeredStateEpollFd;
+    int triggeredStateWakeupEventFd;
+    std::thread eventThread;
+    std::atomic_bool triggerThreadStop{false};
+    int brownoutStatsFd;
+    int triggeredIdxFd;
+    int triggeredIdxEpollFd;
+    int wakeupEventFd;
+    std::thread brownoutEventThread;
+    std::atomic_bool threadStop{false};
+
+    int mainPmicID;
+    int subPmicID;
+    double mainLpfBitResolutions[METER_CHANNEL_MAX];
+    double subLpfBitResolutions[METER_CHANNEL_MAX];
+    char *mainLpfChannelNames[METER_CHANNEL_MAX];
+    char *subLpfChannelNames[METER_CHANNEL_MAX];
+    std::vector<MitigationConfig::numericSysfs> totalNumericSysfsStatPaths;
+
+    void BrownoutEventThread();
+    void TriggerEventThread();
+    void initTotalNumericSysfsPaths();
+    void initPmicRelated();
+    int initFd();
+    int initTrigFd();
+    void tearDownBrownoutEventThread();
+    void tearDownTriggerEventThread();
+    int readNumericStats(struct BrownoutStatsExtend*);
+    bool parseBrownoutStatsExtend(FILE *);
+    void printBrownoutStatsExtendSummary(FILE *, struct BrownoutStatsExtend *);
+    void getBrownoutStatsCSVRow(struct BrownoutStatsExtend *, struct BrownoutStatsCSVRow *);
+};
+
+}  // namespace pixel
+}  // namespace google
+}  // namespace hardware
+}  // namespace android
diff --git a/battery_mitigation/include/battery_mitigation/MitigationConfig.h b/battery_mitigation/include/battery_mitigation/MitigationConfig.h
index 825e30c..2062d29 100644
--- a/battery_mitigation/include/battery_mitigation/MitigationConfig.h
+++ b/battery_mitigation/include/battery_mitigation/MitigationConfig.h
@@ -17,11 +17,22 @@
 #ifndef HARDWARE_GOOGLE_PIXEL_BATTERY_MITIGATION_CONFIG_H
 #define HARDWARE_GOOGLE_PIXEL_BATTERY_MITIGATION_CONFIG_H
 
+#include "uapi/brownout_stats.h"
+
 namespace android {
 namespace hardware {
 namespace google {
 namespace pixel {
 
+enum TriggeredEvent {
+    UVLO1,
+    UVLO2,
+    OILO1,
+    OILO2,
+    SMPL,
+    MAX_EVENT,
+};
+
 class MitigationConfig {
   public:
     struct Config {
@@ -32,6 +43,42 @@
         const char *const TimestampFormat;
     };
 
+    struct numericSysfs {
+        std::string name;
+        std::string path;
+    };
+
+    struct numericSysfsList {
+        std::string name;
+        std::vector<std::string> paths;
+    };
+
+    struct platformSpecific {
+        const std::vector<numericSysfsList> NumericSysfsStatPaths;
+        const std::vector<numericSysfsList> NumericSysfsStatDirs;
+    };
+
+    struct pmicCommon {
+        const char *const OdpmDir;
+        const char *const OdpmEnabledRailsPath;
+        const char *const PmicNamePath;
+    };
+
+    struct EventThreadConfig {
+        const std::vector<numericSysfs> NumericSysfsStatPaths;
+        const std::vector<numericSysfs> NumericSysfsStatDirs;
+        const char *const TriggeredIdxPath;
+        const char *const triggeredStatePath[MAX_EVENT];
+        const char *const BrownoutStatsPath;
+        const char *const StoringPath;
+        const char *const ParsedThismealPath;
+        const char *const ParsedLastmealPath;
+        const char *const ParsedLastmealCSVPath;
+        const char *const FvpStatsPath;
+        const std::vector<pmicCommon> PmicCommon;
+        const platformSpecific PlatformSpecific;
+    };
+
     MitigationConfig(const struct Config &cfg);
 
   private:
diff --git a/battery_mitigation/include/battery_mitigation/uapi/brownout_stats.h b/battery_mitigation/include/battery_mitigation/uapi/brownout_stats.h
new file mode 100644
index 0000000..c913e40
--- /dev/null
+++ b/battery_mitigation/include/battery_mitigation/uapi/brownout_stats.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __BROWNOUT_STATS_H
+#define __BROWNOUT_STATS_H
+
+#define METER_CHANNEL_MAX     12
+#define DATA_LOGGING_LEN      20
+#define TRIGGERED_SOURCE_MAX  17
+
+struct odpm_instant_data {
+    struct timespec time;
+    unsigned int value[METER_CHANNEL_MAX];
+};
+
+/* Notice: sysfs only allocates a buffer of PAGE_SIZE
+ * so the sizeof brownout_stats should be smaller than that
+ */
+struct brownout_stats {
+    struct timespec triggered_time;
+    unsigned int triggered_idx;
+
+    struct odpm_instant_data main_odpm_instant_data[DATA_LOGGING_LEN];
+    struct odpm_instant_data sub_odpm_instant_data[DATA_LOGGING_LEN];
+    unsigned int triggered_state[DATA_LOGGING_LEN];
+};
+
+#endif /* __BROWNOUT_STATS_H */
diff --git a/common/init.pixel.rc b/common/init.pixel.rc
index 25b14fe..dc499c3 100644
--- a/common/init.pixel.rc
+++ b/common/init.pixel.rc
@@ -17,10 +17,3 @@
 
 on property:ota.warm_reset=0
     write /sys/module/msm_poweroff/parameters/warm_reset 0
-
-on init
-    copy_per_line /dev/cpuctl/tasks /dev/cpuctl/system/tasks
-
-# Migrate tasks again in case kernel threads are created during boot
-on property:sys.boot_completed=1
-    copy_per_line /dev/cpuctl/tasks /dev/cpuctl/system/tasks
diff --git a/common/pixel-common-device.mk b/common/pixel-common-device.mk
index 5df9cda..043bc0e 100644
--- a/common/pixel-common-device.mk
+++ b/common/pixel-common-device.mk
@@ -57,3 +57,8 @@
 # Virtual fingerprint HAL
 PRODUCT_PACKAGES_DEBUG += com.android.hardware.biometrics.fingerprint.virtual
 
+# Virtual face HAL
+ifeq ($(RELEASE_AIDL_USE_UNFROZEN), true)
+PRODUCT_PACKAGES_DEBUG += com.android.hardware.biometrics.face.virtual
+endif
+
diff --git a/gpu_probe/Android.bp b/gpu_probe/Android.bp
new file mode 100644
index 0000000..370014c
--- /dev/null
+++ b/gpu_probe/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2023 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.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary {
+  name: "gpu_probe",
+  srcs: [
+    "main.rs",
+  ],
+  rustlibs: [
+    "liblibloading",
+    "libandroid_logger",
+    "liblog_rust",
+  ],
+  shared_libs: ["libdl"],
+  vendor: true,
+  init_rc: [ "gpu_probe.rc" ],
+}
diff --git a/gpu_probe/gpu_probe.rc b/gpu_probe/gpu_probe.rc
new file mode 100644
index 0000000..6cee644
--- /dev/null
+++ b/gpu_probe/gpu_probe.rc
@@ -0,0 +1,9 @@
+# Start gpu_probe service for Pixel devices.
+
+service gpu_probe /vendor/bin/gpu_probe
+    class hal
+    oneshot
+    disabled
+    user system
+    capabilities SYS_ADMIN
+    group graphics
diff --git a/gpu_probe/main.rs b/gpu_probe/main.rs
new file mode 100644
index 0000000..d417180
--- /dev/null
+++ b/gpu_probe/main.rs
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+fn main() {
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("gpu_probe")
+            .with_max_level(log::LevelFilter::Info),
+    );
+
+    log::info!("Starting pixel gpu_probe");
+    std::panic::set_hook(Box::new(|panic_msg| {
+        log::error!("{}", panic_msg);
+    }));
+    unsafe {
+        let gpudataproducer_library =
+            libloading::Library::new("/vendor/lib64/libgpudataproducer.so").unwrap();
+        let start: libloading::Symbol<fn() -> ()> = gpudataproducer_library.get(b"start").unwrap();
+        start();
+    };
+}
diff --git a/health/ChargerDetect.cpp b/health/ChargerDetect.cpp
index 26d926f..a261529 100644
--- a/health/ChargerDetect.cpp
+++ b/health/ChargerDetect.cpp
@@ -70,7 +70,7 @@
         while ((entry = readdir(dir.get()))) {
             const char* name = entry->d_name;
 
-            KLOG_INFO(LOG_TAG, "Psy name:%s", name);
+            KLOG_DEBUG(LOG_TAG, "Psy name:%s", name);
             if (strstr(name, kTcpmPsyFilter)) {
                 *tcpmPsyName = name;
             }
@@ -119,7 +119,7 @@
 
     if (tcpmPsyName.empty()) {
         populateTcpmPsyName(&tcpmPsyName);
-        KLOG_INFO(LOG_TAG, "TcpmPsyName:%s\n", tcpmPsyName.c_str());
+        KLOG_DEBUG(LOG_TAG, "TcpmPsyName:%s\n", tcpmPsyName.c_str());
     }
 
     if (!getIntField(kUsbOnlinePath)) {
@@ -150,7 +150,7 @@
         return;
     }
 
-    KLOG_INFO(LOG_TAG, "TcpmPsy Usbtype:%s\n", usbPsyType.c_str());
+    KLOG_DEBUG(LOG_TAG, "TcpmPsy Usbtype:%s\n", usbPsyType.c_str());
 
     return;
 }
diff --git a/misc_writer/include/misc_writer/misc_writer.h b/misc_writer/include/misc_writer/misc_writer.h
index ba1b685..7757640 100644
--- a/misc_writer/include/misc_writer/misc_writer.h
+++ b/misc_writer/include/misc_writer/misc_writer.h
@@ -40,6 +40,9 @@
   kWriteTimeOffset,
   kSetMaxRamSize,
   kClearMaxRamSize,
+  kWriteTimeRtcOffset,
+  kWriteTimeMinRtc,
+  kSetSotaConfig,
 
   kUnset = -1,
 };
@@ -62,6 +65,12 @@
   static constexpr uint32_t kMaxRamSizeOffsetInVendorSpace = 192;
   static constexpr char kMaxRamSize[] = "max-ram-size=";
   static constexpr uint32_t kSotaStateOffsetInVendorSpace = 224;
+  static constexpr uint32_t kRTimeRtcOffsetValOffsetInVendorSpace = 264;
+  static constexpr char kTimeRtcOffset[] = "timertcoffset=";
+  static constexpr uint32_t kRTimeMinRtcValOffsetInVendorSpace = 296;
+  static constexpr char kTimeMinRtc[] = "timeminrtc=";
+  static constexpr uint32_t kFaceauthEvalValOffsetInVendorSpace = 328;
+  static constexpr uint32_t kSotaScheduleShipmodeOffsetInVendorSpace = 360;
 
   // Minimum and maximum valid value for max-ram-size
   static constexpr int32_t kRamSizeDefault = -1;
diff --git a/misc_writer/misc_writer.cpp b/misc_writer/misc_writer.cpp
index 216b188..710c2ff 100644
--- a/misc_writer/misc_writer.cpp
+++ b/misc_writer/misc_writer.cpp
@@ -93,6 +93,18 @@
                           ? std::string(kMaxRamSize).append(stringdata_).append("\n")
                           : std::string(32, 0);
         break;
+    case MiscWriterActions::kWriteTimeRtcOffset:
+        offset = override_offset.value_or(kRTimeRtcOffsetValOffsetInVendorSpace);
+        content = std::string(kTimeRtcOffset) + stringdata_;
+        content.resize(32);
+        break;
+    case MiscWriterActions::kWriteTimeMinRtc:
+        offset = override_offset.value_or(kRTimeMinRtcValOffsetInVendorSpace);
+        content = std::string(kTimeMinRtc) + stringdata_;
+        content.resize(32);
+        break;
+    case MiscWriterActions::kSetSotaConfig:
+        goto sota_config;
     case MiscWriterActions::kUnset:
       LOG(ERROR) << "The misc writer action must be set";
       return false;
@@ -104,7 +116,8 @@
     return false;
   }
 
-  if (action_ == MiscWriterActions::kSetSotaFlag) {
+sota_config:
+  if (action_ == MiscWriterActions::kSetSotaFlag || action_ == MiscWriterActions::kSetSotaConfig) {
     content = ::android::base::GetProperty("persist.vendor.nfc.factoryota.state", "");
     if (content.size() != 0 && content.size() <= 40) {
       offset = kSotaStateOffsetInVendorSpace;
@@ -114,6 +127,15 @@
           return false;
       }
     }
+    content = ::android::base::GetProperty("persist.vendor.nfc.factoryota.schedule_shipmode", "");
+    if (content.size() != 0 && content.size() <= 32) {
+      offset = kSotaScheduleShipmodeOffsetInVendorSpace;
+      if (std::string err;
+          !WriteMiscPartitionVendorSpace(content.data(), content.size(), offset, &err)) {
+          LOG(ERROR) << "Failed to write " << content << " at offset " << offset << " : " << err;
+          return false;
+      }
+    }
   }
 
   return true;
diff --git a/misc_writer/misc_writer_main.cpp b/misc_writer/misc_writer_main.cpp
index 0af45a9..f3546ef 100644
--- a/misc_writer/misc_writer_main.cpp
+++ b/misc_writer/misc_writer_main.cpp
@@ -42,6 +42,7 @@
   std::cerr << "  --clear-dark-theme   Clear the dark theme flag\n";
   std::cerr << "  --set-sota           Write the silent OTA flag\n";
   std::cerr << "  --clear-sota         Clear the silent OTA flag\n";
+  std::cerr << "  --set-sota-config    Set the silent OTA configs\n";
   std::cerr << "  --set-enable-pkvm    Write the enable pKVM flag\n";
   std::cerr << "  --set-disable-pkvm   Write the disable pKVM flag\n";
   std::cerr << "  --set-wrist-orientation <0-3> Write the wrist orientation flag\n";
@@ -50,6 +51,8 @@
   std::cerr << "  --set-timeoffset              Write the time offset value (tz_time - utc_time)\n";
   std::cerr << "  --set-max-ram-size <2048-65536> Write the sw limit max ram size in MB\n";
   std::cerr << "  --set-max-ram-size <-1>         Clear the sw limit max ram size\n";
+  std::cerr << "  --set-timertcoffset           Write the time offset value (utc_time - rtc_time)\n";
+  std::cerr << "  --set-minrtc                  Write the minimum expected rtc value for tilb\n";
   std::cerr << "Writes the given hex string to the specified offset in vendor space in /misc "
                "partition.\nDefault offset is used for each action unless "
                "--override-vendor-space-offset is specified.\n";
@@ -71,6 +74,9 @@
     { "set-timeformat", required_argument, nullptr, 0},
     { "set-timeoffset", required_argument, nullptr, 0},
     { "set-max-ram-size", required_argument, nullptr, 0},
+    { "set-timertcoffset", required_argument, nullptr, 0},
+    { "set-minrtc", required_argument, nullptr, 0},
+    { "set-sota-config", no_argument, nullptr, 0 },
     { nullptr, 0, nullptr, 0 },
   };
 
@@ -82,6 +88,7 @@
     { "set-enable-pkvm", MiscWriterActions::kSetEnablePkvmFlag },
     { "set-disable-pkvm", MiscWriterActions::kSetDisablePkvmFlag },
     { "clear-wrist-orientation", MiscWriterActions::kClearWristOrientationFlag },
+    { "set-sota-config", MiscWriterActions::kSetSotaConfig },
   };
 
   std::unique_ptr<MiscWriter> misc_writer;
@@ -173,6 +180,30 @@
         misc_writer = std::make_unique<MiscWriter>(MiscWriterActions::kSetMaxRamSize,
                                                    std::to_string(max_ram_size));
       }
+    } else if (option_name == "set-timertcoffset"s) {
+      long long int timertcoffset = strtoll(optarg, NULL, 10);
+      if (0 == timertcoffset) {
+        LOG(ERROR) << "Failed to parse the timertcoffset:" << optarg;
+        return Usage(argv[0]);
+      }
+      if (misc_writer) {
+        LOG(ERROR) << "Misc writer action has already been set";
+        return Usage(argv[0]);
+      }
+      misc_writer = std::make_unique<MiscWriter>(MiscWriterActions::kWriteTimeRtcOffset,
+                                                     std::to_string(timertcoffset));
+    } else if (option_name == "set-minrtc"s) {
+      long long int minrtc = strtoll(optarg, NULL, 10);
+      if (0 == minrtc) {
+        LOG(ERROR) << "Failed to parse the minrtc:" << optarg;
+        return Usage(argv[0]);
+      }
+      if (misc_writer) {
+        LOG(ERROR) << "Misc writer action has already been set";
+        return Usage(argv[0]);
+      }
+      misc_writer = std::make_unique<MiscWriter>(MiscWriterActions::kWriteTimeMinRtc,
+                                                     std::to_string(minrtc));
     } else if (auto iter = action_map.find(option_name); iter != action_map.end()) {
       if (misc_writer) {
         LOG(ERROR) << "Misc writer action has already been set";
diff --git a/mm/pixel-mm-gki.rc b/mm/pixel-mm-gki.rc
index 52603d5..694396e 100644
--- a/mm/pixel-mm-gki.rc
+++ b/mm/pixel-mm-gki.rc
@@ -51,6 +51,10 @@
     write /sys/kernel/tracing/instances/pixel/events/dmabuf_heap/dma_buf_release/enable 1
     write /sys/kernel/tracing/instances/pixel/events/trusty/trusty_dma_buf_put/enable 1
 
+    # Allow max_usage_kb to be reset by system processes
+    chown system system /sys/kernel/vendor_mm/gcma_heap/trusty:faceauth_rawimage_heap/max_usage_kb
+    chmod 0660 /sys/kernel/vendor_mm/gcma_heap/trusty:faceauth_rawimage_heap/max_usage_kb
+
 # turns off tracing right before bugreporting to keep more traces
 on property:init.svc.dumpstatez=running
     write /sys/kernel/tracing/instances/pixel/tracing_on 0
diff --git a/pixelstats/Android.bp b/pixelstats/Android.bp
index a0e55a3..92fc08a 100644
--- a/pixelstats/Android.bp
+++ b/pixelstats/Android.bp
@@ -131,7 +131,7 @@
     generated_headers: ["pixelstatsatoms.h"],
     export_generated_headers: ["pixelstatsatoms.h"],
     shared_libs: [
-        "android.frameworks.stats-V1-ndk",
+        "android.frameworks.stats-V2-ndk",
     ]
 }
 
@@ -167,7 +167,7 @@
     "-Werror",
   ],
   shared_libs: [
-    "android.frameworks.stats-V1-ndk",
+    "android.frameworks.stats-V2-ndk",
     "libbase",
     "libbinder_ndk",
     "libcutils",
@@ -179,7 +179,7 @@
     "pixelatoms-cpp",
   ],
   export_shared_lib_headers: [
-    "android.frameworks.stats-V1-ndk",
+    "android.frameworks.stats-V2-ndk",
     "pixelatoms-cpp",
   ],
   static_libs: [
diff --git a/pixelstats/BatteryEEPROMReporter.cpp b/pixelstats/BatteryEEPROMReporter.cpp
index d5ed720..b8060a7 100644
--- a/pixelstats/BatteryEEPROMReporter.cpp
+++ b/pixelstats/BatteryEEPROMReporter.cpp
@@ -38,6 +38,7 @@
 
 #define LINESIZE 71
 #define LINESIZE_V2 31
+#define LINESIZE_MAX17201_HIST 80
 
 BatteryEEPROMReporter::BatteryEEPROMReporter() {}
 
@@ -58,7 +59,6 @@
         ALOGE("Unable to read %s - %s", path.c_str(), strerror(errno));
         return;
     }
-    ALOGD("checkAndReport: %s", file_contents.c_str());
 
     int16_t i, num;
     struct BatteryHistory hist;
@@ -269,6 +269,112 @@
         ALOGE("Unable to report BatteryEEPROM to Stats service");
 }
 
+void BatteryEEPROMReporter::checkAndReportGMSR(const std::shared_ptr<IStats> &stats_client,
+                                               const std::string &path) {
+    struct BatteryHistory gmsr;
+    std::string file_contents;
+    int16_t num;
+
+    if (path.empty())
+        return;
+
+    if (!ReadFileToString(path, &file_contents)) {
+        ALOGE("Unable to read gmsr path: %s - %s", path.c_str(), strerror(errno));
+        return;
+    }
+
+    gmsr.checksum = 0xFFFF;
+    if (path.find("max77779") == std::string::npos) {
+        num = sscanf(file_contents.c_str(),  "rcomp0\t:%4" SCNx16 "\ntempco\t:%4" SCNx16
+                     "\nfullcaprep\t:%4" SCNx16 "\ncycles\t:%4" SCNx16 "\nfullcapnom\t:%4" SCNx16
+                     "\nqresidual00\t:%4" SCNx16 "\nqresidual10\t:%4" SCNx16
+                     "\nqresidual20\t:%4" SCNx16 "\nqresidual30\t:%4" SCNx16
+                     "\ncv_mixcap\t:%4" SCNx16 "\nhalftime\t:%4" SCNx16,
+                     &gmsr.rcomp0, &gmsr.tempco, &gmsr.full_rep, &gmsr.cycle_cnt, &gmsr.full_cap,
+                     &gmsr.max_vbatt, &gmsr.min_vbatt, &gmsr.max_ibatt, &gmsr.min_ibatt,
+                     &gmsr.esr, &gmsr.rslow);
+        if (num != kNum77759GMSRFields) {
+            ALOGE("Couldn't process 77759GMSR. num=%d\n", num);
+            return;
+        }
+    } else {
+        num = sscanf(file_contents.c_str(),  "rcomp0\t:%4" SCNx16 "\ntempco\t:%4" SCNx16
+                     "\nfullcaprep\t:%4" SCNx16 "\ncycles\t:%4" SCNx16 "\nfullcapnom\t:%4" SCNx16,
+                     &gmsr.rcomp0, &gmsr.tempco, &gmsr.full_rep, &gmsr.cycle_cnt, &gmsr.full_cap);
+        if (num != kNum77779GMSRFields) {
+            ALOGE("Couldn't process 77779GMSR. num=%d\n", num);
+            return;
+        }
+    }
+
+    reportEvent(stats_client, gmsr);
+}
+
+void BatteryEEPROMReporter::checkAndReportMaxfgHistory(const std::shared_ptr<IStats> &stats_client,
+                                                       const std::string &path) {
+    std::string file_contents;
+    int16_t i;
+
+    if (path.empty())
+        return;
+
+    if (!ReadFileToString(path, &file_contents)) {
+        ALOGD("Unable to read maxfg_hist path: %s - %s", path.c_str(), strerror(errno));
+        return;
+    }
+
+    std::string hist_each;
+    const int kHistTotalLen = file_contents.size();
+
+    ALOGD("checkAndReportMaxfgHistory:size=%d\n%s", kHistTotalLen, file_contents.c_str());
+
+    for (i = 0; i < kHistTotalLen; i++) {
+        struct BatteryHistory maxfg_hist;
+        uint16_t nQRTable00, nQRTable10, nQRTable20, nQRTable30, nCycles, nFullCapNom;
+        uint16_t nRComp0, nTempCo, nIAvgEmpty, nFullCapRep, nVoltTemp, nMaxMinCurr, nMaxMinVolt;
+        uint16_t nMaxMinTemp, nSOC, nTimerH;
+        int16_t num;
+        size_t hist_offset = i * LINESIZE_MAX17201_HIST;
+
+        if (hist_offset >= file_contents.size())
+            break;
+
+        hist_each = file_contents.substr(hist_offset, LINESIZE_MAX17201_HIST);
+        num = sscanf(hist_each.c_str(), "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16
+                     "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16
+                     "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16,
+                     &nQRTable00, &nQRTable10, &nQRTable20, &nQRTable30, &nCycles, &nFullCapNom,
+                     &nRComp0, &nTempCo, &nIAvgEmpty, &nFullCapRep, &nVoltTemp, &nMaxMinCurr,
+                     &nMaxMinVolt, &nMaxMinTemp, &nSOC, &nTimerH);
+
+        if (num != kNum17201HISTFields) {
+            ALOGE("Couldn't process %s (num=%d)", hist_each.c_str(), num);
+            continue;
+        }
+
+        /* not assign: nQRTable00, nQRTable10, nQRTable20, nQRTable30 */
+        maxfg_hist.reserve = 0xFF;
+        maxfg_hist.tempco = nTempCo;
+        maxfg_hist.rcomp0 = nRComp0;
+        maxfg_hist.full_rep = nFullCapNom;
+        maxfg_hist.full_cap = nFullCapRep;
+        maxfg_hist.cycle_cnt = nCycles * 16 / 100; // LSB: 16%;
+        maxfg_hist.timer_h = (nTimerH * 32 / 10) / 24; // LSB: 3.2 hours
+        maxfg_hist.batt_soc = (nSOC >> 8) & 0x00FF;
+        maxfg_hist.msoc = nSOC & 0x00FF;
+        maxfg_hist.max_ibatt = ((nMaxMinCurr >> 8) & 0x00FF) * 80;
+        maxfg_hist.min_ibatt = (nMaxMinCurr & 0x00FF) * 80 * (-1);
+        maxfg_hist.max_vbatt = ((nMaxMinVolt >> 8) & 0x00FF) * 20;
+        maxfg_hist.min_vbatt = (nMaxMinVolt & 0x00FF) * 20;
+        maxfg_hist.max_temp = (nMaxMinTemp >> 8) & 0x00FF;
+        maxfg_hist.min_temp = nMaxMinTemp & 0x00FF;
+        maxfg_hist.esr = nIAvgEmpty;
+        maxfg_hist.rslow = nVoltTemp;
+
+        reportEvent(stats_client, maxfg_hist);
+    }
+}
+
 }  // namespace pixel
 }  // namespace google
 }  // namespace hardware
diff --git a/pixelstats/BrownoutDetectedReporter.cpp b/pixelstats/BrownoutDetectedReporter.cpp
index e22d2df..a5f4e39 100644
--- a/pixelstats/BrownoutDetectedReporter.cpp
+++ b/pixelstats/BrownoutDetectedReporter.cpp
@@ -218,6 +218,92 @@
     return 0;
 }
 
+int BrownoutDetectedReporter::brownoutReasonCheck(const std::string &brownoutReasonProp) {
+    std::string reason = android::base::GetProperty(brownoutReasonProp.c_str(), "");
+    if (reason.empty()) {
+        // Brownout not found
+        return -1;
+    }
+    auto key = kBrownoutReason.find(reason);
+    if (key == kBrownoutReason.end()) {
+        return -1;
+    }
+    return key->second;
+}
+
+int parseIRQ(const std::string &element) {
+    int idx = atoi(element.c_str());
+    if (idx == SMPL_WARN) {
+        return BrownoutDetected::SMPL_WARN;
+    } else if (idx == UVLO1) {
+        return BrownoutDetected::UVLO1;
+    } else if (idx == UVLO2) {
+        return BrownoutDetected::UVLO2;
+    } else if (idx == BATOILO) {
+        return BrownoutDetected::BATOILO;
+    } else if (idx == BATOILO2) {
+        return BrownoutDetected::BATOILO2;
+    }
+    return -1;
+}
+
+void BrownoutDetectedReporter::logBrownoutCsv(const std::shared_ptr<IStats> &stats_client,
+                                              const std::string &CsvFilePath,
+                                              const std::string &brownoutReasonProp) {
+    std::string csvFile;
+    if (!android::base::ReadFileToString(CsvFilePath, &csvFile)) {
+        return;
+    }
+    std::istringstream content(csvFile);
+    std::string line;
+    struct BrownoutDetectedInfo max_value = {};
+    max_value.voltage_now_ = DEFAULT_BATTERY_VOLT;
+    max_value.battery_soc_ = DEFAULT_BATTERY_SOC;
+    max_value.battery_temp_ = DEFAULT_BATTERY_TEMP;
+    std::smatch pattern_match;
+    max_value.brownout_reason_ = brownoutReasonCheck(brownoutReasonProp);
+    if (max_value.brownout_reason_ < 0) {
+        return;
+    }
+    bool isAlreadyUpdated = false;
+    std::vector<std::vector<std::string>> rows;
+    int row_num = 0;
+    while (std::getline(content, line)) {
+        if (std::regex_match(line, pattern_match, kAlreadyUpdatedPattern)) {
+            isAlreadyUpdated = true;
+            break;
+        }
+        row_num++;
+        if (row_num == 1) {
+            continue;
+        }
+        std::vector<std::string> row;
+        std::stringstream ss(line);
+        std::string field;
+        while (getline(ss, field, ',')) {
+            row.push_back(field);
+        }
+
+        max_value.triggered_timestamp_ = parseTimestamp(row[TIMESTAMP_IDX].c_str());
+        max_value.triggered_irq_ = parseIRQ(row[IRQ_IDX]);
+        max_value.battery_soc_ = atoi(row[SOC_IDX].c_str());
+        max_value.battery_temp_ = atoi(row[TEMP_IDX].c_str());
+        max_value.battery_cycle_ = atoi(row[CYCLE_IDX].c_str());
+        max_value.voltage_now_ = atoi(row[VOLTAGE_IDX].c_str());
+        for (int i = 0; i < DVFS_MAX_IDX; i++) {
+            max_value.dvfs_value_[i] = atoi(row[i + DVFS_CHANNEL_0].c_str());
+        }
+        for (int i = 0; i < ODPM_MAX_IDX; i++) {
+            max_value.odpm_value_[i] = atoi(row[i + ODPM_CHANNEL_0].c_str());
+        }
+    }
+    if (!isAlreadyUpdated && max_value.battery_temp_ != DEFAULT_BATTERY_TEMP) {
+        std::string file_content = "LASTMEAL_UPDATED\n" + csvFile;
+        android::base::WriteStringToFile(file_content, CsvFilePath);
+        uploadData(stats_client, max_value);
+    }
+}
+
 void BrownoutDetectedReporter::logBrownout(const std::shared_ptr<IStats> &stats_client,
                                            const std::string &logFilePath,
                                            const std::string &brownoutReasonProp) {
@@ -233,17 +319,10 @@
     max_value.battery_temp_ = DEFAULT_BATTERY_TEMP;
     std::smatch pattern_match;
     int odpm_index = 0, dvfs_index = 0;
-    std::string reason = android::base::GetProperty(brownoutReasonProp.c_str(), "");
-    if (reason.empty()) {
-        // Brownout not found
+    max_value.brownout_reason_ = brownoutReasonCheck(brownoutReasonProp);
+    if (max_value.brownout_reason_ < 0) {
         return;
     }
-
-    auto key = kBrownoutReason.find(reason);
-    if (key == kBrownoutReason.end()) {
-        return;
-    }
-    max_value.brownout_reason_ = key->second;
     bool isAlreadyUpdated = false;
     while (std::getline(content, line)) {
         if (std::regex_match(line, pattern_match, kAlreadyUpdatedPattern)) {
@@ -277,7 +356,7 @@
             max_value.triggered_timestamp_ = parseTimestamp(line.c_str());
             continue;
         }
-        if (updateIfFound(line, kBatterySocPattern, &max_value.battery_soc_, kUpdateMax)) {
+        if (updateIfFound(line, kBatterySocPattern, &max_value.battery_soc_, kUpdateMin)) {
             continue;
         }
         if (updateIfFound(line, kBatteryTempPattern, &max_value.battery_temp_, kUpdateMin)) {
diff --git a/pixelstats/ChargeStatsReporter.cpp b/pixelstats/ChargeStatsReporter.cpp
index 2354acd..39d9120 100644
--- a/pixelstats/ChargeStatsReporter.cpp
+++ b/pixelstats/ChargeStatsReporter.cpp
@@ -234,12 +234,9 @@
 void ChargeStatsReporter::checkAndReport(const std::shared_ptr<IStats> &stats_client,
                                          const std::string &path) {
     std::string file_contents, line, wfile_contents, wline_at, wline_ac, pca_file_contents,
-            pca_line, thermal_file_contents, gcharger_file_contents;
+            pca_line, thermal_file_contents, gcharger_file_contents, gdbatt_file_contents;
     std::istringstream ss;
-    bool has_wireless = wireless_charge_stats_.CheckWirelessContentsAndAck(&wfile_contents);
-    bool has_pca = pca_charge_stats_.CheckPcaContentsAndAck(&pca_file_contents);
-    bool has_thermal = checkContentsAndAck(&thermal_file_contents, kThermalChargeMetricsPath);
-    bool has_gcharger = checkContentsAndAck(&gcharger_file_contents, kGChargerMetricsPath);
+    bool has_wireless, has_pca, has_thermal, has_gcharger, has_dual_batt;
 
     if (!ReadFileToString(path.c_str(), &file_contents)) {
         ALOGE("Unable to read %s - %s", path.c_str(), strerror(errno));
@@ -262,6 +259,7 @@
         return;
     }
 
+    has_pca = pca_charge_stats_.CheckPcaContentsAndAck(&pca_file_contents);
     if (has_pca) {
         std::istringstream pca_ss;
 
@@ -269,6 +267,7 @@
         std::getline(pca_ss, pca_line);
     }
 
+    has_wireless = wireless_charge_stats_.CheckWirelessContentsAndAck(&wfile_contents);
     if (has_wireless) {
         std::istringstream wss;
 
@@ -287,6 +286,7 @@
         ReportVoltageTierStats(stats_client, line.c_str(), has_wireless, wfile_contents);
     }
 
+    has_thermal = checkContentsAndAck(&thermal_file_contents, kThermalChargeMetricsPath);
     if (has_thermal) {
         std::istringstream wss;
         wss.str(thermal_file_contents);
@@ -295,6 +295,7 @@
         }
     }
 
+    has_gcharger = checkContentsAndAck(&gcharger_file_contents, kGChargerMetricsPath);
     if (has_gcharger) {
         std::istringstream wss;
         wss.str(gcharger_file_contents);
@@ -302,6 +303,15 @@
             ReportVoltageTierStats(stats_client, line.c_str());
         }
     }
+
+    has_dual_batt = checkContentsAndAck(&gdbatt_file_contents, kGDualBattMetricsPath);
+    if (has_dual_batt) {
+        std::istringstream wss;
+        wss.str(gdbatt_file_contents);
+        while (std::getline(wss, line)) {
+            ReportVoltageTierStats(stats_client, line.c_str());
+        }
+    }
 }
 
 bool ChargeStatsReporter::checkContentsAndAck(std::string *file_contents, const std::string &path) {
diff --git a/pixelstats/StatsHelper.cpp b/pixelstats/StatsHelper.cpp
index 1d8d16b..62af17c 100644
--- a/pixelstats/StatsHelper.cpp
+++ b/pixelstats/StatsHelper.cpp
@@ -201,6 +201,31 @@
         ALOGE("Unable to report VendorUsbPortOverheat to Stats service");
 }
 
+void reportUsbDataSessionEvent(const std::shared_ptr<IStats> &stats_client,
+                               const PixelAtoms::VendorUsbDataSessionEvent &usb_data_event) {
+    // Load values array
+    std::vector<VendorAtomValue> values(4);
+    VendorAtomValue tmp;
+    tmp.set<VendorAtomValue::intValue>(usb_data_event.usb_role());
+    values[0] = tmp;
+    tmp.set<VendorAtomValue::repeatedIntValue>(std::vector<int32_t>(
+            usb_data_event.usb_states().begin(), usb_data_event.usb_states().end()));
+    values[1] = tmp;
+    tmp.set<VendorAtomValue::repeatedLongValue>(std::vector<int64_t>(
+            usb_data_event.elapsed_time_ms().begin(), usb_data_event.elapsed_time_ms().end()));
+    values[2] = tmp;
+    tmp.set<VendorAtomValue::longValue>(usb_data_event.duration_ms());
+    values[3] = tmp;
+
+    // Send vendor atom to IStats HAL
+    VendorAtom event = {.reverseDomainName = "",
+                        .atomId = PixelAtoms::Atom::kVendorUsbDataSessionEvent,
+                        .values = std::move(values)};
+    const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
+    if (!ret.isOk())
+        ALOGE("Unable to report VendorUsbDataSessionEvent to Stats service");
+}
+
 }  // namespace pixel
 }  // namespace google
 }  // namespace hardware
diff --git a/pixelstats/SysfsCollector.cpp b/pixelstats/SysfsCollector.cpp
index 4460ca1..bce967c 100644
--- a/pixelstats/SysfsCollector.cpp
+++ b/pixelstats/SysfsCollector.cpp
@@ -62,7 +62,9 @@
 using android::hardware::google::pixel::PixelAtoms::StorageUfsResetCount;
 using android::hardware::google::pixel::PixelAtoms::ThermalDfsStats;
 using android::hardware::google::pixel::PixelAtoms::VendorAudioAdaptedInfoStatsReported;
+using android::hardware::google::pixel::PixelAtoms::VendorAudioBtMediaStatsReported;
 using android::hardware::google::pixel::PixelAtoms::VendorAudioHardwareStatsReported;
+using android::hardware::google::pixel::PixelAtoms::VendorAudioOffloadedEffectStatsReported;
 using android::hardware::google::pixel::PixelAtoms::VendorAudioPcmStatsReported;
 using android::hardware::google::pixel::PixelAtoms::VendorAudioPdmStatsReported;
 using android::hardware::google::pixel::PixelAtoms::VendorAudioThirdPartyEffectStatsReported;
@@ -97,6 +99,7 @@
       kZramMmStatPath("/sys/block/zram0/mm_stat"),
       kZramBdStatPath("/sys/block/zram0/bd_stat"),
       kEEPROMPath(sysfs_paths.EEPROMPath),
+      kBrownoutCsvPath(sysfs_paths.BrownoutCsvPath),
       kBrownoutLogPath(sysfs_paths.BrownoutLogPath),
       kBrownoutReasonProp(sysfs_paths.BrownoutReasonProp),
       kPowerMitigationStatsPath(sysfs_paths.MitigationPath),
@@ -123,7 +126,12 @@
       kAdaptedInfoDurationPath(sysfs_paths.AdaptedInfoDurationPath),
       kPcmLatencyPath(sysfs_paths.PcmLatencyPath),
       kPcmCountPath(sysfs_paths.PcmCountPath),
-      kTotalCallCountPath(sysfs_paths.TotalCallCountPath) {}
+      kTotalCallCountPath(sysfs_paths.TotalCallCountPath),
+      kOffloadEffectsIdPath(sysfs_paths.OffloadEffectsIdPath),
+      kOffloadEffectsDurationPath(sysfs_paths.OffloadEffectsDurationPath),
+      kBluetoothAudioUsagePath(sysfs_paths.BluetoothAudioUsagePath),
+      kGMSRPath(sysfs_paths.GMSRPath),
+      kMaxfgHistoryPath("/dev/maxfg_history") {}
 
 bool SysfsCollector::ReadFileToInt(const std::string &path, int *val) {
     return ReadFileToInt(path.c_str(), val);
@@ -192,10 +200,17 @@
 void SysfsCollector::logBatteryEEPROM(const std::shared_ptr<IStats> &stats_client) {
     if (kEEPROMPath == nullptr || strlen(kEEPROMPath) == 0) {
         ALOGV("Battery EEPROM path not specified");
-        return;
+    } else {
+        battery_EEPROM_reporter_.checkAndReport(stats_client, kEEPROMPath);
     }
 
-    battery_EEPROM_reporter_.checkAndReport(stats_client, kEEPROMPath);
+    if (kGMSRPath == nullptr || strlen(kGMSRPath) == 0) {
+         ALOGV("Battery GMSR path not specified");
+    } else {
+        battery_EEPROM_reporter_.checkAndReportGMSR(stats_client, kGMSRPath);
+    }
+
+    battery_EEPROM_reporter_.checkAndReportMaxfgHistory(stats_client, kMaxfgHistoryPath);
 }
 
 /**
@@ -958,22 +973,23 @@
         // The size should be the same as the number of fields in ZramMmStat
         std::vector<VendorAtomValue> values(6);
         VendorAtomValue tmp;
-        tmp.set<VendorAtomValue::intValue>(orig_data_size);
+        tmp.set<VendorAtomValue::longValue>(orig_data_size);
         values[ZramMmStat::kOrigDataSizeFieldNumber - kVendorAtomOffset] = tmp;
-        tmp.set<VendorAtomValue::intValue>(compr_data_size);
+        tmp.set<VendorAtomValue::longValue>(compr_data_size);
         values[ZramMmStat::kComprDataSizeFieldNumber - kVendorAtomOffset] = tmp;
-        tmp.set<VendorAtomValue::intValue>(mem_used_total);
+        tmp.set<VendorAtomValue::longValue>(mem_used_total);
         values[ZramMmStat::kMemUsedTotalFieldNumber - kVendorAtomOffset] = tmp;
-        tmp.set<VendorAtomValue::intValue>(same_pages);
+        tmp.set<VendorAtomValue::longValue>(same_pages);
         values[ZramMmStat::kSamePagesFieldNumber - kVendorAtomOffset] = tmp;
-        tmp.set<VendorAtomValue::intValue>(huge_pages);
+        tmp.set<VendorAtomValue::longValue>(huge_pages);
         values[ZramMmStat::kHugePagesFieldNumber - kVendorAtomOffset] = tmp;
 
         // Skip the first data to avoid a big spike in this accumulated value.
         if (prev_huge_pages_since_boot_ == -1)
-            tmp.set<VendorAtomValue::intValue>(0);
+            tmp.set<VendorAtomValue::longValue>(0);
         else
-            tmp.set<VendorAtomValue::intValue>(huge_pages_since_boot - prev_huge_pages_since_boot_);
+            tmp.set<VendorAtomValue::longValue>(huge_pages_since_boot -
+                                                prev_huge_pages_since_boot_);
 
         values[ZramMmStat::kHugePagesSinceBootFieldNumber - kVendorAtomOffset] = tmp;
         prev_huge_pages_since_boot_ = huge_pages_since_boot;
@@ -1012,11 +1028,11 @@
         // Load values array
         std::vector<VendorAtomValue> values(3);
         VendorAtomValue tmp;
-        tmp.set<VendorAtomValue::intValue>(bd_count);
+        tmp.set<VendorAtomValue::longValue>(bd_count);
         values[ZramBdStat::kBdCountFieldNumber - kVendorAtomOffset] = tmp;
-        tmp.set<VendorAtomValue::intValue>(bd_reads);
+        tmp.set<VendorAtomValue::longValue>(bd_reads);
         values[ZramBdStat::kBdReadsFieldNumber - kVendorAtomOffset] = tmp;
-        tmp.set<VendorAtomValue::intValue>(bd_writes);
+        tmp.set<VendorAtomValue::longValue>(bd_writes);
         values[ZramBdStat::kBdWritesFieldNumber - kVendorAtomOffset] = tmp;
 
         // Send vendor atom to IStats HAL
@@ -1523,6 +1539,147 @@
 }
 
 /**
+ * Report audio Offload Effects usage stats duration per day.
+ */
+void SysfsCollector::logOffloadEffectsStats(const std::shared_ptr<IStats> &stats_client) {
+    std::string file_contents;
+    std::vector<int> uuids;
+    std::vector<int> durations;
+
+    if (kOffloadEffectsIdPath == nullptr) {
+        ALOGD("Offload Effects ID Path is not specified");
+        return;
+    }
+
+    if (kOffloadEffectsDurationPath == nullptr) {
+        ALOGD("Offload Effects Duration Path is not specified");
+        return;
+    }
+
+    if (!ReadFileToString(kOffloadEffectsIdPath, &file_contents)) {
+        ALOGD("Unable to read Offload Effect ID path %s", kOffloadEffectsIdPath);
+    } else {
+        std::stringstream file_content_stream(file_contents);
+        int uuid;
+        while (file_content_stream.good() && file_content_stream >> uuid) {
+            uuids.push_back(uuid);
+        }
+    }
+
+    if (!ReadFileToString(kOffloadEffectsDurationPath, &file_contents)) {
+        ALOGD("Unable to read Offload Effect duration path %s", kOffloadEffectsDurationPath);
+    } else {
+        std::stringstream file_content_stream(file_contents);
+        int duration;
+        while (file_content_stream.good() && file_content_stream >> duration) {
+            durations.push_back(duration);
+        }
+    }
+
+    if (durations.size() * 4 != uuids.size()) {
+        ALOGD("ID and duration data does not match: %zu and %zu", durations.size(), uuids.size());
+        return;
+    }
+
+    for (int index = 0; index < durations.size(); index++) {
+        std::vector<VendorAtomValue> values(3);
+        VendorAtomValue tmp;
+        int64_t uuid_msb = ((int64_t)uuids[index * 4] << 32 | uuids[index * 4 + 1]);
+        int64_t uuid_lsb = ((int64_t)uuids[index * 4 + 2] << 32 | uuids[index * 4 + 3]);
+
+        if (uuid_msb == 0 && uuid_lsb == 0) {
+            continue;
+        }
+
+        tmp.set<VendorAtomValue::VendorAtomValue::longValue>(uuid_msb);
+        values[VendorAudioOffloadedEffectStatsReported::kEffectUuidMsbFieldNumber -
+               kVendorAtomOffset] = tmp;
+
+        tmp.set<VendorAtomValue::VendorAtomValue::longValue>(uuid_lsb);
+        values[VendorAudioOffloadedEffectStatsReported::kEffectUuidLsbFieldNumber -
+               kVendorAtomOffset] = tmp;
+
+        tmp.set<VendorAtomValue::intValue>(durations[index]);
+        values[VendorAudioOffloadedEffectStatsReported::kEffectActiveSecondsPerDayFieldNumber -
+               kVendorAtomOffset] = tmp;
+
+        // Send vendor atom to IStats HAL
+        VendorAtom event = {.reverseDomainName = "",
+                            .atomId = PixelAtoms::Atom::kVendorAudioOffloadedEffectStatsReported,
+                            .values = std::move(values)};
+
+        const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
+        if (!ret.isOk()) {
+            ALOGE("Unable to report VendorAudioOffloadedEffectStatsReported at index %d", index);
+        } else {
+            ALOGD("Reported VendorAudioOffloadedEffectStatsReported successfully at index %d",
+                  index);
+        }
+    }
+}
+
+/**
+ * Report bluetooth audio usage stats.
+ * This function will report at most 5 atoms showing different instance stats.
+ */
+void SysfsCollector::logBluetoothAudioUsage(const std::shared_ptr<IStats> &stats_client) {
+    std::string file_contents;
+    std::vector<int> duration_per_codec;
+
+    constexpr int num_codec = 5;
+
+    if (kBluetoothAudioUsagePath == nullptr) {
+        ALOGD("Bluetooth Audio stats path not specified");
+        return;
+    }
+
+    if (!ReadFileToString(kBluetoothAudioUsagePath, &file_contents)) {
+        ALOGD("Unable to read Bluetooth Audio stats path %s", kBluetoothAudioUsagePath);
+    } else {
+        std::stringstream file_content_stream(file_contents);
+        int duration;
+        while (file_content_stream.good() && file_content_stream >> duration) {
+            duration_per_codec.push_back(duration);
+        }
+    }
+
+    if (duration_per_codec.size() != num_codec) {
+        ALOGD("Bluetooth Audio num codec != number of codec. %zu / %d", duration_per_codec.size(),
+              num_codec);
+        return;
+    }
+
+    for (int index = 0; index < num_codec; index++) {
+        std::vector<VendorAtomValue> values(2);
+        VendorAtomValue tmp;
+
+        if (duration_per_codec[index] == 0) {
+            ALOGD("Skipped VendorAudioBtMediaStatsReported at codec:%d", index);
+            continue;
+        }
+
+        tmp.set<VendorAtomValue::intValue>(index);
+        values[VendorAudioBtMediaStatsReported::kBtCodecTypeFieldNumber - kVendorAtomOffset] = tmp;
+
+        tmp.set<VendorAtomValue::intValue>(duration_per_codec[index]);
+        values[VendorAudioBtMediaStatsReported::kActiveSecondsPerDayFieldNumber -
+               kVendorAtomOffset] = tmp;
+
+        // Send vendor atom to IStats HAL
+        VendorAtom event = {.reverseDomainName = "",
+                            .atomId = PixelAtoms::Atom::kVendorAudioBtMediaStatsReported,
+                            .values = std::move(values)};
+
+        const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
+        if (!ret.isOk())
+            ALOGE("Unable to report VendorAudioBtMediaStatsReported at index %d", index);
+        else
+            ALOGD("Reporting VendorAudioBtMediaStatsReported: codec:%d, duration:%d", index,
+                  duration_per_codec[index]);
+    }
+}
+
+/**
  * Logs the Resume Latency stats.
  */
 void SysfsCollector::logVendorResumeLatencyStats(const std::shared_ptr<IStats> &stats_client) {
@@ -1939,6 +2096,8 @@
     logWavesStats(stats_client);
     logAdaptedInfoStats(stats_client);
     logPcmUsageStats(stats_client);
+    logOffloadEffectsStats(stats_client);
+    logBluetoothAudioUsage(stats_client);
 }
 
 void SysfsCollector::aggregatePer5Min() {
@@ -1951,7 +2110,10 @@
         ALOGE("Unable to get AIDL Stats service");
         return;
     }
-    if (kBrownoutLogPath != nullptr && strlen(kBrownoutLogPath) > 0)
+    if (kBrownoutCsvPath != nullptr && strlen(kBrownoutCsvPath) > 0)
+        brownout_detected_reporter_.logBrownoutCsv(stats_client, kBrownoutCsvPath,
+                                                   kBrownoutReasonProp);
+    else if (kBrownoutLogPath != nullptr && strlen(kBrownoutLogPath) > 0)
         brownout_detected_reporter_.logBrownout(stats_client, kBrownoutLogPath,
                                                 kBrownoutReasonProp);
 }
diff --git a/pixelstats/test/TEST_MAPPING b/pixelstats/TEST_MAPPING
similarity index 100%
rename from pixelstats/test/TEST_MAPPING
rename to pixelstats/TEST_MAPPING
diff --git a/pixelstats/UeventListener.cpp b/pixelstats/UeventListener.cpp
index 8af43a0..6ab91f6 100644
--- a/pixelstats/UeventListener.cpp
+++ b/pixelstats/UeventListener.cpp
@@ -45,6 +45,7 @@
 #include <cutils/uevent.h>
 #include <fcntl.h>
 #include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
+#include <linux/thermal.h>
 #include <log/log.h>
 #include <pixelstats/StatsHelper.h>
 #include <pixelstats/UeventListener.h>
@@ -68,6 +69,7 @@
 using android::base::WriteStringToFile;
 using android::hardware::google::pixel::PixelAtoms::GpuEvent;
 using android::hardware::google::pixel::PixelAtoms::PdVidPid;
+using android::hardware::google::pixel::PixelAtoms::ThermalSensorAbnormalityDetected;
 using android::hardware::google::pixel::PixelAtoms::VendorHardwareFailed;
 using android::hardware::google::pixel::PixelAtoms::VendorUsbPortOverheat;
 
@@ -80,6 +82,8 @@
 constexpr int32_t PID_OFFSET = 2;
 constexpr int32_t PID_LENGTH = 4;
 constexpr uint32_t PID_P30 = 0x4f05;
+constexpr const char *THERMAL_ABNORMAL_INFO_EQ = "THERMAL_ABNORMAL_INFO=";
+constexpr const char *THERMAL_ABNORMAL_TYPE_EQ = "THERMAL_ABNORMAL_TYPE=";
 
 bool UeventListener::ReadFileToInt(const std::string &path, int *val) {
     return ReadFileToInt(path.c_str(), val);
@@ -300,6 +304,87 @@
         ALOGE("Unable to report GPU event.");
 }
 
+/**
+ * Report thermal abnormal event.
+ * The data is sent as uevent environment parameters:
+ *      1. THERMAL_ABNORMAL_TYPE={type}
+ *      2. THERMAL_ABNORMAL_INFO=Name:{name},Val:{val}
+ * This atom logs data in 3 potential events:
+ *      1. thermistor or tj temperature reading stuck
+ *      2. thermistor or tj showing very high temperature reading
+ *      3. thermistor or tj showing very low temperature reading
+ */
+void UeventListener::ReportThermalAbnormalEvent(const std::shared_ptr<IStats> &stats_client,
+                                                const char *devpath,
+                                                const char *thermal_abnormal_event_type,
+                                                const char *thermal_abnormal_event_info) {
+    if (!stats_client || !devpath ||
+        strncmp(devpath, "DEVPATH=/module/pixel_metrics",
+                strlen("DEVPATH=/module/pixel_metrics")) ||
+        !thermal_abnormal_event_type || !thermal_abnormal_event_info)
+        return;
+    ALOGD("Thermal Abnormal Type: %s, Thermal Abnormal Info: %s", thermal_abnormal_event_type,
+          thermal_abnormal_event_info);
+    std::vector<std::string> type_msg = android::base::Split(thermal_abnormal_event_type, "=");
+    std::vector<std::string> info_msg = android::base::Split(thermal_abnormal_event_info, "=");
+    if (type_msg.size() != 2 || info_msg.size() != 2) {
+        ALOGE("Invalid msg size for thermal abnormal with type(%zu) and info(%zu)", type_msg.size(),
+              info_msg.size());
+        return;
+    }
+
+    if (type_msg[0] != "THERMAL_ABNORMAL_TYPE" || info_msg[0] != "THERMAL_ABNORMAL_INFO") {
+        ALOGE("Invalid msg prefix for thermal abnormal with type(%s) and info(%s)",
+              type_msg[0].c_str(), info_msg[0].c_str());
+        return;
+    }
+
+    auto abnormality_type = kThermalAbnormalityTypeStrToEnum.find(type_msg[1]);
+    if (abnormality_type == kThermalAbnormalityTypeStrToEnum.end()) {
+        ALOGE("Unknown thermal abnormal event type %s", type_msg[1].c_str());
+        return;
+    }
+
+    std::vector<std::string> info_list = android::base::Split(info_msg[1], ",");
+    if (info_list.size() != 2) {
+        ALOGE("Thermal abnormal info(%s) split size %zu != 2", info_msg[1].c_str(),
+              info_list.size());
+        return;
+    }
+
+    const auto &name_msg = info_list[0], val_msg = info_list[1];
+    if (!android::base::StartsWith(name_msg, "name:") ||
+        !android::base::StartsWith(val_msg, "val:")) {
+        ALOGE("Invalid prefix for thermal abnormal info name(%s), val(%s)", name_msg.c_str(),
+              val_msg.c_str());
+        return;
+    }
+
+    auto name_start_pos = std::strlen("name:");
+    auto name = name_msg.substr(name_start_pos);
+    if (name.length() > THERMAL_NAME_LENGTH) {
+        ALOGE("Invalid sensor name %s with length %zu > %d", name.c_str(), name.length(),
+              THERMAL_NAME_LENGTH);
+        return;
+    }
+
+    auto val_start_pos = std::strlen("val:");
+    auto val_str = val_msg.substr(val_start_pos);
+    int val;
+    if (sscanf(val_str.c_str(), "%d", &val) != 1) {
+        ALOGE("Invalid value for thermal abnormal info: %s", val_str.c_str());
+        return;
+    }
+    ALOGI("Reporting Thermal Abnormal event of type: %s(%d) for %s with val: %d",
+          abnormality_type->first.c_str(), abnormality_type->second, name.c_str(), val);
+    VendorAtom event = {.reverseDomainName = "",
+                        .atomId = PixelAtoms::Atom::kThermalSensorAbnormalityDetected,
+                        .values = {abnormality_type->second, name, val}};
+    const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
+    if (!ret.isOk())
+        ALOGE("Unable to report Thermal Abnormal event.");
+}
+
 bool UeventListener::ProcessUevent() {
     char msg[UEVENT_MSG_LEN + 2];
     char *cp;
@@ -308,6 +393,7 @@
     const char *devpath;
     bool collect_partner_id = false;
     const char *gpu_event_type = nullptr, *gpu_event_info = nullptr;
+    const char *thermal_abnormal_event_type = nullptr, *thermal_abnormal_event_info = nullptr;
     int n;
 
     if (uevent_fd_ < 0) {
@@ -367,6 +453,10 @@
             gpu_event_type = cp;
         } else if (!strncmp(cp, "GPU_UEVENT_INFO=", strlen("GPU_UEVENT_INFO="))) {
             gpu_event_info = cp;
+        } else if (!strncmp(cp, THERMAL_ABNORMAL_TYPE_EQ, strlen(THERMAL_ABNORMAL_TYPE_EQ))) {
+            thermal_abnormal_event_type = cp;
+        } else if (!strncmp(cp, THERMAL_ABNORMAL_INFO_EQ, strlen(THERMAL_ABNORMAL_INFO_EQ))) {
+            thermal_abnormal_event_info = cp;
         }
         /* advance to after the next \0 */
         while (*cp++) {
@@ -387,6 +477,8 @@
             ReportTypeCPartnerId(stats_client);
         }
         ReportGpuEvent(stats_client, driver, gpu_event_type, gpu_event_info);
+        ReportThermalAbnormalEvent(stats_client, devpath, thermal_abnormal_event_type,
+                                   thermal_abnormal_event_info);
     }
 
     if (log_fd_ > 0) {
diff --git a/pixelstats/include/pixelstats/BatteryEEPROMReporter.h b/pixelstats/include/pixelstats/BatteryEEPROMReporter.h
index 13a203b..4157712 100644
--- a/pixelstats/include/pixelstats/BatteryEEPROMReporter.h
+++ b/pixelstats/include/pixelstats/BatteryEEPROMReporter.h
@@ -45,6 +45,9 @@
   public:
     BatteryEEPROMReporter();
     void checkAndReport(const std::shared_ptr<IStats> &stats_client, const std::string &path);
+    void checkAndReportGMSR(const std::shared_ptr<IStats> &stats_client, const std::string &path);
+    void checkAndReportMaxfgHistory(const std::shared_ptr<IStats> &stats_client,
+                                    const std::string &path);
 
   private:
     // Proto messages are 1-indexed and VendorAtom field numbers start at 2, so
@@ -112,7 +115,7 @@
     /* The number of elements in struct BatteryHistory for P20 series */
     const int kNumBatteryHistoryFields = 19;
 
-    /* P21 history format */
+    /* P21+ history format */
     struct BatteryHistoryExtend {
         uint16_t tempco;
         uint16_t rcomp0;
@@ -135,6 +138,10 @@
     bool checkLogEvent(struct BatteryHistory hist);
     void reportEvent(const std::shared_ptr<IStats> &stats_client,
                      const struct BatteryHistory &hist);
+
+    const int kNum77759GMSRFields = 11;
+    const int kNum77779GMSRFields = 5;
+    const int kNum17201HISTFields = 16;
 };
 
 }  // namespace pixel
diff --git a/pixelstats/include/pixelstats/BrownoutDetectedReporter.h b/pixelstats/include/pixelstats/BrownoutDetectedReporter.h
index 5f460d9..d643776 100644
--- a/pixelstats/include/pixelstats/BrownoutDetectedReporter.h
+++ b/pixelstats/include/pixelstats/BrownoutDetectedReporter.h
@@ -34,6 +34,39 @@
 
 #define ODPM_MAX_IDX 24
 #define DVFS_MAX_IDX 6
+
+enum CsvIdx {
+    TIMESTAMP_IDX,
+    IRQ_IDX,
+    SOC_IDX,
+    TEMP_IDX,
+    CYCLE_IDX,
+    VOLTAGE_IDX,
+    CURRENT_IDX,
+    DVFS_CHANNEL_0 = 7,
+    ODPM_CHANNEL_0 = 12,
+};
+
+enum Irq {
+    SMPL_WARN,
+    OCP_WARN_CPUCL1,
+    OCP_WARN_CPUCL2,
+    SOFT_OCP_WARN_CPUCL1,
+    SOFT_OCP_WARN_CPUCL2,
+    OCP_WARN_TPU,
+    SOFT_OCP_WARN_TPU,
+    OCP_WARN_GPU,
+    SOFT_OCP_WARN_GPU,
+    PMIC_SOC,
+    UVLO1,
+    UVLO2,
+    BATOILO,
+    BATOILO2,
+    PMIC_120C,
+    PMIC_140C,
+    PMIC_OVERHEAT,
+};
+
 enum Update { kUpdateMax, kUpdateMin };
 
 /**
@@ -43,6 +76,9 @@
   public:
     void logBrownout(const std::shared_ptr<IStats> &stats_client, const std::string &logFilePath,
                      const std::string &brownoutReasonProp);
+    void logBrownoutCsv(const std::shared_ptr<IStats> &stats_client, const std::string &logFilePath,
+                        const std::string &brownoutReasonProp);
+    int brownoutReasonCheck(const std::string &brownoutReasonProp);
 
   private:
     struct BrownoutDetectedInfo {
diff --git a/pixelstats/include/pixelstats/ChargeStatsReporter.h b/pixelstats/include/pixelstats/ChargeStatsReporter.h
index 62c3abc..69b6eb9 100644
--- a/pixelstats/include/pixelstats/ChargeStatsReporter.h
+++ b/pixelstats/include/pixelstats/ChargeStatsReporter.h
@@ -60,6 +60,8 @@
             "/sys/devices/platform/google,charger/thermal_stats";
 
     const std::string kGChargerMetricsPath = "/sys/devices/platform/google,charger/charge_stats";
+
+    const std::string kGDualBattMetricsPath = "/sys/class/power_supply/dualbatt/dbatt_stats";
 };
 
 }  // namespace pixel
diff --git a/pixelstats/include/pixelstats/StatsHelper.h b/pixelstats/include/pixelstats/StatsHelper.h
index 0139549..f936677 100644
--- a/pixelstats/include/pixelstats/StatsHelper.h
+++ b/pixelstats/include/pixelstats/StatsHelper.h
@@ -50,6 +50,8 @@
 void reportSpeakerHealthStat(const std::shared_ptr<IStats> &stats_client,
                              const PixelAtoms::VendorSpeakerStatsReported &speakerHealthStat);
 
+void reportUsbDataSessionEvent(const std::shared_ptr<IStats> &stats_client,
+                               const PixelAtoms::VendorUsbDataSessionEvent &usb_session);
 }  // namespace pixel
 }  // namespace google
 }  // namespace hardware
diff --git a/pixelstats/include/pixelstats/SysfsCollector.h b/pixelstats/include/pixelstats/SysfsCollector.h
index 494acd7..e60842a 100644
--- a/pixelstats/include/pixelstats/SysfsCollector.h
+++ b/pixelstats/include/pixelstats/SysfsCollector.h
@@ -62,6 +62,7 @@
         const char *const EEPROMPath;
         const char *const MitigationPath;
         const char *const MitigationDurationPath;
+        const char *const BrownoutCsvPath;
         const char *const BrownoutLogPath;
         const char *const BrownoutReasonProp;
         const char *const SpeakerTemperaturePath;
@@ -87,6 +88,10 @@
         const char *const PcmLatencyPath;
         const char *const PcmCountPath;
         const char *const TotalCallCountPath;
+        const char *const OffloadEffectsIdPath;
+        const char *const OffloadEffectsDurationPath;
+        const char *const BluetoothAudioUsagePath;
+        const char *const GMSRPath;
     };
 
     SysfsCollector(const struct SysfsPaths &paths);
@@ -140,6 +145,9 @@
     void logWavesStats(const std::shared_ptr<IStats> &stats_client);
     void logAdaptedInfoStats(const std::shared_ptr<IStats> &stats_client);
     void logPcmUsageStats(const std::shared_ptr<IStats> &stats_client);
+    void logOffloadEffectsStats(const std::shared_ptr<IStats> &stats_client);
+    void logBluetoothAudioUsage(const std::shared_ptr<IStats> &stats_client);
+    void logBatteryGMSR(const std::shared_ptr<IStats> &stats_client);
 
     const char *const kSlowioReadCntPath;
     const char *const kSlowioWriteCntPath;
@@ -159,6 +167,7 @@
     const char *const kZramMmStatPath;
     const char *const kZramBdStatPath;
     const char *const kEEPROMPath;
+    const char *const kBrownoutCsvPath;
     const char *const kBrownoutLogPath;
     const char *const kBrownoutReasonProp;
     const char *const kPowerMitigationStatsPath;
@@ -186,6 +195,11 @@
     const char *const kPcmLatencyPath;
     const char *const kPcmCountPath;
     const char *const kTotalCallCountPath;
+    const char *const kOffloadEffectsIdPath;
+    const char *const kOffloadEffectsDurationPath;
+    const char *const kBluetoothAudioUsagePath;
+    const char *const kGMSRPath;
+    const char *const kMaxfgHistoryPath;
 
     BatteryEEPROMReporter battery_EEPROM_reporter_;
     MmMetricsReporter mm_metrics_reporter_;
diff --git a/pixelstats/include/pixelstats/UeventListener.h b/pixelstats/include/pixelstats/UeventListener.h
index 352beb8..6e6f817 100644
--- a/pixelstats/include/pixelstats/UeventListener.h
+++ b/pixelstats/include/pixelstats/UeventListener.h
@@ -28,7 +28,6 @@
 namespace pixel {
 
 using aidl::android::frameworks::stats::IStats;
-
 /**
  * A class to listen for uevents and report reliability events to
  * the PixelStats HAL.
@@ -47,6 +46,7 @@
         const char *const TypeCPartnerPidPath;
         const char *const WirelessChargerPtmcUevent;  // Deprecated.
         const char *const WirelessChargerPtmcPath;    // Deprecated.
+        const char *const GMSRPath;
     };
     constexpr static const char *const ssoc_details_path =
             "/sys/class/power_supply/battery/ssoc_details";
@@ -59,6 +59,7 @@
     constexpr static const char *const typec_partner_pid_path_default =
             "/sys/class/typec/port0-partner/identity/product";
     constexpr static const char *const typec_partner_uevent_default = "DEVTYPE=typec_partner";
+    constexpr static const char *const gmsr_path = "";
 
     UeventListener(const std::string audio_uevent, const std::string ssoc_details_path = "",
                    const std::string overheat_path = overheat_path_default,
@@ -90,7 +91,9 @@
     void ReportTypeCPartnerId(const std::shared_ptr<IStats> &stats_client);
     void ReportGpuEvent(const std::shared_ptr<IStats> &stats_client, const char *driver,
                         const char *gpu_event_type, const char *gpu_event_info);
-
+    void ReportThermalAbnormalEvent(const std::shared_ptr<IStats> &stats_client,
+                                    const char *devpath, const char *thermal_abnormal_event_type,
+                                    const char *thermal_abnormal_event_info);
     const std::string kAudioUevent;
     const std::string kBatterySSOCPath;
     const std::string kUsbPortOverheatPath;
@@ -98,6 +101,7 @@
     const std::string kTypeCPartnerUevent;
     const std::string kTypeCPartnerVidPath;
     const std::string kTypeCPartnerPidPath;
+    const std::string kBatteryGMSRPath;
 
     const std::unordered_map<std::string, PixelAtoms::GpuEvent::GpuEventType>
             kGpuEventTypeStrToEnum{
@@ -140,6 +144,28 @@
                     {"CSF_RESET_FAILED", PixelAtoms::GpuEvent::GpuEventInfo::
                                                  GpuEvent_GpuEventInfo_MALI_CSF_RESET_FAILED}};
 
+    const std::unordered_map<std::string,
+                             PixelAtoms::ThermalSensorAbnormalityDetected::AbnormalityType>
+            kThermalAbnormalityTypeStrToEnum{
+                    {"UNKNOWN", PixelAtoms::ThermalSensorAbnormalityDetected::AbnormalityType::
+                                        ThermalSensorAbnormalityDetected_AbnormalityType_UNKNOWN},
+                    {"SENSOR_STUCK",
+                     PixelAtoms::ThermalSensorAbnormalityDetected::AbnormalityType::
+                             ThermalSensorAbnormalityDetected_AbnormalityType_SENSOR_STUCK},
+                    {"EXTREME_HIGH_TEMP",
+                     PixelAtoms::ThermalSensorAbnormalityDetected::AbnormalityType::
+                             ThermalSensorAbnormalityDetected_AbnormalityType_EXTREME_HIGH_TEMP},
+                    {"EXTREME_LOW_TEMP",
+                     PixelAtoms::ThermalSensorAbnormalityDetected::AbnormalityType::
+                             ThermalSensorAbnormalityDetected_AbnormalityType_EXTREME_LOW_TEMP},
+                    {"HIGH_RISING_SPEED",
+                     PixelAtoms::ThermalSensorAbnormalityDetected::AbnormalityType::
+                             ThermalSensorAbnormalityDetected_AbnormalityType_HIGH_RISING_SPEED},
+                    {"TEMP_READ_FAIL",
+                     PixelAtoms::ThermalSensorAbnormalityDetected::AbnormalityType::
+                             ThermalSensorAbnormalityDetected_AbnormalityType_TEMP_READ_FAIL},
+            };
+
     BatteryCapacityReporter battery_capacity_reporter_;
     ChargeStatsReporter charge_stats_reporter_;
 
diff --git a/pixelstats/pixelatoms.proto b/pixelstats/pixelatoms.proto
index 3f5e76f..c773c2b 100644
--- a/pixelstats/pixelatoms.proto
+++ b/pixelstats/pixelatoms.proto
@@ -112,6 +112,10 @@
       VendorAudioAdaptedInfoStatsReported vendor_audio_adapted_info_stats_reported = 105058;
       GpuEvent gpu_event = 105059;
       VendorAudioPcmStatsReported vendor_audio_pcm_stats_reported = 105060;
+      VendorUsbDataSessionEvent vendor_usb_data_session_event = 105061;
+      ThermalSensorAbnormalityDetected thermal_sensor_abnormality_detected = 105062;
+      VendorAudioOffloadedEffectStatsReported vendor_audio_offloaded_effect_stats_reported = 105063;
+      VendorAudioBtMediaStatsReported vendor_audio_bt_media_stats_reported = 105064;
     }
     // AOSP atom ID range ends at 109999
     reserved 109997; // reserved for VtsVendorAtomJavaTest test atom
@@ -1514,6 +1518,7 @@
         SMPL_WARN = 1;
         UVLO2 = 2;
         BATOILO = 3;
+        BATOILO2 = 4;
     }
 
     enum BrownoutReason {
@@ -1990,3 +1995,111 @@
   /* Total active count of the pcm type per day. */
   optional int32 pcm_active_counts_per_day = 4;
 }
+
+/**
+  * Keep track of information about a USB data session, which is defined
+  * as the period when a port enters a data role (either host or device) to
+  * when the port exits the data role.
+  */
+message VendorUsbDataSessionEvent {
+  /* Vendor reverse domain name (expecting "com.google.pixel") */
+  optional string reverse_domain_name = 1;
+
+  enum UsbDataRole {
+    USB_ROLE_UNKNOWN = 0;
+    USB_ROLE_DEVICE = 1;
+    USB_ROLE_HOST = 2;
+  }
+  /**
+    * USB device states are key milestones in a USB connection.
+    * For device data role, a typical transition would be like:
+    * not attached -> default -> addressed -> configured.
+    * For host data role, a typical transition would be like
+    * not attached -> powered -> default -> addressed -> configured.
+    */
+  enum UsbDeviceState {
+    USB_STATE_UNKNOWN = 0;
+    USB_STATE_NOT_ATTACHED = 1;
+    USB_STATE_ATTACHED = 2;
+    USB_STATE_POWERED = 3;
+    USB_STATE_DEFAULT = 4;
+    USB_STATE_ADDRESSED = 5;
+    USB_STATE_CONFIGURED = 6;
+    USB_STATE_SUSPENDED = 7;
+  }
+  /* USB data role of the data session. */
+  optional UsbDataRole usb_role = 2;
+  /* Usb device state transitions during the data session. */
+  repeated UsbDeviceState usb_states = 3;
+  /**
+    * Elapsed time from the start of the data session when entering the
+    * state, mapped 1-1 to the usb_states field.
+    */
+  repeated int64 elapsed_time_ms = 4;
+  // Duration of the data session.
+  optional int64 duration_ms = 5;
+}
+
+/*
+ * Logs the thermal sensor abnormal event when detected.
+ * Logged from:
+ *    virtual sensors: hardware/google/pixel/thermal/utils/thermal_stats_helper.cpp
+ *    thermistors & SoC: hardware/google/pixel/pixelstats/UeventListener.cpp
+ */
+message ThermalSensorAbnormalityDetected {
+  enum AbnormalityType {
+    UNKNOWN = 0;
+    SENSOR_STUCK = 1;
+    EXTREME_HIGH_TEMP = 2;
+    EXTREME_LOW_TEMP = 3;
+    HIGH_RISING_SPEED = 4;
+    TEMP_READ_FAIL = 5;
+  }
+
+  /* Vendor reverse domain name */
+  optional string reverse_domain_name = 1;
+  /* Type of Thermal Sensor Abnormality */
+  optional AbnormalityType type = 2;
+  /* Name of the problematic sensor */
+  optional string sensor = 3;
+  /* Abnormal temp reading of sensor */
+  optional int32 temp = 4;
+}
+
+/**
+ * Logs the reported vendor audio offloaded effects usage stats.
+ */
+message VendorAudioOffloadedEffectStatsReported {
+  /* Vendor reverse domain name */
+  optional string reverse_domain_name = 1;
+
+  /* UUID most significant bit */
+  optional int64 effect_uuid_msb = 2;
+
+  /* UUID least significant bit */
+  optional int64 effect_uuid_lsb = 3;
+
+  /* Active seconds per day. */
+  optional int32 effect_active_seconds_per_day = 4;
+}
+
+/*
+ * Logs the Bluetooth Audio stats.
+ * Two stats are recorded, count and duration (in ms) per features.
+ */
+message VendorAudioBtMediaStatsReported {
+  /* Vendor reverse domain name */
+  optional string reverse_domain_name = 1;
+
+  enum Codec {
+    UNKNOWN = 0;
+    SBC = 1;
+    AAC = 2;
+    OPUS = 3;
+    LC3 = 4;
+  }
+  /* Codec to record. */
+  optional Codec bt_codec_type = 2;
+  /* Total active seconds to record. */
+  optional int32 active_seconds_per_day = 3;
+}
diff --git a/power-libperfmgr/Android.bp b/power-libperfmgr/Android.bp
index 2163954..9ccd6fd 100644
--- a/power-libperfmgr/Android.bp
+++ b/power-libperfmgr/Android.bp
@@ -24,6 +24,7 @@
         "disp-power/DisplayLowPower.cpp",
         "disp-power/InteractionHandler.cpp",
     ],
+    cpp_std: "gnu++20",
     shared_libs: [
         "libbase",
         "libcutils",
@@ -41,14 +42,49 @@
     export_include_dirs: ["hidl"],
 }
 
+cc_test {
+    name: "libadpf_test",
+    defaults: ["android.hardware.power-ndk_static"],
+    proprietary: true,
+    vendor: true,
+    require_root: true,
+    srcs: [
+        "aidl/tests/BackgroundWorkerTest.cpp",
+        "aidl/tests/PowerHintSessionTest.cpp",
+        "aidl/tests/SessionTaskMapTest.cpp",
+        "aidl/tests/UClampVoterTest.cpp",
+        "aidl/BackgroundWorker.cpp",
+        "aidl/PowerHintSession.cpp",
+        "aidl/PowerSessionManager.cpp",
+        "aidl/SessionTaskMap.cpp",
+        "aidl/SessionValueEntry.cpp",
+        "aidl/UClampVoter.cpp",
+    ],
+    cpp_std: "gnu++20",
+    static_libs: [
+        "libgmock",
+    ],
+    shared_libs: [
+        "liblog",
+        "libbase",
+        "libcutils",
+        "libutils",
+        "libperfmgr",
+        "libbinder_ndk",
+        "libprocessgroup",
+        "pixel-power-ext-V1-ndk",
+    ],
+    test_suites: ["device-tests"],
+}
+
 cc_binary {
     name: "android.hardware.power-service.pixel-libperfmgr",
+    defaults: ["android.hardware.power-ndk_shared"],
     relative_install_path: "hw",
     init_rc: ["aidl/android.hardware.power-service.pixel-libperfmgr.rc"],
     vintf_fragments: ["aidl/android.hardware.power-service.pixel.xml"],
     vendor: true,
     shared_libs: [
-        "android.hardware.power-V4-ndk",
         "libbase",
         "libcutils",
         "liblog",
@@ -58,22 +94,26 @@
         "libperfmgr",
         "libprocessgroup",
         "pixel-power-ext-V1-ndk",
-        "libprotobuf-cpp-full",
     ],
     srcs: [
+        "aidl/BackgroundWorker.cpp",
         "aidl/service.cpp",
         "aidl/Power.cpp",
         "aidl/PowerExt.cpp",
         "aidl/PowerHintSession.cpp",
         "aidl/PowerSessionManager.cpp",
+        "aidl/UClampVoter.cpp",
+        "aidl/SessionTaskMap.cpp",
+        "aidl/SessionValueEntry.cpp",
     ],
+    cpp_std: "gnu++20",
 }
 
 cc_binary {
     name: "sendhint",
+    defaults: ["android.hardware.power-ndk_shared"],
     vendor: true,
     shared_libs: [
-        "android.hardware.power-V3-ndk",
         "libbase",
         "libcutils",
         "liblog",
diff --git a/power-libperfmgr/aidl/AdpfTypes.h b/power-libperfmgr/aidl/AdpfTypes.h
new file mode 100644
index 0000000..bd9dbc9
--- /dev/null
+++ b/power-libperfmgr/aidl/AdpfTypes.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+enum class AdpfErrorCode : int32_t { ERR_OK = 0, ERR_BAD_STATE = -1, ERR_BAD_ARG = -2 };
+
+enum class AdpfHintType : int32_t {
+    ADPF_VOTE_DEFAULT = 1,
+    ADPF_CPU_LOAD_UP = 2,
+    ADPF_CPU_LOAD_RESET = 3,
+    ADPF_CPU_LOAD_RESUME = 4,
+    ADPF_VOTE_POWER_EFFICIENCY = 5
+};
+
+constexpr int kUclampMin{0};
+constexpr int kUclampMax{1024};
+
+}  // namespace pixel
+}  // namespace impl
+}  // namespace power
+}  // namespace hardware
+}  // namespace google
+}  // namespace aidl
diff --git a/power-libperfmgr/aidl/AppDescriptorTrace.h b/power-libperfmgr/aidl/AppDescriptorTrace.h
new file mode 100644
index 0000000..1f09269
--- /dev/null
+++ b/power-libperfmgr/aidl/AppDescriptorTrace.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/power/SessionMode.h>
+#include <android-base/stringprintf.h>
+
+#include <string>
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+template <class T>
+constexpr size_t enum_size() {
+    return static_cast<size_t>(*(ndk::enum_range<T>().end() - 1)) + 1;
+}
+
+// The App Hint Descriptor struct manages information necessary
+// to calculate the next uclamp min value from the PID function
+// and is separate so that it can be used as a pointer for
+// easily passing to the pid function
+struct AppDescriptorTrace {
+    AppDescriptorTrace(const std::string &idString) {
+        using ::android::base::StringPrintf;
+        trace_pid_err = StringPrintf("adpf.%s-%s", idString.c_str(), "pid.err");
+        trace_pid_integral = StringPrintf("adpf.%s-%s", idString.c_str(), "pid.integral");
+        trace_pid_derivative = StringPrintf("adpf.%s-%s", idString.c_str(), "pid.derivative");
+        trace_pid_pOut = StringPrintf("adpf.%s-%s", idString.c_str(), "pid.pOut");
+        trace_pid_iOut = StringPrintf("adpf.%s-%s", idString.c_str(), "pid.iOut");
+        trace_pid_dOut = StringPrintf("adpf.%s-%s", idString.c_str(), "pid.dOut");
+        trace_pid_output = StringPrintf("adpf.%s-%s", idString.c_str(), "pid.output");
+        trace_target = StringPrintf("adpf.%s-%s", idString.c_str(), "target");
+        trace_active = StringPrintf("adpf.%s-%s", idString.c_str(), "active");
+        trace_add_threads = StringPrintf("adpf.%s-%s", idString.c_str(), "add_threads");
+        trace_actl_last = StringPrintf("adpf.%s-%s", idString.c_str(), "act_last");
+        trace_min = StringPrintf("adpf.%s-%s", idString.c_str(), "min");
+        trace_batch_size = StringPrintf("adpf.%s-%s", idString.c_str(), "batch_size");
+        trace_hint_count = StringPrintf("adpf.%s-%s", idString.c_str(), "hint_count");
+        trace_hint_overtime = StringPrintf("adpf.%s-%s", idString.c_str(), "hint_overtime");
+        trace_is_first_frame = StringPrintf("adpf.%s-%s", idString.c_str(), "is_first_frame");
+        trace_session_hint = StringPrintf("adpf.%s-%s", idString.c_str(), "session_hint");
+        for (size_t i = 0; i < trace_modes.size(); ++i) {
+            trace_modes[i] = StringPrintf(
+                    "adpf.%s-%s_mode", idString.c_str(),
+                    toString(static_cast<aidl::android::hardware::power::SessionMode>(i)).c_str());
+        }
+    }
+
+    // Trace values
+    std::string trace_pid_err;
+    std::string trace_pid_integral;
+    std::string trace_pid_derivative;
+    std::string trace_pid_pOut;
+    std::string trace_pid_iOut;
+    std::string trace_pid_dOut;
+    std::string trace_pid_output;
+    std::string trace_target;
+    std::string trace_active;
+    std::string trace_add_threads;
+    std::string trace_actl_last;
+    std::string trace_min;
+    std::string trace_batch_size;
+    std::string trace_hint_count;
+    std::string trace_hint_overtime;
+    std::string trace_is_first_frame;
+    std::string trace_session_hint;
+    std::array<std::string, enum_size<aidl::android::hardware::power::SessionMode>()> trace_modes;
+};
+
+}  // namespace pixel
+}  // namespace impl
+}  // namespace power
+}  // namespace hardware
+}  // namespace google
+}  // namespace aidl
diff --git a/power-libperfmgr/aidl/BackgroundWorker.cpp b/power-libperfmgr/aidl/BackgroundWorker.cpp
new file mode 100644
index 0000000..7e86f70
--- /dev/null
+++ b/power-libperfmgr/aidl/BackgroundWorker.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2023 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 "powerhal-libperfmgr"
+#define ATRACE_TAG (ATRACE_TAG_POWER | ATRACE_TAG_HAL)
+
+#include "BackgroundWorker.h"
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+PriorityQueueWorkerPool::PriorityQueueWorkerPool(size_t threadCount,
+                                                 const std::string &threadNamePrefix) {
+    mRunning = true;
+    mThreadPool.reserve(threadCount);
+    for (size_t threadId = 0; threadId < threadCount; ++threadId) {
+        mThreadPool.push_back(std::thread([&, tid = threadId]() { loop(); }));
+
+        if (!threadNamePrefix.empty()) {
+            const std::string fullThreadName = threadNamePrefix + std::to_string(threadId);
+            pthread_setname_np(mThreadPool.back().native_handle(), fullThreadName.c_str());
+        }
+    }
+}
+
+PriorityQueueWorkerPool::~PriorityQueueWorkerPool() {
+    {
+        std::lock_guard<std::mutex> lock(mMutex);
+        mRunning = false;
+        mCv.notify_all();
+    }
+    for (auto &t : mThreadPool) {
+        if (t.joinable()) {
+            t.join();
+        }
+    }
+}
+
+void PriorityQueueWorkerPool::addCallback(int64_t templateQueueWorkerId,
+                                          std::function<void(int64_t)> callback) {
+    if (!callback) {
+        // Don't add callback if it isn't callable to prevent having to check later
+        return;
+    }
+    std::unique_lock<std::shared_mutex> lock(mSharedMutex);
+    auto itr = mCallbackMap.find(templateQueueWorkerId);
+    if (itr != mCallbackMap.end()) {
+        return;
+    }
+    mCallbackMap[templateQueueWorkerId] = callback;
+}
+
+void PriorityQueueWorkerPool::removeCallback(int64_t templateQueueWorkerId) {
+    std::unique_lock<std::shared_mutex> lock(mSharedMutex);
+    auto itr = mCallbackMap.find(templateQueueWorkerId);
+    if (itr == mCallbackMap.end()) {
+        return;
+    }
+    mCallbackMap.erase(itr);
+}
+
+void PriorityQueueWorkerPool::schedule(int64_t templateQueueWorkerId, int64_t packageId,
+                                       std::chrono::steady_clock::time_point deadline) {
+    std::unique_lock<std::mutex> lock(mMutex);
+    mPackageQueue.emplace(deadline, templateQueueWorkerId, packageId);
+    mCv.notify_all();
+}
+
+void PriorityQueueWorkerPool::loop() {
+    Package package;
+    while (mRunning) {
+        std::unique_lock<std::mutex> lock(mMutex);
+        // Default to longest wait possible without overflowing if there is
+        // nothing to work on in the queue
+        std::chrono::steady_clock::time_point deadline =
+                std::chrono::steady_clock::time_point::max();
+
+        // Use next item to work on deadline if available
+        if (!mPackageQueue.empty()) {
+            deadline = mPackageQueue.top().deadline;
+        }
+
+        // Wait until signal or deadline
+        mCv.wait_until(lock, deadline, [&]() {
+            // Check if stop running requested, if so return now
+            if (!mRunning)
+                return true;
+
+            // Check if nothing in queue (e.g. spurious wakeup), wait as long as possible again
+            if (mPackageQueue.empty()) {
+                deadline = std::chrono::steady_clock::time_point::max();
+                return false;
+            }
+
+            // Something in queue, use that as next deadline
+            deadline = mPackageQueue.top().deadline;
+            // Check if deadline is in the future still, continue waiting with updated deadline
+            if (deadline > std::chrono::steady_clock::now())
+                return false;
+            // Next work entry's deadline is in the past or exactly now, time to work on it
+            return true;
+        });
+
+        if (!mRunning)
+            break;
+        if (mPackageQueue.empty())
+            continue;
+
+        // Copy work entry from queue and unlock
+        package = mPackageQueue.top();
+        mPackageQueue.pop();
+        lock.unlock();
+
+        // Find callback based on package's callback id
+        {
+            std::shared_lock<std::shared_mutex> lockCb(mSharedMutex);
+            auto callbackItr = mCallbackMap.find(package.templateQueueWorkerId);
+            if (callbackItr == mCallbackMap.end()) {
+                // Callback was removed before package could be worked on, that's ok just ignore
+                continue;
+            }
+            // Exceptions disabled so no need to wrap this
+            callbackItr->second(package.packageId);
+        }
+    }
+}
+
+}  // namespace pixel
+}  // namespace impl
+}  // namespace power
+}  // namespace hardware
+}  // namespace google
+}  // namespace aidl
diff --git a/power-libperfmgr/aidl/BackgroundWorker.h b/power-libperfmgr/aidl/BackgroundWorker.h
new file mode 100644
index 0000000..5d070e1
--- /dev/null
+++ b/power-libperfmgr/aidl/BackgroundWorker.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+#include <queue>
+#include <shared_mutex>
+#include <string>
+#include <thread>
+#include <unordered_map>
+
+#include "AdpfTypes.h"
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+// Background thread processing from priority queue based on time deadline
+// This class isn't meant to be used directly, use TemplatePriorityQueueWorker below
+class PriorityQueueWorkerPool {
+  public:
+    // CTOR
+    // thread count is number of threads to create in thread pool
+    // thread name prefix is use for naming threads to help with debugging
+    PriorityQueueWorkerPool(size_t threadCount, const std::string &threadNamePrefix);
+    // DTOR
+    ~PriorityQueueWorkerPool();
+    // Map callback id to callback function
+    void addCallback(int64_t templateQueueWorkerId, std::function<void(int64_t)> callback);
+    // Unmap callback id with callback function
+    void removeCallback(int64_t templateQueueWorkerId);
+    // Schedule work for specific worker id with package id to be run at time deadline
+    void schedule(int64_t templateQueueWorkerId, int64_t packageId,
+                  std::chrono::steady_clock::time_point deadline);
+
+  private:
+    // Thread coordination
+    std::mutex mMutex;
+    bool mRunning;
+    std::condition_variable mCv;
+    std::vector<std::thread> mThreadPool;
+    void loop();
+
+    // Work package with worker id to find correct callback in
+    struct Package {
+        Package() {}
+        Package(std::chrono::steady_clock::time_point pDeadline, int64_t pTemplateQueueWorkerId,
+                int64_t pPackageId)
+            : deadline(pDeadline),
+              templateQueueWorkerId(pTemplateQueueWorkerId),
+              packageId(pPackageId) {}
+        std::chrono::steady_clock::time_point deadline;
+        int64_t templateQueueWorkerId{0};
+        int64_t packageId{0};
+        // Sort time earliest first
+        bool operator<(const Package &p) const { return deadline > p.deadline; }
+    };
+    std::priority_queue<Package> mPackageQueue;
+
+    // Callback management
+    std::shared_mutex mSharedMutex;
+    std::unordered_map<int64_t, std::function<void(int64_t)>> mCallbackMap;
+};
+
+// Generic templated worker for registering a single std::function callback one time
+// and reusing it to reduce memory allocations. Many TemplatePriorityQueueWorkers
+// can make use of the same PriorityQueue worker which enables sharing a thread pool
+// across callbacks of different types. This class is a template to allow for different
+// types of work packages while not requiring virtual calls.
+template <typename PACKAGE>
+class TemplatePriorityQueueWorker {
+  public:
+    // CTOR, callback to run when added work is run, worker to use for adding work to
+    TemplatePriorityQueueWorker(std::function<void(const PACKAGE &)> cb,
+                                std::shared_ptr<PriorityQueueWorkerPool> worker)
+        : mCallbackId(reinterpret_cast<std::intptr_t>(this)), mCallback(cb), mWorker(worker) {
+        if (!mCallback) {
+            mCallback = [](const auto &) {};
+        }
+        mWorker->addCallback(mCallbackId, [&](int64_t packageId) { process(packageId); });
+    }
+
+    // DTOR
+    ~TemplatePriorityQueueWorker() { mWorker->removeCallback(mCallbackId); }
+
+    void schedule(const PACKAGE &package,
+                  std::chrono::steady_clock::time_point t = std::chrono::steady_clock::now()) {
+        {
+            std::lock_guard<std::mutex> lock(mMutex);
+            ++mPackageIdCounter;
+            mPackages.emplace(mPackageIdCounter, package);
+        }
+        mWorker->schedule(mCallbackId, mPackageIdCounter, t);
+    }
+
+  private:
+    int64_t mCallbackId{0};
+    std::function<void(const PACKAGE &)> mCallback;
+    // Must ensure PriorityQueueWorker does not go out of scope before this class does
+    std::shared_ptr<PriorityQueueWorkerPool> mWorker;
+    mutable std::mutex mMutex;
+    // Counter is used as a unique identifier for work packages
+    int64_t mPackageIdCounter{0};
+    // Want a container that is:
+    // fast to add, fast random find find, fast random removal,
+    // and with reasonable space efficiency
+    std::unordered_map<int64_t, PACKAGE> mPackages;
+
+    void process(int64_t packageId) {
+        PACKAGE package;
+        {
+            std::lock_guard<std::mutex> lock(mMutex);
+            auto itr = mPackages.find(packageId);
+            if (itr == mPackages.end()) {
+                // Work id does not have matching entry, drop it
+                return;
+            }
+
+            package = itr->second;
+            mPackages.erase(itr);
+        }
+        mCallback(package);
+    }
+};
+
+}  // namespace pixel
+}  // namespace impl
+}  // namespace power
+}  // namespace hardware
+}  // namespace google
+}  // namespace aidl
diff --git a/power-libperfmgr/aidl/Power.cpp b/power-libperfmgr/aidl/Power.cpp
index 382a7b7..e15d985 100644
--- a/power-libperfmgr/aidl/Power.cpp
+++ b/power-libperfmgr/aidl/Power.cpp
@@ -183,6 +183,9 @@
     if (type == Mode::LOW_POWER) {
         supported = true;
     }
+    if (!supported && HintManager::GetInstance()->IsAdpfProfileSupported(toString(type))) {
+        supported = true;
+    }
     LOG(INFO) << "Power mode " << toString(type) << " isModeSupported: " << supported;
     *_aidl_return = supported;
     return ndk::ScopedAStatus::ok();
@@ -231,6 +234,9 @@
 
 ndk::ScopedAStatus Power::isBoostSupported(Boost type, bool *_aidl_return) {
     bool supported = HintManager::GetInstance()->IsHintSupported(toString(type));
+    if (!supported && HintManager::GetInstance()->IsAdpfProfileSupported(toString(type))) {
+        supported = true;
+    }
     LOG(INFO) << "Power boost " << toString(type) << " isBoostSupported: " << supported;
     *_aidl_return = supported;
     return ndk::ScopedAStatus::ok();
diff --git a/power-libperfmgr/aidl/PowerExt.cpp b/power-libperfmgr/aidl/PowerExt.cpp
index f6cd084..bf0a104 100644
--- a/power-libperfmgr/aidl/PowerExt.cpp
+++ b/power-libperfmgr/aidl/PowerExt.cpp
@@ -57,6 +57,10 @@
 
 ndk::ScopedAStatus PowerExt::isModeSupported(const std::string &mode, bool *_aidl_return) {
     bool supported = HintManager::GetInstance()->IsHintSupported(mode);
+
+    if (!supported && HintManager::GetInstance()->IsAdpfProfileSupported(mode)) {
+        supported = true;
+    }
     LOG(INFO) << "PowerExt mode " << mode << " isModeSupported: " << supported;
     *_aidl_return = supported;
     return ndk::ScopedAStatus::ok();
@@ -82,6 +86,9 @@
 
 ndk::ScopedAStatus PowerExt::isBoostSupported(const std::string &boost, bool *_aidl_return) {
     bool supported = HintManager::GetInstance()->IsHintSupported(boost);
+    if (!supported && HintManager::GetInstance()->IsAdpfProfileSupported(boost)) {
+        supported = true;
+    }
     LOG(INFO) << "PowerExt boost " << boost << " isBoostSupported: " << supported;
     *_aidl_return = supported;
     return ndk::ScopedAStatus::ok();
diff --git a/power-libperfmgr/aidl/PowerHintSession.cpp b/power-libperfmgr/aidl/PowerHintSession.cpp
index eadc4cf..0262d40 100644
--- a/power-libperfmgr/aidl/PowerHintSession.cpp
+++ b/power-libperfmgr/aidl/PowerHintSession.cpp
@@ -48,6 +48,8 @@
 
 namespace {
 
+static std::atomic<int64_t> sSessionIDCounter{0};
+
 static inline int64_t ns_to_100us(int64_t ns) {
     return ns / 100000;
 }
@@ -57,7 +59,7 @@
 int64_t PowerHintSession::convertWorkDurationToBoostByPid(
         const std::vector<WorkDuration> &actualDurations) {
     std::shared_ptr<AdpfConfig> adpfConfig = HintManager::GetInstance()->GetAdpfProfile();
-    const nanoseconds &targetDuration = mDescriptor->duration;
+    const nanoseconds &targetDuration = mDescriptor->targetNs;
     int64_t &integral_error = mDescriptor->integral_error;
     int64_t &previous_error = mDescriptor->previous_error;
     uint64_t samplingWindowP = adpfConfig->mSamplingWindowP;
@@ -103,52 +105,62 @@
                                  derivative_sum / dt / (length - d_start));
 
     int64_t output = pOut + iOut + dOut;
-    if (ATRACE_ENABLED()) {
-        traceSessionVal("pid.err", err_sum / (length - p_start));
-        traceSessionVal("pid.integral", integral_error);
-        traceSessionVal("pid.derivative", derivative_sum / dt / (length - d_start));
-        traceSessionVal("pid.pOut", pOut);
-        traceSessionVal("pid.iOut", iOut);
-        traceSessionVal("pid.dOut", dOut);
-        traceSessionVal("pid.output", output);
-    }
+    ATRACE_INT(mAppDescriptorTrace.trace_pid_err.c_str(), err_sum / (length - p_start));
+    ATRACE_INT(mAppDescriptorTrace.trace_pid_integral.c_str(), integral_error);
+    ATRACE_INT(mAppDescriptorTrace.trace_pid_derivative.c_str(),
+               derivative_sum / dt / (length - d_start));
+    ATRACE_INT(mAppDescriptorTrace.trace_pid_pOut.c_str(), pOut);
+    ATRACE_INT(mAppDescriptorTrace.trace_pid_iOut.c_str(), iOut);
+    ATRACE_INT(mAppDescriptorTrace.trace_pid_dOut.c_str(), dOut);
+    ATRACE_INT(mAppDescriptorTrace.trace_pid_output.c_str(), output);
     return output;
 }
 
-PowerHintSession::PowerHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t> &threadIds,
-                                   int64_t durationNanos)
-    : mStaleTimerHandler(sp<StaleTimerHandler>::make(this)),
-      mBoostTimerHandler(sp<BoostTimerHandler>::make(this)) {
-    mDescriptor = new AppHintDesc(tgid, uid, threadIds);
-    mDescriptor->duration = std::chrono::nanoseconds(durationNanos);
-    mIdString = StringPrintf("%" PRId32 "-%" PRId32 "-%" PRIxPTR, mDescriptor->tgid,
-                             mDescriptor->uid, reinterpret_cast<uintptr_t>(this) & 0xffff);
+AppHintDesc::AppHintDesc(int64_t sessionId, int32_t tgid, int32_t uid,
+                         std::chrono::nanoseconds pTargetNs)
+    : sessionId(sessionId),
+      tgid(tgid),
+      uid(uid),
+      targetNs(pTargetNs),
+      pidSetPoint(0),
+      is_active(true),
+      update_count(0),
+      integral_error(0),
+      previous_error(0) {}
 
-    mPowerManagerHandler = PowerSessionManager::getInstance();
+PowerHintSession::PowerHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t> &threadIds,
+                                   int64_t durationNs)
+    : mPSManager(PowerSessionManager::getInstance()),
+      mSessionId(++sSessionIDCounter),
+      mIdString(StringPrintf("%" PRId32 "-%" PRId32 "-%" PRId64, tgid, uid, mSessionId)),
+      mDescriptor(std::make_shared<AppHintDesc>(mSessionId, tgid, uid,
+                                                std::chrono::nanoseconds(durationNs))),
+      mAppDescriptorTrace(mIdString) {
+    ATRACE_CALL();
+    ATRACE_INT(mAppDescriptorTrace.trace_target.c_str(), mDescriptor->targetNs.count());
+    ATRACE_INT(mAppDescriptorTrace.trace_active.c_str(), mDescriptor->is_active.load());
+
     mLastUpdatedTime.store(std::chrono::steady_clock::now());
-    if (ATRACE_ENABLED()) {
-        traceSessionVal("target", mDescriptor->duration.count());
-        traceSessionVal("active", mDescriptor->is_active.load());
-    }
-    PowerSessionManager::getInstance()->addPowerSession(this);
+    mPSManager->addPowerSession(mIdString, mDescriptor, threadIds);
     // init boost
-    sendHint(SessionHint::CPU_LOAD_RESET);
+    auto adpfConfig = HintManager::GetInstance()->GetAdpfProfile();
+    mPSManager->voteSet(
+            mSessionId, AdpfHintType::ADPF_CPU_LOAD_RESET, adpfConfig->mUclampMinHigh, kUclampMax,
+            std::chrono::steady_clock::now(),
+            duration_cast<nanoseconds>(mDescriptor->targetNs * adpfConfig->mStaleTimeFactor / 2.0));
+
+    mPSManager->voteSet(mSessionId, AdpfHintType::ADPF_VOTE_DEFAULT, adpfConfig->mUclampMinInit,
+                        kUclampMax, std::chrono::steady_clock::now(), mDescriptor->targetNs);
     ALOGV("PowerHintSession created: %s", mDescriptor->toString().c_str());
 }
 
 PowerHintSession::~PowerHintSession() {
+    ATRACE_CALL();
     close();
     ALOGV("PowerHintSession deleted: %s", mDescriptor->toString().c_str());
-    if (ATRACE_ENABLED()) {
-        traceSessionVal("target", 0);
-        traceSessionVal("actl_last", 0);
-        traceSessionVal("active", 0);
-    }
-    delete mDescriptor;
-}
-
-void PowerHintSession::traceSessionVal(char const *identifier, int64_t val) const {
-    ATRACE_INT(StringPrintf("adpf.%s-%s", mIdString.c_str(), identifier).c_str(), val);
+    ATRACE_INT(mAppDescriptorTrace.trace_target.c_str(), 0);
+    ATRACE_INT(mAppDescriptorTrace.trace_actl_last.c_str(), 0);
+    ATRACE_INT(mAppDescriptorTrace.trace_active.c_str(), 0);
 }
 
 bool PowerHintSession::isAppSession() {
@@ -156,18 +168,16 @@
     return mDescriptor->uid >= AID_APP_START;
 }
 
-void PowerHintSession::updateUniveralBoostMode() {
-    if (!isAppSession()) {
-        return;
+void PowerHintSession::updatePidSetPoint(int pidSetPoint, bool updateVote) {
+    mDescriptor->pidSetPoint = pidSetPoint;
+    if (updateVote) {
+        auto adpfConfig = HintManager::GetInstance()->GetAdpfProfile();
+        mPSManager->voteSet(
+                mSessionId, AdpfHintType::ADPF_VOTE_DEFAULT, pidSetPoint, kUclampMax,
+                std::chrono::steady_clock::now(),
+                duration_cast<nanoseconds>(mDescriptor->targetNs * adpfConfig->mStaleTimeFactor));
     }
-    if (ATRACE_ENABLED()) {
-        const std::string tag = StringPrintf("%s:updateUniveralBoostMode()", mIdString.c_str());
-        ATRACE_BEGIN(tag.c_str());
-    }
-    PowerHintMonitor::getInstance()->getLooper()->sendMessage(mPowerManagerHandler, NULL);
-    if (ATRACE_ENABLED()) {
-        ATRACE_END();
-    }
+    ATRACE_INT(mAppDescriptorTrace.trace_min.c_str(), pidSetPoint);
 }
 
 void PowerHintSession::tryToSendPowerHint(std::string hint) {
@@ -179,30 +189,10 @@
     }
 }
 
-int PowerHintSession::setSessionUclampMin(int32_t min, bool resetStale) {
-    {
-        std::lock_guard<std::mutex> guard(mSessionLock);
-        mDescriptor->current_min = min;
-    }
-    if (min != 0 && resetStale) {
-        mStaleTimerHandler->updateTimer();
-    }
-    PowerSessionManager::getInstance()->setUclampMin(this, min);
-
-    if (ATRACE_ENABLED()) {
-        traceSessionVal("min", min);
-    }
-    return 0;
-}
-
-int PowerHintSession::getUclampMin() {
-    return mDescriptor->current_min;
-}
-
 void PowerHintSession::dumpToStream(std::ostream &stream) {
     stream << "ID.Min.Act.Timeout(" << mIdString;
-    stream << ", " << mDescriptor->current_min;
-    stream << ", " << mDescriptor->is_active.load();
+    stream << ", " << mDescriptor->pidSetPoint;
+    stream << ", " << mDescriptor->is_active;
     stream << ", " << isTimeout() << ")";
 }
 
@@ -215,12 +205,9 @@
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     // Reset to default uclamp value.
     mDescriptor->is_active.store(false);
-    setStale();
-    if (ATRACE_ENABLED()) {
-        traceSessionVal("active", mDescriptor->is_active.load());
-    }
-    updateUniveralBoostMode();
-    PowerSessionManager::getInstance()->removeThreadsFromPowerSession(this);
+    mPSManager->pause(mSessionId);
+    ATRACE_INT(mAppDescriptorTrace.trace_active.c_str(), false);
+    ATRACE_INT(mAppDescriptorTrace.trace_min.c_str(), 0);
     return ndk::ScopedAStatus::ok();
 }
 
@@ -232,13 +219,10 @@
     if (mDescriptor->is_active.load())
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     mDescriptor->is_active.store(true);
-    PowerSessionManager::getInstance()->addThreadsFromPowerSession(this);
     // resume boost
-    setSessionUclampMin(mDescriptor->current_min);
-    if (ATRACE_ENABLED()) {
-        traceSessionVal("active", mDescriptor->is_active.load());
-    }
-    updateUniveralBoostMode();
+    mPSManager->resume(mSessionId);
+    ATRACE_INT(mAppDescriptorTrace.trace_active.c_str(), true);
+    ATRACE_INT(mAppDescriptorTrace.trace_min.c_str(), mDescriptor->pidSetPoint);
     return ndk::ScopedAStatus::ok();
 }
 
@@ -248,12 +232,9 @@
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
     // Remove the session from PowerSessionManager first to avoid racing.
-    PowerSessionManager::getInstance()->removePowerSession(this);
-    mStaleTimerHandler->setSessionDead();
-    mBoostTimerHandler->setSessionDead();
-    setSessionUclampMin(0);
+    mPSManager->removePowerSession(mSessionId);
     mDescriptor->is_active.store(false);
-    updateUniveralBoostMode();
+    ATRACE_INT(mAppDescriptorTrace.trace_min.c_str(), 0);
     return ndk::ScopedAStatus::ok();
 }
 
@@ -268,12 +249,11 @@
     }
     targetDurationNanos =
             targetDurationNanos * HintManager::GetInstance()->GetAdpfProfile()->mTargetTimeFactor;
-    ALOGV("update target duration: %" PRId64 " ns", targetDurationNanos);
 
-    mDescriptor->duration = std::chrono::nanoseconds(targetDurationNanos);
-    if (ATRACE_ENABLED()) {
-        traceSessionVal("target", mDescriptor->duration.count());
-    }
+    mDescriptor->targetNs = std::chrono::nanoseconds(targetDurationNanos);
+    mPSManager->updateTargetWorkDuration(mSessionId, AdpfHintType::ADPF_VOTE_DEFAULT,
+                                         mDescriptor->targetNs);
+    ATRACE_INT(mAppDescriptorTrace.trace_target.c_str(), targetDurationNanos);
 
     return ndk::ScopedAStatus::ok();
 }
@@ -284,53 +264,53 @@
         ALOGE("Error: session is dead");
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
-    if (mDescriptor->duration.count() == 0LL) {
+    if (mDescriptor->targetNs.count() == 0LL) {
         ALOGE("Expect to call updateTargetWorkDuration() first.");
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
-    if (actualDurations.size() == 0) {
-        ALOGE("Error: duration.size() shouldn't be %zu.", actualDurations.size());
+    if (actualDurations.empty()) {
+        ALOGE("Error: durations shouldn't be empty.");
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
     }
     if (!mDescriptor->is_active.load()) {
         ALOGE("Error: shouldn't report duration during pause state.");
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
-    std::shared_ptr<AdpfConfig> adpfConfig = HintManager::GetInstance()->GetAdpfProfile();
+    auto adpfConfig = HintManager::GetInstance()->GetAdpfProfile();
     mDescriptor->update_count++;
     bool isFirstFrame = isTimeout();
-    if (ATRACE_ENABLED()) {
-        traceSessionVal("batch_size", actualDurations.size());
-        traceSessionVal("actl_last", actualDurations.back().durationNanos);
-        traceSessionVal("target", mDescriptor->duration.count());
-        traceSessionVal("hint.count", mDescriptor->update_count);
-        traceSessionVal("hint.overtime",
-                        actualDurations.back().durationNanos - mDescriptor->duration.count() > 0);
-    }
+    ATRACE_INT(mAppDescriptorTrace.trace_batch_size.c_str(), actualDurations.size());
+    ATRACE_INT(mAppDescriptorTrace.trace_actl_last.c_str(), actualDurations.back().durationNanos);
+    ATRACE_INT(mAppDescriptorTrace.trace_target.c_str(), mDescriptor->targetNs.count());
+    ATRACE_INT(mAppDescriptorTrace.trace_hint_count.c_str(), mDescriptor->update_count);
+    ATRACE_INT(mAppDescriptorTrace.trace_hint_overtime.c_str(),
+               actualDurations.back().durationNanos - mDescriptor->targetNs.count() > 0);
+    ATRACE_INT(mAppDescriptorTrace.trace_is_first_frame.c_str(), (isFirstFrame) ? (1) : (0));
 
     mLastUpdatedTime.store(std::chrono::steady_clock::now());
     if (isFirstFrame) {
         if (isAppSession()) {
             tryToSendPowerHint("ADPF_FIRST_FRAME");
         }
-        updateUniveralBoostMode();
+
+        mPSManager->updateUniversalBoostMode();
     }
 
-    disableTemporaryBoost();
+    mPSManager->disableBoosts(mSessionId);
 
     if (!adpfConfig->mPidOn) {
-        setSessionUclampMin(adpfConfig->mUclampMinHigh);
+        updatePidSetPoint(adpfConfig->mUclampMinHigh);
         return ndk::ScopedAStatus::ok();
     }
 
     int64_t output = convertWorkDurationToBoostByPid(actualDurations);
 
-    /* apply to all the threads in the group */
+    // Apply to all the threads in the group
     int next_min = std::min(static_cast<int>(adpfConfig->mUclampMinHigh),
-                            mDescriptor->current_min + static_cast<int>(output));
+                            mDescriptor->pidSetPoint + static_cast<int>(output));
     next_min = std::max(static_cast<int>(adpfConfig->mUclampMinLow), next_min);
-    setSessionUclampMin(next_min);
 
+    updatePidSetPoint(next_min);
     return ndk::ScopedAStatus::ok();
 }
 
@@ -339,26 +319,38 @@
         ALOGE("Error: session is dead");
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
-    disableTemporaryBoost();
-    std::shared_ptr<AdpfConfig> adpfConfig = HintManager::GetInstance()->GetAdpfProfile();
+    if (mDescriptor->targetNs.count() == 0LL) {
+        ALOGE("Expect to call updateTargetWorkDuration() first.");
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    auto adpfConfig = HintManager::GetInstance()->GetAdpfProfile();
+
     switch (hint) {
         case SessionHint::CPU_LOAD_UP:
-            mNextUclampMin.store(mDescriptor->current_min);
-            mBoostTimerHandler->updateTimer(mDescriptor->duration * 2);
-            setSessionUclampMin(adpfConfig->mUclampMinHigh);
+            updatePidSetPoint(mDescriptor->pidSetPoint);
+            mPSManager->voteSet(mSessionId, AdpfHintType::ADPF_CPU_LOAD_UP,
+                                adpfConfig->mUclampMinHigh, kUclampMax,
+                                std::chrono::steady_clock::now(), mDescriptor->targetNs * 2);
             break;
         case SessionHint::CPU_LOAD_DOWN:
-            setSessionUclampMin(adpfConfig->mUclampMinLow);
+            updatePidSetPoint(adpfConfig->mUclampMinLow);
             break;
         case SessionHint::CPU_LOAD_RESET:
-            mNextUclampMin.store(std::max(adpfConfig->mUclampMinInit,
-                                          static_cast<uint32_t>(mDescriptor->current_min)));
-            mBoostTimerHandler->updateTimer(duration_cast<nanoseconds>(
-                    mDescriptor->duration * adpfConfig->mStaleTimeFactor / 2.0));
-            setSessionUclampMin(adpfConfig->mUclampMinHigh);
+            updatePidSetPoint(std::max(adpfConfig->mUclampMinInit,
+                                       static_cast<uint32_t>(mDescriptor->pidSetPoint)),
+                              false);
+            mPSManager->voteSet(mSessionId, AdpfHintType::ADPF_CPU_LOAD_RESET,
+                                adpfConfig->mUclampMinHigh, kUclampMax,
+                                std::chrono::steady_clock::now(),
+                                duration_cast<nanoseconds>(mDescriptor->targetNs *
+                                                           adpfConfig->mStaleTimeFactor / 2.0));
             break;
         case SessionHint::CPU_LOAD_RESUME:
-            setSessionUclampMin(mDescriptor->current_min);
+            mPSManager->voteSet(mSessionId, AdpfHintType::ADPF_CPU_LOAD_RESUME,
+                                mDescriptor->pidSetPoint, kUclampMax,
+                                std::chrono::steady_clock::now(),
+                                duration_cast<nanoseconds>(mDescriptor->targetNs *
+                                                           adpfConfig->mStaleTimeFactor / 2.0));
             break;
         default:
             ALOGE("Error: hint is invalid");
@@ -366,10 +358,28 @@
     }
     tryToSendPowerHint(toString(hint));
     mLastUpdatedTime.store(std::chrono::steady_clock::now());
-    if (ATRACE_ENABLED()) {
-        mLastHintSent = static_cast<int>(hint);
-        traceSessionVal("session_hint", static_cast<int>(hint));
+    mLastHintSent = static_cast<int>(hint);
+    ATRACE_INT(mAppDescriptorTrace.trace_session_hint.c_str(), static_cast<int>(hint));
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PowerHintSession::setMode(SessionMode mode, bool enabled) {
+    if (mSessionClosed) {
+        ALOGE("Error: session is dead");
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
+
+    switch (mode) {
+        case SessionMode::POWER_EFFICIENCY:
+            break;
+        default:
+            ALOGE("Error: mode is invalid");
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+
+    mModes[static_cast<size_t>(mode)] = enabled;
+    ATRACE_INT(mAppDescriptorTrace.trace_modes[static_cast<size_t>(mode)].c_str(), enabled);
+    mLastUpdatedTime.store(std::chrono::steady_clock::now());
     return ndk::ScopedAStatus::ok();
 }
 
@@ -378,38 +388,23 @@
         ALOGE("Error: session is dead");
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
-    if (threadIds.size() == 0) {
-        LOG(ERROR) << "Error: threadIds.size() shouldn't be " << threadIds.size();
+    if (threadIds.empty()) {
+        ALOGE("Error: threadIds should not be empty");
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
     }
 
-    PowerSessionManager::getInstance()->removeThreadsFromPowerSession(this);
-    mDescriptor->threadIds.resize(threadIds.size());
-    std::copy(threadIds.begin(), threadIds.end(), back_inserter(mDescriptor->threadIds));
-    PowerSessionManager::getInstance()->addThreadsFromPowerSession(this);
+    mPSManager->setThreadsFromPowerSession(mSessionId, threadIds);
     // init boost
-    setSessionUclampMin(HintManager::GetInstance()->GetAdpfProfile()->mUclampMinInit);
+    updatePidSetPoint(HintManager::GetInstance()->GetAdpfProfile()->mUclampMinInit);
     return ndk::ScopedAStatus::ok();
 }
 
 std::string AppHintDesc::toString() const {
-    std::string out =
-            StringPrintf("session %" PRIxPTR "\n", reinterpret_cast<uintptr_t>(this) & 0xffff);
-    const int64_t durationNanos = duration.count();
-    out.append(StringPrintf("  duration: %" PRId64 " ns\n", durationNanos));
-    out.append(StringPrintf("  uclamp.min: %d \n", current_min));
+    std::string out = StringPrintf("session %" PRId64 "\n", sessionId);
+    out.append(
+            StringPrintf("  duration: %" PRId64 " ns\n", static_cast<int64_t>(targetNs.count())));
+    out.append(StringPrintf("  uclamp.min: %d \n", pidSetPoint));
     out.append(StringPrintf("  uid: %d, tgid: %d\n", uid, tgid));
-
-    out.append("  threadIds: [");
-    bool first = true;
-    for (int tid : threadIds) {
-        if (!first) {
-            out.append(", ");
-        }
-        out.append(std::to_string(tid));
-        first = false;
-    }
-    out.append("]\n");
     return out;
 }
 
@@ -422,105 +417,11 @@
     time_point<steady_clock> staleTime =
             mLastUpdatedTime.load() +
             nanoseconds(static_cast<int64_t>(
-                    mDescriptor->duration.count() *
+                    mDescriptor->targetNs.count() *
                     HintManager::GetInstance()->GetAdpfProfile()->mStaleTimeFactor));
     return now >= staleTime;
 }
 
-const std::vector<int32_t> &PowerHintSession::getTidList() const {
-    return mDescriptor->threadIds;
-}
-
-bool PowerHintSession::disableTemporaryBoost() {
-    if (ATRACE_ENABLED()) {
-        if (mLastHintSent != -1) {
-            mLastHintSent = -1;
-            traceSessionVal("session_hint", -1);
-        }
-    }
-
-    // replace temporary uclamp_min value with true min
-    std::optional<int> trueMin = mNextUclampMin.load();
-    if (trueMin.has_value()) {
-        std::lock_guard<std::mutex> guard(mSessionLock);
-        mDescriptor->current_min = *trueMin;
-        mNextUclampMin.store(std::nullopt);
-        return true;
-    }
-
-    return false;
-}
-
-void PowerHintSession::setStale() {
-    // Make sure any temporary boost is disabled
-    disableTemporaryBoost();
-    // Reset to default uclamp value.
-    PowerSessionManager::getInstance()->setUclampMin(this, 0);
-    // Deliver a task to check if all sessions are inactive.
-    updateUniveralBoostMode();
-    if (ATRACE_ENABLED()) {
-        traceSessionVal("min", 0);
-    }
-}
-
-void PowerHintSession::SessionTimerHandler::updateTimer(nanoseconds delay) {
-    mTimeout.store(steady_clock::now() + delay);
-    {
-        std::lock_guard<std::mutex> guard(mMessageLock);
-        sp<MessageHandler> selfPtr = sp<MessageHandler>::fromExisting(this);
-        PowerHintMonitor::getInstance()->getLooper()->removeMessages(selfPtr);
-        PowerHintMonitor::getInstance()->getLooper()->sendMessageDelayed(delay.count(), selfPtr,
-                                                                         NULL);
-    }
-    if (ATRACE_ENABLED()) {
-        mSession->traceSessionVal(("timer." + mName).c_str(), 0);
-    }
-}
-
-void PowerHintSession::SessionTimerHandler::handleMessage(const Message &) {
-    std::lock_guard<std::mutex> guard(mClosedLock);
-    if (mIsSessionDead) {
-        return;
-    }
-    time_point now = steady_clock::now();
-    int64_t next = (mTimeout.load() - now).count();
-    if (next > 0) {
-        // Schedule for the stale timeout check.
-        std::lock_guard<std::mutex> guard(mMessageLock);
-        sp<MessageHandler> selfPtr = sp<MessageHandler>::fromExisting(this);
-        PowerHintMonitor::getInstance()->getLooper()->removeMessages(selfPtr);
-        PowerHintMonitor::getInstance()->getLooper()->sendMessageDelayed(next, selfPtr, NULL);
-    } else {
-        onTimeout();
-    }
-    if (ATRACE_ENABLED()) {
-        mSession->traceSessionVal(("timer." + mName).c_str(), next > 0 ? 0 : 1);
-    }
-}
-
-void PowerHintSession::SessionTimerHandler::setSessionDead() {
-    std::lock_guard<std::mutex> guard(mClosedLock);
-    mIsSessionDead = true;
-    PowerHintMonitor::getInstance()->getLooper()->removeMessages(
-            sp<MessageHandler>::fromExisting(this));
-}
-
-void PowerHintSession::StaleTimerHandler::updateTimer() {
-    SessionTimerHandler::updateTimer(duration_cast<nanoseconds>(
-            mSession->mDescriptor->duration *
-            HintManager::GetInstance()->GetAdpfProfile()->mStaleTimeFactor));
-}
-
-void PowerHintSession::StaleTimerHandler::onTimeout() {
-    mSession->setStale();
-}
-
-void PowerHintSession::BoostTimerHandler::onTimeout() {
-    if (mSession->disableTemporaryBoost()) {
-        mSession->setSessionUclampMin(mSession->getUclampMin(), false);
-    }
-}
-
 }  // namespace pixel
 }  // namespace impl
 }  // namespace power
diff --git a/power-libperfmgr/aidl/PowerHintSession.h b/power-libperfmgr/aidl/PowerHintSession.h
index e1c2523..4c304ff 100644
--- a/power-libperfmgr/aidl/PowerHintSession.h
+++ b/power-libperfmgr/aidl/PowerHintSession.h
@@ -18,13 +18,16 @@
 
 #include <aidl/android/hardware/power/BnPowerHintSession.h>
 #include <aidl/android/hardware/power/SessionHint.h>
+#include <aidl/android/hardware/power/SessionMode.h>
 #include <aidl/android/hardware/power/WorkDuration.h>
 #include <utils/Looper.h>
 #include <utils/Thread.h>
 
-#include <mutex>
+#include <array>
 #include <unordered_map>
 
+#include "AppDescriptorTrace.h"
+
 namespace aidl {
 namespace google {
 namespace hardware {
@@ -34,6 +37,7 @@
 
 using aidl::android::hardware::power::BnPowerHintSession;
 using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::SessionMode;
 using aidl::android::hardware::power::WorkDuration;
 using ::android::Message;
 using ::android::MessageHandler;
@@ -43,23 +47,20 @@
 using std::chrono::steady_clock;
 using std::chrono::time_point;
 
+class PowerSessionManager;
+
+// The App Hint Descriptor struct manages information necessary
+// to calculate the next uclamp min value from the PID function
+// and is separate so that it can be used as a pointer for
+// easily passing to the pid function
 struct AppHintDesc {
-    AppHintDesc(int32_t tgid, int32_t uid, std::vector<int32_t> threadIds)
-        : tgid(tgid),
-          uid(uid),
-          threadIds(std::move(threadIds)),
-          duration(0LL),
-          current_min(0),
-          is_active(true),
-          update_count(0),
-          integral_error(0),
-          previous_error(0) {}
+    AppHintDesc(int64_t sessionId, int32_t tgid, int32_t uid, std::chrono::nanoseconds pTargetNs);
     std::string toString() const;
+    int64_t sessionId{0};
     const int32_t tgid;
     const int32_t uid;
-    std::vector<int32_t> threadIds;
-    nanoseconds duration;
-    int current_min;
+    nanoseconds targetNs;
+    int pidSetPoint;
     // status
     std::atomic<bool> is_active;
     // pid
@@ -68,6 +69,10 @@
     int64_t previous_error;
 };
 
+// The Power Hint Session is responsible for providing an
+// interface for creating, updating, and closing power hints
+// for a Session. Each sesion that is mapped to multiple
+// threads (or task ids).
 class PowerHintSession : public BnPowerHintSession {
   public:
     explicit PowerHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t> &threadIds,
@@ -80,73 +85,33 @@
     ndk::ScopedAStatus reportActualWorkDuration(
             const std::vector<WorkDuration> &actualDurations) override;
     ndk::ScopedAStatus sendHint(SessionHint hint) override;
+    ndk::ScopedAStatus setMode(SessionMode mode, bool enabled) override;
     ndk::ScopedAStatus setThreads(const std::vector<int32_t> &threadIds) override;
     bool isActive();
     bool isTimeout();
-    void setStale();
-    // Is this hint session for a user application
+    // Is hint session for a user application
     bool isAppSession();
-    const std::vector<int> &getTidList() const;
-    int getUclampMin();
     void dumpToStream(std::ostream &stream);
 
-    // Disable any temporary boost and return to normal operation. It does not
-    // reset the actual uclamp value, and relies on the caller to do so, to
-    // prevent double-setting. Returns true if it actually disabled an active boost
-    bool disableTemporaryBoost();
-
   private:
-    class SessionTimerHandler : public MessageHandler {
-      public:
-        SessionTimerHandler(PowerHintSession *session, std::string name)
-            : mSession(session), mIsSessionDead(false), mName(name) {}
-        void updateTimer(nanoseconds delay);
-        void handleMessage(const Message &message) override;
-        void setSessionDead();
-        virtual void onTimeout() = 0;
-
-      protected:
-        PowerHintSession *mSession;
-        std::mutex mClosedLock;
-        std::mutex mMessageLock;
-        std::atomic<time_point<steady_clock>> mTimeout;
-        bool mIsSessionDead;
-        const std::string mName;
-    };
-
-    class StaleTimerHandler : public SessionTimerHandler {
-      public:
-        StaleTimerHandler(PowerHintSession *session) : SessionTimerHandler(session, "stale") {}
-        void updateTimer();
-        void onTimeout() override;
-    };
-
-    class BoostTimerHandler : public SessionTimerHandler {
-      public:
-        BoostTimerHandler(PowerHintSession *session) : SessionTimerHandler(session, "boost") {}
-        void onTimeout() override;
-    };
-
-  private:
-    void updateUniveralBoostMode();
-    int setSessionUclampMin(int32_t min, bool resetStale = true);
     void tryToSendPowerHint(std::string hint);
+    void updatePidSetPoint(int pidSetPoint, bool updateVote = true);
     int64_t convertWorkDurationToBoostByPid(const std::vector<WorkDuration> &actualDurations);
-    void traceSessionVal(char const *identifier, int64_t val) const;
-    AppHintDesc *mDescriptor = nullptr;
-    sp<StaleTimerHandler> mStaleTimerHandler;
-    sp<BoostTimerHandler> mBoostTimerHandler;
-    std::atomic<time_point<steady_clock>> mLastUpdatedTime;
-    sp<MessageHandler> mPowerManagerHandler;
-    std::mutex mSessionLock;
-    std::atomic<bool> mSessionClosed = false;
+    // Data
+    sp<PowerSessionManager> mPSManager;
+    int64_t mSessionId = 0;
     std::string mIdString;
-    // Used when setting a temporary boost value to hold the true boost
-    std::atomic<std::optional<int>> mNextUclampMin;
-    // To cache the status of whether ADPF hints are supported.
+    std::shared_ptr<AppHintDesc> mDescriptor;
+    // Trace strings
+    AppDescriptorTrace mAppDescriptorTrace;
+    std::atomic<time_point<steady_clock>> mLastUpdatedTime;
+    std::atomic<bool> mSessionClosed = false;
+    // Are cpu load change related hints are supported
     std::unordered_map<std::string, std::optional<bool>> mSupportedHints;
     // Last session hint sent, used for logging
     int mLastHintSent = -1;
+    // Use the value of the last enum in enum_range +1 as array size
+    std::array<bool, enum_size<SessionMode>()> mModes{};
 };
 
 }  // namespace pixel
diff --git a/power-libperfmgr/aidl/PowerSessionManager.cpp b/power-libperfmgr/aidl/PowerSessionManager.cpp
index 2d2aad2..d8e77bd 100644
--- a/power-libperfmgr/aidl/PowerSessionManager.cpp
+++ b/power-libperfmgr/aidl/PowerSessionManager.cpp
@@ -20,12 +20,16 @@
 #include "PowerSessionManager.h"
 
 #include <android-base/file.h>
+#include <android-base/stringprintf.h>
 #include <log/log.h>
 #include <perfmgr/HintManager.h>
+#include <private/android_filesystem_config.h>
 #include <processgroup/processgroup.h>
 #include <sys/syscall.h>
 #include <utils/Trace.h>
 
+#include "AdpfTypes.h"
+
 namespace aidl {
 namespace google {
 namespace hardware {
@@ -33,6 +37,7 @@
 namespace impl {
 namespace pixel {
 
+using ::android::base::StringPrintf;
 using ::android::perfmgr::AdpfConfig;
 using ::android::perfmgr::HintManager;
 
@@ -51,17 +56,10 @@
     __u32 sched_util_max;
 };
 
-static int sched_setattr(int pid, struct sched_attr *attr, unsigned int flags) {
-    if (!HintManager::GetInstance()->GetAdpfProfile()->mUclampMinOn) {
-        ALOGV("PowerSessionManager:%s: skip", __func__);
-        return 0;
-    }
-    return syscall(__NR_sched_setattr, pid, attr, flags);
-}
-
-static void set_uclamp_min(int tid, int min) {
+static int set_uclamp_min(int tid, int min) {
+    static constexpr int32_t kMinUclampValue = 0;
     static constexpr int32_t kMaxUclampValue = 1024;
-    min = std::max(0, min);
+    min = std::max(kMinUclampValue, min);
     min = std::min(min, kMaxUclampValue);
 
     sched_attr attr = {};
@@ -70,19 +68,16 @@
     attr.sched_flags = (SCHED_FLAG_KEEP_ALL | SCHED_FLAG_UTIL_CLAMP_MIN);
     attr.sched_util_min = min;
 
-    int ret = sched_setattr(tid, &attr, 0);
+    const int ret = syscall(__NR_sched_setattr, tid, attr, 0);
     if (ret) {
-        if (errno == ESRCH) {
-            ALOGV("sched_setattr failed for thread %d, err=%d", tid, errno);
-        } else {
-            ALOGW("sched_setattr failed for thread %d, err=%d", tid, errno);
-        }
+        ALOGW("sched_setattr failed for thread %d, err=%d", tid, errno);
+        return errno;
     }
+    return 0;
 }
 }  // namespace
 
 void PowerSessionManager::updateHintMode(const std::string &mode, bool enabled) {
-    ALOGV("PowerSessionManager::updateHintMode: mode: %s, enabled: %d", mode.c_str(), enabled);
     if (enabled && mode.compare(0, 8, "REFRESH_") == 0) {
         if (mode.compare("REFRESH_120FPS") == 0) {
             mDisplayRefreshRate = 120;
@@ -107,89 +102,99 @@
     return mDisplayRefreshRate;
 }
 
-void PowerSessionManager::addPowerSession(PowerHintSession *session) {
-    std::lock_guard<std::mutex> guard(mLock);
-    mSessions.insert(session);
-    addThreadsFromPowerSessionLocked(session);
-}
-
-void PowerSessionManager::removePowerSession(PowerHintSession *session) {
-    std::lock_guard<std::mutex> guard(mLock);
-    mSessions.erase(session);
-    removeThreadsFromPowerSessionLocked(session);
-}
-
-void PowerSessionManager::addThreadsFromPowerSession(PowerHintSession *session) {
-    std::lock_guard<std::mutex> guard(mLock);
-    addThreadsFromPowerSessionLocked(session);
-}
-
-void PowerSessionManager::addThreadsFromPowerSessionLocked(PowerHintSession *session) {
-    for (auto t : session->getTidList()) {
-        if (mTidSessionListMap[t].empty()) {
-            if (!SetTaskProfiles(t, {"ResetUclampGrp"})) {
-                ALOGW("Failed to set ResetUclampGrp task profile for tid:%d", t);
-            }
-        }
-        mTidSessionListMap[t].insert(session);
+void PowerSessionManager::addPowerSession(const std::string &idString,
+                                          const std::shared_ptr<AppHintDesc> &sessionDescriptor,
+                                          const std::vector<int32_t> &threadIds) {
+    if (!sessionDescriptor) {
+        ALOGE("sessionDescriptor is null. PowerSessionManager failed to add power session: %s",
+              idString.c_str());
+        return;
     }
+    const auto timeNow = std::chrono::steady_clock::now();
+    VoteRange pidVoteRange(false, kUclampMin, kUclampMax, timeNow, sessionDescriptor->targetNs);
+
+    SessionValueEntry sve;
+    sve.tgid = sessionDescriptor->tgid;
+    sve.uid = sessionDescriptor->uid;
+    sve.idString = idString;
+    sve.isActive = sessionDescriptor->is_active;
+    sve.isAppSession = sessionDescriptor->uid >= AID_APP_START;
+    sve.lastUpdatedTime = timeNow;
+    sve.votes = std::make_shared<Votes>();
+    sve.votes->add(
+            static_cast<std::underlying_type_t<AdpfHintType>>(AdpfHintType::ADPF_VOTE_DEFAULT),
+            pidVoteRange);
+
+    bool addedRes = false;
+    {
+        std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
+        addedRes = mSessionTaskMap.add(sessionDescriptor->sessionId, sve, {});
+    }
+    if (!addedRes) {
+        ALOGE("sessionTaskMap failed to add power session: %" PRId64, sessionDescriptor->sessionId);
+    }
+
+    setThreadsFromPowerSession(sessionDescriptor->sessionId, threadIds);
 }
 
-void PowerSessionManager::removeThreadsFromPowerSession(PowerHintSession *session) {
-    std::lock_guard<std::mutex> guard(mLock);
-    removeThreadsFromPowerSessionLocked(session);
-}
+void PowerSessionManager::removePowerSession(int64_t sessionId) {
+    // To remove a session we also need to undo the effects the session
+    // has on currently enabled votes which means setting vote to inactive
+    // and then forceing a uclamp update to occur
+    forceSessionActive(sessionId, false);
 
-void PowerSessionManager::removeThreadsFromPowerSessionLocked(PowerHintSession *session) {
-    for (auto t : session->getTidList()) {
-        size_t cnt = mTidSessionListMap[t].erase(session);
-        if (cnt != 0 && mTidSessionListMap[t].empty()) {
-            if (!SetTaskProfiles(t, {"NoResetUclampGrp"})) {
-                ALOGW("Failed to set NoResetUclampGrp task profile for tid:%d", t);
-            }
+    std::vector<pid_t> addedThreads;
+    std::vector<pid_t> removedThreads;
+
+    {
+        // Wait till end to remove session because it needs to be around for apply U clamp
+        // to work above since applying the uclamp needs a valid session id
+        std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
+        mSessionTaskMap.replace(sessionId, {}, &addedThreads, &removedThreads);
+        mSessionTaskMap.remove(sessionId);
+    }
+
+    for (auto tid : removedThreads) {
+        if (!SetTaskProfiles(tid, {"NoResetUclampGrp"})) {
+            ALOGE("Failed to set NoResetUclampGrp task profile for tid:%d", tid);
         }
     }
 }
 
-void PowerSessionManager::setUclampMin(PowerHintSession *session, int val) {
-    std::lock_guard<std::mutex> guard(mLock);
-    setUclampMinLocked(session, val);
-}
-
-void PowerSessionManager::setUclampMinLocked(PowerHintSession *session, int val) {
-    for (auto t : session->getTidList()) {
-        // Get thex max uclamp.min across sessions which include the tid.
-        int tidMax = 0;
-        for (PowerHintSession *s : mTidSessionListMap[t]) {
-            if (!s->isActive() || s->isTimeout())
-                continue;
-            tidMax = std::max(tidMax, s->getUclampMin());
-        }
-        set_uclamp_min(t, std::max(val, tidMax));
+void PowerSessionManager::setThreadsFromPowerSession(int64_t sessionId,
+                                                     const std::vector<int32_t> &threadIds) {
+    std::vector<pid_t> addedThreads;
+    std::vector<pid_t> removedThreads;
+    forceSessionActive(sessionId, false);
+    {
+        std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
+        mSessionTaskMap.replace(sessionId, threadIds, &addedThreads, &removedThreads);
     }
+    for (auto tid : addedThreads) {
+        if (!SetTaskProfiles(tid, {"ResetUclampGrp"})) {
+            ALOGE("Failed to set ResetUclampGrp task profile for tid:%d", tid);
+        }
+    }
+    for (auto tid : removedThreads) {
+        if (!SetTaskProfiles(tid, {"NoResetUclampGrp"})) {
+            ALOGE("Failed to set NoResetUclampGrp task profile for tid:%d", tid);
+        }
+    }
+    forceSessionActive(sessionId, true);
 }
 
 std::optional<bool> PowerSessionManager::isAnyAppSessionActive() {
-    std::lock_guard<std::mutex> guard(mLock);
-    bool active = false;
-    for (PowerHintSession *s : mSessions) {
-        // session active and not stale is actually active.
-        if (s->isActive() && !s->isTimeout() && s->isAppSession()) {
-            active = true;
-            break;
-        }
+    bool isAnyAppSessionActive = false;
+    {
+        std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
+        isAnyAppSessionActive =
+                mSessionTaskMap.isAnyAppSessionActive(std::chrono::steady_clock::now());
     }
-    if (active == mActive) {
-        return std::nullopt;
-    } else {
-        mActive = active;
-    }
-
-    return active;
+    return isAnyAppSessionActive;
 }
 
-void PowerSessionManager::handleMessage(const Message &) {
-    auto active = isAnyAppSessionActive();
+void PowerSessionManager::updateUniversalBoostMode() {
+    const auto active = isAnyAppSessionActive();
     if (!active.has_value()) {
         return;
     }
@@ -202,26 +207,148 @@
 
 void PowerSessionManager::dumpToFd(int fd) {
     std::ostringstream dump_buf;
-    std::lock_guard<std::mutex> guard(mLock);
     dump_buf << "========== Begin PowerSessionManager ADPF list ==========\n";
-    for (PowerHintSession *s : mSessions) {
-        s->dumpToStream(dump_buf);
-        dump_buf << " Tid:Ref[";
-        for (size_t i = 0, len = s->getTidList().size(); i < len; i++) {
-            int t = s->getTidList()[i];
-            dump_buf << t << ":" << mTidSessionListMap[t].size();
-            if (i < len - 1) {
-                dump_buf << ", ";
-            }
-        }
-        dump_buf << "]\n";
-    }
+    std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
+    mSessionTaskMap.forEachSessionValTasks(
+            [&](auto /* sessionId */, const auto &sessionVal, const auto &tasks) {
+                sessionVal.dump(dump_buf);
+                dump_buf << " Tid:Ref[";
+
+                size_t tasksLen = tasks.size();
+                for (auto taskId : tasks) {
+                    dump_buf << taskId << ":";
+                    const auto &sessionIds = mSessionTaskMap.getSessionIds(taskId);
+                    if (!sessionIds.empty()) {
+                        dump_buf << sessionIds.size();
+                    }
+                    if (tasksLen > 0) {
+                        dump_buf << ", ";
+                        --tasksLen;
+                    }
+                }
+                dump_buf << "]\n";
+            });
     dump_buf << "========== End PowerSessionManager ADPF list ==========\n";
     if (!::android::base::WriteStringToFd(dump_buf.str(), fd)) {
         ALOGE("Failed to dump one of session list to fd:%d", fd);
     }
 }
 
+void PowerSessionManager::pause(int64_t sessionId) {
+    {
+        std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
+        auto sessValPtr = mSessionTaskMap.findSession(sessionId);
+        if (nullptr == sessValPtr) {
+            ALOGW("Pause failed, session is null %" PRId64, sessionId);
+            return;
+        }
+
+        if (!sessValPtr->isActive) {
+            ALOGW("Sess(%" PRId64 "), cannot pause, already inActive", sessionId);
+            return;
+        }
+        sessValPtr->isActive = false;
+    }
+    applyUclamp(sessionId, std::chrono::steady_clock::now());
+    updateUniversalBoostMode();
+}
+
+void PowerSessionManager::resume(int64_t sessionId) {
+    {
+        std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
+        auto sessValPtr = mSessionTaskMap.findSession(sessionId);
+        if (nullptr == sessValPtr) {
+            ALOGW("Resume failed, session is null %" PRId64, sessionId);
+            return;
+        }
+
+        if (sessValPtr->isActive) {
+            ALOGW("Sess(%" PRId64 "), cannot resume, already active", sessionId);
+            return;
+        }
+        sessValPtr->isActive = true;
+    }
+    applyUclamp(sessionId, std::chrono::steady_clock::now());
+    updateUniversalBoostMode();
+}
+
+void PowerSessionManager::updateTargetWorkDuration(int64_t sessionId, AdpfHintType voteId,
+                                                   std::chrono::nanoseconds durationNs) {
+    int voteIdInt = static_cast<std::underlying_type_t<AdpfHintType>>(voteId);
+    std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
+    auto sessValPtr = mSessionTaskMap.findSession(sessionId);
+    if (nullptr == sessValPtr) {
+        ALOGE("Failed to updateTargetWorkDuration, session val is null id: %" PRId64, sessionId);
+        return;
+    }
+
+    sessValPtr->votes->updateDuration(voteIdInt, durationNs);
+    // Note, for now we are not recalculating and applying uclamp because
+    // that maintains behavior from before.  In the future we may want to
+    // revisit that decision.
+}
+
+void PowerSessionManager::voteSet(int64_t sessionId, AdpfHintType voteId, int uclampMin,
+                                  int uclampMax, std::chrono::steady_clock::time_point startTime,
+                                  std::chrono::nanoseconds durationNs) {
+    const int voteIdInt = static_cast<std::underlying_type_t<AdpfHintType>>(voteId);
+    const auto timeoutDeadline = startTime + durationNs;
+    const VoteRange vr(true, uclampMin, uclampMax, startTime, durationNs);
+    bool scheduleTimeout = false;
+
+    {
+        std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
+        auto sessValPtr = mSessionTaskMap.findSession(sessionId);
+        if (nullptr == sessValPtr) {
+            // Because of the async nature of some events an event for a session
+            // that has been removed is a possibility so this is a verbose log
+            // instead of a warning or error
+            return;
+        }
+
+        if (!sessValPtr->votes->voteIsActive(voteIdInt)) {
+            scheduleTimeout = true;
+        }
+        if (timeoutDeadline < sessValPtr->votes->voteTimeout(voteIdInt)) {
+            scheduleTimeout = true;
+        }
+        sessValPtr->votes->add(voteIdInt, vr);
+        sessValPtr->lastUpdatedTime = startTime;
+    }
+
+    applyUclamp(sessionId, startTime);  // std::chrono::steady_clock::now());
+
+    if (scheduleTimeout) {
+        // Sent event to handle stale-vote/timeout in the future
+        EventSessionTimeout eTimeout;
+        eTimeout.timeStamp = startTime;  // eSet.timeStamp;
+        eTimeout.sessionId = sessionId;
+        eTimeout.voteId = voteIdInt;
+        mEventSessionTimeoutWorker.schedule(eTimeout, timeoutDeadline);
+    }
+}
+
+void PowerSessionManager::disableBoosts(int64_t sessionId) {
+    {
+        std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
+        auto sessValPtr = mSessionTaskMap.findSession(sessionId);
+        if (nullptr == sessValPtr) {
+            // Because of the async nature of some events an event for a session
+            // that has been removed is a possibility so this is a verbose log
+            // instead of a warning or error
+            return;
+        }
+
+        // sessValPtr->disableBoosts();
+        for (auto vid :
+             {AdpfHintType::ADPF_CPU_LOAD_UP, AdpfHintType::ADPF_CPU_LOAD_RESET,
+              AdpfHintType::ADPF_CPU_LOAD_RESUME, AdpfHintType::ADPF_VOTE_POWER_EFFICIENCY}) {
+            auto vint = static_cast<std::underlying_type_t<AdpfHintType>>(vid);
+            sessValPtr->votes->setUseVote(vint, false);
+        }
+    }
+}
+
 void PowerSessionManager::enableSystemTopAppBoost() {
     if (HintManager::GetInstance()->IsHintSupported(kDisableBoostHintName)) {
         ALOGV("PowerSessionManager::enableSystemTopAppBoost!!");
@@ -236,22 +363,105 @@
     }
 }
 
-// =========== PowerHintMonitor implementation start from here ===========
-void PowerHintMonitor::start() {
-    if (!isRunning()) {
-        run("PowerHintMonitor", ::android::PRIORITY_HIGHEST);
+void PowerSessionManager::handleEvent(const EventSessionTimeout &eventTimeout) {
+    bool recalcUclamp = false;
+    const auto tNow = std::chrono::steady_clock::now();
+    {
+        std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
+        auto sessValPtr = mSessionTaskMap.findSession(eventTimeout.sessionId);
+        if (nullptr == sessValPtr) {
+            // It is ok for session timeouts to fire after a session has been
+            // removed
+            return;
+        }
+
+        // To minimize the number of events pushed into the queue, we are using
+        // the following logic to make use of a single timeout event which will
+        // requeue itself if the timeout has been changed since it was added to
+        // the work queue.  Requeue Logic:
+        // if vote active and vote timeout <= sched time
+        //    then deactivate vote and recalc uclamp (near end of function)
+        // if vote active and vote timeout > sched time
+        //    then requeue timeout event for new deadline (which is vote timeout)
+        const bool voteIsActive = sessValPtr->votes->voteIsActive(eventTimeout.voteId);
+        const auto voteTimeout = sessValPtr->votes->voteTimeout(eventTimeout.voteId);
+
+        if (voteIsActive) {
+            if (voteTimeout <= tNow) {
+                sessValPtr->votes->setUseVote(eventTimeout.voteId, false);
+                recalcUclamp = true;
+            } else {
+                // Can unlock sooner than we do
+                auto eventTimeout2 = eventTimeout;
+                mEventSessionTimeoutWorker.schedule(eventTimeout2, voteTimeout);
+            }
+        }
+    }
+
+    if (!recalcUclamp) {
+        return;
+    }
+
+    // It is important to use the correct time here, time now is more reasonable
+    // than trying to use the event's timestamp which will be slightly off given
+    // the background priority queue introduces latency
+    applyUclamp(eventTimeout.sessionId, tNow);
+    updateUniversalBoostMode();
+}
+
+void PowerSessionManager::applyUclamp(int64_t sessionId,
+                                      std::chrono::steady_clock::time_point timePoint) {
+    const bool uclampMinOn = HintManager::GetInstance()->GetAdpfProfile()->mUclampMinOn;
+
+    {
+        std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
+        auto sessValPtr = mSessionTaskMap.findSession(sessionId);
+        if (nullptr == sessValPtr) {
+            return;
+        }
+
+        if (!uclampMinOn) {
+            ALOGV("PowerSessionManager::set_uclamp_min: skip");
+        } else {
+            auto &threadList = mSessionTaskMap.getTaskIds(sessionId);
+            auto tidIter = threadList.begin();
+            while (tidIter != threadList.end()) {
+                UclampRange uclampRange;
+                mSessionTaskMap.getTaskVoteRange(*tidIter, timePoint, &uclampRange.uclampMin,
+                                                 &uclampRange.uclampMax);
+                int stat = set_uclamp_min(*tidIter, uclampRange.uclampMin);
+                if (stat == ESRCH) {
+                    ALOGV("Removing dead thread %d from hint session %s.", *tidIter,
+                          sessValPtr->idString.c_str());
+                    if (mSessionTaskMap.removeDeadTaskSessionMap(sessionId, *tidIter)) {
+                        ALOGV("Removed dead thread-session map.");
+                    }
+                    tidIter = threadList.erase(tidIter);
+                } else {
+                    tidIter++;
+                }
+            }
+        }
+
+        sessValPtr->lastUpdatedTime = timePoint;
     }
 }
 
-bool PowerHintMonitor::threadLoop() {
-    while (true) {
-        mLooper->pollOnce(-1);
+void PowerSessionManager::forceSessionActive(int64_t sessionId, bool isActive) {
+    {
+        std::lock_guard<std::mutex> lock(mSessionTaskMapMutex);
+        auto sessValPtr = mSessionTaskMap.findSession(sessionId);
+        if (nullptr == sessValPtr) {
+            return;
+        }
+        sessValPtr->isActive = isActive;
     }
-    return true;
-}
 
-sp<Looper> PowerHintMonitor::getLooper() {
-    return mLooper;
+    // As currently written, call needs to occur synchronously so as to ensure
+    // that the SessionId remains valid and mapped to the proper threads/tasks
+    // which enables apply u clamp to work correctly
+    applyUclamp(sessionId, std::chrono::steady_clock::now());
+    updateUniversalBoostMode();
 }
 
 }  // namespace pixel
diff --git a/power-libperfmgr/aidl/PowerSessionManager.h b/power-libperfmgr/aidl/PowerSessionManager.h
index 7c74039..b55e747 100644
--- a/power-libperfmgr/aidl/PowerSessionManager.h
+++ b/power-libperfmgr/aidl/PowerSessionManager.h
@@ -24,7 +24,9 @@
 #include <optional>
 #include <unordered_set>
 
+#include "BackgroundWorker.h"
 #include "PowerHintSession.h"
+#include "SessionTaskMap.h"
 
 namespace aidl {
 namespace google {
@@ -35,30 +37,41 @@
 
 using ::android::Looper;
 using ::android::Message;
-using ::android::MessageHandler;
 using ::android::Thread;
 using ::android::perfmgr::HintManager;
 
 constexpr char kPowerHalAdpfDisableTopAppBoost[] = "vendor.powerhal.adpf.disable.hint";
 
-class PowerSessionManager : public MessageHandler {
+class PowerSessionManager : public ::android::RefBase {
   public:
-    // current hint info
+    // Update the current hint info
     void updateHintMode(const std::string &mode, bool enabled);
     void updateHintBoost(const std::string &boost, int32_t durationMs);
     int getDisplayRefreshRate();
-    // monitoring session status
-    void addPowerSession(PowerHintSession *session);
-    void removePowerSession(PowerHintSession *session);
-    void addThreadsFromPowerSession(PowerHintSession *session);
-    void addThreadsFromPowerSessionLocked(PowerHintSession *session);
-    void removeThreadsFromPowerSession(PowerHintSession *session);
-    void removeThreadsFromPowerSessionLocked(PowerHintSession *session);
-    void setUclampMin(PowerHintSession *session, int min);
-    void setUclampMinLocked(PowerHintSession *session, int min);
-    void handleMessage(const Message &message) override;
+    // Add and remove power hint session
+    void addPowerSession(const std::string &idString,
+                         const std::shared_ptr<AppHintDesc> &sessionDescriptor,
+                         const std::vector<int32_t> &threadIds);
+    void removePowerSession(int64_t sessionId);
+    // Replace current threads in session with threadIds
+    void setThreadsFromPowerSession(int64_t sessionId, const std::vector<int32_t> &threadIds);
+    // Pause and resume power hint session
+    void pause(int64_t sessionId);
+    void resume(int64_t sessionId);
+
+    void updateUniversalBoostMode();
     void dumpToFd(int fd);
 
+    void updateTargetWorkDuration(int64_t sessionId, AdpfHintType voteId,
+                                  std::chrono::nanoseconds durationNs);
+
+    // Set vote for power hint session
+    void voteSet(int64_t sessionId, AdpfHintType voteId, int uclampMin, int uclampMax,
+                 std::chrono::steady_clock::time_point startTime,
+                 std::chrono::nanoseconds durationNs);
+
+    void disableBoosts(int64_t sessionId);
+
     // Singleton
     static sp<PowerSessionManager> getInstance() {
         static sp<PowerSessionManager> instance = new PowerSessionManager();
@@ -71,43 +84,38 @@
     void enableSystemTopAppBoost();
     const std::string kDisableBoostHintName;
 
-    std::unordered_set<PowerHintSession *> mSessions;  // protected by mLock
-    std::unordered_map<int, std::unordered_set<PowerHintSession *>> mTidSessionListMap;
-    bool mActive;  // protected by mLock
-    /**
-     * mLock to pretect the above data objects opertions.
-     **/
-    std::mutex mLock;
     int mDisplayRefreshRate;
+
+    // Rewrite specific
+    mutable std::mutex mSessionTaskMapMutex;
+    SessionTaskMap mSessionTaskMap;
+    std::shared_ptr<PriorityQueueWorkerPool> mPriorityQueueWorkerPool;
+
+    // Session timeout
+    struct EventSessionTimeout {
+        std::chrono::steady_clock::time_point timeStamp;
+        int64_t sessionId{0};
+        int voteId{0};
+    };
+    void handleEvent(const EventSessionTimeout &e);
+    TemplatePriorityQueueWorker<EventSessionTimeout> mEventSessionTimeoutWorker;
+
+    // Calculate uclamp range
+    void applyUclamp(int64_t sessionId, std::chrono::steady_clock::time_point timePoint);
+    // Force a session active or in-active, helper for other methods
+    void forceSessionActive(int64_t sessionId, bool isActive);
+
     // Singleton
     PowerSessionManager()
         : kDisableBoostHintName(::android::base::GetProperty(kPowerHalAdpfDisableTopAppBoost,
                                                              "ADPF_DISABLE_TA_BOOST")),
-          mActive(false),
-          mDisplayRefreshRate(60) {}
+          mDisplayRefreshRate(60),
+          mPriorityQueueWorkerPool(new PriorityQueueWorkerPool(1, "adpf_handler")),
+          mEventSessionTimeoutWorker([&](auto e) { handleEvent(e); }, mPriorityQueueWorkerPool) {}
     PowerSessionManager(PowerSessionManager const &) = delete;
     void operator=(PowerSessionManager const &) = delete;
 };
 
-class PowerHintMonitor : public Thread {
-  public:
-    void start();
-    bool threadLoop() override;
-    sp<Looper> getLooper();
-    // Singleton
-    static sp<PowerHintMonitor> getInstance() {
-        static sp<PowerHintMonitor> instance = new PowerHintMonitor();
-        return instance;
-    }
-    PowerHintMonitor(PowerHintMonitor const &) = delete;
-    void operator=(PowerHintMonitor const &) = delete;
-
-  private:
-    sp<Looper> mLooper;
-    // Singleton
-    PowerHintMonitor() : Thread(false), mLooper(new Looper(true)) {}
-};
-
 }  // namespace pixel
 }  // namespace impl
 }  // namespace power
diff --git a/power-libperfmgr/aidl/SessionTaskMap.cpp b/power-libperfmgr/aidl/SessionTaskMap.cpp
new file mode 100644
index 0000000..62dae33
--- /dev/null
+++ b/power-libperfmgr/aidl/SessionTaskMap.cpp
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#include "SessionTaskMap.h"
+
+#include <algorithm>
+#include <sstream>
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+bool SessionTaskMap::add(int64_t sessionId, const SessionValueEntry &sv,
+                         const std::vector<pid_t> &taskIds) {
+    if (mSessions.find(sessionId) != mSessions.end()) {
+        return false;
+    }
+
+    auto sessValPtr = std::make_shared<SessionValueEntry>();
+    (*sessValPtr) = sv;
+    sessValPtr->sessionId = sessionId;
+
+    auto &sessEntry = mSessions[sessionId];
+    sessEntry.val = sessValPtr;
+    sessEntry.linkedTasks = taskIds;
+
+    for (auto taskId : taskIds) {
+        mTasks[taskId].push_back(sessValPtr);
+    }
+    return true;
+}
+
+void SessionTaskMap::addVote(int64_t sessionId, int voteId, int uclampMin, int uclampMax,
+                             std::chrono::steady_clock::time_point startTime,
+                             std::chrono::nanoseconds durationNs) {
+    auto sessItr = mSessions.find(sessionId);
+    if (sessItr == mSessions.end()) {
+        return;
+    }
+
+    sessItr->second.val->votes->add(voteId,
+                                    VoteRange(true, uclampMin, uclampMax, startTime, durationNs));
+}
+
+std::shared_ptr<SessionValueEntry> SessionTaskMap::findSession(int64_t sessionId) {
+    auto sessItr = mSessions.find(sessionId);
+    if (sessItr == mSessions.end()) {
+        return nullptr;
+    }
+    return sessItr->second.val;
+}
+
+void SessionTaskMap::getTaskVoteRange(pid_t taskId, std::chrono::steady_clock::time_point timeNow,
+                                      int *uclampMin, int *uclampMax) const {
+    UclampRange uclampRange;
+    auto taskItr = mTasks.find(taskId);
+    if (taskItr == mTasks.end()) {
+        return;
+    }
+
+    for (const auto &sessInTask : taskItr->second) {
+        if (!sessInTask->isActive) {
+            continue;
+        }
+        sessInTask->votes->getUclampRange(&uclampRange, timeNow);
+    }
+    *uclampMin = uclampRange.uclampMin;
+    *uclampMax = uclampRange.uclampMax;
+}
+
+std::vector<int64_t> SessionTaskMap::getSessionIds(pid_t taskId) const {
+    auto itr = mTasks.find(taskId);
+    if (itr == mTasks.end()) {
+        static const std::vector<int64_t> emptySessionIdVec;
+        return emptySessionIdVec;
+    }
+    std::vector<int64_t> res;
+    res.reserve(itr->second.size());
+    for (const auto &i : itr->second) {
+        res.push_back(i->sessionId);
+    }
+    return res;
+}
+
+std::vector<pid_t> &SessionTaskMap::getTaskIds(int64_t sessionId) {
+    auto taskItr = mSessions.find(sessionId);
+    if (taskItr == mSessions.end()) {
+        static std::vector<pid_t> emptyTaskIdVec;
+        return emptyTaskIdVec;
+    }
+    return taskItr->second.linkedTasks;
+}
+
+bool SessionTaskMap::isAnyAppSessionActive(std::chrono::steady_clock::time_point timePoint) const {
+    for (auto &sessionVal : mSessions) {
+        if (!sessionVal.second.val->isAppSession) {
+            continue;
+        }
+        if (!sessionVal.second.val->isActive) {
+            continue;
+        }
+        if (!sessionVal.second.val->votes->allTimedOut(timePoint)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool SessionTaskMap::remove(int64_t sessionId) {
+    auto sessItr = mSessions.find(sessionId);
+    if (sessItr == mSessions.end()) {
+        return false;
+    }
+
+    // For each task id in linked tasks need to remove the corresponding
+    // task to session mapping in the task map
+    for (const auto taskId : sessItr->second.linkedTasks) {
+        // Used linked task ids to cleanup
+        auto taskItr = mTasks.find(taskId);
+        if (taskItr == mTasks.end()) {
+            // Inconsisent state
+            continue;
+        }
+
+        // Now lookup session id in task's set
+        auto taskSessItr =
+                std::find(taskItr->second.begin(), taskItr->second.end(), sessItr->second.val);
+        if (taskSessItr == taskItr->second.end()) {
+            // Should not happen
+            continue;
+        }
+
+        // Remove session id from task map
+        taskItr->second.erase(taskSessItr);
+        if (taskItr->second.empty()) {
+            mTasks.erase(taskItr);
+        }
+    }
+
+    // Now we can safely remove session entirely since there are no more
+    // mappings in task to session id
+    mSessions.erase(sessItr);
+    return true;
+}
+
+bool SessionTaskMap::removeDeadTaskSessionMap(int64_t sessionId, pid_t taskId) {
+    auto sessItr = mSessions.find(sessionId);
+    if (sessItr == mSessions.end()) {
+        return false;
+    }
+
+    auto taskItr = mTasks.find(taskId);
+    if (taskItr == mTasks.end()) {
+        // Inconsisent state
+        return false;
+    }
+
+    // Now lookup session id in task's set
+    auto taskSessItr =
+            std::find(taskItr->second.begin(), taskItr->second.end(), sessItr->second.val);
+    if (taskSessItr == taskItr->second.end()) {
+        // Should not happen
+        return false;
+    }
+
+    // Remove session id from task map
+    taskItr->second.erase(taskSessItr);
+    if (taskItr->second.empty()) {
+        mTasks.erase(taskItr);
+    }
+
+    return true;
+}
+
+bool SessionTaskMap::replace(int64_t sessionId, const std::vector<pid_t> &taskIds,
+                             std::vector<pid_t> *addedThreads, std::vector<pid_t> *removedThreads) {
+    auto itr = mSessions.find(sessionId);
+    if (itr == mSessions.end()) {
+        return false;
+    }
+
+    // Make copies of val and threads
+    auto svTmp = itr->second.val;
+    const auto previousTaskIds = itr->second.linkedTasks;
+
+    // Determine newly added threads
+    if (addedThreads) {
+        for (auto tid : taskIds) {
+            auto taskSessItr = mTasks.find(tid);
+            if (taskSessItr == mTasks.end()) {
+                addedThreads->push_back(tid);
+            }
+        }
+    }
+
+    // Remove session from mappings
+    remove(sessionId);
+    // Add session value and task mappings
+    add(sessionId, *svTmp, taskIds);
+
+    // Determine completely removed threads
+    if (removedThreads) {
+        for (auto tid : previousTaskIds) {
+            auto taskSessItr = mTasks.find(tid);
+            if (taskSessItr == mTasks.end()) {
+                removedThreads->push_back(tid);
+            }
+        }
+    }
+
+    return true;
+}
+
+size_t SessionTaskMap::sizeSessions() const {
+    return mSessions.size();
+}
+
+size_t SessionTaskMap::sizeTasks() const {
+    return mTasks.size();
+}
+
+const std::string &SessionTaskMap::idString(int64_t sessionId) const {
+    auto sessItr = mSessions.find(sessionId);
+    if (sessItr == mSessions.end()) {
+        static const std::string emptyString;
+        return emptyString;
+    }
+    return sessItr->second.val->idString;
+}
+
+bool SessionTaskMap::isAppSession(int64_t sessionId) const {
+    auto sessItr = mSessions.find(sessionId);
+    if (sessItr == mSessions.end()) {
+        return false;
+    }
+
+    return sessItr->second.val->isAppSession;
+}
+
+}  // namespace pixel
+}  // namespace impl
+}  // namespace power
+}  // namespace hardware
+}  // namespace google
+}  // namespace aidl
diff --git a/power-libperfmgr/aidl/SessionTaskMap.h b/power-libperfmgr/aidl/SessionTaskMap.h
new file mode 100644
index 0000000..35e7b85
--- /dev/null
+++ b/power-libperfmgr/aidl/SessionTaskMap.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <unordered_map>
+#include <vector>
+
+#include "SessionValueEntry.h"
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+/**
+ * Map session id to a value and link to many task ids
+ * Maintain consistency between mappings
+ * e.g.
+ *  Sessions[sid1] -> SessionValueEntry1, [tid1, tid2]
+ *  Tasks[tid1] -> [sid1]
+ *  Tasks[tid2] -> [sid1]
+ *  ...
+ *  Sessions[sid2] -> SessionValueEntry2, [tid2, tid3]
+ *  Tasks[tid1] -> [sid1]
+ *  Tasks[tid2] -> [sid1, sid2]
+ *  Tasks[tid3] -> [sid2]
+ */
+class SessionTaskMap {
+  public:
+    // Add a session with associated tasks to mapping
+    bool add(int64_t sessionId, const SessionValueEntry &sv, const std::vector<pid_t> &taskIds);
+
+    // Add a vote to a session
+    void addVote(int64_t sessionId, int voteId, int uclampMin, int uclampMax,
+                 std::chrono::steady_clock::time_point startTime,
+                 std::chrono::nanoseconds durationNs);
+
+    // Find session id and run callback on session value, linked tasks
+    std::shared_ptr<SessionValueEntry> findSession(int64_t sessionId);
+
+    void getTaskVoteRange(pid_t taskId, std::chrono::steady_clock::time_point timeNow,
+                          int *uclampMin, int *uclampmax) const;
+
+    // Find session ids given a task id if it exists
+    std::vector<int64_t> getSessionIds(pid_t taskId) const;
+
+    // Get a vec of tasks associated with a session
+    std::vector<pid_t> &getTaskIds(int64_t sessionId);
+
+    // Return true if any app session is active, false otherwise
+    bool isAnyAppSessionActive(std::chrono::steady_clock::time_point timePoint) const;
+
+    // Remove a session based on session id
+    bool remove(int64_t sessionId);
+
+    // Maintain value of session, remove old task mapping add new
+    bool replace(int64_t sessionId, const std::vector<pid_t> &taskIds,
+                 std::vector<pid_t> *addedThreads, std::vector<pid_t> *removedThreads);
+
+    size_t sizeSessions() const;
+
+    size_t sizeTasks() const;
+
+    // Given task id, for each linked-to session id call fn
+    template <typename FN>
+    void forEachSessionInTask(pid_t taskId, FN fn) const {
+        auto taskSessItr = mTasks.find(taskId);
+        if (taskSessItr == mTasks.end()) {
+            return;
+        }
+        for (const auto session : taskSessItr->second) {
+            auto sessionItr = mSessions.find(session->sessionId);
+            if (sessionItr == mSessions.end()) {
+                continue;
+            }
+            fn(sessionItr->first, *(sessionItr->second.val));
+        }
+    }
+
+    // Iterate over all entries in session map and run callback fn
+    // fn takes int64_t session id, session entry val, linked task ids
+    template <typename FN>
+    void forEachSessionValTasks(FN fn) const {
+        for (const auto &e : mSessions) {
+            fn(e.first, *(e.second.val), e.second.linkedTasks);
+        }
+    }
+
+    // Returns string id of session
+    const std::string &idString(int64_t sessionId) const;
+
+    // Returns true if session id is an app session id
+    bool isAppSession(int64_t sessionId) const;
+
+    // Remove dead task-session map entry
+    bool removeDeadTaskSessionMap(int64_t sessionId, pid_t taskId);
+
+  private:
+    // Internal struct to hold per-session data and linked tasks
+    struct ValEntry {
+        std::shared_ptr<SessionValueEntry> val;
+        std::vector<pid_t> linkedTasks;
+    };
+    // Map session id to value
+    std::unordered_map<int64_t, ValEntry> mSessions;
+    // Map task id to set of session ids
+    std::unordered_map<pid_t, std::vector<std::shared_ptr<SessionValueEntry>>> mTasks;
+};
+
+}  // namespace pixel
+}  // namespace impl
+}  // namespace power
+}  // namespace hardware
+}  // namespace google
+}  // namespace aidl
diff --git a/power-libperfmgr/aidl/SessionValueEntry.cpp b/power-libperfmgr/aidl/SessionValueEntry.cpp
new file mode 100644
index 0000000..b420ae8
--- /dev/null
+++ b/power-libperfmgr/aidl/SessionValueEntry.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#include "SessionValueEntry.h"
+
+#include <sstream>
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+std::ostream &SessionValueEntry::dump(std::ostream &os) const {
+    auto timeNow = std::chrono::steady_clock::now();
+    os << "ID.Min.Act(" << idString;
+    if (votes) {
+        UclampRange uclampRange;
+        votes->getUclampRange(&uclampRange, timeNow);
+        os << ", " << uclampRange.uclampMin;
+        os << "-" << uclampRange.uclampMax;
+    } else {
+        os << ", votes nullptr";
+    }
+    os << ", " << isActive;
+    return os;
+}
+
+}  // namespace pixel
+}  // namespace impl
+}  // namespace power
+}  // namespace hardware
+}  // namespace google
+}  // namespace aidl
diff --git a/power-libperfmgr/aidl/SessionValueEntry.h b/power-libperfmgr/aidl/SessionValueEntry.h
new file mode 100644
index 0000000..f43dba4
--- /dev/null
+++ b/power-libperfmgr/aidl/SessionValueEntry.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <ostream>
+
+#include "AdpfTypes.h"
+#include "UClampVoter.h"
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+// Per-power-session values (equivalent to original PowerHintSession)
+// Responsible for maintaining the state of the power session via attributes
+// Primarily this means actual uclamp value and whether session is active
+// (i.e. whether to include this power session uclmap when setting task uclamp)
+struct SessionValueEntry {
+    int64_t sessionId{0};
+    // Thread group id
+    int64_t tgid{0};
+    uid_t uid{0};
+    std::string idString;
+    bool isActive{true};
+    bool isAppSession{false};
+    std::chrono::steady_clock::time_point lastUpdatedTime;
+    std::shared_ptr<Votes> votes;
+
+    // Write info about power session to ostream for logging and debugging
+    std::ostream &dump(std::ostream &os) const;
+};
+
+}  // namespace pixel
+}  // namespace impl
+}  // namespace power
+}  // namespace hardware
+}  // namespace google
+}  // namespace aidl
diff --git a/power-libperfmgr/aidl/UClampVoter.cpp b/power-libperfmgr/aidl/UClampVoter.cpp
new file mode 100644
index 0000000..251f541
--- /dev/null
+++ b/power-libperfmgr/aidl/UClampVoter.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#include "UClampVoter.h"
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+static void confine(UclampRange *uclampRange, const VoteRange &vr,
+                    std::chrono::steady_clock::time_point t) {
+    if (!vr.isTimeInRange(t)) {
+        return;
+    }
+    uclampRange->uclampMin = std::max(uclampRange->uclampMin, vr.uclampMin());
+    uclampRange->uclampMax = std::min(uclampRange->uclampMax, vr.uclampMax());
+}
+
+VoteRange VoteRange::makeMinRange(int uclampMin, std::chrono::steady_clock::time_point startTime,
+                                  std::chrono::nanoseconds durationNs) {
+    VoteRange v(true, uclampMin, kUclampMax, startTime, durationNs);
+    return v;
+}
+
+std::ostream &operator<<(std::ostream &o, const VoteRange &vr) {
+    o << "[" << vr.uclampMin() << "," << vr.uclampMax() << "]";
+    return o;
+}
+
+Votes::Votes() {}
+
+void Votes::add(int voteId, const VoteRange &v) {
+    mVotes[voteId] = v;
+}
+
+void Votes::updateDuration(int voteId, std::chrono::nanoseconds durationNs) {
+    auto voteItr = mVotes.find(voteId);
+    if (voteItr != mVotes.end()) {
+        voteItr->second.updateDuration(durationNs);
+    }
+}
+
+void Votes::getUclampRange(UclampRange *uclampRange,
+                           std::chrono::steady_clock::time_point t) const {
+    if (nullptr == uclampRange) {
+        return;
+    }
+    for (const auto &v : mVotes) {
+        confine(uclampRange, v.second, t);
+    }
+}
+
+bool Votes::anyTimedOut(std::chrono::steady_clock::time_point t) const {
+    for (const auto &v : mVotes) {
+        if (!v.second.isTimeInRange(t)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool Votes::allTimedOut(std::chrono::steady_clock::time_point t) const {
+    for (const auto &v : mVotes) {
+        if (v.second.isTimeInRange(t)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool Votes::remove(int voteId) {
+    auto itr = mVotes.find(voteId);
+    if (itr == mVotes.end()) {
+        return false;
+    }
+    mVotes.erase(itr);
+    return true;
+}
+
+bool Votes::setUseVote(int voteId, bool active) {
+    auto itr = mVotes.find(voteId);
+    if (itr == mVotes.end()) {
+        return false;
+    }
+    itr->second.setActive(active);
+    return true;
+}
+
+size_t Votes::size() const {
+    return mVotes.size();
+}
+
+bool Votes::voteIsActive(int voteId) {
+    auto itr = mVotes.find(voteId);
+    if (itr == mVotes.end()) {
+        return false;
+    }
+    return itr->second.active();
+}
+
+std::chrono::steady_clock::time_point Votes::voteTimeout(int voteId) {
+    auto itr = mVotes.find(voteId);
+    if (itr == mVotes.end()) {
+        return std::chrono::steady_clock::time_point{};
+    }
+    return itr->second.startTime() + itr->second.durationNs();
+}
+
+}  // namespace pixel
+}  // namespace impl
+}  // namespace power
+}  // namespace hardware
+}  // namespace google
+}  // namespace aidl
diff --git a/power-libperfmgr/aidl/UClampVoter.h b/power-libperfmgr/aidl/UClampVoter.h
new file mode 100644
index 0000000..4b9df33
--- /dev/null
+++ b/power-libperfmgr/aidl/UClampVoter.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+#include <ostream>
+#include <unordered_map>
+
+#include "AdpfTypes.h"
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+// --------------------------------------------------------
+// Hold a min and max for acceptable uclamp values
+struct UclampRange {
+    int uclampMin{kUclampMin};
+    int uclampMax{kUclampMax};
+};
+
+// --------------------------------------------------------
+// Hold a min max range of acceptable votes, is active status, time duration
+// info, and helper methods for consistent use
+class VoteRange {
+  public:
+    VoteRange() {}
+
+    VoteRange(bool active, int uclampMin, int uclampMax,
+              std::chrono::steady_clock::time_point startTime, std::chrono::nanoseconds durationNs)
+        : mActive(active),
+          mUclampRange({std::min(uclampMin, uclampMax), std::max(uclampMin, uclampMax)}),
+          mStartTime(startTime),
+          mDurationNs(durationNs) {}
+
+    // Returns true if this vote range is active, false if it is not active
+    bool active() const { return mActive; }
+
+    // Returns the utilization clamp minimum
+    int uclampMin() const { return mUclampRange.uclampMin; }
+
+    // Returns the utilization clamp maximum
+    int uclampMax() const { return mUclampRange.uclampMax; }
+
+    // Returns the start time of this vote range
+    std::chrono::steady_clock::time_point startTime() const { return mStartTime; }
+
+    // Returns the duration in nanoseconds of the vote range
+    std::chrono::nanoseconds durationNs() const { return mDurationNs; }
+
+    // Set the is active flag to bool param
+    void setActive(bool active) { mActive = active; }
+
+    // Update the vote duration
+    void updateDuration(std::chrono::nanoseconds durationNs) { mDurationNs = durationNs; }
+
+    // Return true if time point parameter in range of startTime to startTime+duration
+    inline bool isTimeInRange(std::chrono::steady_clock::time_point t) const {
+        return mActive && ((mStartTime <= t) && ((mStartTime + mDurationNs) >= t));
+    }
+
+    // Factory method to make a vote range
+    static VoteRange makeMinRange(int uclampMin, std::chrono::steady_clock::time_point startTime,
+                                  std::chrono::nanoseconds durationNs);
+
+  private:
+    bool mActive{true};
+    UclampRange mUclampRange;
+    std::chrono::steady_clock::time_point mStartTime{};
+    std::chrono::nanoseconds mDurationNs{};
+};
+
+// Helper for logging
+std::ostream &operator<<(std::ostream &o, const VoteRange &vr);
+
+// --------------------------------------------------------
+// Thread safe collection of votes that can be used to get
+// a clamped range
+class Votes {
+  public:
+    Votes();
+
+    // Add a vote and associate with vote id, overwrites existing vote
+    void add(int voteId, const VoteRange &v);
+
+    // Update the duration of a vote given a vote id
+    void updateDuration(int voteId, std::chrono::nanoseconds durationNs);
+
+    // Given input UclampRange, and a time point now, increase the min and
+    // decrease max if this VoteRange is in range, return UclampRange with
+    // the largest min and the smallest max
+    void getUclampRange(UclampRange *uclampRange, std::chrono::steady_clock::time_point t) const;
+
+    // Return true if any vote has timed out, otherwise return false
+    bool anyTimedOut(std::chrono::steady_clock::time_point t) const;
+
+    // Return true if all votes have timed out, otherwise return false
+    bool allTimedOut(std::chrono::steady_clock::time_point t) const;
+
+    // Remove vote based on vote vote id, return true if remove was successful,
+    // false if remove failed for example no vote with that id exists
+    bool remove(int voteId);
+
+    // Turn on/off vote
+    bool setUseVote(int voteId, bool active);
+
+    // Return number of votes
+    size_t size() const;
+
+    bool voteIsActive(int voteId);
+
+    std::chrono::steady_clock::time_point voteTimeout(int voteId);
+
+  private:
+    std::unordered_map<int, VoteRange> mVotes;
+};
+
+}  // namespace pixel
+}  // namespace impl
+}  // namespace power
+}  // namespace hardware
+}  // namespace google
+}  // namespace aidl
diff --git a/power-libperfmgr/aidl/android.hardware.power-service.pixel-libperfmgr.rc b/power-libperfmgr/aidl/android.hardware.power-service.pixel-libperfmgr.rc
index 5dc9c9e..b6e7b83 100644
--- a/power-libperfmgr/aidl/android.hardware.power-service.pixel-libperfmgr.rc
+++ b/power-libperfmgr/aidl/android.hardware.power-service.pixel-libperfmgr.rc
@@ -7,6 +7,10 @@
 on late-fs
      start vendor.power-hal-aidl
 
+# Unblock thermalHAL under off mode charge
+on charger
+    start vendor.power-hal-aidl
+
 # Restart powerHAL when framework died
 on property:init.svc.zygote=restarting && property:vendor.powerhal.state=*
     setprop vendor.powerhal.state ""
diff --git a/power-libperfmgr/aidl/android.hardware.power-service.pixel.xml b/power-libperfmgr/aidl/android.hardware.power-service.pixel.xml
index f5dd6b9..418fb83 100644
--- a/power-libperfmgr/aidl/android.hardware.power-service.pixel.xml
+++ b/power-libperfmgr/aidl/android.hardware.power-service.pixel.xml
@@ -1,7 +1,7 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>android.hardware.power</name>
-        <version>4</version>
+        <version>5</version>
         <fqname>IPower/default</fqname>
     </hal>
 </manifest>
diff --git a/power-libperfmgr/aidl/service.cpp b/power-libperfmgr/aidl/service.cpp
index c3e2dd1..3d8d6a7 100644
--- a/power-libperfmgr/aidl/service.cpp
+++ b/power-libperfmgr/aidl/service.cpp
@@ -33,13 +33,13 @@
 using aidl::google::hardware::power::impl::pixel::DisplayLowPower;
 using aidl::google::hardware::power::impl::pixel::Power;
 using aidl::google::hardware::power::impl::pixel::PowerExt;
-using aidl::google::hardware::power::impl::pixel::PowerHintMonitor;
 using aidl::google::hardware::power::impl::pixel::PowerSessionManager;
 using ::android::perfmgr::HintManager;
 
 constexpr std::string_view kPowerHalInitProp("vendor.powerhal.init");
 
 int main() {
+    android::base::SetDefaultTag(LOG_TAG);
     // Parse config but do not start the looper
     std::shared_ptr<HintManager> hm = HintManager::GetInstance();
     if (!hm) {
@@ -69,10 +69,6 @@
     CHECK(status == STATUS_OK);
     LOG(INFO) << "Pixel Power HAL AIDL Service with Extension is started.";
 
-    if (HintManager::GetInstance()->GetAdpfProfile()) {
-        PowerHintMonitor::getInstance()->start();
-    }
-
     std::thread initThread([&]() {
         ::android::base::WaitForProperty(kPowerHalInitProp.data(), "1");
         HintManager::GetInstance()->Start();
diff --git a/power-libperfmgr/aidl/tests/BackgroundWorkerTest.cpp b/power-libperfmgr/aidl/tests/BackgroundWorkerTest.cpp
new file mode 100644
index 0000000..14caea8
--- /dev/null
+++ b/power-libperfmgr/aidl/tests/BackgroundWorkerTest.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "aidl/BackgroundWorker.h"
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+using std::literals::chrono_literals::operator""s;
+using std::literals::chrono_literals::operator""ms;
+using std::literals::chrono_literals::operator""ns;
+
+constexpr double kTIMING_TOLERANCE_MS = std::chrono::milliseconds(25).count();
+
+// Use this work to package some work identifier val along with time t of when it was
+// processed to validate that the waiting time is adhered to as closely as possible
+struct work {
+    int64_t val{0};
+    std::chrono::steady_clock::time_point t;
+};
+
+auto getDurationMs(std::chrono::steady_clock::time_point endTime,
+                   std::chrono::steady_clock::time_point startTime) {
+    return std::chrono::duration<double, std::milli>(endTime - startTime);
+}
+
+TEST(PriorityQueueWorkerPool, testSingle) {
+    const int pqId = 1;
+    std::condition_variable cv;
+    std::mutex m;
+    std::vector<work> vec;
+    vec.reserve(3);
+
+    auto p = std::make_shared<PriorityQueueWorkerPool>(1, "adpf_");
+    p->addCallback(pqId, [&](int64_t packageId) {
+        std::lock_guard<std::mutex> lock(m);
+        vec.push_back({packageId, std::chrono::steady_clock::now()});
+        cv.notify_all();
+    });
+
+    const auto tNow = std::chrono::steady_clock::now();
+    p->schedule(pqId, 500, tNow + 500ms);
+    p->schedule(pqId, 100, tNow + 100ms);
+    p->schedule(pqId, 300, tNow + 300ms);
+
+    std::unique_lock<std::mutex> lock(m);
+    EXPECT_EQ(0, vec.size());
+    cv.wait_for(lock, 1500ms, [&]() { return vec.size() == 3; });
+
+    EXPECT_EQ(3, vec.size());
+    EXPECT_EQ(100, vec[0].val);
+    EXPECT_NEAR(100, getDurationMs(vec[0].t, tNow).count(), kTIMING_TOLERANCE_MS);
+    EXPECT_EQ(300, vec[1].val);
+    EXPECT_NEAR(300, getDurationMs(vec[1].t, tNow).count(), kTIMING_TOLERANCE_MS);
+    EXPECT_EQ(500, vec[2].val);
+    EXPECT_NEAR(500, getDurationMs(vec[2].t, tNow).count(), kTIMING_TOLERANCE_MS);
+}
+
+TEST(TemplatePriorityQueueWorker, testSingle) {
+    std::condition_variable cv;
+    std::mutex m;
+    std::vector<work> vec;
+    vec.reserve(3);
+
+    auto p = std::make_shared<PriorityQueueWorkerPool>(1, "adpf_");
+    TemplatePriorityQueueWorker<int> worker{
+            [&](int i) {
+                std::lock_guard<std::mutex> lock(m);
+                vec.push_back({i, std::chrono::steady_clock::now()});
+                cv.notify_all();
+            },
+            p};
+
+    // Would be nice to have a pause option for testing
+    const auto tNow = std::chrono::steady_clock::now();
+    worker.schedule(303, tNow + 500ms);
+    worker.schedule(101, tNow + 100ms);
+    worker.schedule(202, tNow + 300ms);
+
+    std::unique_lock<std::mutex> lock(m);
+    EXPECT_EQ(0, vec.size());
+    cv.wait_for(lock, 1500ms, [&]() { return vec.size() == 3; });
+
+    EXPECT_EQ(3, vec.size());
+    EXPECT_EQ(101, vec[0].val);
+    EXPECT_NEAR(100, getDurationMs(vec[0].t, tNow).count(), kTIMING_TOLERANCE_MS);
+    EXPECT_EQ(202, vec[1].val);
+    EXPECT_NEAR(300, getDurationMs(vec[1].t, tNow).count(), kTIMING_TOLERANCE_MS);
+    EXPECT_EQ(303, vec[2].val);
+    EXPECT_NEAR(500, getDurationMs(vec[2].t, tNow).count(), kTIMING_TOLERANCE_MS);
+}
+
+TEST(TemplatePriorityQueueWorker, testDouble) {
+    std::condition_variable cv;
+    std::mutex m;
+    std::vector<work> vec;
+    vec.reserve(6);
+
+    auto p = std::make_shared<PriorityQueueWorkerPool>(1, "adpf_");
+    TemplatePriorityQueueWorker<int> worker1{
+            [&](int i) {
+                std::lock_guard<std::mutex> lock(m);
+                vec.push_back({i, std::chrono::steady_clock::now()});
+                cv.notify_all();
+            },
+            p};
+
+    TemplatePriorityQueueWorker<std::string> worker2{
+            [&](const std::string &s) {
+                std::lock_guard<std::mutex> lock(m);
+                vec.push_back({atoi(s.c_str()), std::chrono::steady_clock::now()});
+                cv.notify_all();
+            },
+            p};
+
+    // Would be nice to have a pause option for testing
+    const auto tNow = std::chrono::steady_clock::now();
+    worker1.schedule(5, tNow + 300ms);
+    worker1.schedule(1, tNow + 100ms);
+    worker1.schedule(3, tNow + 200ms);
+    worker2.schedule("2", tNow + 150ms);
+    worker2.schedule("4", tNow + 250ms);
+    worker2.schedule("6", tNow + 350ms);
+
+    std::unique_lock<std::mutex> lock(m);
+    EXPECT_EQ(0, vec.size());
+    cv.wait_for(lock, 1500ms, [&]() { return vec.size() == 6; });
+
+    EXPECT_EQ(6, vec.size());
+    EXPECT_EQ(1, vec[0].val);
+    EXPECT_NEAR(100, getDurationMs(vec[0].t, tNow).count(), kTIMING_TOLERANCE_MS);
+    EXPECT_EQ(2, vec[1].val);
+    EXPECT_NEAR(150, getDurationMs(vec[1].t, tNow).count(), kTIMING_TOLERANCE_MS);
+    EXPECT_EQ(3, vec[2].val);
+    EXPECT_NEAR(200, getDurationMs(vec[2].t, tNow).count(), kTIMING_TOLERANCE_MS);
+    EXPECT_EQ(4, vec[3].val);
+    EXPECT_NEAR(250, getDurationMs(vec[3].t, tNow).count(), kTIMING_TOLERANCE_MS);
+    EXPECT_EQ(5, vec[4].val);
+    EXPECT_NEAR(300, getDurationMs(vec[4].t, tNow).count(), kTIMING_TOLERANCE_MS);
+    EXPECT_EQ(6, vec[5].val);
+    EXPECT_NEAR(350, getDurationMs(vec[5].t, tNow).count(), kTIMING_TOLERANCE_MS);
+}
+
+}  // namespace pixel
+}  // namespace impl
+}  // namespace power
+}  // namespace hardware
+}  // namespace google
+}  // namespace aidl
diff --git a/power-libperfmgr/aidl/tests/PowerHintSessionTest.cpp b/power-libperfmgr/aidl/tests/PowerHintSessionTest.cpp
new file mode 100644
index 0000000..110889f
--- /dev/null
+++ b/power-libperfmgr/aidl/tests/PowerHintSessionTest.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include <gtest/gtest.h>
+#include <sys/syscall.h>
+
+#include <chrono>
+#include <mutex>
+#include <thread>
+#include <unordered_map>
+#include <vector>
+
+// define private as public to expose the private members for test.
+#define private public
+#include "aidl/PowerHintSession.h"
+#include "aidl/PowerSessionManager.h"
+
+#define gettid() syscall(SYS_gettid)
+
+using std::literals::chrono_literals::operator""ms;
+using std::literals::chrono_literals::operator""ns;
+using std::literals::chrono_literals::operator""s;
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+class PowerHintSessionTest : public ::testing::Test {
+  public:
+    void SetUp() {
+        // create a list of threads
+        for (int i = 0; i < numOfThreads; i++) {
+            threadIsAlive.emplace_back(true);
+            threadList.emplace_back(std::thread([this, threadInd = i]() {
+                ALOGI("Test thread %d is running.", (int32_t)gettid());
+                {
+                    std::lock_guard<std::mutex> lock(m);
+                    threadIds[threadInd] = gettid();
+                }
+                while (threadIsAlive[threadInd]) {
+                    std::this_thread::sleep_for(50ms);
+                }
+                ALOGI("Test thread %d is closed.", (int32_t)gettid());
+            }));
+        }
+        std::this_thread::sleep_for(50ms);
+
+        // create two hint sessions
+        for (int i = 0; i < numOfThreads; i++) {
+            if (i <= numOfThreads / 2) {
+                session1Threads.emplace_back(threadIds[i]);
+            }
+
+            if (i >= numOfThreads / 2) {
+                session2Threads.emplace_back(threadIds[i]);
+            }
+        }
+
+        sess1 = ndk::SharedRefBase::make<PowerHintSession>(1, 1, session1Threads, 1000000);
+        sess2 = ndk::SharedRefBase::make<PowerHintSession>(2, 2, session2Threads, 1000000);
+    }
+
+    void TearDown() {
+        for (int i = 0; i < numOfThreads; i++) {
+            if (threadIsAlive[i]) {
+                threadIsAlive[i] = false;
+                threadList[i].join();
+            }
+        }
+        threadList.clear();
+        threadIds.clear();
+        threadIsAlive.clear();
+        session1Threads.clear();
+        session2Threads.clear();
+    }
+
+  protected:
+    static const int numOfThreads = 3;
+    std::vector<std::thread> threadList;
+    std::unordered_map<int, int32_t> threadIds;
+    std::vector<bool> threadIsAlive;
+    std::mutex m;
+    std::vector<int32_t> session1Threads;
+    std::vector<int32_t> session2Threads;
+    std::shared_ptr<PowerHintSession> sess1;
+    std::shared_ptr<PowerHintSession> sess2;
+
+    // close the i-th thread in thread list.
+    void closeThread(int i) {
+        if (i < 0 || i >= numOfThreads)
+            return;
+        if (threadIsAlive[i]) {
+            threadIsAlive[i] = false;
+            threadList[i].join();
+        }
+    }
+};
+
+TEST_F(PowerHintSessionTest, removeDeadThread) {
+    ALOGI("Running dead thread test for hint sessions.");
+    auto sessManager = sess1->mPSManager;
+    ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size());
+
+    // The sessions' thread list doesn't change after thread died until the uclamp
+    // min update is triggered.
+    int deadThreadInd = numOfThreads / 2;
+    auto deadThreadID = threadIds[deadThreadInd];
+    closeThread(deadThreadInd);
+    ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks,
+              session1Threads);
+    ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess2->mSessionId].linkedTasks,
+              session2Threads);
+    ASSERT_EQ(sessManager->mSessionTaskMap.mTasks[deadThreadID].size(), 2);
+
+    // Trigger an update of uclamp min.
+    auto tNow = std::chrono::duration_cast<std::chrono::nanoseconds>(
+                        std::chrono::high_resolution_clock::now().time_since_epoch())
+                        .count();
+    WorkDuration wDur(tNow, 1100000);
+    sess1->reportActualWorkDuration(std::vector<WorkDuration>{wDur});
+    ASSERT_EQ(sessManager->mSessionTaskMap.mTasks[deadThreadID].size(), 1);
+    sess2->reportActualWorkDuration(std::vector<WorkDuration>{wDur});
+    ASSERT_EQ(sessManager->mSessionTaskMap.mTasks.count(deadThreadID), 0);
+    std::erase(session1Threads, deadThreadID);
+    std::erase(session2Threads, deadThreadID);
+    ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks,
+              session1Threads);
+    ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess2->mSessionId].linkedTasks,
+              session2Threads);
+
+    // Close all the threads in session 1.
+    for (int i = 0; i <= numOfThreads / 2; i++) {
+        closeThread(i);
+    }
+    tNow = std::chrono::duration_cast<std::chrono::nanoseconds>(
+                   std::chrono::high_resolution_clock::now().time_since_epoch())
+                   .count();
+    sess1->reportActualWorkDuration(std::vector<WorkDuration>{wDur});
+    ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size());  // Session still alive
+    ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks.size(), 0);
+}
+
+TEST_F(PowerHintSessionTest, setThreads) {
+    auto sessManager = sess1->mPSManager;
+    ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size());
+
+    ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks,
+              session1Threads);
+
+    std::vector<int32_t> newSess1Threads;
+    for (auto tid : threadIds) {
+        newSess1Threads.emplace_back(tid.second);
+    }
+    sess1->setThreads(newSess1Threads);
+    ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks,
+              newSess1Threads);
+
+    sess1->close();
+    sess2->close();
+}
+
+}  // namespace pixel
+}  // namespace impl
+}  // namespace power
+}  // namespace hardware
+}  // namespace google
+}  // namespace aidl
diff --git a/power-libperfmgr/aidl/tests/SessionTaskMapTest.cpp b/power-libperfmgr/aidl/tests/SessionTaskMapTest.cpp
new file mode 100644
index 0000000..7f7dab3
--- /dev/null
+++ b/power-libperfmgr/aidl/tests/SessionTaskMapTest.cpp
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "aidl/SessionTaskMap.h"
+
+using std::literals::chrono_literals::operator""ms;
+using std::literals::chrono_literals::operator""ns;
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+SessionValueEntry makeSession(int tg) {
+    SessionValueEntry sv;
+    sv.tgid = tg;
+    sv.uid = tg + 1;
+    sv.idString = "Sess" + std::to_string(tg);
+    sv.isActive = true;
+    sv.isAppSession = false;
+    sv.lastUpdatedTime = std::chrono::steady_clock::now();
+    sv.votes = std::make_shared<Votes>();
+    return sv;
+}
+
+// Get all sessions associated with taskId
+std::vector<int64_t> getSessions(int taskId, const SessionTaskMap &m) {
+    std::vector<int64_t> sessionIds;
+    m.forEachSessionInTask(
+            taskId, [&](int sessionId, const auto & /*sve*/) { sessionIds.push_back(sessionId); });
+    std::sort(sessionIds.begin(), sessionIds.end());
+    return sessionIds;
+}
+
+// Get all tasks associated with sessionId
+std::vector<int> getTasks(int64_t sessionId, const SessionTaskMap &m) {
+    std::vector<int> tasks;
+    m.forEachSessionValTasks([&](int64_t sessId, const auto & /*sve*/, const auto &linkedTasks) {
+        if (sessId != sessionId)
+            return;
+        tasks.insert(std::end(tasks), std::begin(linkedTasks), std::end(linkedTasks));
+    });
+    std::sort(tasks.begin(), tasks.end());
+    return tasks;
+}
+
+// Tests ...
+TEST(SessionTaskMapTest, add) {
+    SessionTaskMap m;
+    EXPECT_TRUE(m.add(1, makeSession(1000), {10, 20, 30}));
+    EXPECT_TRUE(m.add(2, makeSession(2000), {40, 50}));
+    EXPECT_TRUE(m.add(3, makeSession(2000), {60}));
+    EXPECT_FALSE(m.add(3, makeSession(2000), {70}));
+}
+
+TEST(SessionTaskMapTest, threeWayMappingSessions) {
+    SessionTaskMap m;
+    m.add(1, makeSession(1000), {10, 20, 30});
+    m.add(2, makeSession(2000), {40, 50, 60});
+    m.add(3, makeSession(3000), {50});
+
+    // Check three tasks map properly to sessions
+    EXPECT_EQ(std::vector<int64_t>({1}), getSessions(10, m));
+    EXPECT_EQ(std::vector<int64_t>({1}), getSessions(20, m));
+    EXPECT_EQ(std::vector<int64_t>({1}), getSessions(30, m));
+    EXPECT_EQ(std::vector<int64_t>({2}), getSessions(40, m));
+    EXPECT_EQ(std::vector<int64_t>({2, 3}), getSessions(50, m));
+    EXPECT_EQ(std::vector<int64_t>({2}), getSessions(60, m));
+}
+
+TEST(SessionTaskMapTest, threeWayMappingTasks) {
+    SessionTaskMap m;
+    m.add(1, makeSession(1000), {10, 20, 30});
+    m.add(2, makeSession(2000), {40, 50, 60});
+    m.add(3, makeSession(3000), {50});
+
+    // Check three sessions map properly to tasks
+    EXPECT_EQ(std::vector<int>({10, 20, 30}), getTasks(1, m));
+    EXPECT_EQ(std::vector<int>({40, 50, 60}), getTasks(2, m));
+    EXPECT_EQ(std::vector<int>({50}), getTasks(3, m));
+}
+
+TEST(SessionTaskMapTest, removeNonExisting) {
+    SessionTaskMap m;
+    EXPECT_FALSE(m.remove(1));
+}
+
+TEST(SessionTaskMapTest, removeMappingSessions) {
+    SessionTaskMap m;
+    m.add(1, makeSession(1000), {10, 20, 30});
+    m.add(2, makeSession(2000), {40, 50, 60});
+    m.add(3, makeSession(3000), {50});
+
+    // remove
+    EXPECT_TRUE(m.remove(2));
+
+    // Check that remaining tasks map correctly to sessions
+    EXPECT_EQ(std::vector<int64_t>({1}), getSessions(10, m));
+    EXPECT_EQ(std::vector<int64_t>({1}), getSessions(20, m));
+    EXPECT_EQ(std::vector<int64_t>({1}), getSessions(30, m));
+    EXPECT_EQ(std::vector<int64_t>({}), getSessions(40, m));
+    EXPECT_EQ(std::vector<int64_t>({3}), getSessions(50, m));
+}
+
+TEST(SessionTaskMapTest, removeMappingTasks) {
+    SessionTaskMap m;
+    EXPECT_FALSE(m.remove(1));
+
+    m.add(1, makeSession(1000), {10, 20, 30});
+    m.add(2, makeSession(2000), {40, 50, 60});
+    m.add(3, makeSession(3000), {50});
+
+    // remove
+    EXPECT_TRUE(m.remove(2));
+    EXPECT_FALSE(m.remove(2));
+
+    // Check that remaining tasks map correctly to sessions
+    EXPECT_EQ(std::vector<int>({10, 20, 30}), getTasks(1, m));
+    EXPECT_EQ(std::vector<int>({}), getTasks(2, m));
+    EXPECT_EQ(std::vector<int>({50}), getTasks(3, m));
+}
+
+TEST(SessionTaskMapTest, findEmpty) {
+    SessionTaskMap m;
+    EXPECT_EQ(nullptr, m.findSession(1));
+}
+
+TEST(SessionTaskMapTest, findSessionExists) {
+    SessionTaskMap m;
+    EXPECT_TRUE(m.add(1, makeSession(1000), {}));
+    EXPECT_NE(nullptr, m.findSession(1));
+}
+
+TEST(SessionTaskMapTest, findSessionEmptyExistsEmpty) {
+    SessionTaskMap m;
+    EXPECT_EQ(nullptr, m.findSession(1));
+    EXPECT_TRUE(m.add(1, makeSession(1000), {}));
+    EXPECT_NE(nullptr, m.findSession(1));
+    EXPECT_TRUE(m.remove(1));
+    EXPECT_EQ(nullptr, m.findSession(1));
+}
+
+TEST(SessionTaskMapTest, sizeTasks) {
+    SessionTaskMap m;
+    EXPECT_EQ(0, m.sizeTasks());
+    EXPECT_TRUE(m.add(1, makeSession(1000), {10, 20, 30}));
+    EXPECT_TRUE(m.add(2, makeSession(2000), {40, 50, 60}));
+    EXPECT_EQ(6, m.sizeTasks());
+}
+
+TEST(SessionTaskMapTest, sizeSessions) {
+    SessionTaskMap m;
+    EXPECT_EQ(0, m.sizeSessions());
+    EXPECT_TRUE(m.add(1, makeSession(1000), {10, 20, 30}));
+    EXPECT_TRUE(m.add(2, makeSession(2000), {40, 50, 60}));
+    EXPECT_EQ(2, m.sizeSessions());
+}
+
+TEST(SessionTaskMapTest, replace) {
+    SessionTaskMap m;
+
+    // Add three sessions where sessions 2 and 3 have shared threads
+    EXPECT_TRUE(m.add(1, makeSession(1000), {10, 20, 30}));
+    EXPECT_TRUE(m.add(2, makeSession(2000), {20}));
+
+    std::vector<pid_t> addedThreads;
+    std::vector<pid_t> removedThreads;
+
+    m.replace(1, {10, 40}, &addedThreads, &removedThreads);
+    EXPECT_EQ(1, addedThreads.size());
+    EXPECT_EQ(40, addedThreads[0]);
+
+    EXPECT_EQ(1, removedThreads.size());
+    EXPECT_EQ(30, removedThreads[0]);
+}
+
+TEST(SessionTaskMapTest, remove) {
+    SessionTaskMap m;
+    auto tNow = std::chrono::steady_clock::now();
+    const int64_t sessionId = 1;
+    SessionValueEntry sve;
+    sve.isAppSession = true;
+    sve.votes = std::make_shared<Votes>();
+    sve.votes->add(sessionId, VoteRange::makeMinRange(123, tNow, 400ms));
+    m.add(sessionId, sve, {10, 20, 30});
+    EXPECT_TRUE(m.isAnyAppSessionActive(tNow));
+    EXPECT_TRUE(m.remove(sessionId));
+    EXPECT_FALSE(m.isAnyAppSessionActive(tNow));
+}
+
+TEST(SessionTaskMapTest, isAnyAppActive) {
+    SessionTaskMap m;
+    auto tNow = std::chrono::steady_clock::now();
+
+    EXPECT_FALSE(m.isAnyAppSessionActive(tNow));
+
+    const int sessionId = 1000;
+    SessionValueEntry sv;
+    sv.isActive = true;
+    sv.isAppSession = true;
+    sv.lastUpdatedTime = tNow;
+    sv.votes = std::make_shared<Votes>();
+    sv.votes->add(1, VoteRange::makeMinRange(123, tNow, 400ms));
+
+    EXPECT_TRUE(m.add(sessionId, sv, {10, 20, 30}));
+    EXPECT_TRUE(m.isAnyAppSessionActive(tNow));
+    EXPECT_FALSE(m.isAnyAppSessionActive(tNow + 500ms));
+}
+
+int getVoteMin(const SessionTaskMap &m, int64_t taskId, std::chrono::steady_clock::time_point t) {
+    int uclampMin;
+    int uclampMax;
+    m.getTaskVoteRange(taskId, t, &uclampMin, &uclampMax);
+    return uclampMin;
+}
+
+TEST(SessionTaskMapTest, votesEdgeCaseOverlap) {
+    SessionTaskMap m;
+
+    // Sess 1: 10
+    EXPECT_TRUE(m.add(1, makeSession(1000), {10}));
+    const auto t0 = std::chrono::steady_clock::now();
+    const int voteMax = 1000;
+
+    // Session  Vote  UClamp  [Time start----------------Time End]  Delta
+    // 1        1     111     [20----60]                            40
+    // 1        2     122              [60-85]                      25
+    // 1        3     133              [60--90]                     30
+    m.addVote(1, 1, 111, voteMax, t0 + 20ns, 40ns);
+    m.addVote(1, 2, 122, voteMax, t0 + 60ns, 25ns);
+    m.addVote(1, 3, 133, voteMax, t0 + 60ns, 30ns);
+
+    // Before any votes active
+    EXPECT_EQ(0, getVoteMin(m, 10, t0 + 0ns));
+    // First vote active
+    EXPECT_EQ(111, getVoteMin(m, 10, t0 + 20ns));
+    // In middle of first vote
+    EXPECT_EQ(111, getVoteMin(m, 10, t0 + 35ns));
+    // First, second, and third votes active
+    EXPECT_EQ(133, getVoteMin(m, 10, t0 + 60ns));
+    // Second and third votes are being used
+    EXPECT_EQ(133, getVoteMin(m, 10, t0 + 61ns));
+    // Third vote is being used
+    EXPECT_EQ(133, getVoteMin(m, 10, t0 + 86ns));
+    // No votes active
+    EXPECT_EQ(0, getVoteMin(m, 10, t0 + 91ns));
+}
+
+TEST(SessionTaskMapTest, votesEdgeCaseNoOverlap) {
+    SessionTaskMap m;
+    // Sess 2: 30
+    EXPECT_TRUE(m.add(2, makeSession(2000), {20}));
+    const auto t0 = std::chrono::steady_clock::now();
+    const int voteMax = 1000;
+
+    // Session  Vote  UClamp  [Time start----------------Time End]  Delta
+    // 2        1     211       [30-55]                             25
+    // 2        2     222                       [100-135]           35
+    // 2        3     233                                [140-180]  40
+    m.addVote(2, 1, 211, voteMax, t0 + 30ns, 25ns);
+    m.addVote(2, 2, 222, voteMax, t0 + 100ns, 35ns);
+    m.addVote(2, 3, 233, voteMax, t0 + 140ns, 40ns);
+
+    // No votes active yet
+    EXPECT_EQ(0, getVoteMin(m, 20, t0 + 0ns));
+    // First vote active
+    EXPECT_EQ(211, getVoteMin(m, 20, t0 + 30ns));
+    // Second vote active
+    EXPECT_EQ(222, getVoteMin(m, 20, t0 + 100ns));
+    // Third vote active
+    EXPECT_EQ(233, getVoteMin(m, 20, t0 + 140ns));
+    // No votes active
+    EXPECT_EQ(0, getVoteMin(m, 20, t0 + 181ns));
+}
+
+TEST(SessionTaskMapTest, TwoSessionsOneInactive) {
+    const auto tNow = std::chrono::steady_clock::now();
+    SessionTaskMap m;
+
+    {
+        SessionValueEntry sv;
+        sv.isActive = true;
+        sv.isAppSession = true;
+        sv.lastUpdatedTime = tNow;
+        sv.votes = std::make_shared<Votes>();
+        sv.votes->add(11, VoteRange::makeMinRange(111, tNow, 400ms));
+        EXPECT_TRUE(m.add(1001, sv, {10, 20, 30}));
+    }
+
+    {
+        SessionValueEntry sv;
+        sv.isActive = true;
+        sv.isAppSession = true;
+        sv.lastUpdatedTime = tNow;
+        sv.votes = std::make_shared<Votes>();
+        sv.votes->add(22, VoteRange::makeMinRange(222, tNow, 400ms));
+        EXPECT_TRUE(m.add(2001, sv, {10, 20, 30}));
+    }
+
+    UclampRange uclampRange;
+    m.getTaskVoteRange(10, tNow + 10ns, &uclampRange.uclampMin, &uclampRange.uclampMax);
+    EXPECT_EQ(222, uclampRange.uclampMin);
+
+    auto sessItr = m.findSession(2001);
+    EXPECT_NE(nullptr, sessItr);
+    sessItr->isActive = false;
+
+    uclampRange.uclampMin = 0;
+    uclampRange.uclampMax = 1024;
+    m.getTaskVoteRange(10, tNow + 10ns, &uclampRange.uclampMin, &uclampRange.uclampMax);
+    EXPECT_EQ(111, uclampRange.uclampMin);
+}
+
+}  // namespace pixel
+}  // namespace impl
+}  // namespace power
+}  // namespace hardware
+}  // namespace google
+}  // namespace aidl
diff --git a/power-libperfmgr/aidl/tests/UClampVoterTest.cpp b/power-libperfmgr/aidl/tests/UClampVoterTest.cpp
new file mode 100644
index 0000000..53418a4
--- /dev/null
+++ b/power-libperfmgr/aidl/tests/UClampVoterTest.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "aidl/UClampVoter.h"
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+using std::literals::chrono_literals::operator""s;
+using std::literals::chrono_literals::operator""ms;
+using std::literals::chrono_literals::operator""ns;
+
+TEST(VoteRange, active) {
+    auto tNow = std::chrono::steady_clock::now();
+    VoteRange vr(true, 101, 202, tNow, 200ms);
+    EXPECT_TRUE(vr.active());
+}
+
+TEST(VoteRange, inActive) {
+    auto tNow = std::chrono::steady_clock::now();
+    VoteRange vr(false, 101, 202, tNow, 200ms);
+    EXPECT_FALSE(vr.active());
+}
+
+TEST(VoteRange, makeMinRange) {
+    auto tNow = std::chrono::steady_clock::now();
+    auto vr = VoteRange::makeMinRange(123, tNow, 210ms);
+    EXPECT_EQ(123, vr.uclampMin());
+    EXPECT_EQ(tNow, vr.startTime());
+    EXPECT_EQ(210ms, vr.durationNs());
+}
+
+TEST(VoteRange, isTimeInRange) {
+    const auto tNow = std::chrono::steady_clock::now();
+    auto voteRange = VoteRange::makeMinRange(234, tNow, 250ms);
+    EXPECT_EQ(234, voteRange.uclampMin());
+    EXPECT_FALSE(voteRange.isTimeInRange(tNow - 1ns));
+    EXPECT_TRUE(voteRange.isTimeInRange(tNow));
+    EXPECT_TRUE(voteRange.isTimeInRange(tNow + 1ns));
+    EXPECT_TRUE(voteRange.isTimeInRange(tNow + 250ms));
+    EXPECT_FALSE(voteRange.isTimeInRange(tNow + 250ms + 1ns));
+}
+
+TEST(VoteRange, isTimeInRangeInActive) {
+    const auto tNow = std::chrono::steady_clock::now();
+    auto voteRange = VoteRange::makeMinRange(345, tNow, 250ms);
+    voteRange.setActive(false);
+    EXPECT_FALSE(voteRange.active());
+    // Still reports 345 as the min even if inactive
+    EXPECT_EQ(345, voteRange.uclampMin());
+    EXPECT_FALSE(voteRange.isTimeInRange(tNow));
+    EXPECT_FALSE(voteRange.isTimeInRange(tNow + 1ns));
+    EXPECT_FALSE(voteRange.isTimeInRange(tNow + 250ms));
+    EXPECT_FALSE(voteRange.isTimeInRange(tNow + 250ms + 1ns));
+}
+
+TEST(VoteRange, getUclampRange) {
+    const auto tNow = std::chrono::steady_clock::now();
+    const auto tNext = tNow + 1s;
+    const auto tEnd1 = tNow + 4000000001ns;
+    const auto tPrev = tNow - 1s;
+
+    const auto voteFirst = VoteRange::makeMinRange(11, tNow, 4000000000ns);
+    EXPECT_FALSE(voteFirst.isTimeInRange(tPrev));
+    EXPECT_TRUE(voteFirst.isTimeInRange(tNext));
+    EXPECT_FALSE(voteFirst.isTimeInRange(tEnd1));
+
+    Votes votes;
+    votes.add(1, voteFirst);
+    UclampRange ur;
+
+    votes.getUclampRange(&ur, tNext);
+    EXPECT_EQ(11, ur.uclampMin);
+    EXPECT_EQ(kUclampMax, ur.uclampMax);
+}
+
+TEST(UclampVoter, simple) {
+    const auto tNow = std::chrono::steady_clock::now();
+
+    auto votes = std::make_shared<Votes>();
+    EXPECT_EQ(0, votes->size());
+
+    votes->add(1, VoteRange::makeMinRange(11, tNow, 4s));
+    EXPECT_EQ(1, votes->size());
+
+    votes->add(2, VoteRange::makeMinRange(22, tNow, 1s));
+    EXPECT_EQ(2, votes->size());
+
+    UclampRange ur1;
+    votes->getUclampRange(&ur1, tNow);
+    EXPECT_EQ(22, ur1.uclampMin);
+    EXPECT_EQ(kUclampMax, ur1.uclampMax);
+
+    UclampRange ur2;
+    votes->getUclampRange(&ur2, tNow + 2s);
+    EXPECT_EQ(11, ur2.uclampMin);
+    EXPECT_EQ(kUclampMax, ur2.uclampMax);
+
+    UclampRange ur3;
+    votes->getUclampRange(&ur3, tNow + 5s);
+    EXPECT_EQ(0, ur3.uclampMin);
+    EXPECT_EQ(1024, ur3.uclampMax);
+
+    EXPECT_FALSE(votes->allTimedOut(tNow + 200ns));
+    EXPECT_TRUE(votes->allTimedOut(tNow + 5s));
+
+    EXPECT_TRUE(votes->setUseVote(2, false));
+    EXPECT_TRUE(votes->anyTimedOut(tNow + 5s));
+    EXPECT_TRUE(votes->setUseVote(2, true));
+
+    EXPECT_FALSE(votes->anyTimedOut(tNow + 200ns));
+}
+
+TEST(UclampVoter, overwrite) {
+    const auto tNow = std::chrono::steady_clock::now();
+
+    auto votes = std::make_shared<Votes>();
+    EXPECT_EQ(0, votes->size());
+
+    votes->add(1, VoteRange::makeMinRange(11, tNow, 4s));
+    EXPECT_EQ(1, votes->size());
+
+    votes->add(2, VoteRange::makeMinRange(22, tNow, 2s));
+    EXPECT_EQ(2, votes->size());
+
+    UclampRange ucr1;
+    votes->getUclampRange(&ucr1, tNow + 1s);
+    EXPECT_EQ(22, ucr1.uclampMin);
+
+    votes->add(1, VoteRange::makeMinRange(32, tNow, 5s));
+    UclampRange ucr2;
+    votes->getUclampRange(&ucr2, tNow + 1s);
+    EXPECT_EQ(32, ucr2.uclampMin);
+}
+
+TEST(UclampVoter, updateDuration) {
+    const auto tNow = std::chrono::steady_clock::now();
+
+    auto votes = std::make_shared<Votes>();
+    EXPECT_EQ(0, votes->size());
+
+    votes->add(1, VoteRange::makeMinRange(11, tNow, 4s));
+    votes->add(2, VoteRange::makeMinRange(22, tNow, 2s));
+    EXPECT_EQ(2, votes->size());
+
+    EXPECT_TRUE(votes->allTimedOut(tNow + 7s));
+    votes->updateDuration(1, 8s);
+    EXPECT_FALSE(votes->allTimedOut(tNow + 7s));
+    votes->updateDuration(5, 10s);
+    EXPECT_TRUE(votes->allTimedOut(tNow + 9s));
+}
+
+TEST(UclampVoter, loadVoteTest) {
+    const int defaultVoteId = 1;
+    const int loadVoteId = 2;
+    const int uclampMinDefault = 50;
+    const int uclampMinInit = 162;
+    const int uclampMinHigh = 450;
+    const auto tNow = std::chrono::steady_clock::now();
+    UclampRange ucr;
+    auto votes = std::make_shared<Votes>();
+
+    // Default: min = 50 (original)
+    votes->add(defaultVoteId, VoteRange::makeMinRange(uclampMinDefault, tNow, 400ms));
+    votes->getUclampRange(&ucr, tNow + 100ms);
+    EXPECT_EQ(uclampMinDefault, ucr.uclampMin);
+
+    // Default: min = UclampMinInit
+    votes->add(defaultVoteId, VoteRange::makeMinRange(uclampMinInit, tNow, 400ns));
+    // Load: min = uclampMinHigh
+    votes->add(loadVoteId, VoteRange::makeMinRange(uclampMinHigh, tNow, 250ns));
+
+    // Check that load is enabled
+    ucr.uclampMin = 0;
+    votes->getUclampRange(&ucr, tNow + 100ns);
+    EXPECT_EQ(uclampMinHigh, ucr.uclampMin);
+
+    // Timeout or restore after 1st frame
+    // Expect to get 162.
+    ucr.uclampMin = 0;
+    votes->getUclampRange(&ucr, tNow + 350ns);
+    EXPECT_EQ(uclampMinInit, ucr.uclampMin);
+}
+
+}  // namespace pixel
+}  // namespace impl
+}  // namespace power
+}  // namespace hardware
+}  // namespace google
+}  // namespace aidl
diff --git a/power-libperfmgr/disp-power/InteractionHandler.cpp b/power-libperfmgr/disp-power/InteractionHandler.cpp
index d2357a6..9cb7f1e 100644
--- a/power-libperfmgr/disp-power/InteractionHandler.cpp
+++ b/power-libperfmgr/disp-power/InteractionHandler.cpp
@@ -48,8 +48,9 @@
 
 static const bool kDisplayIdleSupport =
         ::android::base::GetBoolProperty("vendor.powerhal.disp.idle_support", true);
-static const std::array<const char *, 2> kDispIdlePath = {"/sys/class/drm/card0/device/idle_state",
-                                                          "/sys/class/graphics/fb0/idle_state"};
+static const std::array<const char *, 3> kDispIdlePath = {"/sys/class/drm/card0/device/idle_state",
+                                                          "/sys/class/graphics/fb0/idle_state",
+                                                          "/sys/class/graphics/fb0/device/idle_state"};
 static const uint32_t kWaitMs =
         ::android::base::GetUintProperty("vendor.powerhal.disp.idle_wait", /*default*/ 100U);
 static const uint32_t kMinDurationMs =
diff --git a/power-libperfmgr/libperfmgr/AdpfConfig.cc b/power-libperfmgr/libperfmgr/AdpfConfig.cc
index 36e5209..c017965 100644
--- a/power-libperfmgr/libperfmgr/AdpfConfig.cc
+++ b/power-libperfmgr/libperfmgr/AdpfConfig.cc
@@ -56,8 +56,6 @@
     dump_buf << "UclampMin_High: " << mUclampMinHigh << "\n";
     dump_buf << "UclampMin_Low: " << mUclampMinLow << "\n";
     dump_buf << "ReportingRateLimitNs: " << mReportingRateLimitNs << "\n";
-    dump_buf << "EarlyBoost_On: " << mEarlyBoostOn << "\n";
-    dump_buf << "EarlyBoost_TimeFactor: " << mEarlyBoostTimeFactor << "\n";
     dump_buf << "TargetTimeFactor: " << mTargetTimeFactor << "\n";
     dump_buf << "StaleTimeFactor: " << mStaleTimeFactor << "\n";
     if (!android::base::WriteStringToFd(dump_buf.str(), fd)) {
diff --git a/power-libperfmgr/libperfmgr/HintManager.cc b/power-libperfmgr/libperfmgr/HintManager.cc
index dd313ef..bc6b10d 100644
--- a/power-libperfmgr/libperfmgr/HintManager.cc
+++ b/power-libperfmgr/libperfmgr/HintManager.cc
@@ -58,7 +58,7 @@
 
 bool HintManager::IsHintSupported(const std::string& hint_type) const {
     if (actions_.find(hint_type) == actions_.end()) {
-        LOG(INFO) << "Hint type not present in actions: " << hint_type;
+        LOG(DEBUG) << "Hint type not present in actions: " << hint_type;
         return false;
     }
     return true;
@@ -644,6 +644,16 @@
     return actions_parsed;
 }
 
+#define ADPF_PARSE(VARIABLE, ENTRY, TYPE)                                                        \
+    static_assert(std::is_same<decltype(adpfs[i][ENTRY].as##TYPE()), decltype(VARIABLE)>::value, \
+                  "Parser type mismatch");                                                       \
+    if (adpfs[i][ENTRY].empty() || !adpfs[i][ENTRY].is##TYPE()) {                                \
+        LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][" ENTRY "]'s Values";           \
+        adpfs_parsed.clear();                                                                    \
+        return adpfs_parsed;                                                                     \
+    }                                                                                            \
+    VARIABLE = adpfs[i][ENTRY].as##TYPE()
+
 std::vector<std::shared_ptr<AdpfConfig>> HintManager::ParseAdpfConfigs(
         const std::string &json_doc) {
     // function starts
@@ -665,8 +675,6 @@
     uint64_t samplingWindowD;
     double staleTimeFactor;
     uint64_t reportingRate;
-    bool earlyBoostOn;
-    double earlyBoostTimeFactor;
     double targetTimeFactor;
     std::vector<std::shared_ptr<AdpfConfig>> adpfs_parsed;
     std::set<std::string> name_parsed;
@@ -695,162 +703,31 @@
             return adpfs_parsed;
         }
 
-        if (adpfs[i]["PID_On"].empty() || !adpfs[i]["PID_On"].isBool()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_On]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        pidOn = adpfs[i]["PID_On"].asBool();
-
-        if (adpfs[i]["PID_Po"].empty() || !adpfs[i]["PID_Po"].isDouble()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_Po]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        pidPOver = adpfs[i]["PID_Po"].asDouble();
-
-        if (adpfs[i]["PID_Pu"].empty() || !adpfs[i]["PID_Pu"].isDouble()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_Pu]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        pidPUnder = adpfs[i]["PID_Pu"].asDouble();
-
-        if (adpfs[i]["PID_I"].empty() || !adpfs[i]["PID_I"].isDouble()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_I]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        pidI = adpfs[i]["PID_I"].asDouble();
-
-        if (adpfs[i]["PID_I_Init"].empty() || !adpfs[i]["PID_I_Init"].isInt64()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_I_Init]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        pidIInit = adpfs[i]["PID_I_Init"].asInt64();
-
-        if (adpfs[i]["PID_I_High"].empty() || !adpfs[i]["PID_I_High"].isInt64()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_I_High]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        pidIHighLimit = adpfs[i]["PID_I_High"].asInt64();
-
-        if (adpfs[i]["PID_I_Low"].empty() || !adpfs[i]["PID_I_Low"].isInt64()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_I_Low]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        pidILowLimit = adpfs[i]["PID_I_Low"].asInt64();
-
-        if (adpfs[i]["PID_Do"].empty() || !adpfs[i]["PID_Do"].isDouble()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_Do]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        pidDOver = adpfs[i]["PID_Do"].asDouble();
-
-        if (adpfs[i]["PID_Du"].empty() || !adpfs[i]["PID_Du"].isDouble()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_Du]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        pidDUnder = adpfs[i]["PID_Du"].asDouble();
-
-        if (adpfs[i]["UclampMin_On"].empty() || !adpfs[i]["UclampMin_On"].isBool()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][UclampMin_On]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        adpfUclamp = adpfs[i]["UclampMin_On"].asBool();
-
-        if (adpfs[i]["UclampMin_Init"].empty() || !adpfs[i]["UclampMin_Init"].isInt()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][UclampMin_Init]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        uclampMinInit = adpfs[i]["UclampMin_Init"].asInt();
-
-        if (adpfs[i]["UclampMin_High"].empty() || !adpfs[i]["UclampMin_High"].isUInt()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][UclampMin_High]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        uclampMinHighLimit = adpfs[i]["UclampMin_High"].asUInt();
-
-        if (adpfs[i]["UclampMin_Low"].empty() || !adpfs[i]["UclampMin_Low"].isUInt()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][UclampMin_Low]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        uclampMinLowLimit = adpfs[i]["UclampMin_Low"].asUInt();
-
-        if (adpfs[i]["SamplingWindow_P"].empty() || !adpfs[i]["SamplingWindow_P"].isUInt64()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][SamplingWindow_P]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        samplingWindowP = adpfs[i]["SamplingWindow_P"].asUInt64();
-
-        if (adpfs[i]["SamplingWindow_I"].empty() || !adpfs[i]["SamplingWindow_I"].isUInt64()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][SamplingWindow_I]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        samplingWindowI = adpfs[i]["SamplingWindow_I"].asUInt64();
-
-        if (adpfs[i]["SamplingWindow_D"].empty() || !adpfs[i]["SamplingWindow_D"].isUInt64()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][SamplingWindow_D]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        samplingWindowD = adpfs[i]["SamplingWindow_D"].asUInt64();
-
-        if (adpfs[i]["StaleTimeFactor"].empty() || !adpfs[i]["StaleTimeFactor"].isUInt64()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][StaleTimeFactor]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        staleTimeFactor = adpfs[i]["StaleTimeFactor"].asDouble();
-
-        if (adpfs[i]["ReportingRateLimitNs"].empty() ||
-            !adpfs[i]["ReportingRateLimitNs"].isInt64()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name
-                       << "][ReportingRateLimitNs]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        reportingRate = adpfs[i]["ReportingRateLimitNs"].asInt64();
-
-        if (adpfs[i]["EarlyBoost_On"].empty() || !adpfs[i]["EarlyBoost_On"].isBool()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][EarlyBoost_On]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        earlyBoostOn = adpfs[i]["EarlyBoost_On"].asBool();
-
-        if (adpfs[i]["EarlyBoost_TimeFactor"].empty() ||
-            !adpfs[i]["EarlyBoost_TimeFactor"].isDouble()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name
-                       << "][EarlyBoost_TimeFactor]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        earlyBoostTimeFactor = adpfs[i]["EarlyBoost_TimeFactor"].asDouble();
-
-        if (adpfs[i]["TargetTimeFactor"].empty() || !adpfs[i]["TargetTimeFactor"].isDouble()) {
-            LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][TargetTimeFactor]'s Values";
-            adpfs_parsed.clear();
-            return adpfs_parsed;
-        }
-        targetTimeFactor = adpfs[i]["TargetTimeFactor"].asDouble();
+        ADPF_PARSE(pidOn, "PID_On", Bool);
+        ADPF_PARSE(pidPOver, "PID_Po", Double);
+        ADPF_PARSE(pidPUnder, "PID_Pu", Double);
+        ADPF_PARSE(pidI, "PID_I", Double);
+        ADPF_PARSE(pidIInit, "PID_I_Init", Int64);
+        ADPF_PARSE(pidIHighLimit, "PID_I_High", Int64);
+        ADPF_PARSE(pidILowLimit, "PID_I_Low", Int64);
+        ADPF_PARSE(pidDOver, "PID_Do", Double);
+        ADPF_PARSE(pidDUnder, "PID_Du", Double);
+        ADPF_PARSE(adpfUclamp, "UclampMin_On", Bool);
+        ADPF_PARSE(uclampMinInit, "UclampMin_Init", UInt);
+        ADPF_PARSE(uclampMinHighLimit, "UclampMin_High", UInt);
+        ADPF_PARSE(uclampMinLowLimit, "UclampMin_Low", UInt);
+        ADPF_PARSE(samplingWindowP, "SamplingWindow_P", UInt64);
+        ADPF_PARSE(samplingWindowI, "SamplingWindow_I", UInt64);
+        ADPF_PARSE(samplingWindowD, "SamplingWindow_D", UInt64);
+        ADPF_PARSE(staleTimeFactor, "StaleTimeFactor", Double);
+        ADPF_PARSE(reportingRate, "ReportingRateLimitNs", UInt64);
+        ADPF_PARSE(targetTimeFactor, "TargetTimeFactor", Double);
 
         adpfs_parsed.emplace_back(std::make_shared<AdpfConfig>(
                 name, pidOn, pidPOver, pidPUnder, pidI, pidIInit, pidIHighLimit, pidILowLimit,
                 pidDOver, pidDUnder, adpfUclamp, uclampMinInit, uclampMinHighLimit,
                 uclampMinLowLimit, samplingWindowP, samplingWindowI, samplingWindowD, reportingRate,
-                earlyBoostOn, earlyBoostTimeFactor, targetTimeFactor, staleTimeFactor));
+                targetTimeFactor, staleTimeFactor));
     }
     LOG(INFO) << adpfs_parsed.size() << " AdpfConfigs parsed successfully";
     return adpfs_parsed;
@@ -872,5 +749,14 @@
     return false;
 }
 
+bool HintManager::IsAdpfProfileSupported(const std::string &profile_name) const {
+    for (std::size_t i = 0; i < adpfs_.size(); ++i) {
+        if (adpfs_[i]->mName == profile_name) {
+            return true;
+        }
+    }
+    return false;
+}
+
 }  // namespace perfmgr
 }  // namespace android
diff --git a/power-libperfmgr/libperfmgr/include/perfmgr/AdpfConfig.h b/power-libperfmgr/libperfmgr/include/perfmgr/AdpfConfig.h
index 7df1bfe..deab85a 100644
--- a/power-libperfmgr/libperfmgr/include/perfmgr/AdpfConfig.h
+++ b/power-libperfmgr/libperfmgr/include/perfmgr/AdpfConfig.h
@@ -43,9 +43,6 @@
     uint64_t mSamplingWindowI;
     uint64_t mSamplingWindowD;
     int64_t mReportingRateLimitNs;
-    int64_t mFreezeDurationNs;
-    bool mEarlyBoostOn;
-    double mEarlyBoostTimeFactor;
     double mTargetTimeFactor;
     // Stale control
     double mStaleTimeFactor;
@@ -59,8 +56,8 @@
                int64_t pidIInit, int64_t pidIHigh, int64_t pidILow, double pidDo, double pidDu,
                bool uclampMinOn, uint32_t uclampMinInit, uint32_t uclampMinHigh,
                uint32_t uclampMinLow, uint64_t samplingWindowP, uint64_t samplingWindowI,
-               uint64_t samplingWindowD, int64_t reportingRateLimitNs, bool earlyBoostOn,
-               double earlyBoostTimeFactor, double targetTimeFactor, double staleTimeFactor)
+               uint64_t samplingWindowD, int64_t reportingRateLimitNs, double targetTimeFactor,
+               double staleTimeFactor)
         : mName(std::move(name)),
           mPidOn(pidOn),
           mPidPo(pidPo),
@@ -79,8 +76,6 @@
           mSamplingWindowI(samplingWindowI),
           mSamplingWindowD(samplingWindowD),
           mReportingRateLimitNs(reportingRateLimitNs),
-          mEarlyBoostOn(earlyBoostOn),
-          mEarlyBoostTimeFactor(earlyBoostTimeFactor),
           mTargetTimeFactor(targetTimeFactor),
           mStaleTimeFactor(staleTimeFactor) {}
 };
diff --git a/power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h b/power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h
index 13211a1..34726f5 100644
--- a/power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h
+++ b/power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h
@@ -125,6 +125,9 @@
     // get current ADPF.
     std::shared_ptr<AdpfConfig> GetAdpfProfile() const;
 
+    // Query if given AdpfProfile supported.
+    bool IsAdpfProfileSupported(const std::string &name) const;
+
     // Static method to construct HintManager from the JSON config file.
     static std::unique_ptr<HintManager> GetFromJSON(
         const std::string& config_path, bool start = true);
diff --git a/power-libperfmgr/libperfmgr/tests/HintManagerTest.cc b/power-libperfmgr/libperfmgr/tests/HintManagerTest.cc
index 979e7dc..f323e46 100644
--- a/power-libperfmgr/libperfmgr/tests/HintManagerTest.cc
+++ b/power-libperfmgr/libperfmgr/tests/HintManagerTest.cc
@@ -803,10 +803,6 @@
     EXPECT_EQ(157U, adpfs[1]->mUclampMinLow);
     EXPECT_EQ(166666660LL, adpfs[0]->mReportingRateLimitNs);
     EXPECT_EQ(83333330LL, adpfs[1]->mReportingRateLimitNs);
-    EXPECT_EQ(false, adpfs[0]->mEarlyBoostOn);
-    EXPECT_EQ(true, adpfs[1]->mEarlyBoostOn);
-    EXPECT_EQ(0.8, adpfs[0]->mEarlyBoostTimeFactor);
-    EXPECT_EQ(1.2, adpfs[1]->mEarlyBoostTimeFactor);
     EXPECT_EQ(1.0, adpfs[0]->mTargetTimeFactor);
     EXPECT_EQ(1.4, adpfs[1]->mTargetTimeFactor);
     EXPECT_EQ(10.0, adpfs[0]->mStaleTimeFactor);
@@ -851,5 +847,17 @@
     EXPECT_EQ("REFRESH_120FPS", hm->GetAdpfProfile()->mName);
 }
 
+TEST_F(HintManagerTest, IsAdpfProfileSupported) {
+    TemporaryFile json_file;
+    ASSERT_TRUE(android::base::WriteStringToFile(json_doc_, json_file.path)) << strerror(errno);
+    std::unique_ptr<HintManager> hm = HintManager::GetFromJSON(json_file.path, false);
+    EXPECT_NE(nullptr, hm.get());
+
+    // Check if given AdpfProfile supported
+    EXPECT_FALSE(hm->IsAdpfProfileSupported("NoSuchProfile"));
+    EXPECT_TRUE(hm->IsAdpfProfileSupported("REFRESH_60FPS"));
+    EXPECT_TRUE(hm->IsAdpfProfileSupported("REFRESH_120FPS"));
+}
+
 }  // namespace perfmgr
 }  // namespace android
diff --git a/recovery/Android.bp b/recovery/Android.bp
index 4c7e0c7..cd3526b 100644
--- a/recovery/Android.bp
+++ b/recovery/Android.bp
@@ -27,3 +27,27 @@
         "librecovery_ui",
     ],
 }
+
+cc_library_static {
+    name: "librecovery_ui_pixel_watch",
+    owner: "google",
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-pedantic",
+    ],
+    srcs: [
+        "recovery_watch_ui.cpp",
+    ],
+
+    whole_static_libs: [
+        "libmisc_writer",
+        "libbootloader_message",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "librecovery_ui",
+    ],
+}
diff --git a/recovery/recovery_watch_ui.cpp b/recovery/recovery_watch_ui.cpp
new file mode 100644
index 0000000..39a0952
--- /dev/null
+++ b/recovery/recovery_watch_ui.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include <dlfcn.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include <android-base/endian.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <misc_writer/misc_writer.h>
+#include <recovery_ui/device.h>
+#include <recovery_ui/wear_ui.h>
+
+namespace android {
+namespace hardware {
+namespace google {
+namespace pixel {
+
+namespace {
+
+// Provision Silent OTA(SOTA) flag while reason is "enable-sota"
+bool ProvisionSilentOtaFlag(const std::string& reason) {
+    if (android::base::StartsWith(reason, MiscWriter::kSotaFlag)) {
+        MiscWriter misc_writer(MiscWriterActions::kSetSotaFlag);
+        if (!misc_writer.PerformAction()) {
+            LOG(ERROR) << "Failed to set the silent ota flag";
+            return false;
+        }
+        LOG(INFO) << "Silent ota flag set successful";
+    }
+    return true;
+}
+
+}  // namespace
+
+class PixelWatchDevice : public ::Device {
+  public:
+    explicit PixelWatchDevice(::WearRecoveryUI* const ui) : ::Device(ui) {}
+
+    /** Hook to wipe user data not stored in /data */
+    bool PostWipeData() override {
+        // Try to do everything but report a failure if anything wasn't successful
+        bool totalSuccess = false;
+
+        // Additional behavior along with wiping data
+        auto reason = GetReason();
+        CHECK(reason.has_value());
+        if (!ProvisionSilentOtaFlag(reason.value())) {
+            totalSuccess = false;
+        }
+
+        return totalSuccess;
+    }
+};
+
+}  // namespace pixel
+}  // namespace google
+}  // namespace hardware
+}  // namespace android
+
+Device *make_device() {
+    return new ::android::hardware::google::pixel::PixelWatchDevice(new ::WearRecoveryUI);
+}
diff --git a/thermal/Android.bp b/thermal/Android.bp
index 61403b3..afd3c81 100644
--- a/thermal/Android.bp
+++ b/thermal/Android.bp
@@ -15,6 +15,7 @@
         "utils/powerhal_helper.cpp",
         "utils/thermal_stats_helper.cpp",
         "utils/thermal_watcher.cpp",
+        "virtualtemp_estimator/virtualtemp_estimator.cpp",
     ],
     vendor: true,
     relative_install_path: "hw",
@@ -31,9 +32,9 @@
         "libutils",
         "libnl",
         "libbinder_ndk",
-        "android.frameworks.stats-V1-ndk",
+        "android.frameworks.stats-V2-ndk",
         "android.hardware.power-V1-ndk",
-        "android.hardware.thermal-V1-ndk",
+        "android.hardware.thermal-V2-ndk",
         "pixel-power-ext-V1-ndk",
         "pixelatoms-cpp",
     ],
@@ -41,7 +42,7 @@
         "libpixelstats",
     ],
     export_shared_lib_headers: [
-        "android.frameworks.stats-V1-ndk",
+        "android.frameworks.stats-V2-ndk",
         "pixelatoms-cpp",
     ],
     cflags: [
@@ -79,6 +80,7 @@
         "utils/thermal_watcher.cpp",
         "tests/mock_thermal_helper.cpp",
         "tests/thermal_looper_test.cpp",
+        "virtualtemp_estimator/virtualtemp_estimator.cpp",
     ],
     shared_libs: [
         "libbase",
@@ -88,9 +90,9 @@
         "libnl",
         "liblog",
         "libbinder_ndk",
-        "android.frameworks.stats-V1-ndk",
+        "android.frameworks.stats-V2-ndk",
         "android.hardware.power-V1-ndk",
-        "android.hardware.thermal-V1-ndk",
+        "android.hardware.thermal-V2-ndk",
         "pixel-power-ext-V1-ndk",
         "pixelatoms-cpp",
     ],
@@ -119,3 +121,38 @@
         "pixel-thermal-symlinks.rc",
     ],
 }
+
+
+cc_binary {
+    name: "virtualtemp_estimator_test",
+    srcs: [
+        "virtualtemp_estimator/virtualtemp_estimator.cpp",
+        "virtualtemp_estimator/virtualtemp_estimator_test.cpp"
+        ],
+    shared_libs: [
+        "libc",
+        "liblog",
+        "libcutils",
+        "libbinder",
+        "libhidlbase",
+        "libutils",
+        "libjsoncpp",],
+    vendor: true,
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wextra",
+        "-Wunused",
+    ],
+    tidy: true,
+    tidy_checks: [
+        "android-*",
+        "cert-*",
+        "clang-analyzer-security*",
+    ],
+    tidy_checks_as_errors: [
+        "android-*",
+        "clang-analyzer-security*",
+        "cert-*",
+    ],
+}
diff --git a/thermal/Thermal.cpp b/thermal/Thermal.cpp
index 4b55ab9..13cdcaa 100644
--- a/thermal/Thermal.cpp
+++ b/thermal/Thermal.cpp
@@ -646,15 +646,17 @@
             }
         }
         {
-            dump_buf << "getEmulTemperatures:" << std::endl;
+            dump_buf << "getEmulSettings:" << std::endl;
             for (const auto &sensor_status_pair : sensor_status_map) {
-                if (sensor_status_pair.second.emul_setting == nullptr) {
+                if (sensor_status_pair.second.override_status.emul_temp == nullptr) {
                     continue;
                 }
-                dump_buf << " Name: " << sensor_status_pair.first
-                         << " EmulTemp: " << sensor_status_pair.second.emul_setting->emul_temp
+                dump_buf << " Name: " << sensor_status_pair.first << " EmulTemp: "
+                         << sensor_status_pair.second.override_status.emul_temp->temp
                          << " EmulSeverity: "
-                         << sensor_status_pair.second.emul_setting->emul_severity << std::endl;
+                         << sensor_status_pair.second.override_status.emul_temp->severity
+                         << " maxThrottling: " << std::boolalpha
+                         << sensor_status_pair.second.override_status.max_throttling << std::endl;
             }
         }
         {
@@ -774,17 +776,18 @@
         return STATUS_OK;
     }
 
-    if (std::string(args[0]) == "emul_temp") {
-        return (numArgs != 3 ||
-                !thermal_helper_->emulTemp(std::string(args[1]), std::strtod(args[2], nullptr)))
-                       ? STATUS_BAD_VALUE
-                       : STATUS_OK;
-    } else if (std::string(args[0]) == "emul_severity") {
-        return (numArgs != 3 ||
-                !thermal_helper_->emulSeverity(std::string(args[1]),
-                                               static_cast<int>(std::strtol(args[2], nullptr, 10))))
-                       ? STATUS_BAD_VALUE
-                       : STATUS_OK;
+    if (std::string(args[0]) == "emul_temp" && numArgs >= 3) {
+        return thermal_helper_->emulTemp(
+                       std::string(args[1]), std::atof(args[2]),
+                       numArgs == 3 ? false : std::string(args[3]) == "max_throttling")
+                       ? STATUS_OK
+                       : STATUS_BAD_VALUE;
+    } else if (std::string(args[0]) == "emul_severity" && numArgs >= 3) {
+        return thermal_helper_->emulSeverity(
+                       std::string(args[1]), std::atof(args[2]),
+                       numArgs == 3 ? false : std::string(args[3]) == "max_throttling")
+                       ? STATUS_OK
+                       : STATUS_BAD_VALUE;
     } else if (std::string(args[0]) == "emul_clear") {
         return (numArgs != 2 || !thermal_helper_->emulClear(std::string(args[1])))
                        ? STATUS_BAD_VALUE
diff --git a/thermal/android.hardware.thermal-service.pixel.rc b/thermal/android.hardware.thermal-service.pixel.rc
index f9f823b..a1c89fc 100644
--- a/thermal/android.hardware.thermal-service.pixel.rc
+++ b/thermal/android.hardware.thermal-service.pixel.rc
@@ -10,5 +10,5 @@
     class hal
     user system
     group system
-    priority -20
+    priority -10
     disabled
diff --git a/thermal/android.hardware.thermal-service.pixel.xml b/thermal/android.hardware.thermal-service.pixel.xml
index bdee744..08dc68c 100644
--- a/thermal/android.hardware.thermal-service.pixel.xml
+++ b/thermal/android.hardware.thermal-service.pixel.xml
@@ -1,7 +1,7 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>android.hardware.thermal</name>
-        <version>1</version>
+        <version>2</version>
         <fqname>IThermal/default</fqname>
     </hal>
 </manifest>
diff --git a/thermal/device.mk b/thermal/device.mk
deleted file mode 100644
index 1d71d31..0000000
--- a/thermal/device.mk
+++ /dev/null
@@ -1,13 +0,0 @@
-PRODUCT_PACKAGES += \
-    android.hardware.thermal-service.pixel
-
-# Thermal utils
-PRODUCT_PACKAGES += \
-    thermal_symlinks
-
-ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT)))
-PRODUCT_PACKAGES += \
-    thermal_logd
-endif
-
-BOARD_SEPOLICY_DIRS += hardware/google/pixel-sepolicy/thermal
diff --git a/thermal/tests/mock_thermal_helper.h b/thermal/tests/mock_thermal_helper.h
index 43ff328..c706461 100644
--- a/thermal/tests/mock_thermal_helper.h
+++ b/thermal/tests/mock_thermal_helper.h
@@ -34,8 +34,8 @@
                 (bool, TemperatureType, std::vector<TemperatureThreshold> *), (const, override));
     MOCK_METHOD(bool, fillCurrentCoolingDevices, (bool, CoolingType, std::vector<CoolingDevice> *),
                 (const, override));
-    MOCK_METHOD(bool, emulTemp, (std::string_view, const float), (override));
-    MOCK_METHOD(bool, emulSeverity, (std::string_view, const int), (override));
+    MOCK_METHOD(bool, emulTemp, (std::string_view, const float, const bool), (override));
+    MOCK_METHOD(bool, emulSeverity, (std::string_view, const int, const bool), (override));
     MOCK_METHOD(bool, emulClear, (std::string_view), (override));
     MOCK_METHOD(bool, isInitializedOk, (), (const, override));
     MOCK_METHOD(bool, readTemperature,
diff --git a/thermal/thermal-helper.cpp b/thermal/thermal-helper.cpp
index dc4a599..61de8bc 100644
--- a/thermal/thermal-helper.cpp
+++ b/thermal/thermal-helper.cpp
@@ -157,9 +157,11 @@
         ret = false;
     }
 
-    if (!thermal_stats_helper_.initializeStats(config, sensor_info_map_,
-                                               cooling_device_info_map_)) {
-        LOG(FATAL) << "Failed to initialize thermal stats";
+    if (ret) {
+        if (!thermal_stats_helper_.initializeStats(config, sensor_info_map_,
+                                                   cooling_device_info_map_)) {
+            LOG(FATAL) << "Failed to initialize thermal stats";
+        }
     }
 
     for (auto &name_status_pair : sensor_info_map_) {
@@ -169,7 +171,7 @@
                 .prev_cold_severity = ThrottlingSeverity::NONE,
                 .last_update_time = boot_clock::time_point::min(),
                 .thermal_cached = {NAN, boot_clock::time_point::min()},
-                .emul_setting = nullptr,
+                .override_status = {nullptr, false, false},
         };
 
         if (name_status_pair.second.throttling_info != nullptr) {
@@ -286,42 +288,82 @@
     return true;
 }
 
-bool ThermalHelperImpl::emulTemp(std::string_view target_sensor, const float value) {
-    LOG(INFO) << "Set " << target_sensor.data() << " emul_temp "
-              << "to " << value;
+void ThermalHelperImpl::checkUpdateSensorForEmul(std::string_view target_sensor,
+                                                 const bool max_throttling) {
+    // Force update all the sensors which are related to the target emul sensor
+    for (auto &[sensor_name, sensor_info] : sensor_info_map_) {
+        if (sensor_info.virtual_sensor_info == nullptr || !sensor_info.is_watch) {
+            continue;
+        }
+
+        const auto &linked_sensors = sensor_info.virtual_sensor_info->linked_sensors;
+        if (std::find(linked_sensors.begin(), linked_sensors.end(), target_sensor) ==
+            linked_sensors.end()) {
+            continue;
+        }
+
+        auto &sensor_status = sensor_status_map_.at(sensor_name.data());
+        sensor_status.override_status.max_throttling = max_throttling;
+        sensor_status.override_status.pending_update = true;
+
+        checkUpdateSensorForEmul(sensor_name, max_throttling);
+    }
+}
+
+bool ThermalHelperImpl::emulTemp(std::string_view target_sensor, const float temp,
+                                 const bool max_throttling) {
+    LOG(INFO) << "Set " << target_sensor.data() << " emul_temp: " << temp
+              << " max_throttling: " << max_throttling;
 
     std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_);
+
     // Check the target sensor is valid
     if (!sensor_status_map_.count(target_sensor.data())) {
         LOG(ERROR) << "Cannot find target emul sensor: " << target_sensor.data();
         return false;
     }
 
-    sensor_status_map_.at(target_sensor.data())
-            .emul_setting.reset(new EmulSetting{value, -1, true});
+    auto &sensor_status = sensor_status_map_.at(target_sensor.data());
+
+    sensor_status.override_status.emul_temp.reset(new EmulTemp{temp, -1});
+    sensor_status.override_status.max_throttling = max_throttling;
+    sensor_status.override_status.pending_update = true;
+
+    checkUpdateSensorForEmul(target_sensor.data(), max_throttling);
 
     thermal_watcher_->wake();
     return true;
 }
 
-bool ThermalHelperImpl::emulSeverity(std::string_view target_sensor, const int severity) {
-    LOG(INFO) << "Set " << target_sensor.data() << " emul_severity "
-              << "to " << severity;
+bool ThermalHelperImpl::emulSeverity(std::string_view target_sensor, const int severity,
+                                     const bool max_throttling) {
+    LOG(INFO) << "Set " << target_sensor.data() << " emul_severity: " << severity
+              << " max_throttling: " << max_throttling;
 
     std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_);
     // Check the target sensor is valid
-    if (!sensor_status_map_.count(target_sensor.data())) {
+    if (!sensor_status_map_.count(target_sensor.data()) ||
+        !sensor_info_map_.count(target_sensor.data())) {
         LOG(ERROR) << "Cannot find target emul sensor: " << target_sensor.data();
         return false;
     }
+    const auto &sensor_info = sensor_info_map_.at(target_sensor.data());
+
     // Check the emul severity is valid
     if (severity > static_cast<int>(kThrottlingSeverityCount)) {
         LOG(ERROR) << "Invalid emul severity value " << severity;
         return false;
     }
 
-    sensor_status_map_.at(target_sensor.data())
-            .emul_setting.reset(new EmulSetting{NAN, severity, true});
+    const auto temp = sensor_info.hot_thresholds[severity] / sensor_info.multiplier;
+
+    auto &sensor_status = sensor_status_map_.at(target_sensor.data());
+
+    sensor_status.override_status.emul_temp.reset(new EmulTemp{temp, severity});
+    sensor_status.override_status.max_throttling = max_throttling;
+    sensor_status.override_status.pending_update = true;
+
+    checkUpdateSensorForEmul(target_sensor.data(), max_throttling);
 
     thermal_watcher_->wake();
     return true;
@@ -332,19 +374,22 @@
 
     std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_);
     if (target_sensor == "all") {
-        for (auto &sensor_status : sensor_status_map_) {
-            if (sensor_status.second.emul_setting != nullptr) {
-                sensor_status.second.emul_setting.reset(new EmulSetting{NAN, -1, true});
-            }
+        for (auto &[sensor_name, sensor_status] : sensor_status_map_) {
+            sensor_status.override_status = {
+                    .emul_temp = nullptr, .max_throttling = false, .pending_update = true};
+            checkUpdateSensorForEmul(sensor_name, false);
         }
-    } else if (sensor_status_map_.count(target_sensor.data()) &&
-               sensor_status_map_.at(target_sensor.data()).emul_setting != nullptr) {
-        sensor_status_map_.at(target_sensor.data())
-                .emul_setting.reset(new EmulSetting{NAN, -1, true});
+    } else if (sensor_status_map_.count(target_sensor.data())) {
+        auto &sensor_status = sensor_status_map_.at(target_sensor.data());
+        sensor_status.override_status = {
+                .emul_temp = nullptr, .max_throttling = false, .pending_update = true};
+        checkUpdateSensorForEmul(target_sensor.data(), false);
     } else {
         LOG(ERROR) << "Cannot find target emul sensor: " << target_sensor.data();
         return false;
     }
+
+    thermal_watcher_->wake();
     return true;
 }
 
@@ -377,8 +422,15 @@
     std::map<std::string, float> sensor_log_map;
     auto &sensor_status = sensor_status_map_.at(sensor_name.data());
 
-    if (!readThermalSensor(sensor_name, &temp, force_no_cache, &sensor_log_map) ||
-        std::isnan(temp)) {
+    if (!readThermalSensor(sensor_name, &temp, force_no_cache, &sensor_log_map)) {
+        LOG(ERROR) << "Failed to read thermal sensor " << sensor_name.data();
+        thermal_stats_helper_.reportThermalAbnormality(
+                ThermalSensorAbnormalityDetected::TEMP_READ_FAIL, sensor_name, std::nullopt);
+        return false;
+    }
+
+    if (std::isnan(temp)) {
+        LOG(INFO) << "Sensor " << sensor_name.data() << " temperature is nan.";
         return false;
     }
 
@@ -407,10 +459,11 @@
         *throttling_status = status;
     }
 
-    if (sensor_status.emul_setting != nullptr && sensor_status.emul_setting->emul_severity >= 0) {
+    if (sensor_status.override_status.emul_temp != nullptr &&
+        sensor_status.override_status.emul_temp->severity >= 0) {
         std::shared_lock<std::shared_mutex> _lock(sensor_status_map_mutex_);
         out->throttlingStatus =
-                static_cast<ThrottlingSeverity>(sensor_status.emul_setting->emul_severity);
+                static_cast<ThrottlingSeverity>(sensor_status.override_status.emul_temp->severity);
     } else {
         out->throttlingStatus =
                 static_cast<size_t>(status.first) > static_cast<size_t>(status.second)
@@ -853,12 +906,55 @@
     return true;
 }
 
+float ThermalHelperImpl::runVirtualTempEstimator(std::string_view sensor_name,
+                                                 std::map<std::string, float> *sensor_log_map) {
+    std::vector<float> model_inputs;
+    float estimated_vt = NAN;
+    constexpr int kCelsius2mC = 1000;
+
+    ATRACE_NAME(StringPrintf("ThermalHelper::runVirtualTempEstimator - %s", sensor_name.data())
+                        .c_str());
+    if (!(sensor_info_map_.count(sensor_name.data()) &&
+          sensor_status_map_.count(sensor_name.data()))) {
+        LOG(ERROR) << sensor_name << " not part of sensor_info_map_ or sensor_status_map_";
+        return NAN;
+    }
+
+    const auto &sensor_info = sensor_info_map_.at(sensor_name.data());
+    if (!sensor_info.virtual_sensor_info->vt_estimator) {
+        LOG(ERROR) << "vt_estimator not valid for " << sensor_name;
+        return NAN;
+    }
+
+    model_inputs.reserve(sensor_info.virtual_sensor_info->linked_sensors.size());
+
+    for (size_t i = 0; i < sensor_info.virtual_sensor_info->linked_sensors.size(); i++) {
+        std::string linked_sensor = sensor_info.virtual_sensor_info->linked_sensors[i];
+
+        if ((*sensor_log_map).count(linked_sensor.data())) {
+            float value = (*sensor_log_map)[linked_sensor.data()];
+            model_inputs.push_back(value / kCelsius2mC);
+        } else {
+            LOG(ERROR) << "failed to read sensor: " << linked_sensor;
+            return NAN;
+        }
+    }
+
+    ::thermal::vtestimator::VtEstimatorStatus ret =
+            sensor_info.virtual_sensor_info->vt_estimator->Estimate(model_inputs, &estimated_vt);
+    if (ret != ::thermal::vtestimator::kVtEstimatorOk) {
+        LOG(ERROR) << "Failed to run estimator (ret: " << ret << ") for " << sensor_name;
+        return NAN;
+    }
+
+    return (estimated_vt * kCelsius2mC);
+}
+
 constexpr int kTranTimeoutParam = 2;
 
 bool ThermalHelperImpl::readThermalSensor(std::string_view sensor_name, float *temp,
                                           const bool force_no_cache,
                                           std::map<std::string, float> *sensor_log_map) {
-    float temp_val = 0.0;
     std::string file_reading;
     boot_clock::time_point now = boot_clock::now();
 
@@ -873,9 +969,8 @@
 
     {
         std::shared_lock<std::shared_mutex> _lock(sensor_status_map_mutex_);
-        if (sensor_status.emul_setting != nullptr &&
-            !isnan(sensor_status.emul_setting->emul_temp)) {
-            *temp = sensor_status.emul_setting->emul_temp;
+        if (sensor_status.override_status.emul_temp != nullptr) {
+            *temp = sensor_status.override_status.emul_temp->temp;
             return true;
         }
     }
@@ -903,57 +998,75 @@
         }
         *temp = std::stof(::android::base::Trim(file_reading));
     } else {
-        for (size_t i = 0; i < sensor_info.virtual_sensor_info->linked_sensors.size(); i++) {
-            float sensor_reading = 0.0;
-            // Get the sensor reading data
-            if (!readDataByType(sensor_info.virtual_sensor_info->linked_sensors[i], &sensor_reading,
+        const auto &linked_sensors_size = sensor_info.virtual_sensor_info->linked_sensors.size();
+        std::vector<float> sensor_readings(linked_sensors_size, 0.0);
+
+        // Calculate temperature of each of the linked sensor
+        for (size_t i = 0; i < linked_sensors_size; i++) {
+            if (!readDataByType(sensor_info.virtual_sensor_info->linked_sensors[i],
+                                &sensor_readings[i],
                                 sensor_info.virtual_sensor_info->linked_sensors_type[i],
                                 force_no_cache, sensor_log_map)) {
                 LOG(ERROR) << "Failed to read " << sensor_name.data() << "'s linked sensor "
                            << sensor_info.virtual_sensor_info->linked_sensors[i];
                 return false;
             }
-
-            float coefficient = 0.0;
-            if (!readDataByType(sensor_info.virtual_sensor_info->coefficients[i], &coefficient,
-                                sensor_info.virtual_sensor_info->coefficients_type[i],
-                                force_no_cache, sensor_log_map)) {
-                LOG(ERROR) << "Failed to read " << sensor_name.data() << "'s coefficient "
-                           << sensor_info.virtual_sensor_info->coefficients[i];
-                return false;
-            }
-
-            if (std::isnan(sensor_reading) || std::isnan(coefficient)) {
+            if (std::isnan(sensor_readings[i])) {
                 LOG(INFO) << sensor_name << " data is under collecting";
                 return true;
             }
-
-            switch (sensor_info.virtual_sensor_info->formula) {
-                case FormulaOption::COUNT_THRESHOLD:
-                    if ((coefficient < 0 && sensor_reading < -coefficient) ||
-                        (coefficient >= 0 && sensor_reading >= coefficient))
-                        temp_val += 1;
-                    break;
-                case FormulaOption::WEIGHTED_AVG:
-                    temp_val += sensor_reading * coefficient;
-                    break;
-                case FormulaOption::MAXIMUM:
-                    if (i == 0)
-                        temp_val = std::numeric_limits<float>::lowest();
-                    if (sensor_reading * coefficient > temp_val)
-                        temp_val = sensor_reading * coefficient;
-                    break;
-                case FormulaOption::MINIMUM:
-                    if (i == 0)
-                        temp_val = std::numeric_limits<float>::max();
-                    if (sensor_reading * coefficient < temp_val)
-                        temp_val = sensor_reading * coefficient;
-                    break;
-                default:
-                    break;
-            }
         }
-        *temp = (temp_val + sensor_info.virtual_sensor_info->offset);
+
+        if (sensor_info.virtual_sensor_info->formula == FormulaOption::USE_ML_MODEL) {
+            *temp = runVirtualTempEstimator(sensor_name, sensor_log_map);
+
+            if (std::isnan(*temp)) {
+                LOG(ERROR) << "VirtualEstimator returned NAN for " << sensor_name;
+                return false;
+            }
+        } else {
+            float temp_val = 0.0;
+            for (size_t i = 0; i < linked_sensors_size; i++) {
+                float coefficient = 0.0;
+                if (!readDataByType(sensor_info.virtual_sensor_info->coefficients[i], &coefficient,
+                                    sensor_info.virtual_sensor_info->coefficients_type[i],
+                                    force_no_cache, sensor_log_map)) {
+                    LOG(ERROR) << "Failed to read " << sensor_name.data() << "'s coefficient "
+                               << sensor_info.virtual_sensor_info->coefficients[i];
+                    return false;
+                }
+                if (std::isnan(coefficient)) {
+                    LOG(INFO) << sensor_name << " data is under collecting";
+                    return true;
+                }
+                switch (sensor_info.virtual_sensor_info->formula) {
+                    case FormulaOption::COUNT_THRESHOLD:
+                        if ((coefficient < 0 && sensor_readings[i] < -coefficient) ||
+                            (coefficient >= 0 && sensor_readings[i] >= coefficient))
+                            temp_val += 1;
+                        break;
+                    case FormulaOption::WEIGHTED_AVG:
+                        temp_val += sensor_readings[i] * coefficient;
+                        break;
+                    case FormulaOption::MAXIMUM:
+                        if (i == 0)
+                            temp_val = std::numeric_limits<float>::lowest();
+                        if (sensor_readings[i] * coefficient > temp_val)
+                            temp_val = sensor_readings[i] * coefficient;
+                        break;
+                    case FormulaOption::MINIMUM:
+                        if (i == 0)
+                            temp_val = std::numeric_limits<float>::max();
+                        if (sensor_readings[i] * coefficient < temp_val)
+                            temp_val = sensor_readings[i] * coefficient;
+                        break;
+                    default:
+                        LOG(ERROR) << "Unknown formula type for sensor " << sensor_name.data();
+                        return false;
+                }
+            }
+            *temp = (temp_val + sensor_info.virtual_sensor_info->offset);
+        }
     }
 
     if (!isnan(sensor_info.step_ratio) && !isnan(sensor_status.thermal_cached.temp) &&
@@ -993,6 +1106,7 @@
         TemperatureThreshold threshold;
         SensorStatus &sensor_status = name_status_pair.second;
         const SensorInfo &sensor_info = sensor_info_map_.at(name_status_pair.first);
+        bool max_throttling = false;
 
         // Only handle the sensors in allow list
         if (!sensor_info.is_watch) {
@@ -1046,12 +1160,10 @@
         }
         {
             std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_);
-            if (sensor_status.emul_setting != nullptr &&
-                sensor_status.emul_setting->pending_update) {
-                force_update = true;
-                sensor_status.emul_setting->pending_update = false;
-                LOG(INFO) << "Update " << name_status_pair.first.data()
-                          << " right away with emul setting";
+            max_throttling = sensor_status.override_status.max_throttling;
+            if (sensor_status.override_status.pending_update) {
+                force_update = sensor_status.override_status.pending_update;
+                sensor_status.override_status.pending_update = false;
             }
         }
         LOG(VERBOSE) << "sensor " << name_status_pair.first
@@ -1110,7 +1222,7 @@
             // update thermal throttling request
             thermal_throttling_.thermalThrottlingUpdate(
                     temp, sensor_info, sensor_status.severity, time_elapsed_ms,
-                    power_files_.GetPowerStatusMap(), cooling_device_info_map_);
+                    power_files_.GetPowerStatusMap(), cooling_device_info_map_, max_throttling);
         }
 
         thermal_throttling_.computeCoolingDevicesRequest(
diff --git a/thermal/thermal-helper.h b/thermal/thermal-helper.h
index 6ae13c4..489b792 100644
--- a/thermal/thermal-helper.h
+++ b/thermal/thermal-helper.h
@@ -55,9 +55,14 @@
     boot_clock::time_point timestamp;
 };
 
-struct EmulSetting {
-    float emul_temp;
-    int emul_severity;
+struct EmulTemp {
+    float temp;
+    int severity;
+};
+
+struct OverrideStatus {
+    std::unique_ptr<EmulTemp> emul_temp;
+    bool max_throttling;
     bool pending_update;
 };
 
@@ -67,7 +72,7 @@
     ThrottlingSeverity prev_cold_severity;
     boot_clock::time_point last_update_time;
     ThermalSample thermal_cached;
-    std::unique_ptr<EmulSetting> emul_setting;
+    OverrideStatus override_status;
 };
 
 class ThermalHelper {
@@ -79,8 +84,10 @@
                                            std::vector<TemperatureThreshold> *thresholds) const = 0;
     virtual bool fillCurrentCoolingDevices(bool filterType, CoolingType type,
                                            std::vector<CoolingDevice> *coolingdevices) const = 0;
-    virtual bool emulTemp(std::string_view target_sensor, const float temp) = 0;
-    virtual bool emulSeverity(std::string_view target_sensor, const int severity) = 0;
+    virtual bool emulTemp(std::string_view target_sensor, const float temp,
+                          const bool max_throttling) = 0;
+    virtual bool emulSeverity(std::string_view target_sensor, const int severity,
+                              const bool max_throttling) = 0;
     virtual bool emulClear(std::string_view target_sensor) = 0;
     virtual bool isInitializedOk() const = 0;
     virtual bool readTemperature(
@@ -117,8 +124,10 @@
                                    std::vector<TemperatureThreshold> *thresholds) const override;
     bool fillCurrentCoolingDevices(bool filterType, CoolingType type,
                                    std::vector<CoolingDevice> *coolingdevices) const override;
-    bool emulTemp(std::string_view target_sensor, const float temp) override;
-    bool emulSeverity(std::string_view target_sensor, const int severity) override;
+    bool emulTemp(std::string_view target_sensor, const float temp,
+                  const bool max_throttling) override;
+    bool emulSeverity(std::string_view target_sensor, const int severity,
+                      const bool max_throttling) override;
     bool emulClear(std::string_view target_sensor) override;
 
     // Disallow copy and assign.
@@ -203,10 +212,13 @@
     // Read temperature data according to thermal sensor's info
     bool readThermalSensor(std::string_view sensor_name, float *temp, const bool force_sysfs,
                            std::map<std::string, float> *sensor_log_map);
+    float runVirtualTempEstimator(std::string_view sensor_name,
+                                  std::map<std::string, float> *sensor_log_map);
     void updateCoolingDevices(const std::vector<std::string> &cooling_devices_to_update);
     // Check the max CDEV state for cdev_ceiling
     void maxCoolingRequestCheck(
             std::unordered_map<std::string, BindedCdevInfo> *binded_cdev_info_map);
+    void checkUpdateSensorForEmul(std::string_view target_sensor, const bool max_throttling);
     sp<ThermalWatcher> thermal_watcher_;
     PowerFiles power_files_;
     ThermalFiles thermal_sensors_;
diff --git a/thermal/utils/power_files.cpp b/thermal/utils/power_files.cpp
index 767f434..2e4a77f 100644
--- a/thermal/utils/power_files.cpp
+++ b/thermal/utils/power_files.cpp
@@ -125,8 +125,8 @@
 
         if (power_history.size()) {
             power_status_map_[power_rail_info_pair.first] = {
-                    .power_history = power_history,
                     .last_update_time = boot_clock::time_point::min(),
+                    .power_history = power_history,
                     .last_updated_avg_power = NAN,
             };
         } else {
diff --git a/thermal/utils/powerhal_helper.cpp b/thermal/utils/powerhal_helper.cpp
index f8ac7d0..8b63390 100644
--- a/thermal/utils/powerhal_helper.cpp
+++ b/thermal/utils/powerhal_helper.cpp
@@ -58,7 +58,8 @@
     }
 
     const std::string kInstance = std::string(IPower::descriptor) + "/default";
-    ndk::SpAIBinder power_binder = ndk::SpAIBinder(AServiceManager_getService(kInstance.c_str()));
+    ndk::SpAIBinder power_binder =
+            ndk::SpAIBinder(AServiceManager_waitForService(kInstance.c_str()));
     ndk::SpAIBinder ext_power_binder;
 
     if (power_binder.get() == nullptr) {
diff --git a/thermal/utils/thermal_info.cpp b/thermal/utils/thermal_info.cpp
index c5535c5..d03b6d8 100644
--- a/thermal/utils/thermal_info.cpp
+++ b/thermal/utils/thermal_info.cpp
@@ -129,8 +129,75 @@
     *out = ret;
     return true;
 }
+
+bool getTempRangeInfoFromJsonValues(const Json::Value &values, TempRangeInfo *temp_range_info) {
+    if (values.size() != 2) {
+        LOG(ERROR) << "Temp Range Values size: " << values.size() << "is invalid.";
+        return false;
+    }
+
+    float min_temp = getFloatFromValue(values[0]);
+    float max_temp = getFloatFromValue(values[1]);
+
+    if (std::isnan(min_temp) || std::isnan(max_temp)) {
+        LOG(ERROR) << "Illegal temp range: thresholds not defined properly " << min_temp << " : "
+                   << max_temp;
+        return false;
+    }
+
+    if (min_temp > max_temp) {
+        LOG(ERROR) << "Illegal temp range: temp_min_threshold(" << min_temp
+                   << ") > temp_max_threshold(" << max_temp << ")";
+        return false;
+    }
+    temp_range_info->min_temp_threshold = min_temp;
+    temp_range_info->max_temp_threshold = max_temp;
+    LOG(INFO) << "Temp Range Info: " << temp_range_info->min_temp_threshold
+              << " <= t <= " << temp_range_info->max_temp_threshold;
+    return true;
+}
+
+bool getTempStuckInfoFromJsonValue(const Json::Value &values, TempStuckInfo *temp_stuck_info) {
+    if (values["MinStuckDuration"].empty()) {
+        LOG(ERROR) << "Minimum stuck duration not present.";
+        return false;
+    }
+    int min_stuck_duration_int = getIntFromValue(values["MinStuckDuration"]);
+    if (min_stuck_duration_int <= 0) {
+        LOG(ERROR) << "Invalid Minimum stuck duration " << min_stuck_duration_int;
+        return false;
+    }
+
+    if (values["MinPollingCount"].empty()) {
+        LOG(ERROR) << "Minimum polling count not present.";
+        return false;
+    }
+    int min_polling_count = getIntFromValue(values["MinPollingCount"]);
+    if (min_polling_count <= 0) {
+        LOG(ERROR) << "Invalid Minimum stuck duration " << min_polling_count;
+        return false;
+    }
+    temp_stuck_info->min_stuck_duration = std::chrono::milliseconds(min_stuck_duration_int);
+    temp_stuck_info->min_polling_count = min_polling_count;
+    LOG(INFO) << "Temp Stuck Info: polling_count=" << temp_stuck_info->min_polling_count
+              << " stuck_duration=" << temp_stuck_info->min_stuck_duration.count();
+    return true;
+}
 }  // namespace
 
+std::ostream &operator<<(std::ostream &stream, const SensorFusionType &sensor_fusion_type) {
+    switch (sensor_fusion_type) {
+        case SensorFusionType::SENSOR:
+            return stream << "SENSOR";
+        case SensorFusionType::ODPM:
+            return stream << "ODPM";
+        case SensorFusionType::CONSTANT:
+            return stream << "CONSTANT";
+        default:
+            return stream << "UNDEFINED";
+    }
+}
+
 bool ParseThermalConfig(std::string_view config_path, Json::Value *config) {
     std::string json_doc;
     if (!::android::base::ReadFileToString(config_path.data(), &json_doc)) {
@@ -165,6 +232,8 @@
     std::vector<std::string> coefficients;
     std::vector<SensorFusionType> coefficients_type;
     FormulaOption formula = FormulaOption::COUNT_THRESHOLD;
+    std::string vt_estimator_model_file;
+    std::unique_ptr<::thermal::vtestimator::VirtualTempEstimator> vt_estimator;
 
     Json::Value values = sensor["Combination"];
     if (values.size()) {
@@ -178,6 +247,21 @@
         return false;
     }
 
+    if (sensor["Formula"].asString().compare("COUNT_THRESHOLD") == 0) {
+        formula = FormulaOption::COUNT_THRESHOLD;
+    } else if (sensor["Formula"].asString().compare("WEIGHTED_AVG") == 0) {
+        formula = FormulaOption::WEIGHTED_AVG;
+    } else if (sensor["Formula"].asString().compare("MAXIMUM") == 0) {
+        formula = FormulaOption::MAXIMUM;
+    } else if (sensor["Formula"].asString().compare("MINIMUM") == 0) {
+        formula = FormulaOption::MINIMUM;
+    } else if (sensor["Formula"].asString().compare("USE_ML_MODEL") == 0) {
+        formula = FormulaOption::USE_ML_MODEL;
+    } else {
+        LOG(ERROR) << "Sensor[" << name << "]'s Formula is invalid";
+        return false;
+    }
+
     values = sensor["CombinationType"];
     if (!values.size()) {
         linked_sensors_type.reserve(linked_sensors.size());
@@ -216,7 +300,8 @@
         LOG(ERROR) << "Sensor[" << name << "] has no Coefficient setting";
         return false;
     }
-    if (linked_sensors.size() != coefficients.size()) {
+    if ((linked_sensors.size() != coefficients.size()) &&
+        (formula != FormulaOption::USE_ML_MODEL)) {
         LOG(ERROR) << "Sensor[" << name << "] has invalid Coefficient size";
         return false;
     }
@@ -280,21 +365,43 @@
         }
     }
 
-    if (sensor["Formula"].asString().compare("COUNT_THRESHOLD") == 0) {
-        formula = FormulaOption::COUNT_THRESHOLD;
-    } else if (sensor["Formula"].asString().compare("WEIGHTED_AVG") == 0) {
-        formula = FormulaOption::WEIGHTED_AVG;
-    } else if (sensor["Formula"].asString().compare("MAXIMUM") == 0) {
-        formula = FormulaOption::MAXIMUM;
-    } else if (sensor["Formula"].asString().compare("MINIMUM") == 0) {
-        formula = FormulaOption::MINIMUM;
-    } else {
-        LOG(ERROR) << "Sensor[" << name << "]'s Formula is invalid";
-        return false;
+    if (formula == FormulaOption::USE_ML_MODEL) {
+        if (sensor["ModelPath"].empty()) {
+            LOG(ERROR) << "Sensor[" << name << "] has no ModelPath";
+            return false;
+        }
+
+        if (!linked_sensors.size()) {
+            LOG(ERROR) << "Sensor[" << name << "] uses USE_ML_MODEL and has zero linked_sensors";
+            return false;
+        }
+
+        vt_estimator = std::make_unique<::thermal::vtestimator::VirtualTempEstimator>(
+                linked_sensors.size());
+        if (!vt_estimator) {
+            LOG(ERROR) << "Failed to create vt estimator for Sensor[" << name
+                       << "] with linked sensor size : " << linked_sensors.size();
+            return false;
+        }
+
+        vt_estimator_model_file = "vendor/etc/" + sensor["ModelPath"].asString();
+
+        ::thermal::vtestimator::VtEstimatorStatus ret =
+                vt_estimator->Initialize(vt_estimator_model_file.c_str());
+        if (ret != ::thermal::vtestimator::kVtEstimatorOk) {
+            LOG(ERROR) << "Failed to initialize vt estimator for Sensor[" << name
+                       << "] with ModelPath: " << vt_estimator_model_file
+                       << " with ret code : " << ret;
+            return false;
+        }
+
+        LOG(INFO) << "Successfully created vt_estimator for Sensor[" << name
+                  << "] with input samples: " << linked_sensors.size();
     }
-    virtual_sensor_info->reset(new VirtualSensorInfo{linked_sensors, linked_sensors_type,
-                                                     coefficients, coefficients_type, offset,
-                                                     trigger_sensors, formula});
+
+    virtual_sensor_info->reset(new VirtualSensorInfo{
+            linked_sensors, linked_sensors_type, coefficients, coefficients_type, offset,
+            trigger_sensors, formula, vt_estimator_model_file, std::move(vt_estimator)});
     return true;
 }
 
@@ -434,14 +541,14 @@
                 .limit_info = limit_info,
                 .power_thresholds = power_thresholds,
                 .release_logic = release_logic,
-                .high_power_check = high_power_check,
-                .throttling_with_power_link = throttling_with_power_link,
                 .cdev_weight_for_pid = cdev_weight_for_pid,
                 .cdev_ceiling = cdev_ceiling,
                 .max_release_step = max_release_step,
                 .max_throttle_step = max_throttle_step,
                 .cdev_floor_with_power_link = cdev_floor_with_power_link,
                 .power_rail = power_rail,
+                .high_power_check = high_power_check,
+                .throttling_with_power_link = throttling_with_power_link,
                 .enabled = enabled,
         };
     }
@@ -1023,14 +1130,6 @@
             return false;
         }
 
-        std::string rail;
-        if (power_rails[i]["Rail"].empty()) {
-            rail = name;
-        } else {
-            rail = power_rails[i]["Rail"].asString();
-        }
-        LOG(INFO) << "PowerRail[" << i << "]'s Rail: " << rail;
-
         std::vector<std::string> linked_power_rails;
         std::vector<float> coefficient;
         float offset = 0;
@@ -1124,7 +1223,6 @@
         }
 
         (*power_rails_parsed)[name] = {
-                .rail = rail,
                 .power_sample_count = power_sample_count,
                 .power_sample_delay = power_sample_delay,
                 .virtual_power_rail_info = std::move(virtual_power_rail_info),
@@ -1232,44 +1330,180 @@
     return true;
 }
 
-bool ParseStatsConfig(const Json::Value &config,
-                      const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
-                      const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_,
-                      StatsConfig *stats_config_parsed) {
-    Json::Value stats_config = config["Stats"];
+bool ParseSensorAbnormalStatsConfig(
+        const Json::Value &abnormal_stats_config,
+        const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
+        AbnormalStatsInfo *abnormal_stats_info_parsed) {
+    if (abnormal_stats_config.empty()) {
+        LOG(INFO) << "No sensors abnormality monitoring info present.";
+        return true;
+    }
 
+    Json::Value values;
+
+    std::optional<TempRangeInfo> default_temp_range_info;
+    std::vector<AbnormalStatsInfo::SensorsTempRangeInfo> sensors_temp_range_infos;
+    Json::Value outlier_temp_config = abnormal_stats_config["Outlier"];
+    if (outlier_temp_config) {
+        LOG(INFO) << "Start to parse outlier temp config.";
+
+        if (outlier_temp_config["Default"]) {
+            LOG(INFO) << "Start to parse defaultTempRange.";
+            if (!getTempRangeInfoFromJsonValues(outlier_temp_config["Default"],
+                                                &default_temp_range_info.value())) {
+                LOG(ERROR) << "Failed to parse default temp range config.";
+                return false;
+            }
+        }
+
+        Json::Value configs = outlier_temp_config["Configs"];
+        if (configs) {
+            std::unordered_set<std::string> sensors_parsed;
+            for (Json::Value::ArrayIndex i = 0; i < configs.size(); i++) {
+                LOG(INFO) << "Start to parse temp range config[" << i << "]";
+                AbnormalStatsInfo::SensorsTempRangeInfo sensors_temp_range_info;
+                values = configs[i]["Monitor"];
+                if (!values.size()) {
+                    LOG(ERROR) << "Invalid config no sensor list present for outlier temp "
+                                  "config.";
+                    return false;
+                }
+                for (Json::Value::ArrayIndex j = 0; j < values.size(); j++) {
+                    const std::string &sensor = values[j].asString();
+                    if (!sensor_info_map_.count(sensor)) {
+                        LOG(ERROR) << "Unknown sensor " << sensor;
+                        return false;
+                    }
+                    auto result = sensors_parsed.insert(sensor);
+                    if (!result.second) {
+                        LOG(ERROR) << "Duplicate Sensor Temp Range Config: " << sensor;
+                        return false;
+                    }
+                    LOG(INFO) << "Monitored sensor [" << j << "]: " << sensor;
+                    sensors_temp_range_info.sensors.push_back(sensor);
+                }
+                if (!getTempRangeInfoFromJsonValues(configs[i]["TempRange"],
+                                                    &sensors_temp_range_info.temp_range_info)) {
+                    LOG(ERROR) << "Failed to parse temp range config.";
+                    return false;
+                }
+                sensors_temp_range_infos.push_back(sensors_temp_range_info);
+            }
+        }
+    }
+    std::optional<TempStuckInfo> default_temp_stuck_info;
+    std::vector<AbnormalStatsInfo::SensorsTempStuckInfo> sensors_temp_stuck_infos;
+    Json::Value stuck_temp_config = abnormal_stats_config["Stuck"];
+    if (stuck_temp_config) {
+        LOG(INFO) << "Start to parse stuck temp config.";
+
+        if (stuck_temp_config["Default"]) {
+            LOG(INFO) << "Start to parse defaultTempStuck.";
+            if (!getTempStuckInfoFromJsonValue(stuck_temp_config["Default"],
+                                               &default_temp_stuck_info.value())) {
+                LOG(ERROR) << "Failed to parse default temp stuck config.";
+                return false;
+            }
+        }
+
+        Json::Value configs = stuck_temp_config["Configs"];
+        if (configs) {
+            std::unordered_set<std::string> sensors_parsed;
+            for (Json::Value::ArrayIndex i = 0; i < configs.size(); i++) {
+                LOG(INFO) << "Start to parse temp stuck config[" << i << "]";
+                AbnormalStatsInfo::SensorsTempStuckInfo sensor_temp_stuck_info;
+                values = configs[i]["Monitor"];
+                if (!values.size()) {
+                    LOG(ERROR) << "Invalid config no sensor list present for stuck temp "
+                                  "config.";
+                    return false;
+                }
+                for (Json::Value::ArrayIndex j = 0; j < values.size(); j++) {
+                    const std::string &sensor = values[j].asString();
+                    if (!sensor_info_map_.count(sensor)) {
+                        LOG(ERROR) << "Unknown sensor " << sensor;
+                        return false;
+                    }
+                    auto result = sensors_parsed.insert(sensor);
+                    if (!result.second) {
+                        LOG(ERROR) << "Duplicate Sensor Temp Stuck Config: " << sensor;
+                        return false;
+                    }
+                    LOG(INFO) << "Monitored sensor [" << j << "]: " << sensor;
+                    sensor_temp_stuck_info.sensors.push_back(sensor);
+                }
+                if (!getTempStuckInfoFromJsonValue(configs[i]["TempStuck"],
+                                                   &sensor_temp_stuck_info.temp_stuck_info)) {
+                    LOG(ERROR) << "Failed to parse temp stuck config.";
+                    return false;
+                }
+                sensors_temp_stuck_infos.push_back(sensor_temp_stuck_info);
+            }
+        }
+    }
+    *abnormal_stats_info_parsed = {
+            .default_temp_range_info = default_temp_range_info,
+            .sensors_temp_range_infos = sensors_temp_range_infos,
+            .default_temp_stuck_info = default_temp_stuck_info,
+            .sensors_temp_stuck_infos = sensors_temp_stuck_infos,
+    };
+    return true;
+}
+
+bool ParseSensorStatsConfig(const Json::Value &config,
+                            const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
+                            StatsInfo<float> *sensor_stats_info_parsed,
+                            AbnormalStatsInfo *abnormal_stats_info_parsed) {
+    Json::Value stats_config = config["Stats"];
     if (stats_config.empty()) {
         LOG(INFO) << "No Stats Config present.";
         return true;
     }
-
-    LOG(INFO) << "Parse Stats Config for Sensor Temp.";
-    // Parse sensor stats config
-    if (!ParseStatsInfo(stats_config["Sensors"], sensor_info_map_,
-                        &stats_config_parsed->sensor_stats_info,
-                        std::numeric_limits<float>::lowest())) {
-        LOG(ERROR) << "Failed to parse sensor temp stats info.";
-        stats_config_parsed->clear();
-        return false;
-    }
-
     // Parse cooling device user vote
-    if (stats_config["CoolingDevices"].empty()) {
-        LOG(INFO) << "No cooling device stats present.";
+    Json::Value sensor_config = stats_config["Sensors"];
+    if (sensor_config.empty()) {
+        LOG(INFO) << "No Sensor Stats Config present.";
         return true;
     }
-
-    LOG(INFO) << "Parse Stats Config for Sensor CDev Request.";
-    if (!ParseStatsInfo(stats_config["CoolingDevices"]["RecordVotePerSensor"],
-                        cooling_device_info_map_, &stats_config_parsed->cooling_device_request_info,
-                        -1)) {
-        LOG(ERROR) << "Failed to parse cooling device user vote stats info.";
-        stats_config_parsed->clear();
+    LOG(INFO) << "Parse Stats Config for Sensor Temp.";
+    // Parse sensor stats config
+    if (!ParseStatsInfo(stats_config["Sensors"], sensor_info_map_, sensor_stats_info_parsed,
+                        std::numeric_limits<float>::lowest())) {
+        LOG(ERROR) << "Failed to parse sensor temp stats info.";
+        sensor_stats_info_parsed->clear();
+        return false;
+    }
+    if (!ParseSensorAbnormalStatsConfig(sensor_config["Abnormality"], sensor_info_map_,
+                                        abnormal_stats_info_parsed)) {
+        LOG(ERROR) << "Failed to parse sensor abnormal stats config.";
         return false;
     }
     return true;
 }
 
+bool ParseCoolingDeviceStatsConfig(
+        const Json::Value &config,
+        const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_,
+        StatsInfo<int> *cooling_device_request_info_parsed) {
+    Json::Value stats_config = config["Stats"];
+    if (stats_config.empty()) {
+        LOG(INFO) << "No Stats Config present.";
+        return true;
+    }
+    // Parse cooling device user vote
+    if (stats_config["CoolingDevices"].empty()) {
+        LOG(INFO) << "No cooling device stats present.";
+        return true;
+    }
+    LOG(INFO) << "Parse Stats Config for Sensor CDev Request.";
+    if (!ParseStatsInfo(stats_config["CoolingDevices"]["RecordVotePerSensor"],
+                        cooling_device_info_map_, cooling_device_request_info_parsed, -1)) {
+        LOG(ERROR) << "Failed to parse cooling device user vote stats info.";
+        cooling_device_request_info_parsed->clear();
+        return false;
+    }
+    return true;
+}
 }  // namespace implementation
 }  // namespace thermal
 }  // namespace hardware
diff --git a/thermal/utils/thermal_info.h b/thermal/utils/thermal_info.h
index 12997f2..843424e 100644
--- a/thermal/utils/thermal_info.h
+++ b/thermal/utils/thermal_info.h
@@ -27,6 +27,8 @@
 #include <unordered_set>
 #include <variant>
 
+#include "virtualtemp_estimator/virtualtemp_estimator.h"
+
 namespace aidl {
 namespace android {
 namespace hardware {
@@ -48,11 +50,12 @@
 constexpr int kMaxStatsResidencyCount = 20;
 constexpr int kMaxStatsThresholdCount = kMaxStatsResidencyCount - 1;
 
-enum FormulaOption : uint32_t {
+enum class FormulaOption : uint32_t {
     COUNT_THRESHOLD = 0,
     WEIGHTED_AVG,
     MAXIMUM,
     MINIMUM,
+    USE_ML_MODEL
 };
 
 template <typename T>
@@ -93,12 +96,40 @@
     }
 };
 
-enum SensorFusionType : uint32_t {
+struct TempRangeInfo {
+    int max_temp_threshold;
+    int min_temp_threshold;
+};
+
+struct TempStuckInfo {
+    int min_polling_count;
+    std::chrono::milliseconds min_stuck_duration;
+};
+
+struct AbnormalStatsInfo {
+    struct SensorsTempRangeInfo {
+        std::vector<std::string> sensors;
+        TempRangeInfo temp_range_info;
+    };
+    struct SensorsTempStuckInfo {
+        std::vector<std::string> sensors;
+        TempStuckInfo temp_stuck_info;
+    };
+
+    std::optional<TempRangeInfo> default_temp_range_info;
+    std::vector<SensorsTempRangeInfo> sensors_temp_range_infos;
+    std::optional<TempStuckInfo> default_temp_stuck_info;
+    std::vector<SensorsTempStuckInfo> sensors_temp_stuck_infos;
+};
+
+enum class SensorFusionType : uint32_t {
     SENSOR = 0,
     ODPM,
     CONSTANT,
 };
 
+std::ostream &operator<<(std::ostream &os, const SensorFusionType &sensor_fusion_type);
+
 struct VirtualSensorInfo {
     std::vector<std::string> linked_sensors;
     std::vector<SensorFusionType> linked_sensors_type;
@@ -108,6 +139,8 @@
     float offset;
     std::vector<std::string> trigger_sensors;
     FormulaOption formula;
+    std::string vt_estimator_model_file;
+    std::unique_ptr<::thermal::vtestimator::VirtualTempEstimator> vt_estimator;
 };
 
 struct VirtualPowerRailInfo {
@@ -118,7 +151,7 @@
 };
 
 // The method when the ODPM power is lower than threshold
-enum ReleaseLogic : uint32_t {
+enum class ReleaseLogic : uint32_t {
     INCREASE = 0,      // Increase throttling by step
     DECREASE,          // Decrease throttling by step
     STEPWISE,          // Support both increase and decrease logix
@@ -195,7 +228,6 @@
 };
 
 struct PowerRailInfo {
-    std::string rail;
     int power_sample_count;
     std::chrono::milliseconds power_sample_delay;
     std::unique_ptr<VirtualPowerRailInfo> virtual_power_rail_info;
@@ -208,10 +240,14 @@
                         std::unordered_map<std::string, CdevInfo> *cooling_device_parsed);
 bool ParsePowerRailInfo(const Json::Value &config,
                         std::unordered_map<std::string, PowerRailInfo> *power_rail_parsed);
-bool ParseStatsConfig(const Json::Value &config,
-                      const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
-                      const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_,
-                      StatsConfig *stats_config);
+bool ParseSensorStatsConfig(const Json::Value &config,
+                            const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
+                            StatsInfo<float> *sensor_stats_info_parsed,
+                            AbnormalStatsInfo *abnormal_stats_info_parsed);
+bool ParseCoolingDeviceStatsConfig(
+        const Json::Value &config,
+        const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_,
+        StatsInfo<int> *cooling_device_request_info_parsed);
 }  // namespace implementation
 }  // namespace thermal
 }  // namespace hardware
diff --git a/thermal/utils/thermal_stats_helper.cpp b/thermal/utils/thermal_stats_helper.cpp
index bbd9954..d4571d9 100644
--- a/thermal/utils/thermal_stats_helper.cpp
+++ b/thermal/utils/thermal_stats_helper.cpp
@@ -18,7 +18,6 @@
 
 #include <android-base/logging.h>
 #include <android/binder_manager.h>
-#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
 
 #include <algorithm>
 #include <numeric>
@@ -75,26 +74,49 @@
     return bucket;
 }
 
+void resetCurrentTempStatus(CurrTempStatus *curr_temp_status, float new_temp) {
+    curr_temp_status->temp = new_temp;
+    curr_temp_status->start_time = boot_clock::now();
+    curr_temp_status->repeat_count = 1;
+}
+
 }  // namespace
 
 bool ThermalStatsHelper::initializeStats(
         const Json::Value &config,
         const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
         const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_) {
-    StatsConfig stats_config;
-    if (!ParseStatsConfig(config, sensor_info_map_, cooling_device_info_map_, &stats_config)) {
-        LOG(ERROR) << "Failed to parse stats config";
+    StatsInfo<float> sensor_stats_info;
+    AbnormalStatsInfo abnormal_stats_info;
+    if (!ParseSensorStatsConfig(config, sensor_info_map_, &sensor_stats_info,
+                                &abnormal_stats_info)) {
+        LOG(ERROR) << "Failed to parse sensor stats config";
         return false;
     }
-    bool is_initialized_ =
-            initializeSensorTempStats(stats_config.sensor_stats_info, sensor_info_map_) &&
-            initializeSensorCdevRequestStats(stats_config.cooling_device_request_info,
-                                             sensor_info_map_, cooling_device_info_map_);
-    if (is_initialized_) {
-        last_total_stats_report_time = boot_clock::now();
-        LOG(INFO) << "Thermal Stats Initialized Successfully";
+    StatsInfo<int> cooling_device_request_info;
+    if (!ParseCoolingDeviceStatsConfig(config, cooling_device_info_map_,
+                                       &cooling_device_request_info)) {
+        LOG(ERROR) << "Failed to parse cooling device stats config";
+        return false;
     }
-    return is_initialized_;
+    if (!initializeSensorTempStats(sensor_stats_info, sensor_info_map_)) {
+        LOG(ERROR) << "Failed to initialize sensor temp stats";
+        return false;
+    }
+    if (!initializeSensorCdevRequestStats(cooling_device_request_info, sensor_info_map_,
+                                          cooling_device_info_map_)) {
+        LOG(ERROR) << "Failed to initialize sensor cooling device request stats";
+        return false;
+    }
+    if (!initializeSensorAbnormalityStats(abnormal_stats_info, sensor_info_map_)) {
+        LOG(ERROR) << "Failed to initialize sensor abnormal stats";
+        return false;
+    }
+
+    last_total_stats_report_time = boot_clock::now();
+    abnormal_stats_reported_per_update_interval = 0;
+    LOG(INFO) << "Thermal Stats Initialized Successfully";
+    return true;
 }
 
 bool ThermalStatsHelper::initializeSensorCdevRequestStats(
@@ -161,7 +183,8 @@
 bool ThermalStatsHelper::initializeSensorTempStats(
         const StatsInfo<float> &sensor_stats_info,
         const std::unordered_map<std::string, SensorInfo> &sensor_info_map_) {
-    std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_);
+    std::unique_lock<std::shared_mutex> _lock(sensor_stats_mutex_);
+    auto &temp_stats_map_ = sensor_stats.temp_stats_map_;
     const int severity_time_in_state_size = kThrottlingSeverityCount;
     for (const auto &[sensor, sensor_info] : sensor_info_map_) {
         // Record by severity
@@ -169,7 +192,7 @@
             isRecordByDefaultThreshold(
                     sensor_stats_info.record_by_default_threshold_all_or_name_set_, sensor)) {
             // number of buckets = number of severity
-            sensor_temp_stats_map_[sensor].stats_by_default_threshold =
+            temp_stats_map_[sensor].stats_by_default_threshold =
                     StatsRecord(severity_time_in_state_size);
             LOG(INFO) << "Sensor temp stats on basis of severity initialized for [" << sensor
                       << "]";
@@ -178,8 +201,7 @@
         // Record by custom threshold
         if (sensor_stats_info.record_by_threshold.count(sensor)) {
             for (const auto &threshold_list : sensor_stats_info.record_by_threshold.at(sensor)) {
-                sensor_temp_stats_map_[sensor].stats_by_custom_threshold.emplace_back(
-                        threshold_list);
+                temp_stats_map_[sensor].stats_by_custom_threshold.emplace_back(threshold_list);
                 LOG(INFO) << "Sensor temp stats on basis of threshold initialized for [" << sensor
                           << "]";
             }
@@ -188,6 +210,54 @@
     return true;
 }
 
+bool ThermalStatsHelper::initializeSensorAbnormalityStats(
+        const AbnormalStatsInfo &abnormal_stats_info,
+        const std::unordered_map<std::string, SensorInfo> &sensor_info_map_) {
+    std::unique_lock<std::shared_mutex> _lock(sensor_stats_mutex_);
+    auto &temp_range_info_map_ = sensor_stats.temp_range_info_map_;
+    for (const auto &sensors_temp_range_info : abnormal_stats_info.sensors_temp_range_infos) {
+        const auto &temp_range_info_ptr =
+                std::make_shared<TempRangeInfo>(sensors_temp_range_info.temp_range_info);
+        for (const auto &sensor : sensors_temp_range_info.sensors) {
+            temp_range_info_map_[sensor] = temp_range_info_ptr;
+        }
+    }
+    auto &temp_stuck_info_map_ = sensor_stats.temp_stuck_info_map_;
+    for (const auto &sensors_temp_stuck_info : abnormal_stats_info.sensors_temp_stuck_infos) {
+        const auto &temp_stuck_info_ptr =
+                std::make_shared<TempStuckInfo>(sensors_temp_stuck_info.temp_stuck_info);
+        for (const auto &sensor : sensors_temp_stuck_info.sensors) {
+            temp_stuck_info_map_[sensor] = temp_stuck_info_ptr;
+        }
+    }
+    const auto &default_temp_range_info_ptr =
+            abnormal_stats_info.default_temp_range_info
+                    ? std::make_shared<TempRangeInfo>(
+                              abnormal_stats_info.default_temp_range_info.value())
+                    : nullptr;
+    const auto &default_temp_stuck_info_ptr =
+            abnormal_stats_info.default_temp_stuck_info
+                    ? std::make_shared<TempStuckInfo>(
+                              abnormal_stats_info.default_temp_stuck_info.value())
+                    : nullptr;
+    for (const auto &sensor_info : sensor_info_map_) {
+        const auto &sensor = sensor_info.first;
+        if (default_temp_range_info_ptr && !temp_range_info_map_.count(sensor))
+            temp_range_info_map_[sensor] = default_temp_range_info_ptr;
+        if (default_temp_stuck_info_ptr && !temp_stuck_info_map_.count(sensor))
+            temp_stuck_info_map_[sensor] = default_temp_stuck_info_ptr;
+    }
+
+    for (const auto &sensor_temp_stuck_info : temp_stuck_info_map_) {
+        sensor_stats.curr_temp_status_map_[sensor_temp_stuck_info.first] = {
+                .temp = std::numeric_limits<float>::min(),
+                .start_time = boot_clock::time_point::min(),
+                .repeat_count = 0,
+        };
+    }
+    return true;
+}
+
 void ThermalStatsHelper::updateStatsRecord(StatsRecord *stats_record, int new_state) {
     const auto now = boot_clock::now();
     const auto cur_state_duration = std::chrono::duration_cast<std::chrono::milliseconds>(
@@ -231,11 +301,13 @@
 
 void ThermalStatsHelper::updateSensorTempStatsByThreshold(std::string_view sensor,
                                                           float temperature) {
-    std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_);
-    if (!sensor_temp_stats_map_.count(sensor.data())) {
+    std::unique_lock<std::shared_mutex> _lock(sensor_stats_mutex_);
+    verifySensorAbnormality(sensor, temperature);
+    auto &temp_stats_map_ = sensor_stats.temp_stats_map_;
+    if (!temp_stats_map_.count(sensor.data())) {
         return;
     }
-    auto &sensor_temp_stats = sensor_temp_stats_map_[sensor.data()];
+    auto &sensor_temp_stats = temp_stats_map_[sensor.data()];
     for (auto &stats_by_threshold : sensor_temp_stats.stats_by_custom_threshold) {
         int value = calculateThresholdBucket(stats_by_threshold.thresholds, temperature);
         if (value != stats_by_threshold.stats_record.cur_state) {
@@ -256,11 +328,11 @@
 
 void ThermalStatsHelper::updateSensorTempStatsBySeverity(std::string_view sensor,
                                                          const ThrottlingSeverity &severity) {
-    std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_);
-    if (sensor_temp_stats_map_.count(sensor.data()) &&
-        sensor_temp_stats_map_[sensor.data()].stats_by_default_threshold.has_value()) {
-        auto &stats_record =
-                sensor_temp_stats_map_[sensor.data()].stats_by_default_threshold.value();
+    std::unique_lock<std::shared_mutex> _lock(sensor_stats_mutex_);
+    auto &temp_stats_map_ = sensor_stats.temp_stats_map_;
+    if (temp_stats_map_.count(sensor.data()) &&
+        temp_stats_map_[sensor.data()].stats_by_default_threshold.has_value()) {
+        auto &stats_record = temp_stats_map_[sensor.data()].stats_by_default_threshold.value();
         int value = static_cast<int>(severity);
         if (value != stats_record.cur_state) {
             LOG(VERBOSE) << "Updating sensor stats for sensor: " << sensor.data()
@@ -270,6 +342,52 @@
     }
 }
 
+void ThermalStatsHelper::verifySensorAbnormality(std::string_view sensor, float temp) {
+    LOG(VERBOSE) << "Verify sensor abnormality for " << sensor << " with temp " << temp;
+    if (sensor_stats.temp_range_info_map_.count(sensor.data())) {
+        const auto &temp_range_info = sensor_stats.temp_range_info_map_[sensor.data()];
+        if (temp < temp_range_info->min_temp_threshold) {
+            LOG(ERROR) << "Outlier Temperature Detected, sensor: " << sensor.data()
+                       << " temp: " << temp << " < " << temp_range_info->min_temp_threshold;
+            reportThermalAbnormality(ThermalSensorAbnormalityDetected::EXTREME_LOW_TEMP, sensor,
+                                     std::round(temp));
+        } else if (temp > temp_range_info->max_temp_threshold) {
+            LOG(ERROR) << "Outlier Temperature Detected, sensor: " << sensor.data()
+                       << " temp: " << temp << " > " << temp_range_info->max_temp_threshold;
+            reportThermalAbnormality(ThermalSensorAbnormalityDetected::EXTREME_HIGH_TEMP, sensor,
+                                     std::round(temp));
+        }
+    }
+    if (sensor_stats.temp_stuck_info_map_.count(sensor.data())) {
+        const auto &temp_stuck_info = sensor_stats.temp_stuck_info_map_[sensor.data()];
+        auto &curr_temp_status = sensor_stats.curr_temp_status_map_[sensor.data()];
+        LOG(VERBOSE) << "Current Temp Status: temp=" << curr_temp_status.temp
+                     << " repeat_count=" << curr_temp_status.repeat_count
+                     << " start_time=" << curr_temp_status.start_time.time_since_epoch().count();
+        if (std::fabs(curr_temp_status.temp - temp) <= kPrecisionThreshold) {
+            curr_temp_status.repeat_count++;
+            if (temp_stuck_info->min_polling_count <= curr_temp_status.repeat_count) {
+                auto time_elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
+                        boot_clock::now() - curr_temp_status.start_time);
+                if (temp_stuck_info->min_stuck_duration <= time_elapsed_ms) {
+                    LOG(ERROR) << "Stuck Temperature Detected, sensor: " << sensor.data()
+                               << " temp: " << temp << " repeated "
+                               << temp_stuck_info->min_polling_count << " times for "
+                               << time_elapsed_ms.count() << "ms";
+                    if (reportThermalAbnormality(ThermalSensorAbnormalityDetected::SENSOR_STUCK,
+                                                 sensor, std::round(temp))) {
+                        // reset current status to verify for sensor stuck with start time as
+                        // current polling
+                        resetCurrentTempStatus(&curr_temp_status, temp);
+                    }
+                }
+            }
+        } else {
+            resetCurrentTempStatus(&curr_temp_status, temp);
+        }
+    }
+}
+
 int ThermalStatsHelper::reportStats() {
     const auto curTime = boot_clock::now();
     const auto since_last_total_stats_update_ms =
@@ -290,13 +408,14 @@
     int count_failed_reporting =
             reportAllSensorTempStats(stats_client) + reportAllSensorCdevRequestStats(stats_client);
     last_total_stats_report_time = curTime;
+    abnormal_stats_reported_per_update_interval = 0;
     return count_failed_reporting;
 }
 
 int ThermalStatsHelper::reportAllSensorTempStats(const std::shared_ptr<IStats> &stats_client) {
     int count_failed_reporting = 0;
-    std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_);
-    for (auto &[sensor, temp_stats] : sensor_temp_stats_map_) {
+    std::unique_lock<std::shared_mutex> _lock(sensor_stats_mutex_);
+    for (auto &[sensor, temp_stats] : sensor_stats.temp_stats_map_) {
         for (size_t threshold_set_idx = 0;
              threshold_set_idx < temp_stats.stats_by_custom_threshold.size(); threshold_set_idx++) {
             auto &stats_by_threshold = temp_stats.stats_by_custom_threshold[threshold_set_idx];
@@ -445,6 +564,40 @@
     return stats_residency;
 }
 
+bool ThermalStatsHelper::reportThermalAbnormality(
+        const ThermalSensorAbnormalityDetected::AbnormalityType &type, std::string_view name,
+        std::optional<int> reading) {
+    const auto value_str = reading.has_value() ? std::to_string(reading.value()) : "undefined";
+    if (abnormal_stats_reported_per_update_interval >= kMaxAbnormalLoggingPerUpdateInterval) {
+        LOG(ERROR) << "Thermal abnormal atom logging rate limited for " << name.data()
+                   << " with value " << value_str;
+        return true;
+    }
+    const std::shared_ptr<IStats> stats_client = getStatsService();
+    if (!stats_client) {
+        LOG(ERROR) << "Unable to get AIDL Stats service";
+        return false;
+    }
+    std::vector<VendorAtomValue> values(3);
+    values[ThermalSensorAbnormalityDetected::kTypeFieldNumber - kVendorAtomOffset] =
+            VendorAtomValue::make<VendorAtomValue::intValue>(type);
+    values[ThermalSensorAbnormalityDetected::kSensorFieldNumber - kVendorAtomOffset] =
+            VendorAtomValue::make<VendorAtomValue::stringValue>(name);
+    if (reading.has_value()) {
+        values[ThermalSensorAbnormalityDetected::kTempFieldNumber - kVendorAtomOffset] =
+                VendorAtomValue::make<VendorAtomValue::intValue>(reading.value());
+    }
+    if (!reportAtom(stats_client, PixelAtoms::Atom::kThermalSensorAbnormalityDetected,
+                    std::move(values))) {
+        LOG(ERROR) << "Failed to log thermal abnormal atom for " << name.data() << " with value "
+                   << value_str;
+        return false;
+    }
+    LOG(INFO) << "Thermal abnormality reported for " << name.data() << " with value " << value_str;
+    abnormal_stats_reported_per_update_interval++;
+    return true;
+}
+
 bool ThermalStatsHelper::reportAtom(const std::shared_ptr<IStats> &stats_client,
                                     const int32_t &atom_id, std::vector<VendorAtomValue> &&values) {
     LOG(VERBOSE) << "Reporting thermal stats for atom_id " << atom_id;
@@ -467,7 +620,7 @@
 }
 
 std::unordered_map<std::string, SensorTempStats> ThermalStatsHelper::GetSensorTempStatsSnapshot() {
-    auto sensor_temp_stats_snapshot = sensor_temp_stats_map_;
+    auto sensor_temp_stats_snapshot = sensor_stats.temp_stats_map_;
     for (auto &sensor_temp_stats_pair : sensor_temp_stats_snapshot) {
         for (auto &temp_stats : sensor_temp_stats_pair.second.stats_by_custom_threshold) {
             // update the last unclosed entry and start new record with same state
diff --git a/thermal/utils/thermal_stats_helper.h b/thermal/utils/thermal_stats_helper.h
index ef2f811..fde9e9c 100644
--- a/thermal/utils/thermal_stats_helper.h
+++ b/thermal/utils/thermal_stats_helper.h
@@ -19,6 +19,7 @@
 #include <aidl/android/frameworks/stats/IStats.h>
 #include <aidl/android/hardware/thermal/Temperature.h>
 #include <android-base/chrono_utils.h>
+#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
 
 #include <chrono>
 #include <shared_mutex>
@@ -37,10 +38,18 @@
 using aidl::android::frameworks::stats::IStats;
 using aidl::android::frameworks::stats::VendorAtomValue;
 using ::android::base::boot_clock;
+using ::android::hardware::google::pixel::PixelAtoms::ThermalSensorAbnormalityDetected;
 using std::chrono::system_clock;
 using SystemTimePoint = std::chrono::time_point<std::chrono::system_clock>;
 
+// Number of abnormal atoms to be logged per kUpdateIntervalMs
+constexpr int kMaxAbnormalLoggingPerUpdateInterval = 20;
 constexpr int kMaxStatsReportingFailCount = 3;
+// Proto messages are 1-indexed and VendorAtom field numbers start at 2, so
+// store everything in the values array at the index of the field number
+// -2.
+constexpr int kVendorAtomOffset = 2;
+constexpr float kPrecisionThreshold = 1e-4;
 
 struct StatsRecord {
     int cur_state; /* temperature / cdev state at current time */
@@ -98,6 +107,23 @@
     SystemTimePoint min_temp_timestamp = SystemTimePoint::min();
 };
 
+struct CurrTempStatus {
+    float temp;
+    boot_clock::time_point start_time;
+    int repeat_count;
+};
+
+struct SensorStats {
+    // Temperature residency stats for each sensor being watched
+    std::unordered_map<std::string, SensorTempStats> temp_stats_map_;
+    // Min, Max Temp threshold info for each sensor being monitored
+    std::unordered_map<std::string, std::shared_ptr<TempRangeInfo>> temp_range_info_map_;
+    // Temperature Stuck info for each sensor being monitored
+    std::unordered_map<std::string, std::shared_ptr<TempStuckInfo>> temp_stuck_info_map_;
+    // Current temperature status for each sensor being monitored for stuck
+    std::unordered_map<std::string, CurrTempStatus> curr_temp_status_map_;
+};
+
 class ThermalStatsHelper {
   public:
     ThermalStatsHelper() = default;
@@ -122,6 +148,8 @@
      *  >0, count represents the number of stats failed to report.
      */
     int reportStats();
+    bool reportThermalAbnormality(const ThermalSensorAbnormalityDetected::AbnormalityType &type,
+                                  std::string_view name, std::optional<int> reading);
     // Get a snapshot of Thermal Stats Sensor Map till that point in time
     std::unordered_map<std::string, SensorTempStats> GetSensorTempStatsSnapshot();
     // Get a snapshot of Thermal Stats Sensor Map till that point in time
@@ -132,10 +160,9 @@
     static constexpr std::chrono::milliseconds kUpdateIntervalMs =
             std::chrono::duration_cast<std::chrono::milliseconds>(24h);
     boot_clock::time_point last_total_stats_report_time = boot_clock::time_point::min();
-
-    mutable std::shared_mutex sensor_temp_stats_map_mutex_;
-    // Temperature stats for each sensor being watched
-    std::unordered_map<std::string, SensorTempStats> sensor_temp_stats_map_;
+    int abnormal_stats_reported_per_update_interval = 0;
+    mutable std::shared_mutex sensor_stats_mutex_;
+    SensorStats sensor_stats;
     mutable std::shared_mutex sensor_cdev_request_stats_map_mutex_;
     // userVote request stat for the sensor to the corresponding cdev (sensor -> cdev ->
     // StatsRecord)
@@ -149,7 +176,11 @@
             const StatsInfo<int> &request_stats_info,
             const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
             const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_);
+    bool initializeSensorAbnormalityStats(
+            const AbnormalStatsInfo &abnormal_stats_info,
+            const std::unordered_map<std::string, SensorInfo> &sensor_info_map_);
     void updateStatsRecord(StatsRecord *stats_record, int new_state);
+    void verifySensorAbnormality(std::string_view sensor, float temperature);
     int reportAllSensorTempStats(const std::shared_ptr<IStats> &stats_client);
     bool reportSensorTempStats(const std::shared_ptr<IStats> &stats_client, std::string_view sensor,
                                const SensorTempStats &sensor_temp_stats, StatsRecord *stats_record);
diff --git a/thermal/utils/thermal_throttling.cpp b/thermal/utils/thermal_throttling.cpp
index 57dca16..295f76f 100644
--- a/thermal/utils/thermal_throttling.cpp
+++ b/thermal/utils/thermal_throttling.cpp
@@ -187,7 +187,8 @@
 // return power budget based on PID algo
 float ThermalThrottling::updatePowerBudget(const Temperature &temp, const SensorInfo &sensor_info,
                                            std::chrono::milliseconds time_elapsed_ms,
-                                           ThrottlingSeverity curr_severity) {
+                                           ThrottlingSeverity curr_severity,
+                                           const bool max_throttling) {
     float p = 0, d = 0;
     float power_budget = std::numeric_limits<float>::max();
     bool target_changed = false;
@@ -210,6 +211,11 @@
 
     // Compute PID
     float err = sensor_info.hot_thresholds[target_state] - temp.value;
+
+    if (max_throttling && err <= 0) {
+        return sensor_info.throttling_info->min_alloc_power[target_state];
+    }
+
     p = err * (err < 0 ? sensor_info.throttling_info->k_po[target_state]
                        : sensor_info.throttling_info->k_pu[target_state]);
 
@@ -317,7 +323,8 @@
         const Temperature &temp, const SensorInfo &sensor_info,
         const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms,
         const std::unordered_map<std::string, PowerStatus> &power_status_map,
-        const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map) {
+        const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map,
+        const bool max_throttling) {
     float total_weight = 0;
     float last_updated_avg_power = NAN;
     float allocated_power = 0;
@@ -329,7 +336,8 @@
     std::string log_buf;
 
     std::unique_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_);
-    auto total_power_budget = updatePowerBudget(temp, sensor_info, time_elapsed_ms, curr_severity);
+    auto total_power_budget =
+            updatePowerBudget(temp, sensor_info, time_elapsed_ms, curr_severity, max_throttling);
     const auto &profile = thermal_throttling_status_map_[temp.name].profile;
 
     if (sensor_info.throttling_info->excluded_power_info_map.size()) {
@@ -466,30 +474,34 @@
                         thermal_throttling_status_map_[temp.name].pid_cdev_request_map.at(
                                 binded_cdev_info_pair.first);
 
-                if (binded_cdev_info_pair.second.max_release_step !=
-                            std::numeric_limits<int>::max() &&
-                    (power_data_invalid || cdev_power_adjustment > 0)) {
-                    if (!power_data_invalid && curr_cdev_vote < max_cdev_vote) {
-                        cdev_power_budget = cdev_info.state2power[curr_cdev_vote];
-                        LOG(VERBOSE) << temp.name << "'s " << binded_cdev_info_pair.first
-                                     << " vote: " << curr_cdev_vote
-                                     << " is lower than max cdev vote: " << max_cdev_vote;
-                    } else {
-                        const auto target_state = std::max(
-                                curr_cdev_vote - binded_cdev_info_pair.second.max_release_step, 0);
-                        cdev_power_budget =
-                                std::min(cdev_power_budget, cdev_info.state2power[target_state]);
+                if (!max_throttling) {
+                    if (binded_cdev_info_pair.second.max_release_step !=
+                                std::numeric_limits<int>::max() &&
+                        (power_data_invalid || cdev_power_adjustment > 0)) {
+                        if (!power_data_invalid && curr_cdev_vote < max_cdev_vote) {
+                            cdev_power_budget = cdev_info.state2power[curr_cdev_vote];
+                            LOG(VERBOSE) << temp.name << "'s " << binded_cdev_info_pair.first
+                                         << " vote: " << curr_cdev_vote
+                                         << " is lower than max cdev vote: " << max_cdev_vote;
+                        } else {
+                            const auto target_state = std::max(
+                                    curr_cdev_vote - binded_cdev_info_pair.second.max_release_step,
+                                    0);
+                            cdev_power_budget = std::min(cdev_power_budget,
+                                                         cdev_info.state2power[target_state]);
+                        }
                     }
-                }
 
-                if (binded_cdev_info_pair.second.max_throttle_step !=
-                            std::numeric_limits<int>::max() &&
-                    (power_data_invalid || cdev_power_adjustment < 0)) {
-                    const auto target_state = std::min(
-                            curr_cdev_vote + binded_cdev_info_pair.second.max_throttle_step,
-                            cdev_info.max_state);
-                    cdev_power_budget =
-                            std::max(cdev_power_budget, cdev_info.state2power[target_state]);
+                    if (binded_cdev_info_pair.second.max_throttle_step !=
+                                std::numeric_limits<int>::max() &&
+                        (power_data_invalid || cdev_power_adjustment < 0)) {
+                        const auto target_state = std::min(
+                                curr_cdev_vote + binded_cdev_info_pair.second.max_throttle_step,
+                                binded_cdev_info_pair.second
+                                        .cdev_ceiling[static_cast<size_t>(curr_severity)]);
+                        cdev_power_budget =
+                                std::max(cdev_power_budget, cdev_info.state2power[target_state]);
+                    }
                 }
 
                 thermal_throttling_status_map_[temp.name].pid_power_budget_map.at(
@@ -671,7 +683,8 @@
         const Temperature &temp, const SensorInfo &sensor_info,
         const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms,
         const std::unordered_map<std::string, PowerStatus> &power_status_map,
-        const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map) {
+        const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map,
+        const bool max_throttling) {
     if (!thermal_throttling_status_map_.count(temp.name)) {
         return;
     }
@@ -682,7 +695,7 @@
 
     if (thermal_throttling_status_map_[temp.name].pid_power_budget_map.size()) {
         if (!allocatePowerToCdev(temp, sensor_info, curr_severity, time_elapsed_ms,
-                                 power_status_map, cooling_device_info_map)) {
+                                 power_status_map, cooling_device_info_map, max_throttling)) {
             LOG(ERROR) << "Sensor " << temp.name << " PID request cdev failed";
             // Clear the CDEV request if the power budget is failed to be allocated
             for (auto &pid_cdev_request_pair :
@@ -773,6 +786,7 @@
         }
         request_state = std::min(request_state, cdev_ceiling);
         if (cdev_request_pair.second != request_state) {
+            ATRACE_INT((atrace_prefix + std::string("-final_request")).c_str(), request_state);
             if (updateCdevMaxRequestAndNotifyIfChange(cdev_name, cdev_request_pair.second,
                                                       request_state)) {
                 cooling_devices_to_update->emplace_back(cdev_name);
diff --git a/thermal/utils/thermal_throttling.h b/thermal/utils/thermal_throttling.h
index 76c41e0..cac7f8d 100644
--- a/thermal/utils/thermal_throttling.h
+++ b/thermal/utils/thermal_throttling.h
@@ -62,17 +62,12 @@
     ThermalThrottling(const ThermalThrottling &) = delete;
     void operator=(const ThermalThrottling &) = delete;
 
-    // Check if the thermal throttling profile need to be switched
-    void parseProfileProperty(std::string_view sensor_name, const SensorInfo &sensor_info);
     // Clear throttling data
     void clearThrottlingData(std::string_view sensor_name, const SensorInfo &sensor_info);
     // Register map for throttling algo
     bool registerThermalThrottling(
             std::string_view sensor_name, const std::shared_ptr<ThrottlingInfo> &throttling_info,
             const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map);
-    // Register map for throttling release algo
-    bool registerThrottlingReleaseToWatch(std::string_view sensor_name, std::string_view cdev_name,
-                                          const BindedCdevInfo &binded_cdev_info);
     // Get throttling status map
     const std::unordered_map<std::string, ThermalThrottlingStatus> &GetThermalThrottlingStatusMap()
             const {
@@ -84,7 +79,8 @@
             const Temperature &temp, const SensorInfo &sensor_info,
             const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms,
             const std::unordered_map<std::string, PowerStatus> &power_status_map,
-            const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map);
+            const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map,
+            const bool max_throttling = false);
 
     // Compute the throttling target from all the sensors' request
     void computeCoolingDevicesRequest(std::string_view sensor_name, const SensorInfo &sensor_info,
@@ -95,10 +91,12 @@
     bool getCdevMaxRequest(std::string_view cdev_name, int *max_state);
 
   private:
+    // Check if the thermal throttling profile need to be switched
+    void parseProfileProperty(std::string_view sensor_name, const SensorInfo &sensor_info);
     // PID algo - get the total power budget
     float updatePowerBudget(const Temperature &temp, const SensorInfo &sensor_info,
                             std::chrono::milliseconds time_elapsed_ms,
-                            ThrottlingSeverity curr_severity);
+                            ThrottlingSeverity curr_severity, const bool max_throttling);
 
     // PID algo - return the power number from excluded power rail list
     float computeExcludedPower(const SensorInfo &sensor_info,
@@ -111,7 +109,8 @@
             const Temperature &temp, const SensorInfo &sensor_info,
             const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms,
             const std::unordered_map<std::string, PowerStatus> &power_status_map,
-            const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map);
+            const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map,
+            const bool max_throttling);
     // PID algo - map the target throttling state according to the power budget
     void updateCdevRequestByPower(
             std::string sensor_name,
diff --git a/thermal/utils/thermal_watcher.cpp b/thermal/utils/thermal_watcher.cpp
index d8bc92e..f8ca2c2 100644
--- a/thermal/utils/thermal_watcher.cpp
+++ b/thermal/utils/thermal_watcher.cpp
@@ -401,7 +401,7 @@
 
 bool ThermalWatcher::startWatchingDeviceFiles() {
     if (cb_) {
-        auto ret = this->run("FileWatcherThread", ::android::PRIORITY_HIGHEST);
+        auto ret = this->run("FileWatcherThread", -10);
         if (ret != ::android::NO_ERROR) {
             LOG(ERROR) << "ThermalWatcherThread start fail";
             return false;
diff --git a/thermal/virtualtemp_estimator/virtualtemp_estimator.cpp b/thermal/virtualtemp_estimator/virtualtemp_estimator.cpp
new file mode 100644
index 0000000..2dc2185
--- /dev/null
+++ b/thermal/virtualtemp_estimator/virtualtemp_estimator.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+#include "virtualtemp_estimator.h"
+
+#include <android-base/logging.h>
+#include <dlfcn.h>
+
+#include <vector>
+
+namespace thermal {
+namespace vtestimator {
+
+void VirtualTempEstimator::LoadTFLiteWrapper() {
+    if (!data_) {
+        LOG(ERROR) << "data_ is nullptr during LoadTFLiteWrapper";
+        return;
+    }
+
+    std::unique_lock<std::mutex> lock(data_->tflite_methods.mutex_);
+
+    void *mLibHandle = dlopen("/vendor/lib64/libthermal_tflite_wrapper.so", 0);
+    if (mLibHandle == nullptr) {
+        LOG(ERROR) << "Could not load libthermal_tflite_wrapper library with error: " << dlerror();
+        return;
+    }
+
+    data_->tflite_methods.create =
+            reinterpret_cast<tflitewrapper_create>(dlsym(mLibHandle, "Create"));
+    if (!data_->tflite_methods.create) {
+        LOG(ERROR) << "Could not link and cast tflitewrapper_create with error: " << dlerror();
+    }
+
+    data_->tflite_methods.init = reinterpret_cast<tflitewrapper_init>(dlsym(mLibHandle, "Init"));
+    if (!data_->tflite_methods.init) {
+        LOG(ERROR) << "Could not link and cast tflitewrapper_init with error: " << dlerror();
+    }
+
+    data_->tflite_methods.invoke =
+            reinterpret_cast<tflitewrapper_invoke>(dlsym(mLibHandle, "Invoke"));
+    if (!data_->tflite_methods.invoke) {
+        LOG(ERROR) << "Could not link and cast tflitewrapper_invoke with error: " << dlerror();
+    }
+
+    data_->tflite_methods.destroy =
+            reinterpret_cast<tflitewrapper_destroy>(dlsym(mLibHandle, "Destroy"));
+    if (!data_->tflite_methods.destroy) {
+        LOG(ERROR) << "Could not link and cast tflitewrapper_destroy with error: " << dlerror();
+    }
+}
+
+VirtualTempEstimator::VirtualTempEstimator(size_t num_input_samples) {
+    data_ = std::make_unique<VirtualTempEstimatorTFLiteData>(num_input_samples);
+    LoadTFLiteWrapper();
+}
+
+VirtualTempEstimator::~VirtualTempEstimator() {
+    LOG(INFO) << "VirtualTempEstimator destructor";
+}
+
+VtEstimatorStatus VirtualTempEstimator::Initialize(const char *model_path) {
+    LOG(INFO) << "Initialize VirtualTempEstimator\n";
+
+    if (!data_) {
+        LOG(ERROR) << "data_ is nullptr during Initialize\n";
+        return kVtEstimatorInitFailed;
+    }
+
+    std::unique_lock<std::mutex> lock(data_->tflite_methods.mutex_);
+
+    if (!model_path) {
+        LOG(ERROR) << "Invalid model_path:" << model_path;
+        return kVtEstimatorInvalidArgs;
+    }
+
+    if (!data_->input_buffer || !data_->input_buffer_size) {
+        LOG(ERROR) << "Invalid data_ members " << model_path
+                   << " input_buffer: " << data_->input_buffer
+                   << " input_buffer_size: " << data_->input_buffer_size;
+        return kVtEstimatorInitFailed;
+    }
+
+    if (!data_->tflite_methods.create || !data_->tflite_methods.init ||
+        !data_->tflite_methods.invoke || !data_->tflite_methods.destroy) {
+        LOG(ERROR) << "Invalid tflite methods";
+        return kVtEstimatorInitFailed;
+    }
+
+    data_->tflite_wrapper = data_->tflite_methods.create(kNumInputTensors, kNumOutputTensors);
+    if (!data_->tflite_wrapper) {
+        LOG(ERROR) << "Failed to create tflite wrapper";
+        return kVtEstimatorInitFailed;
+    }
+
+    int ret = data_->tflite_methods.init(data_->tflite_wrapper, model_path);
+    if (ret) {
+        LOG(ERROR) << "Failed to Init tflite_wrapper for " << model_path << " (ret: )" << ret
+                   << ")";
+        return kVtEstimatorInitFailed;
+    }
+
+    data_->is_initialized = true;
+    data_->model_path = model_path;
+
+    LOG(INFO) << "Successfully initialized VirtualTempEstimator for " << model_path;
+    return kVtEstimatorOk;
+}
+
+VtEstimatorStatus VirtualTempEstimator::Estimate(const std::vector<float> &thermistors,
+                                                 float *output) {
+    if (!data_) {
+        LOG(ERROR) << "data_ is nullptr during Estimate\n";
+        return kVtEstimatorInitFailed;
+    }
+
+    std::unique_lock<std::mutex> lock(data_->tflite_methods.mutex_);
+
+    if (!data_->is_initialized) {
+        LOG(ERROR) << "data_ not initialized for " << data_->model_path;
+        return kVtEstimatorInitFailed;
+    }
+
+    if ((thermistors.size() != data_->input_buffer_size) || (!output)) {
+        LOG(ERROR) << "Invalid args for " << data_->model_path
+                   << " thermistors.size(): " << thermistors.size()
+                   << " input_buffer_size: " << data_->input_buffer_size << " output: " << output;
+        return kVtEstimatorInvalidArgs;
+    }
+
+    // copy input data into input tensors
+    for (size_t i = 0; i < data_->input_buffer_size; ++i) {
+        data_->input_buffer[i] = thermistors[i];
+    }
+
+    int ret = data_->tflite_methods.invoke(data_->tflite_wrapper, data_->input_buffer,
+                                           data_->input_buffer_size, output, 1);
+    if (ret) {
+        LOG(ERROR) << "Failed to Invoke for " << data_->model_path << " (ret: " << ret << ")";
+        return kVtEstimatorInvokeFailed;
+    }
+
+    return kVtEstimatorOk;
+}
+
+}  // namespace vtestimator
+}  // namespace thermal
diff --git a/thermal/virtualtemp_estimator/virtualtemp_estimator.h b/thermal/virtualtemp_estimator/virtualtemp_estimator.h
new file mode 100644
index 0000000..0ae37f9
--- /dev/null
+++ b/thermal/virtualtemp_estimator/virtualtemp_estimator.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+#pragma once
+
+#include <vector>
+
+#include "virtualtemp_estimator_data.h"
+
+namespace thermal {
+namespace vtestimator {
+
+enum VtEstimatorStatus {
+    kVtEstimatorOk = 0,
+    kVtEstimatorInvalidArgs = 1,
+    kVtEstimatorInitFailed = 2,
+    kVtEstimatorInvokeFailed = 3,
+    kVtEstimatorUnSupported = 4,
+};
+
+// Class to estimate virtual temperature based on a model
+class VirtualTempEstimator {
+  public:
+    // Implicit copy-move headers.
+    VirtualTempEstimator(const VirtualTempEstimator &) = delete;
+    VirtualTempEstimator(VirtualTempEstimator &&) = default;
+    VirtualTempEstimator &operator=(const VirtualTempEstimator &) = delete;
+    VirtualTempEstimator &operator=(VirtualTempEstimator &&) = default;
+
+    VirtualTempEstimator(size_t num_input_samples);
+    ~VirtualTempEstimator();
+
+    // Initializes the model provided by model_path.
+    VtEstimatorStatus Initialize(const char *model_path);
+
+    // Performs the inference on the loaded VT model.
+    // Output of the inference is returned in output argument
+    VtEstimatorStatus Estimate(const std::vector<float> &thermistors, float *output);
+
+  private:
+    void LoadTFLiteWrapper();
+    std::unique_ptr<VirtualTempEstimatorTFLiteData> data_;
+};
+
+}  // namespace vtestimator
+}  // namespace thermal
diff --git a/thermal/virtualtemp_estimator/virtualtemp_estimator_data.h b/thermal/virtualtemp_estimator/virtualtemp_estimator_data.h
new file mode 100644
index 0000000..935c753
--- /dev/null
+++ b/thermal/virtualtemp_estimator/virtualtemp_estimator_data.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include <cstddef>
+#include <mutex>
+#include <string>
+
+#pragma once
+
+namespace thermal {
+namespace vtestimator {
+
+// Current version only supports single input/output tensors
+constexpr int kNumInputTensors = 1;
+constexpr int kNumOutputTensors = 1;
+
+typedef void *(*tflitewrapper_create)(int num_input_tensors, int num_output_tensors);
+typedef bool (*tflitewrapper_init)(void *handle, const char *model_path);
+typedef bool (*tflitewrapper_invoke)(void *handle, float *input_samples, int num_input_samples,
+                                     float *output_samples, int num_output_samples);
+typedef void (*tflitewrapper_destroy)(void *handle);
+
+struct TFLiteWrapperMethods {
+    tflitewrapper_create create;
+    tflitewrapper_init init;
+    tflitewrapper_invoke invoke;
+    tflitewrapper_destroy destroy;
+    mutable std::mutex mutex_;
+};
+
+struct VirtualTempEstimatorTFLiteData {
+    VirtualTempEstimatorTFLiteData(size_t num_input_samples) {
+        input_buffer = new float[num_input_samples];
+        input_buffer_size = num_input_samples;
+        is_initialized = false;
+        tflite_wrapper = nullptr;
+
+        tflite_methods.create = nullptr;
+        tflite_methods.init = nullptr;
+        tflite_methods.invoke = nullptr;
+        tflite_methods.destroy = nullptr;
+    }
+
+    void *tflite_wrapper;
+    float *input_buffer;
+    size_t input_buffer_size;
+    std::string model_path;
+    TFLiteWrapperMethods tflite_methods;
+    bool is_initialized;
+
+    ~VirtualTempEstimatorTFLiteData() {
+        if (tflite_wrapper && tflite_methods.destroy) {
+            tflite_methods.destroy(tflite_wrapper);
+        }
+
+        if (input_buffer) {
+            delete input_buffer;
+        }
+    }
+};
+
+}  // namespace vtestimator
+}  // namespace thermal
diff --git a/thermal/virtualtemp_estimator/virtualtemp_estimator_test.cpp b/thermal/virtualtemp_estimator/virtualtemp_estimator_test.cpp
new file mode 100644
index 0000000..fde9997
--- /dev/null
+++ b/thermal/virtualtemp_estimator/virtualtemp_estimator_test.cpp
@@ -0,0 +1,416 @@
+// Copyright (C) 2023 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.
+
+/**
+ *@file  virtualtemp_estimator_test.cc
+ * Test application to verify virtualtemp estimator
+ *
+ */
+// Test application to run and verify virtualtemp estimator interface unit tests
+
+#include "virtualtemp_estimator.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parsedouble.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <cutils/properties.h>
+#include <cutils/trace.h>
+#include <json/reader.h>
+#include <json/value.h>
+#include <json/writer.h>
+#include <log/log.h>
+#include <malloc.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include <climits>
+#include <fstream>
+#include <iostream>
+
+constexpr std::string_view kDefaultModel("/vendor/etc/vt_estimation_model.tflite");
+constexpr std::string_view kConfigProperty("vendor.thermal.config");
+constexpr std::string_view kConfigDefaultFileName("thermal_info_config.json");
+constexpr int kmillion = 1000000;
+constexpr int klog_interval_usec = 10 * kmillion;
+
+static inline unsigned long get_elapsed_time_usec(struct timeval start, struct timeval end) {
+    unsigned long elapsed_time = (end.tv_sec - start.tv_sec) * kmillion;
+    elapsed_time += (end.tv_usec - start.tv_usec);
+
+    return elapsed_time;
+}
+
+static std::vector<std::string> get_input_combination(std::string_view thermal_config_path) {
+    std::vector<std::string> result;
+    std::string json_doc;
+    if (!android::base::ReadFileToString(thermal_config_path.data(), &json_doc)) {
+        std::cout << "Failed to read JSON config from " << thermal_config_path.data();
+        return result;
+    }
+
+    Json::Value root;
+    Json::CharReaderBuilder reader_builder;
+    std::unique_ptr<Json::CharReader> reader(reader_builder.newCharReader());
+    std::string errorMessage;
+
+    if (!reader->parse(&*json_doc.begin(), &*json_doc.end(), &root, &errorMessage)) {
+        std::cout << "Failed to parse JSON config: " << errorMessage;
+        return result;
+    }
+
+    Json::Value sensors = root["Sensors"];
+    std::cout << "Sensors size: " << sensors.size() << std::endl;
+
+    for (Json::Value::ArrayIndex i = 0; i < sensors.size(); ++i) {
+        const std::string &name = sensors[i]["Name"].asString();
+        if (name == "VIRTUAL-SKIN-MODEL") {
+            Json::Value values = sensors[i]["Combination"];
+            if (values.size() == 0) {
+                return result;
+            }
+
+            std::cout << "Combination : [";
+            for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+                result.push_back(values[j].asString());
+                std::cout << result.back() << ", ";
+            }
+            std::cout << "]" << std::endl;
+        }
+    }
+
+    return result;
+}
+
+static int run_random_input_inference(std::string_view model_path,
+                                      std::string_view thermal_config_path, int min_inference_count,
+                                      int inference_delay_sec) {
+    float output;
+    unsigned long prev_log_time = 0;
+    thermal::vtestimator::VtEstimatorStatus ret;
+    std::vector<std::string> input_combination = get_input_combination(thermal_config_path.data());
+    int input_size = input_combination.size();
+    thermal::vtestimator::VirtualTempEstimator vt_estimator_(input_size);
+
+    std::cout << "Initialize estimator\n";
+    ret = vt_estimator_.Initialize(model_path.data());
+    if (ret != thermal::vtestimator::kVtEstimatorOk) {
+        std::cout << "Failed to Initialize estimator (ret: " << ret << ")\n";
+        return -1;
+    }
+
+    struct timeval start_loop_time;
+    int inference_count = 0;
+    unsigned long max_inference_time = 0, min_inference_time = ULONG_MAX;
+    unsigned long sum_inference_time = 0;
+    float avg_inference_time = 0;
+    std::vector<unsigned long> inference_times;
+
+    gettimeofday(&start_loop_time, nullptr);
+    do {
+        struct timeval begin, end;
+        std::vector<float> thermistors;
+
+        // preparing random inputs with starting temperature between 20C to 40C
+        int r = 20 + std::rand() % 20;
+        for (int i = 0; i < input_size; ++i) {
+            thermistors.push_back(r + i);
+        }
+
+        gettimeofday(&begin, nullptr);
+        ret = vt_estimator_.Estimate(thermistors, &output);
+        gettimeofday(&end, nullptr);
+        if (ret != thermal::vtestimator::kVtEstimatorOk) {
+            std::cout << "Failed to run estimator (ret: " << ret << ")\n";
+            return -1;
+        }
+
+        unsigned long inference_time_usec = get_elapsed_time_usec(begin, end);
+
+        inference_count++;
+        max_inference_time = std::max(max_inference_time, inference_time_usec);
+        min_inference_time = std::min(min_inference_time, inference_time_usec);
+        sum_inference_time += inference_time_usec;
+        avg_inference_time = sum_inference_time / inference_count;
+        inference_times.push_back(inference_time_usec);
+
+        unsigned long elapsed_time = get_elapsed_time_usec(start_loop_time, end);
+        if (elapsed_time - prev_log_time >= klog_interval_usec) {
+            std::cout << "elapsed_time_sec: " << elapsed_time / kmillion
+                      << " inference_count: " << inference_count
+                      << " min_inference_time: " << min_inference_time
+                      << " max_inference_time: " << max_inference_time
+                      << " avg_inference_time: " << avg_inference_time << std::endl;
+            prev_log_time = elapsed_time;
+        }
+
+        if (inference_delay_sec)
+            sleep(inference_delay_sec);
+    } while (inference_count < min_inference_count);
+
+    std::cout << "\n\ntotal inference count: " << inference_count << std::endl;
+    std::cout << "total inference time: " << sum_inference_time << std::endl;
+    std::cout << "avg_inference_time: " << avg_inference_time << std::endl;
+    std::cout << "min_inference_time: " << min_inference_time << std::endl;
+    std::cout << "max_inference_time: " << max_inference_time << std::endl;
+
+    std::sort(inference_times.begin(), inference_times.end());
+    std::cout << "\n\n";
+    std::cout << "p50: " << inference_times[inference_count * 0.5] << std::endl;
+    std::cout << "p90: " << inference_times[inference_count * 0.9] << std::endl;
+
+    return 0;
+}
+
+static int run_single_inference(std::string_view model_path, char *input) {
+    if (!input) {
+        std::cout << "input is nullptr" << std::endl;
+        return -1;
+    }
+
+    std::vector<float> thermistors;
+    char *ip = input;
+    char *saveptr;
+
+    std::cout << "Parsing thermistors from input string: ";
+    ip = strtok_r(ip, " ", &saveptr);
+    while (ip) {
+        float thermistor_value;
+
+        if (sscanf(ip, "%f", &thermistor_value) != 1) {
+            std::cout << "inputs parsing failed";
+        }
+
+        std::cout << thermistor_value << " ";
+        thermistors.push_back(thermistor_value);
+
+        ip = strtok_r(NULL, " ", &saveptr);
+    }
+    std::cout << std::endl;
+    std::cout << "thermistors.size(): " << thermistors.size() << std::endl;
+
+    float output;
+    thermal::vtestimator::VtEstimatorStatus ret;
+    thermal::vtestimator::VirtualTempEstimator vt_estimator_(thermistors.size());
+
+    std::cout << "Initialize estimator\n";
+    ret = vt_estimator_.Initialize(model_path.data());
+    if (ret != thermal::vtestimator::kVtEstimatorOk) {
+        std::cout << "Failed to Initialize estimator (ret: " << ret << ")\n";
+        return -1;
+    }
+
+    std::cout << "run estimator\n";
+    ret = vt_estimator_.Estimate(thermistors, &output);
+    if (ret != thermal::vtestimator::kVtEstimatorOk) {
+        std::cout << "Failed to run estimator (ret: " << ret << ")\n";
+        return -1;
+    }
+
+    std::cout << "output: " << output << std::endl;
+    return 0;
+}
+
+static int run_batch_process(std::string_view model_path, std::string_view thermal_config_path,
+                             const char *input_file, const char *output_file) {
+    if (!input_file || !output_file) {
+        std::cout << "input and output files required for batch process\n";
+        return -1;
+    }
+
+    std::cout << "get_input_combination(): ";
+    std::vector<std::string> input_combination = get_input_combination(thermal_config_path.data());
+    if (input_combination.size() == 0) {
+        LOG(ERROR) << "Invalid input_combination";
+        return -1;
+    }
+
+    thermal::vtestimator::VtEstimatorStatus ret;
+    thermal::vtestimator::VirtualTempEstimator vt_estimator_(input_combination.size());
+
+    std::cout << "Initialize estimator\n";
+    ret = vt_estimator_.Initialize(model_path.data());
+    if (ret != thermal::vtestimator::kVtEstimatorOk) {
+        std::cout << "Failed to Initialize estimator (ret: " << ret << ")\n";
+        return -1;
+    }
+
+    std::string json_doc;
+    if (!android::base::ReadFileToString(input_file, &json_doc)) {
+        LOG(ERROR) << "Failed to read JSON config from " << input_file;
+        return -1;
+    }
+    Json::Value root;
+    Json::CharReaderBuilder reader_builder;
+    std::unique_ptr<Json::CharReader> reader(reader_builder.newCharReader());
+    std::string errorMessage;
+
+    if (!reader->parse(&*json_doc.begin(), &*json_doc.end(), &root, &errorMessage)) {
+        LOG(ERROR) << "Failed to parse JSON config: " << errorMessage;
+        return -1;
+    }
+
+    std::cout << "Number of testcases " << root.size() << std::endl;
+
+    for (auto const &testcase_name : root.getMemberNames()) {
+        if (testcase_name == "Metadata") {
+            continue;
+        }
+
+        Json::Value testcase = root[testcase_name];
+        Json::Value model_vt_outputs;
+        int loop_count = testcase[input_combination[0]].size();
+
+        std::cout << "tc: " << testcase_name << " count: " << loop_count << std::endl;
+        for (int i = 0; i < loop_count; ++i) {
+            std::vector<float> model_inputs;
+            float model_output;
+            int num_inputs = input_combination.size();
+
+            for (int j = 0; j < num_inputs; ++j) {
+                std::string input_name = input_combination[j];
+                std::string value_str = testcase[input_name][std::to_string(i)].asString();
+
+                std::cout << "tc[" << testcase_name << "] entry[" << i << "] input[" << input_name
+                          << "] value_str[" << value_str << "]\n";
+
+                float value;
+                if (android::base::ParseFloat(value_str, &value) == false) {
+                    std::cout << "Failed to parse value_str : " << value_str << " to float\n";
+                }
+
+                model_inputs.push_back(value);
+            }
+
+            ret = vt_estimator_.Estimate(model_inputs, &model_output);
+            if (ret != thermal::vtestimator::kVtEstimatorOk) {
+                std::cout << "Failed to run estimator (ret: " << ret << ")\n";
+                return -1;
+            }
+
+            model_vt_outputs[std::to_string(i)] = std::to_string(model_output);
+        }
+
+        testcase["model_vt"] = model_vt_outputs;
+        root[testcase_name] = testcase;
+        std::cout << "completed testcase_name: " << testcase_name << std::endl;
+    }
+
+    Json::StreamWriterBuilder writer_builder;
+    writer_builder["indentation"] = "";
+    std::unique_ptr<Json::StreamWriter> writer(writer_builder.newStreamWriter());
+    std::ofstream output_stream(output_file, std::ofstream::out);
+    writer->write(root, &output_stream);
+
+    return 0;
+}
+
+void print_usage() {
+    std::string message = "usage: \n";
+    message += "-m : input mode (";
+    message += "0: single inference ";
+    message += "1: json input file ";
+    message += "2: generate random inputs) \n";
+    message += "-p : path to model file \n";
+    message += "-t : path to thermal config file \n";
+    message += "-i : input samples (mode 0), path to input file (mode 1) \n";
+    message += "-o : output file (mode 1) \n";
+    message += "-d : delay between inferences in seconds (mode 2) \n";
+    message += "-c : inference count (mode 2)";
+
+    std::cout << message << std::endl;
+}
+
+int main(int argc, char *argv[]) {
+    int c, mode = -1;
+    char *input = nullptr, *output = nullptr;
+    std::string model_path, thermal_config_path;
+    int min_inference_count = -1;
+    int inference_delay_sec = 0;
+
+    while ((c = getopt(argc, argv, "hm:p:i:c:o:d:t:")) != -1) switch (c) {
+            case 'm':
+                mode = atoi(optarg);
+                std::cout << "mode: " << mode << std::endl;
+                break;
+            case 'p':
+                model_path = optarg;
+                std::cout << "model_path: " << model_path << std::endl;
+                break;
+            case 't':
+                thermal_config_path = optarg;
+                std::cout << "thermal_config_path: " << thermal_config_path << std::endl;
+                break;
+            case 'i':
+                input = optarg;
+                std::cout << "input: " << input << std::endl;
+                break;
+            case 'o':
+                output = optarg;
+                std::cout << "output: " << output << std::endl;
+                break;
+            case 'c':
+                min_inference_count = atoi(optarg);
+                std::cout << "min_inference_count: " << min_inference_count << std::endl;
+                break;
+            case 'd':
+                inference_delay_sec = atoi(optarg);
+                std::cout << "inference_delay_sec : " << inference_delay_sec << std::endl;
+                break;
+            case 'h':
+                print_usage();
+                return 0;
+            default:
+                std::cout << "unsupported option " << c << std::endl;
+                abort();
+        }
+
+    if (model_path.empty()) {
+        model_path = kDefaultModel;
+        std::cout << "Using default model_path: " << model_path << std::endl;
+    }
+
+    if (thermal_config_path.empty()) {
+        thermal_config_path =
+                "/vendor/etc/" +
+                android::base::GetProperty(kConfigProperty.data(), kConfigDefaultFileName.data());
+        std::cout << "Using default thermal config: " << thermal_config_path << std::endl;
+    }
+
+    int ret = -1;
+    switch (mode) {
+        case 0:
+            ret = run_single_inference(model_path, input);
+            break;
+        case 1:
+            ret = run_batch_process(model_path, thermal_config_path, input, output);
+            break;
+        case 2:
+            ret = run_random_input_inference(model_path, thermal_config_path, min_inference_count,
+                                             inference_delay_sec);
+            break;
+        default:
+            std::cout << "unsupported mode" << std::endl;
+            print_usage();
+            break;
+    }
+
+    std::cout << "Exiting" << std::endl;
+    fflush(stdout);
+
+    return ret;
+}
diff --git a/usb/Android.bp b/usb/Android.bp
index 6fec996..1e05ef2 100644
--- a/usb/Android.bp
+++ b/usb/Android.bp
@@ -20,7 +20,7 @@
 
 cc_library_static {
     name: "libpixelusb",
-    vendor_available: true,
+    vendor: true,
     export_include_dirs: [
         "hidl/include",
         "include",
@@ -45,12 +45,17 @@
         "libhidlbase",
         "libutils",
         "libbinder_ndk",
+        "pixelatoms-cpp",
         "android.hardware.usb.gadget@1.0",
         "android.hardware.thermal@1.0",
         "android.hardware.thermal@2.0",
         "android.hardware.thermal-V1-ndk"
     ],
 
+    export_shared_lib_headers: [
+        "pixelatoms-cpp",
+    ],
+
     static_libs: [
         "libthermalutils",
     ]
@@ -58,7 +63,7 @@
 
 cc_library_static {
     name: "libpixelusb-aidl",
-    vendor_available: true,
+    vendor: true,
     export_include_dirs: [
         "aidl/include",
         "include",
@@ -81,12 +86,17 @@
         "libbinder",
         "libcutils",
         "libutils",
+        "pixelatoms-cpp",
         "android.hardware.usb.gadget-V1-ndk",
         "android.hardware.thermal@1.0",
         "android.hardware.thermal@2.0",
         "android.hardware.thermal-V1-ndk"
     ],
 
+    export_shared_lib_headers: [
+        "pixelatoms-cpp",
+    ],
+
     static_libs: [
         "libthermalutils",
     ]
@@ -94,6 +104,7 @@
 
 cc_fuzz {
     name: "libpixelusb_gadgetutils_fuzzer",
+    vendor: true,
 
     srcs:[
         "UsbGadgetUtils_fuzz.cpp"
diff --git a/usb/CommonUtils.cpp b/usb/CommonUtils.cpp
index 3a396d7..6e0f08c 100644
--- a/usb/CommonUtils.cpp
+++ b/usb/CommonUtils.cpp
@@ -35,12 +35,37 @@
 namespace pixel {
 namespace usb {
 
+// Android metrics requires number of elements in any repeated field cannot exceed 127 elements
+constexpr int kWestworldRepeatedFieldSizeLimit = 127;
+
 using ::android::base::GetProperty;
 using ::android::base::SetProperty;
 using ::android::base::WriteStringToFile;
 using ::std::chrono::microseconds;
 using ::std::chrono::steady_clock;
 using ::std::literals::chrono_literals::operator""ms;
+using android::hardware::google::pixel::PixelAtoms::VendorUsbDataSessionEvent;
+using android::hardware::google::pixel::PixelAtoms::
+        VendorUsbDataSessionEvent_UsbDataRole_USB_ROLE_DEVICE;
+using android::hardware::google::pixel::PixelAtoms::
+        VendorUsbDataSessionEvent_UsbDataRole_USB_ROLE_HOST;
+using android::hardware::google::pixel::PixelAtoms::VendorUsbDataSessionEvent_UsbDeviceState;
+using android::hardware::google::pixel::PixelAtoms::
+        VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_ADDRESSED;
+using android::hardware::google::pixel::PixelAtoms::
+        VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_ATTACHED;
+using android::hardware::google::pixel::PixelAtoms::
+        VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_CONFIGURED;
+using android::hardware::google::pixel::PixelAtoms::
+        VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_DEFAULT;
+using android::hardware::google::pixel::PixelAtoms::
+        VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_NOT_ATTACHED;
+using android::hardware::google::pixel::PixelAtoms::
+        VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_POWERED;
+using android::hardware::google::pixel::PixelAtoms::
+        VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_SUSPENDED;
+using android::hardware::google::pixel::PixelAtoms::
+        VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_UNKNOWN;
 
 int addEpollFd(const base::unique_fd &epfd, const base::unique_fd &fd) {
     struct epoll_event event;
@@ -154,6 +179,52 @@
     return true;
 }
 
+static VendorUsbDataSessionEvent_UsbDeviceState stringToUsbDeviceStateProto(
+        const std::string &state) {
+    if (state == "not attached\n") {
+        return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_NOT_ATTACHED;
+    } else if (state == "attached\n") {
+        return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_ATTACHED;
+    } else if (state == "powered\n") {
+        return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_POWERED;
+    } else if (state == "default\n") {
+        return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_DEFAULT;
+    } else if (state == "addressed\n") {
+        return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_ADDRESSED;
+    } else if (state == "configured\n") {
+        return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_CONFIGURED;
+    } else if (state == "suspended\n") {
+        return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_SUSPENDED;
+    } else {
+        return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_UNKNOWN;
+    }
+}
+
+void BuildVendorUsbDataSessionEvent(bool is_host, boot_clock::time_point currentTime,
+                                    boot_clock::time_point startTime,
+                                    std::vector<std::string> *states,
+                                    std::vector<boot_clock::time_point> *timestamps,
+                                    VendorUsbDataSessionEvent *event) {
+    if (is_host) {
+        event->set_usb_role(VendorUsbDataSessionEvent_UsbDataRole_USB_ROLE_HOST);
+    } else {
+        event->set_usb_role(VendorUsbDataSessionEvent_UsbDataRole_USB_ROLE_DEVICE);
+    }
+
+    for (int i = 0; i < states->size() && i < kWestworldRepeatedFieldSizeLimit; i++) {
+        event->add_usb_states(stringToUsbDeviceStateProto(states->at(i)));
+    }
+
+    for (int i = 0; i < timestamps->size() && i < kWestworldRepeatedFieldSizeLimit; i++) {
+        event->add_elapsed_time_ms(
+                std::chrono::duration_cast<std::chrono::milliseconds>(timestamps->at(i) - startTime)
+                        .count());
+    }
+
+    event->set_duration_ms(
+            std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startTime).count());
+}
+
 }  // namespace usb
 }  // namespace pixel
 }  // namespace google
diff --git a/usb/include/pixelusb/CommonUtils.h b/usb/include/pixelusb/CommonUtils.h
index 91244f8..dbd59c6 100644
--- a/usb/include/pixelusb/CommonUtils.h
+++ b/usb/include/pixelusb/CommonUtils.h
@@ -17,7 +17,9 @@
 #ifndef HARDWARE_GOOGLE_PIXEL_USB_UTILSCOMMON_H_
 #define HARDWARE_GOOGLE_PIXEL_USB_UTILSCOMMON_H_
 
+#include <android-base/chrono_utils.h>
 #include <android-base/unique_fd.h>
+#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
 
 #include <string>
 
@@ -57,6 +59,9 @@
 #define FUNCTION_PATH CONFIG_PATH FUNCTION_NAME
 #define RNDIS_PATH FUNCTIONS_PATH "gsi.rndis"
 
+using ::android::base::boot_clock;
+using android::hardware::google::pixel::PixelAtoms::VendorUsbDataSessionEvent;
+
 // Adds the given fd to the epollfd(epfd).
 int addEpollFd(const ::android::base::unique_fd &epfd, const ::android::base::unique_fd &fd);
 // Extracts vendor functions from the vendor init properties.
@@ -69,6 +74,12 @@
 bool setVidPidCommon(const char *vid, const char *pid);
 // Pulls down USB gadget. Returns true on success, false on failure
 bool resetGadgetCommon();
+void BuildVendorUsbDataSessionEvent(bool is_host, boot_clock::time_point currentTime,
+                                    boot_clock::time_point startTime,
+                                    std::vector<std::string> *states,
+                                    std::vector<boot_clock::time_point> *timestamps,
+                                    VendorUsbDataSessionEvent *event);
+
 }  // namespace usb
 }  // namespace pixel
 }  // namespace google
diff --git a/vibrator/common/StatsBase.cpp b/vibrator/common/StatsBase.cpp
index 68b8a2d..a0402b4 100644
--- a/vibrator/common/StatsBase.cpp
+++ b/vibrator/common/StatsBase.cpp
@@ -129,21 +129,22 @@
     uploadErrorAtoms();
 }
 
-void StatsBase::waitForStatsService() const {
+std::shared_ptr<IStats> StatsBase::waitForStatsService() const {
     STATS_TRACE("waitForStatsService()");
     if (!AServiceManager_isDeclared(kStatsInstanceName.c_str())) {
         ALOGE("IStats service '%s' is not registered.", kStatsInstanceName.c_str());
-        return;
+        return nullptr;
     }
 
     ALOGI("Waiting for IStats service '%s' to come up.", kStatsInstanceName.c_str());
-    const std::shared_ptr<IStats> statsClient = IStats::fromBinder(
+    std::shared_ptr<IStats> client = IStats::fromBinder(
             ndk::SpAIBinder(AServiceManager_waitForService(kStatsInstanceName.c_str())));
-    if (!statsClient) {
+    if (!client) {
         ALOGE("Failed to get IStats service '%s'.", kStatsInstanceName.c_str());
-        return;
+        return nullptr;
     }
     ALOGI("IStats service online.");
+    return client;
 }
 
 void StatsBase::runReporterThread() {
@@ -152,8 +153,6 @@
     auto nextUpload = clock::now() + UPLOAD_INTERVAL;
     auto status = std::cv_status::no_timeout;
 
-    waitForStatsService();
-
     while (!mTerminateReporterThread) {
         drainAtomQueue();
         {
@@ -178,15 +177,14 @@
         std::swap(mAtomQueue, tempQueue);
     }
 
-    std::shared_ptr<IStats> statsClient = IStats::fromBinder(
-            ndk::SpAIBinder(AServiceManager_waitForService(kStatsInstanceName.c_str())));
-    if (!statsClient) {
+    std::shared_ptr<IStats> client = waitForStatsService();
+    if (!client) {
         ALOGE("Failed to get IStats service. Atoms are dropped.");
         return;
     }
 
     for (const VendorAtom &atom : tempQueue) {
-        reportVendorAtom(statsClient, atom);
+        reportVendorAtom(client, atom);
     }
 }
 
diff --git a/vibrator/common/StatsBase.h b/vibrator/common/StatsBase.h
index 2e16220..2b0d986 100644
--- a/vibrator/common/StatsBase.h
+++ b/vibrator/common/StatsBase.h
@@ -31,6 +31,7 @@
 namespace stats {
 
 class VendorAtom;
+class IStats;
 
 }  // namespace stats
 }  // namespace frameworks
@@ -45,6 +46,7 @@
 class StatsBase {
   public:
     using VendorAtom = ::aidl::android::frameworks::stats::VendorAtom;
+    using IStats = ::aidl::android::frameworks::stats::IStats;
 
     StatsBase(const std::string &instance);
     ~StatsBase();
@@ -65,7 +67,7 @@
     void runReporterThread();
     void reportVendorAtomAsync(const VendorAtom &atom);
     void uploadDiagnostics();
-    void waitForStatsService() const;
+    std::shared_ptr<IStats> waitForStatsService() const;
     void drainAtomQueue();
 
     void uploadPlaycountAtoms();
diff --git a/vibrator/cs40l25/Hardware.h b/vibrator/cs40l25/Hardware.h
index e222279..7f06141 100644
--- a/vibrator/cs40l25/Hardware.h
+++ b/vibrator/cs40l25/Hardware.h
@@ -198,9 +198,7 @@
         return true;
     }
     bool isChirpEnabled() override {
-        bool value;
-        getProperty("chirp.enabled", &value, false);
-        return value;
+        return utils::getProperty("persist.vendor.vibrator.hal.chirp.enabled", false);
     }
     void debug(int fd) override { HwCalBase::debug(fd); }
 };
diff --git a/vibrator/cs40l26/Hardware.h b/vibrator/cs40l26/Hardware.h
index 50e2cb6..12ddb59 100644
--- a/vibrator/cs40l26/Hardware.h
+++ b/vibrator/cs40l26/Hardware.h
@@ -297,6 +297,9 @@
         *haptic_pcm = NULL;
         return false;
     }
+    bool isPassthroughI2sHapticSupported() override {
+        return utils::getProperty("ro.vendor.vibrator.hal.passthrough_i2s_supported", false);
+    }
     bool uploadOwtEffect(const uint8_t *owtData, const uint32_t numBytes, struct ff_effect *effect,
                          uint32_t *outEffectIndex, int *status) override {
         ATRACE_NAME(__func__);
@@ -516,9 +519,7 @@
         return true;
     }
     bool isChirpEnabled() override {
-        bool value;
-        getProperty("chirp.enabled", &value, false);
-        return value;
+        return utils::getProperty("persist.vendor.vibrator.hal.chirp.enabled", false);
     }
     bool getSupportedPrimitives(uint32_t *value) override {
         return getProperty("supported_primitives", value, (uint32_t)0);
diff --git a/vibrator/cs40l26/Vibrator.cpp b/vibrator/cs40l26/Vibrator.cpp
index 8c303ff..c2fd73a 100644
--- a/vibrator/cs40l26/Vibrator.cpp
+++ b/vibrator/cs40l26/Vibrator.cpp
@@ -496,6 +496,8 @@
     mHwApi->setF0CompEnable(mHwCal->isF0CompEnabled());
     mHwApi->setRedcCompEnable(mHwCal->isRedcCompEnabled());
 
+    mHasPassthroughHapticDevice = mHwApi->isPassthroughI2sHapticSupported();
+
     mIsUnderExternalControl = false;
 
     mIsChirpEnabled = mHwCal->isChirpEnabled();
@@ -534,7 +536,7 @@
     int32_t ret = IVibrator::CAP_ON_CALLBACK | IVibrator::CAP_PERFORM_CALLBACK |
                   IVibrator::CAP_AMPLITUDE_CONTROL | IVibrator::CAP_GET_RESONANT_FREQUENCY |
                   IVibrator::CAP_GET_Q_FACTOR;
-    if (hasHapticAlsaDevice()) {
+    if (mHasPassthroughHapticDevice || hasHapticAlsaDevice()) {
         ret |= IVibrator::CAP_EXTERNAL_CONTROL;
     } else {
         mStatsApi->logError(kAlsaFailError);
@@ -671,16 +673,21 @@
     ATRACE_NAME("Vibrator::setExternalControl");
     setGlobalAmplitude(enabled);
 
-    if (mHasHapticAlsaDevice || mConfigHapticAlsaDeviceDone || hasHapticAlsaDevice()) {
-        if (!mHwApi->setHapticPcmAmp(&mHapticPcm, enabled, mCard, mDevice)) {
-            mStatsApi->logError(kHwApiError);
-            ALOGE("Failed to %s haptic pcm device: %d", (enabled ? "enable" : "disable"), mDevice);
+    if (!mHasPassthroughHapticDevice) {
+        if (mHasHapticAlsaDevice || mConfigHapticAlsaDeviceDone ||
+            hasHapticAlsaDevice()) {
+            if (!mHwApi->setHapticPcmAmp(&mHapticPcm, enabled, mCard,
+                                         mDevice)) {
+                mStatsApi->logError(kHwApiError);
+                ALOGE("Failed to %s haptic pcm device: %d",
+                      (enabled ? "enable" : "disable"), mDevice);
+                return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+            }
+        } else {
+            mStatsApi->logError(kAlsaFailError);
+            ALOGE("No haptics ALSA device");
             return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
         }
-    } else {
-        mStatsApi->logError(kAlsaFailError);
-        ALOGE("No haptics ALSA device");
-        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
 
     mIsUnderExternalControl = enabled;
@@ -1569,6 +1576,15 @@
         dprintf(fd, "  cs40l26-calib.bin: %s\n", ver.c_str());
         verFile.close();
     }
+    verFile.open("/vendor/firmware/cs40l26-dvl.bin", verBinFileMode);
+    if (verFile.is_open()) {
+        verFile.seekg(36);
+        getline(verFile, ver);
+        ver = ver.substr(0, ver.find('\0') + 1);
+        ver = ver.substr(ver.rfind('\\') + 1);
+        dprintf(fd, "  cs40l26-dvl.bin: %s\n", ver.c_str());
+        verFile.close();
+    }
 
     dprintf(fd, "\n");
 
diff --git a/vibrator/cs40l26/Vibrator.h b/vibrator/cs40l26/Vibrator.h
index c4a992f..b2b7515 100644
--- a/vibrator/cs40l26/Vibrator.h
+++ b/vibrator/cs40l26/Vibrator.h
@@ -100,6 +100,9 @@
         // Set haptics PCM amplifier before triggering audio haptics feature
         virtual bool setHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card,
                                      int device) = 0;
+        // Checks to see if the passthrough i2s haptics feature is supported by
+        // the target device.
+        virtual bool isPassthroughI2sHapticSupported() = 0;
         // Set OWT waveform for compose or compose PWLE request
         virtual bool uploadOwtEffect(const uint8_t *owtData, const uint32_t numBytes,
                                      struct ff_effect *effect, uint32_t *outEffectIndex,
@@ -256,6 +259,7 @@
     int mCard;
     int mDevice;
     bool mHasHapticAlsaDevice{false};
+    bool mHasPassthroughHapticDevice;
     bool mIsUnderExternalControl;
     float mLongEffectScale = 1.0;
     bool mIsChirpEnabled;
diff --git a/vibrator/cs40l26/tests/mocks.h b/vibrator/cs40l26/tests/mocks.h
index 53c9a04..0837938 100644
--- a/vibrator/cs40l26/tests/mocks.h
+++ b/vibrator/cs40l26/tests/mocks.h
@@ -45,6 +45,7 @@
     MOCK_METHOD2(setFFPlay, bool(int8_t index, bool value));
     MOCK_METHOD2(getHapticAlsaDevice, bool(int *card, int *device));
     MOCK_METHOD4(setHapticPcmAmp, bool(struct pcm **haptic_pcm, bool enable, int card, int device));
+    MOCK_METHOD0(isPassthroughI2sHapticSupported, bool());
     MOCK_METHOD5(uploadOwtEffect,
                  bool(const uint8_t *owtData, const uint32_t numBytes, struct ff_effect *effect,
                       uint32_t *outEffectIndex, int *status));
diff --git a/vibrator/cs40l26/tests/test-vibrator.cpp b/vibrator/cs40l26/tests/test-vibrator.cpp
index 254a6bd..0b15095 100644
--- a/vibrator/cs40l26/tests/test-vibrator.cpp
+++ b/vibrator/cs40l26/tests/test-vibrator.cpp
@@ -303,6 +303,7 @@
         EXPECT_CALL(*mMockApi, setMinOnOffInterval(_)).Times(times);
         EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).Times(times);
         EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, _, _, _)).Times(times);
+        EXPECT_CALL(*mMockApi, isPassthroughI2sHapticSupported()).Times(times);
         EXPECT_CALL(*mMockApi, enableDbc()).Times(times);
 
         EXPECT_CALL(*mMockApi, debug(_)).Times(times);
@@ -339,9 +340,11 @@
     std::unique_ptr<MockApi> mockapi;
     std::unique_ptr<MockCal> mockcal;
     std::unique_ptr<MockStats> mockstats;
-    std::string f0Val = std::to_string(std::rand());
-    std::string redcVal = std::to_string(std::rand());
-    std::string qVal = std::to_string(std::rand());
+    int min_val = 0xC8000;
+    int max_val = 0x7FC000;
+    std::string f0Val = std::to_string(std::rand() % (max_val - min_val + 1) + min_val);
+    std::string redcVal = std::to_string(std::rand() % (max_val - min_val + 1) + min_val);
+    std::string qVal = std::to_string(std::rand() % (max_val - min_val + 1) + min_val);
     uint32_t calVer;
     uint32_t supportedPrimitivesBits = 0x0;
     Expectation volGet;
@@ -384,6 +387,7 @@
     EXPECT_CALL(*mMockCal, isRedcCompEnabled()).WillOnce(Return(true));
     EXPECT_CALL(*mMockApi, setRedcCompEnable(true)).WillOnce(Return(true));
 
+    EXPECT_CALL(*mMockApi, isPassthroughI2sHapticSupported()).WillOnce(Return(false));
     EXPECT_CALL(*mMockCal, isChirpEnabled()).WillOnce(Return(true));
     EXPECT_CALL(*mMockCal, getSupportedPrimitives(_))
             .InSequence(supportedPrimitivesSeq)