Merge "Support AAudioStream_flushFromFrame." into main
diff --git a/media/libaaudio/examples/utils/AAudioSimplePlayer.h b/media/libaaudio/examples/utils/AAudioSimplePlayer.h
index a585078..a413c4b 100644
--- a/media/libaaudio/examples/utils/AAudioSimplePlayer.h
+++ b/media/libaaudio/examples/utils/AAudioSimplePlayer.h
@@ -120,7 +120,8 @@
                          AAudioStream_dataCallback dataCallback = nullptr,
                          AAudioStream_errorCallback errorCallback = nullptr,
                          void *userContext = nullptr,
-                         AAudioStream_presentationEndCallback presentationEndCallback = nullptr) {
+                         AAudioStream_presentationEndCallback presentationEndCallback = nullptr,
+                         AAudioStream_partialDataCallback partialDataCallback = nullptr) {
         aaudio_result_t result = AAUDIO_OK;
 
         // Use an AAudioStreamBuilder to contain requested parameters.
@@ -142,6 +143,9 @@
             AAudioStreamBuilder_setPresentationEndCallback(
                     builder, presentationEndCallback, userContext);
         }
+        if (partialDataCallback != nullptr) {
+            AAudioStreamBuilder_setPartialDataCallback(builder, partialDataCallback, userContext);
+        }
         //AAudioStreamBuilder_setFramesPerDataCallback(builder, CALLBACK_SIZE_FRAMES);
         //AAudioStreamBuilder_setBufferCapacityInFrames(builder, 48 * 8);
 
diff --git a/media/libaaudio/include/aaudio/AAudio.h b/media/libaaudio/include/aaudio/AAudio.h
index 051600b..3e5e9ff 100644
--- a/media/libaaudio/include/aaudio/AAudio.h
+++ b/media/libaaudio/include/aaudio/AAudio.h
@@ -2645,6 +2645,70 @@
 AAUDIO_API aaudio_result_t AAudioStream_setOffloadEndOfStream(AAudioStream* _Nonnull stream)
         __INTRODUCED_IN(36);
 
+/**
+ * The values are defined to be used for the accuracy requirement when calling
+ * {@link AAudioStream_flushFromFrame}.
+ */
+typedef enum AAudio_FlushFromAccuracy : int32_t {
+    /**
+     * There is not requirement for frame accuracy when flushing, it is up to the framework
+     * to select a right position to flush from.
+     */
+    AAUDIO_FLUSH_FROM_ACCURACY_UNDEFINED = 0,
+
+    /**
+     * The stream must be flushed from the requested position. If it is not possible to flush
+     * from the requested position, the stream must not be flushed.
+     */
+    AAUDIO_FLUSH_FROM_FRAME_ACCURATE = 1
+} AAudio_FlushFromAccuracy;
+
+/**
+ * Flush all data from given position. If this operation returns successfully, the following
+ * data will be written from the returned position.
+ *
+ * This method will only work when the performance mode is
+ * {@link AAUDIO_PERFORMANCE_MODE_POWER_SAVING_OFFLOADED}.
+ *
+ * The requested position must not be negative or greater than the written frames. The current
+ * written position can be known by querying {@link AAudioStream_getFramesWritten}.
+ *
+ * When clients request to flush from a certain position, the audio system will return the actual
+ * flushed position based on the requested position, playback latency, etc. The written position
+ * will be updated as the actual flush position. All data behind actual flush position will be
+ * flushed. The client can provide data from actual flush position at next write operation or data
+ * callback request. When the stream is flushed, the stream end will be reset. The client must not
+ * write any data before this function returns. Otherwise, the data will be corrupted. When the
+ * method returns successfully and the stream is active, the client must write data immediately
+ * if little audio data remains. Otherwise, the stream will underrun.
+ *
+ * If apps prefer data callback, it is suggested to use {@link AAudioStream_partialDataCallback}.
+ * In that case, after the stream is flushed successfully by calling this method, the app can just
+ * fill partial data from the data callback instead of as much (partial) data as possible. That
+ * can help avoid underrun after successfully calling this method.
+ *
+ * @param stream reference provided by AAudioStreamBuilder_openStream().
+ * @param accuracy the accuracy requirement when flushing. The value must be one of the valid
+ *                 AAudio_FlushFromAccuracy value.
+ * @param[in|out] position the start point in frames to flush the stream. If flushing from frame
+ *                         is supported for the stream, the position will be updated as the actual
+ *                         flush from position when successfully flush or the suggested position
+ *                         to flush from if it cannot flush from the requested position. If there
+ *                         is not enough data to safely flush, position will remain the same.
+ * @return AAUDIO_OK if the stream is successfully flushed.
+ *         AAUDIO_ERROR_UNIMPLEMENTED if it is not supported by the device.
+ *         AAUDIO_ERROR_ILLEGAL_ARGUMENT if the stream is not an output offload stream or the
+ *         accuracy is not one of valid AAudio_FlushFromAccuracy values.
+ *         AAUDIO_ERROR_OUT_OF_RANGE if the provided position is negative or is greater than the
+ *         frames written or the stream cannot flush from the requested position and
+ *         AAUDIO_FLUSH_FROM_FRAME_ACCURATE is requested.
+ *         AAUDIO_ERROR_DISCONNECTED if aaudio service is dead or the stream is disconnected.
+ */
+AAUDIO_API aaudio_result_t AAudioStream_flushFromFrame(
+        AAudioStream* _Nonnull stream,
+        AAudio_FlushFromAccuracy accuracy,
+        int64_t* _Nonnull inOutPosition) __INTRODUCED_IN(37);
+
 /************************************************************************************
  * Helper functions for AAudio MMAP.
  * AAudio MMAP data path uses a memory region that is shared between the hardware and
diff --git a/media/libaaudio/src/binding/AAudioBinderAdapter.cpp b/media/libaaudio/src/binding/AAudioBinderAdapter.cpp
index ee7480b..bcf3353 100644
--- a/media/libaaudio/src/binding/AAudioBinderAdapter.cpp
+++ b/media/libaaudio/src/binding/AAudioBinderAdapter.cpp
@@ -166,4 +166,17 @@
     return result;
 }
 
+aaudio_result_t AAudioBinderAdapter::updateTimestamp(
+        const aaudio::AAudioHandleInfo &streamHandleInfo) {
+    if (streamHandleInfo.getServiceLifetimeId() != mServiceLifetimeId) {
+        return AAUDIO_ERROR_DISCONNECTED;
+    }
+    aaudio_result_t result;
+    Status status = mDelegate->updateTimestamp(streamHandleInfo.getHandle(), &result);
+    if (!status.isOk()) {
+        result = AAudioConvert_androidToAAudioResult(statusTFromBinderStatus(status));
+    }
+    return result;
+}
+
 }  // namespace aaudio
diff --git a/media/libaaudio/src/binding/AAudioBinderAdapter.h b/media/libaaudio/src/binding/AAudioBinderAdapter.h
index 301150f..fe68245 100644
--- a/media/libaaudio/src/binding/AAudioBinderAdapter.h
+++ b/media/libaaudio/src/binding/AAudioBinderAdapter.h
@@ -60,6 +60,8 @@
     aaudio_result_t exitStandby(const AAudioHandleInfo& streamHandleInfo,
                                 AudioEndpointParcelable &parcelable) override;
 
+    aaudio_result_t updateTimestamp(const AAudioHandleInfo& streamHandleInfo) override;
+
 private:
     IAAudioService* const mDelegate;
     // A unique id to recognize the service that the adapter connected to.
diff --git a/media/libaaudio/src/binding/AAudioBinderClient.cpp b/media/libaaudio/src/binding/AAudioBinderClient.cpp
index 439d5af..3c2319b 100644
--- a/media/libaaudio/src/binding/AAudioBinderClient.cpp
+++ b/media/libaaudio/src/binding/AAudioBinderClient.cpp
@@ -206,3 +206,10 @@
 
     return service->exitStandby(streamHandleInfo, endpointOut);
 }
+
+aaudio_result_t AAudioBinderClient::updateTimestamp(const AAudioHandleInfo& streamHandleInfo) {
+    std::shared_ptr<AAudioServiceInterface> service = getAAudioService();
+    if (service.get() == nullptr) return AAUDIO_ERROR_NO_SERVICE;
+
+    return service->updateTimestamp(streamHandleInfo);
+}
diff --git a/media/libaaudio/src/binding/AAudioBinderClient.h b/media/libaaudio/src/binding/AAudioBinderClient.h
index 66d3295..865eb4a 100644
--- a/media/libaaudio/src/binding/AAudioBinderClient.h
+++ b/media/libaaudio/src/binding/AAudioBinderClient.h
@@ -116,6 +116,8 @@
     aaudio_result_t exitStandby(const AAudioHandleInfo& streamHandleInfo,
                                 AudioEndpointParcelable &endpointOut) override;
 
+    aaudio_result_t updateTimestamp(const AAudioHandleInfo& streamHandleInfo) override;
+
     void onStreamChange(aaudio_handle_t /*handle*/, int32_t /*opcode*/, int32_t /*value*/) {
         // TODO This is just a stub so we can have a client Binder to pass to the service.
         // TODO Implemented in a later CL.
diff --git a/media/libaaudio/src/binding/AAudioServiceInterface.h b/media/libaaudio/src/binding/AAudioServiceInterface.h
index 79f498b..2630b59 100644
--- a/media/libaaudio/src/binding/AAudioServiceInterface.h
+++ b/media/libaaudio/src/binding/AAudioServiceInterface.h
@@ -108,6 +108,15 @@
      */
     virtual aaudio_result_t exitStandby(const AAudioHandleInfo& streamHandleInfo,
                                         AudioEndpointParcelable &parcelable) = 0;
+
+    /**
+     * AAudio service will send timestamp periodically. Client call this method to trigger
+     * a timestamp update immediately.
+     *
+     * @param streamHandleInfo the stream handle
+     * @return
+     */
+    virtual aaudio_result_t updateTimestamp(const AAudioHandleInfo& streamHandleInfo) = 0;
 };
 
 } /* namespace aaudio */
diff --git a/media/libaaudio/src/binding/aidl/aaudio/IAAudioService.aidl b/media/libaaudio/src/binding/aidl/aaudio/IAAudioService.aidl
index 485c2e2..768ebe7 100644
--- a/media/libaaudio/src/binding/aidl/aaudio/IAAudioService.aidl
+++ b/media/libaaudio/src/binding/aidl/aaudio/IAAudioService.aidl
@@ -80,4 +80,6 @@
                               int clientThreadId);
 
     int exitStandby(int streamHandle, out Endpoint endpoint);
+
+    int updateTimestamp(int streamHandle);
 }
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index b3e6ef6..a8f7f5a 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -515,11 +515,7 @@
     // Start data callback thread.
     if (result == AAUDIO_OK && isDataCallbackSet()) {
         // Launch the callback loop thread.
-        int64_t periodNanos = mCallbackFrames
-                              * AAUDIO_NANOS_PER_SECOND
-                              / getSampleRate();
-        mCallbackEnabled.store(true);
-        result = createThread_l(periodNanos, aaudio_callback_thread_proc, this);
+        result = startCallback_l();
     }
     if (result != AAUDIO_OK) {
         setState(originalState);
@@ -563,6 +559,12 @@
     }
 }
 
+aaudio_result_t AudioStreamInternal::startCallback_l() {
+    int64_t periodNanos = mCallbackFrames * AAUDIO_NANOS_PER_SECOND / getSampleRate();
+    mCallbackEnabled.store(true);
+    return createThread_l(periodNanos, aaudio_callback_thread_proc, this);
+}
+
 aaudio_result_t AudioStreamInternal::requestStop_l() {
     aaudio_result_t result = stopCallback_l();
     if (result != AAUDIO_OK) {
diff --git a/media/libaaudio/src/client/AudioStreamInternal.h b/media/libaaudio/src/client/AudioStreamInternal.h
index 06131c5..0b7bb07 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.h
+++ b/media/libaaudio/src/client/AudioStreamInternal.h
@@ -155,6 +155,8 @@
      */
     bool isClockModelInControl() const;
 
+    aaudio_result_t startCallback_l() REQUIRES(mStreamLock);
+
     IsochronousClockModel    mClockModel;      // timing model for chasing the HAL
 
     std::unique_ptr<AudioEndpoint> mAudioEndpoint;   // source for reads or sink for writes
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
index 2346e00..b01bac3 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
@@ -503,6 +503,62 @@
     mCallbackCV.notify_one();
 }
 
+aaudio_result_t AudioStreamInternalPlay::flushFromFrame_l(
+        AAudio_FlushFromAccuracy accuracy, int64_t* position) {
+    if (getServiceHandle() == AAUDIO_HANDLE_INVALID) {
+        ALOGD("%s() mServiceStreamHandle invalid", __func__);
+        return AAUDIO_ERROR_DISCONNECTED;
+    }
+    if (isDisconnected()) {
+        ALOGD("%s() but DISCONNECTED", __func__);
+        return AAUDIO_ERROR_DISCONNECTED;
+    }
+
+    aaudio_result_t result = AAUDIO_OK;
+    {
+        std::lock_guard _endpointLock(mEndpointMutex);
+        int64_t framesWritten = getFramesWritten();
+        if (framesWritten < *position) {
+            ALOGE("%s(), the requested position is not yet written", __func__);
+            result = AAUDIO_ERROR_OUT_OF_RANGE;
+        }
+
+        // The position is updated from the server, it may not be very accurate if the stream has
+        // been active for a while. In that case, updates the latest timestamp and then get the
+        // actual rewind position again.
+        if (aaudio_result_t res = mServiceInterface.updateTimestamp(mServiceStreamHandleInfo);
+                res != AAUDIO_OK) {
+            ALOGE("%s() failed to update timestamp, error=%d", __func__, res);
+            return res;
+        }
+        processCommands();
+        const int64_t safePosition = getFramesRead() + mOffloadSafeMarginInFrames;
+        if (safePosition > framesWritten) {
+            ALOGE("%s() do not have enough data, safePosition=%jd, frameWritten=%jd",
+                  __func__, safePosition, framesWritten);
+            return AAUDIO_ERROR_OUT_OF_RANGE;
+        }
+        int64_t actualPosition = std::max(safePosition, *position);
+        if (accuracy == AAUDIO_FLUSH_FROM_FRAME_ACCURATE && actualPosition != *position) {
+            result = AAUDIO_ERROR_OUT_OF_RANGE;
+        }
+        if (result != AAUDIO_OK) {
+            *position = actualPosition;
+            return result;
+        }
+
+        // Rewind successfully, update the written position as the rewound position.
+        mLastFramesWritten = actualPosition;
+        mAudioEndpoint->setDataWriteCounter(actualPosition - mFramesOffsetFromService);
+    }
+    {
+        std::lock_guard _streamEndLock(mStreamEndMutex);
+        mOffloadEosPending = false;
+    }
+    wakeupCallbackThread();
+    return result;
+}
+
 // Render audio in the application callback and then write the data to the stream.
 void *AudioStreamInternalPlay::callbackLoop() {
     ALOGD("%s() entering >>>>>>>>>>>>>>>", __func__);
@@ -528,8 +584,11 @@
                 }
             }
         }
-        // Call application using the AAudio callback interface.
-        callbackResult = maybeCallDataCallback(mCallbackBuffer.get(), mCallbackFrames);
+        {
+            std::lock_guard _endpointLock(mEndpointMutex);
+            // Call application using the AAudio callback interface.
+            callbackResult = maybeCallDataCallback(mCallbackBuffer.get(), mCallbackFrames);
+        }
 
         if (callbackResult < 0) {
             if (!shouldStopStream()) {
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.h b/media/libaaudio/src/client/AudioStreamInternalPlay.h
index f5c39a4..2b6a60b 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.h
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.h
@@ -104,6 +104,8 @@
     aaudio_result_t requestStop_l() REQUIRES(mStreamLock) final;
 
     void wakeupCallbackThread() final;
+    aaudio_result_t flushFromFrame_l(AAudio_FlushFromAccuracy accuracy, int64_t* position)
+            REQUIRES(mStreamLock) final;
 private:
     /*
      * Asynchronous write with data conversion.
@@ -133,6 +135,8 @@
     std::mutex mCallbackMutex;
     std::condition_variable mCallbackCV;
     bool mSuspendCallback GUARDED_BY(mCallbackMutex){false};
+
+    std::mutex mEndpointMutex;
 };
 
 } /* namespace aaudio */
diff --git a/media/libaaudio/src/core/AAudioAudio.cpp b/media/libaaudio/src/core/AAudioAudio.cpp
index a720643..d0567cf 100644
--- a/media/libaaudio/src/core/AAudioAudio.cpp
+++ b/media/libaaudio/src/core/AAudioAudio.cpp
@@ -26,6 +26,7 @@
 #include <aaudio/AAudio.h>
 #include <aaudio/AAudioTesting.h>
 #include <com_android_media_aaudio.h>
+#include <com_android_media_audioserver.h>
 #include <system/aaudio/AAudio.h>
 #include <system/audio.h>
 #include "AudioClock.h"
@@ -776,3 +777,12 @@
     AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
     return audioStream->setOffloadEndOfStream();
 }
+
+AAUDIO_API aaudio_result_t AAudioStream_flushFromFrame(
+        AAudioStream* stream, AAudio_FlushFromAccuracy accuracy, int64_t* inOutPosition) {
+    if (!com::android::media::audioserver::mmap_pcm_offload_support()) {
+        return AAUDIO_ERROR_UNIMPLEMENTED;
+    }
+    AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
+    return audioStream->flushFromFrame(accuracy, inOutPosition);
+}
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index d998e7e..123b705 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -28,6 +28,7 @@
 
 #include <aaudio/AAudio.h>
 #include <android-base/strings.h>
+#include <com_android_media_audioserver.h>
 
 #include "AudioStreamBuilder.h"
 #include "AudioStream.h"
@@ -669,6 +670,28 @@
     return android::base::Join(mTags, AUDIO_ATTRIBUTES_TAGS_SEPARATOR);
 }
 
+aaudio_result_t AudioStream::flushFromFrame(AAudio_FlushFromAccuracy accuracy, int64_t* position) {
+    ALOGD("%s(%d, %jd)", __func__, accuracy, *position);
+    if (!com_android_media_audioserver_mmap_pcm_offload_support()) {
+        return AAUDIO_ERROR_UNIMPLEMENTED;
+    }
+    if (getDirection() != AAUDIO_DIRECTION_OUTPUT ||
+        getPerformanceMode() != AAUDIO_PERFORMANCE_MODE_POWER_SAVING_OFFLOADED ||
+        (accuracy != AAUDIO_FLUSH_FROM_ACCURACY_UNDEFINED &&
+                accuracy != AAUDIO_FLUSH_FROM_FRAME_ACCURATE)) {
+        return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
+    }
+    if (*position < 0) {
+        return AAUDIO_ERROR_OUT_OF_RANGE;
+    }
+    const int64_t requestedPosition = *position;
+    std::lock_guard lock(mStreamLock);
+    const aaudio_result_t result = flushFromFrame_l(accuracy, position);
+    ALOGD("%s(%d, %jd), actual position = %jd, result = %d",
+          __func__, accuracy, requestedPosition, *position, result);
+    return result;
+}
+
 int AudioStream::dataCallbackInternal(void *audioData, int32_t numFrames) {
     const aaudio_data_callback_result_t result = std::invoke(mDataCallbackProc,
             (AAudioStream*) this,
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index db42422..67de5a9 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -96,6 +96,12 @@
 
     virtual aaudio_result_t requestStop_l() REQUIRES(mStreamLock) = 0;
 
+    virtual aaudio_result_t flushFromFrame_l(AAudio_FlushFromAccuracy accuracy [[maybe_unused]],
+                                             int64_t* position [[maybe_unused]])
+                                             REQUIRES(mStreamLock) {
+        return AAUDIO_ERROR_UNIMPLEMENTED;
+    }
+
 public:
     virtual aaudio_result_t getTimestamp(clockid_t clockId,
                                        int64_t *framePosition,
@@ -521,6 +527,9 @@
 
     aaudio_result_t safeReleaseCloseInternal() EXCLUDES(mStreamLock);
 
+    aaudio_result_t flushFromFrame(AAudio_FlushFromAccuracy accuracy, int64_t* position)
+            EXCLUDES(mStreamLock);
+
 protected:
 
     // PlayerBase allows the system to control the stream volume.
diff --git a/media/libaaudio/src/libaaudio.map.txt b/media/libaaudio/src/libaaudio.map.txt
index 4fed354..416f26f 100644
--- a/media/libaaudio/src/libaaudio.map.txt
+++ b/media/libaaudio/src/libaaudio.map.txt
@@ -79,6 +79,7 @@
     AAudioStream_getOffloadDelay; #introduced=36
     AAudioStream_getOffloadPadding; #introduced=36
     AAudioStream_setOffloadEndOfStream; #introduced=36
+    AAudioStream_flushFromFrame; #introduced=37
 
     AAudioStreamBuilder_addTag; # systemapi
     AAudioStreamBuilder_clearTags; # systemapi
diff --git a/media/libaaudio/tests/Android.bp b/media/libaaudio/tests/Android.bp
index 1ff75be..1e099dc 100644
--- a/media/libaaudio/tests/Android.bp
+++ b/media/libaaudio/tests/Android.bp
@@ -292,3 +292,11 @@
     header_libs: ["libaaudio_example_utils"],
     shared_libs: ["libaaudio"],
 }
+
+cc_binary {
+    name: "test_flush_from_frame",
+    defaults: ["libaaudio_tests_defaults"],
+    srcs: ["test_flush_from_frame.cpp"],
+    header_libs: ["libaaudio_example_utils"],
+    shared_libs: ["libaaudio"],
+}
diff --git a/media/libaaudio/tests/test_flush_from_frame.cpp b/media/libaaudio/tests/test_flush_from_frame.cpp
new file mode 100644
index 0000000..70727cd
--- /dev/null
+++ b/media/libaaudio/tests/test_flush_from_frame.cpp
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <chrono>
+#include <memory>
+#include <mutex>
+#include <stdio.h>
+#include <stdlib.h>
+#include <thread>
+#include <vector>
+
+#include <aaudio/AAudio.h>
+
+#include "AAudioArgsParser.h"
+#include "AAudioSimplePlayer.h"
+#include "SineGenerator.h"
+
+/**
+ * This tests AAudioStream_flushFromPosition. This test will do playback in offload mode
+ * and try to flush from frame every 2 seconds. It will switch the sine generator between
+ * 440Hz and 880Hz when flush from frame.
+ */
+
+constexpr static int DEFAULT_TIME_TO_RUN_IN_SECOND = 10;
+constexpr static int PLAYBACK_LENGTH_SECONDS = 2;
+constexpr static int MORE_DATA_IN_SECONDS = 3;
+
+int32_t MyPartialDataCallback(
+        AAudioStream* stream, void* userData, void* audioData, int32_t numFrames);
+
+void MyErrorCallback(AAudioStream* /*stream*/, void* /*userData*/, aaudio_result_t error);
+
+class MyPlayer : public AAudioSimplePlayer {
+public:
+    MyPlayer(AAudioArgsParser& argParser, bool useDataCallback)
+            : mArgParser(argParser), mUseDataCallback(useDataCallback) {}
+
+    aaudio_result_t open() {
+        aaudio_result_t result = AAudioSimplePlayer::open(
+                mArgParser,
+                nullptr /*dataCallback*/,
+                &MyErrorCallback,
+                this,
+                nullptr /*presentationEndCallback*/,
+                mUseDataCallback ? &MyPartialDataCallback : nullptr);
+        if (result != AAUDIO_OK) {
+            return result;
+        }
+        mChannelCount = getChannelCount();
+        mSampleRate = getSampleRate();
+        mSines.resize(2);
+        for (int i = 0; i < mChannelCount; ++i) {
+            SineGenerator sine;
+            sine.setup(440.0, mSampleRate);
+            mSines[0].push_back(sine);
+            SineGenerator sine1;
+            sine1.setup(880.0, mSampleRate);
+            mSines[1].push_back(sine1);
+        }
+        return result;
+    }
+
+    int32_t renderAudio(AAudioStream* stream, void* audioData, int32_t numFrames) {
+        int index = 0;
+        {
+            std::lock_guard _l(mFlushMutex);
+            if (mPendingFlush) {
+                return 0;
+            }
+            index = mSineGeneratorIndex;
+        }
+        // Just handle PCM_16 and PCM_FLOAT for testing
+        if (!fillData(stream, audioData, numFrames, index)) {
+            printf("Failed to render data, stop the stream\n");
+            return -1;
+        }
+        return numFrames;
+    }
+
+    void writeData() {
+        for (int i = 0; i < MORE_DATA_IN_SECONDS; ++i) {
+            writeOneSecondData();
+        }
+    }
+
+    aaudio_result_t flushFromFrame(AAudio_FlushFromAccuracy accuracy, int64_t* position) {
+        if (position == nullptr) {
+            return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
+        }
+        aaudio_result_t result = AAUDIO_OK;
+        {
+            std::lock_guard _l(mFlushMutex);
+            mPendingFlush = true;
+            mSineGeneratorIndex ^= 1;
+        }
+        {
+            std::lock_guard _l(mStreamMutex);
+            auto framesWritten = AAudioStream_getFramesWritten(getStream());
+            int64_t requestedPosition = *position;
+            result = AAudioStream_flushFromFrame(getStream(), accuracy, position);
+            printf("%s(%d, %jd) result=%d actual position=%jd, frames written before "
+                   "flushFromFrame=%jd frames written after flushFromFrame=%jd\n",
+                   __func__, accuracy, requestedPosition, result, *position, framesWritten,
+                   AAudioStream_getFramesWritten(getStream()));
+        }
+        {
+            std::lock_guard _l(mFlushMutex);
+            mPendingFlush = false;
+        }
+        return result;
+    }
+
+    aaudio_result_t close() override {
+        std::lock_guard _l(mStreamMutex);
+        return AAudioSimplePlayer::close();
+    }
+
+private:
+    void writeOneSecondData() {
+        int index = 0;
+        {
+            std::lock_guard _l(mFlushMutex);
+            if (mPendingFlush) {
+                return;
+            }
+            index = mSineGeneratorIndex;
+        }
+        // Lock to prevent the stream is released
+        std::lock_guard _l(mStreamMutex);
+        AAudioStream* stream = getStream();
+        if (stream == nullptr) {
+            return;
+        }
+        int bytesPerFrame = mChannelCount;
+        std::shared_ptr<uint8_t[]> data;
+        switch (AAudioStream_getFormat(stream)) {
+            case AAUDIO_FORMAT_PCM_I16: {
+                bytesPerFrame *= 2;
+            } break;
+            case AAUDIO_FORMAT_PCM_FLOAT: {
+                bytesPerFrame *= 4;
+            } break;
+            default:
+                printf("Unsupported format %d\n", AAudioStream_getFormat(stream));
+                return;
+        }
+        data = std::make_shared<uint8_t[]>(bytesPerFrame * mSampleRate);
+        fillData(stream, static_cast<void*>(data.get()), mSampleRate, index);
+        int bytesWritten = 0;
+        int framesLeft = mSampleRate;
+        while (framesLeft > 0) {
+            auto framesWritten = AAudioStream_write(
+                    stream, static_cast<void *>(&data[bytesWritten]),
+                    framesLeft, 0 /*timeoutNanoseconds*/);
+            if (framesWritten < 0) {
+                printf("Failed to write data %d\n", framesWritten);
+                return;
+            }
+            printf("Write data succeed, frames=%d\n", framesWritten);
+            framesLeft -= framesWritten;
+            bytesWritten += framesWritten * bytesPerFrame;
+        }
+    }
+
+    bool fillData(AAudioStream* stream, void* data, int numFrames, int sineGeneratorIndex) {
+        switch (AAudioStream_getFormat(stream)) {
+            case AAUDIO_FORMAT_PCM_I16: {
+                int16_t *audioBuffer = static_cast<int16_t *>(data);
+                for (int i = 0; i < mChannelCount; ++i) {
+                    mSines[sineGeneratorIndex][i].render(&audioBuffer[i], mChannelCount, numFrames);
+                }
+            } break;
+            case AAUDIO_FORMAT_PCM_FLOAT: {
+                float *audioBuffer = static_cast<float *>(data);
+                for (int i = 0; i < mChannelCount; ++i) {
+                    mSines[sineGeneratorIndex][i].render(&audioBuffer[i], mChannelCount, numFrames);
+                }
+            } break;
+            default:
+                return false;
+        }
+        return true;
+    }
+
+    const AAudioArgsParser mArgParser;
+    const bool mUseDataCallback;
+
+    int mSampleRate;
+    int mChannelCount;
+    std::vector<std::vector<SineGenerator>> mSines;
+    int mSineGeneratorIndex = 0;
+
+    std::mutex mStreamMutex;
+
+    std::mutex mFlushMutex;
+    bool mPendingFlush = false;
+};
+
+int32_t MyPartialDataCallback(
+        AAudioStream* stream, void* userData, void* audioData, int32_t numFrames) {
+    MyPlayer* player = static_cast<MyPlayer*>(userData);
+    return player->renderAudio(stream, audioData, numFrames);
+}
+
+void MyErrorCallback(AAudioStream* /*stream*/, void* /*userData*/, aaudio_result_t error) {
+    printf("Error callback, error=%d\n", error);
+}
+
+static void usage() {
+    printf("This test will playback in offload mode and try to flush from frame "
+           "every 2 seconds\n");
+    AAudioArgsParser::usage();
+    printf("      -T{seconds} time to run the test\n");
+    printf("      -B use blocking write instead of data callback\n");
+}
+
+int main(int argc, char **argv) {
+    AAudioArgsParser argParser;
+    int timeToRun = DEFAULT_TIME_TO_RUN_IN_SECOND;
+    bool useDataCallback = true;
+    for (int i = 1; i < argc; ++i) {
+        const char *arg = argv[i];
+        if (argParser.parseArg(arg)) {
+            if (arg[0] == '-') {
+                char option = arg[1];
+                switch (option) {
+                    case 'T':
+                        timeToRun = atoi(&arg[2]);
+                        break;
+                    case 'B':
+                        useDataCallback = false;
+                        break;
+                    default:
+                        usage();
+                        exit(EXIT_FAILURE);
+                }
+            } else {
+                usage();
+                exit(EXIT_FAILURE);
+            }
+        }
+    }
+
+    // Force to use offload mode
+    argParser.setPerformanceMode(AAUDIO_PERFORMANCE_MODE_POWER_SAVING_OFFLOADED);
+    argParser.setNumberOfBursts(0);
+
+    MyPlayer player(argParser, useDataCallback);
+    if (auto result = player.open(); result != AAUDIO_OK) {
+        printf("Failed to open stream, error=%d\n", result);
+        exit(EXIT_FAILURE);
+    }
+
+    if (auto result = player.start(); result != AAUDIO_OK) {
+        printf("Failed to start stream, error=%d", result);
+        exit(EXIT_FAILURE);
+    }
+
+    auto timeStart = std::chrono::system_clock::now();
+    int64_t position = 0;
+
+    while (std::chrono::duration_cast<std::chrono::seconds>(
+            std::chrono::system_clock::now() - timeStart).count() < timeToRun) {
+        if (useDataCallback) {
+            std::this_thread::sleep_for(std::chrono::seconds(PLAYBACK_LENGTH_SECONDS));
+            player.flushFromFrame(AAUDIO_FLUSH_FROM_ACCURACY_UNDEFINED, &position);
+        } else {
+            auto timeToWakeUp = std::chrono::system_clock::now() +
+                    std::chrono::seconds(PLAYBACK_LENGTH_SECONDS);
+            player.writeData();
+            std::this_thread::sleep_until(timeToWakeUp);
+            player.flushFromFrame(AAUDIO_FLUSH_FROM_ACCURACY_UNDEFINED, &position);
+        }
+    }
+
+    player.close();
+    return EXIT_SUCCESS;
+}
diff --git a/services/oboeservice/AAudioService.cpp b/services/oboeservice/AAudioService.cpp
index 76a507d..2e59ac5 100644
--- a/services/oboeservice/AAudioService.cpp
+++ b/services/oboeservice/AAudioService.cpp
@@ -314,6 +314,17 @@
     AIDL_RETURN(result);
 }
 
+Status AAudioService::updateTimestamp(int32_t streamHandle, int32_t *_aidl_return) {
+    static_assert(std::is_same_v<aaudio_result_t, std::decay_t<typeof(*_aidl_return)>>);
+
+    const sp<AAudioServiceStreamBase> serviceStream = convertHandleToServiceStream(streamHandle);
+    if (serviceStream.get() == nullptr) {
+        ALOGW("%s(), invalid streamHandle = 0x%0x", __func__, streamHandle);
+        AIDL_RETURN(AAUDIO_ERROR_INVALID_HANDLE);
+    }
+    AIDL_RETURN(serviceStream->updateTimestamp());
+}
+
 bool AAudioService::isCallerInService() {
     const pid_t clientPid = VALUE_OR_FATAL(
             aidl2legacy_int32_t_pid_t(mAudioClient.attributionSource.pid));
diff --git a/services/oboeservice/AAudioService.h b/services/oboeservice/AAudioService.h
index ada3d53..e18060f 100644
--- a/services/oboeservice/AAudioService.h
+++ b/services/oboeservice/AAudioService.h
@@ -86,6 +86,8 @@
     binder::Status exitStandby(int32_t streamHandle, ::aaudio::Endpoint* endpoint,
                                int32_t* _aidl_return) override;
 
+    binder::Status updateTimestamp(int32_t streamHandle, int32_t* _aidl_return) override;
+
     aaudio_result_t startClient(aaudio::aaudio_handle_t streamHandle,
                                 const android::AudioClient& client,
                                 const audio_attributes_t *attr,
diff --git a/services/oboeservice/AAudioServiceStreamBase.cpp b/services/oboeservice/AAudioServiceStreamBase.cpp
index 8ec7de1..ffaaa04 100644
--- a/services/oboeservice/AAudioServiceStreamBase.cpp
+++ b/services/oboeservice/AAudioServiceStreamBase.cpp
@@ -403,6 +403,10 @@
     return AAUDIO_OK;
 }
 
+aaudio_result_t AAudioServiceStreamBase::updateTimestamp() {
+    return sendCommand(UPDATE_TIMESTAMP, nullptr /*param*/, true /*waitForReply*/, TIMEOUT_NANOS);
+}
+
 // implement Runnable, periodically send timestamps to client and process commands from queue.
 // Enter standby mode if idle for a while.
 __attribute__((no_sanitize("integer")))
@@ -547,6 +551,9 @@
                     command->result = param == nullptr ? AAUDIO_ERROR_ILLEGAL_ARGUMENT
                                                        : stopClient_l(param->mClientHandle);
                 } break;
+                case UPDATE_TIMESTAMP: {
+                    command->result = sendCurrentTimestamp_l();
+                } break;
                 default:
                     ALOGE("Invalid command op code: %d", command->operationCode);
                     break;
diff --git a/services/oboeservice/AAudioServiceStreamBase.h b/services/oboeservice/AAudioServiceStreamBase.h
index 20737bc..4580522 100644
--- a/services/oboeservice/AAudioServiceStreamBase.h
+++ b/services/oboeservice/AAudioServiceStreamBase.h
@@ -122,6 +122,8 @@
      */
     aaudio_result_t exitStandby(AudioEndpointParcelable *parcelable) EXCLUDES(mLock);
 
+    aaudio_result_t updateTimestamp() EXCLUDES(mLock);
+
     virtual aaudio_result_t startClient(const android::AudioClient& client,
                                         const audio_attributes_t *attr __unused,
                                         audio_port_handle_t *clientHandle __unused) {
@@ -405,6 +407,7 @@
         EXIT_STANDBY,
         START_CLIENT,
         STOP_CLIENT,
+        UPDATE_TIMESTAMP,
     };
     AAudioThread            mCommandThread;
     std::atomic_bool        mThreadEnabled{false};
diff --git a/services/oboeservice/fuzzer/oboeservice_fuzzer.cpp b/services/oboeservice/fuzzer/oboeservice_fuzzer.cpp
index e80f51d..142361d 100644
--- a/services/oboeservice/fuzzer/oboeservice_fuzzer.cpp
+++ b/services/oboeservice/fuzzer/oboeservice_fuzzer.cpp
@@ -189,6 +189,8 @@
         return AAUDIO_ERROR_UNAVAILABLE;
     }
 
+    aaudio_result_t updateTimestamp(const AAudioHandleInfo& streamHandleInfo) override;
+
     void onStreamChange(aaudio_handle_t handle, int32_t opcode, int32_t value) {}
 
     int getDeathCount() { return mDeathCount; }
@@ -338,6 +340,14 @@
     return service->unregisterAudioThread(streamHandleInfo, clientThreadId);
 }
 
+aaudio_result_t FuzzAAudioClient::updateTimestamp(const AAudioHandleInfo& streamHandleInfo) {
+    AAudioServiceInterface *service = getAAudioService();
+    if (!service) {
+        return AAUDIO_ERROR_NO_SERVICE;
+    }
+    return service->updateTimestamp(streamHandleInfo);
+}
+
 class OboeserviceFuzzer {
    public:
     OboeserviceFuzzer();
@@ -411,7 +421,7 @@
     }
     while (fdp.remaining_bytes()) {
         AudioEndpointParcelable audioEndpointParcelable;
-        int action = fdp.ConsumeIntegralInRange<int32_t>(0, 4);
+        int action = fdp.ConsumeIntegralInRange<int32_t>(0, 5);
         switch (action) {
             case 0:
                 mClient->getStreamDescription(streamHandleInfo, audioEndpointParcelable);
@@ -428,6 +438,9 @@
             case 4:
                 mClient->flushStream(streamHandleInfo);
                 break;
+            case 5:
+                mClient->updateTimestamp(streamHandleInfo);
+                break;
         }
     }
     mClient->closeStream(streamHandleInfo);