audioserver: add automated audio power logging

We utilize the wakelock and batterystat logic to measure
by app the power used during audio playback.

This is integrated into audioserver to allow close tracking
of apps in and out of suspend.

This is default disabled for now. Before trying, use
$ adb shell setenforce 0
$ adb shell setprop persist.audio.power_stats.enabled true
$ adb shell aflags enable com.android.media.audioserver.power_stats true

Flag: com.android.media.audioserver.power_stats
Test: atest powerstats_collector_tests
Test: atest audio_powerstats_benchmark
Test: audio_powerstatscollector_benchmark
Test: atest audio_token_benchmark
Bug: 350114693
Change-Id: I6e0c04089fce72b1e3e22532e39f38f5d6657f28
diff --git a/media/psh_utils/Android.bp b/media/psh_utils/Android.bp
index 4662db8..dafa63b 100644
--- a/media/psh_utils/Android.bp
+++ b/media/psh_utils/Android.bp
@@ -19,18 +19,23 @@
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
     srcs: [
+        "AudioPowerManager.cpp",
+        "AudioToken.cpp",
         "HealthStats.cpp",
         "HealthStatsProvider.cpp",
+        "PowerClientStats.cpp",
         "PowerStats.cpp",
         "PowerStatsCollector.cpp",
         "PowerStatsProvider.cpp",
     ],
     shared_libs: [
+        "com.android.media.audio-aconfig-cc",
         "libaudioutils",
         "libbase",
         "libbinder_ndk",
         "libcutils",
         "liblog",
+        "libmediautils",
         "libutils",
     ],
     cflags: [
diff --git a/media/psh_utils/AudioPowerManager.cpp b/media/psh_utils/AudioPowerManager.cpp
new file mode 100644
index 0000000..3ae681a
--- /dev/null
+++ b/media/psh_utils/AudioPowerManager.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2024 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 "AudioToken.h"
+#define LOG_TAG "AudioPowerManager"
+#include <com_android_media_audioserver.h>
+#include <cutils/properties.h>
+#include <utils/Log.h>
+#include <psh_utils/AudioPowerManager.h>
+
+namespace android::media::psh_utils {
+
+/* static */
+AudioPowerManager& AudioPowerManager::getAudioPowerManager() {
+    [[clang::no_destroy]] static AudioPowerManager apm;
+    return apm;
+}
+
+std::unique_ptr<Token> AudioPowerManager::startClient(pid_t pid, uid_t uid,
+        const std::string& additional) {
+    std::shared_ptr<PowerClientStats> powerClientStats;
+    std::lock_guard l(mMutex);
+    if (mPowerClientStats.count(uid) == 0) {
+        const auto it = mHistoricalClients.find(uid);
+        if (it == mHistoricalClients.end()) {
+            powerClientStats = std::make_shared<PowerClientStats>(uid, additional);
+        } else {
+            powerClientStats = it->second;
+            mHistoricalClients.erase(it);
+        }
+        mPowerClientStats[uid] = powerClientStats;
+    } else {
+        powerClientStats = mPowerClientStats[uid];
+    }
+    powerClientStats->addPid(pid);
+    mPidToUid[pid] = uid;
+    std::unique_ptr<Token> token =
+            std::make_unique<AudioClientToken>(powerClientStats, pid, uid, additional);
+    mOutstandingTokens.emplace(token.get());
+    return token;
+}
+
+std::unique_ptr<Token> AudioPowerManager::startTrack(uid_t uid, const std::string& additional) {
+    std::lock_guard l(mMutex);
+    if (mPowerClientStats.count(uid) == 0) {
+        ALOGW("%s: Cannot find uid: %d", __func__, uid);
+        return {};
+    }
+    auto powerClientStats = mPowerClientStats[uid];
+    std::unique_ptr<Token> token =
+            std::make_unique<AudioTrackToken>(powerClientStats, additional);
+    mOutstandingTokens.emplace(token.get());
+    return token;
+}
+
+std::unique_ptr<Token> AudioPowerManager::startThread(
+        pid_t pid, const std::string& wakeLockName,
+        WakeFlag wakeFlag, const std::string& additional) {
+    std::lock_guard l(mMutex);
+    std::unique_ptr<Token> token =
+            std::make_unique<AudioThreadToken>(pid, wakeLockName, wakeFlag, additional);
+    mOutstandingTokens.emplace(token.get());
+    return token;
+}
+
+std::string AudioPowerManager::toString() const {
+    const std::string prefix("  ");
+    std::string result;
+    std::lock_guard l(mMutex);
+    result.append("Power Tokens:\n");
+    std::vector<std::string> tokenInfo;
+    for (const auto& token: mOutstandingTokens) {
+        tokenInfo.emplace_back(token->toString());
+    }
+    std::sort(tokenInfo.begin(), tokenInfo.end());
+    for (const auto& info: tokenInfo) {
+        result.append(prefix).append(info).append("\n");
+    }
+    result.append("Power Clients:\n");
+    for (const auto& [uid, powerClientStats]: mPowerClientStats) {
+        result.append(powerClientStats->toString(true, prefix));
+    }
+    result.append("Power Client History:\n");
+    for (const auto& [power, powerClientStats]: mHistoricalClients) {
+        result.append(powerClientStats->toString(true, prefix));
+    }
+    return result;
+}
+
+void AudioPowerManager::stopClient(pid_t pid) {
+    std::lock_guard l(mMutex);
+    const auto pidit = mPidToUid.find(pid);
+    if (pidit == mPidToUid.end()) return;
+    const uid_t uid = pidit->second;
+    const auto it = mPowerClientStats.find(uid);
+    if (it == mPowerClientStats.end()) return;
+
+    auto powerClientStats = it->second;
+    size_t count = powerClientStats->removePid(pid);
+    if (count == 0) {
+        mHistoricalClients[uid] = powerClientStats;
+        mPowerClientStats.erase(it);
+        if (mHistoricalClients.size() > kHistory) {
+            mHistoricalClients.erase(mHistoricalClients.begin()); // remove oldest.
+        }
+    }
+    mPidToUid.erase(pid);
+}
+
+void AudioPowerManager::clear_token_ptr(Token* token) {
+    if (token != nullptr) {
+        std::lock_guard l(mMutex);
+        (void)mOutstandingTokens.erase(token);
+    }
+}
+
+/* static */
+bool AudioPowerManager::enabled() {
+    static const bool enabled = com::android::media::audioserver::power_stats()
+            && property_get_bool("persist.audio.power_stats.enabled", false);
+    return enabled;
+}
+
+} // namespace android::media::psh_utils
diff --git a/media/psh_utils/AudioToken.cpp b/media/psh_utils/AudioToken.cpp
new file mode 100644
index 0000000..626f959
--- /dev/null
+++ b/media/psh_utils/AudioToken.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2024 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 "AudioToken"
+#include <android-base/logging.h>
+#include <utils/Log.h>
+#include "AudioToken.h"
+#include <psh_utils/AudioPowerManager.h>
+
+namespace android::media::psh_utils {
+
+/* static */
+constinit std::atomic<size_t> AudioClientToken::sIdCounter{};
+
+AudioClientToken::AudioClientToken(
+        std::shared_ptr<PowerClientStats> powerClientStats, pid_t pid, uid_t uid,
+        const std::string& additional)
+    : mPowerClientStats(std::move(powerClientStats))
+    , mPid(pid)
+    , mAdditional(additional)
+    , mId(sIdCounter.fetch_add(1, std::memory_order_relaxed)) {
+        (void)uid;
+}
+
+AudioClientToken::~AudioClientToken() {
+    auto& apm = AudioPowerManager::getAudioPowerManager();
+
+    // APM has a back pointer to AudioToken, which is accessible on toString().
+    // We first remove ourselves to prevent use after free.
+    apm.clear_token_ptr(this);
+    apm.stopClient(mPid);
+}
+
+std::string AudioClientToken::toString() const {
+    std::string result("Client-");
+    result.append(std::to_string(mId)).append(": ")
+            .append(" pid: ").append(std::to_string(mPid));
+    if (!mAdditional.empty()) {
+        result.append(" ").append(mAdditional);
+    }
+    return result;
+}
+
+std::unique_ptr<Token> createAudioClientToken(pid_t pid, uid_t uid,
+        const std::string& additional) {
+    return AudioPowerManager::getAudioPowerManager().startClient(pid, uid, additional);
+}
+
+/* static */
+constinit std::atomic<size_t> AudioThreadToken::sIdCounter{};
+
+AudioThreadToken::AudioThreadToken(
+        pid_t tid, const std::string& wakeLockName,
+        WakeFlag wakeFlag, const std::string& additional)
+    : mTid(tid)
+    , mWakeLockName(wakeLockName)
+    , mWakeFlag(wakeFlag)
+    , mAdditional(additional)
+    , mId(sIdCounter.fetch_add(1, std::memory_order_relaxed)) {
+}
+
+AudioThreadToken::~AudioThreadToken() {
+    auto& apm = AudioPowerManager::getAudioPowerManager();
+
+    // APM has a back pointer to AudioToken, which is accessible on toString().
+    // We first remove ourselves to prevent use after free.
+    apm.clear_token_ptr(this);
+}
+
+std::string AudioThreadToken::toString() const {
+    std::string result("Thread-");
+    result.append(std::to_string(mId)).append(": ")
+            .append(" ThreadBase-tid: ").append(std::to_string(mTid))
+            .append(" wakeLockName: ").append(mWakeLockName)
+            .append(" wakeFlag: ").append(::android::media::psh_utils::toString(mWakeFlag));
+    if (!mAdditional.empty()) {
+        result.append(" ").append(mAdditional);
+    }
+    return result;
+}
+
+std::unique_ptr<Token> createAudioThreadToken(
+        pid_t pid, const std::string& wakeLockName,
+        WakeFlag wakeFlag, const std::string& additional) {
+    return AudioPowerManager::getAudioPowerManager().startThread(
+            pid, wakeLockName, wakeFlag, additional);
+}
+
+/* static */
+constinit std::atomic<size_t> AudioTrackToken::sIdCounter{};
+
+AudioTrackToken::AudioTrackToken(
+        std::shared_ptr<PowerClientStats> powerClientStats, const std::string& additional)
+    : mPowerClientStats(std::move(powerClientStats))
+    , mAdditional(additional)
+    , mId(sIdCounter.fetch_add(1, std::memory_order_relaxed)) {
+        if (mPowerClientStats){
+            mPowerClientStats->getCommandThread().add(
+                    "start",
+                    [pas = mPowerClientStats, actualNs = systemTime(SYSTEM_TIME_BOOTTIME)]() {
+                        pas->start(actualNs);
+                    });
+        }
+}
+
+AudioTrackToken::~AudioTrackToken() {
+    // APM has a back pointer to AudioToken, which is accessible on toString().
+    // We first remove ourselves to prevent use after free.
+    AudioPowerManager::getAudioPowerManager().clear_token_ptr(this);
+    if (mPowerClientStats) {
+        mPowerClientStats->getCommandThread().add(
+                "stop",
+                [pas = mPowerClientStats, actualNs = systemTime(SYSTEM_TIME_BOOTTIME)]() {
+                    pas->stop(actualNs);
+                });
+    }
+}
+
+std::string AudioTrackToken::toString() const {
+    std::string result("Track-");
+    result.append(std::to_string(mId)).append(": ")
+            .append(mPowerClientStats ? mPowerClientStats->toString() : std::string("null"));
+    if (!mAdditional.empty()) {
+        result.append(" ").append(mAdditional);
+    }
+    return result;
+}
+
+std::unique_ptr<Token> createAudioTrackToken(uid_t uid, const std::string& additional) {
+    return AudioPowerManager::getAudioPowerManager().startTrack(uid, additional);
+}
+
+
+} // namespace android::media::psh_utils
diff --git a/media/psh_utils/AudioToken.h b/media/psh_utils/AudioToken.h
new file mode 100644
index 0000000..aa25b04
--- /dev/null
+++ b/media/psh_utils/AudioToken.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 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 <psh_utils/PowerClientStats.h>
+#include <psh_utils/Token.h>
+
+#include <atomic>
+#include <memory>
+#include <string>
+
+namespace android::media::psh_utils {
+
+class AudioClientToken : public Token {
+public:
+    AudioClientToken(std::shared_ptr<PowerClientStats> powerClientStats, pid_t pid, uid_t uid,
+             const std::string& additional);
+    ~AudioClientToken() override;
+
+    // AudioPowerManager may call toString() while AudioToken is in its dtor.
+    // It is safe so long as toString is final.
+    std::string toString() const final;
+
+private:
+    const std::shared_ptr<PowerClientStats> mPowerClientStats;
+    const pid_t mPid;
+    const std::string mAdditional;
+    const size_t mId;
+    static constinit std::atomic<size_t> sIdCounter;
+};
+
+class AudioThreadToken : public Token {
+public:
+    AudioThreadToken(
+            pid_t tid, const std::string& wakeLockName,
+            WakeFlag wakeFlag, const std::string& additional);
+    ~AudioThreadToken() override;
+
+    // AudioPowerManager may call toString() while AudioToken is in its dtor.
+    // It is safe so long as toString is final.
+    std::string toString() const final;
+
+private:
+    const pid_t mTid;
+    const std::string mWakeLockName;
+    const WakeFlag mWakeFlag;
+    const std::string mAdditional;
+    const size_t mId;
+    static constinit std::atomic<size_t> sIdCounter;
+};
+
+class AudioTrackToken : public Token {
+public:
+    AudioTrackToken(
+            std::shared_ptr<PowerClientStats> powerClientStats, const std::string& additional);
+    ~AudioTrackToken() override;
+
+    // AudioPowerManager may call toString() while AudioToken is in its dtor.
+    // It is safe so long as toString is final.
+    std::string toString() const final;
+
+private:
+    const std::shared_ptr<PowerClientStats> mPowerClientStats;
+    const std::string mAdditional;
+    const size_t mId;
+    static constinit std::atomic<size_t> sIdCounter;
+};
+
+} // namespace android::media::psh_utils
diff --git a/media/psh_utils/PowerClientStats.cpp b/media/psh_utils/PowerClientStats.cpp
new file mode 100644
index 0000000..77d79e4
--- /dev/null
+++ b/media/psh_utils/PowerClientStats.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 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 <psh_utils/PowerClientStats.h>
+#include <mediautils/ServiceUtilities.h>
+
+namespace android::media::psh_utils {
+
+/* static */
+audio_utils::CommandThread& PowerClientStats::getCommandThread() {
+    [[clang::no_destroy]] static audio_utils::CommandThread ct;
+    return ct;
+}
+
+PowerClientStats::PowerClientStats(uid_t uid, const std::string& additional)
+        : mUid(uid), mAdditional(additional) {}
+
+void PowerClientStats::start(int64_t actualNs) {
+    std::lock_guard l(mMutex);
+    ++mTokenCount;
+    if (mStartNs == 0) mStartNs = actualNs;
+    if (mStartStats) return;
+    mStartStats = PowerStatsCollector::getCollector().getStats(kStatTimeToleranceNs);
+}
+
+void PowerClientStats::stop(int64_t actualNs) {
+    std::lock_guard l(mMutex);
+    if (--mTokenCount > 0) return;
+    if (mStartNs != 0) mDeltaNs += actualNs - mStartNs;
+    mStartNs = 0;
+    if (!mStartStats) return;
+    const auto stopStats = PowerStatsCollector::getCollector().getStats(kStatTimeToleranceNs);
+    if (stopStats && stopStats != mStartStats) {
+        *mDeltaStats += *stopStats - *mStartStats;
+    }
+    mStartStats.reset();
+}
+
+void PowerClientStats::addPid(pid_t pid) {
+    std::lock_guard l(mMutex);
+    mPids.emplace(pid);
+}
+
+size_t PowerClientStats::removePid(pid_t pid) {
+    std::lock_guard l(mMutex);
+    mPids.erase(pid);
+    return mPids.size();
+}
+
+std::string PowerClientStats::toString(bool stats, const std::string& prefix) const {
+    std::lock_guard l(mMutex);
+
+    // Adjust delta time and stats if currently running.
+    auto deltaStats = mDeltaStats;
+    auto deltaNs = mDeltaNs;
+    if (mStartNs) deltaNs += systemTime(SYSTEM_TIME_BOOTTIME) - mStartNs;
+    if (mStartStats) {
+        const auto stopStats = PowerStatsCollector::getCollector().getStats(kStatTimeToleranceNs);
+        if (stopStats && stopStats != mStartStats) {
+            auto newStats = std::make_shared<PowerStats>(*deltaStats);
+            *newStats += *stopStats - *mStartStats;
+            deltaStats = newStats;
+        }
+    }
+
+    std::string result(prefix);
+    result.append("uid: ")
+            .append(std::to_string(mUid))
+            .append(" ").append(mediautils::UidInfo::getInfo(mUid)->package)
+            .append(" streams: ").append(std::to_string(mTokenCount))
+            .append(" seconds: ").append(std::to_string(deltaNs * 1e-9));
+    result.append(" {");
+    for (auto pid : mPids) {
+        result.append(" ").append(std::to_string(pid));
+    }
+    result.append(" }");
+    if (!mAdditional.empty()) {
+        result.append("\n").append(prefix).append(mAdditional);
+    }
+    if (stats) {
+        std::string prefix2(prefix);
+        prefix2.append("  ");
+        result.append("\n").append(deltaStats->normalizedEnergy(prefix2));
+    }
+    return result;
+}
+
+} // namespace android::media::psh_utils
diff --git a/media/psh_utils/benchmarks/Android.bp b/media/psh_utils/benchmarks/Android.bp
index 20efaa9..2382c69 100644
--- a/media/psh_utils/benchmarks/Android.bp
+++ b/media/psh_utils/benchmarks/Android.bp
@@ -17,14 +17,15 @@
         "-Werror",
     ],
     static_libs: [
-        "libaudioutils",
         "libpshutils",
     ],
     shared_libs: [
+        "libaudioutils",
         "libbase",
         "libbinder_ndk",
         "libcutils",
         "liblog",
+        "libmediautils",
         "libutils",
     ],
 }
@@ -38,14 +39,37 @@
         "-Werror",
     ],
     static_libs: [
-        "libaudioutils",
         "libpshutils",
     ],
     shared_libs: [
+        "libaudioutils",
         "libbase",
         "libbinder_ndk",
         "libcutils",
         "liblog",
+        "libmediautils",
+        "libutils",
+    ],
+}
+
+cc_benchmark {
+    name: "audio_token_benchmark",
+
+    srcs: ["audio_token_benchmark.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    static_libs: [
+        "libpshutils",
+    ],
+    shared_libs: [
+        "libaudioutils",
+        "libbase",
+        "libbinder_ndk",
+        "libcutils",
+        "liblog",
+        "libmediautils",
         "libutils",
     ],
 }
diff --git a/media/psh_utils/benchmarks/audio_powerstats_benchmark.cpp b/media/psh_utils/benchmarks/audio_powerstats_benchmark.cpp
index d3f815c..4d8b224 100644
--- a/media/psh_utils/benchmarks/audio_powerstats_benchmark.cpp
+++ b/media/psh_utils/benchmarks/audio_powerstats_benchmark.cpp
@@ -26,6 +26,140 @@
 #include <thread>
 #include <vector>
 
+/*
+Pixel 9 XL Pro
+---------------------------------------------------------------------------------------------------------------
+Benchmark                                               Time                        CPU              Iteration
+---------------------------------------------------------------------------------------------------------------
+audio_powerstats_benchmark:
+  #MemoryFixture/CacheAccess/64/0/0/1             5.195761589711465 ns       5.183635029038574 ns    135160912
+  #MemoryFixture/CacheAccess/128/0/0/1            10.37270431027728 ns      10.341754343667125 ns     67574354
+  #MemoryFixture/CacheAccess/256/0/0/1           20.767353363364098 ns      20.708496782017836 ns     33809541
+  #MemoryFixture/CacheAccess/512/0/0/1            41.53473855852046 ns       41.45724926375999 ns     16900399
+  #MemoryFixture/CacheAccess/1024/0/0/1           82.89673650172568 ns       82.68064919937272 ns      8462177
+  #MemoryFixture/CacheAccess/2048/0/0/1          165.77648929323732 ns      165.45127650324827 ns      4227878
+  #MemoryFixture/CacheAccess/4096/0/0/1           331.9272979248067 ns       331.0722959129879 ns      2114919
+  #MemoryFixture/CacheAccess/8192/0/0/1           663.8090302013887 ns       662.2813002594532 ns      1054528
+  #MemoryFixture/CacheAccess/16384/0/0/1         1327.4224893455748 ns      1324.0114138292752 ns       529095
+  #MemoryFixture/CacheAccess/32768/0/0/1         2657.1037276954685 ns      2651.8974883509522 ns       263970
+  #MemoryFixture/CacheAccess/65536/0/0/1          5314.170125835522 ns        5305.20127734871 ns       131679
+  #MemoryFixture/CacheAccess/131072/0/0/1        10624.517848490625 ns      10602.467739493763 ns        66056
+  #MemoryFixture/CacheAccess/262144/0/0/1         21271.09560700047 ns      21224.851075464823 ns        32916
+  #MemoryFixture/CacheAccess/524288/0/0/1         42556.76641626909 ns       42444.65628786041 ns        16508
+  #MemoryFixture/CacheAccess/1048576/0/0/1         85440.6313100312 ns       85076.15703685701 ns         8221
+  #MemoryFixture/CacheAccess/2097152/0/0/1       170908.37391089948 ns      169402.58059051324 ns         4132
+  #MemoryFixture/CacheAccess/4194304/0/0/1        373635.4350000207 ns      372955.40777777723 ns         1800
+  #MemoryFixture/CacheAccess/8388608/0/0/1        685101.2127660838 ns       681594.2330754338 ns         1034
+  #MemoryFixture/CacheAccess/16777216/0/0/1       1588009.158696047 ns      1581510.0217391246 ns          460
+  #MemoryFixture/CacheAccess/33554432/0/0/1      2721626.5387591715 ns       2708149.166666674 ns          258
+  #MemoryFixture/CacheAccess/67108864/0/0/1       5433705.728680161 ns       5413515.914728661 ns          129
+  #MemoryFixture/CacheAccess/64/1/0/1             5.201213751848433 ns       5.180967321755995 ns    135673202
+  #MemoryFixture/CacheAccess/128/1/0/1           10.386209252693448 ns      10.351378045484042 ns     67486234
+  #MemoryFixture/CacheAccess/256/1/0/1           20.742666405371747 ns      20.686210353062208 ns     33848169
+  #MemoryFixture/CacheAccess/512/1/0/1            41.52781367071582 ns        41.4438085044977 ns     16870391
+  #MemoryFixture/CacheAccess/1024/1/0/1           83.03849985460687 ns        82.6732197351271 ns      8442915
+  #MemoryFixture/CacheAccess/2048/1/0/1          166.02706801365886 ns      165.60695561393828 ns      4226169
+  #MemoryFixture/CacheAccess/4096/1/0/1          332.05696890075365 ns       331.3395040136246 ns      2112679
+  #MemoryFixture/CacheAccess/8192/1/0/1           664.3009073119205 ns       662.5811781316487 ns      1055315
+  #MemoryFixture/CacheAccess/16384/1/0/1         1329.0792867154223 ns      1325.0185471435798 ns       527251
+  #MemoryFixture/CacheAccess/32768/1/0/1         2652.9089904482526 ns      2645.5388137876826 ns       264236
+  #MemoryFixture/CacheAccess/65536/1/0/1          5312.635002724743 ns       5300.412875575496 ns       132064
+  #MemoryFixture/CacheAccess/131072/1/0/1        10625.299202810635 ns      10594.376178351697 ns        65982
+  #MemoryFixture/CacheAccess/262144/1/0/1        21270.763359464152 ns      21206.192921372138 ns        33029
+  #MemoryFixture/CacheAccess/524288/1/0/1         42496.14168177758 ns      42381.692498487435 ns        16530
+  #MemoryFixture/CacheAccess/1048576/1/0/1        85425.34302253627 ns       85063.54206182965 ns         8119
+  #MemoryFixture/CacheAccess/2097152/1/0/1        170961.8011639407 ns      169732.18840931158 ns         4124
+  #MemoryFixture/CacheAccess/4194304/1/0/1       440086.77439029363 ns      439010.07195122127 ns         1640
+  #MemoryFixture/CacheAccess/8388608/1/0/1        677684.5246376056 ns        675888.058937202 ns         1035
+  #MemoryFixture/CacheAccess/16777216/1/0/1      1571417.6297115851 ns      1566423.4922394755 ns          451
+  #MemoryFixture/CacheAccess/33554432/1/0/1       2723418.325581582 ns      2708535.8062015465 ns          258
+  #MemoryFixture/CacheAccess/67108864/1/0/1       5435209.372094963 ns       5413991.821705426 ns          129
+  #MemoryFixture/CacheAccess/64/2/0/1             5.204700890931938 ns       5.183036658664568 ns    135406460
+  #MemoryFixture/CacheAccess/128/2/0/1           10.376355078178406 ns      10.348754235460566 ns     67518337
+  #MemoryFixture/CacheAccess/256/2/0/1           20.726238269323797 ns      20.670377070062745 ns     33834057
+  #MemoryFixture/CacheAccess/512/2/0/1            41.49336362336435 ns       41.38084547087122 ns     16890919
+  #MemoryFixture/CacheAccess/1024/2/0/1           82.96761236822086 ns        82.7676652782051 ns      8440753
+  #MemoryFixture/CacheAccess/2048/2/0/1          165.90277344671065 ns      165.63781837950896 ns      4223301
+  #MemoryFixture/CacheAccess/4096/2/0/1          332.08415463794364 ns      331.07455763248197 ns      2110971
+  #MemoryFixture/CacheAccess/8192/2/0/1            662.569068830069 ns       661.1656746088121 ns      1055382
+  #MemoryFixture/CacheAccess/16384/2/0/1         1327.7767031437843 ns       1324.268331214332 ns       528552
+  #MemoryFixture/CacheAccess/32768/2/0/1          2654.714983405558 ns      2647.9097623853727 ns       264546
+  #MemoryFixture/CacheAccess/65536/2/0/1          5304.774145979664 ns       5290.380748589911 ns       131554
+  #MemoryFixture/CacheAccess/131072/2/0/1          10631.0978039924 ns      10602.125724165107 ns        65938
+  #MemoryFixture/CacheAccess/262144/2/0/1        21258.936606489668 ns      21202.585867458216 ns        33016
+  #MemoryFixture/CacheAccess/524288/2/0/1         42460.85577331506 ns      42355.304775195626 ns        16481
+  #MemoryFixture/CacheAccess/1048576/2/0/1        85428.60153206348 ns       85160.29036964968 ns         8224
+  #MemoryFixture/CacheAccess/2097152/2/0/1       170233.91140170072 ns      169409.06511740564 ns         4131
+  #MemoryFixture/CacheAccess/4194304/2/0/1        402022.9022378908 ns      401018.78327444167 ns         1698
+  #MemoryFixture/CacheAccess/8388608/2/0/1        677677.2908216701 ns       675843.1642512042 ns         1035
+  #MemoryFixture/CacheAccess/16777216/2/0/1      1554294.1339100641 ns       1549490.831533465 ns          463
+  #MemoryFixture/CacheAccess/33554432/2/0/1       2722937.453488912 ns       2709007.093023249 ns          258
+  #MemoryFixture/CacheAccess/67108864/2/0/1      5435791.6511618495 ns       5415184.511627913 ns          129
+  #MemoryFixture/CacheAccess/64/0/2/1             5.198916380394579 ns       5.178607363536682 ns    135270162
+  #MemoryFixture/CacheAccess/128/0/2/1            10.39285189976819 ns      10.361873548918089 ns     67571996
+  #MemoryFixture/CacheAccess/256/0/2/1            20.75920269645178 ns       20.69937217558235 ns     33765810
+  #MemoryFixture/CacheAccess/512/0/2/1            41.51556112567147 ns      41.372342254073665 ns     16947585
+  #MemoryFixture/CacheAccess/1024/0/2/1           82.96516513710067 ns       82.78508396196703 ns      8459485
+  #MemoryFixture/CacheAccess/2048/0/2/1          166.19624228249646 ns      165.74873814180103 ns      4221552
+  #MemoryFixture/CacheAccess/4096/0/2/1           331.7269469760912 ns      330.91601941885546 ns      2111762
+  #MemoryFixture/CacheAccess/8192/0/2/1           663.8953077112178 ns       662.4540856828183 ns      1059419
+  #MemoryFixture/CacheAccess/16384/0/2/1          1326.843343898467 ns       1323.254749209487 ns       527193
+  #MemoryFixture/CacheAccess/32768/0/2/1         2656.6743123958327 ns      2651.2139933123217 ns       264069
+  #MemoryFixture/CacheAccess/65536/0/2/1          5316.515245822549 ns       5306.343742646622 ns       131741
+  #MemoryFixture/CacheAccess/131072/0/2/1         10623.00981164003 ns      10584.927048634307 ns        66044
+  #MemoryFixture/CacheAccess/262144/0/2/1        21210.760294289023 ns      21148.895028460767 ns        33028
+  #MemoryFixture/CacheAccess/524288/0/2/1         42522.49017237178 ns       42412.58560628969 ns        16535
+  #MemoryFixture/CacheAccess/1048576/0/2/1        86800.15632693251 ns       86501.64057114806 ns         8124
+  #MemoryFixture/CacheAccess/2097152/0/2/1       177705.58147680553 ns      177010.52665832458 ns         3995
+  #MemoryFixture/CacheAccess/4194304/0/2/1        449051.5944408481 ns        448237.065698044 ns         1583
+  #MemoryFixture/CacheAccess/8388608/0/2/1       1389931.2189924652 ns      1386035.8565891527 ns          516
+  #MemoryFixture/CacheAccess/16777216/0/2/1       4438020.074999826 ns       4420751.918750021 ns          160
+  #MemoryFixture/CacheAccess/33554432/0/2/1     1.730178560976084E7 ns    1.7182623121951293E7 ns           41
+  #MemoryFixture/CacheAccess/67108864/0/2/1     5.283456416664952E7 ns     5.258297450000053E7 ns           12
+  #MemoryFixture/CacheAccess/64/1/2/1             5.204121573005314 ns       5.179569988739665 ns    135600170
+  #MemoryFixture/CacheAccess/128/1/2/1           10.387460007442089 ns      10.354541036512236 ns     67512560
+  #MemoryFixture/CacheAccess/256/1/2/1            20.77893771735786 ns      20.727591539055314 ns     33750321
+  #MemoryFixture/CacheAccess/512/1/2/1             41.4739992379063 ns      41.315239742240664 ns     16908639
+  #MemoryFixture/CacheAccess/1024/1/2/1           82.95454097741914 ns        82.7976946763163 ns      8446970
+  #MemoryFixture/CacheAccess/2048/1/2/1          165.86154320354674 ns      165.43862697234525 ns      4233855
+  #MemoryFixture/CacheAccess/4096/1/2/1           331.8942415618145 ns      331.28362462222265 ns      2109704
+  #MemoryFixture/CacheAccess/8192/1/2/1           663.6968508366361 ns        662.640989545053 ns      1057011
+  #MemoryFixture/CacheAccess/16384/1/2/1          1328.002697434852 ns         1325.3893625606 ns       527909
+  #MemoryFixture/CacheAccess/32768/1/2/1          2656.826225607798 ns       2651.831997636693 ns       264032
+  #MemoryFixture/CacheAccess/65536/1/2/1          5313.403312198365 ns      5296.6562514184625 ns       132178
+  #MemoryFixture/CacheAccess/131072/1/2/1        10603.411688232678 ns      10580.430523642488 ns        66152
+  #MemoryFixture/CacheAccess/262144/1/2/1         21213.68814698657 ns       21160.64858647625 ns        33038
+  #MemoryFixture/CacheAccess/524288/1/2/1         42446.96972817497 ns       42358.49900102921 ns        16517
+  #MemoryFixture/CacheAccess/1048576/1/2/1        85427.89922199522 ns       85099.99477267203 ns         8226
+  #MemoryFixture/CacheAccess/2097152/1/2/1       179576.28781830988 ns      178747.97179230847 ns         4006
+  #MemoryFixture/CacheAccess/4194304/1/2/1        453971.4271099782 ns      453200.38874680526 ns         1564
+  #MemoryFixture/CacheAccess/8388608/1/2/1        1413810.749999729 ns      1409767.6830708506 ns          508
+  #MemoryFixture/CacheAccess/16777216/1/2/1       4481396.176099637 ns       4463691.635220161 ns          159
+  #MemoryFixture/CacheAccess/33554432/1/2/1    1.7363190725006916E7 ns    1.7271956449999947E7 ns           40
+  #MemoryFixture/CacheAccess/67108864/1/2/1     5.310257300000861E7 ns     5.283166808333325E7 ns           12
+  #MemoryFixture/CacheAccess/64/2/2/1             5.194585073441566 ns       5.169936491706671 ns    135797225
+  #MemoryFixture/CacheAccess/128/2/2/1           10.375776978615239 ns      10.351150907177248 ns     67504271
+  #MemoryFixture/CacheAccess/256/2/2/1            20.73537619800892 ns      20.682392672592464 ns     33819437
+  #MemoryFixture/CacheAccess/512/2/2/1            41.46680809632523 ns       41.35825233901641 ns     16913944
+  #MemoryFixture/CacheAccess/1024/2/2/1           82.84606240770246 ns        82.6134204462559 ns      8446202
+  #MemoryFixture/CacheAccess/2048/2/2/1          165.87278324509214 ns      165.32429617950757 ns      4232933
+  #MemoryFixture/CacheAccess/4096/2/2/1           331.2406082982817 ns       330.5629607515063 ns      2116922
+  #MemoryFixture/CacheAccess/8192/2/2/1            663.159315900038 ns       661.6959690484747 ns      1056103
+  #MemoryFixture/CacheAccess/16384/2/2/1         1327.5666883524236 ns      1324.3426747207684 ns       528758
+  #MemoryFixture/CacheAccess/32768/2/2/1         2654.9809699966722 ns       2648.132907418347 ns       264372
+  #MemoryFixture/CacheAccess/65536/2/2/1           5314.47981229803 ns       5303.806613499892 ns       131912
+  #MemoryFixture/CacheAccess/131072/2/2/1         10606.19082560071 ns      10585.181869071148 ns        66097
+  #MemoryFixture/CacheAccess/262144/2/2/1        21187.819721722273 ns      21154.040502117125 ns        33060
+  #MemoryFixture/CacheAccess/524288/2/2/1         42442.62465239758 ns       42311.67198645875 ns        16542
+  #MemoryFixture/CacheAccess/1048576/2/2/1        85539.82432104382 ns       85200.15385547924 ns         8248
+  #MemoryFixture/CacheAccess/2097152/2/2/1         180928.070870083 ns      180122.69382093282 ns         3965
+  #MemoryFixture/CacheAccess/4194304/2/2/1       456790.68648981495 ns       455950.1693600544 ns         1547
+  #MemoryFixture/CacheAccess/8388608/2/2/1       1427351.7287124782 ns      1423232.2613861358 ns          505
+  #MemoryFixture/CacheAccess/16777216/2/2/1       4513772.829113805 ns       4495839.088607608 ns          158
+  #MemoryFixture/CacheAccess/33554432/2/2/1    1.7454686475002743E7 ns    1.7364546800000016E7 ns           40
+  #MemoryFixture/CacheAccess/67108864/2/2/1    5.2963768833327174E7 ns     5.266439433333403E7 ns           12
+
+ */
 float result = 0;
 
 using android::media::psh_utils::CoreClass;
diff --git a/media/psh_utils/benchmarks/audio_powerstatscollector_benchmark.cpp b/media/psh_utils/benchmarks/audio_powerstatscollector_benchmark.cpp
index 9e581bc..021eb5a 100644
--- a/media/psh_utils/benchmarks/audio_powerstatscollector_benchmark.cpp
+++ b/media/psh_utils/benchmarks/audio_powerstatscollector_benchmark.cpp
@@ -22,16 +22,17 @@
 #include <benchmark/benchmark.h>
 
 /*
- Pixel 8 Pro
+ Pixel 9 Pro XL
+ (tolerance is the amount of time a cached value is valid).
 ------------------------------------------------------------------------------------------
  Benchmark                            Time                      CPU             Iteration
 ------------------------------------------------------------------------------------------
 audio_powerstatscollector_benchmark:
-  #BM_StatsToleranceMs/0      1.2005660120994434E8 ns            2532739.72 ns          100
-  #BM_StatsToleranceMs/50        1281.095987079007 ns     346.0322183913503 ns      2022168
-  #BM_StatsToleranceMs/100       459.9668862534226 ns    189.47902626735942 ns      2891307
-  #BM_StatsToleranceMs/200       233.8438662484292 ns    149.84041813854736 ns      4407343
-  #BM_StatsToleranceMs/500      184.42197142314103 ns    144.86896036787098 ns      7295167
+  #BM_StatsToleranceMs/0      6.346578290999787E7 ns            2069264.56 ns          100
+  #BM_StatsToleranceMs/50      454.12461256065177 ns     203.1644161064639 ns      2615571
+  #BM_StatsToleranceMs/100     167.74983887731364 ns    101.99598388920647 ns      5436852
+  #BM_StatsToleranceMs/200     102.57950838168422 ns     79.40969988086803 ns      7600815
+  #BM_StatsToleranceMs/500      86.87348495571898 ns     75.24841434306252 ns      9789318
 */
 
 // We check how expensive it is to query stats depending
diff --git a/media/psh_utils/benchmarks/audio_token_benchmark.cpp b/media/psh_utils/benchmarks/audio_token_benchmark.cpp
new file mode 100644
index 0000000..47003c0
--- /dev/null
+++ b/media/psh_utils/benchmarks/audio_token_benchmark.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 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 "audio_token_benchmark"
+#include <utils/Log.h>
+
+#include <psh_utils/Token.h>
+
+#include <benchmark/benchmark.h>
+
+/*
+ Pixel 9 Pro XL
+------------------------------------------------------------------------------------------
+ Benchmark                            Time                      CPU             Iteration
+------------------------------------------------------------------------------------------
+audio_token_benchmark:
+  #BM_ClientToken     494.6548907301575 ns     492.4932166101717 ns      1376819
+  #BM_ThreadToken    140.34316175293938 ns    139.91778452790845 ns      5000397
+  #BM_TrackToken      944.0571625384163 ns     893.7912613357879 ns       643096
+*/
+
+static void BM_ClientToken(benchmark::State& state) {
+    constexpr pid_t kPid = 10;
+    constexpr uid_t kUid = 100;
+    while (state.KeepRunning()) {
+        auto token = android::media::psh_utils::createAudioClientToken(
+                kPid, kUid);
+        benchmark::ClobberMemory();
+    }
+}
+
+BENCHMARK(BM_ClientToken);
+
+static void BM_ThreadToken(benchmark::State& state) {
+    constexpr pid_t kTid = 20;
+    constexpr const char* kWakeLockTag = "thread";
+    while (state.KeepRunning()) {
+        auto token = android::media::psh_utils::createAudioThreadToken(
+                kTid, kWakeLockTag);
+        benchmark::ClobberMemory();
+    }
+}
+
+BENCHMARK(BM_ThreadToken);
+
+static void BM_TrackToken(benchmark::State& state) {
+    constexpr pid_t kPid = 10;
+    constexpr uid_t kUid = 100;
+    auto clientToken = android::media::psh_utils::createAudioClientToken(
+                kPid, kUid);
+    while (state.KeepRunning()) {
+        auto token = android::media::psh_utils::createAudioTrackToken(kUid);
+        benchmark::ClobberMemory();
+    }
+}
+
+BENCHMARK(BM_TrackToken);
+
+BENCHMARK_MAIN();
diff --git a/media/psh_utils/include/psh_utils/AudioPowerManager.h b/media/psh_utils/include/psh_utils/AudioPowerManager.h
new file mode 100644
index 0000000..47dfdb2
--- /dev/null
+++ b/media/psh_utils/include/psh_utils/AudioPowerManager.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 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 "PowerClientStats.h"
+#include "PowerStatsCollector.h"
+#include "Token.h"
+
+#include <android-base/thread_annotations.h>
+#include <audio_utils/linked_hash_map.h>
+#include <list>
+#include <map>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace android::media::psh_utils {
+
+/**
+ * AudioPowerManager is a singleton class that
+ * serializes the power, wakelock, and performance
+ * messages
+ */
+class AudioPowerManager {
+    friend class AudioClientToken;
+    friend class AudioThreadToken;
+    friend class AudioTrackToken;
+
+public:
+    static AudioPowerManager& getAudioPowerManager();
+
+    /**
+     * Returns a token indicating that a client is started.
+     * This is associated with an application.
+     */
+    std::unique_ptr<Token> startClient(pid_t pid, uid_t uid,
+            const std::string& additional);
+
+    /**
+     * Returns a token that represents a start instance for uid.
+     * This is typically associated with an AudioTrack / AudioRecord start.
+     */
+    std::unique_ptr<Token> startTrack(uid_t uid, const std::string& additional);
+
+    /**
+     * Returns a token that represents a wakelock for a Thread start.
+     */
+    std::unique_ptr<Token> startThread(
+            pid_t pid, const std::string& wakeLockName,
+            WakeFlag wakeFlag, const std::string& additional);
+
+    std::string toString() const;
+
+    static bool enabled();
+
+private:
+    // For AudioToken dtor only.
+    void clear_token_ptr(Token* token);
+    void stopClient(pid_t pid);
+
+    static constexpr size_t kHistory = 6;
+
+    mutable std::mutex mMutex;
+    std::unordered_set<Token *> mOutstandingTokens GUARDED_BY(mMutex);
+    std::unordered_map<pid_t, uid_t> mPidToUid GUARDED_BY(mMutex);
+    std::map<uid_t, std::shared_ptr<PowerClientStats>> mPowerClientStats GUARDED_BY(mMutex);
+    audio_utils::linked_hash_map<uid_t, std::shared_ptr<PowerClientStats>>
+            mHistoricalClients GUARDED_BY(mMutex);
+};
+
+} // namespace android::media::psh_utils
diff --git a/media/psh_utils/include/psh_utils/PowerClientStats.h b/media/psh_utils/include/psh_utils/PowerClientStats.h
new file mode 100644
index 0000000..93d1fa9
--- /dev/null
+++ b/media/psh_utils/include/psh_utils/PowerClientStats.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 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 "PowerStats.h"
+#include "PowerStatsCollector.h"
+
+#include <android-base/thread_annotations.h>
+#include <audio_utils/CommandThread.h>
+#include <memory>
+#include <set>
+
+namespace android::media::psh_utils {
+
+/**
+ * PowerClientStats accumulates power measurements based on start and stop events.
+ *
+ * The start and stop events must eventually be matched, but several start events
+ * in a row only results in the power counted once.
+ */
+class PowerClientStats {
+public:
+    // A command thread is used for tokens to dispatch start and stop sequentially
+    // with less overhead to the caller.
+    static audio_utils::CommandThread& getCommandThread();
+
+    /**
+     * Creates an UID based power stat tracker.
+     *
+     * @param uid uid of app
+     * @param additional string to be printed out.
+     */
+    PowerClientStats(uid_t uid, const std::string& additional);
+
+    /**
+     * Starts power tracking.
+     */
+    void start(int64_t actualNs) EXCLUDES(mMutex);
+
+    /**
+     * Stops power tracking (saves the difference) - must be paired with start().
+     */
+    void stop(int64_t actualNs) EXCLUDES(mMutex);
+
+    /**
+     * Adds a pid to the App for string printing.
+     */
+    void addPid(pid_t pid) EXCLUDES(mMutex);
+
+    /**
+     * Removes the pid from the App for string printing.
+     */
+    size_t removePid(pid_t pid) EXCLUDES(mMutex);
+
+    /**
+     * Returns the string info.
+     * @param stats if true returns the stats.
+     * @return stat string.
+     */
+    std::string toString(bool stats = false, const std::string& prefix = {})
+            const EXCLUDES(mMutex);
+
+private:
+    // Snapshots are taken no more often than 500ms.
+    static constexpr int64_t kStatTimeToleranceNs = 500'000'000;
+
+    mutable std::mutex mMutex;
+    const uid_t mUid;
+    const std::string mName;
+    const std::string mAdditional;
+    std::set<pid_t> mPids GUARDED_BY(mMutex); // pids sharing same uid
+    int64_t mTokenCount GUARDED_BY(mMutex) = 0;
+    int64_t mStartNs GUARDED_BY(mMutex) = 0;
+    std::shared_ptr<const PowerStats> mStartStats GUARDED_BY(mMutex);
+
+    // Total actual time app is active (stop - start)
+    int64_t mDeltaNs GUARDED_BY(mMutex) = 0;
+    // The stats taken for the active time (snapshots are quantized to 500ms accuracy).
+    std::shared_ptr<PowerStats> mDeltaStats GUARDED_BY(mMutex) = std::make_shared<PowerStats>();
+};
+
+} // namespace android::media::psh_utils
diff --git a/media/psh_utils/include/psh_utils/Token.h b/media/psh_utils/include/psh_utils/Token.h
new file mode 100644
index 0000000..2b52d11
--- /dev/null
+++ b/media/psh_utils/include/psh_utils/Token.h
@@ -0,0 +1,62 @@
+
+/*
+ * Copyright (C) 2024 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 <string>
+
+namespace android::media::psh_utils {
+
+class Token {
+public:
+    virtual ~Token() = default;
+    virtual std::string toString() const = 0;
+};
+
+// Client tokens (one per Audio Client PID)
+std::unique_ptr<Token> createAudioClientToken(pid_t pid, uid_t uid,
+        const std::string& additional = {});
+
+enum class WakeFlag {
+    kNone = 0,
+    kLowLatency = 1,
+    kLowPower = 2,
+};
+
+inline std::string toString(WakeFlag wakeFlag) {
+    std::string result;
+    for (const auto& [flag, name] : std::initializer_list<std::pair<WakeFlag, std::string>> {
+            {WakeFlag::kLowLatency, "kLowLatency"},
+            {WakeFlag::kLowPower, "kLowPower"},
+        }) {
+        if (static_cast<int>(flag) & static_cast<int>(wakeFlag)) {
+            if (!result.empty()) result.append("|");
+            result.append(name);
+        }
+    }
+    return result;
+}
+
+// Thread tokens (one per ThreadBase PID started).
+std::unique_ptr<Token> createAudioThreadToken(
+        pid_t pid, const std::string& wakeLockName,
+        WakeFlag wakeFlag = WakeFlag::kNone, const std::string& additional = {});
+
+// AudioTrack/AudioRecord tokens.
+std::unique_ptr<Token> createAudioTrackToken(uid_t uid, const std::string& additional = {});
+
+} // namespace android::media::psh_utils
diff --git a/services/audioflinger/Android.bp b/services/audioflinger/Android.bp
index 264fc4f..01bde42 100644
--- a/services/audioflinger/Android.bp
+++ b/services/audioflinger/Android.bp
@@ -234,6 +234,22 @@
         "libpermission",
     ],
 
+    export_static_lib_headers: [
+        "libpshutils",
+    ],
+
+    shared: {
+        static_libs: [
+            "libpshutils",
+        ],
+    },
+
+    static: {
+        whole_static_libs: [
+            "libpshutils",
+        ],
+    },
+
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index d94862a..e1b244b 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -899,6 +899,17 @@
 
         BUFLOG_RESET;
 
+        if (media::psh_utils::AudioPowerManager::enabled()) {
+            char value[PROPERTY_VALUE_MAX];
+            property_get("ro.build.display.id", value, "Unknown build");
+            std::string build(value);
+            build.append("\n");
+            write(fd, build.c_str(), build.size());
+            const std::string powerLog =
+                    media::psh_utils::AudioPowerManager::getAudioPowerManager().toString();
+            write(fd, powerLog.c_str(), powerLog.size());
+        }
+
         if (locked) {
             mutex().unlock();
         }
@@ -2327,6 +2338,9 @@
                                                      pid_t pid,
                                                      uid_t uid)
     : mAudioFlinger(audioFlinger), mPid(pid), mUid(uid), mAudioFlingerClient(client)
+    , mClientToken(media::psh_utils::AudioPowerManager::enabled()
+            ? media::psh_utils::createAudioClientToken(pid, uid)
+            : nullptr)
 {
 }
 
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 7c58c96..ba2b920 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -38,6 +38,7 @@
 #include <media/audiohal/DevicesFactoryHalInterface.h>
 #include <mediautils/ServiceUtilities.h>
 #include <mediautils/Synchronization.h>
+#include <psh_utils/AudioPowerManager.h>
 
 // not needed with the includes above, added to prevent transitive include dependency.
 #include <utils/KeyedVector.h>
@@ -499,6 +500,7 @@
         const pid_t             mPid;
         const uid_t             mUid;
         const sp<media::IAudioFlingerClient> mAudioFlingerClient;
+        const std::unique_ptr<media::psh_utils::Token> mClientToken;
     };
 
     // --- MediaLogNotifier ---
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 406b832..0d961bb 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -81,6 +81,7 @@
 #include <powermanager/PowerManager.h>
 #include <private/android_filesystem_config.h>
 #include <private/media/AudioTrackShared.h>
+#include <psh_utils/AudioPowerManager.h>
 #include <system/audio_effects/effect_aec.h>
 #include <system/audio_effects/effect_downmix.h>
 #include <system/audio_effects/effect_ns.h>
@@ -1218,6 +1219,10 @@
                     {} /* historyTag */);
         if (status.isOk()) {
             mWakeLockToken = binder;
+            if (media::psh_utils::AudioPowerManager::enabled()) {
+                mThreadToken = media::psh_utils::createAudioThreadToken(
+                        getTid(), String8(getWakeLockTag()).c_str());
+            }
         }
         ALOGV("acquireWakeLock_l() %s status %d", mThreadName, status.exceptionCode());
     }
@@ -1243,6 +1248,7 @@
         }
         mWakeLockToken.clear();
     }
+    mThreadToken.reset();
 }
 
 void ThreadBase::getPowerManager_l() {
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index a7a2630..4c4939b 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -33,6 +33,7 @@
 #include <fastpath/FastMixer.h>
 #include <mediautils/Synchronization.h>
 #include <mediautils/ThreadSnapshot.h>
+#include <psh_utils/Token.h>
 #include <timing/MonotonicFrameCounter.h>
 #include <utils/Log.h>
 
@@ -725,6 +726,7 @@
                 char                    mThreadName[kThreadNameLength]; // guaranteed NUL-terminated
     sp<os::IPowerManager> mPowerManager GUARDED_BY(mutex());
     sp<IBinder> mWakeLockToken GUARDED_BY(mutex());
+    std::unique_ptr<media::psh_utils::Token> mThreadToken GUARDED_BY(mutex());
                 const sp<PMDeathRecipient> mDeathRecipient;
                 // list of suspended effects per session and per type. The first (outer) vector is
                 // keyed by session ID, the second (inner) by type UUID timeLow field
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index 1342b7b..cde7fc2 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -24,6 +24,7 @@
 #include <android-base/macros.h>  // DISALLOW_COPY_AND_ASSIGN
 #include <datapath/TrackMetrics.h>
 #include <mediautils/BatteryNotifier.h>
+#include <psh_utils/AudioPowerManager.h>
 
 #include <atomic>    // avoid transitive dependency
 #include <list>      // avoid transitive dependency
@@ -240,17 +241,13 @@
      * Called when a track moves to active state to record its contribution to battery usage.
      * Track state transitions should eventually be handled within the track class.
      */
-    void beginBatteryAttribution() final {
-        mBatteryStatsHolder.emplace(uid());
-    }
+    void beginBatteryAttribution() final;
 
     /**
      * Called when a track moves out of the active state to record its contribution
      * to battery usage.
      */
-    void endBatteryAttribution() final {
-        mBatteryStatsHolder.reset();
-    }
+    void endBatteryAttribution() final;
 
 protected:
     DISALLOW_COPY_AND_ASSIGN(TrackBase);
@@ -400,6 +397,7 @@
     std::atomic_flag    mChangeNotified = ATOMIC_FLAG_INIT;
     // RAII object for battery stats book-keeping
     std::optional<mediautils::BatteryStatsAudioHandle> mBatteryStatsHolder;
+    std::unique_ptr<media::psh_utils::Token> mTrackToken;
 };
 
 class PatchTrackBase : public PatchProxyBufferProvider, public virtual IAfPatchTrackBase
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index 5aa58a2..9ce01f8 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -325,6 +325,18 @@
         });
 }
 
+void TrackBase::beginBatteryAttribution() {
+    mBatteryStatsHolder.emplace(uid());
+    if (media::psh_utils::AudioPowerManager::enabled()) {
+        mTrackToken = media::psh_utils::createAudioTrackToken(uid());
+    }
+}
+
+void TrackBase::endBatteryAttribution() {
+    mBatteryStatsHolder.reset();
+    mTrackToken.reset();
+}
+
 PatchTrackBase::PatchTrackBase(const sp<ClientProxy>& proxy,
         IAfThreadBase* thread, const Timeout& timeout)
     : mProxy(proxy)