Merge "media: Add avic_OWNERS" am: 8db7b98158 am: b58ec7163b am: 736b990ac2 am: 070e306e8e

Original change: https://android-review.googlesource.com/c/platform/frameworks/av/+/2215506

Change-Id: Iebaec49c28c2e41e43a5a8ce3d0326bb79e3795a
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/camera/aidl/android/hardware/ICameraServiceProxy.aidl b/camera/aidl/android/hardware/ICameraServiceProxy.aidl
index 88783fb..fefea13 100644
--- a/camera/aidl/android/hardware/ICameraServiceProxy.aidl
+++ b/camera/aidl/android/hardware/ICameraServiceProxy.aidl
@@ -48,5 +48,5 @@
     /**
      * Checks if the camera has been disabled via device policy.
      */
-    boolean isCameraDisabled();
+    boolean isCameraDisabled(int userId);
 }
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index 35205e5..a22ec19 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -41,8 +41,51 @@
         {
             "path": "frameworks/av/drm/mediadrm/plugins"
         }
+    ],
+
+    "platinum-postsubmit": [
+        // runs regularly, independent of changes in this tree.
+        // signals if changes elsewhere break media functionality
+        // @FlakyTest: in staged-postsubmit, but not postsubmit
+        {
+            "name": "CtsMediaCodecTestCases",
+            "options": [
+                {
+                    "include-filter": "android.media.codec.cts.EncodeDecodeTest"
+                }
+            ]
+        },
+        {
+            "name": "CtsMediaCodecTestCases",
+            "options": [
+                {
+                    "include-filter": "android.media.codec.cts.DecodeEditEncodeTest"
+                },
+                {
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
+                }
+            ]
+        }
+    ],
+
+    "staged-platinum-postsubmit": [
+        // runs every four hours
+        {
+            "name": "CtsMediaCodecTestCases",
+            "options": [
+                {
+                    "include-filter": "android.media.codec.cts.EncodeDecodeTest"
+                }
+            ]
+        },
+        {
+            "name": "CtsMediaCodecTestCases",
+            "options": [
+                {
+                    "include-filter": "android.media.codec.cts.DecodeEditEncodeTest"
+                }
+            ]
+        }
     ]
 
-    // TODO (b/229286407) Add EncodeDecodeTest and DecodeEditEncodeTest to
-    // platinum-postsubmit once issues in cuttlefish are fixed
 }
diff --git a/media/codec2/sfplugin/CCodec.cpp b/media/codec2/sfplugin/CCodec.cpp
index 296d7ed..124a893 100644
--- a/media/codec2/sfplugin/CCodec.cpp
+++ b/media/codec2/sfplugin/CCodec.cpp
@@ -1899,6 +1899,7 @@
         comp = state->comp;
     }
     status_t err = comp->stop();
+    mChannel->stopUseOutputSurface();
     if (err != C2_OK) {
         // TODO: convert err into status_t
         mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
@@ -1972,6 +1973,7 @@
     }
 
     mChannel->reset();
+    mChannel->stopUseOutputSurface();
     // thiz holds strong ref to this while the thread is running.
     sp<CCodec> thiz(this);
     std::thread([thiz, sendCallback] { thiz->release(sendCallback); }).detach();
@@ -2139,7 +2141,8 @@
 
     std::map<size_t, sp<MediaCodecBuffer>> clientInputBuffers;
     status_t err = mChannel->prepareInitialInputBuffers(&clientInputBuffers);
-    if (err != OK) {
+    // FIXME(b/237656746)
+    if (err != OK && err != NO_MEMORY) {
         ALOGE("Resume request for Input Buffers failed");
         mCallback->onError(err, ACTION_CODE_FATAL);
         return;
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.cpp b/media/codec2/sfplugin/CCodecBufferChannel.cpp
index 3ca263f..4bf8dce 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.cpp
+++ b/media/codec2/sfplugin/CCodecBufferChannel.cpp
@@ -1578,6 +1578,17 @@
     mFirstValidFrameIndex = mFrameIndex.load(std::memory_order_relaxed);
 }
 
+void CCodecBufferChannel::stopUseOutputSurface() {
+    if (mOutputSurface.lock()->surface) {
+        C2BlockPool::local_id_t outputPoolId;
+        {
+            Mutexed<BlockPools>::Locked pools(mBlockPools);
+            outputPoolId = pools->outputPoolId;
+        }
+        if (mComponent) mComponent->stopUsingOutputSurface(outputPoolId);
+    }
+}
+
 void CCodecBufferChannel::reset() {
     stop();
     if (mInputSurface != nullptr) {
@@ -1593,14 +1604,6 @@
         Mutexed<Output>::Locked output(mOutput);
         output->buffers.reset();
     }
-    if (mOutputSurface.lock()->surface) {
-        C2BlockPool::local_id_t outputPoolId;
-        {
-            Mutexed<BlockPools>::Locked pools(mBlockPools);
-            outputPoolId = pools->outputPoolId;
-        }
-        mComponent->stopUsingOutputSurface(outputPoolId);
-    }
 }
 
 void CCodecBufferChannel::release() {
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.h b/media/codec2/sfplugin/CCodecBufferChannel.h
index f29a225..61fb06f 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.h
+++ b/media/codec2/sfplugin/CCodecBufferChannel.h
@@ -149,6 +149,12 @@
             std::map<size_t, sp<MediaCodecBuffer>> &&clientInputBuffers);
 
     /**
+     * Stop using buffers of the current output surface for other Codec
+     * instances to use the surface safely.
+     */
+    void stopUseOutputSurface();
+
+    /**
      * Stop queueing buffers to the component. This object should never queue
      * buffers after this call, until start() is called.
      */
diff --git a/media/libaudioclient/AudioEffect.cpp b/media/libaudioclient/AudioEffect.cpp
index 641c4d9..2870c4c 100644
--- a/media/libaudioclient/AudioEffect.cpp
+++ b/media/libaudioclient/AudioEffect.cpp
@@ -568,7 +568,6 @@
     if (cb != nullptr) {
         cb->onError(mStatus);
     }
-    mIEffect.clear();
 }
 
 // -------------------------------------------------------------------------
diff --git a/media/libaudiohal/impl/EffectsFactoryHalHidl.cpp b/media/libaudiohal/impl/EffectsFactoryHalHidl.cpp
index d7217fc..3470380 100644
--- a/media/libaudiohal/impl/EffectsFactoryHalHidl.cpp
+++ b/media/libaudiohal/impl/EffectsFactoryHalHidl.cpp
@@ -17,6 +17,9 @@
 #define LOG_TAG "EffectsFactoryHalHidl"
 //#define LOG_NDEBUG 0
 
+#include <optional>
+#include <tuple>
+
 #include <cutils/native_handle.h>
 
 #include <UuidUtils.h>
@@ -38,51 +41,71 @@
 using namespace ::android::hardware::audio::common::CPP_VERSION;
 using namespace ::android::hardware::audio::effect::CPP_VERSION;
 
+class EffectDescriptorCache {
+  public:
+    using QueryResult = std::tuple<Return<void>, Result, hidl_vec<EffectDescriptor>>;
+    QueryResult queryAllDescriptors(IEffectsFactory* effectsFactory);
+  private:
+    std::mutex mLock;
+    std::optional<hidl_vec<EffectDescriptor>> mLastDescriptors;  // GUARDED_BY(mLock)
+};
+
+EffectDescriptorCache::QueryResult EffectDescriptorCache::queryAllDescriptors(
+        IEffectsFactory* effectsFactory) {
+    {
+        std::lock_guard l(mLock);
+        if (mLastDescriptors.has_value()) {
+            return {::android::hardware::Void(), Result::OK, mLastDescriptors.value()};
+        }
+    }
+    Result retval = Result::NOT_INITIALIZED;
+    hidl_vec<EffectDescriptor> descriptors;
+    Return<void> ret = effectsFactory->getAllDescriptors(
+            [&](Result r, const hidl_vec<EffectDescriptor>& result) {
+                retval = r;
+                if (retval == Result::OK) {
+                    descriptors = result;
+                }
+            });
+    if (ret.isOk() && retval == Result::OK) {
+        std::lock_guard l(mLock);
+        mLastDescriptors = descriptors;
+    }
+    return {std::move(ret), retval, std::move(descriptors)};
+}
+
 EffectsFactoryHalHidl::EffectsFactoryHalHidl(sp<IEffectsFactory> effectsFactory)
-        : EffectConversionHelperHidl("EffectsFactory") {
+        : EffectConversionHelperHidl("EffectsFactory"), mCache(new EffectDescriptorCache) {
     ALOG_ASSERT(effectsFactory != nullptr, "Provided IEffectsFactory service is NULL");
     mEffectsFactory = effectsFactory;
 }
 
-status_t EffectsFactoryHalHidl::queryAllDescriptors() {
-    if (mEffectsFactory == 0) return NO_INIT;
-    Result retval = Result::NOT_INITIALIZED;
-    Return<void> ret = mEffectsFactory->getAllDescriptors(
-            [&](Result r, const hidl_vec<EffectDescriptor>& result) {
-                retval = r;
-                if (retval == Result::OK) {
-                    mLastDescriptors = result;
-                }
-            });
-    if (ret.isOk()) {
-        return retval == Result::OK ? OK : NO_INIT;
-    }
-    mLastDescriptors.resize(0);
-    return processReturn(__FUNCTION__, ret);
-}
-
 status_t EffectsFactoryHalHidl::queryNumberEffects(uint32_t *pNumEffects) {
-    status_t queryResult = queryAllDescriptors();
-    if (queryResult == OK) {
-        *pNumEffects = mLastDescriptors.size();
+    if (mEffectsFactory == 0) return NO_INIT;
+    auto [ret, retval, descriptors] = mCache->queryAllDescriptors(mEffectsFactory.get());
+    if (ret.isOk() && retval == Result::OK) {
+        *pNumEffects = descriptors.size();
+        return OK;
+    } else if (ret.isOk()) {
+        return NO_INIT;
     }
-    return queryResult;
+    return processReturn(__FUNCTION__, ret);
 }
 
 status_t EffectsFactoryHalHidl::getDescriptor(
         uint32_t index, effect_descriptor_t *pDescriptor) {
-    // TODO: We need somehow to track the changes on the server side
-    // or figure out how to convert everybody to query all the descriptors at once.
     if (pDescriptor == nullptr) {
         return BAD_VALUE;
     }
-    if (mLastDescriptors.size() == 0) {
-        status_t queryResult = queryAllDescriptors();
-        if (queryResult != OK) return queryResult;
+    if (mEffectsFactory == 0) return NO_INIT;
+    auto [ret, retval, descriptors] = mCache->queryAllDescriptors(mEffectsFactory.get());
+    if (ret.isOk() && retval == Result::OK) {
+        if (index >= descriptors.size()) return NAME_NOT_FOUND;
+        EffectUtils::effectDescriptorToHal(descriptors[index], pDescriptor);
+    } else if (ret.isOk()) {
+        return NO_INIT;
     }
-    if (index >= mLastDescriptors.size()) return NAME_NOT_FOUND;
-    EffectUtils::effectDescriptorToHal(mLastDescriptors[index], pDescriptor);
-    return OK;
+    return processReturn(__FUNCTION__, ret);
 }
 
 status_t EffectsFactoryHalHidl::getDescriptor(
@@ -114,21 +137,15 @@
     if (pEffectType == nullptr || descriptors == nullptr) {
         return BAD_VALUE;
     }
+    if (mEffectsFactory == 0) return NO_INIT;
 
-    uint32_t numEffects = 0;
-    status_t status = queryNumberEffects(&numEffects);
-    if (status != NO_ERROR) {
-        ALOGW("%s error %d from FactoryHal queryNumberEffects", __func__, status);
-        return status;
+    auto [ret, retval, hidlDescs] = mCache->queryAllDescriptors(mEffectsFactory.get());
+    if (!ret.isOk() || retval != Result::OK) {
+        return processReturn(__FUNCTION__, ret, retval);
     }
-
-    for (uint32_t i = 0; i < numEffects; i++) {
+    for (const auto& hidlDesc : hidlDescs) {
         effect_descriptor_t descriptor;
-        status = getDescriptor(i, &descriptor);
-        if (status != NO_ERROR) {
-            ALOGW("%s error %d from FactoryHal getDescriptor", __func__, status);
-            continue;
-        }
+        EffectUtils::effectDescriptorToHal(hidlDesc, &descriptor);
         if (memcmp(&descriptor.type, pEffectType, sizeof(effect_uuid_t)) == 0) {
             descriptors->push_back(descriptor);
         }
diff --git a/media/libaudiohal/impl/EffectsFactoryHalHidl.h b/media/libaudiohal/impl/EffectsFactoryHalHidl.h
index e1882e1..7ccddc6 100644
--- a/media/libaudiohal/impl/EffectsFactoryHalHidl.h
+++ b/media/libaudiohal/impl/EffectsFactoryHalHidl.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H
 #define ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H
 
+#include <memory>
+
 #include PATH(android/hardware/audio/effect/FILE_VERSION/IEffectsFactory.h)
 #include <media/audiohal/EffectsFactoryHalInterface.h>
 
@@ -28,6 +30,8 @@
 using ::android::hardware::hidl_vec;
 using namespace ::android::hardware::audio::effect::CPP_VERSION;
 
+class EffectDescriptorCache;
+
 class EffectsFactoryHalHidl : public EffectsFactoryHalInterface, public EffectConversionHelperHidl
 {
   public:
@@ -63,9 +67,7 @@
 
   private:
     sp<IEffectsFactory> mEffectsFactory;
-    hidl_vec<EffectDescriptor> mLastDescriptors;
-
-    status_t queryAllDescriptors();
+    std::unique_ptr<EffectDescriptorCache> mCache;
 };
 
 } // namespace effect
diff --git a/media/libheadtracking/HeadTrackingProcessor.cpp b/media/libheadtracking/HeadTrackingProcessor.cpp
index 2fc9ff9..101b825 100644
--- a/media/libheadtracking/HeadTrackingProcessor.cpp
+++ b/media/libheadtracking/HeadTrackingProcessor.cpp
@@ -88,10 +88,12 @@
     }
 
     void calculate(int64_t timestamp) override {
-        // Handle the screen first, since it might trigger a recentering of the head.
+        bool screenStable = true;
+
+        // Handle the screen first, since it might: trigger a recentering of the head.
         if (mWorldToScreenTimestamp.has_value()) {
             const Pose3f worldToLogicalScreen = mScreenPoseBias.getOutput();
-            bool screenStable = mScreenStillnessDetector.calculate(timestamp);
+            screenStable = mScreenStillnessDetector.calculate(timestamp);
             mModeSelector.setScreenStable(mWorldToScreenTimestamp.value(), screenStable);
             // Whenever the screen is unstable, recenter the head pose.
             if (!screenStable) {
@@ -105,7 +107,8 @@
         if (mWorldToHeadTimestamp.has_value()) {
             Pose3f worldToHead = mHeadPoseBias.getOutput();
             // Auto-recenter.
-            if (mHeadStillnessDetector.calculate(timestamp)) {
+            bool headStable = mHeadStillnessDetector.calculate(timestamp);
+            if (headStable || !screenStable) {
                 recenter(true, false);
                 worldToHead = mHeadPoseBias.getOutput();
             }
diff --git a/media/libheadtracking/StillnessDetector.cpp b/media/libheadtracking/StillnessDetector.cpp
index be7c893..5232084 100644
--- a/media/libheadtracking/StillnessDetector.cpp
+++ b/media/libheadtracking/StillnessDetector.cpp
@@ -26,6 +26,9 @@
     mFifo.clear();
     mWindowFull = false;
     mSuppressionDeadline.reset();
+    // A "true" state indicates stillness is detected (default = true)
+    mCurrentState = true;
+    mPreviousState = true;
 }
 
 void StillnessDetector::setInput(int64_t timestamp, const Pose3f& input) {
@@ -33,7 +36,15 @@
     discardOld(timestamp);
 }
 
+bool StillnessDetector::getPreviousState() const {
+    return mPreviousState;
+}
+
 bool StillnessDetector::calculate(int64_t timestamp) {
+    // Move the current stillness state to the previous state.
+    // This allows us to detect transitions into and out of stillness.
+    mPreviousState = mCurrentState;
+
     discardOld(timestamp);
 
     // Check whether all the poses in the queue are in the proximity of the new one. We want to do
@@ -60,15 +71,17 @@
 
     // If the window has not been full, return the default value.
     if (!mWindowFull) {
-        return mOptions.defaultValue;
+        mCurrentState = mOptions.defaultValue;
     }
-
     // Force "in motion" while the suppression deadline is active.
-    if (mSuppressionDeadline.has_value()) {
-        return false;
+    else if (mSuppressionDeadline.has_value()) {
+        mCurrentState = false;
+    }
+    else {
+        mCurrentState = !moved;
     }
 
-    return !moved;
+    return mCurrentState;
 }
 
 void StillnessDetector::discardOld(int64_t timestamp) {
diff --git a/media/libheadtracking/StillnessDetector.h b/media/libheadtracking/StillnessDetector.h
index ee4b2d8..abd7cc4 100644
--- a/media/libheadtracking/StillnessDetector.h
+++ b/media/libheadtracking/StillnessDetector.h
@@ -80,7 +80,8 @@
     void setInput(int64_t timestamp, const Pose3f& input);
     /** Calculate whether the stream is still at the given timestamp. */
     bool calculate(int64_t timestamp);
-
+    /** Return the stillness state from the previous call to calculate() */
+    bool getPreviousState() const;
   private:
     struct TimestampedPose {
         int64_t timestamp;
@@ -92,6 +93,8 @@
     const float mCosHalfRotationalThreshold;
     std::deque<TimestampedPose> mFifo;
     bool mWindowFull = false;
+    bool mCurrentState = true;
+    bool mPreviousState = true;
     // As soon as motion is detected, this will be set for the time of detection + window duration,
     // and during this time we will always consider outselves in motion without checking. This is
     // used for hyteresis purposes, since because of the approximate method we use for determining
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index ea1fdf4..a0bc8ca 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -1966,6 +1966,10 @@
             format->setString("mime", MEDIA_MIMETYPE_VIDEO_HEVC);
             break;
 
+        case VIDEO_ENCODER_DOLBY_VISION:
+            format->setString("mime", MEDIA_MIMETYPE_VIDEO_DOLBY_VISION);
+            break;
+
         default:
             CHECK(!"Should not be here, unsupported video encoding.");
             break;
diff --git a/media/libmediaplayerservice/nuplayer/AWakeLock.cpp b/media/libmediaplayerservice/nuplayer/AWakeLock.cpp
index c3bd207..25a8ae4 100644
--- a/media/libmediaplayerservice/nuplayer/AWakeLock.cpp
+++ b/media/libmediaplayerservice/nuplayer/AWakeLock.cpp
@@ -59,11 +59,10 @@
         if (mPowerManager != NULL) {
             sp<IBinder> binder = new BBinder();
             int64_t token = IPCThreadState::self()->clearCallingIdentity();
-            binder::Status status = mPowerManager->acquireWakeLock(
+            binder::Status status = mPowerManager->acquireWakeLockAsync(
                     binder, POWERMANAGER_PARTIAL_WAKE_LOCK,
                     String16("AWakeLock"), String16("media"),
-                    {} /* workSource */, {} /* historyTag */, -1 /* displayId */,
-                    nullptr /* callback */);
+                    {} /* workSource */, {} /* historyTag */);
             IPCThreadState::self()->restoreCallingIdentity(token);
             if (status.isOk()) {
                 mWakeLockToken = binder;
diff --git a/media/libstagefright/HevcUtils.cpp b/media/libstagefright/HevcUtils.cpp
index 5f9c20e..60df162 100644
--- a/media/libstagefright/HevcUtils.cpp
+++ b/media/libstagefright/HevcUtils.cpp
@@ -102,10 +102,11 @@
 static bool findParam(uint32_t key, T *param,
         KeyedVector<uint32_t, uint64_t> &params) {
     CHECK(param);
-    if (params.indexOfKey(key) < 0) {
+    ssize_t index = params.indexOfKey(key);
+    if (index < 0) {
         return false;
     }
-    *param = (T) params[key];
+    *param = (T) params[index];
     return true;
 }
 
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index e50880a..c963e19 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -3534,6 +3534,17 @@
                     }
                     setState(STARTED);
                     postPendingRepliesAndDeferredMessages("kWhatStartCompleted");
+
+                    // Now that the codec has started, configure, by default, the peek behavior to
+                    // be undefined for backwards compatibility with older releases. Later, if an
+                    // app explicitly enables or disables peek, the parameter will be turned off and
+                    // the legacy undefined behavior is disallowed.
+                    // See updateTunnelPeek called in onSetParameters for more details.
+                    if (mTunneled && mTunnelPeekState == TunnelPeekState::kLegacyMode) {
+                        sp<AMessage> params = new AMessage;
+                        params->setInt32("android._tunnel-peek-set-legacy", 1);
+                        mCodec->signalSetParameters(params);
+                    }
                     break;
                 }
 
@@ -3973,14 +3984,6 @@
                 mTunneled = false;
             }
 
-            // If mTunnelPeekState is still in kLegacyMode at this point,
-            // configure the codec in legacy mode
-            if (mTunneled && (mTunnelPeekState == TunnelPeekState::kLegacyMode)) {
-                sp<AMessage> params = new AMessage;
-                params->setInt32("android._tunnel-peek-set-legacy", 1);
-                onSetParameters(params);
-            }
-
             int32_t background = 0;
             if (format->findInt32("android._background-mode", &background) && background) {
                 androidSetThreadPriority(gettid(), ANDROID_PRIORITY_BACKGROUND);
diff --git a/media/ndk/NdkMediaCodec.cpp b/media/ndk/NdkMediaCodec.cpp
index 38e422d..ed31c02 100644
--- a/media/ndk/NdkMediaCodec.cpp
+++ b/media/ndk/NdkMediaCodec.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <charconv>
 #include <inttypes.h>
 #include <mutex>
 #include <set>
@@ -324,29 +325,61 @@
             }
 
             AMessage::Type type;
-            int64_t mediaTimeUs, systemNano;
-            size_t index = 0;
+            size_t n = data->countEntries();
 
-            // TODO. This code has dependency with MediaCodec::CreateFramesRenderedMessage.
-            for (size_t ix = 0; ix < data->countEntries(); ix++) {
-                AString name = data->getEntryNameAt(ix, &type);
-                if (name.startsWith(AStringPrintf("%zu-media-time-us", index).c_str())) {
-                    AMessage::ItemData data = msg->getEntryAt(index);
-                    data.find(&mediaTimeUs);
-                } else if (name.startsWith(AStringPrintf("%zu-system-nano", index).c_str())) {
-                    AMessage::ItemData data = msg->getEntryAt(index);
-                    data.find(&systemNano);
+            thread_local std::vector<std::optional<int64_t>> mediaTimesInUs;
+            thread_local std::vector<std::optional<int64_t>> systemTimesInNs;
+            mediaTimesInUs.resize(n);
+            systemTimesInNs.resize(n);
+            std::fill_n(mediaTimesInUs.begin(), n, std::nullopt);
+            std::fill_n(systemTimesInNs.begin(), n, std::nullopt);
+            for (size_t i = 0; i < n; i++) {
+                AString name = data->getEntryNameAt(i, &type);
+                if (name.endsWith("-media-time-us")) {
+                    int64_t mediaTimeUs;
+                    AMessage::ItemData itemData = data->getEntryAt(i);
+                    itemData.find(&mediaTimeUs);
 
-                    Mutex::Autolock _l(mCodec->mFrameRenderedCallbackLock);
-                    if (mCodec->mFrameRenderedCallback != NULL) {
+                    int index = -1;
+                    std::from_chars_result result = std::from_chars(
+                            name.c_str(), name.c_str() + name.find("-"), index);
+                    if (result.ec == std::errc() && 0 <= index && index < n) {
+                        mediaTimesInUs[index] = mediaTimeUs;
+                    } else {
+                        std::error_code ec = std::make_error_code(result.ec);
+                        ALOGE("Unexpected media time index: #%d with value %lldus (err=%d %s)",
+                              index, (long long)mediaTimeUs, ec.value(), ec.message().c_str());
+                    }
+                } else if (name.endsWith("-system-nano")) {
+                    int64_t systemNano;
+                    AMessage::ItemData itemData = data->getEntryAt(i);
+                    itemData.find(&systemNano);
+
+                    int index = -1;
+                    std::from_chars_result result = std::from_chars(
+                            name.c_str(), name.c_str() + name.find("-"), index);
+                    if (result.ec == std::errc() && 0 <= index && index < n) {
+                        systemTimesInNs[index] = systemNano;
+                    } else {
+                        std::error_code ec = std::make_error_code(result.ec);
+                        ALOGE("Unexpected system time index: #%d with value %lldns (err=%d %s)",
+                              index, (long long)systemNano, ec.value(), ec.message().c_str());
+                    }
+                }
+            }
+
+            Mutex::Autolock _l(mCodec->mFrameRenderedCallbackLock);
+            if (mCodec->mFrameRenderedCallback != NULL) {
+                for (size_t i = 0; i < n; ++i) {
+                    if (mediaTimesInUs[i] && systemTimesInNs[i]) {
                         mCodec->mFrameRenderedCallback(
                                 mCodec,
                                 mCodec->mFrameRenderedCallbackUserData,
-                                mediaTimeUs,
-                                systemNano);
+                                mediaTimesInUs[i].value(),
+                                systemTimesInNs[i].value());
+                    } else {
+                        break;
                     }
-
-                    index++;
                 }
             }
             break;
@@ -529,22 +562,31 @@
         AMediaCodecOnAsyncNotifyCallback callback,
         void *userdata) {
 
-    Mutex::Autolock _l(mData->mAsyncCallbackLock);
-
-    if (mData->mAsyncNotify == NULL) {
-        mData->mAsyncNotify = new AMessage(kWhatAsyncNotify, mData->mHandler);
+    {
+        Mutex::Autolock _l(mData->mAsyncCallbackLock);
+        if (mData->mAsyncNotify == NULL) {
+            mData->mAsyncNotify = new AMessage(kWhatAsyncNotify, mData->mHandler);
+        }
+        // we set this ahead so that we can be ready
+        // to receive callbacks as soon as the next call is a
+        // success.
+        mData->mAsyncCallback = callback;
+        mData->mAsyncCallbackUserData = userdata;
     }
 
     // always call, codec may have been reset/re-configured since last call.
     status_t err = mData->mCodec->setCallback(mData->mAsyncNotify);
     if (err != OK) {
+        {
+            //The setup gone wrong. clean up the pointers.
+            Mutex::Autolock _l(mData->mAsyncCallbackLock);
+            mData->mAsyncCallback = {};
+            mData->mAsyncCallbackUserData = nullptr;
+        }
         ALOGE("setAsyncNotifyCallback: err(%d), failed to set async callback", err);
         return translate_error(err);
     }
 
-    mData->mAsyncCallback = callback;
-    mData->mAsyncCallbackUserData = userdata;
-
     return AMEDIA_OK;
 }
 
diff --git a/media/utils/MethodStatistics.cpp b/media/utils/MethodStatistics.cpp
index b179b20..086757b 100644
--- a/media/utils/MethodStatistics.cpp
+++ b/media/utils/MethodStatistics.cpp
@@ -22,7 +22,8 @@
 
 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 {
+    static const std::map<std::string, std::shared_ptr<std::vector<std::string>>,
+            std::less<> /* transparent comparator */> m {
         {
             METHOD_STATISTICS_MODULE_NAME_AUDIO_HIDL,
             std::shared_ptr<std::vector<std::string>>(
@@ -34,13 +35,14 @@
               })
         },
     };
-    auto it = m.find({moduleName.begin(), moduleName.end()});
+    auto it = m.find(moduleName);
     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) {
+        std::map<std::string, std::shared_ptr<MethodStatistics<std::string>>,
+                std::less<> /* transparent comparator */> &map) {
     if (classNames) {
         for (const auto& className : *classNames) {
             map.emplace(className, std::make_shared<MethodStatistics<std::string>>());
@@ -51,17 +53,18 @@
 // 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 =
+    static const std::map<std::string, std::shared_ptr<MethodStatistics<std::string>>,
+            std::less<> /* transparent comparator */> m =
         // copy elided initialization of map m.
         [](){
-            std::map<std::string, std::shared_ptr<MethodStatistics<std::string>>> m;
+            std::map<std::string, std::shared_ptr<MethodStatistics<std::string>>, std::less<>> m;
             addClassesToMap(
                     getStatisticsClassesForModule(METHOD_STATISTICS_MODULE_NAME_AUDIO_HIDL),
                     m);
             return m;
         }();
 
-    auto it = m.find({className.begin(), className.end()});
+    auto it = m.find(className);
     if (it == m.end()) return {};
     return it->second;
 }
diff --git a/media/utils/TimeCheck.cpp b/media/utils/TimeCheck.cpp
index 0848eb3..6823f4f 100644
--- a/media/utils/TimeCheck.cpp
+++ b/media/utils/TimeCheck.cpp
@@ -21,6 +21,7 @@
 #include <android-base/logging.h>
 #include <audio_utils/clock.h>
 #include <mediautils/EventLog.h>
+#include <mediautils/FixedString.h>
 #include <mediautils/MethodStatistics.h>
 #include <mediautils/TimeCheck.h>
 #include <utils/Log.h>
@@ -138,22 +139,24 @@
     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(timeoutMs == 0
+TimeCheck::TimeCheck(std::string_view tag, OnTimerFunc&& onTimer, Duration requestedTimeoutDuration,
+        Duration secondChanceDuration, bool crashOnTimeout)
+    : mTimeCheckHandler{ std::make_shared<TimeCheckHandler>(
+            tag, std::move(onTimer), crashOnTimeout, requestedTimeoutDuration,
+            secondChanceDuration, std::chrono::system_clock::now(), gettid()) }
+    , mTimerHandle(requestedTimeoutDuration.count() == 0
+              /* for TimeCheck we don't consider a non-zero secondChanceDuration here */
               ? 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();
+                      [ timeCheckHandler = mTimeCheckHandler ](TimerThread::Handle timerHandle) {
+                          timeCheckHandler->onTimeout(timerHandle);
                       },
-                      std::chrono::milliseconds(timeoutMs))) {}
+                      requestedTimeoutDuration,
+                      secondChanceDuration)) {}
 
 TimeCheck::~TimeCheck() {
     if (mTimeCheckHandler) {
@@ -161,23 +164,77 @@
     }
 }
 
+/* static */
+std::string TimeCheck::analyzeTimeouts(
+        float requestedTimeoutMs, float elapsedSteadyMs, float elapsedSystemMs) {
+    // Track any OS clock issues with suspend.
+    // It is possible that the elapsedSystemMs is much greater than elapsedSteadyMs if
+    // a suspend occurs; however, we always expect the timeout ms should always be slightly
+    // less than the elapsed steady ms regardless of whether a suspend occurs or not.
+
+    std::string s("Timeout ms ");
+    s.append(std::to_string(requestedTimeoutMs))
+        .append(" elapsed steady ms ").append(std::to_string(elapsedSteadyMs))
+        .append(" elapsed system ms ").append(std::to_string(elapsedSystemMs));
+
+    // Is there something unusual?
+    static constexpr float TOLERANCE_CONTEXT_SWITCH_MS = 200.f;
+
+    if (requestedTimeoutMs > elapsedSteadyMs || requestedTimeoutMs > elapsedSystemMs) {
+        s.append("\nError: early expiration - "
+                "requestedTimeoutMs should be less than elapsed time");
+    }
+
+    if (elapsedSteadyMs > elapsedSystemMs + TOLERANCE_CONTEXT_SWITCH_MS) {
+        s.append("\nWarning: steady time should not advance faster than system time");
+    }
+
+    // This has been found in suspend stress testing.
+    if (elapsedSteadyMs > requestedTimeoutMs + TOLERANCE_CONTEXT_SWITCH_MS) {
+        s.append("\nWarning: steady time significantly exceeds timeout "
+                "- possible thread stall or aborted suspend");
+    }
+
+    // This has been found in suspend stress testing.
+    if (elapsedSystemMs > requestedTimeoutMs + TOLERANCE_CONTEXT_SWITCH_MS) {
+        s.append("\nInformation: system time significantly exceeds timeout "
+                "- possible suspend");
+    }
+    return s;
+}
+
+// To avoid any potential race conditions, the timer handle
+// (expiration = clock steady start + timeout) is passed into the callback.
 void TimeCheck::TimeCheckHandler::onCancel(TimerThread::Handle timerHandle) const
 {
     if (TimeCheck::getTimeCheckThread().cancelTask(timerHandle) && onTimer) {
-        const std::chrono::system_clock::time_point endTime = std::chrono::system_clock::now();
-        onTimer(false /* timeout */,
-                std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(
-                        endTime - startTime).count());
+        const std::chrono::steady_clock::time_point endSteadyTime =
+                std::chrono::steady_clock::now();
+        const float elapsedSteadyMs = std::chrono::duration_cast<FloatMs>(
+                endSteadyTime - timerHandle + timeoutDuration).count();
+        // send the elapsed steady time for statistics.
+        onTimer(false /* timeout */, elapsedSteadyMs);
     }
 }
 
-void TimeCheck::TimeCheckHandler::onTimeout() const
+// To avoid any potential race conditions, the timer handle
+// (expiration = clock steady start + timeout) is passed into the callback.
+void TimeCheck::TimeCheckHandler::onTimeout(TimerThread::Handle timerHandle) const
 {
-    const std::chrono::system_clock::time_point endTime = std::chrono::system_clock::now();
+    const std::chrono::steady_clock::time_point endSteadyTime = std::chrono::steady_clock::now();
+    const std::chrono::system_clock::time_point endSystemTime = std::chrono::system_clock::now();
+    // timerHandle incorporates the timeout
+    const float elapsedSteadyMs = std::chrono::duration_cast<FloatMs>(
+            endSteadyTime - (timerHandle - timeoutDuration)).count();
+    const float elapsedSystemMs = std::chrono::duration_cast<FloatMs>(
+            endSystemTime - startSystemTime).count();
+    const float requestedTimeoutMs = std::chrono::duration_cast<FloatMs>(
+            timeoutDuration).count();
+    const float secondChanceMs = std::chrono::duration_cast<FloatMs>(
+            secondChanceDuration).count();
+
     if (onTimer) {
-        onTimer(true /* timeout */,
-                std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(
-                        endTime - startTime).count());
+        onTimer(true /* timeout */, elapsedSteadyMs);
     }
 
     if (!crashOnTimeout) return;
@@ -207,8 +264,10 @@
     // Create abort message string - caution: this can be very large.
     const std::string abortMessage = std::string("TimeCheck timeout for ")
             .append(tag)
-            .append(" scheduled ").append(formatTime(startTime))
+            .append(" scheduled ").append(formatTime(startSystemTime))
             .append(" on thread ").append(std::to_string(tid)).append("\n")
+            .append(analyzeTimeouts(requestedTimeoutMs + secondChanceMs,
+                    elapsedSteadyMs, elapsedSystemMs)).append("\n")
             .append(halPids).append("\n")
             .append(summary);
 
@@ -231,16 +290,16 @@
             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),
+            FixedString62(className).append("::").append(methodName),
+            [ safeMethodName = FixedString30(methodName),
               stats = std::move(statistics) ]
             (bool timeout, float elapsedMs) {
                     if (timeout) {
                         ; // ignored, there is no timeout value.
                     } else {
-                        stats->event(method, elapsedMs);
+                        stats->event(safeMethodName.asStringView(), elapsedMs);
                     }
-            }, 0 /* timeoutMs */);
+            }, {} /* timeoutDuration */, {} /* secondChanceDuration */, false /* crashOnTimeout */);
 }
 
 }  // namespace android::mediautils
diff --git a/media/utils/TimerThread-test.cpp b/media/utils/TimerThread-test.cpp
index 93cd64c..9452c07 100644
--- a/media/utils/TimerThread-test.cpp
+++ b/media/utils/TimerThread-test.cpp
@@ -33,10 +33,28 @@
     return std::count(s.begin(), s.end(), c);
 }
 
-TEST(TimerThread, Basic) {
+
+// Split msec time between timeout and second chance time
+// This tests expiration times weighted between timeout and the second chance time.
+#define DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(msec, frac) \
+    std::chrono::milliseconds(int((msec) * (frac)) + 1), \
+    std::chrono::milliseconds(int((msec) * (1.f - (frac))))
+
+// The TimerThreadTest is parameterized on a fraction between 0.f and 1.f which
+// is how the total timeout time is split between the first timeout and the second chance time.
+//
+class TimerThreadTest : public ::testing::TestWithParam<float> {
+protected:
+
+static void testBasic() {
+    const auto frac = GetParam();
+
     std::atomic<bool> taskRan = false;
     TimerThread thread;
-    thread.scheduleTask("Basic", [&taskRan] { taskRan = true; }, 100ms);
+    TimerThread::Handle handle =
+            thread.scheduleTask("Basic", [&taskRan](TimerThread::Handle handle __unused) {
+                    taskRan = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
+    ASSERT_TRUE(TimerThread::isTimeoutHandle(handle));
     std::this_thread::sleep_for(100ms - kJitter);
     ASSERT_FALSE(taskRan);
     std::this_thread::sleep_for(2 * kJitter);
@@ -44,11 +62,15 @@
     ASSERT_EQ(1, countChars(thread.retiredToString(), REQUEST_START));
 }
 
-TEST(TimerThread, Cancel) {
+static void testCancel() {
+    const auto frac = GetParam();
+
     std::atomic<bool> taskRan = false;
     TimerThread thread;
     TimerThread::Handle handle =
-            thread.scheduleTask("Cancel", [&taskRan] { taskRan = true; }, 100ms);
+            thread.scheduleTask("Cancel", [&taskRan](TimerThread::Handle handle __unused) {
+                    taskRan = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
+    ASSERT_TRUE(TimerThread::isTimeoutHandle(handle));
     std::this_thread::sleep_for(100ms - kJitter);
     ASSERT_FALSE(taskRan);
     ASSERT_TRUE(thread.cancelTask(handle));
@@ -57,88 +79,87 @@
     ASSERT_EQ(1, countChars(thread.retiredToString(), REQUEST_START));
 }
 
-TEST(TimerThread, CancelAfterRun) {
+static void testCancelAfterRun() {
+    const auto frac = GetParam();
+
     std::atomic<bool> taskRan = false;
     TimerThread thread;
     TimerThread::Handle handle =
-            thread.scheduleTask("CancelAfterRun", [&taskRan] { taskRan = true; }, 100ms);
+            thread.scheduleTask("CancelAfterRun",
+                    [&taskRan](TimerThread::Handle handle __unused) {
+                            taskRan = true; },
+                            DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
+    ASSERT_TRUE(TimerThread::isTimeoutHandle(handle));
     std::this_thread::sleep_for(100ms + kJitter);
     ASSERT_TRUE(taskRan);
     ASSERT_FALSE(thread.cancelTask(handle));
     ASSERT_EQ(1, countChars(thread.retiredToString(), REQUEST_START));
 }
 
-TEST(TimerThread, MultipleTasks) {
+static void testMultipleTasks() {
+    const auto frac = GetParam();
+
     std::array<std::atomic<bool>, 6> taskRan{};
     TimerThread thread;
 
     auto startTime = std::chrono::steady_clock::now();
 
-    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);
+    thread.scheduleTask("0", [&taskRan](TimerThread::Handle handle __unused) {
+            taskRan[0] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(300, frac));
+    thread.scheduleTask("1", [&taskRan](TimerThread::Handle handle __unused) {
+            taskRan[1] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
+    thread.scheduleTask("2", [&taskRan](TimerThread::Handle handle __unused) {
+            taskRan[2] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
+    thread.scheduleTask("3", [&taskRan](TimerThread::Handle handle __unused) {
+            taskRan[3] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(400, frac));
+    auto handle4 = thread.scheduleTask("4", [&taskRan](TimerThread::Handle handle __unused) {
+            taskRan[4] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
+    thread.scheduleTask("5", [&taskRan](TimerThread::Handle handle __unused) {
+            taskRan[5] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
 
     // 6 tasks pending
     ASSERT_EQ(6, countChars(thread.pendingToString(), REQUEST_START));
     // 0 tasks completed
     ASSERT_EQ(0, countChars(thread.retiredToString(), REQUEST_START));
 
+    // None of the tasks are expected to have finished at the start.
+    std::array<std::atomic<bool>, 6> expected{};
+
     // Task 1 should trigger around 100ms.
     std::this_thread::sleep_until(startTime + 100ms - kJitter);
-    ASSERT_FALSE(taskRan[0]);
-    ASSERT_FALSE(taskRan[1]);
-    ASSERT_FALSE(taskRan[2]);
-    ASSERT_FALSE(taskRan[3]);
-    ASSERT_FALSE(taskRan[4]);
-    ASSERT_FALSE(taskRan[5]);
+
+    ASSERT_EQ(expected, taskRan);
+
 
     std::this_thread::sleep_until(startTime + 100ms + kJitter);
-    ASSERT_FALSE(taskRan[0]);
-    ASSERT_TRUE(taskRan[1]);
-    ASSERT_FALSE(taskRan[2]);
-    ASSERT_FALSE(taskRan[3]);
-    ASSERT_FALSE(taskRan[4]);
-    ASSERT_FALSE(taskRan[5]);
+
+    expected[1] = true;
+    ASSERT_EQ(expected, taskRan);
 
     // Cancel task 4 before it gets a chance to run.
     thread.cancelTask(handle4);
 
     // Tasks 2 and 5 should trigger around 200ms.
     std::this_thread::sleep_until(startTime + 200ms - kJitter);
-    ASSERT_FALSE(taskRan[0]);
-    ASSERT_TRUE(taskRan[1]);
-    ASSERT_FALSE(taskRan[2]);
-    ASSERT_FALSE(taskRan[3]);
-    ASSERT_FALSE(taskRan[4]);
-    ASSERT_FALSE(taskRan[5]);
+
+    ASSERT_EQ(expected, taskRan);
+
 
     std::this_thread::sleep_until(startTime + 200ms + kJitter);
-    ASSERT_FALSE(taskRan[0]);
-    ASSERT_TRUE(taskRan[1]);
-    ASSERT_TRUE(taskRan[2]);
-    ASSERT_FALSE(taskRan[3]);
-    ASSERT_FALSE(taskRan[4]);
-    ASSERT_TRUE(taskRan[5]);
+
+    expected[2] = true;
+    expected[5] = true;
+    ASSERT_EQ(expected, taskRan);
 
     // Task 0 should trigger around 300ms.
     std::this_thread::sleep_until(startTime + 300ms - kJitter);
-    ASSERT_FALSE(taskRan[0]);
-    ASSERT_TRUE(taskRan[1]);
-    ASSERT_TRUE(taskRan[2]);
-    ASSERT_FALSE(taskRan[3]);
-    ASSERT_FALSE(taskRan[4]);
-    ASSERT_TRUE(taskRan[5]);
+
+    ASSERT_EQ(expected, taskRan);
 
     std::this_thread::sleep_until(startTime + 300ms + kJitter);
-    ASSERT_TRUE(taskRan[0]);
-    ASSERT_TRUE(taskRan[1]);
-    ASSERT_TRUE(taskRan[2]);
-    ASSERT_FALSE(taskRan[3]);
-    ASSERT_FALSE(taskRan[4]);
-    ASSERT_TRUE(taskRan[5]);
+
+    expected[0] = true;
+    ASSERT_EQ(expected, taskRan);
 
     // 1 task pending
     ASSERT_EQ(1, countChars(thread.pendingToString(), REQUEST_START));
@@ -147,23 +168,16 @@
 
     // Task 3 should trigger around 400ms.
     std::this_thread::sleep_until(startTime + 400ms - kJitter);
-    ASSERT_TRUE(taskRan[0]);
-    ASSERT_TRUE(taskRan[1]);
-    ASSERT_TRUE(taskRan[2]);
-    ASSERT_FALSE(taskRan[3]);
-    ASSERT_FALSE(taskRan[4]);
-    ASSERT_TRUE(taskRan[5]);
+
+    ASSERT_EQ(expected, taskRan);
 
     // 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]);
-    ASSERT_TRUE(taskRan[2]);
-    ASSERT_TRUE(taskRan[3]);
-    ASSERT_FALSE(taskRan[4]);
-    ASSERT_TRUE(taskRan[5]);
+
+    expected[3] = true;
+    ASSERT_EQ(expected, taskRan);
 
     // 0 tasks pending
     ASSERT_EQ(0, countChars(thread.pendingToString(), REQUEST_START));
@@ -171,6 +185,30 @@
     ASSERT_EQ(5 + 1, countChars(thread.retiredToString(), REQUEST_START));
 }
 
+}; // class TimerThreadTest
+
+TEST_P(TimerThreadTest, Basic) {
+    testBasic();
+}
+
+TEST_P(TimerThreadTest, Cancel) {
+    testCancel();
+}
+
+TEST_P(TimerThreadTest, CancelAfterRun) {
+    testCancelAfterRun();
+}
+
+TEST_P(TimerThreadTest, MultipleTasks) {
+    testMultipleTasks();
+}
+
+INSTANTIATE_TEST_CASE_P(
+        TimerThread,
+        TimerThreadTest,
+        ::testing::Values(0.f, 0.5f, 1.f)
+        );
+
 TEST(TimerThread, TrackedTasks) {
     TimerThread thread;
 
@@ -178,6 +216,10 @@
     auto handle1 = thread.trackTask("1");
     auto handle2 = thread.trackTask("2");
 
+    ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle0));
+    ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle1));
+    ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle2));
+
     // 3 tasks pending
     ASSERT_EQ(3, countChars(thread.pendingToString(), REQUEST_START));
     // 0 tasks retired
@@ -201,6 +243,7 @@
 
     // Add another tracked task.
     auto handle3 = thread.trackTask("3");
+    ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle3));
 
     // 2 tasks pending
     ASSERT_EQ(2, countChars(thread.pendingToString(), REQUEST_START));
diff --git a/media/utils/TimerThread.cpp b/media/utils/TimerThread.cpp
index 6de6b13..5e58a3d 100644
--- a/media/utils/TimerThread.cpp
+++ b/media/utils/TimerThread.cpp
@@ -23,30 +23,35 @@
 
 #include <mediautils/MediaUtilsDelayed.h>
 #include <mediautils/TimerThread.h>
+#include <utils/Log.h>
 #include <utils/ThreadDefs.h>
 
+using namespace std::chrono_literals;
+
 namespace android::mediautils {
 
 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) {
+        std::string_view tag, TimerCallback&& func,
+        Duration timeoutDuration, Duration secondChanceDuration) {
     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);
+    auto request = std::make_shared<const Request>(now, now +
+            std::chrono::duration_cast<std::chrono::system_clock::duration>(timeoutDuration),
+            secondChanceDuration, gettid(), tag);
+    return mMonitorThread.add(std::move(request), std::move(func), timeoutDuration);
 }
 
-TimerThread::Handle TimerThread::trackTask(std::string tag) {
+TimerThread::Handle TimerThread::trackTask(std::string_view tag) {
     const auto now = std::chrono::system_clock::now();
-    std::shared_ptr<const Request> request{
-            new Request{ now, now, gettid(), std::move(tag) }};
+    auto request = std::make_shared<const Request>(now, now,
+            Duration{} /* secondChanceDuration */, gettid(), tag);
     return mNoTimeoutMap.add(std::move(request));
 }
 
 bool TimerThread::cancelTask(Handle handle) {
-    std::shared_ptr<const Request> request = mNoTimeoutMap.isValidHandle(handle) ?
+    std::shared_ptr<const Request> request = isNoTimeoutHandle(handle) ?
              mNoTimeoutMap.remove(handle) : mMonitorThread.remove(handle);
     if (!request) return false;
     mRetiredQueue.add(std::move(request));
@@ -84,6 +89,8 @@
 
     return std::string("now ")
             .append(formatTime(std::chrono::system_clock::now()))
+            .append("\nsecondChanceCount ")
+            .append(std::to_string(mMonitorThread.getSecondChanceCount()))
             .append(analysisSummary)
             .append("\ntimeout [ ")
             .append(requestsToString(timeoutRequests))
@@ -106,10 +113,10 @@
 //
 /* static */
 bool TimerThread::isRequestFromHal(const std::shared_ptr<const Request>& request) {
-    const size_t hidlPos = request->tag.find("Hidl");
+    const size_t hidlPos = request->tag.asStringView().find("Hidl");
     if (hidlPos == std::string::npos) return false;
     // should be a separator afterwards Hidl which indicates the string was in the class.
-    const size_t separatorPos = request->tag.find("::", hidlPos);
+    const size_t separatorPos = request->tag.asStringView().find("::", hidlPos);
     return separatorPos != std::string::npos;
 }
 
@@ -127,12 +134,12 @@
     std::vector<std::shared_ptr<const Request>> pendingExact;
     std::vector<std::shared_ptr<const Request>> pendingPossible;
 
-    // We look at pending requests that were scheduled no later than kDuration
+    // We look at pending requests that were scheduled no later than kPendingDuration
     // after the timeout request. This prevents false matches with calls
     // that naturally block for a short period of time
     // such as HAL write() and read().
     //
-    auto constexpr kDuration = std::chrono::milliseconds(1000);
+    constexpr Duration kPendingDuration = 1000ms;
     for (const auto& pending : pendingRequests) {
         // If the pending tid is the same as timeout tid, problem identified.
         if (pending->tid == timeout->tid) {
@@ -141,7 +148,7 @@
         }
 
         // if the pending tid is scheduled within time limit
-        if (pending->scheduled - timeout->scheduled < kDuration) {
+        if (pending->scheduled - timeout->scheduled < kPendingDuration) {
             pendingPossible.emplace_back(pending);
         }
     }
@@ -241,15 +248,11 @@
     }
 }
 
-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++);
+    const Handle handle = getUniqueHandle_l();
     mMap[handle] = request;
     return handle;
 }
@@ -271,16 +274,6 @@
     }
 }
 
-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(); }) {
@@ -300,24 +293,78 @@
 void TimerThread::MonitorThread::threadFunc() {
     std::unique_lock _l(mMutex);
     while (!mShouldExit) {
+        Handle nextDeadline = INVALID_HANDLE;
+        Handle now = INVALID_HANDLE;
         if (!mMonitorRequests.empty()) {
-            Handle nextDeadline = mMonitorRequests.begin()->first;
-            if (nextDeadline < std::chrono::steady_clock::now()) {
+            nextDeadline = mMonitorRequests.begin()->first;
+            now = std::chrono::steady_clock::now();
+            if (nextDeadline < now) {
+                auto node = mMonitorRequests.extract(mMonitorRequests.begin());
                 // Deadline has expired, handle the request.
+                auto secondChanceDuration = node.mapped().first->secondChanceDuration;
+                if (secondChanceDuration.count() != 0) {
+                    // We now apply the second chance duration to find the clock
+                    // monotonic second deadline.  The unique key is then the
+                    // pair<second_deadline, first_deadline>.
+                    //
+                    // The second chance prevents a false timeout should there be
+                    // any clock monotonic advancement during suspend.
+                    auto newHandle = now + secondChanceDuration;
+                    ALOGD("%s: TimeCheck second chance applied for %s",
+                            __func__, node.mapped().first->tag.c_str()); // should be rare event.
+                    mSecondChanceRequests.emplace_hint(mSecondChanceRequests.end(),
+                            std::make_pair(newHandle, nextDeadline),
+                            std::move(node.mapped()));
+                    // increment second chance counter.
+                    mSecondChanceCount.fetch_add(1 /* arg */, std::memory_order_relaxed);
+                } else {
+                    {
+                        _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(nextDeadline);
+                        // Caution: we don't hold lock when we call TimerCallback,
+                        // but 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();
+                }
+                // always process expiring monitor requests first.
+                continue;
+            }
+        }
+        // now process any second chance requests.
+        if (!mSecondChanceRequests.empty()) {
+            Handle secondDeadline = mSecondChanceRequests.begin()->first.first;
+            if (now == INVALID_HANDLE) now = std::chrono::steady_clock::now();
+            if (secondDeadline < now) {
+                auto node = mSecondChanceRequests.extract(mSecondChanceRequests.begin());
                 {
-                    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.
+                    const Handle originalHandle = node.key().second;
+                    node.mapped().second(originalHandle);
+                    // Caution: we don't hold lock when we call TimerCallback.
+                    // This is benign issue - we permit concurrent operations
+                    // while in the callback to the MonitorQueue.
+                    //
+                    // Anything left over is released here outside lock.
                 }
                 // reacquire the lock - if something was added, we loop immediately to check.
                 _l.lock();
                 continue;
             }
+            // update the deadline.
+            if (nextDeadline == INVALID_HANDLE) {
+                nextDeadline = secondDeadline;
+            } else {
+                nextDeadline = std::min(nextDeadline, secondDeadline);
+            }
+        }
+        if (nextDeadline != INVALID_HANDLE) {
             mCond.wait_until(_l, nextDeadline);
         } else {
             mCond.wait(_l);
@@ -326,26 +373,39 @@
 }
 
 TimerThread::Handle TimerThread::MonitorThread::add(
-        std::shared_ptr<const Request> request, std::function<void()>&& func,
-        std::chrono::milliseconds timeout) {
+        std::shared_ptr<const Request> request, TimerCallback&& func, Duration timeout) {
     std::lock_guard _l(mMutex);
     const Handle handle = getUniqueHandle_l(timeout);
-    mMonitorRequests.emplace(handle, std::make_pair(std::move(request), std::move(func)));
+    mMonitorRequests.emplace_hint(mMonitorRequests.end(),
+            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::pair<std::shared_ptr<const Request>, TimerCallback> data;
     std::unique_lock ul(mMutex);
-    const auto it = mMonitorRequests.find(handle);
-    if (it == mMonitorRequests.end()) {
-        return {};
+    if (const auto it = mMonitorRequests.find(handle);
+        it != mMonitorRequests.end()) {
+        data = std::move(it->second);
+        mMonitorRequests.erase(it);
+        ul.unlock();  // manually release lock here so func (data.second)
+                      // is released outside of lock.
+        return data.first;  // request
     }
-    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;
+
+    // this check is O(N), but since the second chance requests are ordered
+    // in terms of earliest expiration time, we would expect better than average results.
+    for (auto it = mSecondChanceRequests.begin(); it != mSecondChanceRequests.end(); ++it) {
+        if (it->first.second == handle) {
+            data = std::move(it->second);
+            mSecondChanceRequests.erase(it);
+            ul.unlock();  // manually release lock here so func (data.second)
+                          // is released outside of lock.
+            return data.first; // request
+        }
+    }
+    return {};
 }
 
 void TimerThread::MonitorThread::copyRequests(
@@ -354,6 +414,13 @@
     for (const auto &[deadline, monitorpair] : mMonitorRequests) {
         requests.emplace_back(monitorpair.first);
     }
+    // we combine the second map with the first map - this is
+    // everything that is pending on the monitor thread.
+    // The second map will be older than the first map so this
+    // is in order.
+    for (const auto &[deadline, monitorpair] : mSecondChanceRequests) {
+        requests.emplace_back(monitorpair.first);
+    }
 }
 
 }  // namespace android::mediautils
diff --git a/media/utils/fuzzers/TimeCheckFuzz.cpp b/media/utils/fuzzers/TimeCheckFuzz.cpp
index 7966469..65b2885 100644
--- a/media/utils/fuzzers/TimeCheckFuzz.cpp
+++ b/media/utils/fuzzers/TimeCheckFuzz.cpp
@@ -48,7 +48,9 @@
     std::string name = data_provider.ConsumeRandomLengthString(kMaxStringLen);
 
     // 3. The constructor, which is fuzzed here:
-    android::mediautils::TimeCheck timeCheck(name.c_str(), {} /* onTimer */, timeoutMs);
+    android::mediautils::TimeCheck timeCheck(name.c_str(), {} /* onTimer */,
+            std::chrono::milliseconds(timeoutMs),
+            {} /* secondChanceDuration */, true /* crashOnTimeout */);
     // We will leave some buffer to avoid sleeping too long
     uint8_t sleep_amount_ms = data_provider.ConsumeIntegralInRange<uint8_t>(0, timeoutMs / 2);
 
diff --git a/media/utils/include/mediautils/FixedString.h b/media/utils/include/mediautils/FixedString.h
new file mode 100644
index 0000000..047aa82
--- /dev/null
+++ b/media/utils/include/mediautils/FixedString.h
@@ -0,0 +1,282 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <string>
+#include <string_view>
+
+namespace android::mediautils {
+
+/*
+ * FixedString is a stack allocatable string buffer that supports
+ * simple appending of other strings and string_views.
+ *
+ * It is designed for no-malloc operation when std::string
+ * small buffer optimization is insufficient.
+ *
+ * To keep code small, use asStringView() for operations on this.
+ *
+ * Notes:
+ * 1) Appending beyond the internal buffer size results in truncation.
+ *
+ * Alternatives:
+ * 1) If you want a sharable copy-on-write string implementation,
+ *    consider using the legacy android::String8().
+ * 2) Using std::string with a fixed stack allocator may suit your needs,
+ *    but exception avoidance is tricky.
+ * 3) Using C++20 ranges https://en.cppreference.com/w/cpp/ranges if you don't
+ *    need backing store.  Be careful about allocation with ranges.
+ *
+ * Good small sizes are multiples of 16 minus 2, e.g. 14, 30, 46, 62.
+ *
+ * Implementation Notes:
+ * 1) No iterators or [] for FixedString - please convert to std::string_view.
+ * 2) For small N (e.g. less than 62), consider a change to always zero fill and
+ *    potentially prevent always zero terminating (if one only does append).
+ *
+ * Possible arguments to create/append:
+ * 1) A FixedString.
+ * 2) A single char.
+ * 3) A char * pointer.
+ * 4) A std::string.
+ * 5) A std::string_view (or something convertible to it).
+ *
+ * Example:
+ *
+ * FixedString s1(std::string("a"));    // ctor
+ * s1 << "bc" << 'd' << '\n';           // streaming append
+ * s1 += "hello";                       // += append
+ * ASSERT_EQ(s1, "abcd\nhello");
+ */
+template <uint32_t N>
+struct FixedString
+{
+    // Find the best size type.
+    using strsize_t = std::conditional_t<(N > 255), uint32_t, uint8_t>;
+
+    // constructors
+    FixedString() { // override default
+        buffer_[0] = '\0';
+    }
+
+    FixedString(const FixedString& other) { // override default.
+        copyFrom<N>(other);
+    }
+
+    // The following constructor is not explicit to allow
+    // FixedString<8> s = "abcd";
+    template <typename ...Types>
+    FixedString(Types&&... args) {
+        append(std::forward<Types>(args)...);
+    }
+
+    // copy assign (copyFrom checks for equality and returns *this).
+    FixedString& operator=(const FixedString& other) { // override default.
+        return copyFrom<N>(other);
+    }
+
+    template <typename ...Types>
+    FixedString& operator=(Types&&... args) {
+        size_ = 0;
+        return append(std::forward<Types>(args)...);
+    }
+
+    // operator equals
+    bool operator==(const char *s) const {
+        return strncmp(c_str(), s, capacity() + 1) == 0;
+    }
+
+    bool operator==(std::string_view s) const {
+        return size() == s.size() && memcmp(data(), s.data(), size()) == 0;
+    }
+
+    // operator not-equals
+    template <typename T>
+    bool operator!=(const T& other) const {
+        return !operator==(other);
+    }
+
+    // operator +=
+    template <typename ...Types>
+    FixedString& operator+=(Types&&... args) {
+        return append(std::forward<Types>(args)...);
+    }
+
+    // conversion to std::string_view.
+    operator std::string_view() const {
+        return asStringView();
+    }
+
+    // basic observers
+    size_t buffer_offset() const { return offsetof(std::decay_t<decltype(*this)>, buffer_); }
+    static constexpr uint32_t capacity() { return N; }
+    uint32_t size() const { return size_; }
+    uint32_t remaining() const { return size_ >= N ? 0 : N - size_; }
+    bool empty() const { return size_ == 0; }
+    bool full() const { return size_ == N; }  // when full, possible truncation risk.
+    char * data() { return buffer_; }
+    const char * data() const { return buffer_; }
+    const char * c_str() const { return buffer_; }
+
+    inline std::string_view asStringView() const {
+        return { buffer_, static_cast<size_t>(size_) };
+    }
+    inline std::string asString() const {
+        return { buffer_, static_cast<size_t>(size_) };
+    }
+
+    void clear() { size_ = 0; buffer_[0] = 0; }
+
+    // Implementation of append - using templates
+    // to guarantee precedence in the presence of ambiguity.
+    //
+    // Consider C++20 template overloading through constraints and concepts.
+    template <typename T>
+    FixedString& append(const T& t) {
+        using decayT = std::decay_t<T>;
+        if constexpr (is_specialization_v<decayT, FixedString>) {
+            // A FixedString<U>
+            if (size_ == 0) {
+                // optimization to erase everything.
+                return copyFrom(t);
+            } else {
+                return appendStringView({t.data(), t.size()});
+            }
+        } else if constexpr(std::is_same_v<decayT, char>) {
+            if (size_ < N) {
+                buffer_[size_++] = t;
+                buffer_[size_] = '\0';
+            }
+            return *this;
+        } else if constexpr(std::is_same_v<decayT, char *>) {
+            // Some char* ptr.
+            return appendString(t);
+        } else if constexpr (std::is_convertible_v<decayT, std::string_view>) {
+            // std::string_view, std::string, or some other convertible type.
+            return appendStringView(t);
+        } else /* constexpr */ {
+            static_assert(dependent_false_v<T>, "non-matching append type");
+        }
+    }
+
+    FixedString& appendStringView(std::string_view s) {
+        uint32_t total = std::min(static_cast<size_t>(N - size_), s.size());
+        memcpy(buffer_ + size_, s.data(), total);
+        size_ += total;
+        buffer_[size_] = '\0';
+        return *this;
+    }
+
+    FixedString& appendString(const char *s) {
+        // strncpy zero pads to the end,
+        // strlcpy returns total expected length,
+        // we don't have strncpy_s in Bionic,
+        // so we write our own here.
+        while (size_ < N && *s != '\0') {
+            buffer_[size_++] = *s++;
+        }
+        buffer_[size_] = '\0';
+        return *this;
+    }
+
+    // Copy initialize the struct.
+    // Note: We are POD but customize the copying for acceleration
+    // of moving small strings embedded in a large buffers.
+    template <uint32_t U>
+    FixedString& copyFrom(const FixedString<U>& other) {
+        if ((void*)this != (void*)&other) { // not a self-assignment
+            if (other.size() == 0) {
+                size_ = 0;
+                buffer_[0] = '\0';
+                return *this;
+            }
+            constexpr size_t kSizeToCopyWhole = 64;
+            if constexpr (N == U &&
+                    sizeof(*this) == sizeof(other) &&
+                    sizeof(*this) <= kSizeToCopyWhole) {
+                // As we have the same str size type, we can just
+                // memcpy with fixed size, which can be easily optimized.
+                memcpy(static_cast<void*>(this), static_cast<const void*>(&other), sizeof(*this));
+                return *this;
+            }
+            if constexpr (std::is_same_v<strsize_t, typename FixedString<U>::strsize_t>) {
+                constexpr size_t kAlign = 8;  // align to a multiple of 8.
+                static_assert((kAlign & (kAlign - 1)) == 0); // power of 2.
+                // we check any alignment issues.
+                if (buffer_offset() == other.buffer_offset() && other.size() <= capacity()) {
+                    // improve on standard POD copying by reducing size.
+                    const size_t mincpy = buffer_offset() + other.size() + 1 /* nul */;
+                    const size_t maxcpy = std::min(sizeof(*this), sizeof(other));
+                    const size_t cpysize = std::min(mincpy + kAlign - 1 & ~(kAlign - 1), maxcpy);
+                    memcpy(static_cast<void*>(this), static_cast<const void*>(&other), cpysize);
+                    return *this;
+                }
+            }
+            size_ = std::min(other.size(), capacity());
+            memcpy(buffer_, other.data(), size_);
+            buffer_[size_] = '\0';  // zero terminate.
+        }
+        return *this;
+    }
+
+private:
+    //  Template helper methods
+
+    template <typename Test, template <uint32_t> class Ref>
+    struct is_specialization : std::false_type {};
+
+    template <template <uint32_t> class Ref, uint32_t UU>
+    struct is_specialization<Ref<UU>, Ref>: std::true_type {};
+
+    template <typename Test, template <uint32_t> class Ref>
+    static inline constexpr bool is_specialization_v = is_specialization<Test, Ref>::value;
+
+    // For static assert(false) we need a template version to avoid early failure.
+    template <typename T>
+    static inline constexpr bool dependent_false_v = false;
+
+    // POD variables
+    strsize_t size_ = 0;
+    char buffer_[N + 1 /* allow zero termination */];
+};
+
+// Stream operator syntactic sugar.
+// Example:
+// s << 'b' << "c" << "d" << '\n';
+template <uint32_t N, typename ...Types>
+FixedString<N>& operator<<(FixedString<N>& fs, Types&&... args) {
+    return fs.append(std::forward<Types>(args)...);
+}
+
+// We do not use a default size for fixed string as changing
+// the default size would lead to different behavior - we want the
+// size to be explicitly known.
+
+// FixedString62 of 62 chars fits in one typical cache line.
+using FixedString62 = FixedString<62>;
+
+// Slightly smaller
+using FixedString30 = FixedString<30>;
+
+// Since we have added copy and assignment optimizations,
+// we are no longer trivially assignable and copyable.
+// But we check standard layout here to prevent inclusion of unacceptable members or virtuals.
+static_assert(std::is_standard_layout_v<FixedString62>);
+static_assert(std::is_standard_layout_v<FixedString30>);
+
+}  // namespace android::mediautils
diff --git a/media/utils/include/mediautils/MethodStatistics.h b/media/utils/include/mediautils/MethodStatistics.h
index 700fbaa..6d7e990 100644
--- a/media/utils/include/mediautils/MethodStatistics.h
+++ b/media/utils/include/mediautils/MethodStatistics.h
@@ -55,15 +55,23 @@
     /**
      * Adds a method event, typically execution time in ms.
      */
-    void event(Code code, FloatType executeMs) {
+    template <typename C>
+    void event(C&& code, FloatType executeMs) {
         std::lock_guard lg(mLock);
-        mStatisticsMap[code].add(executeMs);
+        auto it = mStatisticsMap.lower_bound(code);
+        if (it != mStatisticsMap.end() && it->first == code) {
+            it->second.add(executeMs);
+        } else {
+            // StatsType ctor takes an optional array of data for initialization.
+            FloatType dataArray[1] = { executeMs };
+            mStatisticsMap.emplace_hint(it, std::forward<C>(code), dataArray);
+        }
     }
 
     /**
      * Returns the name for the method code.
      */
-    std::string getMethodForCode(Code code) const {
+    std::string getMethodForCode(const Code& code) const {
         auto it = mMethodMap.find(code);
         return it == mMethodMap.end() ? std::to_string((int)code) : it->second;
     }
@@ -71,7 +79,7 @@
     /**
      * Returns the number of times the method was invoked by event().
      */
-    size_t getMethodCount(Code code) const {
+    size_t getMethodCount(const Code& code) const {
         std::lock_guard lg(mLock);
         auto it = mStatisticsMap.find(code);
         return it == mStatisticsMap.end() ? 0 : it->second.getN();
@@ -80,7 +88,7 @@
     /**
      * Returns the statistics object for the method.
      */
-    StatsType getStatistics(Code code) const {
+    StatsType getStatistics(const Code& code) const {
         std::lock_guard lg(mLock);
         auto it = mStatisticsMap.find(code);
         return it == mStatisticsMap.end() ? StatsType{} : it->second;
@@ -107,9 +115,10 @@
     }
 
 private:
-    const std::map<Code, std::string> mMethodMap;
+    // Note: we use a transparent comparator std::less<> for heterogeneous key lookup.
+    const std::map<Code, std::string, std::less<>> mMethodMap;
     mutable std::mutex mLock;
-    std::map<Code, StatsType> mStatisticsMap GUARDED_BY(mLock);
+    std::map<Code, StatsType, std::less<>> mStatisticsMap GUARDED_BY(mLock);
 };
 
 // Managed Statistics support.
diff --git a/media/utils/include/mediautils/TimeCheck.h b/media/utils/include/mediautils/TimeCheck.h
index ef03aef..bdb5337 100644
--- a/media/utils/include/mediautils/TimeCheck.h
+++ b/media/utils/include/mediautils/TimeCheck.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <chrono>
 #include <vector>
 
 #include <mediautils/TimerThread.h>
@@ -27,10 +28,33 @@
 
 class TimeCheck {
   public:
+
+    // Duration for TimeCheck is based on steady_clock, typically nanoseconds.
+    using Duration = std::chrono::steady_clock::duration;
+
+    // Duration for printing is in milliseconds, using float for additional precision.
+    using FloatMs = std::chrono::duration<float, std::milli>;
+
+    // OnTimerFunc is the callback function with 2 parameters.
+    //  bool timeout  (which is true when the TimeCheck object
+    //                 times out, false when the TimeCheck object is
+    //                 destroyed or leaves scope before the timer expires.)
+    //  float elapsedMs (the elapsed time to this event).
     using OnTimerFunc = std::function<void(bool /* timeout */, float /* elapsedMs */ )>;
 
     // The default timeout is chosen to be less than system server watchdog timeout
-    static constexpr uint32_t kDefaultTimeOutMs = 5000;
+    // Note: kDefaultTimeOutMs should be no less than 2 seconds, otherwise spurious timeouts
+    // may occur with system suspend.
+    static constexpr TimeCheck::Duration kDefaultTimeoutDuration = std::chrono::milliseconds(3000);
+
+    // Due to suspend abort not incrementing the monotonic clock,
+    // we allow another second chance timeout after the first timeout expires.
+    //
+    // The total timeout is therefore kDefaultTimeoutDuration + kDefaultSecondChanceDuration,
+    // and the result is more stable when the monotonic clock increments during suspend.
+    //
+    static constexpr TimeCheck::Duration kDefaultSecondChanceDuration =
+            std::chrono::milliseconds(2000);
 
     /**
      * TimeCheck is a RAII object which will notify a callback
@@ -44,24 +68,24 @@
      * the deallocation.
      *
      * \param tag       string associated with the TimeCheck object.
-     * \param onTimer   callback function with 2 parameters
-     *                      bool timeout  (which is true when the TimeCheck object
-     *                                    times out, false when the TimeCheck object is
-     *                                    destroyed or leaves scope before the timer expires.)
-     *                      float elapsedMs (the elapsed time to this event).
+     * \param onTimer   callback function with 2 parameters (described above in OnTimerFunc).
      *                  The callback when timeout is true will be called on a different thread.
      *                  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.
+     * \param requestedTimeoutDuration 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 secondChanceDuration additional milliseconds to wait if the first timeout expires.
+     *                  This is used to prevent false timeouts if the steady (monotonic)
+     *                  clock advances on aborted suspend.
      * \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);
+    explicit TimeCheck(std::string_view tag, OnTimerFunc&& onTimer,
+            Duration requestedTimeoutDuration, Duration secondChanceDuration,
+            bool crashOnTimeout);
 
     TimeCheck() = default;
     // Remove copy constructors as there should only be one call to the destructor.
@@ -79,16 +103,36 @@
     // The usage here is const safe.
     class TimeCheckHandler {
     public:
-        const std::string tag;
+        template <typename S, typename F>
+        TimeCheckHandler(S&& _tag, F&& _onTimer, bool _crashOnTimeout,
+            Duration _timeoutDuration, Duration _secondChanceDuration,
+            std::chrono::system_clock::time_point _startSystemTime,
+            pid_t _tid)
+            : tag(std::forward<S>(_tag))
+            , onTimer(std::forward<F>(_onTimer))
+            , crashOnTimeout(_crashOnTimeout)
+            , timeoutDuration(_timeoutDuration)
+            , secondChanceDuration(_secondChanceDuration)
+            , startSystemTime(_startSystemTime)
+            , tid(_tid)
+            {}
+        const FixedString62 tag;
         const OnTimerFunc onTimer;
         const bool crashOnTimeout;
-        const std::chrono::system_clock::time_point startTime;
+        const Duration timeoutDuration;
+        const Duration secondChanceDuration;
+        const std::chrono::system_clock::time_point startSystemTime;
         const pid_t tid;
 
         void onCancel(TimerThread::Handle handle) const;
-        void onTimeout() const;
+        void onTimeout(TimerThread::Handle handle) const;
     };
 
+    // Returns a string that represents the timeout vs elapsed time,
+    // and diagnostics if there are any potential issues.
+    static std::string analyzeTimeouts(
+            float timeoutMs, float elapsedSteadyMs, float elapsedSystemMs);
+
     static TimerThread& getTimeCheckThread();
     static void accessAudioHalPids(std::vector<pid_t>* pids, bool update);
 
diff --git a/media/utils/include/mediautils/TimerThread.h b/media/utils/include/mediautils/TimerThread.h
index ffee602..c76fa7d 100644
--- a/media/utils/include/mediautils/TimerThread.h
+++ b/media/utils/include/mediautils/TimerThread.h
@@ -27,6 +27,8 @@
 
 #include <android-base/thread_annotations.h>
 
+#include <mediautils/FixedString.h>
+
 namespace android::mediautils {
 
 /**
@@ -34,19 +36,93 @@
  */
 class TimerThread {
   public:
-    // A Handle is a time_point that serves as a unique key.  It is ordered.
+    // A Handle is a time_point that serves as a unique key to access a queued
+    // request to the TimerThread.
     using Handle = std::chrono::steady_clock::time_point;
 
+    // Duration is based on steady_clock (typically nanoseconds)
+    // vs the system_clock duration (typically microseconds).
+    using Duration = std::chrono::steady_clock::duration;
+
     static inline constexpr Handle INVALID_HANDLE =
             std::chrono::steady_clock::time_point::min();
 
+    // Handle implementation details:
+    // A Handle represents the timer expiration time based on std::chrono::steady_clock
+    // (clock monotonic).  This Handle is computed as now() + timeout.
+    //
+    // The lsb of the Handle time_point is adjusted to indicate whether there is
+    // a timeout action (1) or not (0).
+    //
+
+    template <size_t COUNT>
+    static constexpr bool is_power_of_2_v = COUNT > 0 && (COUNT & (COUNT - 1)) == 0;
+
+    template <size_t COUNT>
+    static constexpr size_t mask_from_count_v = COUNT - 1;
+
+    static constexpr size_t HANDLE_TYPES = 2;
+    // HANDLE_TYPES must be a power of 2.
+    static_assert(is_power_of_2_v<HANDLE_TYPES>);
+
+    // The handle types
+    enum class HANDLE_TYPE : size_t {
+        NO_TIMEOUT = 0,
+        TIMEOUT = 1,
+    };
+
+    static constexpr size_t HANDLE_TYPE_MASK = mask_from_count_v<HANDLE_TYPES>;
+
+    template <typename T>
+    static constexpr auto enum_as_value(T x) {
+        return static_cast<std::underlying_type_t<T>>(x);
+    }
+
+    static inline bool isNoTimeoutHandle(Handle handle) {
+        return (handle.time_since_epoch().count() & HANDLE_TYPE_MASK) ==
+                enum_as_value(HANDLE_TYPE::NO_TIMEOUT);
+    }
+
+    static inline bool isTimeoutHandle(Handle handle) {
+        return (handle.time_since_epoch().count() & HANDLE_TYPE_MASK) ==
+                enum_as_value(HANDLE_TYPE::TIMEOUT);
+    }
+
+    // Returns a unique Handle that doesn't exist in the container.
+    template <size_t MAX_TYPED_HANDLES, size_t HANDLE_TYPE_AS_VALUE, typename C, typename T>
+    static Handle getUniqueHandleForHandleType_l(C container, T timeout) {
+        static_assert(MAX_TYPED_HANDLES > 0 && HANDLE_TYPE_AS_VALUE < MAX_TYPED_HANDLES
+                && is_power_of_2_v<MAX_TYPED_HANDLES>,
+                " handles must be power of two");
+
+        // Our initial handle is the deadline as computed from steady_clock.
+        auto deadline = std::chrono::steady_clock::now() + timeout;
+
+        // We adjust the lsbs by the minimum increment to have the correct
+        // HANDLE_TYPE in the least significant bits.
+        auto remainder = deadline.time_since_epoch().count() & HANDLE_TYPE_MASK;
+        size_t offset = HANDLE_TYPE_AS_VALUE > remainder ? HANDLE_TYPE_AS_VALUE - remainder :
+                     MAX_TYPED_HANDLES + HANDLE_TYPE_AS_VALUE - remainder;
+        deadline += std::chrono::steady_clock::duration(offset);
+
+        // To avoid key collisions, advance the handle by MAX_TYPED_HANDLES (the modulus factor)
+        // until the key is unique.
+        while (container.find(deadline) != container.end()) {
+            deadline += std::chrono::steady_clock::duration(MAX_TYPED_HANDLES);
+        }
+        return deadline;
+    }
+
+    // TimerCallback invoked on timeout or cancel.
+    using TimerCallback = std::function<void(Handle)>;
+
     /**
      * 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
+     * \param timeoutDuration 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
@@ -54,7 +130,8 @@
      * \returns       a handle that can be used for cancellation.
      */
     Handle scheduleTask(
-            std::string tag, std::function<void()>&& func, std::chrono::milliseconds timeout);
+            std::string_view tag, TimerCallback&& func,
+            Duration timeoutDuration, Duration secondChanceDuration);
 
     /**
      * Tracks a task that shows up on toString() until cancelled.
@@ -62,7 +139,7 @@
      * \param tag     string associated with the task.
      * \returns       a handle that can be used for cancellation.
      */
-    Handle trackTask(std::string tag);
+    Handle trackTask(std::string_view tag);
 
     /**
      * Cancels a task previously scheduled with scheduleTask()
@@ -128,14 +205,30 @@
   private:
     // To minimize movement of data, we pass around shared_ptrs to Requests.
     // These are allocated and deallocated outside of the lock.
+    // TODO(b/243839867) consider options to merge Request with the
+    // TimeCheck::TimeCheckHandler struct.
     struct Request {
+        Request(std::chrono::system_clock::time_point _scheduled,
+                std::chrono::system_clock::time_point _deadline,
+                Duration _secondChanceDuration,
+                pid_t _tid,
+                std::string_view _tag)
+            : scheduled(_scheduled)
+            , deadline(_deadline)
+            , secondChanceDuration(_secondChanceDuration)
+            , tid(_tid)
+            , tag(_tag)
+            {}
+
         const std::chrono::system_clock::time_point scheduled;
-        const std::chrono::system_clock::time_point deadline; // deadline := scheduled + timeout
+        const std::chrono::system_clock::time_point deadline; // deadline := scheduled
+                                                              // + timeoutDuration
+                                                              // + secondChanceDuration
                                                               // if deadline == scheduled, no
                                                               // timeout, task not executed.
+        Duration secondChanceDuration;
         const pid_t tid;
-        const std::string tag;
-
+        const FixedString62 tag;
         std::string toString() const;
     };
 
@@ -160,15 +253,17 @@
                 mRequestQueue GUARDED_BY(mRQMutex);
     };
 
-    // A storage map of tasks without timeouts.  There is no std::function<void()>
+    // A storage map of tasks without timeouts.  There is no TimerCallback
     // 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);
+        Handle getUniqueHandle_l() REQUIRES(mNTMutex) {
+            return getUniqueHandleForHandleType_l<
+                    HANDLE_TYPES, enum_as_value(HANDLE_TYPE::NO_TIMEOUT)>(
+                mMap, Duration{} /* timeout */);
+        }
 
       public:
         bool isValidHandle(Handle handle) const; // lock free
@@ -182,14 +277,26 @@
     // call on timeout.
     // This class is thread-safe.
     class MonitorThread {
+        std::atomic<size_t> mSecondChanceCount{};
         mutable std::mutex mMutex;
-        mutable std::condition_variable mCond;
+        mutable std::condition_variable mCond GUARDED_BY(mMutex);
 
         // Ordered map of requests based on time of deadline.
         //
-        std::map<Handle, std::pair<std::shared_ptr<const Request>, std::function<void()>>>
+        std::map<Handle, std::pair<std::shared_ptr<const Request>, TimerCallback>>
                 mMonitorRequests GUARDED_BY(mMutex);
 
+        // Due to monotonic/steady clock inaccuracies during suspend,
+        // we allow an additional second chance waiting time to prevent
+        // false removal.
+
+        // This mSecondChanceRequests queue is almost always empty.
+        // Using a pair with the original handle allows lookup and keeps
+        // the Key unique.
+        std::map<std::pair<Handle /* new */, Handle /* original */>,
+                std::pair<std::shared_ptr<const Request>, TimerCallback>>
+                        mSecondChanceRequests GUARDED_BY(mMutex);
+
         RequestQueue& mTimeoutQueue; // locked internally, added to when request times out.
 
         // Worker thread variables
@@ -200,16 +307,23 @@
         std::thread mThread;
 
         void threadFunc();
-        Handle getUniqueHandle_l(std::chrono::milliseconds timeout) REQUIRES(mMutex);
+        Handle getUniqueHandle_l(Duration timeout) REQUIRES(mMutex) {
+            return getUniqueHandleForHandleType_l<
+                    HANDLE_TYPES, enum_as_value(HANDLE_TYPE::TIMEOUT)>(
+                mMonitorRequests, timeout);
+        }
 
       public:
         MonitorThread(RequestQueue &timeoutQueue);
         ~MonitorThread();
 
-        Handle add(std::shared_ptr<const Request> request, std::function<void()>&& func,
-                std::chrono::milliseconds timeout);
+        Handle add(std::shared_ptr<const Request> request, TimerCallback&& func,
+                Duration timeout);
         std::shared_ptr<const Request> remove(Handle handle);
         void copyRequests(std::vector<std::shared_ptr<const Request>>& requests) const;
+        size_t getSecondChanceCount() const {
+            return mSecondChanceCount.load(std::memory_order_relaxed);
+        }
     };
 
     // Analysis contains info deduced by analysisTimeout().
@@ -244,16 +358,6 @@
 
     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
 
diff --git a/media/utils/tests/Android.bp b/media/utils/tests/Android.bp
index 1024018..232cc4e 100644
--- a/media/utils/tests/Android.bp
+++ b/media/utils/tests/Android.bp
@@ -124,6 +124,27 @@
 }
 
 cc_test {
+    name: "mediautils_fixedstring_tests",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wextra",
+    ],
+
+    shared_libs: [
+        "libaudioutils",
+        "liblog",
+        "libmediautils",
+        "libutils",
+    ],
+
+    srcs: [
+        "mediautils_fixedstring_tests.cpp",
+    ],
+}
+
+cc_test {
     name: "mediautils_scopedstatistics_tests",
 
     cflags: [
diff --git a/media/utils/tests/mediautils_fixedstring_tests.cpp b/media/utils/tests/mediautils_fixedstring_tests.cpp
new file mode 100644
index 0000000..7ab9a9f
--- /dev/null
+++ b/media/utils/tests/mediautils_fixedstring_tests.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "mediautils_fixedstring_tests"
+
+#include <mediautils/FixedString.h>
+
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+using namespace android::mediautils;
+
+TEST(mediautils_fixedstring_tests, ctor) {
+    FixedString<8> s0("abcde");
+
+    ASSERT_FALSE(s0.empty());
+    ASSERT_EQ(8U, s0.capacity());
+
+    ASSERT_EQ(5U, s0.size());
+    ASSERT_EQ(3U, s0.remaining());
+    ASSERT_EQ(0, strcmp(s0.c_str(), "abcde"));
+
+    ASSERT_EQ(0, strcmp(s0.data(), "abcde"));
+
+    // overflow
+    FixedString<8> s1("abcdefghijk");
+    ASSERT_EQ(8U, s1.size());
+    ASSERT_TRUE(s1.full());
+    ASSERT_EQ(0U, s1.remaining());
+    ASSERT_EQ(0, strcmp(s1.c_str(), "abcdefgh"));
+
+    // overflow
+    FixedString<8> s2(std::string("abcdefghijk"));
+    ASSERT_TRUE(s2.full());
+
+    ASSERT_EQ(8U, s2.size());
+    ASSERT_EQ(0, strcmp(s2.c_str(), "abcdefgh"));
+
+    // complex
+    ASSERT_EQ(s1, s2);
+    ASSERT_EQ(FixedString<12>().append(s1), s2);
+    ASSERT_NE(s1, "bcd");
+
+    // string and stringview
+    ASSERT_EQ(s1.asString(), s1.asStringView());
+
+    FixedString30 s3;
+    s3 = std::string("abcd");
+    ASSERT_EQ(s3, "abcd");
+
+    s3.clear();
+    ASSERT_EQ(s3, "");
+    ASSERT_NE(s3, "abcd");
+    ASSERT_EQ(0U, s3.size());
+}
+
+TEST(mediautils_fixedstring_tests, append) {
+    FixedString<8> s0;
+    ASSERT_EQ(0U, s0.size());
+    ASSERT_EQ(0, strcmp(s0.c_str(), ""));
+    ASSERT_TRUE(s0.empty());
+    ASSERT_FALSE(s0.full());
+
+    s0.append("abc");
+    ASSERT_EQ(3U, s0.size());
+    ASSERT_EQ(0, strcmp(s0.c_str(), "abc"));
+
+    s0.append(std::string("d"));
+    ASSERT_EQ(4U, s0.size());
+    ASSERT_EQ(0, strcmp(s0.c_str(), "abcd"));
+
+    // overflow
+    s0.append("efghijk");
+    ASSERT_EQ(8U, s0.size());
+    ASSERT_EQ(0, strcmp(s0.c_str(), "abcdefgh"));
+    ASSERT_TRUE(s0.full());
+
+    // concatenated
+    ASSERT_EQ(FixedString62("abcd"),
+            FixedString<8>("ab").append("c").append(FixedString<8>("d")));
+    ASSERT_EQ(FixedString<12>("a").append(FixedString<12>("b")), "ab");
+}
+
+TEST(mediautils_fixedstring_tests, plus_equals) {
+    FixedString<8> s0;
+    ASSERT_EQ(0U, s0.size());
+    ASSERT_EQ(0, strcmp(s0.c_str(), ""));
+
+    s0 += "abc";
+    s0 += "def";
+    ASSERT_EQ(s0, "abcdef");
+}
+
+TEST(mediautils_fixedstring_tests, stream_operator) {
+    FixedString<8> s0('a');
+
+    s0 << 'b' << "c" << "d" << '\n';
+    ASSERT_EQ(s0, "abcd\n");
+}
+
+TEST(mediautils_fixedstring_tests, examples) {
+    FixedString30 s1(std::string("a"));
+    s1 << "bc" << 'd' << '\n';
+    s1 += "hello";
+
+    ASSERT_EQ(s1, "abcd\nhello");
+
+    FixedString30 s2;
+    for (const auto &c : s1.asStringView()) {
+        s2.append(c);
+    };
+    ASSERT_EQ(s1, s2);
+
+    FixedString30 s3(std::move(s1));
+}
+
+// Ensure type alias works fine as well.
+using FixedString1024 = FixedString<1024>;
+
+TEST(mediautils_fixedstring_tests, copy) {
+    FixedString1024 s0("abc");
+    FixedString62 s1(s0);
+
+    ASSERT_EQ(3U, s1.size());
+    ASSERT_EQ(0, strcmp(s1.c_str(), "abc"));
+    ASSERT_EQ(s0, s1);
+
+    FixedString<1024> s2(s1);
+    ASSERT_EQ(3U, s2.size());
+    ASSERT_EQ(0, strcmp(s2.c_str(), "abc"));
+    ASSERT_EQ(s2, "abc");
+    ASSERT_NE(s2, "def");
+    ASSERT_EQ(s2, std::string("abc"));
+    ASSERT_NE(s2, std::string("def"));
+    ASSERT_EQ(s1, s2);
+    ASSERT_EQ(s0, s2);
+    ASSERT_EQ(s2, FixedString62(FixedString1024("abc")));
+}
diff --git a/media/utils/tests/timecheck_tests.cpp b/media/utils/tests/timecheck_tests.cpp
index 6ebf44d..8236174 100644
--- a/media/utils/tests/timecheck_tests.cpp
+++ b/media/utils/tests/timecheck_tests.cpp
@@ -39,7 +39,7 @@
             timeoutRegistered = timeout;
             elapsedMsRegistered = elapsedMs;
             event = true;
-        }, 1000 /* msec */, false /* crash */);
+        }, 1000ms /* timeoutDuration */, {} /* secondChanceDuration */, false /* crash */);
     }
     ASSERT_TRUE(event);
     ASSERT_FALSE(timeoutRegistered);
@@ -58,7 +58,7 @@
             timeoutRegistered = timeout;
             elapsedMsRegistered = elapsedMs;
             event = true; // store-release, must be last.
-        }, 1 /* msec */, false /* crash */);
+        }, 1ms /* timeoutDuration */, {} /* secondChanceDuration */, false /* crash */);
         std::this_thread::sleep_for(100ms);
     }
     ASSERT_TRUE(event); // load-acquire, must be first.
@@ -69,4 +69,4 @@
 // 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
+} // namespace
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 8ae06d0..4653f96 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -4602,7 +4602,9 @@
         } else {
             getIAudioFlingerStatistics().event(code, elapsedMs);
         }
-    });
+    }, mediautils::TimeCheck::kDefaultTimeoutDuration,
+    mediautils::TimeCheck::kDefaultSecondChanceDuration,
+    true /* crashOnTimeout */);
 
     // Make sure we connect to Audio Policy Service before calling into AudioFlinger:
     //  - AudioFlinger can call into Audio Policy Service with its global mutex held
diff --git a/services/audioflinger/Effects.cpp b/services/audioflinger/Effects.cpp
index 72c378d..a80d729 100644
--- a/services/audioflinger/Effects.cpp
+++ b/services/audioflinger/Effects.cpp
@@ -1809,7 +1809,7 @@
         } else {
             getIEffectStatistics().event(code, elapsedMs);
         }
-    }, 0 /* timeoutMs */);
+    }, {} /* timeoutDuration */, {} /* secondChanceDuration */, false /* crashOnTimeout */);
     return BnEffect::onTransact(code, data, reply, flags);
 }
 
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 533e0b3..3d44aec 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -178,6 +178,11 @@
 // Direct output thread minimum sleep time in idle or active(underrun) state
 static const nsecs_t kDirectMinSleepTimeUs = 10000;
 
+// Minimum amount of time between checking to see if the timestamp is advancing
+// for underrun detection. If we check too frequently, we may not detect a
+// timestamp update and will falsely detect underrun.
+static const nsecs_t kMinimumTimeBetweenTimestampChecksNs = 150 /* ms */ * 1000;
+
 // The universal constant for ubiquitous 20ms value. The value of 20ms seems to provide a good
 // balance between power consumption and latency, and allows threads to be scheduled reliably
 // by the CFS scheduler.
@@ -2040,7 +2045,8 @@
         mFastTrackAvailMask(((1 << FastMixerState::sMaxFastTracks) - 1) & ~1),
         mHwSupportsPause(false), mHwPaused(false), mFlushPending(false),
         mLeftVolFloat(-1.0), mRightVolFloat(-1.0),
-        mDownStreamPatch{}
+        mDownStreamPatch{},
+        mIsTimestampAdvancing(kMinimumTimeBetweenTimestampChecksNs)
 {
     snprintf(mThreadName, kThreadNameLength, "AudioOut_%X", id);
     mNBLogWriter = audioFlinger->newWriter_l(kLogSize, mThreadName);
@@ -3146,10 +3152,7 @@
     auto backInserter = std::back_inserter(metadata.tracks);
     for (const sp<Track> &track : mActiveTracks) {
         // No track is invalid as this is called after prepareTrack_l in the same critical section
-        // Do not forward metadata for PatchTrack with unspecified stream type
-        if (track->streamType() != AUDIO_STREAM_PATCH) {
-            track->copyMetadataTo(backInserter);
-        }
+        track->copyMetadataTo(backInserter);
     }
     sendMetadataToBackend_l(metadata);
 }
@@ -5907,18 +5910,35 @@
     return trackCount;
 }
 
-bool AudioFlinger::PlaybackThread::checkRunningTimestamp()
+bool AudioFlinger::PlaybackThread::IsTimestampAdvancing::check(AudioStreamOut * output)
 {
+    // Check the timestamp to see if it's advancing once every 150ms. If we check too frequently, we
+    // could falsely detect that the frame position has stalled due to underrun because we haven't
+    // given the Audio HAL enough time to update.
+    const nsecs_t nowNs = systemTime();
+    if (nowNs - mPreviousNs < mMinimumTimeBetweenChecksNs) {
+        return mLatchedValue;
+    }
+    mPreviousNs = nowNs;
+    mLatchedValue = false;
+    // Determine if the presentation position is still advancing.
     uint64_t position = 0;
     struct timespec unused;
-    const status_t ret = mOutput->getPresentationPosition(&position, &unused);
+    const status_t ret = output->getPresentationPosition(&position, &unused);
     if (ret == NO_ERROR) {
-        if (position != mLastCheckedTimestampPosition) {
-            mLastCheckedTimestampPosition = position;
-            return true;
+        if (position != mPreviousPosition) {
+            mPreviousPosition = position;
+            mLatchedValue = true;
         }
     }
-    return false;
+    return mLatchedValue;
+}
+
+void AudioFlinger::PlaybackThread::IsTimestampAdvancing::clear()
+{
+    mLatchedValue = true;
+    mPreviousPosition = 0;
+    mPreviousNs = 0;
 }
 
 // isTrackAllowed_l() must be called with ThreadBase::mLock held
@@ -6353,9 +6373,9 @@
                 // No buffers for this track. Give it a few chances to
                 // fill a buffer, then remove it from active list.
                 // Only consider last track started for mixer state control
+                bool isTimestampAdvancing = mIsTimestampAdvancing.check(mOutput);
                 if (--(track->mRetryCount) <= 0) {
-                    const bool running = checkRunningTimestamp();
-                    if (running) { // still running, give us more time.
+                    if (isTimestampAdvancing) { // HAL is still playing audio, give us more time.
                         track->mRetryCount = kMaxTrackRetriesOffload;
                     } else {
                         ALOGV("BUFFER TIMEOUT: remove track(%d) from active list", trackId);
@@ -6936,9 +6956,9 @@
             } else {
                 // No buffers for this track. Give it a few chances to
                 // fill a buffer, then remove it from active list.
+                bool isTimestampAdvancing = mIsTimestampAdvancing.check(mOutput);
                 if (--(track->mRetryCount) <= 0) {
-                    const bool running = checkRunningTimestamp();
-                    if (running) { // still running, give us more time.
+                    if (isTimestampAdvancing) { // HAL is still playing audio, give us more time.
                         track->mRetryCount = kMaxTrackRetriesOffload;
                     } else {
                         ALOGV("OffloadThread: BUFFER TIMEOUT: remove track(%d) from active list",
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 2f85378..ad5617d 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -1172,7 +1172,7 @@
     volatile int32_t                mSuspended;
 
     int64_t                         mBytesWritten;
-    int64_t                         mFramesWritten; // not reset on standby
+    std::atomic<int64_t>            mFramesWritten; // not reset on standby
     int64_t                         mLastFramesWritten = -1; // track changes in timestamp
                                                              // server frames written.
     int64_t                         mSuspendedFrames; // not reset on standby
@@ -1386,6 +1386,7 @@
     virtual     bool        hasFastMixer() const = 0;
     virtual     FastTrackUnderruns getFastTrackUnderruns(size_t fastIndex __unused) const
                                 { FastTrackUnderruns dummy; return dummy; }
+                const std::atomic<int64_t>& framesWritten() const { return mFramesWritten; }
 
 protected:
                 // accessed by both binder threads and within threadLoop(), lock on mutex needed
@@ -1403,13 +1404,38 @@
 
                 std::atomic_bool mCheckOutputStageEffects{};
 
-                // A differential check on the timestamps to see if there is a change in the
-                // timestamp frame position between the last call to checkRunningTimestamp.
-                uint64_t mLastCheckedTimestampPosition = ~0LL;
 
-                bool checkRunningTimestamp();
+                // Provides periodic checking for timestamp advancement for underrun detection.
+                class IsTimestampAdvancing {
+                public:
+                    // The timestamp will not be checked any faster than the specified time.
+                    IsTimestampAdvancing(nsecs_t minimumTimeBetweenChecksNs)
+                        :   mMinimumTimeBetweenChecksNs(minimumTimeBetweenChecksNs)
+                    {
+                        clear();
+                    }
+                    // Check if the presentation position has advanced in the last periodic time.
+                    bool check(AudioStreamOut * output);
+                    // Clear the internal state when the playback state changes for the output
+                    // stream.
+                    void clear();
+                private:
+                    // The minimum time between timestamp checks.
+                    const nsecs_t mMinimumTimeBetweenChecksNs;
+                    // Add differential check on the timestamps to see if there is a change in the
+                    // timestamp frame position between the last call to check.
+                    uint64_t mPreviousPosition;
+                    // The time at which the last check occurred, to ensure we don't check too
+                    // frequently, giving the Audio HAL enough time to update its timestamps.
+                    nsecs_t mPreviousNs;
+                    // The valued is latched so we don't check timestamps too frequently.
+                    bool mLatchedValue;
+                };
+                IsTimestampAdvancing mIsTimestampAdvancing;
 
-    virtual     void flushHw_l() { mLastCheckedTimestampPosition = ~0LL; }
+    virtual     void flushHw_l() {
+                    mIsTimestampAdvancing.clear();
+                }
 };
 
 class MixerThread : public PlaybackThread {
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index 2677ab3..20bfbb0 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -397,6 +397,8 @@
     int64_t             mLogStartFrames = 0;    // Timestamp frames at start()
     double              mLogLatencyMs = 0.;     // Track the last log latency
 
+    bool                mLogForceVolumeUpdate = true; // force volume update to TrackMetrics.
+
     TrackMetrics        mTrackMetrics;
 
     bool                mServerLatencySupported = false;
diff --git a/services/audioflinger/TrackMetrics.h b/services/audioflinger/TrackMetrics.h
index 30d69ab..6fc70d6 100644
--- a/services/audioflinger/TrackMetrics.h
+++ b/services/audioflinger/TrackMetrics.h
@@ -64,7 +64,6 @@
                     AMEDIAMETRICS_PROP_EVENT_VALUE_BEGINAUDIOINTERVALGROUP, devices.c_str());
         }
         ++mIntervalCount;
-        mIntervalStartTimeNs = systemTime();
     }
 
     void logConstructor(pid_t creatorPid, uid_t creatorUid, int32_t internalTrackId,
@@ -90,11 +89,9 @@
     // Called when we are removed from the Thread.
     void logEndInterval() {
         std::lock_guard l(mLock);
-        if (mIntervalStartTimeNs != 0) {
-            const int64_t elapsedTimeNs = systemTime() - mIntervalStartTimeNs;
-            mIntervalStartTimeNs = 0;
-            mCumulativeTimeNs += elapsedTimeNs;
-            mDeviceTimeNs += elapsedTimeNs;
+        if (mLastVolumeChangeTimeNs != 0) {
+            logVolume_l(mVolume); // flush out the last volume.
+            mLastVolumeChangeTimeNs = 0;
         }
     }
 
@@ -133,20 +130,8 @@
 
     // may be called multiple times during an interval
     void logVolume(float volume) {
-        const int64_t timeNs = systemTime();
         std::lock_guard l(mLock);
-        if (mStartVolumeTimeNs == 0) {
-            mDeviceVolume = mVolume = volume;
-            mLastVolumeChangeTimeNs = mStartVolumeTimeNs = timeNs;
-            updateMinMaxVolume(0, mVolume);
-            return;
-        }
-        const int64_t durationNs = timeNs - mLastVolumeChangeTimeNs;
-        updateMinMaxVolume(durationNs, mVolume);
-        mDeviceVolume = (mDeviceVolume * (mLastVolumeChangeTimeNs - mStartVolumeTimeNs) +
-            mVolume * durationNs) / (timeNs - mStartVolumeTimeNs);
-        mVolume = volume;
-        mLastVolumeChangeTimeNs = timeNs;
+        logVolume_l(volume);
     }
 
     // Use absolute numbers returned by AudioTrackShared.
@@ -158,6 +143,7 @@
     }
 
 private:
+
     // no lock required - all arguments and constants.
     void deliverDeviceMetrics(const char *eventName, const char *devices) const {
         mediametrics::LogItem(mMetricsId)
@@ -167,6 +153,23 @@
            .record();
     }
 
+    void logVolume_l(float volume) REQUIRES(mLock) {
+        const int64_t timeNs = systemTime();
+        const int64_t durationNs = mLastVolumeChangeTimeNs == 0
+                ? 0 : timeNs - mLastVolumeChangeTimeNs;
+        if (durationNs > 0) {
+            // See West's algorithm for weighted averages
+            // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
+            mDeviceVolume += (mVolume - mDeviceVolume) * durationNs
+                      / (durationNs + mDeviceTimeNs);
+            mDeviceTimeNs += durationNs;
+            mCumulativeTimeNs += durationNs;
+        }
+        updateMinMaxVolume(durationNs, mVolume); // always update.
+        mVolume = volume;
+        mLastVolumeChangeTimeNs = timeNs;
+    }
+
     void deliverCumulativeMetrics(const char *eventName) const REQUIRES(mLock) {
         if (mIntervalCount > 0) {
             mediametrics::LogItem item(mMetricsId);
@@ -199,14 +202,12 @@
         // mDevices is not reset by resetIntervalGroupMetrics.
 
         mIntervalCount = 0;
-        mIntervalStartTimeNs = 0;
         // mCumulativeTimeNs is not reset by resetIntervalGroupMetrics.
         mDeviceTimeNs = 0;
 
         mVolume = 0.f;
         mDeviceVolume = 0.f;
-        mStartVolumeTimeNs = 0;
-        mLastVolumeChangeTimeNs = 0;
+        mLastVolumeChangeTimeNs = 0;  // last time volume logged, cleared on endInterval
         mMinVolume = AMEDIAMETRICS_INITIAL_MIN_VOLUME;
         mMaxVolume = AMEDIAMETRICS_INITIAL_MAX_VOLUME;
         mMinVolumeDurationNs = 0;
@@ -230,14 +231,12 @@
 
     // Number of intervals and playing time
     int32_t           mIntervalCount GUARDED_BY(mLock) = 0;
-    int64_t           mIntervalStartTimeNs GUARDED_BY(mLock) = 0;
-    int64_t           mCumulativeTimeNs GUARDED_BY(mLock) = 0;
-    int64_t           mDeviceTimeNs GUARDED_BY(mLock) = 0;
+    int64_t           mCumulativeTimeNs GUARDED_BY(mLock) = 0; // total time.
+    int64_t           mDeviceTimeNs GUARDED_BY(mLock) = 0;     // time on device.
 
     // Average volume
-    double            mVolume GUARDED_BY(mLock) = 0.f;
-    double            mDeviceVolume GUARDED_BY(mLock) = 0.f;
-    int64_t           mStartVolumeTimeNs GUARDED_BY(mLock) = 0;
+    double            mVolume GUARDED_BY(mLock) = 0.f;       // last set volume.
+    double            mDeviceVolume GUARDED_BY(mLock) = 0.f; // running average volume.
     int64_t           mLastVolumeChangeTimeNs GUARDED_BY(mLock) = 0;
 
     // Min/Max volume
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index 6135020..95ca855 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -1094,12 +1094,22 @@
                     __func__, mId, (int)mThreadIoHandle);
         }
 
-        // states to reset position info for non-offloaded/direct tracks
-        if (!isOffloaded() && !isDirect()
+        PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
+
+        // states to reset position info for pcm tracks
+        if (audio_is_linear_pcm(mFormat)
                 && (state == IDLE || state == STOPPED || state == FLUSHED)) {
             mFrameMap.reset();
+
+            if (!isFastTrack() && (isDirect() || isOffloaded())) {
+                // Start point of track -> sink frame map. If the HAL returns a
+                // frame position smaller than the first written frame in
+                // updateTrackFrameInfo, the timestamp can be interpolated
+                // instead of using a larger value.
+                mFrameMap.push(mAudioTrackServerProxy->framesReleased(),
+                               playbackThread->framesWritten());
+            }
         }
-        PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
         if (isFastTrack()) {
             // refresh fast track underruns on start because that field is never cleared
             // by the fast mixer; furthermore, the same track can be recycled, i.e. start
@@ -1123,6 +1133,7 @@
                     .mPosition[ExtendedTimestamp::LOCATION_KERNEL];
             mLogLatencyMs = 0.;
         }
+        mLogForceVolumeUpdate = true;  // at least one volume logged for metrics when starting.
 
         if (status == NO_ERROR || status == ALREADY_EXISTS) {
             // for streaming tracks, remove the buffer read stop limit.
@@ -1394,12 +1405,21 @@
     if (mFinalVolume != volume) { // Compare to an epsilon if too many meaningless updates
         mFinalVolume = volume;
         setMetadataHasChanged();
-        mTrackMetrics.logVolume(volume);
+        mLogForceVolumeUpdate = true;
+    }
+    if (mLogForceVolumeUpdate) {
+        mLogForceVolumeUpdate = false;
+        mTrackMetrics.logVolume(mFinalVolume);
     }
 }
 
 void AudioFlinger::PlaybackThread::Track::copyMetadataTo(MetadataInserter& backInserter) const
 {
+    // Do not forward metadata for PatchTrack with unspecified stream type
+    if (mStreamType == AUDIO_STREAM_PATCH) {
+        return;
+    }
+
     playback_track_metadata_v7_t metadata;
     metadata.base = {
             .usage = mAttr.usage,
diff --git a/services/audiopolicy/config/bluetooth_with_le_audio_policy_configuration_7_0.xml b/services/audiopolicy/config/bluetooth_with_le_audio_policy_configuration_7_0.xml
index e7908eb..c453dea 100644
--- a/services/audiopolicy/config/bluetooth_with_le_audio_policy_configuration_7_0.xml
+++ b/services/audiopolicy/config/bluetooth_with_le_audio_policy_configuration_7_0.xml
@@ -12,6 +12,7 @@
         </mixPort>
         <!-- Le Audio Audio Ports -->
         <mixPort name="le audio output" role="source" />
+        <mixPort name="le audio broadcast output" role="source" />
         <mixPort name="le audio input" role="sink">
             <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                      samplingRates="8000 16000 24000 32000 44100 48000"
@@ -47,6 +48,7 @@
         <devicePort tagName="BLE Headset Out" type="AUDIO_DEVICE_OUT_BLE_HEADSET" role="sink"/>
         <devicePort tagName="BLE Speaker Out" type="AUDIO_DEVICE_OUT_BLE_SPEAKER" role="sink"/>
         <devicePort tagName="BLE Headset In" type="AUDIO_DEVICE_IN_BLE_HEADSET" role="source"/>
+        <devicePort tagName="BLE Broadcast Out" type="AUDIO_DEVICE_OUT_BLE_BROADCAST" role="sink"/>
     </devicePorts>
     <routes>
         <route type="mix" sink="BT A2DP Out"
@@ -63,5 +65,7 @@
                sources="BLE Headset In"/>
         <route type="mix" sink="BLE Speaker Out"
                sources="le audio output"/>
+        <route type="mix" sink="BLE Broadcast Out"
+               sources="le audio broadcast output"/>
     </routes>
 </module>
diff --git a/services/audiopolicy/enginedefault/src/Engine.cpp b/services/audiopolicy/enginedefault/src/Engine.cpp
index d4d514d..08d6453 100644
--- a/services/audiopolicy/enginedefault/src/Engine.cpp
+++ b/services/audiopolicy/enginedefault/src/Engine.cpp
@@ -195,6 +195,12 @@
                     AUDIO_DEVICE_OUT_BLUETOOTH_A2DP, AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
                     AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER, }));
         }
+        // If connected to a dock, never use the device speaker for calls
+        if (!availableOutputDevices.getDevicesFromTypes({AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET})
+                .isEmpty()) {
+            availableOutputDevices.remove(
+                    availableOutputDevices.getDevicesFromTypes({AUDIO_DEVICE_OUT_SPEAKER}));
+        }
         } break;
     case STRATEGY_ACCESSIBILITY: {
         // do not route accessibility prompts to a digital output currently configured with a
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 83de3e2..8586f6f 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -1884,7 +1884,8 @@
     //    (see b/200293124, the incorrect selection of AUDIO_OUTPUT_FLAG_VOIP_RX).
     // 3: the output supporting the exact channel mask
     // 4: the output with a higher channel count than requested
-    // 5: the output with a higher sampling rate than requested
+    // 5: the output with the highest sampling rate if the requested sample rate is
+    //    greater than default sampling rate
     // 6: the output with the highest number of requested performance flags
     // 7: the output with the bit depth the closest to the requested one
     // 8: the primary output
@@ -1944,8 +1945,7 @@
         }
 
         // sampling rate match
-        if (samplingRate > SAMPLE_RATE_HZ_DEFAULT &&
-                samplingRate <= outputDesc->getSamplingRate()) {
+        if (samplingRate > SAMPLE_RATE_HZ_DEFAULT) {
             currentMatchCriteria[4] = outputDesc->getSamplingRate();
         }
 
diff --git a/services/audiopolicy/service/AudioPolicyService.cpp b/services/audiopolicy/service/AudioPolicyService.cpp
index 50a536b..4f3ea6c 100644
--- a/services/audiopolicy/service/AudioPolicyService.cpp
+++ b/services/audiopolicy/service/AudioPolicyService.cpp
@@ -1342,7 +1342,9 @@
         } else {
             getIAudioPolicyServiceStatistics().event(code, elapsedMs);
         }
-    });
+    }, mediautils::TimeCheck::kDefaultTimeoutDuration,
+    mediautils::TimeCheck::kDefaultSecondChanceDuration,
+    true /* crashOnTimeout */);
 
     switch (code) {
         case SHELL_COMMAND_TRANSACTION: {
diff --git a/services/audiopolicy/service/SpatializerPoseController.cpp b/services/audiopolicy/service/SpatializerPoseController.cpp
index 5e9a0e1..9735479 100644
--- a/services/audiopolicy/service/SpatializerPoseController.cpp
+++ b/services/audiopolicy/service/SpatializerPoseController.cpp
@@ -67,13 +67,13 @@
 
 // Screen is considered to be unstable (not still) if it has moved significantly within the last
 // time window of this duration.
-constexpr auto kScreenStillnessWindowDuration = 3s;
+constexpr auto kScreenStillnessWindowDuration = 750ms;
 
 // Screen is considered to have moved significantly if translated by this much (in meter, approx).
 constexpr float kScreenStillnessTranslationThreshold = 0.1f;
 
 // Screen is considered to have moved significantly if rotated by this much (in radians, approx).
-constexpr float kScreenStillnessRotationThreshold = 7.0f / 180 * M_PI;
+constexpr float kScreenStillnessRotationThreshold = 15.0f / 180 * M_PI;
 
 // Time units for system clock ticks. This is what the Sensor Framework timestamps represent and
 // what we use for pose filtering.
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 80410ab..a106cd6 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -1698,7 +1698,13 @@
         return STATUS_ERROR(ERROR_ILLEGAL_ARGUMENT, msg.string());
     }
 
-    if (CameraServiceProxyWrapper::isCameraDisabled()) {
+    userid_t clientUserId = multiuser_get_user_id(clientUid);
+    int callingUid = CameraThreadState::getCallingUid();
+    if (clientUid == USE_CALLING_UID) {
+        clientUserId = multiuser_get_user_id(callingUid);
+    }
+
+    if (CameraServiceProxyWrapper::isCameraDisabled(clientUserId)) {
         String8 msg =
                 String8::format("Camera disabled by device policy");
         ALOGE("%s: %s", __FUNCTION__, msg.string());
diff --git a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp
index 69175cc..dae5eea 100644
--- a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp
+++ b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp
@@ -262,11 +262,11 @@
     sessionStats->onClose(latencyMs);
 }
 
-bool CameraServiceProxyWrapper::isCameraDisabled() {
+bool CameraServiceProxyWrapper::isCameraDisabled(int userId) {
     sp<ICameraServiceProxy> proxyBinder = getCameraServiceProxy();
     if (proxyBinder == nullptr) return true;
     bool ret = false;
-    auto status = proxyBinder->isCameraDisabled(&ret);
+    auto status = proxyBinder->isCameraDisabled(userId, &ret);
     if (!status.isOk()) {
         ALOGE("%s: Failed during camera disabled query: %s", __FUNCTION__,
                 status.exceptionMessage().c_str());
diff --git a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h
index e34a8f0..eb818d1 100644
--- a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h
+++ b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h
@@ -97,7 +97,7 @@
     static int getRotateAndCropOverride(String16 packageName, int lensFacing, int userId);
 
     // Detect if the camera is disabled by device policy.
-    static bool isCameraDisabled();
+    static bool isCameraDisabled(int userId);
 };
 
 } // android
diff --git a/services/oboeservice/AAudioServiceEndpointMMAP.cpp b/services/oboeservice/AAudioServiceEndpointMMAP.cpp
index 3f18b95..ea817ab 100644
--- a/services/oboeservice/AAudioServiceEndpointMMAP.cpp
+++ b/services/oboeservice/AAudioServiceEndpointMMAP.cpp
@@ -71,6 +71,8 @@
 aaudio_result_t AAudioServiceEndpointMMAP::open(const aaudio::AAudioStreamRequest &request) {
     aaudio_result_t result = AAUDIO_OK;
     copyFrom(request.getConstantConfiguration());
+    mRequestedDeviceId = getDeviceId();
+
     mMmapClient.attributionSource = request.getAttributionSource();
     // TODO b/182392769: use attribution source util
     mMmapClient.attributionSource.uid = VALUE_OR_FATAL(
@@ -115,7 +117,7 @@
 
     const audio_attributes_t attributes = getAudioAttributesFrom(this);
 
-    mRequestedDeviceId = deviceId = getDeviceId();
+    deviceId = mRequestedDeviceId;
 
     // Fill in config
     config.format = audioFormat;
@@ -151,6 +153,10 @@
     audio_session_t sessionId = AAudioConvert_aaudioToAndroidSessionId(requestedSessionId);
 
     // Open HAL stream. Set mMmapStream
+    ALOGD("%s trying to open MMAP stream with format=%#x, "
+          "sample_rate=%u, channel_mask=%#x, device=%d",
+          __func__, config.format, config.sample_rate,
+          config.channel_mask, deviceId);
     status_t status = MmapStreamInterface::openMmapStream(streamDirection,
                                                           &attributes,
                                                           &config,
@@ -221,6 +227,9 @@
 
 error:
     close();
+    // restore original requests
+    setDeviceId(mRequestedDeviceId);
+    setSessionId(requestedSessionId);
     return result;
 }