Merge "audioserver: add automated audio power logging" into main
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)