Merge changes from topic "presubmit-am-f038598b58a447c5b353312dcc5d9ed6" into tm-mainline-prod

* changes:
  [automerge] cameraserver: Fix convertToHidl for requestStreamBuffers return. 2p: 213a65c6fd
  cameraserver: Fix convertToHidl for requestStreamBuffers return.
diff --git a/apex/Android.bp b/apex/Android.bp
index b9b9bde..570ca01 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -56,6 +56,7 @@
     prebuilts: [
         "code_coverage.policy",
         "com.android.media-mediatranscoding.rc",
+        "com.android.media-mediatranscoding.32rc",
         "crash_dump.policy",
         "mediaextractor.policy",
         "media-linker-config",
@@ -177,6 +178,7 @@
     ],
     prebuilts: [
         "com.android.media.swcodec-mediaswcodec.rc",
+        "com.android.media.swcodec-mediaswcodec.32rc",
         "com.android.media.swcodec-ld.config.txt",
         "mediaswcodec.policy",
         "code_coverage.policy",
@@ -201,17 +203,34 @@
     compressible: true,
 }
 
+// install as mediatranscoding.* and mediaswcodec.* instead of init.*
+// so we are ready for day we have more than 1 *rc file within the apex.
+
 prebuilt_etc {
     name: "com.android.media-mediatranscoding.rc",
     src: "mediatranscoding.rc",
-    filename: "init.rc",
+    filename: "mediatranscoding.rc",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "com.android.media-mediatranscoding.32rc",
+    src: "mediatranscoding.32rc",
+    filename: "mediatranscoding.32rc",
     installable: false,
 }
 
 prebuilt_etc {
     name: "com.android.media.swcodec-mediaswcodec.rc",
     src: "mediaswcodec.rc",
-    filename: "init.rc",
+    filename: "mediaswcodec.rc",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "com.android.media.swcodec-mediaswcodec.32rc",
+    src: "mediaswcodec.32rc",
+    filename: "mediaswcodec.32rc",
     installable: false,
 }
 
diff --git a/apex/mediaswcodec.32rc b/apex/mediaswcodec.32rc
index 79aef36..f40d172 100644
--- a/apex/mediaswcodec.32rc
+++ b/apex/mediaswcodec.32rc
@@ -1,3 +1,5 @@
+##  for SDK releases >= 32
+##
 service media.swcodec /apex/com.android.media.swcodec/bin/mediaswcodec
     class main
     user mediacodec
diff --git a/apex/mediaswcodec.rc b/apex/mediaswcodec.rc
index 0c9b8c8..46799c7 100644
--- a/apex/mediaswcodec.rc
+++ b/apex/mediaswcodec.rc
@@ -1,3 +1,6 @@
+##  for SDK releases 29..31
+##  where writepid has not yet been replaced by task_profiles
+##
 service media.swcodec /apex/com.android.media.swcodec/bin/mediaswcodec
     class main
     user mediacodec
diff --git a/apex/mediatranscoding.32rc b/apex/mediatranscoding.32rc
index 5169462..edba9b9 100644
--- a/apex/mediatranscoding.32rc
+++ b/apex/mediatranscoding.32rc
@@ -1,3 +1,6 @@
+##  for SDK releases >= 32
+##
+#
 # media.transcoding service is defined on com.android.media apex which goes back
 # to API29, but we only want it started on API31+ devices. So we declare it as
 # "disabled" and start it explicitly on boot.
diff --git a/apex/mediatranscoding.rc b/apex/mediatranscoding.rc
index ae9f8ba..6e453be 100644
--- a/apex/mediatranscoding.rc
+++ b/apex/mediatranscoding.rc
@@ -1,3 +1,7 @@
+##  for SDK releases 29..31
+##  where writepid has not yet been replaced by task_profiles
+##
+#
 # media.transcoding service is defined on com.android.media apex which goes back
 # to API29, but we only want it started on API31+ devices. So we declare it as
 # "disabled" and start it explicitly on boot.
diff --git a/media/codec2/vndk/C2AllocatorGralloc.cpp b/media/codec2/vndk/C2AllocatorGralloc.cpp
index d8d6f06..bc4053d 100644
--- a/media/codec2/vndk/C2AllocatorGralloc.cpp
+++ b/media/codec2/vndk/C2AllocatorGralloc.cpp
@@ -261,7 +261,7 @@
     for (const ui::PlaneLayout &plane : planes) {
         layout->rootPlanes++;
         uint32_t lastOffsetInBits = 0;
-        uint32_t rootIx = 0;
+        uint32_t rootIx = layout->numPlanes;
 
         for (const PlaneLayoutComponent &component : plane.components) {
             if (!gralloc4::isStandardPlaneLayoutComponentType(component.type)) {
@@ -309,7 +309,6 @@
 
             layout->numPlanes++;
             lastOffsetInBits = component.offsetInBits + component.sizeInBits;
-            rootIx++;
         }
     }
     return C2_OK;
@@ -699,17 +698,6 @@
                 C2PlanarLayout::PLANE_V,          // rootIx
                 0,                                // offset
             };
-            // handle interleaved formats
-            intptr_t uvOffset = addr[C2PlanarLayout::PLANE_V] - addr[C2PlanarLayout::PLANE_U];
-            if (uvOffset > 0 && uvOffset < (intptr_t)ycbcrLayout.chroma_step) {
-                layout->rootPlanes = 2;
-                layout->planes[C2PlanarLayout::PLANE_V].rootIx = C2PlanarLayout::PLANE_U;
-                layout->planes[C2PlanarLayout::PLANE_V].offset = uvOffset;
-            } else if (uvOffset < 0 && uvOffset > -(intptr_t)ycbcrLayout.chroma_step) {
-                layout->rootPlanes = 2;
-                layout->planes[C2PlanarLayout::PLANE_U].rootIx = C2PlanarLayout::PLANE_V;
-                layout->planes[C2PlanarLayout::PLANE_U].offset = -uvOffset;
-            }
             break;
         }
 
@@ -830,17 +818,6 @@
                     C2PlanarLayout::PLANE_V,          // rootIx
                     0,                                // offset
                 };
-                // handle interleaved formats
-                intptr_t uvOffset = addr[C2PlanarLayout::PLANE_V] - addr[C2PlanarLayout::PLANE_U];
-                if (uvOffset > 0 && uvOffset < (intptr_t)ycbcrLayout.chroma_step) {
-                    layout->rootPlanes = 2;
-                    layout->planes[C2PlanarLayout::PLANE_V].rootIx = C2PlanarLayout::PLANE_U;
-                    layout->planes[C2PlanarLayout::PLANE_V].offset = uvOffset;
-                } else if (uvOffset < 0 && uvOffset > -(intptr_t)ycbcrLayout.chroma_step) {
-                    layout->rootPlanes = 2;
-                    layout->planes[C2PlanarLayout::PLANE_U].rootIx = C2PlanarLayout::PLANE_V;
-                    layout->planes[C2PlanarLayout::PLANE_U].offset = -uvOffset;
-                }
                 break;
             }
 
@@ -886,6 +863,29 @@
     }
     mLocked = true;
 
+    // handle interleaved formats
+    if (layout->type == C2PlanarLayout::TYPE_YUV && layout->rootPlanes == 3) {
+        intptr_t uvOffset = addr[C2PlanarLayout::PLANE_V] - addr[C2PlanarLayout::PLANE_U];
+        intptr_t uvColInc = layout->planes[C2PlanarLayout::PLANE_U].colInc;
+        if (uvOffset > 0 && uvOffset < uvColInc) {
+            layout->rootPlanes = 2;
+            layout->planes[C2PlanarLayout::PLANE_V].rootIx = C2PlanarLayout::PLANE_U;
+            layout->planes[C2PlanarLayout::PLANE_V].offset = uvOffset;
+        } else if (uvOffset < 0 && uvOffset > -uvColInc) {
+            layout->rootPlanes = 2;
+            layout->planes[C2PlanarLayout::PLANE_U].rootIx = C2PlanarLayout::PLANE_V;
+            layout->planes[C2PlanarLayout::PLANE_U].offset = -uvOffset;
+        }
+    }
+
+    ALOGV("C2AllocationGralloc::map: layout: type=%d numPlanes=%d rootPlanes=%d",
+          layout->type, layout->numPlanes, layout->rootPlanes);
+    for (int i = 0; i < layout->numPlanes; ++i) {
+        const C2PlaneInfo &plane = layout->planes[i];
+        ALOGV("C2AllocationGralloc::map: plane[%d]: colInc=%d rowInc=%d rootIx=%u offset=%u",
+              i, plane.colInc, plane.rowInc, plane.rootIx, plane.offset);
+    }
+
     return C2_OK;
 }
 
diff --git a/media/codec2/vndk/platform/C2SurfaceSyncObj.cpp b/media/codec2/vndk/platform/C2SurfaceSyncObj.cpp
index e55bdc0..2115cc3 100644
--- a/media/codec2/vndk/platform/C2SurfaceSyncObj.cpp
+++ b/media/codec2/vndk/platform/C2SurfaceSyncObj.cpp
@@ -228,10 +228,10 @@
     tv.tv_nsec = timeoutNs % 1000000000;
 
     int ret =  syscall(__NR_futex, &mCond, FUTEX_WAIT, waitId, &tv, NULL, 0);
-    if (ret == 0 || ret == EAGAIN) {
+    if (ret == 0 || errno == EAGAIN) {
         return C2_OK;
     }
-    if (ret == EINTR || ret == ETIMEDOUT) {
+    if (errno == EINTR || errno == ETIMEDOUT) {
         return C2_TIMED_OUT;
     }
     return C2_BAD_VALUE;
diff --git a/media/libaudiohal/impl/ConversionHelperHidl.cpp b/media/libaudiohal/impl/ConversionHelperHidl.cpp
index 1d34814..a1668ca 100644
--- a/media/libaudiohal/impl/ConversionHelperHidl.cpp
+++ b/media/libaudiohal/impl/ConversionHelperHidl.cpp
@@ -99,8 +99,8 @@
     values->setTo(params.toString());
 }
 
-ConversionHelperHidl::ConversionHelperHidl(const char* className)
-        : mClassName(className) {
+ConversionHelperHidl::ConversionHelperHidl(std::string_view className)
+        : mClassName(className.begin(), className.end()) {
 }
 
 // static
@@ -125,7 +125,7 @@
 }
 
 void ConversionHelperHidl::emitError(const char* funcName, const char* description) {
-    ALOGE("%s %p %s: %s (from rpc)", mClassName, this, funcName, description);
+    ALOGE("%s %p %s: %s (from rpc)", mClassName.c_str(), this, funcName, description);
 }
 
 }  // namespace android
diff --git a/media/libaudiohal/impl/ConversionHelperHidl.h b/media/libaudiohal/impl/ConversionHelperHidl.h
index 9368551..3163cbb 100644
--- a/media/libaudiohal/impl/ConversionHelperHidl.h
+++ b/media/libaudiohal/impl/ConversionHelperHidl.h
@@ -40,7 +40,7 @@
     static void parametersToHal(const hidl_vec<ParameterValue>& parameters, String8 *values);
     static void argsFromHal(const Vector<String16>& args, hidl_vec<hidl_string> *hidlArgs);
 
-    ConversionHelperHidl(const char* className);
+    ConversionHelperHidl(std::string_view className);
 
     template<typename R, typename T>
     status_t processReturn(const char* funcName, const Return<R>& ret, T *retval) {
@@ -76,8 +76,12 @@
         return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION;
     }
 
+    const std::string& getClassName() const {
+        return mClassName;
+    }
+
   private:
-    const char* mClassName;
+    const std::string mClassName;
 
     static status_t analyzeResult(const CoreResult& result);
 
diff --git a/media/libaudiohal/impl/DeviceHalHidl.cpp b/media/libaudiohal/impl/DeviceHalHidl.cpp
index 16863e4..7fa1d45 100644
--- a/media/libaudiohal/impl/DeviceHalHidl.cpp
+++ b/media/libaudiohal/impl/DeviceHalHidl.cpp
@@ -23,6 +23,7 @@
 #include <cutils/properties.h>
 #include <hwbinder/IPCThreadState.h>
 #include <media/AudioContainers.h>
+#include <mediautils/TimeCheck.h>
 #include <utils/Log.h>
 
 #include PATH(android/hardware/audio/FILE_VERSION/IPrimaryDevice.h)
@@ -45,13 +46,16 @@
 using namespace ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION;
 using namespace ::android::hardware::audio::CORE_TYPES_CPP_VERSION;
 
+#define TIME_CHECK() auto timeCheck = \
+        mediautils::makeTimeCheckStatsForClassMethod(getClassName(), __func__)
+
 DeviceHalHidl::DeviceHalHidl(const sp<::android::hardware::audio::CPP_VERSION::IDevice>& device)
-        : ConversionHelperHidl("Device"), mDevice(device) {
+        : ConversionHelperHidl("DeviceHalHidl"), mDevice(device) {
 }
 
 DeviceHalHidl::DeviceHalHidl(
         const sp<::android::hardware::audio::CPP_VERSION::IPrimaryDevice>& device)
-        : ConversionHelperHidl("Device"),
+        : ConversionHelperHidl("DeviceHalHidl"),
 #if MAJOR_VERSION <= 6 || (MAJOR_VERSION == 7 && MINOR_VERSION == 0)
           mDevice(device),
 #endif
@@ -84,22 +88,26 @@
 }
 
 status_t DeviceHalHidl::initCheck() {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     return processReturn("initCheck", mDevice->initCheck());
 }
 
 status_t DeviceHalHidl::setVoiceVolume(float volume) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     if (mPrimaryDevice == 0) return INVALID_OPERATION;
     return processReturn("setVoiceVolume", mPrimaryDevice->setVoiceVolume(volume));
 }
 
 status_t DeviceHalHidl::setMasterVolume(float volume) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     return processReturn("setMasterVolume", mDevice->setMasterVolume(volume));
 }
 
 status_t DeviceHalHidl::getMasterVolume(float *volume) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     Result retval;
     Return<void> ret = mDevice->getMasterVolume(
@@ -113,17 +121,20 @@
 }
 
 status_t DeviceHalHidl::setMode(audio_mode_t mode) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     if (mPrimaryDevice == 0) return INVALID_OPERATION;
     return processReturn("setMode", mPrimaryDevice->setMode(AudioMode(mode)));
 }
 
 status_t DeviceHalHidl::setMicMute(bool state) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     return processReturn("setMicMute", mDevice->setMicMute(state));
 }
 
 status_t DeviceHalHidl::getMicMute(bool *state) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     Result retval;
     Return<void> ret = mDevice->getMicMute(
@@ -137,11 +148,13 @@
 }
 
 status_t DeviceHalHidl::setMasterMute(bool state) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     return processReturn("setMasterMute", mDevice->setMasterMute(state));
 }
 
 status_t DeviceHalHidl::getMasterMute(bool *state) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     Result retval;
     Return<void> ret = mDevice->getMasterMute(
@@ -155,6 +168,7 @@
 }
 
 status_t DeviceHalHidl::setParameters(const String8& kvPairs) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     hidl_vec<ParameterValue> hidlParams;
     status_t status = parametersFromHal(kvPairs, &hidlParams);
@@ -165,6 +179,7 @@
 }
 
 status_t DeviceHalHidl::getParameters(const String8& keys, String8 *values) {
+    TIME_CHECK();
     values->clear();
     if (mDevice == 0) return NO_INIT;
     hidl_vec<hidl_string> hidlKeys;
@@ -185,6 +200,7 @@
 
 status_t DeviceHalHidl::getInputBufferSize(
         const struct audio_config *config, size_t *size) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     AudioConfig hidlConfig;
     HidlUtils::audioConfigFromHal(*config, true /*isInput*/, &hidlConfig);
@@ -207,6 +223,7 @@
         struct audio_config *config,
         const char *address,
         sp<StreamOutHalInterface> *outStream) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     DeviceAddress hidlDevice;
     if (status_t status = CoreUtils::deviceAddressFromHal(deviceType, address, &hidlDevice);
@@ -263,6 +280,7 @@
         audio_devices_t outputDevice,
         const char *outputDeviceAddress,
         sp<StreamInHalInterface> *inStream) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     DeviceAddress hidlDevice;
     if (status_t status = CoreUtils::deviceAddressFromHal(devices, address, &hidlDevice);
@@ -326,6 +344,7 @@
 }
 
 status_t DeviceHalHidl::supportsAudioPatches(bool *supportsPatches) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     return processReturn("supportsAudioPatches", mDevice->supportsAudioPatches(), supportsPatches);
 }
@@ -336,6 +355,7 @@
         unsigned int num_sinks,
         const struct audio_port_config *sinks,
         audio_patch_handle_t *patch) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     if (patch == nullptr) return BAD_VALUE;
 
@@ -381,6 +401,7 @@
 }
 
 status_t DeviceHalHidl::releaseAudioPatch(audio_patch_handle_t patch) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     return processReturn("releaseAudioPatch", mDevice->releaseAudioPatch(patch));
 }
@@ -403,10 +424,12 @@
 }
 
 status_t DeviceHalHidl::getAudioPort(struct audio_port *port) {
+    TIME_CHECK();
     return getAudioPortImpl(port);
 }
 
 status_t DeviceHalHidl::getAudioPort(struct audio_port_v7 *port) {
+    TIME_CHECK();
 #if MAJOR_VERSION >= 7
     return getAudioPortImpl(port);
 #else
@@ -427,6 +450,7 @@
 }
 
 status_t DeviceHalHidl::setAudioPortConfig(const struct audio_port_config *config) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     AudioPortConfig hidlConfig;
     HidlUtils::audioPortConfigFromHal(*config, &hidlConfig);
@@ -441,6 +465,7 @@
 }
 #elif MAJOR_VERSION >= 4
 status_t DeviceHalHidl::getMicrophones(std::vector<media::MicrophoneInfo> *microphonesInfo) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     Result retval;
     Return<void> ret = mDevice->getMicrophones(
@@ -461,6 +486,7 @@
 #if MAJOR_VERSION >= 6
 status_t DeviceHalHidl::addDeviceEffect(
         audio_port_handle_t device, sp<EffectHalInterface> effect) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     return processReturn("addDeviceEffect", mDevice->addDeviceEffect(
             static_cast<AudioPortHandle>(device), effect->effectId()));
@@ -475,6 +501,7 @@
 #if MAJOR_VERSION >= 6
 status_t DeviceHalHidl::removeDeviceEffect(
         audio_port_handle_t device, sp<EffectHalInterface> effect) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     return processReturn("removeDeviceEffect", mDevice->removeDeviceEffect(
             static_cast<AudioPortHandle>(device), effect->effectId()));
@@ -487,6 +514,7 @@
 #endif
 
 status_t DeviceHalHidl::setConnectedState(const struct audio_port_v7 *port, bool connected) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
 #if MAJOR_VERSION == 7 && MINOR_VERSION == 1
     if (supportsSetConnectedState7_1) {
@@ -513,6 +541,7 @@
 }
 
 error::Result<audio_hw_sync_t> DeviceHalHidl::getHwAvSync() {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     audio_hw_sync_t value;
     Result result;
@@ -525,6 +554,7 @@
 }
 
 status_t DeviceHalHidl::dump(int fd, const Vector<String16>& args) {
+    TIME_CHECK();
     if (mDevice == 0) return NO_INIT;
     native_handle_t* hidlHandle = native_handle_create(1, 0);
     hidlHandle->data[0] = fd;
diff --git a/media/libaudiohal/impl/StreamHalHidl.cpp b/media/libaudiohal/impl/StreamHalHidl.cpp
index 8ba0f72..77c2da0 100644
--- a/media/libaudiohal/impl/StreamHalHidl.cpp
+++ b/media/libaudiohal/impl/StreamHalHidl.cpp
@@ -22,6 +22,7 @@
 #include <media/AudioParameter.h>
 #include <mediautils/memory.h>
 #include <mediautils/SchedulingPolicyService.h>
+#include <mediautils/TimeCheck.h>
 #include <utils/Log.h>
 
 #include PATH(android/hardware/audio/CORE_TYPES_FILE_VERSION/IStreamOutCallback.h)
@@ -45,8 +46,11 @@
 using namespace ::android::hardware::audio::common::COMMON_TYPES_CPP_VERSION;
 using namespace ::android::hardware::audio::CORE_TYPES_CPP_VERSION;
 
-StreamHalHidl::StreamHalHidl(IStream *stream)
-        : ConversionHelperHidl("Stream"),
+#define TIME_CHECK() auto TimeCheck = \
+       mediautils::makeTimeCheckStatsForClassMethod(getClassName(), __func__)
+
+StreamHalHidl::StreamHalHidl(std::string_view className, IStream *stream)
+        : ConversionHelperHidl(className),
           mStream(stream),
           mHalThreadPriority(HAL_THREAD_PRIORITY_DEFAULT),
           mCachedBufferSize(0){
@@ -67,6 +71,7 @@
 }
 
 status_t StreamHalHidl::getBufferSize(size_t *size) {
+    TIME_CHECK();
     if (!mStream) return NO_INIT;
     status_t status = processReturn("getBufferSize", mStream->getBufferSize(), size);
     if (status == OK) {
@@ -76,6 +81,7 @@
 }
 
 status_t StreamHalHidl::getAudioProperties(audio_config_base_t *configBase) {
+    TIME_CHECK();
     *configBase = AUDIO_CONFIG_BASE_INITIALIZER;
     if (!mStream) return NO_INIT;
 #if MAJOR_VERSION <= 6
@@ -105,6 +111,7 @@
 }
 
 status_t StreamHalHidl::setParameters(const String8& kvPairs) {
+    TIME_CHECK();
     if (!mStream) return NO_INIT;
     hidl_vec<ParameterValue> hidlParams;
     status_t status = parametersFromHal(kvPairs, &hidlParams);
@@ -114,6 +121,7 @@
 }
 
 status_t StreamHalHidl::getParameters(const String8& keys, String8 *values) {
+    TIME_CHECK();
     values->clear();
     if (!mStream) return NO_INIT;
     hidl_vec<hidl_string> hidlKeys;
@@ -134,21 +142,25 @@
 }
 
 status_t StreamHalHidl::addEffect(sp<EffectHalInterface> effect) {
+    TIME_CHECK();
     if (!mStream) return NO_INIT;
     return processReturn("addEffect", mStream->addEffect(effect->effectId()));
 }
 
 status_t StreamHalHidl::removeEffect(sp<EffectHalInterface> effect) {
+    TIME_CHECK();
     if (!mStream) return NO_INIT;
     return processReturn("removeEffect", mStream->removeEffect(effect->effectId()));
 }
 
 status_t StreamHalHidl::standby() {
+    TIME_CHECK();
     if (!mStream) return NO_INIT;
     return processReturn("standby", mStream->standby());
 }
 
 status_t StreamHalHidl::dump(int fd, const Vector<String16>& args) {
+    TIME_CHECK();
     if (!mStream) return NO_INIT;
     native_handle_t* hidlHandle = native_handle_create(1, 0);
     hidlHandle->data[0] = fd;
@@ -173,17 +185,20 @@
 }
 
 status_t StreamHalHidl::start() {
+    TIME_CHECK();
     if (!mStream) return NO_INIT;
     return processReturn("start", mStream->start());
 }
 
 status_t StreamHalHidl::stop() {
+    TIME_CHECK();
     if (!mStream) return NO_INIT;
     return processReturn("stop", mStream->stop());
 }
 
 status_t StreamHalHidl::createMmapBuffer(int32_t minSizeFrames,
                                   struct audio_mmap_buffer_info *info) {
+    TIME_CHECK();
     Result retval;
     Return<void> ret = mStream->createMmapBuffer(
             minSizeFrames,
@@ -216,6 +231,7 @@
 }
 
 status_t StreamHalHidl::getMmapPosition(struct audio_mmap_position *position) {
+    TIME_CHECK();
     Result retval;
     Return<void> ret = mStream->getMmapPosition(
             [&](Result r, const MmapPosition& hidlPosition) {
@@ -244,7 +260,7 @@
 status_t StreamHalHidl::getHalPid(pid_t *pid) {
     using ::android::hidl::base::V1_0::DebugInfo;
     using ::android::hidl::manager::V1_0::IServiceManager;
-
+    TIME_CHECK();
     DebugInfo debugInfo;
     auto ret = mStream->getDebugInfo([&] (const auto &info) {
         debugInfo = info;
@@ -275,6 +291,7 @@
 status_t StreamHalHidl::legacyCreateAudioPatch(const struct audio_port_config& port,
                                                std::optional<audio_source_t> source,
                                                audio_devices_t type) {
+    TIME_CHECK();
     LOG_ALWAYS_FATAL_IF(port.type != AUDIO_PORT_TYPE_DEVICE, "port type must be device");
     unique_malloced_ptr<char> address;
     if (strcmp(port.ext.device.address, "") != 0) {
@@ -293,6 +310,7 @@
 }
 
 status_t StreamHalHidl::legacyReleaseAudioPatch() {
+    TIME_CHECK();
     AudioParameter param;
     param.addInt(String8(AudioParameter::keyRouting), 0);
     return setParameters(param.toString());
@@ -352,7 +370,8 @@
 
 StreamOutHalHidl::StreamOutHalHidl(
         const sp<::android::hardware::audio::CPP_VERSION::IStreamOut>& stream)
-        : StreamHalHidl(stream.get()), mStream(stream), mWriterClient(0), mEfGroup(nullptr) {
+        : StreamHalHidl("StreamOutHalHidl", stream.get())
+        , mStream(stream), mWriterClient(0), mEfGroup(nullptr) {
 }
 
 StreamOutHalHidl::~StreamOutHalHidl() {
@@ -376,11 +395,13 @@
 }
 
 status_t StreamOutHalHidl::getFrameSize(size_t *size) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     return processReturn("getFrameSize", mStream->getFrameSize(), size);
 }
 
 status_t StreamOutHalHidl::getLatency(uint32_t *latency) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     if (mWriterClient == gettid() && mCommandMQ) {
         return callWriterThread(
@@ -394,12 +415,14 @@
 }
 
 status_t StreamOutHalHidl::setVolume(float left, float right) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     return processReturn("setVolume", mStream->setVolume(left, right));
 }
 
 #if MAJOR_VERSION == 2
 status_t StreamOutHalHidl::selectPresentation(int presentationId, int programId) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     std::vector<ParameterValue> parameters;
     String8 halParameters;
@@ -410,6 +433,7 @@
 }
 #elif MAJOR_VERSION >= 4
 status_t StreamOutHalHidl::selectPresentation(int presentationId, int programId) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     return processReturn("selectPresentation",
             mStream->selectPresentation(presentationId, programId));
@@ -417,6 +441,7 @@
 #endif
 
 status_t StreamOutHalHidl::write(const void *buffer, size_t bytes, size_t *written) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     *written = 0;
 
@@ -562,6 +587,7 @@
 }
 
 status_t StreamOutHalHidl::getRenderPosition(uint32_t *dspFrames) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     Result retval;
     Return<void> ret = mStream->getRenderPosition(
@@ -575,6 +601,7 @@
 }
 
 status_t StreamOutHalHidl::getNextWriteTimestamp(int64_t *timestamp) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     Result retval;
     Return<void> ret = mStream->getNextWriteTimestamp(
@@ -588,6 +615,7 @@
 }
 
 status_t StreamOutHalHidl::setCallback(wp<StreamOutHalInterfaceCallback> callback) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     status_t status = processReturn(
             "setCallback", mStream->setCallback(new StreamOutCallback(this)));
@@ -598,6 +626,7 @@
 }
 
 status_t StreamOutHalHidl::supportsPauseAndResume(bool *supportsPause, bool *supportsResume) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     Return<void> ret = mStream->supportsPauseAndResume(
             [&](bool p, bool r) {
@@ -608,32 +637,38 @@
 }
 
 status_t StreamOutHalHidl::pause() {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     return processReturn("pause", mStream->pause());
 }
 
 status_t StreamOutHalHidl::resume() {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     return processReturn("pause", mStream->resume());
 }
 
 status_t StreamOutHalHidl::supportsDrain(bool *supportsDrain) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     return processReturn("supportsDrain", mStream->supportsDrain(), supportsDrain);
 }
 
 status_t StreamOutHalHidl::drain(bool earlyNotify) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     return processReturn(
             "drain", mStream->drain(earlyNotify ? AudioDrain::EARLY_NOTIFY : AudioDrain::ALL));
 }
 
 status_t StreamOutHalHidl::flush() {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     return processReturn("pause", mStream->flush());
 }
 
 status_t StreamOutHalHidl::getPresentationPosition(uint64_t *frames, struct timespec *timestamp) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     if (mWriterClient == gettid() && mCommandMQ) {
         return callWriterThread(
@@ -667,6 +702,7 @@
 #elif MAJOR_VERSION >= 4
 status_t StreamOutHalHidl::updateSourceMetadata(
         const StreamOutHalInterface::SourceMetadata& sourceMetadata) {
+    TIME_CHECK();
 #if MAJOR_VERSION == 4
     ::android::hardware::audio::CORE_TYPES_CPP_VERSION::SourceMetadata hidlMetadata;
 #else
@@ -717,6 +753,7 @@
 #else
 
 status_t StreamOutHalHidl::getDualMonoMode(audio_dual_mono_mode_t* mode) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     Result retval;
     Return<void> ret = mStream->getDualMonoMode(
@@ -730,12 +767,14 @@
 }
 
 status_t StreamOutHalHidl::setDualMonoMode(audio_dual_mono_mode_t mode) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     return processReturn(
             "setDualMonoMode", mStream->setDualMonoMode(static_cast<DualMonoMode>(mode)));
 }
 
 status_t StreamOutHalHidl::getAudioDescriptionMixLevel(float* leveldB) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     Result retval;
     Return<void> ret = mStream->getAudioDescriptionMixLevel(
@@ -749,12 +788,14 @@
 }
 
 status_t StreamOutHalHidl::setAudioDescriptionMixLevel(float leveldB) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     return processReturn(
             "setAudioDescriptionMixLevel", mStream->setAudioDescriptionMixLevel(leveldB));
 }
 
 status_t StreamOutHalHidl::getPlaybackRateParameters(audio_playback_rate_t* playbackRate) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     Result retval;
     Return<void> ret = mStream->getPlaybackRateParameters(
@@ -775,6 +816,7 @@
 }
 
 status_t StreamOutHalHidl::setPlaybackRateParameters(const audio_playback_rate_t& playbackRate) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     return processReturn(
             "setPlaybackRateParameters", mStream->setPlaybackRateParameters(
@@ -809,6 +851,7 @@
 
 status_t StreamOutHalHidl::setEventCallback(
         const sp<StreamOutHalInterfaceEventCallback>& callback) {
+    TIME_CHECK();
     if (mStream == nullptr) return NO_INIT;
     mEventCallback = callback;
     status_t status = processReturn(
@@ -823,12 +866,14 @@
 using hardware::audio::V7_1::LatencyMode;
 
 status_t StreamOutHalHidl::setLatencyMode(audio_latency_mode_t mode) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     return processReturn(
             "setLatencyMode", mStream->setLatencyMode(static_cast<LatencyMode>(mode)));
 };
 
 status_t StreamOutHalHidl::getRecommendedLatencyModes(std::vector<audio_latency_mode_t> *modes) {
+    TIME_CHECK();
     if (!mStream) return NO_INIT;
     Result retval;
     Return<void> ret = mStream->getRecommendedLatencyModes(
@@ -869,6 +914,7 @@
 
 status_t StreamOutHalHidl::setLatencyModeCallback(
         const sp<StreamOutHalInterfaceLatencyModeCallback>& callback) {
+    TIME_CHECK();
 
     if (mStream == nullptr) return NO_INIT;
     mLatencyModeCallback = callback;
@@ -940,7 +986,8 @@
 
 StreamInHalHidl::StreamInHalHidl(
         const sp<::android::hardware::audio::CORE_TYPES_CPP_VERSION::IStreamIn>& stream)
-        : StreamHalHidl(stream.get()), mStream(stream), mReaderClient(0), mEfGroup(nullptr) {
+        : StreamHalHidl("StreamInHalHidl", stream.get())
+        , mStream(stream), mReaderClient(0), mEfGroup(nullptr) {
 }
 
 StreamInHalHidl::~StreamInHalHidl() {
@@ -953,16 +1000,19 @@
 }
 
 status_t StreamInHalHidl::getFrameSize(size_t *size) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     return processReturn("getFrameSize", mStream->getFrameSize(), size);
 }
 
 status_t StreamInHalHidl::setGain(float gain) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     return processReturn("setGain", mStream->setGain(gain));
 }
 
 status_t StreamInHalHidl::read(void *buffer, size_t bytes, size_t *read) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     *read = 0;
 
@@ -1090,11 +1140,13 @@
 }
 
 status_t StreamInHalHidl::getInputFramesLost(uint32_t *framesLost) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     return processReturn("getInputFramesLost", mStream->getInputFramesLost(), framesLost);
 }
 
 status_t StreamInHalHidl::getCapturePosition(int64_t *frames, int64_t *time) {
+    TIME_CHECK();
     if (mStream == 0) return NO_INIT;
     if (mReaderClient == gettid() && mCommandMQ) {
         ReadParameters params;
@@ -1134,6 +1186,7 @@
 #elif MAJOR_VERSION >= 4
 status_t StreamInHalHidl::getActiveMicrophones(
         std::vector<media::MicrophoneInfo> *microphonesInfo) {
+    TIME_CHECK();
     if (!mStream) return NO_INIT;
     Result retval;
     Return<void> ret = mStream->getActiveMicrophones(
@@ -1152,6 +1205,7 @@
 
 status_t StreamInHalHidl::updateSinkMetadata(const
         StreamInHalInterface::SinkMetadata& sinkMetadata) {
+    TIME_CHECK();
 #if MAJOR_VERSION == 4
     ::android::hardware::audio::CORE_TYPES_CPP_VERSION::SinkMetadata hidlMetadata;
 #else
@@ -1179,12 +1233,14 @@
 }
 #else
 status_t StreamInHalHidl::setPreferredMicrophoneDirection(audio_microphone_direction_t direction) {
+    TIME_CHECK();
     if (!mStream) return NO_INIT;
     return processReturn("setPreferredMicrophoneDirection",
         mStream->setMicrophoneDirection(static_cast<MicrophoneDirection>(direction)));
 }
 
 status_t StreamInHalHidl::setPreferredMicrophoneFieldDimension(float zoom) {
+    TIME_CHECK();
     if (!mStream) return NO_INIT;
     return processReturn("setPreferredMicrophoneFieldDimension",
                 mStream->setMicrophoneFieldDimension(zoom));
diff --git a/media/libaudiohal/impl/StreamHalHidl.h b/media/libaudiohal/impl/StreamHalHidl.h
index 4e80e88..b5ecfd1 100644
--- a/media/libaudiohal/impl/StreamHalHidl.h
+++ b/media/libaudiohal/impl/StreamHalHidl.h
@@ -97,7 +97,7 @@
 
   protected:
     // Subclasses can not be constructed directly by clients.
-    explicit StreamHalHidl(IStream *stream);
+    StreamHalHidl(std::string_view className, IStream *stream);
 
     ~StreamHalHidl() override;
 
diff --git a/media/libstagefright/timedtext/test/Android.bp b/media/libstagefright/timedtext/test/Android.bp
index 58c68ef..ae97c50 100644
--- a/media/libstagefright/timedtext/test/Android.bp
+++ b/media/libstagefright/timedtext/test/Android.bp
@@ -36,7 +36,6 @@
 
     static_libs: [
         "libstagefright_timedtext",
-        "libstagefright_foundation",
     ],
 
     header_libs: [
@@ -47,6 +46,7 @@
         "liblog",
         "libmedia",
         "libbinder",
+        "libstagefright_foundation",
     ],
 
     cflags: [
diff --git a/media/utils/Android.bp b/media/utils/Android.bp
index 569ea2a..c333fa6 100644
--- a/media/utils/Android.bp
+++ b/media/utils/Android.bp
@@ -30,6 +30,7 @@
         "ISchedulingPolicyService.cpp",
         "LimitProcessMemory.cpp",
         "MemoryLeakTrackUtil.cpp",
+        "MethodStatistics.cpp",
         "ProcessInfo.cpp",
         "SchedulingPolicyService.cpp",
         "ServiceUtilities.cpp",
diff --git a/media/utils/MethodStatistics.cpp b/media/utils/MethodStatistics.cpp
new file mode 100644
index 0000000..875c43d
--- /dev/null
+++ b/media/utils/MethodStatistics.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 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 <mediautils/MethodStatistics.h>
+
+namespace android::mediautils {
+
+// Repository for MethodStatistics Objects
+
+std::shared_ptr<std::vector<std::string>>
+getStatisticsClassesForModule(std::string_view moduleName) {
+    static const std::map<std::string, std::shared_ptr<std::vector<std::string>>> m {
+        {
+            METHOD_STATISTICS_MODULE_NAME_AUDIO_HIDL,
+            std::shared_ptr<std::vector<std::string>>(
+                new std::vector<std::string>{
+                "DeviceHalHidl",
+                "StreamInHalHidl",
+                "StreamOutHalHidl",
+              })
+        },
+    };
+    auto it = m.find({moduleName.begin(), moduleName.end()});
+    if (it == m.end()) return {};
+    return it->second;
+}
+
+static void addClassesToMap(const std::shared_ptr<std::vector<std::string>> &classNames,
+        std::map<std::string, std::shared_ptr<MethodStatistics<std::string>>> &map) {
+    if (classNames) {
+        for (const auto& className : *classNames) {
+            map.emplace(className, std::make_shared<MethodStatistics<std::string>>());
+        }
+    }
+}
+
+// singleton statistics for DeviceHalHidl StreamOutHalHidl StreamInHalHidl
+std::shared_ptr<MethodStatistics<std::string>>
+getStatisticsForClass(std::string_view className) {
+    static const std::map<std::string, std::shared_ptr<MethodStatistics<std::string>>> m =
+        // copy elided initialization of map m.
+        [](){
+            std::map<std::string, std::shared_ptr<MethodStatistics<std::string>>> m;
+            addClassesToMap(
+                    getStatisticsClassesForModule(METHOD_STATISTICS_MODULE_NAME_AUDIO_HIDL),
+                    m);
+            return m;
+        }();
+
+    auto it = m.find({className.begin(), className.end()});
+    if (it == m.end()) return {};
+    return it->second;
+}
+
+} // android::mediautils
diff --git a/media/utils/TimeCheck.cpp b/media/utils/TimeCheck.cpp
index 5f269af..d0dc9e1 100644
--- a/media/utils/TimeCheck.cpp
+++ b/media/utils/TimeCheck.cpp
@@ -17,23 +17,78 @@
 #define LOG_TAG "TimeCheck"
 
 #include <optional>
-#include <sstream>
 
+#include <audio_utils/clock.h>
 #include <mediautils/EventLog.h>
+#include <mediautils/MethodStatistics.h>
 #include <mediautils/TimeCheck.h>
 #include <utils/Log.h>
 #include "debuggerd/handler.h"
 
 namespace android::mediautils {
 
-namespace {
-
+/**
+ * Returns the std::string "HH:MM:SS.MSc" from a system_clock time_point.
+ */
 std::string formatTime(std::chrono::system_clock::time_point t) {
-    auto msSinceEpoch = std::chrono::round<std::chrono::milliseconds>(t.time_since_epoch());
-    return (std::ostringstream() << msSinceEpoch.count()).str();
+    auto time_string = audio_utils_time_string_from_ns(
+            std::chrono::nanoseconds(t.time_since_epoch()).count());
+
+    // The time string is 19 characters (including null termination).
+    // Example: "03-27 16:47:06.187"
+    //           MM DD HH MM SS MS
+    // We offset by 6 to get HH:MM:SS.MSc
+    //
+    return time_string.time + 6; // offset to remove month/day.
 }
 
-}  // namespace
+/**
+ * Finds the end of the common time prefix.
+ *
+ * This is as an option to remove the common time prefix to avoid
+ * unnecessary duplicated strings.
+ *
+ * \param time1 a time string
+ * \param time2 a time string
+ * \return      the position where the common time prefix ends. For abbreviated
+ *              printing of time2, offset the character pointer by this position.
+ */
+static size_t commonTimePrefixPosition(std::string_view time1, std::string_view time2) {
+    const size_t endPos = std::min(time1.size(), time2.size());
+    size_t i;
+
+    // Find location of the first mismatch between strings
+    for (i = 0; ; ++i) {
+        if (i == endPos) {
+            return i; // strings match completely to the length of one of the strings.
+        }
+        if (time1[i] != time2[i]) {
+            break;
+        }
+        if (time1[i] == '\0') {
+            return i; // "printed" strings match completely.  No need to check further.
+        }
+    }
+
+    // Go backwards until we find a delimeter or space.
+    for (; i > 0
+           && isdigit(time1[i]) // still a number
+           && time1[i - 1] != ' '
+         ; --i) {
+    }
+    return i;
+}
+
+/**
+ * Returns the unique suffix of time2 that isn't present in time1.
+ *
+ * If time2 is identical to time1, then an empty string_view is returned.
+ * This method is used to elide the common prefix when printing times.
+ */
+std::string_view timeSuffix(std::string_view time1, std::string_view time2) {
+    const size_t pos = commonTimePrefixPosition(time1, time2);
+    return time2.substr(pos);
+}
 
 // Audio HAL server pids vector used to generate audio HAL processes tombstone
 // when audioserver watchdog triggers.
@@ -75,23 +130,34 @@
     return sTimeCheckThread;
 }
 
+/* static */
+std::string TimeCheck::toString() {
+    // note pending and retired are individually locked for maximum concurrency,
+    // snapshot is not instantaneous at a single time.
+    return getTimeCheckThread().toString();
+}
+
 TimeCheck::TimeCheck(std::string tag, OnTimerFunc&& onTimer, uint32_t timeoutMs,
         bool crashOnTimeout)
     : mTimeCheckHandler(new TimeCheckHandler{
             std::move(tag), std::move(onTimer), crashOnTimeout,
             std::chrono::system_clock::now(), gettid()})
-    , mTimerHandle(getTimeCheckThread().scheduleTask(
-              // Pass in all the arguments by value to this task for safety.
-              // The thread could call the callback before the constructor is finished.
-              // The destructor will be blocked on the callback, but that is implementation
-              // dependent.
-              [ timeCheckHandler = mTimeCheckHandler ] {
-                  timeCheckHandler->onTimeout();
-              },
-              std::chrono::milliseconds(timeoutMs))) {}
+    , mTimerHandle(timeoutMs == 0
+              ? getTimeCheckThread().trackTask(mTimeCheckHandler->tag)
+              : getTimeCheckThread().scheduleTask(
+                      mTimeCheckHandler->tag,
+                      // Pass in all the arguments by value to this task for safety.
+                      // The thread could call the callback before the constructor is finished.
+                      // The destructor is not blocked on callback.
+                      [ timeCheckHandler = mTimeCheckHandler ] {
+                          timeCheckHandler->onTimeout();
+                      },
+                      std::chrono::milliseconds(timeoutMs))) {}
 
 TimeCheck::~TimeCheck() {
-    mTimeCheckHandler->onCancel(mTimerHandle);
+    if (mTimeCheckHandler) {
+        mTimeCheckHandler->onCancel(mTimerHandle);
+    }
 }
 
 void TimeCheck::TimeCheckHandler::onCancel(TimerThread::Handle timerHandle) const
@@ -115,6 +181,10 @@
 
     if (!crashOnTimeout) return;
 
+    // Generate the TimerThread summary string early before sending signals to the
+    // HAL processes which can affect thread behavior.
+    const std::string summary = getTimeCheckThread().toString(4 /* retiredCount */);
+
     // Generate audio HAL processes tombstones and allow time to complete
     // before forcing restart
     std::vector<pid_t> pids = TimeCheck::getAudioHalPids();
@@ -128,8 +198,28 @@
         ALOGI("No HAL process pid available, skipping tombstones");
     }
     LOG_EVENT_STRING(LOGTAG_AUDIO_BINDER_TIMEOUT, tag.c_str());
-    LOG_ALWAYS_FATAL("TimeCheck timeout for %s on thread %d (start=%s, end=%s)",
-            tag.c_str(), tid, formatTime(startTime).c_str(), formatTime(endTime).c_str());
+    LOG_ALWAYS_FATAL("TimeCheck timeout for %s scheduled %s on thread %d\n%s",
+            tag.c_str(), formatTime(startTime).c_str(), tid, summary.c_str());
+}
+
+// Automatically create a TimeCheck class for a class and method.
+// This is used for Audio HIDL support.
+mediautils::TimeCheck makeTimeCheckStatsForClassMethod(
+        std::string_view className, std::string_view methodName) {
+    std::shared_ptr<MethodStatistics<std::string>> statistics =
+            mediautils::getStatisticsForClass(className);
+    if (!statistics) return {}; // empty TimeCheck.
+    return mediautils::TimeCheck(
+            std::string(className).append("::").append(methodName),
+            [ clazz = std::string(className), method = std::string(methodName),
+              stats = std::move(statistics) ]
+            (bool timeout, float elapsedMs) {
+                    if (timeout) {
+                        ; // ignored, there is no timeout value.
+                    } else {
+                        stats->event(method, elapsedMs);
+                    }
+            }, 0 /* timeoutMs */);
 }
 
 }  // namespace android::mediautils
diff --git a/media/utils/TimerThread-test.cpp b/media/utils/TimerThread-test.cpp
index ee8a811..93cd64c 100644
--- a/media/utils/TimerThread-test.cpp
+++ b/media/utils/TimerThread-test.cpp
@@ -20,54 +20,71 @@
 #include <mediautils/TimerThread.h>
 
 using namespace std::chrono_literals;
+using namespace android::mediautils;
 
-namespace android {
 namespace {
 
 constexpr auto kJitter = 10ms;
 
+// Each task written by *ToString() will start with a left brace.
+constexpr char REQUEST_START = '{';
+
+inline size_t countChars(std::string_view s, char c) {
+    return std::count(s.begin(), s.end(), c);
+}
+
 TEST(TimerThread, Basic) {
     std::atomic<bool> taskRan = false;
     TimerThread thread;
-    thread.scheduleTask([&taskRan] { taskRan = true; }, 100ms);
+    thread.scheduleTask("Basic", [&taskRan] { taskRan = true; }, 100ms);
     std::this_thread::sleep_for(100ms - kJitter);
     ASSERT_FALSE(taskRan);
     std::this_thread::sleep_for(2 * kJitter);
     ASSERT_TRUE(taskRan);
+    ASSERT_EQ(1, countChars(thread.retiredToString(), REQUEST_START));
 }
 
 TEST(TimerThread, Cancel) {
     std::atomic<bool> taskRan = false;
     TimerThread thread;
-    TimerThread::Handle handle = thread.scheduleTask([&taskRan] { taskRan = true; }, 100ms);
+    TimerThread::Handle handle =
+            thread.scheduleTask("Cancel", [&taskRan] { taskRan = true; }, 100ms);
     std::this_thread::sleep_for(100ms - kJitter);
     ASSERT_FALSE(taskRan);
-    thread.cancelTask(handle);
+    ASSERT_TRUE(thread.cancelTask(handle));
     std::this_thread::sleep_for(2 * kJitter);
     ASSERT_FALSE(taskRan);
+    ASSERT_EQ(1, countChars(thread.retiredToString(), REQUEST_START));
 }
 
 TEST(TimerThread, CancelAfterRun) {
     std::atomic<bool> taskRan = false;
     TimerThread thread;
-    TimerThread::Handle handle = thread.scheduleTask([&taskRan] { taskRan = true; }, 100ms);
+    TimerThread::Handle handle =
+            thread.scheduleTask("CancelAfterRun", [&taskRan] { taskRan = true; }, 100ms);
     std::this_thread::sleep_for(100ms + kJitter);
     ASSERT_TRUE(taskRan);
-    thread.cancelTask(handle);
+    ASSERT_FALSE(thread.cancelTask(handle));
+    ASSERT_EQ(1, countChars(thread.retiredToString(), REQUEST_START));
 }
 
 TEST(TimerThread, MultipleTasks) {
-    std::array<std::atomic<bool>, 6> taskRan;
+    std::array<std::atomic<bool>, 6> taskRan{};
     TimerThread thread;
 
     auto startTime = std::chrono::steady_clock::now();
 
-    thread.scheduleTask([&taskRan] { taskRan[0] = true; }, 300ms);
-    thread.scheduleTask([&taskRan] { taskRan[1] = true; }, 100ms);
-    thread.scheduleTask([&taskRan] { taskRan[2] = true; }, 200ms);
-    thread.scheduleTask([&taskRan] { taskRan[3] = true; }, 400ms);
-    auto handle4 = thread.scheduleTask([&taskRan] { taskRan[4] = true; }, 200ms);
-    thread.scheduleTask([&taskRan] { taskRan[5] = true; }, 200ms);
+    thread.scheduleTask("0", [&taskRan] { taskRan[0] = true; }, 300ms);
+    thread.scheduleTask("1", [&taskRan] { taskRan[1] = true; }, 100ms);
+    thread.scheduleTask("2", [&taskRan] { taskRan[2] = true; }, 200ms);
+    thread.scheduleTask("3", [&taskRan] { taskRan[3] = true; }, 400ms);
+    auto handle4 = thread.scheduleTask("4", [&taskRan] { taskRan[4] = true; }, 200ms);
+    thread.scheduleTask("5", [&taskRan] { taskRan[5] = true; }, 200ms);
+
+    // 6 tasks pending
+    ASSERT_EQ(6, countChars(thread.pendingToString(), REQUEST_START));
+    // 0 tasks completed
+    ASSERT_EQ(0, countChars(thread.retiredToString(), REQUEST_START));
 
     // Task 1 should trigger around 100ms.
     std::this_thread::sleep_until(startTime + 100ms - kJitter);
@@ -123,6 +140,11 @@
     ASSERT_FALSE(taskRan[4]);
     ASSERT_TRUE(taskRan[5]);
 
+    // 1 task pending
+    ASSERT_EQ(1, countChars(thread.pendingToString(), REQUEST_START));
+    // 4 tasks ran and 1 cancelled
+    ASSERT_EQ(4 + 1, countChars(thread.retiredToString(), REQUEST_START));
+
     // Task 3 should trigger around 400ms.
     std::this_thread::sleep_until(startTime + 400ms - kJitter);
     ASSERT_TRUE(taskRan[0]);
@@ -132,6 +154,9 @@
     ASSERT_FALSE(taskRan[4]);
     ASSERT_TRUE(taskRan[5]);
 
+    // 4 tasks ran and 1 cancelled
+    ASSERT_EQ(4 + 1, countChars(thread.retiredToString(), REQUEST_START));
+
     std::this_thread::sleep_until(startTime + 400ms + kJitter);
     ASSERT_TRUE(taskRan[0]);
     ASSERT_TRUE(taskRan[1]);
@@ -139,8 +164,62 @@
     ASSERT_TRUE(taskRan[3]);
     ASSERT_FALSE(taskRan[4]);
     ASSERT_TRUE(taskRan[5]);
+
+    // 0 tasks pending
+    ASSERT_EQ(0, countChars(thread.pendingToString(), REQUEST_START));
+    // 5 tasks ran and 1 cancelled
+    ASSERT_EQ(5 + 1, countChars(thread.retiredToString(), REQUEST_START));
 }
 
+TEST(TimerThread, TrackedTasks) {
+    TimerThread thread;
+
+    auto handle0 = thread.trackTask("0");
+    auto handle1 = thread.trackTask("1");
+    auto handle2 = thread.trackTask("2");
+
+    // 3 tasks pending
+    ASSERT_EQ(3, countChars(thread.pendingToString(), REQUEST_START));
+    // 0 tasks retired
+    ASSERT_EQ(0, countChars(thread.retiredToString(), REQUEST_START));
+
+    ASSERT_TRUE(thread.cancelTask(handle0));
+    ASSERT_TRUE(thread.cancelTask(handle1));
+
+    // 1 task pending
+    ASSERT_EQ(1, countChars(thread.pendingToString(), REQUEST_START));
+    // 2 tasks retired
+    ASSERT_EQ(2, countChars(thread.retiredToString(), REQUEST_START));
+
+    // handle1 is stale, cancel returns false.
+    ASSERT_FALSE(thread.cancelTask(handle1));
+
+    // 1 task pending
+    ASSERT_EQ(1, countChars(thread.pendingToString(), REQUEST_START));
+    // 2 tasks retired
+    ASSERT_EQ(2, countChars(thread.retiredToString(), REQUEST_START));
+
+    // Add another tracked task.
+    auto handle3 = thread.trackTask("3");
+
+    // 2 tasks pending
+    ASSERT_EQ(2, countChars(thread.pendingToString(), REQUEST_START));
+    // 2 tasks retired
+    ASSERT_EQ(2, countChars(thread.retiredToString(), REQUEST_START));
+
+    ASSERT_TRUE(thread.cancelTask(handle2));
+
+    // 1 tasks pending
+    ASSERT_EQ(1, countChars(thread.pendingToString(), REQUEST_START));
+    // 3 tasks retired
+    ASSERT_EQ(3, countChars(thread.retiredToString(), REQUEST_START));
+
+    ASSERT_TRUE(thread.cancelTask(handle3));
+
+    // 0 tasks pending
+    ASSERT_EQ(0, countChars(thread.pendingToString(), REQUEST_START));
+    // 4 tasks retired
+    ASSERT_EQ(4, countChars(thread.retiredToString(), REQUEST_START));
+}
 
 }  // namespace
-}  // namespace android
diff --git a/media/utils/TimerThread.cpp b/media/utils/TimerThread.cpp
index dfc84e1..3bc4549 100644
--- a/media/utils/TimerThread.cpp
+++ b/media/utils/TimerThread.cpp
@@ -17,18 +17,167 @@
 #define LOG_TAG "TimerThread"
 
 #include <optional>
+#include <sstream>
+#include <unistd.h>
+#include <vector>
 
 #include <mediautils/TimerThread.h>
 #include <utils/ThreadDefs.h>
 
-namespace android {
+namespace android::mediautils {
 
-TimerThread::TimerThread() : mThread([this] { threadFunc(); }) {
-    pthread_setname_np(mThread.native_handle(), "TimeCheckThread");
-    pthread_setschedprio(mThread.native_handle(), PRIORITY_URGENT_AUDIO);
+extern std::string formatTime(std::chrono::system_clock::time_point t);
+extern std::string_view timeSuffix(std::string_view time1, std::string_view time2);
+
+TimerThread::Handle TimerThread::scheduleTask(
+        std::string tag, std::function<void()>&& func, std::chrono::milliseconds timeout) {
+    const auto now = std::chrono::system_clock::now();
+    std::shared_ptr<const Request> request{
+            new Request{ now, now + timeout, gettid(), std::move(tag) }};
+    return mMonitorThread.add(std::move(request), std::move(func), timeout);
 }
 
-TimerThread::~TimerThread() {
+TimerThread::Handle TimerThread::trackTask(std::string tag) {
+    const auto now = std::chrono::system_clock::now();
+    std::shared_ptr<const Request> request{
+            new Request{ now, now, gettid(), std::move(tag) }};
+    return mNoTimeoutMap.add(std::move(request));
+}
+
+bool TimerThread::cancelTask(Handle handle) {
+    std::shared_ptr<const Request> request = mNoTimeoutMap.isValidHandle(handle) ?
+             mNoTimeoutMap.remove(handle) : mMonitorThread.remove(handle);
+    if (!request) return false;
+    mRetiredQueue.add(std::move(request));
+    return true;
+}
+
+std::string TimerThread::toString(size_t retiredCount) const {
+    return std::string("now ")
+            .append(formatTime(std::chrono::system_clock::now()))
+            .append("\npending [ ")
+            .append(pendingToString())
+            .append(" ]\ntimeout [ ")
+            .append(timeoutToString())
+            .append(" ]\nretired [ ")
+            .append(retiredToString(retiredCount))
+            .append(" ]");
+}
+
+std::vector<std::shared_ptr<const TimerThread::Request>> TimerThread::getPendingRequests() const {
+    constexpr size_t kEstimatedPendingRequests = 8;  // approx 128 byte alloc.
+    std::vector<std::shared_ptr<const Request>> pendingRequests;
+    pendingRequests.reserve(kEstimatedPendingRequests); // preallocate vector out of lock.
+
+    // following are internally locked calls, which add to our local pendingRequests.
+    mMonitorThread.copyRequests(pendingRequests);
+    mNoTimeoutMap.copyRequests(pendingRequests);
+
+    // Sort in order of scheduled time.
+    std::sort(pendingRequests.begin(), pendingRequests.end(),
+        [](const std::shared_ptr<const Request>& r1,
+           const std::shared_ptr<const Request>& r2) {
+               return r1->scheduled < r2->scheduled;
+           });
+    return pendingRequests;
+}
+
+std::string TimerThread::pendingToString() const {
+    return requestsToString(getPendingRequests());
+}
+
+std::string TimerThread::retiredToString(size_t n) const {
+    std::vector<std::shared_ptr<const Request>> retiredRequests;
+    mRetiredQueue.copyRequests(retiredRequests, n);
+
+    // Dump to string
+    return requestsToString(retiredRequests);
+}
+
+std::string TimerThread::timeoutToString(size_t n) const {
+    std::vector<std::shared_ptr<const Request>> timeoutRequests;
+    mTimeoutQueue.copyRequests(timeoutRequests, n);
+
+    // Dump to string
+    return requestsToString(timeoutRequests);
+}
+
+std::string TimerThread::Request::toString() const {
+    const auto scheduledString = formatTime(scheduled);
+    const auto deadlineString = formatTime(deadline);
+    return std::string(tag)
+        .append(" scheduled ").append(scheduledString)
+        .append(" deadline ").append(timeSuffix(scheduledString, deadlineString))
+        .append(" tid ").append(std::to_string(tid));
+}
+
+void TimerThread::RequestQueue::add(std::shared_ptr<const Request> request) {
+    std::lock_guard lg(mRQMutex);
+    mRequestQueue.emplace_back(std::chrono::system_clock::now(), std::move(request));
+    if (mRequestQueue.size() > mRequestQueueMax) {
+        mRequestQueue.pop_front();
+    }
+}
+
+void TimerThread::RequestQueue::copyRequests(
+        std::vector<std::shared_ptr<const Request>>& requests, size_t n) const {
+    std::lock_guard lg(mRQMutex);
+    const size_t size = mRequestQueue.size();
+    size_t i = n >=  size ? 0 : size - n;
+    for (; i < size; ++i) {
+        const auto &[time, request] = mRequestQueue[i];
+        requests.emplace_back(request);
+    }
+}
+
+bool TimerThread::NoTimeoutMap::isValidHandle(Handle handle) const {
+    return handle > getIndexedHandle(mNoTimeoutRequests);
+}
+
+TimerThread::Handle TimerThread::NoTimeoutMap::add(std::shared_ptr<const Request> request) {
+    std::lock_guard lg(mNTMutex);
+    // A unique handle is obtained by mNoTimeoutRequests.fetch_add(1),
+    // This need not be under a lock, but we do so anyhow.
+    const Handle handle = getIndexedHandle(mNoTimeoutRequests++);
+    mMap[handle] = request;
+    return handle;
+}
+
+std::shared_ptr<const TimerThread::Request> TimerThread::NoTimeoutMap::remove(Handle handle) {
+    std::lock_guard lg(mNTMutex);
+    auto it = mMap.find(handle);
+    if (it == mMap.end()) return {};
+    auto request = it->second;
+    mMap.erase(it);
+    return request;
+}
+
+void TimerThread::NoTimeoutMap::copyRequests(
+        std::vector<std::shared_ptr<const Request>>& requests) const {
+    std::lock_guard lg(mNTMutex);
+    for (const auto &[handle, request] : mMap) {
+        requests.emplace_back(request);
+    }
+}
+
+TimerThread::Handle TimerThread::MonitorThread::getUniqueHandle_l(
+        std::chrono::milliseconds timeout) {
+    // To avoid key collisions, advance by 1 tick until the key is unique.
+    auto deadline = std::chrono::steady_clock::now() + timeout;
+    for (; mMonitorRequests.find(deadline) != mMonitorRequests.end();
+         deadline += std::chrono::steady_clock::duration(1))
+        ;
+    return deadline;
+}
+
+TimerThread::MonitorThread::MonitorThread(RequestQueue& timeoutQueue)
+        : mTimeoutQueue(timeoutQueue)
+        , mThread([this] { threadFunc(); }) {
+     pthread_setname_np(mThread.native_handle(), "TimerThread");
+     pthread_setschedprio(mThread.native_handle(), PRIORITY_URGENT_AUDIO);
+}
+
+TimerThread::MonitorThread::~MonitorThread() {
     {
         std::lock_guard _l(mMutex);
         mShouldExit = true;
@@ -37,37 +186,26 @@
     mThread.join();
 }
 
-TimerThread::Handle TimerThread::scheduleTaskAtDeadline(std::function<void()>&& func,
-                                                        TimePoint deadline) {
-    std::lock_guard _l(mMutex);
-
-    // To avoid key collisions, advance by 1 tick until the key is unique.
-    for (; mMonitorRequests.find(deadline) != mMonitorRequests.end();
-         deadline += TimePoint::duration(1))
-        ;
-    mMonitorRequests.emplace(deadline, std::move(func));
-    mCond.notify_all();
-    return deadline;
-}
-
-// Returns true if cancelled, false if handle doesn't exist.
-// Beware of lock inversion with cancelTask() as the callback
-// is called while holding mMutex.
-bool TimerThread::cancelTask(Handle handle) {
-    std::lock_guard _l(mMutex);
-    return mMonitorRequests.erase(handle) != 0;
-}
-
-void TimerThread::threadFunc() {
+void TimerThread::MonitorThread::threadFunc() {
     std::unique_lock _l(mMutex);
-
     while (!mShouldExit) {
         if (!mMonitorRequests.empty()) {
-            TimePoint nextDeadline = mMonitorRequests.begin()->first;
+            Handle nextDeadline = mMonitorRequests.begin()->first;
             if (nextDeadline < std::chrono::steady_clock::now()) {
-                // Deadline expired.
-                mMonitorRequests.begin()->second();
-                mMonitorRequests.erase(mMonitorRequests.begin());
+                // Deadline has expired, handle the request.
+                {
+                    auto node = mMonitorRequests.extract(mMonitorRequests.begin());
+                    _l.unlock();
+                    // We add Request to retired queue early so that it can be dumped out.
+                    mTimeoutQueue.add(std::move(node.mapped().first));
+                    node.mapped().second(); // Caution: we don't hold lock here - but do we care?
+                                            // this is the timeout case!  We will crash soon,
+                                            // maybe before returning.
+                    // anything left over is released here outside lock.
+                }
+                // reacquire the lock - if something was added, we loop immediately to check.
+                _l.lock();
+                continue;
             }
             mCond.wait_until(_l, nextDeadline);
         } else {
@@ -76,4 +214,35 @@
     }
 }
 
-}  // namespace android
+TimerThread::Handle TimerThread::MonitorThread::add(
+        std::shared_ptr<const Request> request, std::function<void()>&& func,
+        std::chrono::milliseconds timeout) {
+    std::lock_guard _l(mMutex);
+    const Handle handle = getUniqueHandle_l(timeout);
+    mMonitorRequests.emplace(handle, std::make_pair(std::move(request), std::move(func)));
+    mCond.notify_all();
+    return handle;
+}
+
+std::shared_ptr<const TimerThread::Request> TimerThread::MonitorThread::remove(Handle handle) {
+    std::unique_lock ul(mMutex);
+    const auto it = mMonitorRequests.find(handle);
+    if (it == mMonitorRequests.end()) {
+        return {};
+    }
+    std::shared_ptr<const TimerThread::Request> request = std::move(it->second.first);
+    std::function<void()> func = std::move(it->second.second);
+    mMonitorRequests.erase(it);
+    ul.unlock();  // manually release lock here so func is released outside of lock.
+    return request;
+}
+
+void TimerThread::MonitorThread::copyRequests(
+        std::vector<std::shared_ptr<const Request>>& requests) const {
+    std::lock_guard lg(mMutex);
+    for (const auto &[deadline, monitorpair] : mMonitorRequests) {
+        requests.emplace_back(monitorpair.first);
+    }
+}
+
+}  // namespace android::mediautils
diff --git a/media/utils/include/mediautils/MethodStatistics.h b/media/utils/include/mediautils/MethodStatistics.h
index 7d8061d..700fbaa 100644
--- a/media/utils/include/mediautils/MethodStatistics.h
+++ b/media/utils/include/mediautils/MethodStatistics.h
@@ -19,6 +19,7 @@
 #include <map>
 #include <mutex>
 #include <string>
+#include <vector>
 
 #include <android-base/thread_annotations.h>
 #include <audio_utils/Statistics.h>
@@ -91,9 +92,16 @@
     std::string dump() const {
         std::stringstream ss;
         std::lock_guard lg(mLock);
-        for (const auto &[code, stats] : mStatisticsMap) {
-            ss << int(code) << " " << getMethodForCode(code) <<
-                    " n=" << stats.getN() << " " << stats.toString() << "\n";
+        if constexpr (std::is_same_v<Code, std::string>) {
+            for (const auto &[code, stats] : mStatisticsMap) {
+                ss << code <<
+                        " n=" << stats.getN() << " " << stats.toString() << "\n";
+            }
+        } else /* constexpr */ {
+            for (const auto &[code, stats] : mStatisticsMap) {
+                ss << int(code) << " " << getMethodForCode(code) <<
+                        " n=" << stats.getN() << " " << stats.toString() << "\n";
+            }
         }
         return ss.str();
     }
@@ -104,6 +112,18 @@
     std::map<Code, StatsType> mStatisticsMap GUARDED_BY(mLock);
 };
 
+// Managed Statistics support.
+// Supported Modules
+#define METHOD_STATISTICS_MODULE_NAME_AUDIO_HIDL "AudioHidl"
+
+// Returns a vector of class names for the module, or a nullptr if module not found.
+std::shared_ptr<std::vector<std::string>>
+getStatisticsClassesForModule(std::string_view moduleName);
+
+// Returns a statistics object for that class, or a nullptr if class not found.
+std::shared_ptr<MethodStatistics<std::string>>
+getStatisticsForClass(std::string_view className);
+
 // Only if used, requires IBinder.h to be included at the location of invocation.
 #define METHOD_STATISTICS_BINDER_CODE_NAMES(CODE_TYPE) \
     {(CODE_TYPE)IBinder::PING_TRANSACTION , "ping"}, \
diff --git a/media/utils/include/mediautils/TimeCheck.h b/media/utils/include/mediautils/TimeCheck.h
index 991a921..ef03aef 100644
--- a/media/utils/include/mediautils/TimeCheck.h
+++ b/media/utils/include/mediautils/TimeCheck.h
@@ -50,27 +50,31 @@
      *                                    destroyed or leaves scope before the timer expires.)
      *                      float elapsedMs (the elapsed time to this event).
      *                  The callback when timeout is true will be called on a different thread.
-     *                  Currently this is guaranteed to block the destructor
-     *                  (potential lock inversion warning here) nevertheless
-     *                  it would be safer not to depend on stack contents.
+     *                  This will cancel the callback on the destructor but is not guaranteed
+     *                  to block for callback completion if it is already in progress
+     *                  (for maximum concurrency and reduced deadlock potential), so use proper
+     *                  lifetime analysis (e.g. shared or weak pointers).
      * \param timeoutMs timeout in milliseconds.
+     *                  A zero timeout means no timeout is set -
+     *                  the callback is called only when
+     *                  the TimeCheck object is destroyed or leaves scope.
      * \param crashOnTimeout true if the object issues an abort on timeout.
      */
     explicit TimeCheck(std::string tag, OnTimerFunc&& onTimer = {},
             uint32_t timeoutMs = kDefaultTimeOutMs, bool crashOnTimeout = true);
+
+    TimeCheck() = default;
     // Remove copy constructors as there should only be one call to the destructor.
     // Move is kept implicitly disabled, but would be logically consistent if enabled.
     TimeCheck(const TimeCheck& other) = delete;
     TimeCheck& operator=(const TimeCheck&) = delete;
 
     ~TimeCheck();
+    static std::string toString();
     static void setAudioHalPids(const std::vector<pid_t>& pids);
     static std::vector<pid_t> getAudioHalPids();
 
   private:
-    static TimerThread& getTimeCheckThread();
-    static void accessAudioHalPids(std::vector<pid_t>* pids, bool update);
-
     // Helper class for handling events.
     // The usage here is const safe.
     class TimeCheckHandler {
@@ -85,11 +89,19 @@
         void onTimeout() const;
     };
 
+    static TimerThread& getTimeCheckThread();
+    static void accessAudioHalPids(std::vector<pid_t>* pids, bool update);
+
     // mTimeCheckHandler is immutable, prefer to be first initialized, last destroyed.
     // Technically speaking, we do not need a shared_ptr here because TimerThread::cancelTask()
     // is mutually exclusive of the callback, but the price paid for lifetime safety is minimal.
     const std::shared_ptr<const TimeCheckHandler> mTimeCheckHandler;
-    const TimerThread::Handle mTimerHandle;
+    const TimerThread::Handle mTimerHandle = TimerThread::INVALID_HANDLE;
 };
 
+// Returns a TimeCheck object that sends info to MethodStatistics
+// obtained from getStatisticsForClass(className).
+TimeCheck makeTimeCheckStatsForClassMethod(
+        std::string_view className, std::string_view methodName);
+
 }  // namespace android::mediautils
diff --git a/media/utils/include/mediautils/TimerThread.h b/media/utils/include/mediautils/TimerThread.h
index acf0b16..2dfe499 100644
--- a/media/utils/include/mediautils/TimerThread.h
+++ b/media/utils/include/mediautils/TimerThread.h
@@ -16,53 +16,225 @@
 
 #pragma once
 
+#include <atomic>
 #include <condition_variable>
+#include <deque>
 #include <functional>
 #include <map>
 #include <mutex>
+#include <string>
 #include <thread>
 
 #include <android-base/thread_annotations.h>
 
-namespace android {
+namespace android::mediautils {
 
 /**
  * A thread for deferred execution of tasks, with cancellation.
  */
 class TimerThread {
   public:
+    // A Handle is a time_point that serves as a unique key.  It is ordered.
     using Handle = std::chrono::steady_clock::time_point;
 
-    TimerThread();
-    ~TimerThread();
+    static inline constexpr Handle INVALID_HANDLE =
+            std::chrono::steady_clock::time_point::min();
 
     /**
-     * Schedule a task to be executed in the future (`timeout` duration from now).
-     * Returns a handle that can be used for cancellation.
+     * Schedules a task to be executed in the future (`timeout` duration from now).
+     *
+     * \param tag     string associated with the task.  This need not be unique,
+     *                as the Handle returned is used for cancelling.
+     * \param func    callback function that is invoked at the timeout.
+     * \param timeout timeout duration which is converted to milliseconds with at
+     *                least 45 integer bits.
+     *                A timeout of 0 (or negative) means the timer never expires
+     *                so func() is never called. These tasks are stored internally
+     *                and reported in the toString() until manually cancelled.
+     * \returns       a handle that can be used for cancellation.
      */
-    template <typename R, typename P>
-    Handle scheduleTask(std::function<void()>&& func, std::chrono::duration<R, P> timeout) {
-        auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout);
-        return scheduleTaskAtDeadline(std::move(func), deadline);
-    }
+    Handle scheduleTask(
+            std::string tag, std::function<void()>&& func, std::chrono::milliseconds timeout);
 
     /**
-     * Cancel a task, previously scheduled with scheduleTask().
-     * If the task has already executed, this is a no-op and returns false.
+     * Tracks a task that shows up on toString() until cancelled.
+     *
+     * \param tag     string associated with the task.
+     * \returns       a handle that can be used for cancellation.
+     */
+    Handle trackTask(std::string tag);
+
+    /**
+     * Cancels a task previously scheduled with scheduleTask()
+     * or trackTask().
+     *
+     * \returns true if cancelled. If the task has already executed
+     *          or if the handle doesn't exist, this is a no-op
+     *          and returns false.
      */
     bool cancelTask(Handle handle);
 
+    std::string toString(size_t retiredCount = SIZE_MAX) const;
+
+    /**
+     * Returns a string representation of the TimerThread queue.
+     *
+     * The queue is dumped in order of scheduling (not deadline).
+     */
+    std::string pendingToString() const;
+
+    /**
+     * Returns a string representation of the last retired tasks.
+     *
+     * These tasks from trackTask() or scheduleTask() are
+     * cancelled.
+     *
+     * These are ordered when the task was retired.
+     *
+     * \param n is maximum number of tasks to dump.
+     */
+    std::string retiredToString(size_t n = SIZE_MAX) const;
+
+
+    /**
+     * Returns a string representation of the last timeout tasks.
+     *
+     * These tasks from scheduleTask() which have  timed-out.
+     *
+     * These are ordered when the task had timed-out.
+     *
+     * \param n is maximum number of tasks to dump.
+     */
+    std::string timeoutToString(size_t n = SIZE_MAX) const;
+
+    /**
+     * Dumps a container with SmartPointer<Request> to a string.
+     *
+     * "{ Request1 } { Request2} ...{ RequestN }"
+     */
+    template <typename T>
+    static std::string requestsToString(const T& containerRequests) {
+        std::string s;
+        // append seems to be faster than stringstream.
+        // https://stackoverflow.com/questions/18892281/most-optimized-way-of-concatenation-in-strings
+        for (const auto& request : containerRequests) {
+            s.append("{ ").append(request->toString()).append(" } ");
+        }
+        // If not empty, there's an extra space at the end, so we trim it off.
+        if (!s.empty()) s.pop_back();
+        return s;
+    }
+
   private:
-    using TimePoint = std::chrono::steady_clock::time_point;
+    // To minimize movement of data, we pass around shared_ptrs to Requests.
+    // These are allocated and deallocated outside of the lock.
+    struct Request {
+        const std::chrono::system_clock::time_point scheduled;
+        const std::chrono::system_clock::time_point deadline; // deadline := scheduled + timeout
+                                                              // if deadline == scheduled, no
+                                                              // timeout, task not executed.
+        const pid_t tid;
+        const std::string tag;
 
-    std::condition_variable mCond;
-    std::mutex mMutex;
-    std::thread mThread;
-    std::map<TimePoint, std::function<void()>> mMonitorRequests GUARDED_BY(mMutex);
-    bool mShouldExit GUARDED_BY(mMutex) = false;
+        std::string toString() const;
+    };
 
-    void threadFunc();
-    Handle scheduleTaskAtDeadline(std::function<void()>&& func, TimePoint deadline);
+    // Deque of requests, in order of add().
+    // This class is thread-safe.
+    class RequestQueue {
+      public:
+        explicit RequestQueue(size_t maxSize)
+            : mRequestQueueMax(maxSize) {}
+
+        void add(std::shared_ptr<const Request>);
+
+        // return up to the last "n" requests retired.
+        void copyRequests(std::vector<std::shared_ptr<const Request>>& requests,
+            size_t n = SIZE_MAX) const;
+
+      private:
+        const size_t mRequestQueueMax;
+        mutable std::mutex mRQMutex;
+        std::deque<std::pair<std::chrono::system_clock::time_point,
+                             std::shared_ptr<const Request>>>
+                mRequestQueue GUARDED_BY(mRQMutex);
+    };
+
+    // A storage map of tasks without timeouts.  There is no std::function<void()>
+    // required, it just tracks the tasks with the tag, scheduled time and the tid.
+    // These tasks show up on a pendingToString() until manually cancelled.
+    class NoTimeoutMap {
+        // This a counter of the requests that have no timeout (timeout == 0).
+        std::atomic<size_t> mNoTimeoutRequests{};
+
+        mutable std::mutex mNTMutex;
+        std::map<Handle, std::shared_ptr<const Request>> mMap GUARDED_BY(mNTMutex);
+
+      public:
+        bool isValidHandle(Handle handle) const; // lock free
+        Handle add(std::shared_ptr<const Request> request);
+        std::shared_ptr<const Request> remove(Handle handle);
+        void copyRequests(std::vector<std::shared_ptr<const Request>>& requests) const;
+    };
+
+    // Monitor thread.
+    // This thread manages shared pointers to Requests and a function to
+    // call on timeout.
+    // This class is thread-safe.
+    class MonitorThread {
+        mutable std::mutex mMutex;
+        mutable std::condition_variable mCond;
+
+        // Ordered map of requests based on time of deadline.
+        //
+        std::map<Handle, std::pair<std::shared_ptr<const Request>, std::function<void()>>>
+                mMonitorRequests GUARDED_BY(mMutex);
+
+        RequestQueue& mTimeoutQueue; // locked internally, added to when request times out.
+
+        // Worker thread variables
+        bool mShouldExit GUARDED_BY(mMutex) = false;
+
+        // To avoid race with initialization,
+        // mThread should be initialized last as the thread is launched immediately.
+        std::thread mThread;
+
+        void threadFunc();
+        Handle getUniqueHandle_l(std::chrono::milliseconds timeout) REQUIRES(mMutex);
+
+      public:
+        MonitorThread(RequestQueue &timeoutQueue);
+        ~MonitorThread();
+
+        Handle add(std::shared_ptr<const Request> request, std::function<void()>&& func,
+                std::chrono::milliseconds timeout);
+        std::shared_ptr<const Request> remove(Handle handle);
+        void copyRequests(std::vector<std::shared_ptr<const Request>>& requests) const;
+    };
+
+    std::vector<std::shared_ptr<const Request>> getPendingRequests() const;
+
+    // A no-timeout request is represented by a handles at the end of steady_clock time,
+    // counting down by the number of no timeout requests previously requested.
+    // We manage them on the NoTimeoutMap, but conceptually they could be scheduled
+    // on the MonitorThread because those time handles won't expire in
+    // the lifetime of the device.
+    static inline Handle getIndexedHandle(size_t index) {
+        return std::chrono::time_point<std::chrono::steady_clock>::max() -
+                    std::chrono::time_point<std::chrono::steady_clock>::duration(index);
+    }
+
+    static constexpr size_t kRetiredQueueMax = 16;
+    RequestQueue mRetiredQueue{kRetiredQueueMax};  // locked internally
+
+    static constexpr size_t kTimeoutQueueMax = 16;
+    RequestQueue mTimeoutQueue{kTimeoutQueueMax};  // locked internally
+
+    NoTimeoutMap mNoTimeoutMap;  // locked internally
+
+    MonitorThread mMonitorThread{mTimeoutQueue};  // This should be initialized last because
+                                                  // the thread is launched immediately.
+                                                  // Locked internally.
 };
 
-}  // namespace android
+}  // namespace android::mediautils
diff --git a/media/utils/tests/timecheck_tests.cpp b/media/utils/tests/timecheck_tests.cpp
index 9833dc9..6ebf44d 100644
--- a/media/utils/tests/timecheck_tests.cpp
+++ b/media/utils/tests/timecheck_tests.cpp
@@ -23,6 +23,9 @@
 #include <utils/Log.h>
 
 using namespace android::mediautils;
+using namespace std::chrono_literals;
+
+namespace {
 
 TEST(timecheck_tests, success) {
     bool timeoutRegistered = false;
@@ -56,7 +59,7 @@
             elapsedMsRegistered = elapsedMs;
             event = true; // store-release, must be last.
         }, 1 /* msec */, false /* crash */);
-        usleep(100 * 1000 /* usec */);  // extra time as callback called by different thread.
+        std::this_thread::sleep_for(100ms);
     }
     ASSERT_TRUE(event); // load-acquire, must be first.
     ASSERT_TRUE(timeoutRegistered); // only called once on failure, not on dealloc.
@@ -65,3 +68,5 @@
 
 // Note: We do not test TimeCheck crash because TimeCheck is multithreaded and the
 // EXPECT_EXIT() signal catching is imperfect due to the gtest fork.
+
+} // namespace
\ No newline at end of file
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 8cafdfd..2813f72 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -917,13 +917,33 @@
         }
         {
             std::string timeCheckStats = getIAudioFlingerStatistics().dump();
-            dprintf(fd, "\nIAudioFlinger binder call profile\n");
+            dprintf(fd, "\nIAudioFlinger binder call profile:\n");
             write(fd, timeCheckStats.c_str(), timeCheckStats.size());
 
             extern mediautils::MethodStatistics<int>& getIEffectStatistics();
             timeCheckStats = getIEffectStatistics().dump();
-            dprintf(fd, "\nIEffect binder call profile\n");
+            dprintf(fd, "\nIEffect binder call profile:\n");
             write(fd, timeCheckStats.c_str(), timeCheckStats.size());
+
+            // Automatically fetch HIDL statistics.
+            std::shared_ptr<std::vector<std::string>> hidlClassNames =
+                    mediautils::getStatisticsClassesForModule(
+                            METHOD_STATISTICS_MODULE_NAME_AUDIO_HIDL);
+            if (hidlClassNames) {
+                for (const auto& className : *hidlClassNames) {
+                    auto stats = mediautils::getStatisticsForClass(className);
+                    if (stats) {
+                        timeCheckStats = stats->dump();
+                        dprintf(fd, "\n%s binder call profile:\n", className.c_str());
+                        write(fd, timeCheckStats.c_str(), timeCheckStats.size());
+                    }
+                }
+            }
+
+            timeCheckStats = mediautils::TimeCheck::toString();
+            dprintf(fd, "\nTimeCheck:\n");
+            write(fd, timeCheckStats.c_str(), timeCheckStats.size());
+            dprintf(fd, "\n");
         }
     }
     return NO_ERROR;
diff --git a/services/audioflinger/Effects.cpp b/services/audioflinger/Effects.cpp
index efd2dbd..e6d7cf7 100644
--- a/services/audioflinger/Effects.cpp
+++ b/services/audioflinger/Effects.cpp
@@ -41,6 +41,7 @@
 #include <media/audiohal/EffectsFactoryHalInterface.h>
 #include <mediautils/MethodStatistics.h>
 #include <mediautils/ServiceUtilities.h>
+#include <mediautils/TimeCheck.h>
 
 #include "AudioFlinger.h"
 
@@ -1780,13 +1781,16 @@
 
 status_t AudioFlinger::EffectHandle::onTransact(
         uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
-    std::chrono::system_clock::time_point startTime = std::chrono::system_clock::now();
-    mediametrics::Defer defer([startTime, code] {
-        std::chrono::system_clock::time_point endTime = std::chrono::system_clock::now();
-        getIEffectStatistics().event(code,
-                std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(
-                        endTime - startTime).count());
-    });
+    const std::string methodName = getIEffectStatistics().getMethodForCode(code);
+    mediautils::TimeCheck check(
+            std::string("IEffect::").append(methodName),
+            [code](bool timeout, float elapsedMs) {
+        if (timeout) {
+            ; // we don't timeout right now on the effect interface.
+        } else {
+            getIEffectStatistics().event(code, elapsedMs);
+        }
+    }, 0 /* timeoutMs */);
     return BnEffect::onTransact(code, data, reply, flags);
 }
 
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index e45de32..5720551 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -3192,9 +3192,10 @@
             }
         }
     }
-    return mEffects.registerEffect(desc, io, session, id,
-                                   (strategy == streamToStrategy(AUDIO_STREAM_MUSIC) ||
-                                   strategy == PRODUCT_STRATEGY_NONE));
+    bool isMusicEffect = (session != AUDIO_SESSION_OUTPUT_STAGE)
+                            && ((strategy == streamToStrategy(AUDIO_STREAM_MUSIC)
+                                    || strategy == PRODUCT_STRATEGY_NONE));
+    return mEffects.registerEffect(desc, io, session, id, isMusicEffect);
 }
 
 status_t AudioPolicyManager::unregisterEffect(int id)
diff --git a/services/camera/libcameraservice/Android.bp b/services/camera/libcameraservice/Android.bp
index 69300be..1e2dccb 100644
--- a/services/camera/libcameraservice/Android.bp
+++ b/services/camera/libcameraservice/Android.bp
@@ -81,7 +81,6 @@
         "device3/Camera3OutputUtils.cpp",
         "device3/Camera3DeviceInjectionMethods.cpp",
         "device3/UHRCropAndMeteringRegionMapper.cpp",
-        "device3/PreviewFrameScheduler.cpp",
         "device3/hidl/HidlCamera3Device.cpp",
         "device3/hidl/HidlCamera3OfflineSession.cpp",
         "device3/hidl/HidlCamera3OutputUtils.cpp",
@@ -112,7 +111,6 @@
     ],
 
     shared_libs: [
-        "libandroid",
         "libbase",
         "libdl",
         "libexif",
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index aeffd24..55acec4 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -2649,7 +2649,7 @@
 
 status_t Camera3Device::registerInFlight(uint32_t frameNumber,
         int32_t numBuffers, CaptureResultExtras resultExtras, bool hasInput,
-        bool hasAppCallback, nsecs_t maxExpectedDuration,
+        bool hasAppCallback, nsecs_t minExpectedDuration, nsecs_t maxExpectedDuration,
         const std::set<std::set<String8>>& physicalCameraIds,
         bool isStillCapture, bool isZslCapture, bool rotateAndCropAuto,
         const std::set<std::string>& cameraIdsWithZoom,
@@ -2659,8 +2659,9 @@
 
     ssize_t res;
     res = mInFlightMap.add(frameNumber, InFlightRequest(numBuffers, resultExtras, hasInput,
-            hasAppCallback, maxExpectedDuration, physicalCameraIds, isStillCapture, isZslCapture,
-            rotateAndCropAuto, cameraIdsWithZoom, requestTimeNs, outputSurfaces));
+            hasAppCallback, minExpectedDuration, maxExpectedDuration, physicalCameraIds,
+            isStillCapture, isZslCapture, rotateAndCropAuto, cameraIdsWithZoom, requestTimeNs,
+            outputSurfaces));
     if (res < 0) return res;
 
     if (mInFlightMap.size() == 1) {
@@ -3216,13 +3217,16 @@
     return true;
 }
 
-nsecs_t Camera3Device::RequestThread::calculateMaxExpectedDuration(const camera_metadata_t *request) {
-    nsecs_t maxExpectedDuration = kDefaultExpectedDuration;
+std::pair<nsecs_t, nsecs_t> Camera3Device::RequestThread::calculateExpectedDurationRange(
+        const camera_metadata_t *request) {
+    std::pair<nsecs_t, nsecs_t> expectedRange(
+            InFlightRequest::kDefaultMinExpectedDuration,
+            InFlightRequest::kDefaultMaxExpectedDuration);
     camera_metadata_ro_entry_t e = camera_metadata_ro_entry_t();
     find_camera_metadata_ro_entry(request,
             ANDROID_CONTROL_AE_MODE,
             &e);
-    if (e.count == 0) return maxExpectedDuration;
+    if (e.count == 0) return expectedRange;
 
     switch (e.data.u8[0]) {
         case ANDROID_CONTROL_AE_MODE_OFF:
@@ -3230,13 +3234,15 @@
                     ANDROID_SENSOR_EXPOSURE_TIME,
                     &e);
             if (e.count > 0) {
-                maxExpectedDuration = e.data.i64[0];
+                expectedRange.first = e.data.i64[0];
+                expectedRange.second = expectedRange.first;
             }
             find_camera_metadata_ro_entry(request,
                     ANDROID_SENSOR_FRAME_DURATION,
                     &e);
             if (e.count > 0) {
-                maxExpectedDuration = std::max(e.data.i64[0], maxExpectedDuration);
+                expectedRange.first = std::max(e.data.i64[0], expectedRange.first);
+                expectedRange.second = expectedRange.first;
             }
             break;
         default:
@@ -3244,12 +3250,13 @@
                     ANDROID_CONTROL_AE_TARGET_FPS_RANGE,
                     &e);
             if (e.count > 1) {
-                maxExpectedDuration = 1e9 / e.data.u8[0];
+                expectedRange.first = 1e9 / e.data.i32[1];
+                expectedRange.second = 1e9 / e.data.i32[0];
             }
             break;
     }
 
-    return maxExpectedDuration;
+    return expectedRange;
 }
 
 bool Camera3Device::RequestThread::skipHFRTargetFPSUpdate(int32_t tag,
@@ -3864,11 +3871,13 @@
                 isZslCapture = true;
             }
         }
+        auto expectedDurationRange = calculateExpectedDurationRange(settings);
         res = parent->registerInFlight(halRequest->frame_number,
                 totalNumBuffers, captureRequest->mResultExtras,
                 /*hasInput*/halRequest->input_buffer != NULL,
                 hasCallback,
-                calculateMaxExpectedDuration(settings),
+                /*min*/expectedDurationRange.first,
+                /*max*/expectedDurationRange.second,
                 requestedPhysicalCameras, isStillCapture, isZslCapture,
                 captureRequest->mRotateAndCropAuto, mPrevCameraIdsWithZoom,
                 (mUseHalBufManager) ? uniqueSurfaceIdMap :
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index c1ba88a..1a240c3 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -302,7 +302,6 @@
     static const nsecs_t       kMinWarnInflightDuration = 5000000000; // 5 s
     static const size_t        kInFlightWarnLimit = 30;
     static const size_t        kInFlightWarnLimitHighSpeed = 256; // batch size 32 * pipe depth 8
-    static const nsecs_t       kDefaultExpectedDuration = 100000000; // 100 ms
     static const nsecs_t       kMinInflightDuration = 5000000000; // 5 s
     static const nsecs_t       kBaseGetBufferWait = 3000000000; // 3 sec.
     // SCHED_FIFO priority for request submission thread in HFR mode
@@ -956,8 +955,9 @@
         // send request in mNextRequests to HAL in a batch. Return true = sucssess
         bool sendRequestsBatch();
 
-        // Calculate the expected maximum duration for a request
-        nsecs_t calculateMaxExpectedDuration(const camera_metadata_t *request);
+        // Calculate the expected (minimum, maximum) duration range for a request
+        std::pair<nsecs_t, nsecs_t> calculateExpectedDurationRange(
+                const camera_metadata_t *request);
 
         // Check and update latest session parameters based on the current request settings.
         bool updateSessionParameters(const CameraMetadata& settings);
@@ -1072,7 +1072,7 @@
 
     status_t registerInFlight(uint32_t frameNumber,
             int32_t numBuffers, CaptureResultExtras resultExtras, bool hasInput,
-            bool callback, nsecs_t maxExpectedDuration,
+            bool callback, nsecs_t minExpectedDuration, nsecs_t maxExpectedDuration,
             const std::set<std::set<String8>>& physicalCameraIds,
             bool isStillCapture, bool isZslCapture, bool rotateAndCropAuto,
             const std::set<std::string>& cameraIdsWithZoom, const SurfaceMap& outputSurfaces,
@@ -1323,6 +1323,9 @@
     // performance class.
     bool mOverrideForPerfClass;
 
+    // The current minimum expected frame duration based on AE_TARGET_FPS_RANGE
+    nsecs_t mMinExpectedDuration = 0;
+
     // Injection camera related methods.
     class Camera3DeviceInjectionMethods : public virtual RefBase {
       public:
diff --git a/services/camera/libcameraservice/device3/Camera3FakeStream.h b/services/camera/libcameraservice/device3/Camera3FakeStream.h
index 48e44dc..8cecabd 100644
--- a/services/camera/libcameraservice/device3/Camera3FakeStream.h
+++ b/services/camera/libcameraservice/device3/Camera3FakeStream.h
@@ -100,6 +100,7 @@
 
     virtual status_t setBatchSize(size_t batchSize) override;
 
+    virtual void onMinDurationChanged(nsecs_t /*duration*/) {}
   protected:
 
     /**
diff --git a/services/camera/libcameraservice/device3/Camera3OfflineSession.h b/services/camera/libcameraservice/device3/Camera3OfflineSession.h
index 0f7d145..a799719 100644
--- a/services/camera/libcameraservice/device3/Camera3OfflineSession.h
+++ b/services/camera/libcameraservice/device3/Camera3OfflineSession.h
@@ -246,6 +246,9 @@
     // For client methods such as disconnect/dump
     std::mutex mInterfaceLock;
 
+    // The current minimum expected frame duration based on AE_TARGET_FPS_RANGE
+    nsecs_t mMinExpectedDuration = 0;
+
     // SetErrorInterface
     void setErrorState(const char *fmt, ...) override;
     void setErrorStateLocked(const char *fmt, ...) override;
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStream.cpp b/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
index 0f61065..b822178 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
@@ -376,32 +376,26 @@
             dumpImageToDisk(timestamp, anwBuffer, anwReleaseFence);
         }
 
-        nsecs_t t = mPreviewFrameScheduler != nullptr ? readoutTimestamp : timestamp;
-        t -= mTimestampOffset;
-        if (mPreviewFrameScheduler != nullptr) {
-            res = mPreviewFrameScheduler->queuePreviewBuffer(t, transform,
-                    anwBuffer, anwReleaseFence);
-            if (res != OK) {
-                ALOGE("%s: Stream %d: Error queuing buffer to preview buffer scheduler: %s (%d)",
-                        __FUNCTION__, mId, strerror(-res), res);
-                return res;
-            }
-        } else {
-            setTransform(transform, true/*mayChangeMirror*/);
-            res = native_window_set_buffers_timestamp(mConsumer.get(), t);
-            if (res != OK) {
-                ALOGE("%s: Stream %d: Error setting timestamp: %s (%d)",
-                      __FUNCTION__, mId, strerror(-res), res);
-                return res;
-            }
+        nsecs_t captureTime = (mSyncToDisplay ? readoutTimestamp : timestamp) - mTimestampOffset;
+        nsecs_t presentTime = mSyncToDisplay ?
+                syncTimestampToDisplayLocked(captureTime) : captureTime;
+        mLastCaptureTime = captureTime;
+        mLastPresentTime = presentTime;
 
-            queueHDRMetadata(anwBuffer->handle, currentConsumer, dynamic_range_profile);
+        setTransform(transform, true/*mayChangeMirror*/);
+        res = native_window_set_buffers_timestamp(mConsumer.get(), presentTime);
+        if (res != OK) {
+            ALOGE("%s: Stream %d: Error setting timestamp: %s (%d)",
+                  __FUNCTION__, mId, strerror(-res), res);
+            return res;
+        }
 
-            res = queueBufferToConsumer(currentConsumer, anwBuffer, anwReleaseFence, surface_ids);
-            if (shouldLogError(res, state)) {
-                ALOGE("%s: Stream %d: Error queueing buffer to native window:"
-                      " %s (%d)", __FUNCTION__, mId, strerror(-res), res);
-            }
+        queueHDRMetadata(anwBuffer->handle, currentConsumer, dynamic_range_profile);
+
+        res = queueBufferToConsumer(currentConsumer, anwBuffer, anwReleaseFence, surface_ids);
+        if (shouldLogError(res, state)) {
+            ALOGE("%s: Stream %d: Error queueing buffer to native window:"
+                  " %s (%d)", __FUNCTION__, mId, strerror(-res), res);
         }
     }
     mLock.lock();
@@ -476,7 +470,7 @@
         return res;
     }
 
-    if ((res = configureConsumerQueueLocked(true /*allowPreviewScheduler*/)) != OK) {
+    if ((res = configureConsumerQueueLocked(true /*allowDisplaySync*/)) != OK) {
         return res;
     }
 
@@ -500,7 +494,7 @@
     return OK;
 }
 
-status_t Camera3OutputStream::configureConsumerQueueLocked(bool allowPreviewScheduler) {
+status_t Camera3OutputStream::configureConsumerQueueLocked(bool allowDisplaySync) {
     status_t res;
 
     mTraceFirstBuffer = true;
@@ -590,16 +584,17 @@
     int timestampBase = getTimestampBase();
     bool isDefaultTimeBase = (timestampBase ==
             OutputConfiguration::TIMESTAMP_BASE_DEFAULT);
-    if (allowPreviewScheduler)  {
+    if (allowDisplaySync)  {
         // We cannot distinguish between a SurfaceView and an ImageReader of
-        // preview buffer format. The PreviewFrameScheduler needs to handle both.
+        // preview buffer format. Frames are synchronized to display in both
+        // cases.
         bool forceChoreographer = (timestampBase ==
                 OutputConfiguration::TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED);
         bool defaultToChoreographer = (isDefaultTimeBase && isConsumedByHWComposer() &&
                 !property_get_bool("camera.disable_preview_scheduler", false));
         if (forceChoreographer || defaultToChoreographer) {
-            mPreviewFrameScheduler = std::make_unique<PreviewFrameScheduler>(*this, mConsumer);
-            mTotalBufferCount += PreviewFrameScheduler::kQueueDepthWatermark;
+            mSyncToDisplay = true;
+            mTotalBufferCount += kDisplaySyncExtraBuffer;
         }
     }
 
@@ -1244,6 +1239,11 @@
     return OK;
 }
 
+void Camera3OutputStream::onMinDurationChanged(nsecs_t duration) {
+    Mutex::Autolock l(mLock);
+    mMinExpectedDuration = duration;
+}
+
 void Camera3OutputStream::returnPrefetchedBuffersLocked() {
     std::vector<Surface::BatchBuffer> batchedBuffers;
 
@@ -1261,9 +1261,52 @@
     }
 }
 
-bool Camera3OutputStream::shouldLogError(status_t res) {
-    Mutex::Autolock l(mLock);
-    return shouldLogError(res, mState);
+nsecs_t Camera3OutputStream::syncTimestampToDisplayLocked(nsecs_t t) {
+    ParcelableVsyncEventData parcelableVsyncEventData;
+    auto res = mDisplayEventReceiver.getLatestVsyncEventData(&parcelableVsyncEventData);
+    if (res != OK) {
+        ALOGE("%s: Stream %d: Error getting latest vsync event data: %s (%d)",
+                __FUNCTION__, mId, strerror(-res), res);
+        return t;
+    }
+
+    const VsyncEventData& vsyncEventData = parcelableVsyncEventData.vsync;
+    nsecs_t currentTime = systemTime();
+
+    // Reset capture to present time offset if more than 1 second
+    // between frames.
+    if (t - mLastCaptureTime > kSpacingResetIntervalNs) {
+        for (size_t i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+            if (vsyncEventData.frameTimelines[i].deadlineTimestamp >= currentTime) {
+                mCaptureToPresentOffset =
+                    vsyncEventData.frameTimelines[i].expectedPresentationTime - t;
+                break;
+            }
+        }
+    }
+
+    nsecs_t idealPresentT = t + mCaptureToPresentOffset;
+    nsecs_t expectedPresentT = 0;
+    nsecs_t minDiff = INT64_MAX;
+    // Derive minimum intervals between presentation times based on minimal
+    // expected duration.
+    size_t minVsyncs = (mMinExpectedDuration + vsyncEventData.frameInterval - 1) /
+            vsyncEventData.frameInterval - 1;
+    nsecs_t minInterval = minVsyncs * vsyncEventData.frameInterval + kTimelineThresholdNs;
+    // Find best timestamp in the vsync timeline:
+    // - closest to the ideal present time,
+    // - deadline timestamp is greater than the current time, and
+    // - the candidate present time is at least minInterval in the future
+    //   compared to last present time.
+    for (const auto& vsyncTime : vsyncEventData.frameTimelines) {
+        if (std::abs(vsyncTime.expectedPresentationTime - idealPresentT) < minDiff &&
+                vsyncTime.deadlineTimestamp >= currentTime &&
+                vsyncTime.expectedPresentationTime > mLastPresentTime + minInterval) {
+            expectedPresentT = vsyncTime.expectedPresentationTime;
+            minDiff = std::abs(vsyncTime.expectedPresentationTime - idealPresentT);
+        }
+    }
+    return expectedPresentT;
 }
 
 }; // namespace camera3
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStream.h b/services/camera/libcameraservice/device3/Camera3OutputStream.h
index 7b12efc..6ea7ef7 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputStream.h
+++ b/services/camera/libcameraservice/device3/Camera3OutputStream.h
@@ -21,13 +21,13 @@
 #include <utils/RefBase.h>
 #include <gui/IProducerListener.h>
 #include <gui/Surface.h>
+#include <gui/DisplayEventReceiver.h>
 
 #include "utils/LatencyHistogram.h"
 #include "Camera3Stream.h"
 #include "Camera3IOStreamBase.h"
 #include "Camera3OutputStreamInterface.h"
 #include "Camera3BufferManager.h"
-#include "PreviewFrameScheduler.h"
 
 namespace android {
 
@@ -240,12 +240,16 @@
     virtual status_t setBatchSize(size_t batchSize = 1) override;
 
     /**
+     * Notify the stream on change of min frame durations.
+     */
+    virtual void onMinDurationChanged(nsecs_t duration) override;
+
+    /**
      * Apply ZSL related consumer usage quirk.
      */
     static void applyZSLUsageQuirk(int format, uint64_t *consumerUsage /*inout*/);
 
     void setImageDumpMask(int mask) { mImageDumpMask = mask; }
-    bool shouldLogError(status_t res);
 
   protected:
     Camera3OutputStream(int id, camera_stream_type_t type,
@@ -278,7 +282,7 @@
 
     status_t getEndpointUsageForSurface(uint64_t *usage,
             const sp<Surface>& surface) const;
-    status_t configureConsumerQueueLocked(bool allowPreviewScheduler);
+    status_t configureConsumerQueueLocked(bool allowDisplaySync);
 
     // Consumer as the output of camera HAL
     sp<Surface> mConsumer;
@@ -392,13 +396,24 @@
 
     void returnPrefetchedBuffersLocked();
 
+    // Synchronize camera timestamp to display, and the return value
+    // can be used as presentation timestamp
+    nsecs_t syncTimestampToDisplayLocked(nsecs_t t);
+
     static const int32_t kDequeueLatencyBinSize = 5; // in ms
     CameraLatencyHistogram mDequeueBufferLatency;
 
     int mImageDumpMask = 0;
 
-    // The preview stream scheduler for re-timing frames
-    std::unique_ptr<PreviewFrameScheduler> mPreviewFrameScheduler;
+    nsecs_t mMinExpectedDuration = 0;
+    bool mSyncToDisplay = false;
+    DisplayEventReceiver mDisplayEventReceiver;
+    nsecs_t mLastCaptureTime = 0;
+    nsecs_t mLastPresentTime = 0;
+    nsecs_t mCaptureToPresentOffset = 0;
+    static constexpr size_t kDisplaySyncExtraBuffer = 2;
+    static constexpr nsecs_t kSpacingResetIntervalNs = 1000000000LL; // 1 second
+    static constexpr nsecs_t kTimelineThresholdNs = 1000000LL; // 1 millisecond
 }; // class Camera3OutputStream
 
 } // namespace camera3
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h b/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h
index e44e795..a6d4b96 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h
+++ b/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h
@@ -108,6 +108,14 @@
      * instead.
      */
     virtual status_t setBatchSize(size_t batchSize = 1) = 0;
+
+    /**
+     * Notify the output stream that the minimum frame duration has changed.
+     *
+     * The minimum frame duration is calculated based on the upper bound of
+     * AE_TARGET_FPS_RANGE in the capture request.
+     */
+    virtual void onMinDurationChanged(nsecs_t duration) = 0;
 };
 
 // Helper class to organize a synchronized mapping of stream IDs to stream instances
diff --git a/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp b/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
index ab25322..ed66df0 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
+++ b/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
@@ -853,6 +853,13 @@
 
             r.shutterTimestamp = msg.timestamp;
             r.shutterReadoutTimestamp = msg.readout_timestamp;
+            if (r.minExpectedDuration != states.minFrameDuration) {
+                for (size_t i = 0; i < states.outputStreams.size(); i++) {
+                    auto outputStream = states.outputStreams[i];
+                    outputStream->onMinDurationChanged(r.minExpectedDuration);
+                }
+                states.minFrameDuration = r.minExpectedDuration;
+            }
             if (r.hasCallback) {
                 ALOGVV("Camera %s: %s: Shutter fired for frame %d (id %d) at %" PRId64,
                     states.cameraId.string(), __FUNCTION__,
diff --git a/services/camera/libcameraservice/device3/Camera3OutputUtils.h b/services/camera/libcameraservice/device3/Camera3OutputUtils.h
index 4d1eb75..dd01408 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputUtils.h
+++ b/services/camera/libcameraservice/device3/Camera3OutputUtils.h
@@ -107,6 +107,7 @@
         InflightRequestUpdateInterface& inflightIntf;
         BufferRecordsInterface& bufferRecordsIntf;
         bool legacyClient;
+        nsecs_t& minFrameDuration;
     };
 
     void processCaptureResult(CaptureOutputStates& states, const camera_capture_result *result);
diff --git a/services/camera/libcameraservice/device3/Camera3SharedOutputStream.cpp b/services/camera/libcameraservice/device3/Camera3SharedOutputStream.cpp
index c09a0b2..d24b527 100644
--- a/services/camera/libcameraservice/device3/Camera3SharedOutputStream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3SharedOutputStream.cpp
@@ -251,7 +251,7 @@
         return res;
     }
 
-    res = configureConsumerQueueLocked(false/*allowPreviewScheduler*/);
+    res = configureConsumerQueueLocked(false/*allowDisplaySync*/);
     if (res != OK) {
         ALOGE("Failed to configureConsumerQueueLocked: %s(%d)", strerror(-res), res);
         return res;
diff --git a/services/camera/libcameraservice/device3/InFlightRequest.h b/services/camera/libcameraservice/device3/InFlightRequest.h
index 0c97f3e..493a9e2 100644
--- a/services/camera/libcameraservice/device3/InFlightRequest.h
+++ b/services/camera/libcameraservice/device3/InFlightRequest.h
@@ -143,6 +143,11 @@
     // is not for constrained high speed recording, this flag will also be true.
     bool hasCallback;
 
+    // Minimum expected frame duration for this request
+    // For manual captures, equal to the max of requested exposure time and frame duration
+    // For auto-exposure modes, equal to 1/(higher end of target FPS range)
+    nsecs_t minExpectedDuration;
+
     // Maximum expected frame duration for this request.
     // For manual captures, equal to the max of requested exposure time and frame duration
     // For auto-exposure modes, equal to 1/(lower end of target FPS range)
@@ -187,8 +192,8 @@
     // Current output transformation
     int32_t transform;
 
-    // TODO: dedupe
-    static const nsecs_t kDefaultExpectedDuration = 100000000; // 100 ms
+    static const nsecs_t kDefaultMinExpectedDuration = 33333333; // 33 ms
+    static const nsecs_t kDefaultMaxExpectedDuration = 100000000; // 100 ms
 
     // Default constructor needed by KeyedVector
     InFlightRequest() :
@@ -199,7 +204,8 @@
             numBuffersLeft(0),
             hasInputBuffer(false),
             hasCallback(true),
-            maxExpectedDuration(kDefaultExpectedDuration),
+            minExpectedDuration(kDefaultMinExpectedDuration),
+            maxExpectedDuration(kDefaultMaxExpectedDuration),
             skipResultMetadata(false),
             errorBufStrategy(ERROR_BUF_CACHE),
             stillCapture(false),
@@ -210,7 +216,7 @@
     }
 
     InFlightRequest(int numBuffers, CaptureResultExtras extras, bool hasInput,
-            bool hasAppCallback, nsecs_t maxDuration,
+            bool hasAppCallback, nsecs_t minDuration, nsecs_t maxDuration,
             const std::set<std::set<String8>>& physicalCameraIdSet, bool isStillCapture,
             bool isZslCapture, bool rotateAndCropAuto, const std::set<std::string>& idsWithZoom,
             nsecs_t requestNs, const SurfaceMap& outSurfaces = SurfaceMap{}) :
@@ -222,6 +228,7 @@
             resultExtras(extras),
             hasInputBuffer(hasInput),
             hasCallback(hasAppCallback),
+            minExpectedDuration(minDuration),
             maxExpectedDuration(maxDuration),
             skipResultMetadata(false),
             errorBufStrategy(ERROR_BUF_CACHE),
diff --git a/services/camera/libcameraservice/device3/PreviewFrameScheduler.cpp b/services/camera/libcameraservice/device3/PreviewFrameScheduler.cpp
deleted file mode 100644
index 80f27ed..0000000
--- a/services/camera/libcameraservice/device3/PreviewFrameScheduler.cpp
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2021 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 "Camera3-PreviewFrameScheduler"
-#define ATRACE_TAG ATRACE_TAG_CAMERA
-//#define LOG_NDEBUG 0
-
-#include <utils/Log.h>
-#include <utils/Trace.h>
-
-#include <android/looper.h>
-#include "PreviewFrameScheduler.h"
-#include "Camera3OutputStream.h"
-
-namespace android {
-
-namespace camera3 {
-
-/**
- * Internal Choreographer thread implementation for polling and handling callbacks
- */
-
-// Callback function for Choreographer
-static void frameCallback(const AChoreographerFrameCallbackData* callbackData, void* data) {
-    PreviewFrameScheduler* parent = static_cast<PreviewFrameScheduler*>(data);
-    if (parent == nullptr) {
-        ALOGE("%s: Invalid data for Choreographer callback!", __FUNCTION__);
-        return;
-    }
-
-    size_t length = AChoreographerFrameCallbackData_getFrameTimelinesLength(callbackData);
-    std::vector<nsecs_t> timeline(length);
-    for (size_t i = 0; i < length; i++) {
-        nsecs_t timestamp = AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos(
-                callbackData, i);
-        timeline[i] = timestamp;
-    }
-
-    parent->onNewPresentationTime(timeline);
-
-    AChoreographer_postVsyncCallback(AChoreographer_getInstance(), frameCallback, data);
-}
-
-struct ChoreographerThread : public Thread {
-    ChoreographerThread();
-    status_t start(PreviewFrameScheduler* parent);
-    virtual status_t readyToRun() override;
-    virtual bool threadLoop() override;
-
-protected:
-    virtual ~ChoreographerThread() {}
-
-private:
-    ChoreographerThread &operator=(const ChoreographerThread &);
-
-    // This only impacts the shutdown time. It won't impact the choreographer
-    // callback frequency.
-    static constexpr nsecs_t kPollingTimeoutMs = 5;
-    PreviewFrameScheduler* mParent = nullptr;
-};
-
-ChoreographerThread::ChoreographerThread() : Thread(false /*canCallJava*/) {
-}
-
-status_t ChoreographerThread::start(PreviewFrameScheduler* parent) {
-    mParent = parent;
-    return run("PreviewChoreographer");
-}
-
-status_t ChoreographerThread::readyToRun() {
-    ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
-    if (AChoreographer_getInstance() == NULL) {
-        return NO_INIT;
-    }
-
-    AChoreographer_postVsyncCallback(
-            AChoreographer_getInstance(), frameCallback, mParent);
-    return OK;
-}
-
-bool ChoreographerThread::threadLoop() {
-    if (exitPending()) {
-        return false;
-    }
-    ALooper_pollOnce(kPollingTimeoutMs, nullptr, nullptr, nullptr);
-    return true;
-}
-
-/**
- * PreviewFrameScheduler implementation
- */
-
-PreviewFrameScheduler::PreviewFrameScheduler(Camera3OutputStream& parent, sp<Surface> consumer) :
-        mParent(parent),
-        mConsumer(consumer),
-        mChoreographerThread(new ChoreographerThread()) {
-}
-
-PreviewFrameScheduler::~PreviewFrameScheduler() {
-    {
-        Mutex::Autolock l(mLock);
-        mChoreographerThread->requestExit();
-    }
-    mChoreographerThread->join();
-}
-
-status_t PreviewFrameScheduler::queuePreviewBuffer(nsecs_t timestamp, int32_t transform,
-        ANativeWindowBuffer* anwBuffer, int releaseFence) {
-    // Start choreographer thread if it's not already running.
-    if (!mChoreographerThread->isRunning()) {
-        status_t res = mChoreographerThread->start(this);
-        if (res != OK) {
-            ALOGE("%s: Failed to init choreographer thread!", __FUNCTION__);
-            return res;
-        }
-    }
-
-    {
-        Mutex::Autolock l(mLock);
-        mPendingBuffers.emplace(timestamp, transform, anwBuffer, releaseFence);
-
-        // Queue buffer to client right away if pending buffers are more than
-        // the queue depth watermark.
-        if (mPendingBuffers.size() > kQueueDepthWatermark) {
-            auto oldBuffer = mPendingBuffers.front();
-            mPendingBuffers.pop();
-
-            status_t res = queueBufferToClientLocked(oldBuffer, oldBuffer.timestamp);
-            if (res != OK) {
-                return res;
-            }
-
-            // Reset the last capture and presentation time
-            mLastCameraCaptureTime = 0;
-            mLastCameraPresentTime = 0;
-        } else {
-            ATRACE_INT(kPendingBufferTraceName, mPendingBuffers.size());
-        }
-    }
-    return OK;
-}
-
-void PreviewFrameScheduler::onNewPresentationTime(const std::vector<nsecs_t>& timeline) {
-    ATRACE_CALL();
-    Mutex::Autolock l(mLock);
-    if (mPendingBuffers.size() > 0) {
-        auto nextBuffer = mPendingBuffers.front();
-        mPendingBuffers.pop();
-
-        // Find the best presentation time by finding the element in the
-        // choreographer timeline that's closest to the ideal presentation time.
-        // The ideal presentation time is the last presentation time + frame
-        // interval.
-        nsecs_t cameraInterval = nextBuffer.timestamp - mLastCameraCaptureTime;
-        nsecs_t idealPresentTime = (cameraInterval < kSpacingResetIntervalNs) ?
-                (mLastCameraPresentTime + cameraInterval) : nextBuffer.timestamp;
-        nsecs_t presentTime = *std::min_element(timeline.begin(), timeline.end(),
-                [idealPresentTime](nsecs_t p1, nsecs_t p2) {
-                        return std::abs(p1 - idealPresentTime) < std::abs(p2 - idealPresentTime);
-                });
-
-        status_t res = queueBufferToClientLocked(nextBuffer, presentTime);
-        ATRACE_INT(kPendingBufferTraceName, mPendingBuffers.size());
-
-        if (mParent.shouldLogError(res)) {
-            ALOGE("%s: Preview Stream: Error queueing buffer to native window:"
-                    " %s (%d)", __FUNCTION__, strerror(-res), res);
-        }
-
-        mLastCameraCaptureTime = nextBuffer.timestamp;
-        mLastCameraPresentTime = presentTime;
-    }
-}
-
-status_t PreviewFrameScheduler::queueBufferToClientLocked(
-        const BufferHolder& bufferHolder, nsecs_t timestamp) {
-    mParent.setTransform(bufferHolder.transform, true/*mayChangeMirror*/);
-
-    status_t res = native_window_set_buffers_timestamp(mConsumer.get(), timestamp);
-    if (res != OK) {
-        ALOGE("%s: Preview Stream: Error setting timestamp: %s (%d)",
-                __FUNCTION__, strerror(-res), res);
-        return res;
-    }
-
-    Camera3Stream::queueHDRMetadata(bufferHolder.anwBuffer.get()->handle, mConsumer,
-            mParent.getDynamicRangeProfile());
-
-    res = mConsumer->queueBuffer(mConsumer.get(), bufferHolder.anwBuffer.get(),
-            bufferHolder.releaseFence);
-    if (res != OK) {
-        close(bufferHolder.releaseFence);
-    }
-
-    return res;
-}
-
-}; // namespace camera3
-
-}; // namespace android
diff --git a/services/camera/libcameraservice/device3/PreviewFrameScheduler.h b/services/camera/libcameraservice/device3/PreviewFrameScheduler.h
deleted file mode 100644
index c0574fd..0000000
--- a/services/camera/libcameraservice/device3/PreviewFrameScheduler.h
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-#ifndef ANDROID_SERVERS_CAMERA_CAMERA3_PREVIEWFRAMESCHEDULER_H
-#define ANDROID_SERVERS_CAMERA_CAMERA3_PREVIEWFRAMESCHEDULER_H
-
-#include <queue>
-
-#include <android/choreographer.h>
-#include <gui/Surface.h>
-#include <gui/ISurfaceComposer.h>
-#include <utils/Condition.h>
-#include <utils/Mutex.h>
-#include <utils/Looper.h>
-#include <utils/Thread.h>
-#include <utils/Timers.h>
-
-namespace android {
-
-namespace camera3 {
-
-class Camera3OutputStream;
-struct ChoreographerThread;
-
-/***
- * Preview stream scheduler for better preview display synchronization
- *
- * The ideal viewfinder user experience is that frames are presented to the
- * user in the same cadence as outputed by the camera sensor. However, the
- * processing latency between frames could vary, due to factors such
- * as CPU load, differences in request settings, etc. This frame processing
- * latency results in variation in presentation of frames to the user.
- *
- * The PreviewFrameScheduler improves the viewfinder user experience by:
- * 1. Cache preview buffers in the scheduler
- * 2. For each choreographer callback, queue the oldest cached buffer with
- *    the best matching presentation timestamp. Frame N's presentation timestamp
- *    is the choreographer timeline timestamp closest to (Frame N-1's
- *    presentation time + camera capture interval between frame N-1 and frame N).
- * 3. Maintain at most 2 queue-able buffers. If the 3rd preview buffer becomes
- *    available, queue the oldest cached buffer to the buffer queue.
- */
-class PreviewFrameScheduler {
-  public:
-    explicit PreviewFrameScheduler(Camera3OutputStream& parent, sp<Surface> consumer);
-    virtual ~PreviewFrameScheduler();
-
-    // Queue preview buffer locally
-    status_t queuePreviewBuffer(nsecs_t timestamp, int32_t transform,
-            ANativeWindowBuffer* anwBuffer, int releaseFence);
-
-    // Callback function with a new presentation timeline from choreographer. This
-    // will trigger a locally queued buffer be sent to the buffer queue.
-    void onNewPresentationTime(const std::vector<nsecs_t>& presentationTimeline);
-
-    // Maintain at most 2 queue-able buffers
-    static constexpr int32_t kQueueDepthWatermark = 2;
-
-  private:
-    // structure holding cached preview buffer info
-    struct BufferHolder {
-        nsecs_t timestamp;
-        int32_t transform;
-        sp<ANativeWindowBuffer> anwBuffer;
-        int releaseFence;
-
-        BufferHolder(nsecs_t t, int32_t tr, ANativeWindowBuffer* anwb, int rf) :
-                timestamp(t), transform(tr), anwBuffer(anwb), releaseFence(rf) {}
-    };
-
-    status_t queueBufferToClientLocked(const BufferHolder& bufferHolder,
-            nsecs_t presentTime);
-
-    static constexpr char kPendingBufferTraceName[] = "pending_preview_buffers";
-
-    // Camera capture interval for resetting frame spacing between preview sessions
-    static constexpr nsecs_t kSpacingResetIntervalNs = 1000000000L; // 1 second
-
-    Camera3OutputStream& mParent;
-    sp<ANativeWindow> mConsumer;
-    mutable Mutex mLock;
-
-    std::queue<BufferHolder> mPendingBuffers;
-    nsecs_t mLastCameraCaptureTime = 0;
-    nsecs_t mLastCameraPresentTime = 0;
-
-    // Choreographer related
-    sp<Looper> mLooper;
-    sp<ChoreographerThread> mChoreographerThread;
-};
-
-}; //namespace camera3
-}; //namespace android
-
-#endif
diff --git a/services/camera/libcameraservice/device3/aidl/AidlCamera3Device.cpp b/services/camera/libcameraservice/device3/aidl/AidlCamera3Device.cpp
index 1add032..f05520f 100644
--- a/services/camera/libcameraservice/device3/aidl/AidlCamera3Device.cpp
+++ b/services/camera/libcameraservice/device3/aidl/AidlCamera3Device.cpp
@@ -371,7 +371,7 @@
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
         mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
         mTagMonitor, mInputStream, mOutputStreams, mSessionStatsBuilder, listener, *this,
-        *this, *(mInterface), mLegacyClient}, mResultMetadataQueue
+        *this, *(mInterface), mLegacyClient, mMinExpectedDuration}, mResultMetadataQueue
     };
 
     for (const auto& result : results) {
@@ -412,7 +412,7 @@
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
         mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
         mTagMonitor, mInputStream, mOutputStreams, mSessionStatsBuilder, listener, *this,
-        *this, *(mInterface), mLegacyClient}, mResultMetadataQueue
+        *this, *(mInterface), mLegacyClient, mMinExpectedDuration}, mResultMetadataQueue
     };
     for (const auto& msg : msgs) {
         camera3::notify(states, msg);
diff --git a/services/camera/libcameraservice/device3/aidl/AidlCamera3OfflineSession.cpp b/services/camera/libcameraservice/device3/aidl/AidlCamera3OfflineSession.cpp
index 895ce56..336719d 100644
--- a/services/camera/libcameraservice/device3/aidl/AidlCamera3OfflineSession.cpp
+++ b/services/camera/libcameraservice/device3/aidl/AidlCamera3OfflineSession.cpp
@@ -123,7 +123,7 @@
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
         mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
         mTagMonitor, mInputStream, mOutputStreams, mSessionStatsBuilder, listener, *this,
-        *this, mBufferRecords, /*legacyClient*/ false}, mResultMetadataQueue
+        *this, mBufferRecords, /*legacyClient*/ false, mMinExpectedDuration}, mResultMetadataQueue
     };
 
     std::lock_guard<std::mutex> lock(mProcessCaptureResultLock);
@@ -168,7 +168,7 @@
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
         mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
         mTagMonitor, mInputStream, mOutputStreams, mSessionStatsBuilder, listener, *this,
-        *this, mBufferRecords, /*legacyClient*/ false}, mResultMetadataQueue
+        *this, mBufferRecords, /*legacyClient*/ false, mMinExpectedDuration}, mResultMetadataQueue
     };
     for (const auto& msg : msgs) {
         camera3::notify(states, msg);
diff --git a/services/camera/libcameraservice/device3/aidl/AidlCamera3OfflineSession.h b/services/camera/libcameraservice/device3/aidl/AidlCamera3OfflineSession.h
index ad4a480..33de2c5 100644
--- a/services/camera/libcameraservice/device3/aidl/AidlCamera3OfflineSession.h
+++ b/services/camera/libcameraservice/device3/aidl/AidlCamera3OfflineSession.h
@@ -75,7 +75,7 @@
     // See explanation for why we need a separate class for this in
     // AidlCamera3Device::AidlCameraDeviceCallbacks in AidlCamera3Device.h
     class AidlCameraDeviceCallbacks :
-            virtual public aidl::android::hardware::camera::device::BnCameraDeviceCallback {
+            public aidl::android::hardware::camera::device::BnCameraDeviceCallback {
       public:
 
         AidlCameraDeviceCallbacks(wp<AidlCamera3OfflineSession> parent) : mParent(parent)  { }
@@ -112,7 +112,9 @@
                     offlineSession) :
       Camera3OfflineSession(id, inputStream, offlineStreamSet, std::move(bufferRecords),
               offlineReqs, offlineStates),
-      mSession(offlineSession) { mCallbacks = std::make_shared<AidlCameraDeviceCallbacks>(this);};
+      mSession(offlineSession) {
+        mCallbacks = ndk::SharedRefBase::make<AidlCameraDeviceCallbacks>(this);
+      };
 
     /**
      * End of CameraOfflineSessionBase interface
diff --git a/services/camera/libcameraservice/device3/hidl/HidlCamera3Device.cpp b/services/camera/libcameraservice/device3/hidl/HidlCamera3Device.cpp
index 87d3ee8..cf6d462 100644
--- a/services/camera/libcameraservice/device3/hidl/HidlCamera3Device.cpp
+++ b/services/camera/libcameraservice/device3/hidl/HidlCamera3Device.cpp
@@ -369,7 +369,7 @@
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
         mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
         mTagMonitor, mInputStream, mOutputStreams, mSessionStatsBuilder, listener, *this, *this,
-        *mInterface, mLegacyClient}, mResultMetadataQueue
+        *mInterface, mLegacyClient, mMinExpectedDuration}, mResultMetadataQueue
     };
 
     //HidlCaptureOutputStates hidlStates {
@@ -431,7 +431,7 @@
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
         mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
         mTagMonitor, mInputStream, mOutputStreams, mSessionStatsBuilder, listener, *this, *this,
-        *mInterface, mLegacyClient}, mResultMetadataQueue
+        *mInterface, mLegacyClient, mMinExpectedDuration}, mResultMetadataQueue
     };
 
     for (const auto& result : results) {
@@ -483,7 +483,7 @@
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
         mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
         mTagMonitor, mInputStream, mOutputStreams, mSessionStatsBuilder, listener, *this, *this,
-        *mInterface, mLegacyClient}, mResultMetadataQueue
+        *mInterface, mLegacyClient, mMinExpectedDuration}, mResultMetadataQueue
     };
     for (const auto& msg : msgs) {
         camera3::notify(states, msg);
diff --git a/services/camera/libcameraservice/device3/hidl/HidlCamera3OfflineSession.cpp b/services/camera/libcameraservice/device3/hidl/HidlCamera3OfflineSession.cpp
index d517c8d..5c97f0e 100644
--- a/services/camera/libcameraservice/device3/hidl/HidlCamera3OfflineSession.cpp
+++ b/services/camera/libcameraservice/device3/hidl/HidlCamera3OfflineSession.cpp
@@ -105,7 +105,7 @@
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
         mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
         mTagMonitor, mInputStream, mOutputStreams, mSessionStatsBuilder, listener, *this, *this,
-        mBufferRecords, /*legacyClient*/ false}, mResultMetadataQueue
+        mBufferRecords, /*legacyClient*/ false, mMinExpectedDuration}, mResultMetadataQueue
     };
 
     std::lock_guard<std::mutex> lock(mProcessCaptureResultLock);
@@ -145,7 +145,7 @@
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
         mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
         mTagMonitor, mInputStream, mOutputStreams, mSessionStatsBuilder, listener, *this, *this,
-        mBufferRecords, /*legacyClient*/ false}, mResultMetadataQueue
+        mBufferRecords, /*legacyClient*/ false, mMinExpectedDuration}, mResultMetadataQueue
     };
 
     std::lock_guard<std::mutex> lock(mProcessCaptureResultLock);
@@ -180,7 +180,7 @@
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
         mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
         mTagMonitor, mInputStream, mOutputStreams, mSessionStatsBuilder, listener, *this, *this,
-        mBufferRecords, /*legacyClient*/ false}, mResultMetadataQueue
+        mBufferRecords, /*legacyClient*/ false, mMinExpectedDuration}, mResultMetadataQueue
     };
     for (const auto& msg : msgs) {
         camera3::notify(states, msg);
diff --git a/services/camera/libcameraservice/tests/PreviewSchedulerTest.cpp b/services/camera/libcameraservice/tests/PreviewSchedulerTest.cpp
deleted file mode 100644
index 025521a..0000000
--- a/services/camera/libcameraservice/tests/PreviewSchedulerTest.cpp
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2021 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_NDEBUG 0
-#define LOG_TAG "PreviewSchedulerTest"
-
-#include <chrono>
-#include <thread>
-#include <utility>
-
-#include <gtest/gtest.h>
-#include <utils/Errors.h>
-#include <utils/Log.h>
-#include <utils/Mutex.h>
-
-#include <gui/BufferItemConsumer.h>
-#include <gui/BufferQueue.h>
-#include <gui/IGraphicBufferProducer.h>
-#include <gui/IGraphicBufferConsumer.h>
-#include <gui/Surface.h>
-
-#include "../device3/Camera3OutputStream.h"
-#include "../device3/PreviewFrameScheduler.h"
-
-using namespace android;
-using namespace android::camera3;
-
-// Consumer buffer available listener
-class SimpleListener : public BufferItemConsumer::FrameAvailableListener {
-public:
-    SimpleListener(size_t frameCount): mFrameCount(frameCount) {}
-
-    void waitForFrames() {
-        Mutex::Autolock lock(mMutex);
-        while (mFrameCount > 0) {
-            mCondition.wait(mMutex);
-        }
-    }
-
-    void onFrameAvailable(const BufferItem& /*item*/) override {
-        Mutex::Autolock lock(mMutex);
-        if (mFrameCount > 0) {
-            mFrameCount--;
-            mCondition.signal();
-        }
-    }
-
-    void reset(size_t frameCount) {
-        Mutex::Autolock lock(mMutex);
-        mFrameCount = frameCount;
-    }
-private:
-    size_t mFrameCount;
-    Mutex mMutex;
-    Condition mCondition;
-};
-
-// Test the PreviewFrameScheduler functionatliy of re-timing buffers
-TEST(PreviewSchedulerTest, BasicPreviewSchedulerTest) {
-    const int ID = 0;
-    const int FORMAT = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
-    const uint32_t WIDTH = 640;
-    const uint32_t HEIGHT = 480;
-    const int32_t TRANSFORM = 0;
-    const nsecs_t T_OFFSET = 0;
-    const android_dataspace DATASPACE = HAL_DATASPACE_UNKNOWN;
-    const camera_stream_rotation_t ROTATION = CAMERA_STREAM_ROTATION_0;
-    const String8 PHY_ID;
-    const std::unordered_set<int32_t> PIX_MODES;
-    const int BUFFER_COUNT = 4;
-    const int TOTAL_BUFFER_COUNT = BUFFER_COUNT * 2;
-
-    // Create buffer queue
-    sp<IGraphicBufferProducer> producer;
-    sp<IGraphicBufferConsumer> consumer;
-    BufferQueue::createBufferQueue(&producer, &consumer);
-    ASSERT_NE(producer, nullptr);
-    ASSERT_NE(consumer, nullptr);
-    ASSERT_EQ(NO_ERROR, consumer->setDefaultBufferSize(WIDTH, HEIGHT));
-
-    // Set up consumer
-    sp<BufferItemConsumer> bufferConsumer = new BufferItemConsumer(consumer,
-            GRALLOC_USAGE_HW_COMPOSER, BUFFER_COUNT);
-    ASSERT_NE(bufferConsumer, nullptr);
-    sp<SimpleListener> consumerListener = new SimpleListener(BUFFER_COUNT);
-    bufferConsumer->setFrameAvailableListener(consumerListener);
-
-    // Set up producer
-    sp<Surface> surface = new Surface(producer);
-    sp<StubProducerListener> listener = new StubProducerListener();
-    ASSERT_EQ(NO_ERROR, surface->connect(NATIVE_WINDOW_API_CPU, listener));
-    sp<ANativeWindow> anw(surface);
-    ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(anw.get(), TOTAL_BUFFER_COUNT));
-
-    // Create Camera3OutputStream and PreviewFrameScheduler
-    sp<Camera3OutputStream> stream = new Camera3OutputStream(ID, surface, WIDTH, HEIGHT,
-            FORMAT, DATASPACE, ROTATION, T_OFFSET, PHY_ID, PIX_MODES);
-    ASSERT_NE(stream, nullptr);
-    std::unique_ptr<PreviewFrameScheduler> scheduler =
-            std::make_unique<PreviewFrameScheduler>(*stream, surface);
-    ASSERT_NE(scheduler, nullptr);
-
-    // The pair of nsecs_t: camera timestamp delta (negative means in the past) and frame interval
-    const std::pair<nsecs_t, nsecs_t> inputTimestamps[][BUFFER_COUNT] = {
-        // 30fps, 33ms interval
-        {{-100000000LL, 33333333LL}, {-66666667LL, 33333333LL},
-          {-33333333LL, 33333333LL}, {0, 0}},
-        // 30fps, variable interval
-        {{-100000000LL, 16666667LL}, {-66666667LL, 33333333LL},
-          {-33333333LL, 50000000LL}, {0, 0}},
-        // 60fps, 16.7ms interval
-        {{-50000000LL, 16666667LL}, {-33333333LL, 16666667LL},
-          {-16666667LL, 16666667LL}, {0, 0}},
-        // 60fps, variable interval
-        {{-50000000LL, 8666667LL}, {-33333333LL, 19666667LL},
-          {-16666667LL, 20666667LL}, {0, 0}},
-    };
-
-    // Go through different use cases, and check the buffer timestamp
-    size_t iterations = sizeof(inputTimestamps)/sizeof(inputTimestamps[0]);
-    for (size_t i = 0; i < iterations; i++) {
-        // Space out different test sets to reset the frame scheduler
-        nsecs_t timeBase = systemTime() - s2ns(1) * (iterations - i);
-        nsecs_t lastQueueTime = 0;
-        nsecs_t duration = 0;
-        for (size_t j = 0; j < BUFFER_COUNT; j++) {
-            ANativeWindowBuffer* buffer = nullptr;
-            int fenceFd;
-            ASSERT_EQ(NO_ERROR, anw->dequeueBuffer(anw.get(), &buffer, &fenceFd));
-
-            // Sleep to space out queuePreviewBuffer
-            nsecs_t currentTime = systemTime();
-            if (duration > 0 && duration > currentTime - lastQueueTime) {
-                std::this_thread::sleep_for(
-                        std::chrono::nanoseconds(duration + lastQueueTime - currentTime));
-            }
-            nsecs_t timestamp = timeBase + inputTimestamps[i][j].first;
-            ASSERT_EQ(NO_ERROR,
-                    scheduler->queuePreviewBuffer(timestamp, TRANSFORM, buffer, fenceFd));
-
-            lastQueueTime = systemTime();
-            duration = inputTimestamps[i][j].second;
-        }
-
-        // Collect output timestamps, making sure they are either set by
-        // producer, or set by the scheduler.
-        consumerListener->waitForFrames();
-        nsecs_t outputTimestamps[BUFFER_COUNT];
-        for (size_t j = 0; j < BUFFER_COUNT; j++) {
-            BufferItem bufferItem;
-            ASSERT_EQ(NO_ERROR, bufferConsumer->acquireBuffer(&bufferItem, 0/*presentWhen*/));
-
-            outputTimestamps[j] = bufferItem.mTimestamp;
-            ALOGV("%s: [%zu][%zu]: input: %" PRId64 ", output: %" PRId64, __FUNCTION__,
-                  i, j, timeBase + inputTimestamps[i][j].first, bufferItem.mTimestamp);
-            ASSERT_GT(bufferItem.mTimestamp, inputTimestamps[i][j].first);
-
-            ASSERT_EQ(NO_ERROR, bufferConsumer->releaseBuffer(bufferItem));
-        }
-
-        // Check the output timestamp intervals are aligned with input intervals
-        const nsecs_t SHIFT_THRESHOLD = ms2ns(2);
-        for (size_t j = 0; j < BUFFER_COUNT - 1; j ++) {
-            nsecs_t interval_shift = outputTimestamps[j+1] - outputTimestamps[j] -
-                    (inputTimestamps[i][j+1].first - inputTimestamps[i][j].first);
-            ASSERT_LE(std::abs(interval_shift), SHIFT_THRESHOLD);
-        }
-
-        consumerListener->reset(BUFFER_COUNT);
-    }
-
-    // Disconnect the surface
-    ASSERT_EQ(NO_ERROR, surface->disconnect(NATIVE_WINDOW_API_CPU));
-}
diff --git a/services/mediametrics/AudioAnalytics.cpp b/services/mediametrics/AudioAnalytics.cpp
index aacc2be..99e3691 100644
--- a/services/mediametrics/AudioAnalytics.cpp
+++ b/services/mediametrics/AudioAnalytics.cpp
@@ -297,33 +297,35 @@
     ALOGD("%s", __func__);
 
     // Add action to save AnalyticsState if audioserver is restarted.
-    // This triggers on an item of "audio.flinger"
-    // with a property "event" set to "AudioFlinger" (the constructor).
+    // This triggers on AudioFlinger or AudioPolicy ctors and onFirstRef,
+    // as well as TimeCheck events.
     mActions.addAction(
         AMEDIAMETRICS_KEY_AUDIO_FLINGER "." AMEDIAMETRICS_PROP_EVENT,
         std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR),
         std::make_shared<AnalyticsActions::Function>(
             [this](const std::shared_ptr<const android::mediametrics::Item> &item){
-                ALOGW("(key=%s) Audioflinger constructor event detected", item->getKey().c_str());
-                mPreviousAnalyticsState.set(std::make_shared<AnalyticsState>(
-                        *mAnalyticsState.get()));
-                // Note: get returns shared_ptr temp, whose lifetime is extended
-                // to end of full expression.
-                mAnalyticsState->clear();  // TODO: filter the analytics state.
-                // Perhaps report this.
-
-                // Set up a timer to expire the previous audio state to save space.
-                // Use the transaction log size as a cookie to see if it is the
-                // same as before.  A benign race is possible where a state is cleared early.
-                const size_t size = mPreviousAnalyticsState->transactionLog().size();
-                mTimedAction.postIn(
-                        std::chrono::seconds(PREVIOUS_STATE_EXPIRE_SEC), [this, size](){
-                    if (mPreviousAnalyticsState->transactionLog().size() == size) {
-                        ALOGD("expiring previous audio state after %d seconds.",
-                                PREVIOUS_STATE_EXPIRE_SEC);
-                        mPreviousAnalyticsState->clear();  // removes data from the state.
-                    }
-                });
+                mHealth.onAudioServerStart(Health::Module::AUDIOFLINGER, item);
+            }));
+    mActions.addAction(
+        AMEDIAMETRICS_KEY_AUDIO_POLICY "." AMEDIAMETRICS_PROP_EVENT,
+        std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR),
+        std::make_shared<AnalyticsActions::Function>(
+            [this](const std::shared_ptr<const android::mediametrics::Item> &item){
+                mHealth.onAudioServerStart(Health::Module::AUDIOPOLICY, item);
+            }));
+    mActions.addAction(
+        AMEDIAMETRICS_KEY_AUDIO_FLINGER "." AMEDIAMETRICS_PROP_EVENT,
+        std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_TIMEOUT),
+        std::make_shared<AnalyticsActions::Function>(
+            [this](const std::shared_ptr<const android::mediametrics::Item> &item){
+                mHealth.onAudioServerTimeout(Health::Module::AUDIOFLINGER, item);
+            }));
+    mActions.addAction(
+        AMEDIAMETRICS_KEY_AUDIO_POLICY "." AMEDIAMETRICS_PROP_EVENT,
+        std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_TIMEOUT),
+        std::make_shared<AnalyticsActions::Function>(
+            [this](const std::shared_ptr<const android::mediametrics::Item> &item){
+                mHealth.onAudioServerTimeout(Health::Module::AUDIOPOLICY, item);
             }));
 
     // Handle legacy aaudio playback stream statistics
@@ -1390,4 +1392,138 @@
     }
 }
 
+// Create new state, typically occurs after an AudioFlinger ctor event.
+void AudioAnalytics::newState()
+{
+    mPreviousAnalyticsState.set(std::make_shared<AnalyticsState>(
+            *mAnalyticsState.get()));
+    // Note: get returns shared_ptr temp, whose lifetime is extended
+    // to end of full expression.
+    mAnalyticsState->clear();  // TODO: filter the analytics state.
+    // Perhaps report this.
+
+    // Set up a timer to expire the previous audio state to save space.
+    // Use the transaction log size as a cookie to see if it is the
+    // same as before.  A benign race is possible where a state is cleared early.
+    const size_t size = mPreviousAnalyticsState->transactionLog().size();
+    mTimedAction.postIn(
+            std::chrono::seconds(PREVIOUS_STATE_EXPIRE_SEC), [this, size](){
+        if (mPreviousAnalyticsState->transactionLog().size() == size) {
+            ALOGD("expiring previous audio state after %d seconds.",
+                    PREVIOUS_STATE_EXPIRE_SEC);
+            mPreviousAnalyticsState->clear();  // removes data from the state.
+        }
+    });
+}
+
+void AudioAnalytics::Health::onAudioServerStart(Module module,
+        const std::shared_ptr<const android::mediametrics::Item> &item)
+{
+    const auto nowTime = std::chrono::system_clock::now();
+    if (module == Module::AUDIOFLINGER) {
+       {
+            std::lock_guard lg(mLock);
+            // reset state on AudioFlinger construction.
+            // AudioPolicy is created after AudioFlinger.
+            mAudioFlingerCtorTime = nowTime;
+            mSimpleLog.log("AudioFlinger ctor");
+        }
+        mAudioAnalytics.newState();
+        return;
+    }
+    if (module == Module::AUDIOPOLICY) {
+        // A start event occurs when audioserver
+        //
+        // (1) Starts the first time
+        // (2) Restarts because of the TimeCheck watchdog
+        // (3) Restarts not because of the TimeCheck watchdog.
+        int64_t executionTimeNs = 0;
+        (void)item->get(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, &executionTimeNs);
+        const float loadTimeMs = executionTimeNs * 1e-6f;
+        std::lock_guard lg(mLock);
+        const int64_t restarts = mStartCount;
+        if (mStopCount == mStartCount) {
+            mAudioPolicyCtorTime = nowTime;
+            ++mStartCount;
+            if (mStopCount == 0) {
+                // (1) First time initialization.
+                ALOGW("%s: (key=%s) AudioPolicy ctor, loadTimeMs:%f",
+                        __func__, item->getKey().c_str(), loadTimeMs);
+                mSimpleLog.log("AudioPolicy ctor, loadTimeMs:%f", loadTimeMs);
+            } else {
+                // (2) Previous failure caught due to TimeCheck.  We know how long restart takes.
+                const float restartMs =
+                        std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(
+                                mAudioFlingerCtorTime - mStopTime).count();
+                ALOGW("%s: (key=%s) AudioPolicy ctor, "
+                        "restarts:%lld restartMs:%f loadTimeMs:%f",
+                        __func__, item->getKey().c_str(),
+                        (long long)restarts, restartMs, loadTimeMs);
+                mSimpleLog.log("AudioPolicy ctor restarts:%lld restartMs:%f loadTimeMs:%f",
+                        (long long)restarts, restartMs, loadTimeMs);
+            }
+        } else {
+            // (3) Previous failure is NOT due to TimeCheck, so we don't know the restart time.
+            // However we can estimate the uptime from the delta time from previous ctor.
+            const float uptimeMs =
+                    std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(
+                            nowTime - mAudioFlingerCtorTime).count();
+            mStopCount = mStartCount;
+            mAudioPolicyCtorTime = nowTime;
+            ++mStartCount;
+
+            ALOGW("%s: (key=%s) AudioPolicy ctor after uncaught failure, "
+                    "mStartCount:%lld mStopCount:%lld uptimeMs:%f loadTimeMs:%f",
+                    __func__, item->getKey().c_str(),
+                    (long long)mStartCount, (long long)mStopCount, uptimeMs, loadTimeMs);
+            mSimpleLog.log("AudioPolicy ctor after uncaught failure, "
+                    "restarts:%lld uptimeMs:%f loadTimeMs:%f",
+                    (long long)restarts, uptimeMs, loadTimeMs);
+        }
+    }
+}
+
+void AudioAnalytics::Health::onAudioServerTimeout(Module module,
+        const std::shared_ptr<const android::mediametrics::Item> &item)
+{
+    std::string moduleName = getModuleName(module);
+    int64_t methodCode{};
+    std::string methodName;
+    (void)item->get(AMEDIAMETRICS_PROP_METHODCODE, &methodCode);
+    (void)item->get(AMEDIAMETRICS_PROP_METHODNAME, &methodName);
+
+    std::lock_guard lg(mLock);
+    if (mStopCount >= mStartCount) {
+        ALOGD("%s: (key=%s) %s timeout %s(%lld) "
+            "unmatched mStopCount(%lld) >= mStartCount(%lld), ignoring",
+            __func__, item->getKey().c_str(), moduleName.c_str(),
+            methodName.c_str(), (long long)methodCode,
+            (long long)mStopCount, (long long)mStartCount);
+        return;
+    }
+
+    const int64_t restarts = mStartCount - 1;
+    ++mStopCount;
+    mStopTime = std::chrono::system_clock::now();
+    const float uptimeMs = std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(
+            mStopTime - mAudioFlingerCtorTime).count();
+    ALOGW("%s: (key=%s) %s timeout %s(%lld) restarts:%lld uptimeMs:%f",
+         __func__, item->getKey().c_str(), moduleName.c_str(),
+         methodName.c_str(), (long long)methodCode,
+         (long long)restarts, uptimeMs);
+    mSimpleLog.log("%s timeout %s(%lld) restarts:%lld uptimeMs:%f",
+            moduleName.c_str(), methodName.c_str(), (long long)methodCode,
+            (long long)restarts, uptimeMs);
+}
+
+std::pair<std::string, int32_t> AudioAnalytics::Health::dump(
+        int32_t lines, const char *prefix) const
+{
+    std::lock_guard lg(mLock);
+    std::string s = mSimpleLog.dumpToString(prefix == nullptr ? "" : prefix, lines);
+    size_t n = std::count(s.begin(), s.end(), '\n');
+    return { s, n };
+}
+
+
 } // namespace android::mediametrics
diff --git a/services/mediametrics/MediaMetricsService.cpp b/services/mediametrics/MediaMetricsService.cpp
index 636b343..ff16b9e 100644
--- a/services/mediametrics/MediaMetricsService.cpp
+++ b/services/mediametrics/MediaMetricsService.cpp
@@ -327,6 +327,15 @@
                 result << "-- some lines may be truncated --\n";
             }
 
+            const int32_t healthLinesToDump = all ? INT32_MAX : 15;
+            result << "\nHealth Message Log:";
+            const auto [ healthDumpString, healthLines ] =
+                    mAudioAnalytics.dumpHealth(healthLinesToDump);
+            result << "\n" << healthDumpString;
+            if (healthLines == healthLinesToDump) {
+                result << "-- some lines may be truncated --\n";
+            }
+
             result << "\nLogSessionId:\n"
                    << mediametrics::ValidateId::get()->dump();
 
diff --git a/services/mediametrics/include/mediametricsservice/AudioAnalytics.h b/services/mediametrics/include/mediametricsservice/AudioAnalytics.h
index a44fcc1..5ee8c30 100644
--- a/services/mediametrics/include/mediametricsservice/AudioAnalytics.h
+++ b/services/mediametrics/include/mediametricsservice/AudioAnalytics.h
@@ -83,6 +83,15 @@
         return mHeatMap.dump(lines);
     }
 
+    /**
+     * Returns a pair consisting of the dump string and the number of lines in the string.
+     *
+     * Health dump.
+     */
+    std::pair<std::string, int32_t> dumpHealth(int32_t lines = INT32_MAX) const {
+        return mHealth.dump(lines);
+    }
+
     void clear() {
         // underlying state is locked.
         mPreviousAnalyticsState->clear();
@@ -247,6 +256,67 @@
         AudioAnalytics &mAudioAnalytics;
     } mAAudioStreamInfo{*this};
 
+    // Create new state, typically occurs after an AudioFlinger ctor event.
+    void newState();
+
+    // Health is a nested class that tracks audioserver health properties
+    class Health {
+    public:
+        explicit Health(AudioAnalytics &audioAnalytics)
+            : mAudioAnalytics(audioAnalytics) {}
+
+        enum class Module {
+            AUDIOFLINGER,
+            AUDIOPOLICY,
+        };
+
+        const char *getModuleName(Module module) {
+            switch (module) {
+                case Module::AUDIOFLINGER: return "AudioFlinger";
+                case Module::AUDIOPOLICY: return "AudioPolicy";
+            }
+            return "Unknown";
+        }
+
+        // Called when we believe audioserver starts (AudioFlinger ctor)
+        void onAudioServerStart(Module module,
+                const std::shared_ptr<const android::mediametrics::Item> &item);
+
+        // Called when we believe audioserver crashes (TimeCheck timeouts).
+        void onAudioServerTimeout(Module module,
+                const std::shared_ptr<const android::mediametrics::Item> &item);
+
+        std::pair<std::string, int32_t> dump(
+                int32_t lines = INT32_MAX, const char *prefix = nullptr) const;
+
+    private:
+        AudioAnalytics& mAudioAnalytics;
+
+        mutable std::mutex mLock;
+
+        // Life cycle of AudioServer
+        // mAudioFlingerCtorTime
+        // mAudioPolicyCtorTime
+        // mAudioPolicyCtorDoneTime
+        // ...
+        // possibly mStopTime  (if TimeCheck thread)
+        //
+        // UpTime is measured from mStopTime - mAudioFlingerCtorTime.
+        //
+        // The stop events come from TimeCheck timeout aborts.  There may be other
+        // uncaught signals, e.g. SIGSEGV, that cause missing stop events.
+        std::chrono::system_clock::time_point mAudioFlingerCtorTime GUARDED_BY(mLock);
+        std::chrono::system_clock::time_point mAudioPolicyCtorTime GUARDED_BY(mLock);
+        std::chrono::system_clock::time_point mAudioPolicyCtorDoneTime GUARDED_BY(mLock);
+        std::chrono::system_clock::time_point mStopTime GUARDED_BY(mLock);
+
+        // mStartCount and mStopCount track the audioserver start and stop events.
+        int64_t mStartCount GUARDED_BY(mLock) = 0;
+        int64_t mStopCount GUARDED_BY(mLock) = 0;
+
+        SimpleLog mSimpleLog GUARDED_BY(mLock) {64};
+    } mHealth{*this};
+
     AudioPowerUsage mAudioPowerUsage;
 };
 
diff --git a/services/mediaresourcemanager/ResourceManagerService.cpp b/services/mediaresourcemanager/ResourceManagerService.cpp
index 57be435..1c09544 100644
--- a/services/mediaresourcemanager/ResourceManagerService.cpp
+++ b/services/mediaresourcemanager/ResourceManagerService.cpp
@@ -696,11 +696,11 @@
         if (clients.size() == 0) {
             // if we are here, run the fourth pass to free one codec with the different type.
             if (secureCodec != NULL) {
-                MediaResource temp(MediaResource::Type::kNonSecureCodec, 1);
+                MediaResource temp(MediaResource::Type::kNonSecureCodec, secureCodec->subType, 1);
                 getClientForResource_l(callingPid, &temp, &clients);
             }
             if (nonSecureCodec != NULL) {
-                MediaResource temp(MediaResource::Type::kSecureCodec, 1);
+                MediaResource temp(MediaResource::Type::kSecureCodec, nonSecureCodec->subType, 1);
                 getClientForResource_l(callingPid, &temp, &clients);
             }
         }