Merge changes Iec35c735,Ic3238144

* changes:
  Allow CPP<->legacy and NDK<->legacy to coexist in the same module
  Declare `ConversionResult` in `android` namespace
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index a22ec19..9aa896c 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -34,12 +34,15 @@
                 }
             ],
             "file_patterns": ["(?i)drm|crypto"]
-        }
-    ],
-
-    "imports": [
+        },
         {
-            "path": "frameworks/av/drm/mediadrm/plugins"
+            "name": "CtsMediaDrmFrameworkTestCases",
+            "options" : [
+                {
+                    "include-annotation": "android.platform.test.annotations.Presubmit"
+                }
+            ],
+            "file_patterns": ["(?i)drm|crypto"]
         }
     ],
 
diff --git a/media/codec2/hidl/client/client.cpp b/media/codec2/hidl/client/client.cpp
index 0acf7d7..9359e29 100644
--- a/media/codec2/hidl/client/client.cpp
+++ b/media/codec2/hidl/client/client.cpp
@@ -1582,6 +1582,10 @@
     return mOutputBufferQueue->outputBuffer(block, input, output);
 }
 
+void Codec2Client::Component::pollForRenderedFrames(FrameEventHistoryDelta* delta) {
+    mOutputBufferQueue->pollForRenderedFrames(delta);
+}
+
 void Codec2Client::Component::setOutputSurfaceMaxDequeueCount(
         int maxDequeueCount) {
     mOutputBufferQueue->updateMaxDequeueBufferCount(maxDequeueCount);
diff --git a/media/codec2/hidl/client/include/codec2/hidl/client.h b/media/codec2/hidl/client/include/codec2/hidl/client.h
index 49d9b28..2fdca29 100644
--- a/media/codec2/hidl/client/include/codec2/hidl/client.h
+++ b/media/codec2/hidl/client/include/codec2/hidl/client.h
@@ -23,6 +23,7 @@
 #include <C2Param.h>
 #include <C2.h>
 
+#include <gui/FrameTimestamps.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <hidl/HidlSupport.h>
 #include <utils/StrongPointer.h>
@@ -408,6 +409,9 @@
             const QueueBufferInput& input,
             QueueBufferOutput* output);
 
+    // Retrieve frame event history from the output surface.
+    void pollForRenderedFrames(FrameEventHistoryDelta* delta);
+
     // Set max dequeue count for output surface.
     void setOutputSurfaceMaxDequeueCount(int maxDequeueCount);
 
diff --git a/media/codec2/hidl/client/include/codec2/hidl/output.h b/media/codec2/hidl/client/include/codec2/hidl/output.h
index a13edf3..35a0224 100644
--- a/media/codec2/hidl/client/include/codec2/hidl/output.h
+++ b/media/codec2/hidl/client/include/codec2/hidl/output.h
@@ -17,6 +17,7 @@
 #ifndef CODEC2_HIDL_V1_0_UTILS_OUTPUT_BUFFER_QUEUE
 #define CODEC2_HIDL_V1_0_UTILS_OUTPUT_BUFFER_QUEUE
 
+#include <gui/FrameTimestamps.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <codec2/hidl/1.0/types.h>
 #include <codec2/hidl/1.2/types.h>
@@ -60,6 +61,9 @@
             const BnGraphicBufferProducer::QueueBufferInput& input,
             BnGraphicBufferProducer::QueueBufferOutput* output);
 
+    // Retrieve frame event history from the output surface.
+    void pollForRenderedFrames(FrameEventHistoryDelta* delta);
+
     // Call holdBufferQueueBlock() on output blocks in the given workList.
     // The OutputBufferQueue will take the ownership of output blocks.
     //
diff --git a/media/codec2/hidl/client/output.cpp b/media/codec2/hidl/client/output.cpp
index f789030..dd10691 100644
--- a/media/codec2/hidl/client/output.cpp
+++ b/media/codec2/hidl/client/output.cpp
@@ -476,6 +476,12 @@
     return OK;
 }
 
+void OutputBufferQueue::pollForRenderedFrames(FrameEventHistoryDelta* delta) {
+    if (mIgbp) {
+        mIgbp->getFrameTimestamps(delta);
+    }
+}
+
 void OutputBufferQueue::holdBufferQueueBlocks(
         const std::list<std::unique_ptr<C2Work>>& workList) {
     forEachBlock(workList,
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.cpp b/media/codec2/sfplugin/CCodecBufferChannel.cpp
index 4bf8dce..fc82426 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.cpp
+++ b/media/codec2/sfplugin/CCodecBufferChannel.cpp
@@ -899,7 +899,7 @@
     }
 
     // TODO: revisit this after C2Fence implementation.
-    android::IGraphicBufferProducer::QueueBufferInput qbi(
+    IGraphicBufferProducer::QueueBufferInput qbi(
             timestampNs,
             false, // droppable
             dataSpace,
@@ -963,9 +963,9 @@
     }
     SetMetadataToGralloc4Handle(dataSpace, hdrStaticInfo, hdrDynamicInfo, block.handle());
 
-    // we don't have dirty regions
-    qbi.setSurfaceDamage(Region::INVALID_REGION);
-    android::IGraphicBufferProducer::QueueBufferOutput qbo;
+    qbi.setSurfaceDamage(Region::INVALID_REGION); // we don't have dirty regions
+    qbi.getFrameTimestamps = true; // we need to know when a frame is rendered
+    IGraphicBufferProducer::QueueBufferOutput qbo;
     status_t result = mComponent->queueToOutputSurface(block, qbi, &qbo);
     if (result != OK) {
         ALOGI("[%s] queueBuffer failed: %d", mName, result);
@@ -984,10 +984,107 @@
     int64_t mediaTimeUs = 0;
     (void)buffer->meta()->findInt64("timeUs", &mediaTimeUs);
     mCCodecCallback->onOutputFramesRendered(mediaTimeUs, timestampNs);
+    trackReleasedFrame(qbo, mediaTimeUs, timestampNs);
+    processRenderedFrames(qbo.frameTimestamps);
 
     return OK;
 }
 
+void CCodecBufferChannel::initializeFrameTrackingFor(ANativeWindow * window) {
+    int hasPresentFenceTimes = 0;
+    window->query(window, NATIVE_WINDOW_FRAME_TIMESTAMPS_SUPPORTS_PRESENT, &hasPresentFenceTimes);
+    mHasPresentFenceTimes = hasPresentFenceTimes == 1;
+    if (mHasPresentFenceTimes) {
+        ALOGI("Using latch times for frame rendered signals - present fences not supported");
+    }
+    mTrackedFrames.clear();
+}
+
+void CCodecBufferChannel::trackReleasedFrame(const IGraphicBufferProducer::QueueBufferOutput& qbo,
+                                             int64_t mediaTimeUs, int64_t desiredRenderTimeNs) {
+    // If the render time is earlier than now, then we're suggesting it should be rendered ASAP,
+    // so track the frame as if the desired render time is now.
+    int64_t nowNs = systemTime(SYSTEM_TIME_MONOTONIC);
+    if (desiredRenderTimeNs < nowNs) {
+        desiredRenderTimeNs = nowNs;
+    }
+    // We've just released a frame to the surface, so keep track of it and later check to see if it
+    // is actually rendered.
+    TrackedFrame frame;
+    frame.number = qbo.nextFrameNumber - 1;
+    frame.mediaTimeUs = mediaTimeUs;
+    frame.desiredRenderTimeNs = desiredRenderTimeNs;
+    frame.latchTime = -1;
+    frame.presentFence = nullptr;
+    mTrackedFrames.push_back(frame);
+}
+
+void CCodecBufferChannel::processRenderedFrames(const FrameEventHistoryDelta& deltas) {
+    // Grab the latch times and present fences from the frame event deltas
+    for (const auto& delta : deltas) {
+        for (auto& frame : mTrackedFrames) {
+            if (delta.getFrameNumber() == frame.number) {
+                delta.getLatchTime(&frame.latchTime);
+                delta.getDisplayPresentFence(&frame.presentFence);
+            }
+        }
+    }
+
+    // Scan all frames and check to see if the frames that SHOULD have been rendered by now, have,
+    // in fact, been rendered.
+    int64_t nowNs = systemTime(SYSTEM_TIME_MONOTONIC);
+    while (!mTrackedFrames.empty()) {
+        TrackedFrame & frame = mTrackedFrames.front();
+        // Frames that should have been rendered at least 100ms in the past are checked
+        if (frame.desiredRenderTimeNs > nowNs - 100*1000*1000LL) {
+            break;
+        }
+
+        // If we don't have a render time by now, then consider the frame as dropped
+        int64_t renderTimeNs = getRenderTimeNs(frame);
+        if (renderTimeNs != -1) {
+            mCCodecCallback->onOutputFramesRendered(frame.mediaTimeUs, renderTimeNs);
+        }
+        mTrackedFrames.pop_front();
+    }
+}
+
+int64_t CCodecBufferChannel::getRenderTimeNs(const TrackedFrame& frame) {
+    // If the device doesn't have accurate present fence times, then use the latch time as a proxy
+    if (!mHasPresentFenceTimes) {
+        if (frame.latchTime == -1) {
+            ALOGD("no latch time for frame %d", (int) frame.number);
+            return -1;
+        }
+        return frame.latchTime;
+    }
+
+    if (frame.presentFence == nullptr) {
+        ALOGW("no present fence for frame %d", (int) frame.number);
+        return -1;
+    }
+
+    nsecs_t actualRenderTimeNs = frame.presentFence->getSignalTime();
+
+    if (actualRenderTimeNs == Fence::SIGNAL_TIME_INVALID) {
+        ALOGW("invalid signal time for frame %d", (int) frame.number);
+        return -1;
+    }
+
+    if (actualRenderTimeNs == Fence::SIGNAL_TIME_PENDING) {
+        ALOGD("present fence has not fired for frame %d", (int) frame.number);
+        return -1;
+    }
+
+    return actualRenderTimeNs;
+}
+
+void CCodecBufferChannel::pollForRenderedBuffers() {
+    FrameEventHistoryDelta delta;
+    mComponent->pollForRenderedFrames(&delta);
+    processRenderedFrames(delta);
+}
+
 status_t CCodecBufferChannel::discardBuffer(const sp<MediaCodecBuffer> &buffer) {
     ALOGV("[%s] discardBuffer: %p", mName, buffer.get());
     bool released = false;
@@ -1604,6 +1701,8 @@
         Mutexed<Output>::Locked output(mOutput);
         output->buffers.reset();
     }
+    // reset the frames that are being tracked for onFrameRendered callbacks
+    mTrackedFrames.clear();
 }
 
 void CCodecBufferChannel::release() {
@@ -1672,6 +1771,8 @@
             output->buffers->flushStash();
         }
     }
+    // reset the frames that are being tracked for onFrameRendered callbacks
+    mTrackedFrames.clear();
 }
 
 void CCodecBufferChannel::onWorkDone(
@@ -2140,7 +2241,7 @@
         output->surface = newSurface;
         output->generation = generation;
     }
-
+    initializeFrameTrackingFor(static_cast<ANativeWindow *>(newSurface.get()));
     return OK;
 }
 
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.h b/media/codec2/sfplugin/CCodecBufferChannel.h
index 61fb06f..73299d7 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.h
+++ b/media/codec2/sfplugin/CCodecBufferChannel.h
@@ -18,6 +18,7 @@
 
 #define CCODEC_BUFFER_CHANNEL_H_
 
+#include <deque>
 #include <map>
 #include <memory>
 #include <vector>
@@ -88,6 +89,7 @@
             const sp<MediaCodecBuffer> &buffer) override;
     virtual status_t renderOutputBuffer(
             const sp<MediaCodecBuffer> &buffer, int64_t timestampNs) override;
+    virtual void pollForRenderedBuffers() override;
     virtual status_t discardBuffer(const sp<MediaCodecBuffer> &buffer) override;
     virtual void getInputBufferArray(Vector<sp<MediaCodecBuffer>> *array) override;
     virtual void getOutputBufferArray(Vector<sp<MediaCodecBuffer>> *array) override;
@@ -263,6 +265,14 @@
         bool mRunning;
     };
 
+    struct TrackedFrame {
+        uint64_t number;
+        int64_t mediaTimeUs;
+        int64_t desiredRenderTimeNs;
+        nsecs_t latchTime;
+        sp<Fence> presentFence;
+    };
+
     void feedInputBufferIfAvailable();
     void feedInputBufferIfAvailableInternal();
     status_t queueInputBufferInternal(sp<MediaCodecBuffer> buffer,
@@ -275,6 +285,12 @@
     void ensureDecryptDestination(size_t size);
     int32_t getHeapSeqNum(const sp<hardware::HidlMemory> &memory);
 
+    void initializeFrameTrackingFor(ANativeWindow * window);
+    void trackReleasedFrame(const IGraphicBufferProducer::QueueBufferOutput& qbo,
+                            int64_t mediaTimeUs, int64_t desiredRenderTimeNs);
+    void processRenderedFrames(const FrameEventHistoryDelta& delta);
+    int64_t getRenderTimeNs(const TrackedFrame& frame);
+
     QueueSync mSync;
     sp<MemoryDealer> mDealer;
     sp<IMemory> mDecryptDestination;
@@ -316,6 +332,9 @@
 
     sp<MemoryDealer> makeMemoryDealer(size_t heapSize);
 
+    std::deque<TrackedFrame> mTrackedFrames;
+    bool mHasPresentFenceTimes;
+
     struct OutputSurface {
         sp<Surface> surface;
         uint32_t generation;
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index d6028d9..ccbe995 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -6751,6 +6751,8 @@
         info->checkReadFence("onOutputBufferDrained before queueBuffer");
         err = mCodec->mNativeWindow->queueBuffer(
                     mCodec->mNativeWindow.get(), info->mGraphicBuffer.get(), info->mFenceFd);
+        // TODO(b/266211548): Poll the native window for rendered buffers, since when queueing
+        // buffers, the frame event history delta is retrieved.
         info->mFenceFd = -1;
         if (err == OK) {
             info->mStatus = BufferInfo::OWNED_BY_NATIVE_WINDOW;
diff --git a/media/libstagefright/ACodecBufferChannel.cpp b/media/libstagefright/ACodecBufferChannel.cpp
index 88b15ae..529ae97 100644
--- a/media/libstagefright/ACodecBufferChannel.cpp
+++ b/media/libstagefright/ACodecBufferChannel.cpp
@@ -477,6 +477,10 @@
     return OK;
 }
 
+void ACodecBufferChannel::pollForRenderedBuffers() {
+    // TODO(b/266211548): Poll the native window for rendered buffers.
+}
+
 status_t ACodecBufferChannel::discardBuffer(const sp<MediaCodecBuffer> &buffer) {
     std::shared_ptr<const std::vector<const BufferInfo>> array(
             std::atomic_load(&mInputBuffers));
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index cdfd2dd..e794750 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -530,6 +530,7 @@
     kWhatOutputFramesRendered = 'outR',
     kWhatOutputBuffersChanged = 'outC',
     kWhatFirstTunnelFrameReady = 'ftfR',
+    kWhatPollForRenderedBuffers = 'plrb',
 };
 
 class BufferCallback : public CodecBase::BufferCallback {
@@ -4578,6 +4579,14 @@
             break;
         }
 
+        case kWhatPollForRenderedBuffers:
+        {
+            if (isExecuting()) {
+                mBufferChannel->pollForRenderedBuffers();
+            }
+            break;
+        }
+
         case kWhatSignalEndOfInputStream:
         {
             if (!isExecuting() || !mHaveInputSurface) {
@@ -5309,7 +5318,6 @@
 size_t MediaCodec::CreateFramesRenderedMessage(
         const std::list<FrameRenderTracker::Info> &done, sp<AMessage> &msg) {
     size_t index = 0;
-
     for (std::list<FrameRenderTracker::Info>::const_iterator it = done.cbegin();
             it != done.cend(); ++it) {
         if (it->getRenderTimeNs() < 0) {
@@ -5358,11 +5366,13 @@
         int64_t mediaTimeUs = -1;
         buffer->meta()->findInt64("timeUs", &mediaTimeUs);
 
+        bool noRenderTime = false;
         int64_t renderTimeNs = 0;
         if (!msg->findInt64("timestampNs", &renderTimeNs)) {
             // use media timestamp if client did not request a specific render timestamp
             ALOGV("using buffer PTS of %lld", (long long)mediaTimeUs);
             renderTimeNs = mediaTimeUs * 1000;
+            noRenderTime = true;
         }
 
         if (mSoftRenderer != NULL) {
@@ -5380,6 +5390,29 @@
                 }
             }
         }
+
+        // If rendering to the screen, then schedule a time in the future to poll to see if this
+        // frame was ever rendered to seed onFrameRendered callbacks.
+        if (mIsSurfaceToScreen) {
+            // can't initialize this in the constructor because the Looper parent class needs to be
+            // initialized first
+            if (mMsgPollForRenderedBuffers == nullptr) {
+                mMsgPollForRenderedBuffers = new AMessage(kWhatPollForRenderedBuffers, this);
+            }
+            // Schedule the poll to occur 100ms after the render time - should be safe for
+            // determining if the frame was ever rendered. If no render time was specified, the
+            // presentation timestamp is used instead, which almost certainly occurs in the past,
+            // since it's almost always a zero-based offset from the start of the stream. In these
+            // scenarios, we expect the frame to be rendered with no delay.
+            int64_t delayUs = noRenderTime ? 0 : renderTimeNs / 1000 - ALooper::GetNowUs();
+            delayUs += 100 * 1000; /* 100ms in microseconds */
+            status_t err =
+                    mMsgPollForRenderedBuffers->postUnique(/* token= */ mMsgPollForRenderedBuffers,
+                                                           delayUs);
+            if (err != OK) {
+                ALOGE("unexpected failure to post pollForRenderedBuffers: %d", err);
+            }
+        }
         status_t err = mBufferChannel->renderOutputBuffer(buffer, renderTimeNs);
 
         if (err == NO_INIT) {
diff --git a/media/libstagefright/include/ACodecBufferChannel.h b/media/libstagefright/include/ACodecBufferChannel.h
index da962d1..f3b0600 100644
--- a/media/libstagefright/include/ACodecBufferChannel.h
+++ b/media/libstagefright/include/ACodecBufferChannel.h
@@ -97,6 +97,7 @@
             const sp<MediaCodecBuffer> &buffer) override;
     virtual status_t renderOutputBuffer(
             const sp<MediaCodecBuffer> &buffer, int64_t timestampNs) override;
+    virtual void pollForRenderedBuffers() override;
     virtual status_t discardBuffer(const sp<MediaCodecBuffer> &buffer) override;
     virtual void getInputBufferArray(Vector<sp<MediaCodecBuffer>> *array) override;
     virtual void getOutputBufferArray(Vector<sp<MediaCodecBuffer>> *array) override;
diff --git a/media/libstagefright/include/media/stagefright/CodecBase.h b/media/libstagefright/include/media/stagefright/CodecBase.h
index 48721ec..aa02151 100644
--- a/media/libstagefright/include/media/stagefright/CodecBase.h
+++ b/media/libstagefright/include/media/stagefright/CodecBase.h
@@ -407,6 +407,14 @@
      */
     virtual status_t renderOutputBuffer(
             const sp<MediaCodecBuffer> &buffer, int64_t timestampNs) = 0;
+
+    /**
+     * Poll for updates about rendered buffers.
+     *
+     * Triggers callbacks to CodecCallback::onOutputFramesRendered.
+     */
+    virtual void pollForRenderedBuffers() = 0;
+
     /**
      * Discard a buffer to the underlying CodecBase object.
      *
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index 703f7ad..aaa62d9 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -622,6 +622,9 @@
                                                  // when low latency is on
     int64_t mInputBufferCounter;  // number of input buffers queued since last reset/flush
 
+    // A rescheduleable message that periodically polls for rendered buffers
+    sp<AMessage> mMsgPollForRenderedBuffers;
+
     class ReleaseSurface;
     std::unique_ptr<ReleaseSurface> mReleaseSurface;
 
diff --git a/media/libstagefright/tests/mediacodec/MediaCodecTest.cpp b/media/libstagefright/tests/mediacodec/MediaCodecTest.cpp
index a8e64b6..ecdaac5 100644
--- a/media/libstagefright/tests/mediacodec/MediaCodecTest.cpp
+++ b/media/libstagefright/tests/mediacodec/MediaCodecTest.cpp
@@ -70,6 +70,7 @@
     MOCK_METHOD(status_t, discardBuffer, (const sp<MediaCodecBuffer> &buffer), (override));
     MOCK_METHOD(void, getInputBufferArray, (Vector<sp<MediaCodecBuffer>> *array), (override));
     MOCK_METHOD(void, getOutputBufferArray, (Vector<sp<MediaCodecBuffer>> *array), (override));
+    MOCK_METHOD(void, pollForRenderedBuffers, (), (override));
 };
 
 class MockCodec : public CodecBase {
diff --git a/media/module/foundation/ALooper.cpp b/media/module/foundation/ALooper.cpp
index a276722..61bac02 100644
--- a/media/module/foundation/ALooper.cpp
+++ b/media/module/foundation/ALooper.cpp
@@ -69,6 +69,10 @@
     return systemTime(SYSTEM_TIME_MONOTONIC) / 1000LL;
 }
 
+int64_t ALooper::getNowUs() {
+    return GetNowUs();
+}
+
 ALooper::ALooper()
     : mRunningLocally(false) {
     // clean up stale AHandlers. Doing it here instead of in the destructor avoids
@@ -170,11 +174,11 @@
 
     int64_t whenUs;
     if (delayUs > 0) {
-        int64_t nowUs = GetNowUs();
+        int64_t nowUs = getNowUs();
         whenUs = (delayUs > INT64_MAX - nowUs ? INT64_MAX : nowUs + delayUs);
 
     } else {
-        whenUs = GetNowUs();
+        whenUs = getNowUs();
     }
 
     List<Event>::iterator it = mEventQueue.begin();
@@ -185,6 +189,7 @@
     Event event;
     event.mWhenUs = whenUs;
     event.mMessage = msg;
+    event.mToken = nullptr;
 
     if (it == mEventQueue.begin()) {
         mQueueChangedCondition.signal();
@@ -193,7 +198,57 @@
     mEventQueue.insert(it, event);
 }
 
+status_t ALooper::postUnique(const sp<AMessage> &msg, const sp<RefBase> &token, int64_t delayUs) {
+    if (token == nullptr) {
+        return -EINVAL;
+    }
+    Mutex::Autolock autoLock(mLock);
+
+    int64_t whenUs;
+    if (delayUs > 0) {
+        int64_t nowUs = getNowUs();
+        whenUs = (delayUs > INT64_MAX - nowUs ? INT64_MAX : nowUs + delayUs);
+    } else {
+        whenUs = getNowUs();
+    }
+
+    // We only need to wake the loop up if we're rescheduling to the earliest event in the queue.
+    // This needs to be checked now, before we reschedule the message, in case this message is
+    // already at the beginning of the queue.
+    bool shouldAwakeLoop = mEventQueue.empty() || whenUs < mEventQueue.begin()->mWhenUs;
+
+    // Erase any previously-posted event with this token.
+    for (auto i = mEventQueue.begin(); i != mEventQueue.end();) {
+        if (i->mToken == token) {
+            i = mEventQueue.erase(i);
+        } else {
+            ++i;
+        }
+    }
+
+    // Find the insertion point for the rescheduled message.
+    List<Event>::iterator i = mEventQueue.begin();
+    while (i != mEventQueue.end() && i->mWhenUs <= whenUs) {
+        ++i;
+    }
+
+    Event event;
+    event.mWhenUs = whenUs;
+    event.mMessage = msg;
+    event.mToken = token;
+    mEventQueue.insert(i, event);
+
+    // If we rescheduled the event to be earlier than the first event, then we need to wake up the
+    // looper earlier than it was previously scheduled to be woken up. Otherwise, it can sleep until
+    // the previous wake-up time and then go to sleep again if needed.
+    if (shouldAwakeLoop){
+        mQueueChangedCondition.signal();
+    }
+    return OK;
+}
+
 bool ALooper::loop() {
+
     Event event;
 
     {
@@ -206,7 +261,7 @@
             return true;
         }
         int64_t whenUs = (*mEventQueue.begin()).mWhenUs;
-        int64_t nowUs = GetNowUs();
+        int64_t nowUs = getNowUs();
 
         if (whenUs > nowUs) {
             int64_t delayUs = whenUs - nowUs;
diff --git a/media/module/foundation/AMessage.cpp b/media/module/foundation/AMessage.cpp
index 5c99cc9..b61dc47 100644
--- a/media/module/foundation/AMessage.cpp
+++ b/media/module/foundation/AMessage.cpp
@@ -430,6 +430,17 @@
     return OK;
 }
 
+status_t AMessage::postUnique(const sp<RefBase> &token, int64_t delayUs) {
+    sp<ALooper> looper = mLooper.promote();
+    if (looper == NULL) {
+        ALOGW("failed to post message as target looper for handler %d is gone.",
+              mTarget);
+        return -ENOENT;
+    }
+
+    return looper->postUnique(this, token, delayUs);
+}
+
 status_t AMessage::postAndAwaitResponse(sp<AMessage> *response) {
     sp<ALooper> looper = mLooper.promote();
     if (looper == NULL) {
diff --git a/media/module/foundation/include/media/stagefright/foundation/ALooper.h b/media/module/foundation/include/media/stagefright/foundation/ALooper.h
index 09c469b..60bda1f 100644
--- a/media/module/foundation/include/media/stagefright/foundation/ALooper.h
+++ b/media/module/foundation/include/media/stagefright/foundation/ALooper.h
@@ -59,6 +59,9 @@
     }
 
 protected:
+    // overridable by test harness
+    virtual int64_t getNowUs();
+
     virtual ~ALooper();
 
 private:
@@ -67,6 +70,7 @@
     struct Event {
         int64_t mWhenUs;
         sp<AMessage> mMessage;
+        sp<RefBase> mToken;
     };
 
     Mutex mLock;
@@ -87,9 +91,14 @@
 
     // START --- methods used only by AMessage
 
-    // posts a message on this looper with the given timeout
+    // Posts a message on this looper with the given timeout.
     void post(const sp<AMessage> &msg, int64_t delayUs);
 
+    // Post a message uniquely on this looper with the given timeout.
+    // This method ensures that there is exactly one message with the same token pending posted on
+    // this looper after the call returns. A null token will result in an EINVAL error status.
+    status_t postUnique(const sp<AMessage> &msg, const sp<RefBase> &token, int64_t delayUs);
+
     // creates a reply token to be used with this looper
     sp<AReplyToken> createReplyToken();
     // waits for a response for the reply token.  If status is OK, the response
diff --git a/media/module/foundation/include/media/stagefright/foundation/AMessage.h b/media/module/foundation/include/media/stagefright/foundation/AMessage.h
index 960212a..6f73597 100644
--- a/media/module/foundation/include/media/stagefright/foundation/AMessage.h
+++ b/media/module/foundation/include/media/stagefright/foundation/AMessage.h
@@ -141,6 +141,11 @@
 
     status_t post(int64_t delayUs = 0);
 
+    // Post a message uniquely to its target with the given timeout.
+    // This method ensures that there is exactly one message with the same token posted to its
+    // target after the call returns. A null token will result in an EINVAL error status.
+    status_t postUnique(const sp<RefBase> &token, int64_t delayUs = 0);
+
     // Posts the message to its target and waits for a response (or error)
     // before returning.
     status_t postAndAwaitResponse(sp<AMessage> *response);
diff --git a/media/module/foundation/tests/AMessage_test.cpp b/media/module/foundation/tests/AMessage_test.cpp
index 2b11326..e08ed77 100644
--- a/media/module/foundation/tests/AMessage_test.cpp
+++ b/media/module/foundation/tests/AMessage_test.cpp
@@ -17,18 +17,43 @@
 //#define LOG_NDEBUG 0
 #define LOG_TAG "AData_test"
 
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <utils/RefBase.h>
 
 #include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/foundation/ALooper.h>
 
 using namespace android;
 
-class AMessageTest : public ::testing::Test {
+using ::testing::InSequence;
+using ::testing::NiceMock;
+
+class LooperWithSettableClock : public ALooper {
+public:
+  LooperWithSettableClock() : mClockUs(0) {}
+
+  void setClockUs(int64_t nowUs) {
+    mClockUs = nowUs;
+  }
+
+  int64_t getNowUs() override {
+    return mClockUs;
+  }
+
+private:
+  int64_t mClockUs;
 };
 
+timespec millis100 = {0, 100L*1000*1000};
 
-TEST(AMessage_tests, item_manipulation) {
+class MockHandler : public AHandler {
+public:
+    MOCK_METHOD(void, onMessageReceived, (const sp<AMessage>&), (override));
+};
+
+TEST(AMessage_tests, settersAndGetters) {
   sp<AMessage> m1 = new AMessage();
 
   m1->setInt32("value", 2);
@@ -120,6 +145,171 @@
   EXPECT_TRUE(m1->findInt32("alittlelonger", &i32));
 
   EXPECT_NE(OK, m1->removeEntryByName("notpresent"));
-
 }
 
+TEST(AMessage_tests, deliversMultipleMessagesInOrderImmediately) {
+  sp<NiceMock<MockHandler>> mockHandler = new NiceMock<MockHandler>;
+  sp<LooperWithSettableClock> looper = new LooperWithSettableClock();
+  looper->registerHandler(mockHandler);
+
+  sp<AMessage> msgNow1 = new AMessage(0, mockHandler);
+  msgNow1->post();
+  sp<AMessage> msgNow2 = new AMessage(0, mockHandler);
+  msgNow2->post();
+
+  {
+    InSequence inSequence;
+    EXPECT_CALL(*mockHandler, onMessageReceived(msgNow1)).Times(1);
+    EXPECT_CALL(*mockHandler, onMessageReceived(msgNow2)).Times(1);
+  }
+  looper->start();
+  nanosleep(&millis100, nullptr); // just enough time for the looper thread to run
+}
+
+TEST(AMessage_tests, doesNotDeliverDelayedMessageImmediately) {
+  sp<NiceMock<MockHandler>> mockHandler = new NiceMock<MockHandler>;
+  sp<LooperWithSettableClock> looper = new LooperWithSettableClock();
+  looper->registerHandler(mockHandler);
+
+  sp<AMessage> msgNow = new AMessage(0, mockHandler);
+  msgNow->post();
+  sp<AMessage> msgDelayed = new AMessage(0, mockHandler);
+  msgDelayed->post(100);
+
+  EXPECT_CALL(*mockHandler, onMessageReceived(msgNow)).Times(1);
+  // note: never called
+  EXPECT_CALL(*mockHandler, onMessageReceived(msgDelayed)).Times(0);
+  looper->start();
+  nanosleep(&millis100, nullptr); // just enough time for the looper thread to run
+}
+
+TEST(AMessage_tests, deliversDelayedMessagesInSequence) {
+  sp<NiceMock<MockHandler>> mockHandler = new NiceMock<MockHandler>;
+  sp<LooperWithSettableClock> looper = new LooperWithSettableClock();
+  looper->registerHandler(mockHandler);
+
+  sp<AMessage> msgIn500 = new AMessage(0, mockHandler);
+  msgIn500->post(500);
+  sp<AMessage> msgNow = new AMessage(0, mockHandler);
+  msgNow->post();
+  sp<AMessage> msgIn100 = new AMessage(0, mockHandler);
+  msgIn100->post(100);
+  // not expected to be received
+  sp<AMessage> msgIn1000 = new AMessage(0, mockHandler);
+  msgIn1000->post(1000);
+
+  looper->setClockUs(500);
+  {
+    InSequence inSequence;
+
+    EXPECT_CALL(*mockHandler, onMessageReceived(msgNow)).Times(1);
+    EXPECT_CALL(*mockHandler, onMessageReceived(msgIn100)).Times(1);
+    EXPECT_CALL(*mockHandler, onMessageReceived(msgIn500)).Times(1);
+  }
+  // note: never called
+  EXPECT_CALL(*mockHandler, onMessageReceived(msgIn1000)).Times(0);
+  looper->start();
+  nanosleep(&millis100, nullptr); // just enough time for the looper thread to run
+}
+
+TEST(AMessage_tests, deliversDelayedUniqueMessage) {
+  sp<NiceMock<MockHandler>> mockHandler = new NiceMock<MockHandler>;
+  sp<LooperWithSettableClock> looper = new LooperWithSettableClock();
+  looper->registerHandler(mockHandler);
+
+  sp<AMessage> msg = new AMessage(0, mockHandler);
+  msg->postUnique(msg, 50);
+
+  looper->setClockUs(50);
+  EXPECT_CALL(*mockHandler, onMessageReceived(msg)).Times(1);
+  looper->start();
+  nanosleep(&millis100, nullptr); // just enough time for the looper thread to run
+}
+
+TEST(AMessage_tests, deliversImmediateUniqueMessage) {
+  sp<NiceMock<MockHandler>> mockHandler = new NiceMock<MockHandler>;
+  // note: we don't need to set the clock, but we do want a stable clock that doesn't advance
+  sp<LooperWithSettableClock> looper = new LooperWithSettableClock();
+  looper->registerHandler(mockHandler);
+
+  sp<AMessage> msg = new AMessage(0, mockHandler);
+  msg->postUnique(msg, 0);
+
+  EXPECT_CALL(*mockHandler, onMessageReceived(msg)).Times(1);
+  looper->start();
+  nanosleep(&millis100, nullptr); // just enough time for the looper thread to run
+}
+
+TEST(AMessage_tests, doesNotDeliverUniqueMessageAfterRescheduleLater) {
+  sp<NiceMock<MockHandler>> mockHandler = new NiceMock<MockHandler>;
+  sp<LooperWithSettableClock> looper = new LooperWithSettableClock();
+  looper->registerHandler(mockHandler);
+
+  sp<AMessage> msg = new AMessage(0, mockHandler);
+  msg->postUnique(msg, 50);
+  msg->postUnique(msg, 100); // reschedule for later
+
+  looper->setClockUs(50); // if the message is correctly rescheduled, it should not be delivered
+  // Never called because the message was rescheduled to a later point in time
+  EXPECT_CALL(*mockHandler, onMessageReceived(msg)).Times(0);
+  looper->start();
+  nanosleep(&millis100, nullptr); // just enough time for the looper thread to run
+}
+
+TEST(AMessage_tests, deliversUniqueMessageAfterRescheduleEarlier) {
+  sp<NiceMock<MockHandler>> mockHandler = new NiceMock<MockHandler>;
+  sp<LooperWithSettableClock> looper = new LooperWithSettableClock();
+  looper->registerHandler(mockHandler);
+
+  sp<AMessage> msg = new AMessage(0, mockHandler);
+  msg->postUnique(msg, 100);
+  msg->postUnique(msg, 50); // reschedule to fire earlier
+
+  looper->setClockUs(50); // if the message is rescheduled correctly, it should be delivered
+  EXPECT_CALL(*mockHandler, onMessageReceived(msg)).Times(1);
+  looper->start();
+  nanosleep(&millis100, nullptr); // just enough time for the looper thread to run
+}
+
+TEST(AMessage_tests, deliversSameMessageTwice) {
+  sp<NiceMock<MockHandler>> mockHandler = new NiceMock<MockHandler>;
+  sp<LooperWithSettableClock> looper = new LooperWithSettableClock();
+  looper->registerHandler(mockHandler);
+
+  sp<AMessage> msg = new AMessage(0, mockHandler);
+  msg->post(50);
+  msg->post(100);
+
+  looper->setClockUs(100);
+  EXPECT_CALL(*mockHandler, onMessageReceived(msg)).Times(2);
+  looper->start();
+  nanosleep(&millis100, nullptr); // just enough time for the looper thread to run
+}
+
+// When messages are posted twice with the same token, it will only be delivered once after being
+// rescheduled.
+TEST(AMessage_tests, deliversUniqueMessageOnce) {
+  sp<NiceMock<MockHandler>> mockHandler = new NiceMock<MockHandler>;
+  sp<LooperWithSettableClock> looper = new LooperWithSettableClock();
+  looper->registerHandler(mockHandler);
+
+  sp<AMessage> msg1 = new AMessage(0, mockHandler);
+  msg1->postUnique(msg1, 50);
+  sp<AMessage> msg2 = new AMessage(0, mockHandler);
+  msg2->postUnique(msg1, 75); // note, using the same token as msg1
+
+  looper->setClockUs(100);
+  EXPECT_CALL(*mockHandler, onMessageReceived(msg1)).Times(0);
+  EXPECT_CALL(*mockHandler, onMessageReceived(msg2)).Times(1);
+  looper->start();
+  nanosleep(&millis100, nullptr); // just enough time for the looper thread to run
+}
+
+TEST(AMessage_tests, postUnique_withNullToken_returnsInvalidArgument) {
+  sp<NiceMock<MockHandler>> mockHandler = new NiceMock<MockHandler>;
+  sp<ALooper> looper = new ALooper();
+  looper->registerHandler(mockHandler);
+
+  sp<AMessage> msg = new AMessage(0, mockHandler);
+  EXPECT_EQ(msg->postUnique(nullptr, 0), -EINVAL);
+}
diff --git a/media/module/foundation/tests/Android.bp b/media/module/foundation/tests/Android.bp
index e72ce43..c409dd2 100644
--- a/media/module/foundation/tests/Android.bp
+++ b/media/module/foundation/tests/Android.bp
@@ -20,10 +20,14 @@
 
     shared_libs: [
         "liblog",
-        "libstagefright_foundation",
         "libutils",
     ],
 
+    static_libs: [
+        "libstagefright_foundation",
+        "libgmock",
+    ],
+
     srcs: [
         "AData_test.cpp",
         "AMessage_test.cpp",