Merge "point to new repo hooks for mainline uploads"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 5cb5222..8fe48c2 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -8,3 +8,4 @@
 # Only turn on clang-format check for the following subfolders.
 clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
                media/libmediatranscoding/
+               services/mediatranscoding/
diff --git a/apex/testing/Android.bp b/apex/testing/Android.bp
index 376d3e4..a04ab3f 100644
--- a/apex/testing/Android.bp
+++ b/apex/testing/Android.bp
@@ -17,6 +17,7 @@
     manifest: "test_manifest.json",
     file_contexts: ":com.android.media-file_contexts",
     defaults: ["com.android.media-defaults"],
+    prebuilts: ["sdkinfo_45"],
     installable: false,
 }
 
diff --git a/camera/CaptureResult.cpp b/camera/CaptureResult.cpp
index 9cbfdb0..755051c 100644
--- a/camera/CaptureResult.cpp
+++ b/camera/CaptureResult.cpp
@@ -49,6 +49,9 @@
         }
         errorPhysicalCameraId = cameraId;
     }
+    parcel->readInt64(&lastCompletedRegularFrameNumber);
+    parcel->readInt64(&lastCompletedReprocessFrameNumber);
+    parcel->readInt64(&lastCompletedZslFrameNumber);
 
     return OK;
 }
@@ -76,6 +79,9 @@
     } else {
         parcel->writeBool(false);
     }
+    parcel->writeInt64(lastCompletedRegularFrameNumber);
+    parcel->writeInt64(lastCompletedReprocessFrameNumber);
+    parcel->writeInt64(lastCompletedZslFrameNumber);
 
     return OK;
 }
diff --git a/camera/include/camera/CaptureResult.h b/camera/include/camera/CaptureResult.h
index dc3d282..f163c1e 100644
--- a/camera/include/camera/CaptureResult.h
+++ b/camera/include/camera/CaptureResult.h
@@ -76,6 +76,34 @@
      */
     String16  errorPhysicalCameraId;
 
+    // The last completed frame numbers shouldn't be checked in onResultReceived() and notifyError()
+    // because the output buffers could be arriving after onResultReceived() and
+    // notifyError(). Given this constraint, we check it for each
+    // onCaptureStarted, and if there is no further onCaptureStarted(),
+    // check for onDeviceIdle() to clear out all pending frame numbers.
+
+    /**
+     * The latest regular request frameNumber for which all buffers and capture result have been
+     * returned or notified as an BUFFER_ERROR/RESULT_ERROR/REQUEST_ERROR. -1 if
+     * none has completed.
+     */
+    int64_t lastCompletedRegularFrameNumber;
+
+    /**
+     * The latest reprocess request frameNumber for which all buffers and capture result have been
+     * returned or notified as an BUFFER_ERROR/RESULT_ERROR/REQUEST_ERROR. -1 if
+     * none has completed.
+     */
+    int64_t lastCompletedReprocessFrameNumber;
+
+    /**
+     * The latest Zsl request frameNumber for which all buffers and capture result have been
+     * returned or notified as an BUFFER_ERROR/RESULT_ERROR/REQUEST_ERROR. -1 if
+     * none has completed.
+     */
+    int64_t lastCompletedZslFrameNumber;
+
+
     /**
      * Constructor initializes object as invalid by setting requestId to be -1.
      */
@@ -87,7 +115,10 @@
           frameNumber(0),
           partialResultCount(0),
           errorStreamId(-1),
-          errorPhysicalCameraId() {
+          errorPhysicalCameraId(),
+          lastCompletedRegularFrameNumber(-1),
+          lastCompletedReprocessFrameNumber(-1),
+          lastCompletedZslFrameNumber(-1) {
     }
 
     /**
diff --git a/camera/ndk/impl/ACameraDevice.cpp b/camera/ndk/impl/ACameraDevice.cpp
index 0d7180a..c15c5a5 100644
--- a/camera/ndk/impl/ACameraDevice.cpp
+++ b/camera/ndk/impl/ACameraDevice.cpp
@@ -1336,56 +1336,97 @@
 void
 CameraDevice::checkAndFireSequenceCompleteLocked() {
     int64_t completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
-    //std::map<int, int64_t> mSequenceLastFrameNumberMap;
     auto it = mSequenceLastFrameNumberMap.begin();
     while (it != mSequenceLastFrameNumberMap.end()) {
         int sequenceId = it->first;
-        int64_t lastFrameNumber = it->second;
-        bool seqCompleted = false;
-        bool hasCallback  = true;
+        int64_t lastFrameNumber = it->second.lastFrameNumber;
+        bool hasCallback = true;
+
+        if (mRemote == nullptr) {
+            ALOGW("Camera %s closed while checking sequence complete", getId());
+            return;
+        }
+        ALOGV("%s: seq %d's last frame number %" PRId64 ", completed %" PRId64,
+                __FUNCTION__, sequenceId, lastFrameNumber, completedFrameNumber);
+        if (!it->second.isSequenceCompleted) {
+            // Check if there is callback for this sequence
+            // This should not happen because we always register callback (with nullptr inside)
+            if (mSequenceCallbackMap.count(sequenceId) == 0) {
+                ALOGW("No callback found for sequenceId %d", sequenceId);
+                hasCallback = false;
+            }
+
+            if (lastFrameNumber <= completedFrameNumber) {
+                ALOGV("Mark sequenceId %d as sequence completed", sequenceId);
+                it->second.isSequenceCompleted = true;
+            }
+
+            if (it->second.isSequenceCompleted && hasCallback) {
+                auto cbIt = mSequenceCallbackMap.find(sequenceId);
+                CallbackHolder cbh = cbIt->second;
+
+                // send seq complete callback
+                sp<AMessage> msg = new AMessage(kWhatCaptureSeqEnd, mHandler);
+                msg->setPointer(kContextKey, cbh.mContext);
+                msg->setObject(kSessionSpKey, cbh.mSession);
+                msg->setPointer(kCallbackFpKey, (void*) cbh.mOnCaptureSequenceCompleted);
+                msg->setInt32(kSequenceIdKey, sequenceId);
+                msg->setInt64(kFrameNumberKey, lastFrameNumber);
+
+                // Clear the session sp before we send out the message
+                // This will guarantee the rare case where the message is processed
+                // before cbh goes out of scope and causing we call the session
+                // destructor while holding device lock
+                cbh.mSession.clear();
+                postSessionMsgAndCleanup(msg);
+            }
+        }
+
+        if (it->second.isSequenceCompleted && it->second.isInflightCompleted) {
+            if (mSequenceCallbackMap.find(sequenceId) != mSequenceCallbackMap.end()) {
+                mSequenceCallbackMap.erase(sequenceId);
+            }
+            it = mSequenceLastFrameNumberMap.erase(it);
+            ALOGV("%s: Remove holder for sequenceId %d", __FUNCTION__, sequenceId);
+        } else {
+            ++it;
+        }
+    }
+}
+
+void
+CameraDevice::removeCompletedCallbackHolderLocked(int64_t lastCompletedRegularFrameNumber) {
+    auto it = mSequenceLastFrameNumberMap.begin();
+    while (it != mSequenceLastFrameNumberMap.end()) {
+        int sequenceId = it->first;
+        int64_t lastFrameNumber = it->second.lastFrameNumber;
 
         if (mRemote == nullptr) {
             ALOGW("Camera %s closed while checking sequence complete", getId());
             return;
         }
 
-        // Check if there is callback for this sequence
-        // This should not happen because we always register callback (with nullptr inside)
-        if (mSequenceCallbackMap.count(sequenceId) == 0) {
-            ALOGW("No callback found for sequenceId %d", sequenceId);
-            hasCallback = false;
-        }
+        ALOGV("%s: seq %d's last frame number %" PRId64
+                ", completed inflight frame number %" PRId64,
+                __FUNCTION__, sequenceId, lastFrameNumber,
+                lastCompletedRegularFrameNumber);
+        if (lastFrameNumber <= lastCompletedRegularFrameNumber) {
+            if (it->second.isSequenceCompleted) {
+                // Check if there is callback for this sequence
+                // This should not happen because we always register callback (with nullptr inside)
+                if (mSequenceCallbackMap.count(sequenceId) == 0) {
+                    ALOGW("No callback found for sequenceId %d", sequenceId);
+                } else {
+                    mSequenceCallbackMap.erase(sequenceId);
+                }
 
-        if (lastFrameNumber <= completedFrameNumber) {
-            ALOGV("seq %d reached last frame %" PRId64 ", completed %" PRId64,
-                    sequenceId, lastFrameNumber, completedFrameNumber);
-            seqCompleted = true;
-        }
-
-        if (seqCompleted && hasCallback) {
-            // remove callback holder from callback map
-            auto cbIt = mSequenceCallbackMap.find(sequenceId);
-            CallbackHolder cbh = cbIt->second;
-            mSequenceCallbackMap.erase(cbIt);
-            // send seq complete callback
-            sp<AMessage> msg = new AMessage(kWhatCaptureSeqEnd, mHandler);
-            msg->setPointer(kContextKey, cbh.mContext);
-            msg->setObject(kSessionSpKey, cbh.mSession);
-            msg->setPointer(kCallbackFpKey, (void*) cbh.mOnCaptureSequenceCompleted);
-            msg->setInt32(kSequenceIdKey, sequenceId);
-            msg->setInt64(kFrameNumberKey, lastFrameNumber);
-
-            // Clear the session sp before we send out the message
-            // This will guarantee the rare case where the message is processed
-            // before cbh goes out of scope and causing we call the session
-            // destructor while holding device lock
-            cbh.mSession.clear();
-            postSessionMsgAndCleanup(msg);
-        }
-
-        // No need to track sequence complete if there is no callback registered
-        if (seqCompleted || !hasCallback) {
-            it = mSequenceLastFrameNumberMap.erase(it);
+                it = mSequenceLastFrameNumberMap.erase(it);
+                ALOGV("%s: Remove holder for sequenceId %d", __FUNCTION__, sequenceId);
+            } else {
+                ALOGV("Mark sequenceId %d as inflight completed", sequenceId);
+                it->second.isInflightCompleted = true;
+                ++it;
+            }
         } else {
             ++it;
         }
@@ -1480,6 +1521,9 @@
         return ret;
     }
 
+    dev->removeCompletedCallbackHolderLocked(
+             std::numeric_limits<int64_t>::max()/*lastCompletedRegularFrameNumber*/);
+
     if (dev->mIdle) {
         // Already in idle state. Possibly other thread did waitUntilIdle
         return ret;
@@ -1522,6 +1566,9 @@
         return ret;
     }
 
+    dev->removeCompletedCallbackHolderLocked(
+            resultExtras.lastCompletedRegularFrameNumber);
+
     int sequenceId = resultExtras.requestId;
     int32_t burstId = resultExtras.burstId;
 
diff --git a/camera/ndk/impl/ACameraDevice.h b/camera/ndk/impl/ACameraDevice.h
index 6c2ceb3..2f2299f 100644
--- a/camera/ndk/impl/ACameraDevice.h
+++ b/camera/ndk/impl/ACameraDevice.h
@@ -267,8 +267,15 @@
     static const int REQUEST_ID_NONE = -1;
     int mRepeatingSequenceId = REQUEST_ID_NONE;
 
-    // sequence id -> last frame number map
-    std::map<int, int64_t> mSequenceLastFrameNumberMap;
+    // sequence id -> last frame number holder map
+    struct RequestLastFrameNumberHolder {
+        int64_t lastFrameNumber;
+        bool isSequenceCompleted = false;
+        bool isInflightCompleted = false;
+        RequestLastFrameNumberHolder(int64_t lastFN) :
+                lastFrameNumber(lastFN) {}
+    };
+    std::map<int, RequestLastFrameNumberHolder> mSequenceLastFrameNumberMap;
 
     struct CallbackHolder {
         CallbackHolder(sp<ACameraCaptureSession>          session,
@@ -338,6 +345,7 @@
 
     void checkRepeatingSequenceCompleteLocked(const int sequenceId, const int64_t lastFrameNumber);
     void checkAndFireSequenceCompleteLocked();
+    void removeCompletedCallbackHolderLocked(int64_t lastCompletedRegularFrameNumber);
 
     // Misc variables
     int32_t mShadingMapSize[2];   // const after constructor
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.cpp b/media/codec2/sfplugin/CCodecBufferChannel.cpp
index 907aa39..4b158bd 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.cpp
+++ b/media/codec2/sfplugin/CCodecBufferChannel.cpp
@@ -18,6 +18,8 @@
 #define LOG_TAG "CCodecBufferChannel"
 #include <utils/Log.h>
 
+#include <algorithm>
+#include <list>
 #include <numeric>
 
 #include <C2AllocatorGralloc.h>
@@ -1242,62 +1244,98 @@
         return UNKNOWN_ERROR;
     }
     size_t numInputSlots = mInput.lock()->numSlots;
-    std::vector<sp<MediaCodecBuffer>> toBeQueued;
-    for (size_t i = 0; i < numInputSlots; ++i) {
+
+    struct ClientInputBuffer {
         size_t index;
         sp<MediaCodecBuffer> buffer;
-        {
-            Mutexed<Input>::Locked input(mInput);
-            if (!input->buffers->requestNewBuffer(&index, &buffer)) {
-                if (i == 0) {
-                    ALOGW("[%s] start: cannot allocate memory at all", mName);
-                    return NO_MEMORY;
-                } else {
-                    ALOGV("[%s] start: cannot allocate memory, only %zu buffers allocated",
-                            mName, i);
-                }
+        size_t capacity;
+    };
+    std::list<ClientInputBuffer> clientInputBuffers;
+
+    {
+        Mutexed<Input>::Locked input(mInput);
+        while (clientInputBuffers.size() < numInputSlots) {
+            ClientInputBuffer clientInputBuffer;
+            if (!input->buffers->requestNewBuffer(&clientInputBuffer.index,
+                                                  &clientInputBuffer.buffer)) {
                 break;
             }
+            clientInputBuffer.capacity = clientInputBuffer.buffer->capacity();
+            clientInputBuffers.emplace_back(std::move(clientInputBuffer));
         }
-        if (buffer) {
-            Mutexed<std::list<sp<ABuffer>>>::Locked configs(mFlushedConfigs);
-            ALOGV("[%s] input buffer %zu available", mName, index);
-            bool post = true;
-            if (!configs->empty()) {
+    }
+    if (clientInputBuffers.empty()) {
+        ALOGW("[%s] start: cannot allocate memory at all", mName);
+        return NO_MEMORY;
+    } else if (clientInputBuffers.size() < numInputSlots) {
+        ALOGD("[%s] start: cannot allocate memory for all slots, "
+              "only %zu buffers allocated",
+              mName, clientInputBuffers.size());
+    } else {
+        ALOGV("[%s] %zu initial input buffers available",
+              mName, clientInputBuffers.size());
+    }
+    // Sort input buffers by their capacities in increasing order.
+    clientInputBuffers.sort(
+            [](const ClientInputBuffer& a, const ClientInputBuffer& b) {
+                return a.capacity < b.capacity;
+            });
+
+    {
+        Mutexed<std::list<sp<ABuffer>>>::Locked configs(mFlushedConfigs);
+        if (!configs->empty()) {
+            while (!configs->empty()) {
                 sp<ABuffer> config = configs->front();
                 configs->pop_front();
-                if (buffer->capacity() >= config->size()) {
-                    memcpy(buffer->base(), config->data(), config->size());
-                    buffer->setRange(0, config->size());
-                    buffer->meta()->clear();
-                    buffer->meta()->setInt64("timeUs", 0);
-                    buffer->meta()->setInt32("csd", 1);
-                    post = false;
-                } else {
-                    ALOGD("[%s] buffer capacity too small for the config (%zu < %zu)",
-                            mName, buffer->capacity(), config->size());
+                // Find the smallest input buffer that can fit the config.
+                auto i = std::find_if(
+                        clientInputBuffers.begin(),
+                        clientInputBuffers.end(),
+                        [cfgSize = config->size()](const ClientInputBuffer& b) {
+                            return b.capacity >= cfgSize;
+                        });
+                if (i == clientInputBuffers.end()) {
+                    ALOGW("[%s] no input buffer large enough for the config "
+                          "(%zu bytes)",
+                          mName, config->size());
+                    return NO_MEMORY;
                 }
-            } else if (oStreamFormat.value == C2BufferData::LINEAR && i == 0
-                        && (!prepend || prepend.value == PREPEND_HEADER_TO_NONE)) {
-                // WORKAROUND: Some apps expect CSD available without queueing
-                //             any input. Queue an empty buffer to get the CSD.
-                buffer->setRange(0, 0);
+                sp<MediaCodecBuffer> buffer = i->buffer;
+                memcpy(buffer->base(), config->data(), config->size());
+                buffer->setRange(0, config->size());
                 buffer->meta()->clear();
                 buffer->meta()->setInt64("timeUs", 0);
-                post = false;
+                buffer->meta()->setInt32("csd", 1);
+                if (queueInputBufferInternal(buffer) != OK) {
+                    ALOGW("[%s] Error while queueing a flushed config",
+                          mName);
+                    return UNKNOWN_ERROR;
+                }
+                clientInputBuffers.erase(i);
             }
-            if (post) {
-                mCallback->onInputBufferAvailable(index, buffer);
-            } else {
-                toBeQueued.emplace_back(buffer);
+        } else if (oStreamFormat.value == C2BufferData::LINEAR &&
+                   (!prepend || prepend.value == PREPEND_HEADER_TO_NONE)) {
+            sp<MediaCodecBuffer> buffer = clientInputBuffers.front().buffer;
+            // WORKAROUND: Some apps expect CSD available without queueing
+            //             any input. Queue an empty buffer to get the CSD.
+            buffer->setRange(0, 0);
+            buffer->meta()->clear();
+            buffer->meta()->setInt64("timeUs", 0);
+            if (queueInputBufferInternal(buffer) != OK) {
+                ALOGW("[%s] Error while queueing an empty buffer to get CSD",
+                      mName);
+                return UNKNOWN_ERROR;
             }
+            clientInputBuffers.pop_front();
         }
     }
-    for (const sp<MediaCodecBuffer> &buffer : toBeQueued) {
-        if (queueInputBufferInternal(buffer) != OK) {
-            ALOGV("[%s] Error while queueing initial buffers", mName);
-        }
+
+    for (const ClientInputBuffer& clientInputBuffer: clientInputBuffers) {
+        mCallback->onInputBufferAvailable(
+                clientInputBuffer.index,
+                clientInputBuffer.buffer);
     }
+
     return OK;
 }
 
diff --git a/media/codecs/g711/decoder/Android.bp b/media/codecs/g711/decoder/Android.bp
index 377833f..efff60b 100644
--- a/media/codecs/g711/decoder/Android.bp
+++ b/media/codecs/g711/decoder/Android.bp
@@ -36,6 +36,7 @@
         cfi: true,
     },
     apex_available: ["com.android.media.swcodec"],
+    min_sdk_version: "29",
 
     target: {
         darwin: {
diff --git a/media/extractors/Android.bp b/media/extractors/Android.bp
index 7c4e62f..f9abfe3 100644
--- a/media/extractors/Android.bp
+++ b/media/extractors/Android.bp
@@ -21,7 +21,6 @@
 
     shared_libs: [
         "liblog",
-        "libmediandk#29",
     ],
 
     // extractors are supposed to work on Q(29)
@@ -39,6 +38,21 @@
 
     version_script: "exports.lds",
 
+    target: {
+        android: {
+            shared_libs: [
+                "libmediandk#29",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libutils",
+                "libmediandk_format",
+                "libmedia_ndkformatpriv",
+            ],
+        },
+    },
+
     sanitize: {
         cfi: true,
         misc_undefined: [
diff --git a/media/extractors/fuzzers/Android.bp b/media/extractors/fuzzers/Android.bp
index 818e466..0a70815 100644
--- a/media/extractors/fuzzers/Android.bp
+++ b/media/extractors/fuzzers/Android.bp
@@ -71,10 +71,15 @@
         "frameworks/av/media/extractors/mp4",
     ],
 
+    header_libs: [
+        "libaudioclient_headers",
+    ],
+
     static_libs: [
         "liblog",
         "libstagefright_foundation",
-        "libmedia",
+        "libmediandk_format",
+        "libmedia_ndkformatpriv",
         "libextractorfuzzerbase",
         "libstagefright_id3",
         "libstagefright_esds",
@@ -83,7 +88,6 @@
 
     shared_libs: [
         "libutils",
-        "libmediandk",
         "libbinder",
     ],
 
@@ -95,6 +99,8 @@
         ],
         componentid: 155276,
     },
+
+    host_supported: true,
 }
 
 cc_fuzz {
diff --git a/media/extractors/mp4/Android.bp b/media/extractors/mp4/Android.bp
index e48e1b7..afa055f 100644
--- a/media/extractors/mp4/Android.bp
+++ b/media/extractors/mp4/Android.bp
@@ -16,4 +16,12 @@
         "libstagefright_id3",
         "libutils",
     ],
+
+    host_supported: true,
+
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
 }
diff --git a/media/extractors/tests/ExtractorUnitTest.cpp b/media/extractors/tests/ExtractorUnitTest.cpp
index 33d6b7a..a18b122 100644
--- a/media/extractors/tests/ExtractorUnitTest.cpp
+++ b/media/extractors/tests/ExtractorUnitTest.cpp
@@ -726,6 +726,9 @@
     if (configParam.profile != kUndefined) {
         if (AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_PROFILE, &profile)) {
             ASSERT_EQ(configParam.profile, profile) << "profile not as expected";
+        } else if (mExtractorName == AAC &&
+                   AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_AAC_PROFILE, &profile)) {
+            ASSERT_EQ(configParam.profile, profile) << "profile not as expected";
         } else {
             ASSERT_TRUE(false) << "profile not returned in extractor";
         }
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index 79fa5ed..4520823 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -288,7 +288,7 @@
             requestStop();
         }
 
-        logBufferState();
+        logReleaseBufferState();
 
         setState(AAUDIO_STREAM_STATE_CLOSING);
         aaudio_handle_t serviceStreamHandle = mServiceStreamHandle;
@@ -783,6 +783,14 @@
         adjustedFrames = std::min(actualFrames, adjustedFrames);
     }
 
+    if (adjustedFrames != mBufferSizeInFrames) {
+        android::mediametrics::LogItem(mMetricsId)
+                .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_SETBUFFERSIZE)
+                .set(AMEDIAMETRICS_PROP_BUFFERSIZEFRAMES, adjustedFrames)
+                .set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t) getXRunCount())
+                .record();
+    }
+
     mBufferSizeInFrames = adjustedFrames;
     ALOGV("%s(%d) returns %d", __func__, requestedFrames, adjustedFrames);
     return (aaudio_result_t) adjustedFrames;
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index 0644368..bc973bd 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -116,9 +116,10 @@
     }
 }
 
-void AudioStream::logBufferState() {
+void AudioStream::logReleaseBufferState() {
     if (mMetricsId.size() > 0) {
         android::mediametrics::LogItem(mMetricsId)
+                .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_RELEASE)
                 .set(AMEDIAMETRICS_PROP_BUFFERSIZEFRAMES, (int32_t) getBufferSize())
                 .set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t) getXRunCount())
                 .record();
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index 613a092..fb71c36 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -115,7 +115,7 @@
 
     // log to MediaMetrics
     virtual void logOpen();
-    void logBufferState();
+    void logReleaseBufferState();
 
     /**
      * Free any hardware or system resources from the open() call.
diff --git a/media/libaaudio/src/legacy/AudioStreamRecord.cpp b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
index 853c0db..6e5110f 100644
--- a/media/libaaudio/src/legacy/AudioStreamRecord.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
@@ -292,7 +292,7 @@
     //  Then call it from here
     if (getState() != AAUDIO_STREAM_STATE_CLOSING) {
         mAudioRecord->removeAudioDeviceCallback(mDeviceCallback);
-        logBufferState();
+        logReleaseBufferState();
         mAudioRecord.clear();
         mFixedBlockWriter.close();
         return AudioStream::release_l();
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.cpp b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
index 1120f05..ea08361 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -254,7 +254,7 @@
 aaudio_result_t AudioStreamTrack::release_l() {
     if (getState() != AAUDIO_STREAM_STATE_CLOSING) {
         mAudioTrack->removeAudioDeviceCallback(mDeviceCallback);
-        logBufferState();
+        logReleaseBufferState();
         // TODO Investigate why clear() causes a hang in test_various.cpp
         // if I call close() from a data callback.
         // But the same thing in AudioRecord is OK!
diff --git a/media/libaaudio/tests/Android.bp b/media/libaaudio/tests/Android.bp
index a6e5f70..8935d57 100644
--- a/media/libaaudio/tests/Android.bp
+++ b/media/libaaudio/tests/Android.bp
@@ -233,6 +233,7 @@
     srcs: ["test_steal_exclusive.cpp"],
     shared_libs: [
         "libaaudio",
+        "liblog",
         "libbinder",
         "libcutils",
         "libutils",
diff --git a/media/libaaudio/tests/test_steal_exclusive.cpp b/media/libaaudio/tests/test_steal_exclusive.cpp
index 2a05910..05c560d 100644
--- a/media/libaaudio/tests/test_steal_exclusive.cpp
+++ b/media/libaaudio/tests/test_steal_exclusive.cpp
@@ -47,137 +47,271 @@
  */
 
 #include <atomic>
+#include <mutex>
 #include <stdio.h>
 #include <thread>
 #include <unistd.h>
 
+#include <android/log.h>
+
 #include <aaudio/AAudio.h>
+#include <aaudio/AAudioTesting.h>
 
 #define DEFAULT_TIMEOUT_NANOS  ((int64_t)1000000000)
 #define SOLO_DURATION_MSEC    2000
 #define DUET_DURATION_MSEC    8000
 #define SLEEP_DURATION_MSEC    500
 
+#define MODULE_NAME  "stealAudio"
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, MODULE_NAME, __VA_ARGS__)
+
 static const char * s_sharingModeToText(aaudio_sharing_mode_t mode) {
     return (mode == AAUDIO_SHARING_MODE_EXCLUSIVE) ? "EXCLUSIVE"
         : ((mode == AAUDIO_SHARING_MODE_SHARED)  ? "SHARED"
             : AAudio_convertResultToText(mode));
 }
 
+static const char * s_performanceModeToText(aaudio_performance_mode_t mode) {
+    return (mode == AAUDIO_PERFORMANCE_MODE_LOW_LATENCY) ? "LOWLAT"
+        : ((mode == AAUDIO_PERFORMANCE_MODE_NONE)  ? "NONE"
+            : AAudio_convertResultToText(mode));
+}
+
+static aaudio_data_callback_result_t s_myDataCallbackProc(
+        AAudioStream * /* stream */,
+        void *userData,
+        void *audioData,
+        int32_t numFrames);
+
 static void s_myErrorCallbackProc(
         AAudioStream *stream,
         void *userData,
         aaudio_result_t error);
 
-struct AudioEngine {
-    AAudioStream        *stream = nullptr;
-    std::thread         *thread = nullptr;
-    aaudio_direction_t   direction = AAUDIO_DIRECTION_OUTPUT;
+class AudioEngine {
+public:
+
+    AudioEngine(const char *name) {
+        mName = name;
+    }
 
     // These counters are read and written by the callback and the main thread.
-    std::atomic<int32_t> framesRead{};
     std::atomic<int32_t> framesCalled{};
     std::atomic<int32_t> callbackCount{};
+    std::atomic<aaudio_sharing_mode_t> sharingMode{};
+    std::atomic<aaudio_performance_mode_t> performanceMode{};
+    std::atomic<bool> isMMap{false};
 
+    void setMaxRetries(int maxRetries) {
+        mMaxRetries = maxRetries;
+    }
+
+    void setOpenDelayMillis(int openDelayMillis) {
+        mOpenDelayMillis = openDelayMillis;
+    }
+
+    void restartStream() {
+        int retriesLeft = mMaxRetries;
+        aaudio_result_t result;
+        do {
+            closeAudioStream();
+            if (mOpenDelayMillis) usleep(mOpenDelayMillis * 1000);
+            openAudioStream(mDirection, mRequestedSharingMode);
+            // It is possible for the stream to be disconnected, or stolen between the time
+            // it is opened and when it is started. If that happens then try again.
+            // If it was stolen then it should succeed the second time because there will already be
+            // a SHARED stream, which will not get stolen.
+            result = AAudioStream_requestStart(mStream);
+            printf("%s: AAudioStream_requestStart() returns %s\n",
+                    mName.c_str(),
+                    AAudio_convertResultToText(result));
+        } while (retriesLeft-- > 0 && result != AAUDIO_OK);
+    }
+
+    aaudio_data_callback_result_t onAudioReady(
+            void * /*audioData */,
+            int32_t numFrames) {
+        callbackCount++;
+        framesCalled += numFrames;
+        return AAUDIO_CALLBACK_RESULT_CONTINUE;
+    }
+
+    aaudio_result_t openAudioStream(aaudio_direction_t direction,
+            aaudio_sharing_mode_t requestedSharingMode) {
+        std::lock_guard<std::mutex> lock(mLock);
+
+        AAudioStreamBuilder *builder = nullptr;
+        mDirection = direction;
+        mRequestedSharingMode = requestedSharingMode;
+
+        // Use an AAudioStreamBuilder to contain requested parameters.
+        aaudio_result_t result = AAudio_createStreamBuilder(&builder);
+        if (result != AAUDIO_OK) {
+            printf("AAudio_createStreamBuilder returned %s",
+                   AAudio_convertResultToText(result));
+            return result;
+        }
+
+        // Request stream properties.
+        AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_FLOAT);
+        AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+        AAudioStreamBuilder_setSharingMode(builder, mRequestedSharingMode);
+        AAudioStreamBuilder_setDirection(builder, direction);
+        AAudioStreamBuilder_setDataCallback(builder, s_myDataCallbackProc, this);
+        AAudioStreamBuilder_setErrorCallback(builder, s_myErrorCallbackProc, this);
+
+        // Create an AAudioStream using the Builder.
+        result = AAudioStreamBuilder_openStream(builder, &mStream);
+        AAudioStreamBuilder_delete(builder);
+        builder = nullptr;
+        if (result != AAUDIO_OK) {
+            printf("AAudioStreamBuilder_openStream returned %s",
+                   AAudio_convertResultToText(result));
+        }
+
+        // See what kind of stream we actually opened.
+        int32_t deviceId = AAudioStream_getDeviceId(mStream);
+        sharingMode = AAudioStream_getSharingMode(mStream);
+        performanceMode = AAudioStream_getPerformanceMode(mStream);
+        isMMap = AAudioStream_isMMapUsed(mStream);
+        printf("%s: opened: deviceId = %3d, sharingMode = %s, perf = %s, %s --------\n",
+               mName.c_str(),
+               deviceId,
+               s_sharingModeToText(sharingMode),
+               s_performanceModeToText(performanceMode),
+               (isMMap ? "MMAP" : "Legacy")
+               );
+
+        return result;
+    }
+
+    aaudio_result_t closeAudioStream() {
+        std::lock_guard<std::mutex> lock(mLock);
+        aaudio_result_t result = AAUDIO_OK;
+        if (mStream != nullptr) {
+            result = AAudioStream_close(mStream);
+            if (result != AAUDIO_OK) {
+                printf("AAudioStream_close returned %s\n",
+                       AAudio_convertResultToText(result));
+            }
+            mStream = nullptr;
+        }
+        return result;
+    }
+
+    /**
+     * @return 0 is OK, -1 for error
+     */
+    int checkEnginePositions() {
+        std::lock_guard<std::mutex> lock(mLock);
+        if (mStream == nullptr) return 0;
+
+        const int64_t framesRead = AAudioStream_getFramesRead(mStream);
+        const int64_t framesWritten = AAudioStream_getFramesWritten(mStream);
+        const int32_t delta = (int32_t)(framesWritten - framesRead);
+        printf("%s: playing framesRead = %7d, framesWritten = %7d"
+               ", delta = %4d, framesCalled = %6d, callbackCount = %4d\n",
+               mName.c_str(),
+               (int32_t) framesRead,
+               (int32_t) framesWritten,
+               delta,
+               framesCalled.load(),
+               callbackCount.load()
+        );
+        if (delta > AAudioStream_getBufferCapacityInFrames(mStream)) {
+            printf("ERROR - delta > capacity\n");
+            return -1;
+        }
+        return 0;
+    }
+
+    aaudio_result_t start() {
+        std::lock_guard<std::mutex> lock(mLock);
+        reset();
+        if (mStream == nullptr) return 0;
+        return AAudioStream_requestStart(mStream);
+    }
+
+    aaudio_result_t stop() {
+        std::lock_guard<std::mutex> lock(mLock);
+        if (mStream == nullptr) return 0;
+        return AAudioStream_requestStop(mStream);
+    }
+
+    bool hasAdvanced() {
+        std::lock_guard<std::mutex> lock(mLock);
+        if (mStream == nullptr) return 0;
+        if (mDirection == AAUDIO_DIRECTION_OUTPUT) {
+            return AAudioStream_getFramesRead(mStream) > 0;
+        } else {
+            return AAudioStream_getFramesWritten(mStream) > 0;
+        }
+    }
+
+    aaudio_result_t verify() {
+        int errorCount = 0;
+        if (hasAdvanced()) {
+            printf("%s: stream is running => PASS\n", mName.c_str());
+        } else {
+            errorCount++;
+            printf("%s: stream should be running => FAIL!!\n", mName.c_str());
+        }
+
+        if (isMMap) {
+            printf("%s: data path is MMAP => PASS\n", mName.c_str());
+        } else {
+            errorCount++;
+            printf("%s: data path is Legacy! => FAIL\n", mName.c_str());
+        }
+
+        // Check for PASS/FAIL
+        if (sharingMode == AAUDIO_SHARING_MODE_SHARED) {
+            printf("%s: mode is SHARED => PASS\n", mName.c_str());
+        } else {
+            errorCount++;
+            printf("%s: modes is EXCLUSIVE => FAIL!!\n", mName.c_str());
+        }
+        return errorCount ? AAUDIO_ERROR_INVALID_FORMAT : AAUDIO_OK;
+    }
+
+private:
     void reset() {
-        framesRead.store(0);
         framesCalled.store(0);
         callbackCount.store(0);
     }
+
+    AAudioStream       *mStream = nullptr;
+    aaudio_direction_t  mDirection = AAUDIO_DIRECTION_OUTPUT;
+    aaudio_sharing_mode_t mRequestedSharingMode = AAUDIO_UNSPECIFIED;
+    std::mutex          mLock;
+    std::string         mName;
+    int                 mMaxRetries = 1;
+    int                 mOpenDelayMillis = 0;
 };
 
 // Callback function that fills the audio output buffer.
 static aaudio_data_callback_result_t s_myDataCallbackProc(
-        AAudioStream *stream,
+        AAudioStream * /* stream */,
         void *userData,
         void *audioData,
         int32_t numFrames
 ) {
-    (void) audioData;
-    (void) numFrames;
-    AudioEngine *engine = (struct AudioEngine *)userData;
-    engine->callbackCount++;
-
-    engine->framesRead = (int32_t)AAudioStream_getFramesRead(stream);
-    engine->framesCalled += numFrames;
-    return AAUDIO_CALLBACK_RESULT_CONTINUE;
-}
-
-static aaudio_result_t s_OpenAudioStream(struct AudioEngine *engine,
-                                         aaudio_direction_t direction) {
-    AAudioStreamBuilder *builder = nullptr;
-    engine->direction = direction;
-
-    // Use an AAudioStreamBuilder to contain requested parameters.
-    aaudio_result_t result = AAudio_createStreamBuilder(&builder);
-    if (result != AAUDIO_OK) {
-        printf("AAudio_createStreamBuilder returned %s",
-               AAudio_convertResultToText(result));
-        return result;
-    }
-
-    // Request stream properties.
-    AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_FLOAT);
-    AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
-    AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_EXCLUSIVE);
-    AAudioStreamBuilder_setDirection(builder, direction);
-    AAudioStreamBuilder_setDataCallback(builder, s_myDataCallbackProc, engine);
-    AAudioStreamBuilder_setErrorCallback(builder, s_myErrorCallbackProc, engine);
-
-    // Create an AAudioStream using the Builder.
-    result = AAudioStreamBuilder_openStream(builder, &engine->stream);
-    AAudioStreamBuilder_delete(builder);
-    builder = nullptr;
-    if (result != AAUDIO_OK) {
-        printf("AAudioStreamBuilder_openStream returned %s",
-               AAudio_convertResultToText(result));
-    }
-
-    // See see what kind of stream we actually opened.
-    int32_t deviceId = AAudioStream_getDeviceId(engine->stream);
-    aaudio_sharing_mode_t actualSharingMode = AAudioStream_getSharingMode(engine->stream);
-    printf("-------- opened: deviceId = %3d, actualSharingMode = %s\n",
-           deviceId,
-           s_sharingModeToText(actualSharingMode));
-
-    return result;
-}
-
-static aaudio_result_t s_CloseAudioStream(struct AudioEngine *engine) {
-    aaudio_result_t result = AAUDIO_OK;
-    if (engine->stream != nullptr) {
-        result = AAudioStream_close(engine->stream);
-        if (result != AAUDIO_OK) {
-            printf("AAudioStream_close returned %s\n",
-                   AAudio_convertResultToText(result));
-        }
-        engine->stream = nullptr;
-    }
-    return result;
+    AudioEngine *engine = (AudioEngine *)userData;
+    return engine->onAudioReady(audioData, numFrames);
 }
 
 static void s_myRestartStreamProc(void *userData) {
+    LOGI("%s() called", __func__);
     printf("%s() - restart in separate thread\n", __func__);
     AudioEngine *engine = (AudioEngine *) userData;
-    int retriesLeft = 1;
-    aaudio_result_t result;
-    do {
-        s_CloseAudioStream(engine);
-        s_OpenAudioStream(engine, engine->direction);
-        // It is possible for the stream to be disconnected, or stolen between the time
-        // it is opened and when it is started. If that happens then try again.
-        // If it was stolen then it should succeed the second time because there will already be
-        // a SHARED stream, which will not get stolen.
-        result = AAudioStream_requestStart(engine->stream);
-        printf("%s() - AAudioStream_requestStart() returns %s\n", __func__,
-                AAudio_convertResultToText(result));
-    } while (retriesLeft-- > 0 && result != AAUDIO_OK);
+    engine->restartStream();
 }
 
 static void s_myErrorCallbackProc(
         AAudioStream * /* stream */,
         void *userData,
         aaudio_result_t error) {
+    LOGI("%s() called", __func__);
     printf("%s() - error = %s\n", __func__, AAudio_convertResultToText(error));
     // Handle error on a separate thread.
     std::thread t(s_myRestartStreamProc, userData);
@@ -185,48 +319,28 @@
 }
 
 static void s_usage() {
-    printf("test_steal_exclusive [-i]\n");
+    printf("test_steal_exclusive [-i] [-r{maxRetries}] [-d{delay}] -s\n");
     printf("     -i direction INPUT, otherwise OUTPUT\n");
+    printf("     -d delay open by milliseconds, default = 0\n");
+    printf("     -r max retries in the error callback, default = 1\n");
+    printf("     -s try to open in SHARED mode\n");
 }
 
-/**
- * @return 0 is OK, -1 for error
- */
-static int s_checkEnginePositions(AudioEngine *engine) {
-    if (engine->stream == nullptr) return 0; // race condition with onError procs!
-
-    const int64_t framesRead = AAudioStream_getFramesRead(engine->stream);
-    const int64_t framesWritten = AAudioStream_getFramesWritten(engine->stream);
-    const int32_t delta = (int32_t)(framesWritten - framesRead);
-    printf("playing framesRead = %7d, framesWritten = %7d"
-           ", delta = %4d, framesCalled = %6d, callbackCount = %4d\n",
-           (int32_t) framesRead,
-           (int32_t) framesWritten,
-           delta,
-           engine->framesCalled.load(),
-           engine->callbackCount.load()
-    );
-    if (delta > AAudioStream_getBufferCapacityInFrames(engine->stream)) {
-        printf("ERROR - delta > capacity\n");
-        return -1;
-    }
-    return 0;
-}
-
-int main(int argc, char **argv) {
-    (void) argc;
-    (void) argv;
-    struct AudioEngine victim;
-    struct AudioEngine thief;
+int main(int argc, char ** argv) {
+    AudioEngine victim("victim");
+    AudioEngine thief("thief");
     aaudio_direction_t direction = AAUDIO_DIRECTION_OUTPUT;
     aaudio_result_t result = AAUDIO_OK;
     int errorCount = 0;
+    int maxRetries = 1;
+    int openDelayMillis = 0;
+    aaudio_sharing_mode_t requestedSharingMode = AAUDIO_SHARING_MODE_EXCLUSIVE;
 
     // Make printf print immediately so that debug info is not stuck
     // in a buffer if we hang or crash.
     setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
 
-    printf("Test Stealing an EXCLUSIVE stream V1.0\n");
+    printf("Test interaction between streams V1.1\n");
     printf("\n");
 
     for (int i = 1; i < argc; i++) {
@@ -234,9 +348,18 @@
         if (arg[0] == '-') {
             char option = arg[1];
             switch (option) {
+                case 'd':
+                    openDelayMillis = atoi(&arg[2]);
+                    break;
                 case 'i':
                     direction = AAUDIO_DIRECTION_INPUT;
                     break;
+                case 'r':
+                    maxRetries = atoi(&arg[2]);
+                    break;
+                case 's':
+                    requestedSharingMode = AAUDIO_SHARING_MODE_SHARED;
+                    break;
                 default:
                     s_usage();
                     exit(EXIT_FAILURE);
@@ -249,16 +372,35 @@
         }
     }
 
-    result = s_OpenAudioStream(&victim, direction);
+    victim.setOpenDelayMillis(openDelayMillis);
+    thief.setOpenDelayMillis(openDelayMillis);
+    victim.setMaxRetries(maxRetries);
+    thief.setMaxRetries(maxRetries);
+
+    result = victim.openAudioStream(direction, requestedSharingMode);
     if (result != AAUDIO_OK) {
         printf("s_OpenAudioStream victim returned %s\n",
                AAudio_convertResultToText(result));
         errorCount++;
     }
-    victim.reset();
+
+    if (victim.sharingMode == requestedSharingMode) {
+        printf("Victim modes is %s => OK\n", s_sharingModeToText(requestedSharingMode));
+    } else {
+        printf("Victim modes should be %s => test not valid!\n",
+                s_sharingModeToText(requestedSharingMode));
+        goto onerror;
+    }
+
+    if (victim.isMMap) {
+        printf("Victim data path is MMAP => OK\n");
+    } else {
+        printf("Victim data path is Legacy! => test not valid\n");
+        goto onerror;
+    }
 
     // Start stream.
-    result = AAudioStream_requestStart(victim.stream);
+    result = victim.start();
     printf("AAudioStream_requestStart(VICTIM) returned %d >>>>>>>>>>>>>>>>>>>>>>\n", result);
     if (result != AAUDIO_OK) {
         errorCount++;
@@ -267,77 +409,69 @@
     if (result == AAUDIO_OK) {
         const int watchLoops = SOLO_DURATION_MSEC / SLEEP_DURATION_MSEC;
         for (int i = watchLoops; i > 0; i--) {
-            errorCount += s_checkEnginePositions(&victim) ? 1 : 0;
+            errorCount += victim.checkEnginePositions() ? 1 : 0;
             usleep(SLEEP_DURATION_MSEC * 1000);
         }
     }
 
-    printf("Try to start the THIEF stream that may steal the VICTIM MMAP resource -----\n");
-    result = s_OpenAudioStream(&thief, direction);
+    printf("Trying to start the THIEF stream, which may steal the VICTIM MMAP resource -----\n");
+    result = thief.openAudioStream(direction, requestedSharingMode);
     if (result != AAUDIO_OK) {
         printf("s_OpenAudioStream victim returned %s\n",
                AAudio_convertResultToText(result));
         errorCount++;
     }
-    thief.reset();
 
     // Start stream.
-    result = AAudioStream_requestStart(thief.stream);
+    result = thief.start();
     printf("AAudioStream_requestStart(THIEF) returned %d >>>>>>>>>>>>>>>>>>>>>>\n", result);
     if (result != AAUDIO_OK) {
         errorCount++;
     }
-    printf("You might enjoy plugging in a headset now to see what happens...\n");
+
+    // Give stream time to advance.
+    usleep(SLEEP_DURATION_MSEC * 1000);
+
+    if (victim.verify()) {
+        errorCount++;
+        goto onerror;
+    }
+    if (thief.verify()) {
+        errorCount++;
+        goto onerror;
+    }
+
+    LOGI("Both streams running. Ask user to plug in headset. ====");
+    printf("\n====\nPlease PLUG IN A HEADSET now!\n====\n\n");
 
     if (result == AAUDIO_OK) {
         const int watchLoops = DUET_DURATION_MSEC / SLEEP_DURATION_MSEC;
         for (int i = watchLoops; i > 0; i--) {
-            printf("victim: ");
-            errorCount += s_checkEnginePositions(&victim) ? 1 : 0;
-            printf(" thief: ");
-            errorCount += s_checkEnginePositions(&thief) ? 1 : 0;
+            errorCount += victim.checkEnginePositions() ? 1 : 0;
+            errorCount += thief.checkEnginePositions() ? 1 : 0;
             usleep(SLEEP_DURATION_MSEC * 1000);
         }
     }
 
-    // Check for PASS/FAIL
-    aaudio_sharing_mode_t victimSharingMode = AAudioStream_getSharingMode(victim.stream);
-    aaudio_sharing_mode_t thiefSharingMode = AAudioStream_getSharingMode(thief.stream);
-    printf("victimSharingMode = %s, thiefSharingMode = %s, - ",
-           s_sharingModeToText(victimSharingMode),
-           s_sharingModeToText(thiefSharingMode));
-    if ((victimSharingMode == AAUDIO_SHARING_MODE_SHARED)
-            && (thiefSharingMode == AAUDIO_SHARING_MODE_SHARED)) {
-        printf("Both modes are SHARED => PASS\n");
-    } else {
-        errorCount++;
-        printf("Both modes should be SHARED => FAIL!!\n");
-    }
+    errorCount += victim.verify() ? 1 : 0;
+    errorCount += thief.verify() ? 1 : 0;
 
-    const int64_t victimFramesRead = AAudioStream_getFramesRead(victim.stream);
-    const int64_t thiefFramesRead = AAudioStream_getFramesRead(thief.stream);
-    printf("victimFramesRead = %d, thiefFramesRead = %d, - ",
-           (int)victimFramesRead, (int)thiefFramesRead);
-    if (victimFramesRead > 0 && thiefFramesRead > 0) {
-        printf("Both streams are running => PASS\n");
-    } else {
-        errorCount++;
-        printf("Both streams should be running => FAIL!!\n");
-    }
-
-    result = AAudioStream_requestStop(victim.stream);
+    result = victim.stop();
     printf("AAudioStream_requestStop() returned %d <<<<<<<<<<<<<<<<<<<<<\n", result);
     if (result != AAUDIO_OK) {
+        printf("stop result = %d = %s\n", result, AAudio_convertResultToText(result));
         errorCount++;
     }
-    result = AAudioStream_requestStop(thief.stream);
+    result = thief.stop();
     printf("AAudioStream_requestStop() returned %d <<<<<<<<<<<<<<<<<<<<<\n", result);
     if (result != AAUDIO_OK) {
+        printf("stop result = %d = %s\n", result, AAudio_convertResultToText(result));
         errorCount++;
     }
 
-    s_CloseAudioStream(&victim);
-    s_CloseAudioStream(&thief);
+onerror:
+    victim.closeAudioStream();
+    thief.closeAudioStream();
 
     printf("aaudio result = %d = %s\n", result, AAudio_convertResultToText(result));
     printf("test %s\n", errorCount ? "FAILED" : "PASSED");
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index 604d182..005d358 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -787,8 +787,9 @@
             .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_STOP)
             .set(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, (int64_t)(systemTime() - beginNs))
             .set(AMEDIAMETRICS_PROP_STATE, stateToString(mState))
+            .set(AMEDIAMETRICS_PROP_BUFFERSIZEFRAMES, (int32_t)mProxy->getBufferSizeInFrames())
+            .set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t) getUnderrunCount_l())
             .record();
-        logBufferSizeUnderruns();
     });
 
     ALOGV("%s(%d): prior state:%s", __func__, mPortId, stateToString(mState));
@@ -1141,16 +1142,6 @@
     return NO_ERROR;
 }
 
-void AudioTrack::logBufferSizeUnderruns() {
-    LOG_ALWAYS_FATAL_IF(mMetricsId.size() == 0, "mMetricsId is empty!");
-    ALOGD("%s(), mMetricsId = %s", __func__, mMetricsId.c_str());
-    // FIXME THis hangs! Why?
-//    android::mediametrics::LogItem(mMetricsId)
-//            .set(AMEDIAMETRICS_PROP_BUFFERSIZEFRAMES, (int32_t) getBufferSizeInFrames())
-//            .set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t) getUnderrunCount())
-//            .record();
-}
-
 ssize_t AudioTrack::setBufferSizeInFrames(size_t bufferSizeInFrames)
 {
     AutoMutex lock(mLock);
@@ -1165,7 +1156,11 @@
     ssize_t originalBufferSize = mProxy->getBufferSizeInFrames();
     ssize_t finalBufferSize  = mProxy->setBufferSizeInFrames((uint32_t) bufferSizeInFrames);
     if (originalBufferSize != finalBufferSize) {
-        logBufferSizeUnderruns();
+        android::mediametrics::LogItem(mMetricsId)
+                .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_SETBUFFERSIZE)
+                .set(AMEDIAMETRICS_PROP_BUFFERSIZEFRAMES, (int32_t)mProxy->getBufferSizeInFrames())
+                .set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t)getUnderrunCount_l())
+                .record();
     }
     return finalBufferSize;
 }
diff --git a/media/libaudioclient/include/media/AudioTrack.h b/media/libaudioclient/include/media/AudioTrack.h
index 17af7d4..0dbd842 100644
--- a/media/libaudioclient/include/media/AudioTrack.h
+++ b/media/libaudioclient/include/media/AudioTrack.h
@@ -1274,8 +1274,6 @@
     std::string mMetricsId;  // GUARDED_BY(mLock), could change in createTrack_l().
     std::string mCallerName; // for example "aaudio"
 
-    void logBufferSizeUnderruns();
-
 private:
     class AudioTrackCallback : public media::BnAudioTrackCallback {
     public:
diff --git a/media/libaudioprocessing/AudioMixerBase.cpp b/media/libaudioprocessing/AudioMixerBase.cpp
index 64f91fe..a54e22f 100644
--- a/media/libaudioprocessing/AudioMixerBase.cpp
+++ b/media/libaudioprocessing/AudioMixerBase.cpp
@@ -1500,7 +1500,7 @@
     ALOGVV("track__Resample\n");
     mResampler->setSampleRate(sampleRate);
     const bool ramp = needsRamp();
-    if (MIXTYPE == MIXTYPE_MONOEXPAND || MIXTYPE == MIXTYPE_STEREOEXPAND
+    if (MIXTYPE == MIXTYPE_MONOEXPAND || MIXTYPE == MIXTYPE_STEREOEXPAND // custom volume handling
             || ramp || aux != NULL) {
         // if ramp:        resample with unity gain to temp buffer and scale/mix in 2nd step.
         // if aux != NULL: resample with unity gain to temp buffer then apply send level.
diff --git a/media/libaudioprocessing/AudioMixerOps.h b/media/libaudioprocessing/AudioMixerOps.h
index 2748182..80bd093 100644
--- a/media/libaudioprocessing/AudioMixerOps.h
+++ b/media/libaudioprocessing/AudioMixerOps.h
@@ -236,7 +236,8 @@
             || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
             || MIXTYPE == MIXTYPE_STEREOEXPAND);
     auto proc = [](auto& a, const auto& b) {
-        if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL) {
+        if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
+                || MIXTYPE == MIXTYPE_STEREOEXPAND) {
             a += b;
         } else {
             a = b;
diff --git a/media/libeffects/preprocessing/PreProcessing.cpp b/media/libeffects/preprocessing/PreProcessing.cpp
index 5fab5be..c7afe2f 100644
--- a/media/libeffects/preprocessing/PreProcessing.cpp
+++ b/media/libeffects/preprocessing/PreProcessing.cpp
@@ -1240,9 +1240,9 @@
             memcpy(outBuffer->s16,
                   session->outBuf,
                   fr * session->outChannelCount * sizeof(int16_t));
-            memcpy(session->outBuf,
-                  session->outBuf + fr * session->outChannelCount,
-                  (session->framesOut - fr) * session->outChannelCount * sizeof(int16_t));
+            memmove(session->outBuf,
+                    session->outBuf + fr * session->outChannelCount,
+                    (session->framesOut - fr) * session->outChannelCount * sizeof(int16_t));
             session->framesOut -= fr;
             framesWr += fr;
         }
@@ -1303,9 +1303,9 @@
                                                         session->procFrame->data_,
                                                         &frOut);
             }
-            memcpy(session->inBuf,
-                   session->inBuf + frIn * session->inChannelCount,
-                   (session->framesIn - frIn) * session->inChannelCount * sizeof(int16_t));
+            memmove(session->inBuf,
+                    session->inBuf + frIn * session->inChannelCount,
+                    (session->framesIn - frIn) * session->inChannelCount * sizeof(int16_t));
             session->framesIn -= frIn;
         } else {
             size_t fr = session->frameCount - session->framesIn;
@@ -1381,9 +1381,9 @@
         memcpy(outBuffer->s16 + framesWr * session->outChannelCount,
               session->outBuf,
               fr * session->outChannelCount * sizeof(int16_t));
-        memcpy(session->outBuf,
-              session->outBuf + fr * session->outChannelCount,
-              (session->framesOut - fr) * session->outChannelCount * sizeof(int16_t));
+        memmove(session->outBuf,
+                session->outBuf + fr * session->outChannelCount,
+                (session->framesOut - fr) * session->outChannelCount * sizeof(int16_t));
         session->framesOut -= fr;
         outBuffer->frameCount += fr;
 
@@ -1837,9 +1837,9 @@
                                                         session->revFrame->data_,
                                                         &frOut);
             }
-            memcpy(session->revBuf,
-                   session->revBuf + frIn * session->inChannelCount,
-                   (session->framesRev - frIn) * session->inChannelCount * sizeof(int16_t));
+            memmove(session->revBuf,
+                    session->revBuf + frIn * session->inChannelCount,
+                    (session->framesRev - frIn) * session->inChannelCount * sizeof(int16_t));
             session->framesRev -= frIn;
         } else {
             size_t fr = session->frameCount - session->framesRev;
diff --git a/media/libmedia/Android.bp b/media/libmedia/Android.bp
index 9fbd4d9..818548d 100644
--- a/media/libmedia/Android.bp
+++ b/media/libmedia/Android.bp
@@ -30,6 +30,7 @@
         "//apex_available:platform",
         "com.android.media",
     ],
+    min_sdk_version: "29",
 }
 
 filegroup {
diff --git a/media/libmediametrics/include/MediaMetricsConstants.h b/media/libmediametrics/include/MediaMetricsConstants.h
index b916a78..0906411 100644
--- a/media/libmediametrics/include/MediaMetricsConstants.h
+++ b/media/libmediametrics/include/MediaMetricsConstants.h
@@ -175,10 +175,12 @@
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_OPEN       "open"
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_PAUSE      "pause"  // AudioTrack
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_READPARAMETERS "readParameters" // Thread
+#define AMEDIAMETRICS_PROP_EVENT_VALUE_RELEASE    "release"
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_RESTORE    "restore"
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_SETMODE    "setMode" // AudioFlinger
+#define AMEDIAMETRICS_PROP_EVENT_VALUE_SETBUFFERSIZE    "setBufferSize" // AudioTrack
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_SETPLAYBACKPARAM "setPlaybackParam" // AudioTrack
-#define AMEDIAMETRICS_PROP_EVENT_VALUE_SETVOICEVOLUME "setVoiceVolume" // AudioFlinger
+#define AMEDIAMETRICS_PROP_EVENT_VALUE_SETVOICEVOLUME   "setVoiceVolume" // AudioFlinger
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_SETVOLUME  "setVolume"  // AudioTrack
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_START      "start"  // AudioTrack, AudioRecord
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_STOP       "stop"   // AudioTrack, AudioRecord
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
index 0eaa503..e6bb2e1 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
@@ -1159,7 +1159,7 @@
         readBuffer(MEDIA_TRACK_TYPE_VIDEO, seekTimeUs, mode, &actualTimeUs);
 
         if (mode != MediaPlayerSeekMode::SEEK_CLOSEST) {
-            seekTimeUs = actualTimeUs;
+            seekTimeUs = std::max<int64_t>(0, actualTimeUs);
         }
         mVideoLastDequeueTimeUs = actualTimeUs;
     }
diff --git a/media/libmediatranscoding/aidl/android/media/ITranscodingClientCallback.aidl b/media/libmediatranscoding/aidl/android/media/ITranscodingClientCallback.aidl
index e810f1e..1a42809 100644
--- a/media/libmediatranscoding/aidl/android/media/ITranscodingClientCallback.aidl
+++ b/media/libmediatranscoding/aidl/android/media/ITranscodingClientCallback.aidl
@@ -19,6 +19,7 @@
 import android.media.TranscodingErrorCode;
 import android.media.TranscodingJobParcel;
 import android.media.TranscodingResultParcel;
+import android.os.ParcelFileDescriptor;
 
 /**
  * ITranscodingClientCallback
@@ -28,6 +29,15 @@
  * {@hide}
  */
 interface ITranscodingClientCallback {
+    /**
+    * Called to open a raw file descriptor to access data under a URI
+    *
+    * @param fileUri The path of the filename.
+    * @param mode The file mode to use. Must be one of ("r, "w", "rw")
+    * @return ParcelFileDescriptor if open the file successfully, null otherwise.
+    */
+    ParcelFileDescriptor openFileDescriptor(in @utf8InCpp String fileUri,
+                                            in @utf8InCpp String mode);
 
     /**
     * Called when the transcoding associated with the jobId finished.
diff --git a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
index 0ae8035..db171ce 100644
--- a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
@@ -59,6 +59,11 @@
 
     virtual ~TestClientCallback() { ALOGI("TestClientCallback destroyed"); };
 
+    Status openFileDescriptor(const std::string& /*in_fileUri*/, const std::string& /*in_mode*/,
+                              ::ndk::ScopedFileDescriptor* /*_aidl_return*/) override {
+        return Status::ok();
+    }
+
     Status onTranscodingFinished(int32_t in_jobId,
                                  const TranscodingResultParcel& in_result) override {
         EXPECT_EQ(in_jobId, in_result.jobId);
diff --git a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
index 25321e3..d4fd483 100644
--- a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
@@ -162,6 +162,11 @@
         ALOGD("TestClient Created");
     }
 
+    Status openFileDescriptor(const std::string& /*in_fileUri*/, const std::string& /*in_mode*/,
+                              ::ndk::ScopedFileDescriptor* /*_aidl_return*/) override {
+        return Status::ok();
+    }
+
     Status onTranscodingFinished(int32_t in_jobId,
                                  const TranscodingResultParcel& in_result) override {
         EXPECT_EQ(in_jobId, in_result.jobId);
diff --git a/media/libmediatranscoding/transcoder/Android.bp b/media/libmediatranscoding/transcoder/Android.bp
index 0b7ddbb..843d047 100644
--- a/media/libmediatranscoding/transcoder/Android.bp
+++ b/media/libmediatranscoding/transcoder/Android.bp
@@ -22,6 +22,7 @@
         "MediaSampleReaderNDK.cpp",
         "MediaSampleWriter.cpp",
         "MediaTrackTranscoder.cpp",
+        "MediaTranscoder.cpp",
         "PassthroughTrackTranscoder.cpp",
         "VideoTrackTranscoder.cpp",
     ],
diff --git a/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
index 1673b5b..10c0c6c 100644
--- a/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
@@ -101,4 +101,8 @@
     return false;
 }
 
-}  // namespace android
\ No newline at end of file
+std::shared_ptr<MediaSampleQueue> MediaTrackTranscoder::getOutputQueue() const {
+    return mOutputQueue;
+}
+
+}  // namespace android
diff --git a/media/libmediatranscoding/transcoder/MediaTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
new file mode 100644
index 0000000..f2f7810
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaTranscoder"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <media/MediaSampleReaderNDK.h>
+#include <media/MediaTranscoder.h>
+#include <media/PassthroughTrackTranscoder.h>
+#include <media/VideoTrackTranscoder.h>
+#include <unistd.h>
+
+namespace android {
+
+#define DEFINE_FORMAT_VALUE_COPY_FUNC(_type, _typeName)                                  \
+    static void copy##_typeName(const char* key, AMediaFormat* to, AMediaFormat* from) { \
+        _type value;                                                                     \
+        if (AMediaFormat_get##_typeName(from, key, &value)) {                            \
+            AMediaFormat_set##_typeName(to, key, value);                                 \
+        }                                                                                \
+    }
+
+DEFINE_FORMAT_VALUE_COPY_FUNC(const char*, String);
+DEFINE_FORMAT_VALUE_COPY_FUNC(int64_t, Int64);
+DEFINE_FORMAT_VALUE_COPY_FUNC(int32_t, Int32);
+
+static AMediaFormat* mergeMediaFormats(AMediaFormat* base, AMediaFormat* overlay) {
+    if (base == nullptr || overlay == nullptr) {
+        LOG(ERROR) << "Cannot merge null formats";
+        return nullptr;
+    }
+
+    AMediaFormat* format = AMediaFormat_new();
+    if (AMediaFormat_copy(format, base) != AMEDIA_OK) {
+        AMediaFormat_delete(format);
+        return nullptr;
+    }
+
+    // Note: AMediaFormat does not expose a function for appending values from another format or for
+    // iterating over all values and keys in a format. Instead we define a static list of known keys
+    // along with their value types and copy the ones that are present. A better solution would be
+    // to either implement required functions in NDK or to parse the overlay format's string
+    // representation and copy all existing keys.
+    static const struct {
+        const char* key;
+        void (*copyValue)(const char* key, AMediaFormat* to, AMediaFormat* from);
+    } kSupportedConfigs[] = {
+            {AMEDIAFORMAT_KEY_MIME, copyString},
+            {AMEDIAFORMAT_KEY_DURATION, copyInt64},
+            {AMEDIAFORMAT_KEY_WIDTH, copyInt32},
+            {AMEDIAFORMAT_KEY_HEIGHT, copyInt32},
+            {AMEDIAFORMAT_KEY_BIT_RATE, copyInt32},
+            {AMEDIAFORMAT_KEY_PROFILE, copyInt32},
+            {AMEDIAFORMAT_KEY_LEVEL, copyInt32},
+            {AMEDIAFORMAT_KEY_COLOR_FORMAT, copyInt32},
+            {AMEDIAFORMAT_KEY_COLOR_RANGE, copyInt32},
+            {AMEDIAFORMAT_KEY_COLOR_STANDARD, copyInt32},
+            {AMEDIAFORMAT_KEY_COLOR_TRANSFER, copyInt32},
+            {AMEDIAFORMAT_KEY_FRAME_RATE, copyInt32},
+            {AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, copyInt32},
+    };
+
+    for (int i = 0; i < (sizeof(kSupportedConfigs) / sizeof(kSupportedConfigs[0])); ++i) {
+        kSupportedConfigs[i].copyValue(kSupportedConfigs[i].key, format, overlay);
+    }
+
+    return format;
+}
+
+void MediaTranscoder::sendCallback(media_status_t status) {
+    bool expected = false;
+    if (mCallbackSent.compare_exchange_strong(expected, true)) {
+        if (status == AMEDIA_OK) {
+            mCallbacks->onFinished(this);
+        } else {
+            mCallbacks->onError(this, status);
+        }
+
+        // Transcoding is done and the callback to the client has been sent, so tear down the
+        // pipeline but do it asynchronously to avoid deadlocks. If an error occurred then
+        // automatically delete the output file.
+        const bool deleteOutputFile = status != AMEDIA_OK;
+        std::thread asyncCancelThread{
+                [self = shared_from_this(), deleteOutputFile] { self->cancel(deleteOutputFile); }};
+        asyncCancelThread.detach();
+    }
+}
+
+void MediaTranscoder::onTrackFinished(const MediaTrackTranscoder* transcoder) {
+    LOG(DEBUG) << "TrackTranscoder " << transcoder << " finished";
+}
+
+void MediaTranscoder::onTrackError(const MediaTrackTranscoder* transcoder, media_status_t status) {
+    LOG(DEBUG) << "TrackTranscoder " << transcoder << " returned error " << status;
+    sendCallback(status);
+}
+
+void MediaTranscoder::onSampleWriterFinished(media_status_t status) {
+    LOG((status != AMEDIA_OK) ? ERROR : DEBUG) << "Sample writer finished with status " << status;
+    sendCallback(status);
+}
+
+std::shared_ptr<MediaTranscoder> MediaTranscoder::create(
+        const std::shared_ptr<CallbackInterface>& callbacks,
+        const std::shared_ptr<Parcel>& pausedState) {
+    if (pausedState != nullptr) {
+        LOG(ERROR) << "Initializing from paused state is currently not supported.";
+        return nullptr;
+    } else if (callbacks == nullptr) {
+        LOG(ERROR) << "Callbacks cannot be null";
+        return nullptr;
+    }
+
+    return std::shared_ptr<MediaTranscoder>(new MediaTranscoder(callbacks));
+}
+
+media_status_t MediaTranscoder::configureSource(const char* path) {
+    if (path == nullptr) {
+        LOG(ERROR) << "Source path cannot be null";
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    const int fd = open(path, O_RDONLY);
+    if (fd <= 0) {
+        LOG(ERROR) << "Unable to open source path: " << path;
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    const size_t fileSize = lseek(fd, 0, SEEK_END);
+    lseek(fd, 0, SEEK_SET);
+
+    mSampleReader = MediaSampleReaderNDK::createFromFd(fd, 0 /* offset */, fileSize);
+    close(fd);
+
+    if (mSampleReader == nullptr) {
+        LOG(ERROR) << "Unable to parse source file: " << path;
+        return AMEDIA_ERROR_UNSUPPORTED;
+    }
+
+    const size_t trackCount = mSampleReader->getTrackCount();
+    for (size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
+        AMediaFormat* trackFormat = mSampleReader->getTrackFormat(static_cast<int>(trackIndex));
+        if (trackFormat == nullptr) {
+            LOG(ERROR) << "Track #" << trackIndex << " has no format";
+            return AMEDIA_ERROR_MALFORMED;
+        }
+
+        mSourceTrackFormats.emplace_back(trackFormat, &AMediaFormat_delete);
+    }
+
+    return AMEDIA_OK;
+}
+
+std::vector<std::shared_ptr<AMediaFormat>> MediaTranscoder::getTrackFormats() const {
+    // Return a deep copy of the formats to avoid the caller modifying our internal formats.
+    std::vector<std::shared_ptr<AMediaFormat>> trackFormats;
+    for (const std::shared_ptr<AMediaFormat>& sourceFormat : mSourceTrackFormats) {
+        AMediaFormat* copy = AMediaFormat_new();
+        AMediaFormat_copy(copy, sourceFormat.get());
+        trackFormats.emplace_back(copy, &AMediaFormat_delete);
+    }
+    return trackFormats;
+}
+
+media_status_t MediaTranscoder::configureTrackFormat(size_t trackIndex, AMediaFormat* trackFormat) {
+    if (mSampleReader == nullptr) {
+        LOG(ERROR) << "Source must be configured before tracks";
+        return AMEDIA_ERROR_INVALID_OPERATION;
+    } else if (trackIndex >= mSourceTrackFormats.size()) {
+        LOG(ERROR) << "Track index " << trackIndex
+                   << " is out of bounds. Track count: " << mSourceTrackFormats.size();
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    std::unique_ptr<MediaTrackTranscoder> transcoder = nullptr;
+    std::shared_ptr<AMediaFormat> format = nullptr;
+
+    if (trackFormat == nullptr) {
+        transcoder = std::make_unique<PassthroughTrackTranscoder>(shared_from_this());
+    } else {
+        const char* srcMime = nullptr;
+        if (!AMediaFormat_getString(mSourceTrackFormats[trackIndex].get(), AMEDIAFORMAT_KEY_MIME,
+                                    &srcMime)) {
+            LOG(ERROR) << "Source track #" << trackIndex << " has no mime type";
+            return AMEDIA_ERROR_MALFORMED;
+        }
+
+        if (strncmp(srcMime, "video/", 6) != 0) {
+            LOG(ERROR) << "Only video tracks are supported for transcoding. Unable to configure "
+                          "track #"
+                       << trackIndex << " with mime " << srcMime;
+            return AMEDIA_ERROR_UNSUPPORTED;
+        }
+
+        const char* dstMime = nullptr;
+        if (AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &dstMime)) {
+            if (strncmp(dstMime, "video/", 6) != 0) {
+                LOG(ERROR) << "Unable to convert media types for track #" << trackIndex << ", from "
+                           << srcMime << " to " << dstMime;
+                return AMEDIA_ERROR_UNSUPPORTED;
+            }
+        }
+
+        transcoder = std::make_unique<VideoTrackTranscoder>(shared_from_this());
+
+        AMediaFormat* mergedFormat =
+                mergeMediaFormats(mSourceTrackFormats[trackIndex].get(), trackFormat);
+        if (mergedFormat == nullptr) {
+            LOG(ERROR) << "Unable to merge source and destination formats";
+            return AMEDIA_ERROR_UNKNOWN;
+        }
+
+        format = std::shared_ptr<AMediaFormat>(mergedFormat, &AMediaFormat_delete);
+    }
+
+    media_status_t status = transcoder->configure(mSampleReader, trackIndex, format);
+    if (status != AMEDIA_OK) {
+        LOG(ERROR) << "Configure track transcoder for track #" << trackIndex << " returned error "
+                   << status;
+        return status;
+    }
+
+    mTrackTranscoders.emplace_back(std::move(transcoder));
+    return AMEDIA_OK;
+}
+
+media_status_t MediaTranscoder::configureDestination(const char* path) {
+    if (path == nullptr || strlen(path) < 1) {
+        LOG(ERROR) << "Invalid destination path: " << path;
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    } else if (mSampleWriter != nullptr) {
+        LOG(ERROR) << "Destination is already configured.";
+        return AMEDIA_ERROR_INVALID_OPERATION;
+    }
+
+    // Write-only, create file if non-existent, don't overwrite existing file.
+    static constexpr int kOpenFlags = O_WRONLY | O_CREAT | O_EXCL;
+    // User R+W permission.
+    static constexpr int kFileMode = S_IRUSR | S_IWUSR;
+
+    const int fd = open(path, kOpenFlags, kFileMode);
+    if (fd < 0) {
+        LOG(ERROR) << "Unable to open destination file \"" << path << "\" for writing: " << fd;
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    mDestinationPath = std::string(path);
+
+    mSampleWriter = std::make_unique<MediaSampleWriter>();
+    const bool initOk = mSampleWriter->init(
+            fd, std::bind(&MediaTranscoder::onSampleWriterFinished, this, std::placeholders::_1));
+    close(fd);
+
+    if (!initOk) {
+        LOG(ERROR) << "Unable to initialize sample writer with destination path " << path;
+        mSampleWriter.reset();
+        return AMEDIA_ERROR_UNKNOWN;
+    }
+
+    return AMEDIA_OK;
+}
+
+media_status_t MediaTranscoder::start() {
+    if (mTrackTranscoders.size() < 1) {
+        LOG(ERROR) << "Unable to start, no tracks are configured.";
+        return AMEDIA_ERROR_INVALID_OPERATION;
+    } else if (mSampleWriter == nullptr) {
+        LOG(ERROR) << "Unable to start, destination is not configured";
+        return AMEDIA_ERROR_INVALID_OPERATION;
+    }
+
+    // Add tracks to the writer.
+    for (auto& transcoder : mTrackTranscoders) {
+        const bool ok = mSampleWriter->addTrack(transcoder->getOutputQueue(),
+                                                transcoder->getOutputFormat());
+        if (!ok) {
+            LOG(ERROR) << "Unable to add track to sample writer.";
+            return AMEDIA_ERROR_UNKNOWN;
+        }
+    }
+
+    bool started = mSampleWriter->start();
+    if (!started) {
+        LOG(ERROR) << "Unable to start sample writer.";
+        return AMEDIA_ERROR_UNKNOWN;
+    }
+
+    // Start transcoders
+    for (auto& transcoder : mTrackTranscoders) {
+        started = transcoder->start();
+        if (!started) {
+            LOG(ERROR) << "Unable to start track transcoder.";
+            cancel(true);
+            return AMEDIA_ERROR_UNKNOWN;
+        }
+    }
+    return AMEDIA_OK;
+}
+
+media_status_t MediaTranscoder::pause(std::shared_ptr<const Parcelable>* pausedState) {
+    (void)pausedState;
+    LOG(ERROR) << "Pause is not currently supported";
+    return AMEDIA_ERROR_UNSUPPORTED;
+}
+
+media_status_t MediaTranscoder::resume() {
+    LOG(ERROR) << "Resume is not currently supported";
+    return AMEDIA_ERROR_UNSUPPORTED;
+}
+
+media_status_t MediaTranscoder::cancel(bool deleteDestinationFile) {
+    bool expected = false;
+    if (!mCancelled.compare_exchange_strong(expected, true)) {
+        // Already cancelled.
+        return AMEDIA_OK;
+    }
+
+    mSampleWriter->stop();
+    for (auto& transcoder : mTrackTranscoders) {
+        transcoder->stop();
+    }
+
+    // TODO(chz): file deletion should be done by upper level from the content URI.
+    if (deleteDestinationFile && !mDestinationPath.empty()) {
+        int error = unlink(mDestinationPath.c_str());
+        if (error) {
+            LOG(ERROR) << "Unable to delete destination file " << mDestinationPath.c_str() << ": "
+                       << error;
+            return AMEDIA_ERROR_IO;
+        }
+    }
+    return AMEDIA_OK;
+}
+
+}  // namespace android
diff --git a/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
index 4404bbb..7806208 100644
--- a/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
@@ -134,7 +134,7 @@
         }
 
         sample->info = info;
-        if (mOutputQueue.enqueue(sample)) {
+        if (mOutputQueue->enqueue(sample)) {
             LOG(ERROR) << "Output queue aborted";
             return AMEDIA_ERROR_IO;
         }
@@ -153,4 +153,7 @@
     mBufferPool->abort();
 }
 
+std::shared_ptr<AMediaFormat> PassthroughTrackTranscoder::getOutputFormat() const {
+    return mSourceFormat;
+}
 }  // namespace android
diff --git a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
index 311e9be..3818545 100644
--- a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
@@ -30,6 +30,11 @@
 static_assert(SAMPLE_FLAG_PARTIAL_FRAME == AMEDIACODEC_BUFFER_FLAG_PARTIAL_FRAME,
               "Sample flag mismatch: PARTIAL_FRAME");
 
+// Color format defined by surface. (See MediaCodecInfo.CodecCapabilities#COLOR_FormatSurface.)
+static constexpr int32_t kColorFormatSurface = 0x7f000789;
+// Default key frame interval in seconds.
+static constexpr float kDefaultKeyFrameIntervalSeconds = 1.0f;
+
 template <typename T>
 void VideoTrackTranscoder::BlockingQueue<T>::push(T const& value, bool front) {
     {
@@ -113,11 +118,24 @@
     media_status_t status = AMEDIA_OK;
 
     if (destinationFormat == nullptr) {
-        LOG(ERROR) << "Destination format is null";
+        LOG(ERROR) << "Destination format is null, use passthrough transcoder";
         return AMEDIA_ERROR_INVALID_PARAMETER;
     }
 
-    mDestinationFormat = destinationFormat;
+    AMediaFormat* encoderFormat = AMediaFormat_new();
+    if (!encoderFormat || AMediaFormat_copy(encoderFormat, destinationFormat.get()) != AMEDIA_OK) {
+        LOG(ERROR) << "Unable to copy destination format";
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    float tmp;
+    if (!AMediaFormat_getFloat(encoderFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, &tmp)) {
+        AMediaFormat_setFloat(encoderFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL,
+                              kDefaultKeyFrameIntervalSeconds);
+    }
+    AMediaFormat_setInt32(encoderFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, kColorFormatSurface);
+
+    mDestinationFormat = std::shared_ptr<AMediaFormat>(encoderFormat, &AMediaFormat_delete);
 
     // Create and configure the encoder.
     const char* destinationMime = nullptr;
@@ -276,7 +294,7 @@
         sample->info.flags = bufferInfo.flags;
         sample->info.presentationTimeUs = bufferInfo.presentationTimeUs;
 
-        const bool aborted = mOutputQueue.enqueue(sample);
+        const bool aborted = mOutputQueue->enqueue(sample);
         if (aborted) {
             LOG(ERROR) << "Output sample queue was aborted. Stopping transcode.";
             mStatus = AMEDIA_ERROR_IO;  // TODO: Define custom error codes?
@@ -321,7 +339,8 @@
     }
 
     AMediaCodec_stop(mDecoder);
-    AMediaCodec_stop(mEncoder.get());
+    // TODO: Stop invalidates all buffers. Stop encoder when last buffer is released.
+    //    AMediaCodec_stop(mEncoder.get());
     return mStatus;
 }
 
@@ -330,4 +349,8 @@
     mCodecMessageQueue.push([this] { mStopRequested = true; }, true /* front */);
 }
 
+std::shared_ptr<AMediaFormat> VideoTrackTranscoder::getOutputFormat() const {
+    return mDestinationFormat;
+}
+
 }  // namespace android
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
index 235766c..a71db67 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
@@ -40,14 +40,14 @@
      * successfully.
      * @param transcoder The MediaTrackTranscoder that finished the transcoding.
      */
-    virtual void onTrackFinished(MediaTrackTranscoder* transcoder);
+    virtual void onTrackFinished(const MediaTrackTranscoder* transcoder);
 
     /**
      * Called when the MediaTrackTranscoder instance encountered an error it could not recover from.
      * @param transcoder The MediaTrackTranscoder that encountered the error.
      * @param status The non-zero error code describing the encountered error.
      */
-    virtual void onTrackError(MediaTrackTranscoder* transcoder, media_status_t status);
+    virtual void onTrackError(const MediaTrackTranscoder* transcoder, media_status_t status);
 
 protected:
     virtual ~MediaTrackTranscoderCallback() = default;
@@ -96,15 +96,24 @@
     bool stop();
 
     /**
-     * Sample output queue.
-     * TODO(b/155918341) Move to protected.
+     * Retrieves the track transcoder's output sample queue.
+     * @return The output sample queue.
      */
-    MediaSampleQueue mOutputQueue = {};
+    std::shared_ptr<MediaSampleQueue> getOutputQueue() const;
+
+    /**
+      * Retrieves the track transcoder's final output format. The output is available after the
+      * track transcoder has been successfully configured.
+      * @return The track output format.
+      */
+    virtual std::shared_ptr<AMediaFormat> getOutputFormat() const = 0;
+
+    virtual ~MediaTrackTranscoder() = default;
 
 protected:
     MediaTrackTranscoder(const std::weak_ptr<MediaTrackTranscoderCallback>& transcoderCallback)
-          : mTranscoderCallback(transcoderCallback){};
-    virtual ~MediaTrackTranscoder() = default;
+          : mOutputQueue(std::make_shared<MediaSampleQueue>()),
+            mTranscoderCallback(transcoderCallback){};
 
     // configureDestinationFormat needs to be implemented by subclasses, and gets called on an
     // external thread before start.
@@ -119,6 +128,7 @@
     // be aborted as soon as possible. It should be safe to call abortTranscodeLoop multiple times.
     virtual void abortTranscodeLoop() = 0;
 
+    std::shared_ptr<MediaSampleQueue> mOutputQueue;
     std::shared_ptr<MediaSampleReader> mMediaSampleReader;
     int mTrackIndex;
     std::shared_ptr<AMediaFormat> mSourceFormat;
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
new file mode 100644
index 0000000..2d18eea
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_TRANSCODER_H
+#define ANDROID_MEDIA_TRANSCODER_H
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <media/MediaSampleReader.h>
+#include <media/MediaSampleWriter.h>
+#include <media/MediaTrackTranscoder.h>
+#include <media/NdkMediaError.h>
+#include <media/NdkMediaFormat.h>
+
+#include <atomic>
+#include <memory>
+#include <unordered_set>
+
+namespace android {
+
+class MediaTranscoder : public std::enable_shared_from_this<MediaTranscoder>,
+                        public MediaTrackTranscoderCallback {
+public:
+    /** Callbacks from transcoder to client. */
+    class CallbackInterface {
+    public:
+        /** Transcoder finished successfully. */
+        virtual void onFinished(const MediaTranscoder* transcoder) = 0;
+
+        /** Transcoder encountered an unrecoverable error. */
+        virtual void onError(const MediaTranscoder* transcoder, media_status_t error) = 0;
+
+        /** Transcoder progress update reported in percent from 0 to 100. */
+        virtual void onProgressUpdate(const MediaTranscoder* transcoder, int32_t progress) = 0;
+
+        /**
+         * Transcoder lost codec resources and paused operations. The client can resume transcoding
+         * again when resources are available by either:
+         *   1) Calling resume on the same MediaTranscoder instance.
+         *   2) Creating a new MediaTranscoding instance with the paused state and then calling
+         *      resume.
+         */
+        virtual void onCodecResourceLost(const MediaTranscoder* transcoder,
+                                         const std::shared_ptr<const Parcelable>& pausedState) = 0;
+
+        virtual ~CallbackInterface() = default;
+    };
+
+    /**
+     * Creates a new MediaTranscoder instance. If the supplied paused state is valid, the transcoder
+     * will be initialized with the paused state and be ready to be resumed right away. It is not
+     * possible to change any configurations on a paused transcoder.
+     */
+    static std::shared_ptr<MediaTranscoder> create(
+            const std::shared_ptr<CallbackInterface>& callbacks,
+            const std::shared_ptr<Parcel>& pausedState = nullptr);
+
+    /** Configures source from path. */
+    media_status_t configureSource(const char* path);
+
+    /** Gets the media formats of all tracks in the file. */
+    std::vector<std::shared_ptr<AMediaFormat>> getTrackFormats() const;
+
+    /**
+     * Configures transcoding of a track. Tracks that are not configured will not present in the
+     * final transcoded file, i.e. tracks will be dropped by default. Passing nullptr for
+     * trackFormat means the track will be copied unchanged ("passthrough") to the destination.
+     * Track configurations must be done after the source has been configured.
+     * Note: trackFormat is not modified but cannot be const.
+     */
+    media_status_t configureTrackFormat(size_t trackIndex, AMediaFormat* trackFormat);
+
+    /** Configures destination from path. */
+    media_status_t configureDestination(const char* path);
+
+    /** Starts transcoding. No configurations can be made once the transcoder has started. */
+    media_status_t start();
+
+    /**
+     * Pauses transcoding. The transcoder's paused state is returned through pausedState. The
+     * paused state is only needed for resuming transcoding with a new MediaTranscoder instance. The
+     * caller can resume transcoding with the current MediaTranscoder instance at any time by
+     * calling resume(). It is not required to cancel a paused transcoder. The paused state is
+     * independent and the caller can always initialize a new transcoder instance with the same
+     * paused state. If the caller wishes to abandon a paused transcoder's operation they can
+     * release the transcoder instance, clear the paused state and delete the partial destination
+     * file. The caller can optionally call cancel to let the transcoder clean up the partial
+     * destination file.
+     */
+    media_status_t pause(std::shared_ptr<const Parcelable>* pausedState);
+
+    /** Resumes a paused transcoding. */
+    media_status_t resume();
+
+    /** Cancels the transcoding. Once canceled the transcoding can not be restarted. returns error
+     * if file could not be deleted. */
+    media_status_t cancel(bool deleteDestinationFile = true);
+
+    virtual ~MediaTranscoder() = default;
+
+private:
+    MediaTranscoder(const std::shared_ptr<CallbackInterface>& callbacks)
+          : mCallbacks(callbacks),
+            mSampleReader(nullptr),
+            mSampleWriter(nullptr),
+            mSourceTrackFormats(),
+            mTrackTranscoders() {}
+
+    // MediaTrackTranscoderCallback
+    virtual void onTrackFinished(const MediaTrackTranscoder* transcoder) override;
+    virtual void onTrackError(const MediaTrackTranscoder* transcoder,
+                              media_status_t status) override;
+    // ~MediaTrackTranscoderCallback
+    void onSampleWriterFinished(media_status_t status);
+    void sendCallback(media_status_t status);
+
+    std::shared_ptr<CallbackInterface> mCallbacks;
+    std::shared_ptr<MediaSampleReader> mSampleReader;
+    std::unique_ptr<MediaSampleWriter> mSampleWriter;
+    std::vector<std::shared_ptr<AMediaFormat>> mSourceTrackFormats;
+    std::vector<std::unique_ptr<MediaTrackTranscoder>> mTrackTranscoders;
+
+    std::string mDestinationPath;
+    std::atomic_bool mCallbackSent = false;
+    std::atomic_bool mCancelled = false;
+};
+
+}  // namespace android
+#endif  // ANDROID_MEDIA_TRANSCODER_H
diff --git a/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
index 42feb85..b9491ed 100644
--- a/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
@@ -90,6 +90,7 @@
     void abortTranscodeLoop() override;
     media_status_t configureDestinationFormat(
             const std::shared_ptr<AMediaFormat>& destinationFormat) override;
+    std::shared_ptr<AMediaFormat> getOutputFormat() const override;
     // ~MediaTrackTranscoder
 
     std::shared_ptr<BufferPool> mBufferPool;
diff --git a/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
index 7d93d60..c47e4b7 100644
--- a/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
@@ -61,6 +61,7 @@
     void abortTranscodeLoop() override;
     media_status_t configureDestinationFormat(
             const std::shared_ptr<AMediaFormat>& destinationFormat) override;
+    std::shared_ptr<AMediaFormat> getOutputFormat() const override;
     // ~MediaTrackTranscoder
 
     // Enqueues an input sample with the decoder.
diff --git a/media/libmediatranscoding/transcoder/tests/Android.bp b/media/libmediatranscoding/transcoder/tests/Android.bp
index 926110885..4160c30 100644
--- a/media/libmediatranscoding/transcoder/tests/Android.bp
+++ b/media/libmediatranscoding/transcoder/tests/Android.bp
@@ -74,3 +74,10 @@
     defaults: ["testdefaults"],
     srcs: ["MediaSampleWriterTests.cpp"],
 }
+
+// MediaTranscoder unit test
+cc_test {
+    name: "MediaTranscoderTests",
+    defaults: ["testdefaults"],
+    srcs: ["MediaTranscoderTests.cpp"],
+}
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
index c5b181d..4d9386a 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
@@ -60,6 +60,7 @@
             break;
         }
         ASSERT_NE(mTranscoder, nullptr);
+        mTranscoderOutputQueue = mTranscoder->getOutputQueue();
 
         initSampleReader();
     }
@@ -120,7 +121,7 @@
             std::shared_ptr<MediaSample> sample;
             bool aborted = false;
             do {
-                aborted = mTranscoder->mOutputQueue.dequeue(&sample);
+                aborted = mTranscoderOutputQueue->dequeue(&sample);
             } while (!aborted && !(sample->info.flags & SAMPLE_FLAG_END_OF_STREAM));
             mQueueWasAborted = aborted;
             mGotEndOfStream =
@@ -142,6 +143,7 @@
 
 protected:
     std::shared_ptr<MediaTrackTranscoder> mTranscoder;
+    std::shared_ptr<MediaSampleQueue> mTranscoderOutputQueue;
     std::shared_ptr<TestCallback> mCallback;
 
     std::shared_ptr<MediaSampleReader> mMediaSampleReader;
@@ -242,7 +244,7 @@
     EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
               AMEDIA_OK);
     ASSERT_TRUE(mTranscoder->start());
-    mTranscoder->mOutputQueue.abort();
+    mTranscoderOutputQueue->abort();
     drainOutputSampleQueue();
     EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_ERROR_IO);
     EXPECT_TRUE(mTranscoder->stop());
@@ -259,7 +261,7 @@
     ASSERT_TRUE(mTranscoder->start());
 
     std::shared_ptr<MediaSample> sample;
-    EXPECT_FALSE(mTranscoder->mOutputQueue.dequeue(&sample));
+    EXPECT_FALSE(mTranscoderOutputQueue->dequeue(&sample));
 
     drainOutputSampleQueue();
     EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
@@ -269,6 +271,7 @@
     EXPECT_TRUE(mGotEndOfStream);
 
     mTranscoder.reset();
+    mTranscoderOutputQueue.reset();
     std::this_thread::sleep_for(std::chrono::milliseconds(20));
     sample.reset();
 }
@@ -280,7 +283,7 @@
     ASSERT_TRUE(mTranscoder->start());
 
     std::shared_ptr<MediaSample> sample;
-    EXPECT_FALSE(mTranscoder->mOutputQueue.dequeue(&sample));
+    EXPECT_FALSE(mTranscoderOutputQueue->dequeue(&sample));
     EXPECT_TRUE(mTranscoder->stop());
 
     std::this_thread::sleep_for(std::chrono::milliseconds(20));
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
new file mode 100644
index 0000000..c4a67bb
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+// Unit Test for MediaTranscoder
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaTranscoderTests"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <media/MediaTranscoder.h>
+
+namespace android {
+
+class TestCallbacks : public MediaTranscoder::CallbackInterface {
+public:
+    virtual void onFinished(const MediaTranscoder* transcoder __unused) override {
+        std::unique_lock<std::mutex> lock(mMutex);
+        EXPECT_FALSE(mFinished);
+        mFinished = true;
+        mCondition.notify_all();
+    }
+
+    virtual void onError(const MediaTranscoder* transcoder __unused,
+                         media_status_t error) override {
+        std::unique_lock<std::mutex> lock(mMutex);
+        EXPECT_NE(error, AMEDIA_OK);
+        EXPECT_FALSE(mFinished);
+        mFinished = true;
+        mStatus = error;
+        mCondition.notify_all();
+    }
+
+    virtual void onProgressUpdate(const MediaTranscoder* transcoder __unused,
+                                  int32_t progress __unused) override {}
+
+    virtual void onCodecResourceLost(const MediaTranscoder* transcoder __unused,
+                                     const std::shared_ptr<const Parcelable>& pausedState
+                                             __unused) override {}
+
+    void waitForTranscodingFinished() {
+        std::unique_lock<std::mutex> lock(mMutex);
+        while (!mFinished) {
+            mCondition.wait(lock);
+        }
+    }
+
+    media_status_t mStatus = AMEDIA_OK;
+
+private:
+    std::mutex mMutex;
+    std::condition_variable mCondition;
+    bool mFinished = false;
+};
+
+static const char* SOURCE_PATH =
+        "/data/local/tmp/TranscoderTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+
+class MediaTranscoderTests : public ::testing::Test {
+public:
+    MediaTranscoderTests() { LOG(DEBUG) << "MediaTranscoderTests created"; }
+    ~MediaTranscoderTests() { LOG(DEBUG) << "MediaTranscoderTests destroyed"; }
+
+    void SetUp() override {
+        LOG(DEBUG) << "MediaTranscoderTests set up";
+        mCallbacks = std::make_shared<TestCallbacks>();
+    }
+
+    void TearDown() override {
+        LOG(DEBUG) << "MediaTranscoderTests tear down";
+        mCallbacks.reset();
+    }
+
+    void deleteFile(const char* path) { unlink(path); }
+
+    using FormatConfigurationCallback = std::function<AMediaFormat*(AMediaFormat*)>;
+    media_status_t transcodeHelper(const char* destPath,
+                                   FormatConfigurationCallback formatCallback) {
+        auto transcoder = MediaTranscoder::create(mCallbacks, nullptr);
+        EXPECT_NE(transcoder, nullptr);
+
+        EXPECT_EQ(transcoder->configureSource(SOURCE_PATH), AMEDIA_OK);
+
+        std::vector<std::shared_ptr<AMediaFormat>> trackFormats = transcoder->getTrackFormats();
+        EXPECT_GT(trackFormats.size(), 0);
+
+        for (int i = 0; i < trackFormats.size(); ++i) {
+            AMediaFormat* format = formatCallback(trackFormats[i].get());
+            EXPECT_EQ(transcoder->configureTrackFormat(i, format), AMEDIA_OK);
+            if (format != nullptr) {
+                AMediaFormat_delete(format);
+            }
+        }
+        deleteFile(destPath);
+        EXPECT_EQ(transcoder->configureDestination(destPath), AMEDIA_OK);
+
+        media_status_t startStatus = transcoder->start();
+        EXPECT_EQ(startStatus, AMEDIA_OK);
+        if (startStatus == AMEDIA_OK) {
+            mCallbacks->waitForTranscodingFinished();
+        }
+
+        return mCallbacks->mStatus;
+    }
+
+    std::shared_ptr<TestCallbacks> mCallbacks;
+};
+
+TEST_F(MediaTranscoderTests, TestPassthrough) {
+    const char* destPath = "/data/local/tmp/MediaTranscoder_Passthrough.MP4";
+
+    EXPECT_EQ(transcodeHelper(destPath, [](AMediaFormat*) { return nullptr; }), AMEDIA_OK);
+
+    // TODO: Validate output file
+}
+
+TEST_F(MediaTranscoderTests, TestBasicVideoTranscode) {
+    const char* destPath = "/data/local/tmp/MediaTranscoder_VideoTranscode.MP4";
+
+    EXPECT_EQ(transcodeHelper(
+                      destPath,
+                      [](AMediaFormat* sourceFormat) {
+                          AMediaFormat* format = nullptr;
+                          const char* mime = nullptr;
+                          AMediaFormat_getString(sourceFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+
+                          if (strncmp(mime, "video/", 6) == 0) {
+                              const int32_t kBitRate = 8 * 1000 * 1000;  // 8Mbs
+                              format = AMediaFormat_new();
+                              AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, kBitRate);
+                          }
+                          return format;
+                      }),
+              AMEDIA_OK);
+
+    // TODO: Validate output file
+}
+
+}  // namespace android
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
index 7a92a37..316793a 100644
--- a/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
@@ -166,7 +166,8 @@
     // Pull transcoder's output samples and compare against input checksums.
     uint64_t sampleCount = 0;
     std::shared_ptr<MediaSample> sample;
-    while (!transcoder.mOutputQueue.dequeue(&sample)) {
+    std::shared_ptr<MediaSampleQueue> outputQueue = transcoder.getOutputQueue();
+    while (!outputQueue->dequeue(&sample)) {
         ASSERT_NE(sample, nullptr);
 
         if (sample->info.flags & SAMPLE_FLAG_END_OF_STREAM) {
diff --git a/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
index 6b9131c..79c227b 100644
--- a/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
+++ b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
@@ -56,13 +56,13 @@
     ~TestCallback() = default;
 
     // MediaTrackTranscoderCallback
-    void onTrackFinished(MediaTrackTranscoder* transcoder __unused) {
+    void onTrackFinished(const MediaTrackTranscoder* transcoder __unused) {
         std::unique_lock<std::mutex> lock(mMutex);
         mTranscodingFinished = true;
         mCv.notify_all();
     }
 
-    void onTrackError(MediaTrackTranscoder* transcoder __unused, media_status_t status) {
+    void onTrackError(const MediaTrackTranscoder* transcoder __unused, media_status_t status) {
         std::unique_lock<std::mutex> lock(mMutex);
         mTranscodingFinished = true;
         mStatus = status;
diff --git a/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
index 3cec1a1..6b1f640 100644
--- a/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
@@ -100,10 +100,11 @@
     EXPECT_EQ(transcoder.configure(mMediaSampleReader, mTrackIndex, mDestinationFormat), AMEDIA_OK);
     ASSERT_TRUE(transcoder.start());
 
-    std::thread sampleConsumerThread{[&transcoder] {
+    std::shared_ptr<MediaSampleQueue> outputQueue = transcoder.getOutputQueue();
+    std::thread sampleConsumerThread{[&outputQueue] {
         uint64_t sampleCount = 0;
         std::shared_ptr<MediaSample> sample;
-        while (!transcoder.mOutputQueue.dequeue(&sample)) {
+        while (!outputQueue->dequeue(&sample)) {
             ASSERT_NE(sample, nullptr);
             const uint32_t flags = sample->info.flags;
 
diff --git a/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh b/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
index 70e2111..01beeee 100755
--- a/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
+++ b/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
@@ -39,3 +39,6 @@
 
 echo "testing MediaSampleWriter"
 adb shell /data/nativetest64/MediaSampleWriterTests/MediaSampleWriterTests
+
+echo "testing MediaTranscoder"
+adb shell /data/nativetest64/MediaTranscoderTests/MediaTranscoderTests
diff --git a/media/libstagefright/ACodecBufferChannel.cpp b/media/libstagefright/ACodecBufferChannel.cpp
index fa13f32..88b15ae 100644
--- a/media/libstagefright/ACodecBufferChannel.cpp
+++ b/media/libstagefright/ACodecBufferChannel.cpp
@@ -22,11 +22,14 @@
 
 #include <C2Buffer.h>
 
+#include <Codec2BufferUtils.h>
+
 #include <android/hardware/cas/native/1.0/IDescrambler.h>
 #include <android/hardware/drm/1.0/types.h>
 #include <binder/MemoryDealer.h>
 #include <hidlmemory/FrameworkUtils.h>
 #include <media/openmax/OMX_Core.h>
+#include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/foundation/AUtils.h>
 #include <media/stagefright/MediaCodec.h>
@@ -91,15 +94,27 @@
 }
 
 status_t ACodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
-    if (mDealer != nullptr) {
-        return -ENOSYS;
-    }
     std::shared_ptr<const std::vector<const BufferInfo>> array(
             std::atomic_load(&mInputBuffers));
     BufferInfoIterator it = findClientBuffer(array, buffer);
     if (it == array->end()) {
         return -ENOENT;
     }
+    if (it->mClientBuffer != it->mCodecBuffer) {
+        // Copy metadata from client to codec buffer.
+        it->mCodecBuffer->meta()->clear();
+        int64_t timeUs;
+        CHECK(it->mClientBuffer->meta()->findInt64("timeUs", &timeUs));
+        it->mCodecBuffer->meta()->setInt64("timeUs", timeUs);
+        int32_t eos;
+        if (it->mClientBuffer->meta()->findInt32("eos", &eos)) {
+            it->mCodecBuffer->meta()->setInt32("eos", eos);
+        }
+        int32_t csd;
+        if (it->mClientBuffer->meta()->findInt32("csd", &csd)) {
+            it->mCodecBuffer->meta()->setInt32("csd", csd);
+        }
+    }
     ALOGV("queueInputBuffer #%d", it->mBufferId);
     sp<AMessage> msg = mInputBufferFilled->dup();
     msg->setObject("buffer", it->mCodecBuffer);
@@ -267,16 +282,30 @@
             }
             C2ConstLinearBlock block{c2Buffer->data().linearBlocks().front()};
             C2ReadView view{block.map().get()};
-            if (view.capacity() > buffer->capacity()) {
-                return -ENOSYS;
-            }
-            memcpy(buffer->base(), view.data(), view.capacity());
-            buffer->setRange(0, view.capacity());
+            size_t copyLength = std::min(size_t(view.capacity()), buffer->capacity());
+            ALOGV_IF(view.capacity() > buffer->capacity(),
+                    "view.capacity() = %zu, buffer->capacity() = %zu",
+                    view.capacity(), buffer->capacity());
+            memcpy(buffer->base(), view.data(), copyLength);
+            buffer->setRange(0, copyLength);
             break;
         }
         case C2BufferData::GRAPHIC: {
-            // TODO
-            return -ENOSYS;
+            sp<ABuffer> imageData;
+            if (!buffer->format()->findBuffer("image-data", &imageData)) {
+                return -ENOSYS;
+            }
+            if (c2Buffer->data().graphicBlocks().size() != 1u) {
+                return -ENOSYS;
+            }
+            C2ConstGraphicBlock block{c2Buffer->data().graphicBlocks().front()};
+            const C2GraphicView view{block.map().get()};
+            status_t err = ImageCopy(
+                    buffer->base(), (const MediaImage2 *)(imageData->base()), view);
+            if (err != OK) {
+                return err;
+            }
+            break;
         }
         case C2BufferData::LINEAR_CHUNKS:  [[fallthrough]];
         case C2BufferData::GRAPHIC_CHUNKS: [[fallthrough]];
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index 890a939..fac6350 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -285,6 +285,7 @@
         "libutils",
         "libmedia_helper",
         "libsfplugin_ccodec",
+        "libsfplugin_ccodec_utils",
         "libstagefright_codecbase",
         "libstagefright_foundation",
         "libstagefright_omx_utils",
diff --git a/media/libstagefright/HevcUtils.cpp b/media/libstagefright/HevcUtils.cpp
index 857380c..aac656a 100644
--- a/media/libstagefright/HevcUtils.cpp
+++ b/media/libstagefright/HevcUtils.cpp
@@ -30,6 +30,8 @@
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/Utils.h>
 
+#define UNUSED_PARAM __attribute__((unused))
+
 namespace android {
 
 static const uint8_t kHevcNalUnitTypes[8] = {
@@ -379,7 +381,7 @@
 }
 
 status_t HevcParameterSets::parsePps(
-        const uint8_t* data __unused, size_t size __unused) {
+        const uint8_t* data UNUSED_PARAM, size_t size UNUSED_PARAM) {
     return OK;
 }
 
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 4c4d228..39423c7 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -945,10 +945,30 @@
     mInMemoryCache = NULL;
     mInMemoryCacheOffset = 0;
 
+    status_t err = OK;
+    int32_t is4bitTrackId = false;
+    if (param && param->findInt32(kKey4BitTrackIds, &is4bitTrackId) && is4bitTrackId) {
+        err = validateAllTracksId(true);
+    } else {
+        err = validateAllTracksId(false);
+    }
+    if (err != OK) {
+        return err;
+    }
 
     ALOGV("muxer starting: mHasMoovBox %d, mHasFileLevelMeta %d",
             mHasMoovBox, mHasFileLevelMeta);
 
+    err = startWriterThread();
+    if (err != OK) {
+        return err;
+    }
+
+    err = setupAndStartLooper();
+    if (err != OK) {
+        return err;
+    }
+
     writeFtypBox(param);
 
     mFreeBoxOffset = mOffset;
@@ -980,22 +1000,22 @@
     seekOrPostError(mFd, mMdatOffset, SEEK_SET);
     write("\x00\x00\x00\x01mdat????????", 16);
 
-    status_t err = startWriterThread();
-    if (err != OK) {
-        return err;
-    }
-
-    setupAndStartLooper();
-
-    int32_t is4bitTrackId = false;
-    if (param && param->findInt32(kKey4BitTrackIds, &is4bitTrackId) && is4bitTrackId) {
-        err = validateAllTracksId(true);
-    }
-    else {
-        err = validateAllTracksId(false);
-    }
-    if (err != OK) {
-        return err;
+    /* Confirm whether the writing of the initial file atoms, ftyp and free,
+     * are written to the file properly by posting kWhatNoIOErrorSoFar to the
+     * MP4WtrCtrlHlpLooper that's handling write and seek errors also. If there
+     * was kWhatIOError, the following two scenarios should be handled.
+     * 1) If kWhatIOError was delivered and processed, MP4WtrCtrlHlpLooper
+     * would have stopped all threads gracefully already and posting
+     * kWhatNoIOErrorSoFar would fail.
+     * 2) If kWhatIOError wasn't delivered or getting processed,
+     * kWhatNoIOErrorSoFar should get posted successfully.  Wait for
+     * response from MP4WtrCtrlHlpLooper.
+     */
+    sp<AMessage> msg = new AMessage(kWhatNoIOErrorSoFar, mReflector);
+    sp<AMessage> response;
+    err = msg->postAndAwaitResponse(&response);
+    if (err != OK || !response->findInt32("err", &err) || err != OK) {
+        return ERROR_IO;
     }
 
     err = startTracks(param);
@@ -1025,13 +1045,16 @@
     }
 
     void *dummy;
-    status_t err = pthread_join(mThread, &dummy);
-    WARN_UNLESS(err == 0, "stopWriterThread pthread_join err: %d", err);
-
-    err = static_cast<status_t>(reinterpret_cast<uintptr_t>(dummy));
+    status_t err = OK;
+    int retVal = pthread_join(mThread, &dummy);
+    if (retVal == 0) {
+        err = static_cast<status_t>(reinterpret_cast<uintptr_t>(dummy));
+        ALOGD("WriterThread stopped. Status:%d", err);
+    } else {
+        ALOGE("stopWriterThread pthread_join status:%d", retVal);
+        err = UNKNOWN_ERROR;
+    }
     mWriterThreadStarted = false;
-    WARN_UNLESS(err == 0, "stopWriterThread pthread_join retVal: %d", err);
-    ALOGD("Writer thread stopped");
     return err;
 }
 
@@ -1089,23 +1112,26 @@
 
 status_t MPEG4Writer::release() {
     ALOGD("release()");
-    if (mPreAllocationEnabled) {
-        truncatePreAllocation();
+    status_t err = OK;
+    if (!truncatePreAllocation()) {
+        if (err == OK) { err = ERROR_IO; }
     }
-    int err = OK;
-    int retVal = fsync(mFd);
-    WARN_UNLESS(retVal == 0, "fsync err:%s(%d)", std::strerror(errno), errno);
-    err |= retVal;
-    retVal = close(mFd);
-    WARN_UNLESS(retVal == 0, "close err:%s(%d)", std::strerror(errno), errno);
-    err |= retVal;
+    if (fsync(mFd) != 0) {
+        ALOGW("(ignored)fsync err:%s(%d)", std::strerror(errno), errno);
+        // Don't bubble up fsync error, b/157291505.
+        // if (err == OK) { err = ERROR_IO; }
+    }
+    if (close(mFd) != 0) {
+        ALOGE("close err:%s(%d)", std::strerror(errno), errno);
+        if (err == OK) { err = ERROR_IO; }
+    }
     mFd = -1;
     if (mNextFd != -1) {
-        retVal = close(mNextFd);
+        if (close(mNextFd) != 0) {
+            ALOGE("close(mNextFd) error:%s(%d)", std::strerror(errno), errno);
+        }
+        if (err == OK) { err = ERROR_IO; }
         mNextFd = -1;
-        WARN_UNLESS(retVal == 0, "close mNextFd error:%s(%d)",
-                    std::strerror(errno), errno);
-        err |= retVal;
     }
     stopAndReleaseLooper();
     mInitCheck = NO_INIT;
@@ -1165,7 +1191,7 @@
     for (List<Track *>::iterator it = mTracks.begin();
         it != mTracks.end(); ++it) {
         status_t trackErr = (*it)->stop(stopSource);
-        WARN_UNLESS(trackErr == 0, "%s track stopped with an error",
+        WARN_UNLESS(trackErr == OK, "%s track stopped with an error",
                     (*it)->getTrackType());
         if (err == OK && trackErr != OK) {
             err = trackErr;
@@ -1254,7 +1280,11 @@
 
     CHECK(mBoxes.empty());
 
-    err = release();
+    status_t errRelease = release();
+    // Prioritize the error that occurred before release().
+    if (err == OK) {
+        err = errRelease;
+    }
     return err;
 }
 
@@ -1577,9 +1607,8 @@
 
     // Can't guarantee that file is usable or write would succeed anymore, hence signal to stop.
     sp<AMessage> msg = new AMessage(kWhatIOError, mReflector);
-    msg->setInt32("errno", errno);
-    status_t err = msg->post();
-    ALOGE("writeOrPostError post:%d", err);
+    msg->setInt32("err", ERROR_IO);
+    WARN_UNLESS(msg->post() == OK, "writeOrPostError:error posting ERROR_IO");
 }
 
 void MPEG4Writer::seekOrPostError(int fd, off64_t offset, int whence) {
@@ -1597,9 +1626,8 @@
 
     // Can't guarantee that file is usable or seek would succeed anymore, hence signal to stop.
     sp<AMessage> msg = new AMessage(kWhatIOError, mReflector);
-    msg->setInt32("errno", errno);
-    status_t err = msg->post();
-    ALOGE("seekOrPostError post:%d", err);
+    msg->setInt32("err", ERROR_IO);
+    WARN_UNLESS(msg->post() == OK, "seekOrPostError:error posting ERROR_IO");
 }
 
 void MPEG4Writer::beginBox(uint32_t id) {
@@ -1838,7 +1866,7 @@
     if (res == -1) {
         ALOGE("fallocate err:%s, %d, fd:%d", strerror(errno), errno, mFd);
         sp<AMessage> msg = new AMessage(kWhatFallocateError, mReflector);
-        msg->setInt32("errno", errno);
+        msg->setInt32("err", ERROR_IO);
         status_t err = msg->post();
         mFallocateErr = true;
         ALOGD("preAllocation post:%d", err);
@@ -1850,6 +1878,9 @@
 }
 
 bool MPEG4Writer::truncatePreAllocation() {
+    if (!mPreAllocationEnabled)
+        return true;
+
     bool status = true;
     off64_t endOffset = std::max(mMdatEndOffset, mOffset);
     /* if mPreAllocateFileEndOffset >= endOffset, then preallocation logic works good. (diff >= 0).
@@ -1861,6 +1892,10 @@
     if(ftruncate(mFd, endOffset) == -1) {
         ALOGE("ftruncate err:%s, %d, fd:%d", strerror(errno), errno, mFd);
         status = false;
+        /* No need to post and handle(stop & notify client) error like it's done in preAllocate(),
+         * because ftruncate() is called during release() only and the error here would be
+         * reported from there as this function is returning false on any error in ftruncate().
+         */
     }
     return status;
 }
@@ -2153,14 +2188,17 @@
     mElstTableEntries->add(htonl((((uint32_t)mediaRate) << 16) | (uint32_t)mediaRateFraction));
 }
 
-void MPEG4Writer::setupAndStartLooper() {
+status_t MPEG4Writer::setupAndStartLooper() {
+    status_t err = OK;
     if (mLooper == nullptr) {
         mLooper = new ALooper;
         mLooper->setName("MP4WtrCtrlHlpLooper");
-        mLooper->start();
+        err = mLooper->start();
         mReflector = new AHandlerReflector<MPEG4Writer>(this);
         mLooper->registerHandler(mReflector);
     }
+    ALOGD("MP4WtrCtrlHlpLooper Started");
+    return err;
 }
 
 void MPEG4Writer::stopAndReleaseLooper() {
@@ -2399,23 +2437,35 @@
         case kWhatIOError: {
             ALOGE("kWhatIOError");
             int32_t err;
-            CHECK(msg->findInt32("errno", &err));
+            CHECK(msg->findInt32("err", &err));
             // Stop tracks' threads and main writer thread.
             stop();
             notify(MEDIA_RECORDER_EVENT_ERROR, MEDIA_RECORDER_ERROR_UNKNOWN, err);
             break;
         }
-        // fallocate() failed, hence notify app about it and stop().
+        // fallocate() failed, hence stop() and notify app.
         case kWhatFallocateError: {
             ALOGE("kWhatFallocateError");
             int32_t err;
-            CHECK(msg->findInt32("errno", &err));
+            CHECK(msg->findInt32("err", &err));
             // Stop tracks' threads and main writer thread.
             stop();
             //TODO: introduce a suitable MEDIA_RECORDER_ERROR_* instead MEDIA_RECORDER_ERROR_UNKNOWN?
             notify(MEDIA_RECORDER_EVENT_ERROR, MEDIA_RECORDER_ERROR_UNKNOWN, err);
             break;
         }
+        /* Response to kWhatNoIOErrorSoFar would be OK always as of now.
+         * Responding with other options could be added later if required.
+         */
+        case kWhatNoIOErrorSoFar: {
+            ALOGD("kWhatNoIOErrorSoFar");
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", OK);
+            sp<AReplyToken> replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+            response->postReply(replyID);
+            break;
+        }
         default:
         TRESPASS();
     }
@@ -2778,11 +2828,16 @@
     mDone = true;
 
     void *dummy;
-    status_t err = pthread_join(mThread, &dummy);
-    WARN_UNLESS(err == 0, "track::stop: pthread_join status:%d", err);
-    status_t threadRetVal = static_cast<status_t>(reinterpret_cast<uintptr_t>(dummy));
-    WARN_UNLESS(threadRetVal == 0, "%s track stopped. Status :%d. %s source",
-                getTrackType(), err, stopSource ? "Stop" : "Not Stop");
+    status_t err = OK;
+    int retVal = pthread_join(mThread, &dummy);
+    if (retVal == 0) {
+        err = static_cast<status_t>(reinterpret_cast<uintptr_t>(dummy));
+        ALOGD("%s track stopped. Status:%d. %s source",
+            getTrackType(), err, stopSource ? "Stop" : "Not Stop");
+    } else {
+        ALOGE("track::stop: pthread_join retVal:%d", retVal);
+        err = UNKNOWN_ERROR;
+    }
     mStarted = false;
     return err;
 }
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index e944e21..6b264d5 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -2497,6 +2497,18 @@
                         }
                         break;
                     }
+                    if (!mLeftover.empty()) {
+                        ssize_t index = dequeuePortBuffer(kPortIndexInput);
+                        CHECK_GE(index, 0);
+
+                        status_t err = handleLeftover(index);
+                        if (err != OK) {
+                            setStickyError(err);
+                            postActivityNotificationIfPossible();
+                            cancelPendingDequeueOperations();
+                        }
+                        break;
+                    }
 
                     if (mFlags & kFlagIsAsync) {
                         if (!mHaveInputSurface) {
@@ -3186,7 +3198,15 @@
                 break;
             }
 
-            status_t err = onQueueInputBuffer(msg);
+            status_t err = UNKNOWN_ERROR;
+            if (!mLeftover.empty()) {
+                mLeftover.push_back(msg);
+                size_t index;
+                msg->findSize("index", &index);
+                err = handleLeftover(index);
+            } else {
+                err = onQueueInputBuffer(msg);
+            }
 
             PostReplyWithError(replyID, err);
             break;
@@ -3473,8 +3493,8 @@
     sp<hardware::HidlMemory> memory;
     size_t offset = 0;
 
-    if ((mFlags & kFlagUseBlockModel) && mOwnerName.startsWith("codec2::")) {
-        if (mCrypto) {
+    if (mFlags & kFlagUseBlockModel) {
+        if (hasCryptoOrDescrambler()) {
             constexpr size_t kInitialDealerCapacity = 1048576;  // 1MB
             thread_local sp<MemoryDealer> sDealer = new MemoryDealer(
                     kInitialDealerCapacity, "CSD(1MB)");
@@ -3599,6 +3619,9 @@
     CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput);
     Mutex::Autolock al(mBufferLock);
 
+    if (portIndex == kPortIndexInput) {
+        mLeftover.clear();
+    }
     for (size_t i = 0; i < mPortBuffers[portIndex].size(); ++i) {
         BufferInfo *info = &mPortBuffers[portIndex][i];
 
@@ -3729,7 +3752,26 @@
             err = mBufferChannel->attachEncryptedBuffer(
                     memory, (mFlags & kFlagIsSecure), key, iv, mode, pattern,
                     offset, subSamples, numSubSamples, buffer);
+        } else {
+            err = UNKNOWN_ERROR;
         }
+
+        if (err == OK && !buffer->asC2Buffer()
+                && c2Buffer && c2Buffer->data().type() == C2BufferData::LINEAR) {
+            C2ConstLinearBlock block{c2Buffer->data().linearBlocks().front()};
+            if (block.size() > buffer->size()) {
+                C2ConstLinearBlock leftover = block.subBlock(
+                        block.offset() + buffer->size(), block.size() - buffer->size());
+                sp<WrapperObject<std::shared_ptr<C2Buffer>>> obj{
+                    new WrapperObject<std::shared_ptr<C2Buffer>>{
+                        C2Buffer::CreateLinearBuffer(leftover)}};
+                msg->setObject("c2buffer", obj);
+                mLeftover.push_front(msg);
+                // Not sending EOS if we have leftovers
+                flags &= ~BUFFER_FLAG_EOS;
+            }
+        }
+
         offset = buffer->offset();
         size = buffer->size();
         if (err != OK) {
@@ -3794,6 +3836,16 @@
     return err;
 }
 
+status_t MediaCodec::handleLeftover(size_t index) {
+    if (mLeftover.empty()) {
+        return OK;
+    }
+    sp<AMessage> msg = mLeftover.front();
+    mLeftover.pop_front();
+    msg->setSize("index", index);
+    return onQueueInputBuffer(msg);
+}
+
 //static
 size_t MediaCodec::CreateFramesRenderedMessage(
         const std::list<FrameRenderTracker::Info> &done, sp<AMessage> &msg) {
diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp
index a1e4d43..ef69827 100644
--- a/media/libstagefright/Utils.cpp
+++ b/media/libstagefright/Utils.cpp
@@ -1135,7 +1135,7 @@
         // assertion, let's be lenient for now...
         // CHECK((ptr[4] >> 2) == 0x3f);  // reserved
 
-        size_t lengthSize __unused = 1 + (ptr[4] & 3);
+        // we can get lengthSize value from 1 + (ptr[4] & 3)
 
         // commented out check below as H264_QVGA_500_NO_AUDIO.3gp
         // violates it...
diff --git a/media/libstagefright/include/media/stagefright/MPEG4Writer.h b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
index e048f07..a1fe57c 100644
--- a/media/libstagefright/include/media/stagefright/MPEG4Writer.h
+++ b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
@@ -87,7 +87,8 @@
     enum {
         kWhatSwitch                  = 'swch',
         kWhatIOError                 = 'ioer',
-        kWhatFallocateError          = 'faer'
+        kWhatFallocateError          = 'faer',
+        kWhatNoIOErrorSoFar          = 'noie'
     };
 
     int  mFd;
@@ -231,7 +232,7 @@
     status_t stopWriterThread();
     static void *ThreadWrapper(void *me);
     void threadFunc();
-    void setupAndStartLooper();
+    status_t setupAndStartLooper();
     void stopAndReleaseLooper();
 
     // Buffer a single chunk to be written out later.
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index d06e973..5f5d88a 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -522,6 +522,9 @@
     class ReleaseSurface;
     std::unique_ptr<ReleaseSurface> mReleaseSurface;
 
+    std::list<sp<AMessage>> mLeftover;
+    status_t handleLeftover(size_t index);
+
     sp<BatteryChecker> mBatteryChecker;
 
     void statsBufferSent(int64_t presentationUs);
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index 4bb21fa..e751a3e 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -172,29 +172,26 @@
         return 0;
     }
 
-    unsigned bsmod __unused = bits.getBits(3);
+    bits.skipBits(3); // bsmod
     unsigned acmod = bits.getBits(3);
-    unsigned cmixlev __unused = 0;
-    unsigned surmixlev __unused = 0;
-    unsigned dsurmod __unused = 0;
 
     if ((acmod & 1) > 0 && acmod != 1) {
         if (bits.numBitsLeft() < 2) {
             return 0;
         }
-        cmixlev = bits.getBits(2);
+        bits.skipBits(2); //cmixlev
     }
     if ((acmod & 4) > 0) {
         if (bits.numBitsLeft() < 2) {
             return 0;
         }
-        surmixlev = bits.getBits(2);
+        bits.skipBits(2); //surmixlev
     }
     if (acmod == 2) {
         if (bits.numBitsLeft() < 2) {
             return 0;
         }
-        dsurmod = bits.getBits(2);
+        bits.skipBits(2); //dsurmod
     }
 
     if (bits.numBitsLeft() < 1) {
@@ -269,7 +266,7 @@
         samplingRate = samplingRateTable2[fscod2];
     } else {
         samplingRate = samplingRateTable[fscod];
-        unsigned numblkscod __unused = bits.getBits(2);
+        bits.skipBits(2); // numblkscod
     }
 
     unsigned acmod = bits.getBits(3);
@@ -1087,7 +1084,7 @@
     }
     unsigned numAUs = bits.getBits(8);
     bits.skipBits(8);
-    unsigned quantization_word_length __unused = bits.getBits(2);
+    bits.skipBits(2); // quantization_word_length
     unsigned audio_sampling_frequency = bits.getBits(3);
     unsigned num_channels = bits.getBits(3);
 
diff --git a/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp b/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
index 7d217eb..f7bf3ba 100644
--- a/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
+++ b/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
@@ -67,7 +67,7 @@
             int32_t dataSpace, int32_t aspects, int32_t pixelFormat) override {
         Message tMsg;
         tMsg.type = Message::Type::EVENT;
-        tMsg.fence = native_handle_create(0, 0);
+        tMsg.fence.setTo(native_handle_create(0, 0), /* shouldOwn = */ true);
         tMsg.data.eventData.event = uint32_t(OMX_EventDataSpaceChanged);
         tMsg.data.eventData.data1 = dataSpace;
         tMsg.data.eventData.data2 = aspects;
diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp
index bb66f4c..c33bf3f 100644
--- a/media/libstagefright/rtsp/ARTSPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTSPConnection.cpp
@@ -329,6 +329,7 @@
     mPass.clear();
     mAuthType = NONE;
     mNonce.clear();
+    mRealm.clear();
 
     mState = DISCONNECTED;
 }
@@ -911,6 +912,14 @@
         CHECK_GE(j, 0);
 
         mNonce.setTo(value, i + 7, j - i - 7);
+
+        i = value.find("realm=");
+        CHECK_GE(i, 0);
+        CHECK_EQ(value.c_str()[i + 6], '\"');
+        j = value.find("\"", i + 7);
+        CHECK_GE(j, 0);
+
+        mRealm.setTo(value, i + 7, j - i - 7);
     }
 
     return true;
@@ -993,7 +1002,7 @@
     AString A1;
     A1.append(mUser);
     A1.append(":");
-    A1.append("Streaming Server");
+    A1.append(mRealm);
     A1.append(":");
     A1.append(mPass);
 
@@ -1029,6 +1038,9 @@
     fragment.append("\", ");
     fragment.append("response=\"");
     fragment.append(digest);
+    fragment.append("\", ");
+    fragment.append("realm=\"");
+    fragment.append(mRealm);
     fragment.append("\"");
     fragment.append("\r\n");
 
diff --git a/media/libstagefright/rtsp/ARTSPConnection.h b/media/libstagefright/rtsp/ARTSPConnection.h
index 56b604d..7cdd4c0 100644
--- a/media/libstagefright/rtsp/ARTSPConnection.h
+++ b/media/libstagefright/rtsp/ARTSPConnection.h
@@ -84,6 +84,7 @@
     AString mUser, mPass;
     AuthType mAuthType;
     AString mNonce;
+    AString mRealm;
     int mSocket;
     int32_t mConnectionID;
     int32_t mNextCSeq;
diff --git a/media/ndk/include/media/NdkMediaCodec.h b/media/ndk/include/media/NdkMediaCodec.h
index 8fb6a87..80d5d50 100644
--- a/media/ndk/include/media/NdkMediaCodec.h
+++ b/media/ndk/include/media/NdkMediaCodec.h
@@ -114,12 +114,12 @@
         int32_t actionCode,
         const char *detail);
 
-struct AMediaCodecOnAsyncNotifyCallback {
+typedef struct AMediaCodecOnAsyncNotifyCallback {
       AMediaCodecOnAsyncInputAvailable  onAsyncInputAvailable;
       AMediaCodecOnAsyncOutputAvailable onAsyncOutputAvailable;
       AMediaCodecOnAsyncFormatChanged   onAsyncFormatChanged;
       AMediaCodecOnAsyncError           onAsyncError;
-};
+} AMediaCodecOnAsyncNotifyCallback;
 
 #if __ANDROID_API__ >= 21
 
diff --git a/services/audioflinger/FastCapture.cpp b/services/audioflinger/FastCapture.cpp
index dd84bf2..d6d6e25 100644
--- a/services/audioflinger/FastCapture.cpp
+++ b/services/audioflinger/FastCapture.cpp
@@ -154,7 +154,7 @@
         mReadBufferState = -1;
         dumpState->mFrameCount = frameCount;
     }
-
+    dumpState->mSilenced = current->mSilenceCapture;
 }
 
 void FastCapture::onWork()
@@ -208,6 +208,9 @@
             mReadBufferState = frameCount;
         }
         if (mReadBufferState > 0) {
+            if (current->mSilenceCapture) {
+                memset(mReadBuffer, 0, mReadBufferState * Format_frameSize(mFormat));
+            }
             ssize_t framesWritten = mPipeSink->write(mReadBuffer, mReadBufferState);
             audio_track_cblk_t* cblk = current->mCblk;
             if (fastPatchRecordBufferProvider != 0) {
diff --git a/services/audioflinger/FastCaptureDumpState.cpp b/services/audioflinger/FastCaptureDumpState.cpp
index 53eeba5..b8b3866 100644
--- a/services/audioflinger/FastCaptureDumpState.cpp
+++ b/services/audioflinger/FastCaptureDumpState.cpp
@@ -44,10 +44,11 @@
     double periodSec = (double) mFrameCount / mSampleRate;
     dprintf(fd, "  FastCapture command=%s readSequence=%u framesRead=%u\n"
                 "              readErrors=%u sampleRate=%u frameCount=%zu\n"
-                "              measuredWarmup=%.3g ms, warmupCycles=%u period=%.2f ms\n",
+                "              measuredWarmup=%.3g ms, warmupCycles=%u period=%.2f ms\n"
+                "              silenced: %s\n",
                 FastCaptureState::commandToString(mCommand), mReadSequence, mFramesRead,
                 mReadErrors, mSampleRate, mFrameCount, measuredWarmupMs, mWarmupCycles,
-                periodSec * 1e3);
+                periodSec * 1e3, mSilenced ? "true" : "false");
 }
 
 }   // android
diff --git a/services/audioflinger/FastCaptureDumpState.h b/services/audioflinger/FastCaptureDumpState.h
index 6f9c4c3..a1b8706 100644
--- a/services/audioflinger/FastCaptureDumpState.h
+++ b/services/audioflinger/FastCaptureDumpState.h
@@ -35,6 +35,7 @@
     uint32_t mReadErrors;       // total number of read() errors
     uint32_t mSampleRate;
     size_t   mFrameCount;
+    bool     mSilenced = false; // capture is silenced
 };
 
 }   // android
diff --git a/services/audioflinger/FastCaptureState.h b/services/audioflinger/FastCaptureState.h
index d287232..f949275 100644
--- a/services/audioflinger/FastCaptureState.h
+++ b/services/audioflinger/FastCaptureState.h
@@ -41,6 +41,8 @@
     audio_format_t  mFastPatchRecordFormat = AUDIO_FORMAT_INVALID;
     AudioBufferProvider* mFastPatchRecordBufferProvider = nullptr;   // a reference to a patch
                                                                      // record in fast mode
+    bool            mSilenceCapture = false;    // request to silence capture for fast track.
+                                                // note: this also silences the normal mixer pipe
 
     // Extends FastThreadState::Command
     static const Command
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 08df775..7e63851 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -7088,6 +7088,8 @@
         // reference to a fast track which is about to be removed
         sp<RecordTrack> fastTrackToRemove;
 
+        bool silenceFastCapture = false;
+
         { // scope for mLock
             Mutex::Autolock _l(mLock);
 
@@ -7175,14 +7177,33 @@
                             __func__, activeTrackState, activeTrack->id(), size);
                 }
 
-                activeTracks.add(activeTrack);
-                i++;
-
                 if (activeTrack->isFastTrack()) {
                     ALOG_ASSERT(!mFastTrackAvail);
                     ALOG_ASSERT(fastTrack == 0);
+                    // if the active fast track is silenced either:
+                    // 1) silence the whole capture from fast capture buffer if this is
+                    //    the only active track
+                    // 2) invalidate this track: this will cause the client to reconnect and possibly
+                    //    be invalidated again until unsilenced
+                    if (activeTrack->isSilenced()) {
+                        if (size > 1) {
+                            activeTrack->invalidate();
+                            ALOG_ASSERT(fastTrackToRemove == 0);
+                            fastTrackToRemove = activeTrack;
+                            removeTrack_l(activeTrack);
+                            mActiveTracks.remove(activeTrack);
+                            size--;
+                            continue;
+                        } else {
+                            silenceFastCapture = true;
+                        }
+                    }
                     fastTrack = activeTrack;
                 }
+
+                activeTracks.add(activeTrack);
+                i++;
+
             }
 
             mActiveTracks.updatePowerState(this);
@@ -7256,6 +7277,10 @@
                         AUDIO_FORMAT_INVALID : fastTrack->format();
                 didModify = true;
             }
+            if (state->mSilenceCapture != silenceFastCapture) {
+                state->mSilenceCapture = silenceFastCapture;
+                didModify = true;
+            }
             sq->end(didModify);
             if (didModify) {
                 sq->push(block);
@@ -7340,8 +7365,10 @@
 
         // Update server timestamp with server stats
         // systemTime() is optional if the hardware supports timestamps.
-        mTimestamp.mPosition[ExtendedTimestamp::LOCATION_SERVER] += framesRead;
-        mTimestamp.mTimeNs[ExtendedTimestamp::LOCATION_SERVER] = lastIoEndNs;
+        if (framesRead >= 0) {
+            mTimestamp.mPosition[ExtendedTimestamp::LOCATION_SERVER] += framesRead;
+            mTimestamp.mTimeNs[ExtendedTimestamp::LOCATION_SERVER] = lastIoEndNs;
+        }
 
         // Update server timestamp with kernel stats
         if (mPipeSource.get() == nullptr /* don't obtain for FastCapture, could block */) {
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 2a9a4c4..f05be06 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -3820,7 +3820,11 @@
                     ALOGE("%s output not found for id %d", __func__, patch->sources[0].id);
                     return BAD_VALUE;
                 }
-                // Reset handle so that setOutputDevice will force new AF patch to reach the sink
+                if (patchDesc->getHandle() != outputDesc->getPatchHandle()) {
+                    // force SwOutput patch removal as AF counter part patch has already gone.
+                    ALOGV("%s reset patch handle on Output as different from SWBridge", __func__);
+                    removeAudioPatch(outputDesc->getPatchHandle());
+                }
                 outputDesc->setPatchHandle(AUDIO_PATCH_HANDLE_NONE);
                 setOutputDevices(outputDesc,
                                  getNewOutputDevices(outputDesc, true /*fromCache*/),
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index b00a2d9..01e70d8 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -756,9 +756,10 @@
         for (size_t i = 0; i < mInFlightMap.size(); i++) {
             InFlightRequest r = mInFlightMap.valueAt(i);
             lines.appendFormat("      Frame %d |  Timestamp: %" PRId64 ", metadata"
-                    " arrived: %s, buffers left: %d\n", mInFlightMap.keyAt(i),
+                    " arrived: %s, buffers left: %d, buffers returned with STATUS_ERROR: %d, "
+                    " buffers notified with error: %d\n", mInFlightMap.keyAt(i),
                     r.shutterTimestamp, r.haveResultMetadata ? "true" : "false",
-                    r.numBuffersLeft);
+                    r.numBuffersLeft, r.numErrorBuffersReturned, r.numErrorBuffersNotified);
         }
     }
     write(fd, lines.string(), lines.size());
@@ -1043,8 +1044,9 @@
     }
     CaptureOutputStates states {
         mId,
-        mInFlightLock, mInFlightMap,
-        mOutputLock,  mResultQueue, mResultSignal,
+        mInFlightLock, mLastCompletedRegularFrameNumber,
+        mLastCompletedReprocessFrameNumber, mLastCompletedZslFrameNumber,
+        mInFlightMap, mOutputLock,  mResultQueue, mResultSignal,
         mNextShutterFrameNumber,
         mNextReprocessShutterFrameNumber, mNextZslStillShutterFrameNumber,
         mNextResultFrameNumber,
@@ -1100,8 +1102,9 @@
 
     CaptureOutputStates states {
         mId,
-        mInFlightLock, mInFlightMap,
-        mOutputLock,  mResultQueue, mResultSignal,
+        mInFlightLock, mLastCompletedRegularFrameNumber,
+        mLastCompletedReprocessFrameNumber, mLastCompletedZslFrameNumber,
+        mInFlightMap, mOutputLock,  mResultQueue, mResultSignal,
         mNextShutterFrameNumber,
         mNextReprocessShutterFrameNumber, mNextZslStillShutterFrameNumber,
         mNextResultFrameNumber,
@@ -1139,8 +1142,9 @@
 
     CaptureOutputStates states {
         mId,
-        mInFlightLock, mInFlightMap,
-        mOutputLock,  mResultQueue, mResultSignal,
+        mInFlightLock, mLastCompletedRegularFrameNumber,
+        mLastCompletedReprocessFrameNumber, mLastCompletedZslFrameNumber,
+        mInFlightMap, mOutputLock,  mResultQueue, mResultSignal,
         mNextShutterFrameNumber,
         mNextReprocessShutterFrameNumber, mNextZslStillShutterFrameNumber,
         mNextResultFrameNumber,
@@ -1777,13 +1781,6 @@
     mStatusChanged.broadcast();
 }
 
-void Camera3Device::pauseStateNotify(bool enable) {
-    Mutex::Autolock il(mInterfaceLock);
-    Mutex::Autolock l(mLock);
-
-    mPauseStateNotify = enable;
-}
-
 // Pause to reconfigure
 status_t Camera3Device::internalPauseAndWaitLocked(nsecs_t maxExpectedDuration) {
     if (mRequestThread.get() != nullptr) {
@@ -2359,7 +2356,7 @@
     return false;
 }
 
-bool Camera3Device::reconfigureCamera(const CameraMetadata& sessionParams) {
+bool Camera3Device::reconfigureCamera(const CameraMetadata& sessionParams, int clientStatusId) {
     ATRACE_CALL();
     bool ret = false;
 
@@ -2373,7 +2370,16 @@
         return true;
     }
 
-    auto rc = internalPauseAndWaitLocked(maxExpectedDuration);
+    status_t rc = NO_ERROR;
+    bool markClientActive = false;
+    if (mStatus == STATUS_ACTIVE) {
+        markClientActive = true;
+        mPauseStateNotify = true;
+        mStatusTracker->markComponentIdle(clientStatusId, Fence::NO_FENCE);
+
+        rc = internalPauseAndWaitLocked(maxExpectedDuration);
+    }
+
     if (rc == NO_ERROR) {
         mNeedConfig = true;
         rc = configureStreamsLocked(mOperatingMode, sessionParams, /*notifyRequestThread*/ false);
@@ -2401,6 +2407,10 @@
         ALOGE("%s: Failed to pause streaming: %d", __FUNCTION__, rc);
     }
 
+    if (markClientActive) {
+        mStatusTracker->markComponentActive(clientStatusId);
+    }
+
     return ret;
 }
 
@@ -4277,22 +4287,11 @@
         }
 
         if (res == OK) {
-            sp<StatusTracker> statusTracker = mStatusTracker.promote();
-            if (statusTracker != 0) {
-                sp<Camera3Device> parent = mParent.promote();
-                if (parent != nullptr) {
-                    parent->pauseStateNotify(true);
-                }
-
-                statusTracker->markComponentIdle(mStatusId, Fence::NO_FENCE);
-
-                if (parent != nullptr) {
-                    mReconfigured |= parent->reconfigureCamera(mLatestSessionParams);
-                }
-
-                statusTracker->markComponentActive(mStatusId);
-                setPaused(false);
+            sp<Camera3Device> parent = mParent.promote();
+            if (parent != nullptr) {
+                mReconfigured |= parent->reconfigureCamera(mLatestSessionParams, mStatusId);
             }
+            setPaused(false);
 
             if (mNextRequests[0].captureRequest->mInputStream != nullptr) {
                 mNextRequests[0].captureRequest->mInputStream->restoreConfiguredState();
@@ -5906,11 +5905,13 @@
     //       though technically no other thread should be talking to Camera3Device at this point
     Camera3OfflineStates offlineStates(
             mTagMonitor, mVendorTagId, mUseHalBufManager, mNeedFixupMonochromeTags,
-            mUsePartialResult, mNumPartialResults, mNextResultFrameNumber,
-            mNextReprocessResultFrameNumber, mNextZslStillResultFrameNumber,
-            mNextShutterFrameNumber, mNextReprocessShutterFrameNumber,
-            mNextZslStillShutterFrameNumber, mDeviceInfo, mPhysicalDeviceInfoMap,
-            mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers);
+            mUsePartialResult, mNumPartialResults, mLastCompletedRegularFrameNumber,
+            mLastCompletedReprocessFrameNumber, mLastCompletedZslFrameNumber,
+            mNextResultFrameNumber, mNextReprocessResultFrameNumber,
+            mNextZslStillResultFrameNumber, mNextShutterFrameNumber,
+            mNextReprocessShutterFrameNumber, mNextZslStillShutterFrameNumber,
+            mDeviceInfo, mPhysicalDeviceInfoMap, mDistortionMappers,
+            mZoomRatioMappers, mRotateAndCropMappers);
 
     *session = new Camera3OfflineSession(mId, inputStream, offlineStreamSet,
             std::move(bufferRecords), offlineReqs, offlineStates, offlineSession);
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index c059f55..408f1f9 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -638,17 +638,10 @@
                                             const SurfaceMap &surfaceMap);
 
     /**
-     * Pause state updates to the client application.  Needed to mask out idle/active
-     * transitions during internal reconfigure
-     */
-    void pauseStateNotify(bool enable);
-
-    /**
      * Internally re-configure camera device using new session parameters.
-     * This will get triggered by the request thread. Be sure to call
-     * pauseStateNotify(true) before going idle in the requesting location.
+     * This will get triggered by the request thread.
      */
-    bool reconfigureCamera(const CameraMetadata& sessionParams);
+    bool reconfigureCamera(const CameraMetadata& sessionParams, int clientStatusId);
 
     /**
      * Return true in case of any output or input abandoned streams,
@@ -1017,6 +1010,9 @@
     std::mutex                    mInFlightLock;
     camera3::InFlightRequestMap   mInFlightMap;
     nsecs_t                       mExpectedInflightDuration = 0;
+    int64_t                       mLastCompletedRegularFrameNumber = -1;
+    int64_t                       mLastCompletedReprocessFrameNumber = -1;
+    int64_t                       mLastCompletedZslFrameNumber = -1;
     // End of mInFlightLock protection scope
 
     int mInFlightStatusId; // const after initialize
diff --git a/services/camera/libcameraservice/device3/Camera3OfflineSession.cpp b/services/camera/libcameraservice/device3/Camera3OfflineSession.cpp
index 5942868..95f9633 100644
--- a/services/camera/libcameraservice/device3/Camera3OfflineSession.cpp
+++ b/services/camera/libcameraservice/device3/Camera3OfflineSession.cpp
@@ -61,6 +61,9 @@
         mNeedFixupMonochromeTags(offlineStates.mNeedFixupMonochromeTags),
         mUsePartialResult(offlineStates.mUsePartialResult),
         mNumPartialResults(offlineStates.mNumPartialResults),
+        mLastCompletedRegularFrameNumber(offlineStates.mLastCompletedRegularFrameNumber),
+        mLastCompletedReprocessFrameNumber(offlineStates.mLastCompletedReprocessFrameNumber),
+        mLastCompletedZslFrameNumber(offlineStates.mLastCompletedZslFrameNumber),
         mNextResultFrameNumber(offlineStates.mNextResultFrameNumber),
         mNextReprocessResultFrameNumber(offlineStates.mNextReprocessResultFrameNumber),
         mNextZslStillResultFrameNumber(offlineStates.mNextZslStillResultFrameNumber),
@@ -247,8 +250,9 @@
 
     CaptureOutputStates states {
         mId,
-        mOfflineReqsLock, mOfflineReqs,
-        mOutputLock, mResultQueue, mResultSignal,
+        mOfflineReqsLock, mLastCompletedRegularFrameNumber,
+        mLastCompletedReprocessFrameNumber, mLastCompletedZslFrameNumber,
+        mOfflineReqs, mOutputLock, mResultQueue, mResultSignal,
         mNextShutterFrameNumber,
         mNextReprocessShutterFrameNumber, mNextZslStillShutterFrameNumber,
         mNextResultFrameNumber,
@@ -285,8 +289,9 @@
 
     CaptureOutputStates states {
         mId,
-        mOfflineReqsLock, mOfflineReqs,
-        mOutputLock, mResultQueue, mResultSignal,
+        mOfflineReqsLock, mLastCompletedRegularFrameNumber,
+        mLastCompletedReprocessFrameNumber, mLastCompletedZslFrameNumber,
+        mOfflineReqs, mOutputLock, mResultQueue, mResultSignal,
         mNextShutterFrameNumber,
         mNextReprocessShutterFrameNumber, mNextZslStillShutterFrameNumber,
         mNextResultFrameNumber,
@@ -318,8 +323,9 @@
 
     CaptureOutputStates states {
         mId,
-        mOfflineReqsLock, mOfflineReqs,
-        mOutputLock, mResultQueue, mResultSignal,
+        mOfflineReqsLock, mLastCompletedRegularFrameNumber,
+        mLastCompletedReprocessFrameNumber, mLastCompletedZslFrameNumber,
+        mOfflineReqs, mOutputLock, mResultQueue, mResultSignal,
         mNextShutterFrameNumber,
         mNextReprocessShutterFrameNumber, mNextZslStillShutterFrameNumber,
         mNextResultFrameNumber,
diff --git a/services/camera/libcameraservice/device3/Camera3OfflineSession.h b/services/camera/libcameraservice/device3/Camera3OfflineSession.h
index 208f70d..c4c7a85 100644
--- a/services/camera/libcameraservice/device3/Camera3OfflineSession.h
+++ b/services/camera/libcameraservice/device3/Camera3OfflineSession.h
@@ -57,10 +57,11 @@
             const TagMonitor& tagMonitor, const metadata_vendor_id_t vendorTagId,
             const bool useHalBufManager, const bool needFixupMonochromeTags,
             const bool usePartialResult, const uint32_t numPartialResults,
-            const uint32_t nextResultFN, const uint32_t nextReprocResultFN,
-            const uint32_t nextZslResultFN,  const uint32_t nextShutterFN,
-            const uint32_t nextReprocShutterFN, const uint32_t nextZslShutterFN,
-            const CameraMetadata& deviceInfo,
+            const int64_t lastCompletedRegularFN, const int64_t lastCompletedReprocessFN,
+            const int64_t lastCompletedZslFN, const uint32_t nextResultFN,
+            const uint32_t nextReprocResultFN, const uint32_t nextZslResultFN,
+            const uint32_t nextShutterFN, const uint32_t nextReprocShutterFN,
+            const uint32_t nextZslShutterFN, const CameraMetadata& deviceInfo,
             const std::unordered_map<std::string, CameraMetadata>& physicalDeviceInfoMap,
             const std::unordered_map<std::string, camera3::DistortionMapper>& distortionMappers,
             const std::unordered_map<std::string, camera3::ZoomRatioMapper>& zoomRatioMappers,
@@ -69,6 +70,9 @@
             mTagMonitor(tagMonitor), mVendorTagId(vendorTagId),
             mUseHalBufManager(useHalBufManager), mNeedFixupMonochromeTags(needFixupMonochromeTags),
             mUsePartialResult(usePartialResult), mNumPartialResults(numPartialResults),
+            mLastCompletedRegularFrameNumber(lastCompletedRegularFN),
+            mLastCompletedReprocessFrameNumber(lastCompletedReprocessFN),
+            mLastCompletedZslFrameNumber(lastCompletedZslFN),
             mNextResultFrameNumber(nextResultFN),
             mNextReprocessResultFrameNumber(nextReprocResultFN),
             mNextZslStillResultFrameNumber(nextZslResultFN),
@@ -90,6 +94,15 @@
     const bool mUsePartialResult;
     const uint32_t mNumPartialResults;
 
+    // The last completed (buffers, result metadata, and error notify) regular
+    // request frame number
+    const int64_t mLastCompletedRegularFrameNumber;
+    // The last completed (buffers, result metadata, and error notify) reprocess
+    // request frame number
+    const int64_t mLastCompletedReprocessFrameNumber;
+    // The last completed (buffers, result metadata, and error notify) zsl
+    // request frame number
+    const int64_t mLastCompletedZslFrameNumber;
     // the minimal frame number of the next non-reprocess result
     const uint32_t mNextResultFrameNumber;
     // the minimal frame number of the next reprocess result
@@ -214,6 +227,12 @@
     std::mutex mOutputLock;
     std::list<CaptureResult> mResultQueue;
     std::condition_variable mResultSignal;
+    // the last completed frame number of regular requests
+    int64_t mLastCompletedRegularFrameNumber;
+    // the last completed frame number of reprocess requests
+    int64_t mLastCompletedReprocessFrameNumber;
+    // the last completed frame number of ZSL still capture requests
+    int64_t mLastCompletedZslFrameNumber;
     // the minimal frame number of the next non-reprocess result
     uint32_t mNextResultFrameNumber;
     // the minimal frame number of the next reprocess result
diff --git a/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp b/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
index 603f516..4994393 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
+++ b/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
@@ -395,7 +395,7 @@
 
 void removeInFlightRequestIfReadyLocked(CaptureOutputStates& states, int idx) {
     InFlightRequestMap& inflightMap = states.inflightMap;
-    const InFlightRequest &request = inflightMap.valueAt(idx);
+    InFlightRequest &request = inflightMap.editValueAt(idx);
     const uint32_t frameNumber = inflightMap.keyAt(idx);
 
     nsecs_t sensorTimestamp = request.sensorTimestamp;
@@ -405,8 +405,8 @@
     // In the case of a successful request:
     //      all input and output buffers, all result metadata, shutter callback
     //      arrived.
-    // In the case of a unsuccessful request:
-    //      all input and output buffers arrived.
+    // In the case of an unsuccessful request:
+    //      all input and output buffers, as well as error notifications, arrived.
     if (request.numBuffersLeft == 0 &&
             (request.skipResultMetadata ||
             (request.haveResultMetadata && shutterTimestamp != 0))) {
@@ -430,14 +430,26 @@
         assert(request.requestStatus != OK ||
                request.pendingOutputBuffers.size() == 0);
 
-        returnOutputBuffers(
+        size_t bufferErrorCnt = returnOutputBuffers(
             states.useHalBufManager, states.listener,
             request.pendingOutputBuffers.array(),
             request.pendingOutputBuffers.size(), 0, /*timestampIncreasing*/true,
             request.outputSurfaces, request.resultExtras);
 
-        removeInFlightMapEntryLocked(states, idx);
-        ALOGVV("%s: removed frame %d from InFlightMap", __FUNCTION__, frameNumber);
+        request.numErrorBuffersReturned += bufferErrorCnt;
+        if (request.numErrorBuffersReturned == request.numErrorBuffersNotified) {
+            removeInFlightMapEntryLocked(states, idx);
+            ALOGVV("%s: removed frame %d from InFlightMap", __FUNCTION__, frameNumber);
+
+            // Note down the just completed frame number
+            if (request.hasInputBuffer) {
+                states.lastCompletedReprocessFrameNumber = frameNumber;
+            } else if (request.zslCapture) {
+                states.lastCompletedZslFrameNumber = frameNumber;
+            } else {
+                states.lastCompletedRegularFrameNumber = frameNumber;
+            }
+        }
     }
 
     states.inflightIntf.checkInflightMapLengthLocked();
@@ -487,10 +499,10 @@
         InFlightRequest &request = states.inflightMap.editValueAt(idx);
         ALOGVV("%s: got InFlightRequest requestId = %" PRId32
                 ", frameNumber = %" PRId64 ", burstId = %" PRId32
-                ", partialResultCount = %d, hasCallback = %d",
+                ", partialResultCount = %d, hasCallback = %d, num_output_buffers %d",
                 __FUNCTION__, request.resultExtras.requestId,
                 request.resultExtras.frameNumber, request.resultExtras.burstId,
-                result->partial_result, request.hasCallback);
+                result->partial_result, request.hasCallback, result->num_output_buffers);
         // Always update the partial count to the latest one if it's not 0
         // (buffers only). When framework aggregates adjacent partial results
         // into one, the latest partial count will be used.
@@ -589,10 +601,11 @@
                 result->num_output_buffers);
         } else {
             bool timestampIncreasing = !(request.zslCapture || request.hasInputBuffer);
-            returnOutputBuffers(states.useHalBufManager, states.listener,
+            auto numErrorBuffers = returnOutputBuffers(states.useHalBufManager, states.listener,
                 result->output_buffers, result->num_output_buffers,
                 shutterTimestamp, timestampIncreasing,
                 request.outputSurfaces, request.resultExtras);
+            request.numErrorBuffersReturned += numErrorBuffers;
         }
 
         if (result->result != NULL && !isPartialResult) {
@@ -630,6 +643,7 @@
                       "  its stream:%s (%d)",  __FUNCTION__,
                       frameNumber, strerror(-res), res);
             }
+
         } else {
             ALOGW("%s: Input buffer should be NULL if there is no input"
                     " buffer sent in the request, skipping input buffer return.",
@@ -785,7 +799,7 @@
     processCaptureResult(states, &r);
 }
 
-void returnOutputBuffers(
+size_t returnOutputBuffers(
         bool useHalBufManager,
         sp<NotificationListener> listener,
         const camera3_stream_buffer_t *outputBuffers, size_t numBuffers,
@@ -793,6 +807,7 @@
         const SurfaceMap& outputSurfaces,
         const CaptureResultExtras &inResultExtras) {
 
+    size_t numErrorBuffers = 0;
     for (size_t i = 0; i < numBuffers; i++)
     {
         if (outputBuffers[i].buffer == nullptr) {
@@ -801,6 +816,10 @@
                 // has not got a output buffer handle filled yet. This is though illegal if HAL
                 // buffer management API is not being used.
                 ALOGE("%s: cannot return a null buffer!", __FUNCTION__);
+            } else {
+                if (outputBuffers[i].status == CAMERA3_BUFFER_STATUS_ERROR) {
+                    numErrorBuffers++;
+                }
             }
             continue;
         }
@@ -844,8 +863,13 @@
                         hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_BUFFER,
                         extras);
             }
+        } else {
+            if (outputBuffers[i].status == CAMERA3_BUFFER_STATUS_ERROR) {
+               numErrorBuffers++;
+            }
         }
     }
+    return numErrorBuffers;
 }
 
 void notifyShutter(CaptureOutputStates& states, const camera3_shutter_msg_t &msg) {
@@ -899,6 +923,12 @@
                     msg.frame_number, r.resultExtras.requestId, msg.timestamp);
                 // Call listener, if any
                 if (states.listener != nullptr) {
+                    r.resultExtras.lastCompletedRegularFrameNumber =
+                            states.lastCompletedRegularFrameNumber;
+                    r.resultExtras.lastCompletedReprocessFrameNumber =
+                            states.lastCompletedReprocessFrameNumber;
+                    r.resultExtras.lastCompletedZslFrameNumber =
+                            states.lastCompletedZslFrameNumber;
                     states.listener->notifyShutter(r.resultExtras, msg.timestamp);
                 }
                 // send pending result and buffers
@@ -909,11 +939,12 @@
                     r.rotateAndCropAuto, r.cameraIdsWithZoom, r.physicalMetadatas);
             }
             bool timestampIncreasing = !(r.zslCapture || r.hasInputBuffer);
-            returnOutputBuffers(
+            size_t bufferErrorCnt = returnOutputBuffers(
                     states.useHalBufManager, states.listener,
                     r.pendingOutputBuffers.array(),
                     r.pendingOutputBuffers.size(), r.shutterTimestamp, timestampIncreasing,
                     r.outputSurfaces, r.resultExtras);
+            r.numErrorBuffersReturned += bufferErrorCnt;
             r.pendingOutputBuffers.clear();
 
             removeInFlightRequestIfReadyLocked(states, idx);
@@ -976,7 +1007,7 @@
                     InFlightRequest &r = states.inflightMap.editValueAt(idx);
                     r.requestStatus = msg.error_code;
                     resultExtras = r.resultExtras;
-                    bool logicalDeviceResultError = false;
+                    bool physicalDeviceResultError = false;
                     if (hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_RESULT ==
                             errorCode) {
                         if (physicalCameraId.size() > 0) {
@@ -990,23 +1021,39 @@
                             }
                             r.physicalCameraIds.erase(iter);
                             resultExtras.errorPhysicalCameraId = physicalCameraId;
-                        } else {
-                            logicalDeviceResultError = true;
+                            physicalDeviceResultError = true;
                         }
                     }
 
-                    if (logicalDeviceResultError
-                            ||  hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_REQUEST ==
-                            errorCode) {
-                        r.skipResultMetadata = true;
-                    }
-                    if (logicalDeviceResultError) {
-                        // In case of missing result check whether the buffers
-                        // returned. If they returned, then remove inflight
-                        // request.
-                        // TODO: should we call this for ERROR_CAMERA_REQUEST as well?
-                        //       otherwise we are depending on HAL to send the buffers back after
-                        //       calling notifyError. Not sure if that's in the spec.
+                    if (!physicalDeviceResultError) {
+                        if (hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_RESULT
+                                == errorCode) {
+                            r.skipResultMetadata = true;
+                        } else if (hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_BUFFER
+                                == errorCode) {
+                            r.numErrorBuffersNotified ++;
+                        } else {
+                            // errorCode is ERROR_CAMERA_REQUEST
+                            if (!r.skipResultMetadata) {
+                                // In case HAL calls multiples ERROR_REQUEST
+                                // callback, only count the pending buffer
+                                // notify error counter once. And also handle
+                                // the case where ERROR_BUFFERs are sent before
+                                // ERROR_REQUEST, even though it's not allowed
+                                // by the HAL API.
+                                if (r.numErrorBuffersNotified != 0) {
+                                    ALOGW("Camera %s: %s: HAL should not notify ERROR_REQUEST"
+                                            " and ERROR_BUFFER for the same request",
+                                            states.cameraId.string(), __FUNCTION__);
+                                }
+                                r.numErrorBuffersNotified =
+                                        r.numOutputBuffers - r.numErrorBuffersNotified;
+                                r.skipResultMetadata = true;
+                            }
+                        }
+
+                        // Check whether the buffers returned. If they returned,
+                        // remove inflight request.
                         removeInFlightRequestIfReadyLocked(states, idx);
                     }
                 } else {
@@ -1332,14 +1379,23 @@
     { // First return buffers cached in mInFlightMap
         std::lock_guard<std::mutex> l(states.inflightLock);
         for (size_t idx = 0; idx < states.inflightMap.size(); idx++) {
-            const InFlightRequest &request = states.inflightMap.valueAt(idx);
-            returnOutputBuffers(
+            InFlightRequest &request = states.inflightMap.editValueAt(idx);
+            size_t bufferErrorCnt = returnOutputBuffers(
                 states.useHalBufManager, states.listener,
                 request.pendingOutputBuffers.array(),
                 request.pendingOutputBuffers.size(), 0,
                 /*timestampIncreasing*/true, request.outputSurfaces,
                 request.resultExtras);
+            request.numErrorBuffersReturned += bufferErrorCnt;
+            ALOGW("%s: Frame %d |  Timestamp: %" PRId64 ", metadata"
+                    " arrived: %s, buffers left: %d, buffers returned with STATUS_ERROR: %d, "
+                    " buffers notified with error: %d\n", __FUNCTION__,
+                    states.inflightMap.keyAt(idx), request.shutterTimestamp,
+                    request.haveResultMetadata ? "true" : "false",
+                    request.numBuffersLeft, request.numErrorBuffersReturned,
+                    request.numErrorBuffersNotified);
         }
+
         states.inflightMap.clear();
         states.inflightIntf.onInflightMapFlushedLocked();
     }
diff --git a/services/camera/libcameraservice/device3/Camera3OutputUtils.h b/services/camera/libcameraservice/device3/Camera3OutputUtils.h
index fbb47f8..53f78f2 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputUtils.h
+++ b/services/camera/libcameraservice/device3/Camera3OutputUtils.h
@@ -45,7 +45,8 @@
      * Helper methods shared between Camera3Device/Camera3OfflineSession for HAL callbacks
      */
     // helper function to return the output buffers to output streams.
-    void returnOutputBuffers(
+    // returns the number of STATUS_ERROR buffers
+    size_t returnOutputBuffers(
             bool useHalBufManager,
             sp<NotificationListener> listener, // Only needed when outputSurfaces is not empty
             const camera3_stream_buffer_t *outputBuffers,
@@ -60,6 +61,9 @@
     struct CaptureOutputStates {
         const String8& cameraId;
         std::mutex& inflightLock;
+        int64_t& lastCompletedRegularFrameNumber;
+        int64_t& lastCompletedZslFrameNumber;
+        int64_t& lastCompletedReprocessFrameNumber;
         InFlightRequestMap& inflightMap; // end of inflightLock scope
         std::mutex& outputLock;
         std::list<CaptureResult>& resultQueue;
diff --git a/services/camera/libcameraservice/device3/InFlightRequest.h b/services/camera/libcameraservice/device3/InFlightRequest.h
index 424043b..3cb8324 100644
--- a/services/camera/libcameraservice/device3/InFlightRequest.h
+++ b/services/camera/libcameraservice/device3/InFlightRequest.h
@@ -43,6 +43,19 @@
     // Decremented by calls to process_capture_result with valid output
     // and input buffers
     int     numBuffersLeft;
+    // Total number of output buffers for this request
+    int     numOutputBuffers;
+
+    // The inflight request is considered complete if all buffers are returned
+    // and numErrorBuffersReturned == numErrorBuffersNotified.
+
+    // The number of buffers returned with STATUS_ERROR;
+    int numErrorBuffersReturned;
+    // The number of buffers that are notified as error:
+    //   +1 for each notifyError(ERROR_BUFFER), and
+    //   +numOutputBuffers for notifyError(ERROR_REQUEST)
+    int numErrorBuffersNotified;
+
     CaptureResultExtras resultExtras;
     // If this request has any input buffer
     bool hasInputBuffer;
@@ -110,6 +123,9 @@
             requestStatus(OK),
             haveResultMetadata(false),
             numBuffersLeft(0),
+            numOutputBuffers(0),
+            numErrorBuffersReturned(0),
+            numErrorBuffersNotified(0),
             hasInputBuffer(false),
             hasCallback(true),
             maxExpectedDuration(kDefaultExpectedDuration),
@@ -129,6 +145,9 @@
             requestStatus(OK),
             haveResultMetadata(false),
             numBuffersLeft(numBuffers),
+            numOutputBuffers(hasInput ? numBuffers-1 : numBuffers),
+            numErrorBuffersReturned(0),
+            numErrorBuffersNotified(0),
             resultExtras(extras),
             hasInputBuffer(hasInput),
             hasCallback(hasAppCallback),
diff --git a/services/mediametrics/statsd_drm.cpp b/services/mediametrics/statsd_drm.cpp
index 89d6f8f..ac58929 100644
--- a/services/mediametrics/statsd_drm.cpp
+++ b/services/mediametrics/statsd_drm.cpp
@@ -50,30 +50,26 @@
     int64_t pkgVersionCode = item->getPkgVersionCode();
     int64_t mediaApexVersion = 0;
 
-    char *vendor = nullptr;
-    (void) item->getCString("vendor", &vendor);
-    char *description = nullptr;
-    (void) item->getCString("description", &description);
-    char *serialized_metrics = nullptr;
-    (void) item->getCString("serialized_metrics", &serialized_metrics);
+    std::string vendor;
+    (void) item->getString("vendor", &vendor);
+    std::string description;
+    (void) item->getString("description", &description);
+    std::string serialized_metrics;
+    (void) item->getString("serialized_metrics", &serialized_metrics);
 
     if (enabled_statsd) {
-        android::util::BytesField bf_serialized(serialized_metrics ? serialized_metrics : nullptr,
-                                                serialized_metrics ? strlen(serialized_metrics)
-                                                                   : 0);
+        android::util::BytesField bf_serialized(serialized_metrics.c_str(),
+                                                serialized_metrics.size());
         android::util::stats_write(android::util::MEDIAMETRICS_MEDIADRM_REPORTED,
                                    timestamp, pkgName.c_str(), pkgVersionCode,
                                    mediaApexVersion,
-                                   vendor, description,
+                                   vendor.c_str(),
+                                   description.c_str(),
                                    bf_serialized);
     } else {
-        ALOGV("NOT sending: mediadrm private data (len=%zu)",
-              serialized_metrics ? strlen(serialized_metrics) : 0);
+        ALOGV("NOT sending: mediadrm private data (len=%zu)", serialized_metrics.size());
     }
 
-    free(vendor);
-    free(description);
-    free(serialized_metrics);
     return true;
 }
 
@@ -87,23 +83,20 @@
     int64_t pkgVersionCode = item->getPkgVersionCode();
     int64_t mediaApexVersion = 0;
 
-    char *serialized_metrics = nullptr;
-    (void) item->getCString("serialized_metrics", &serialized_metrics);
+    std::string serialized_metrics;
+    (void) item->getString("serialized_metrics", &serialized_metrics);
 
     if (enabled_statsd) {
-        android::util::BytesField bf_serialized(serialized_metrics ? serialized_metrics : nullptr,
-                                                serialized_metrics ? strlen(serialized_metrics)
-                                                                   : 0);
+        android::util::BytesField bf_serialized(serialized_metrics.c_str(),
+                                                serialized_metrics.size());
         android::util::stats_write(android::util::MEDIAMETRICS_DRM_WIDEVINE_REPORTED,
                                    timestamp, pkgName.c_str(), pkgVersionCode,
                                    mediaApexVersion,
                                    bf_serialized);
     } else {
-        ALOGV("NOT sending: widevine private data (len=%zu)",
-              serialized_metrics ? strlen(serialized_metrics) : 0);
+        ALOGV("NOT sending: widevine private data (len=%zu)", serialized_metrics.size());
     }
 
-    free(serialized_metrics);
     return true;
 }
 
@@ -123,14 +116,14 @@
     int64_t pkgVersionCode = item->getPkgVersionCode();
     int64_t mediaApexVersion = 0;
 
-    char *plugin_id = nullptr;
-    (void) item->getCString("plugin_id", &plugin_id);
-    char *description = nullptr;
-    (void) item->getCString("description", &description);
+    std::string plugin_id;
+    (void) item->getString("plugin_id", &plugin_id);
+    std::string description;
+    (void) item->getString("description", &description);
     int32_t method_id = -1;
     (void) item->getInt32("method_id", &method_id);
-    char *mime_types = nullptr;
-    (void) item->getCString("mime_types", &mime_types);
+    std::string mime_types;
+    (void) item->getString("mime_types", &mime_types);
 
     // Corresponds to the 13 APIs tracked in the MediametricsDrmManagerReported statsd proto
     // Please see also DrmManager::kMethodIdMap
@@ -141,16 +134,15 @@
 
     android::util::stats_write(android::util::MEDIAMETRICS_DRMMANAGER_REPORTED,
                                timestamp, pkgName.c_str(), pkgVersionCode, mediaApexVersion,
-                               plugin_id, description, method_id, mime_types,
+                               plugin_id.c_str(), description.c_str(),
+                               method_id, mime_types.c_str(),
                                methodCounts[0], methodCounts[1], methodCounts[2],
                                methodCounts[3], methodCounts[4], methodCounts[5],
                                methodCounts[6], methodCounts[7], methodCounts[8],
                                methodCounts[9], methodCounts[10], methodCounts[11],
                                methodCounts[12]);
 
-    free(plugin_id);
-    free(description);
-    free(mime_types);
     return true;
 }
+
 } // namespace android
diff --git a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppA.xml b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppA.xml
index 56e34f5..91c14a5 100644
--- a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppA.xml
+++ b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppA.xml
@@ -20,7 +20,8 @@
     android:versionName="1.0" >
 
     <application android:label="TestAppA">
-        <activity android:name="com.android.tests.transcoding.MainActivity">
+        <activity android:name="com.android.tests.transcoding.MainActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppB.xml b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppB.xml
index e1e7857..4baa35a 100644
--- a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppB.xml
+++ b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppB.xml
@@ -20,7 +20,8 @@
     android:versionName="1.0" >
 
     <application android:label="TestAppB">
-        <activity android:name="com.android.tests.transcoding.MainActivity">
+        <activity android:name="com.android.tests.transcoding.MainActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppC.xml b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppC.xml
index 55693a4..3dde3af 100644
--- a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppC.xml
+++ b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppC.xml
@@ -20,7 +20,8 @@
     android:versionName="1.0" >
 
     <application android:label="TestAppC">
-        <activity android:name="com.android.tests.transcoding.MainActivity">
+        <activity android:name="com.android.tests.transcoding.MainActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/services/mediatranscoding/tests/mediatranscodingservice_tests.cpp b/services/mediatranscoding/tests/mediatranscodingservice_tests.cpp
index 7f7d509..babcfb5 100644
--- a/services/mediatranscoding/tests/mediatranscodingservice_tests.cpp
+++ b/services/mediatranscoding/tests/mediatranscodingservice_tests.cpp
@@ -211,6 +211,11 @@
 
     virtual ~TestClientCallback() { ALOGI("TestClientCallback %d destroyed", mClientId); }
 
+    Status openFileDescriptor(const std::string& /*in_fileUri*/, const std::string& /*in_mode*/,
+                              ::ndk::ScopedFileDescriptor* /*_aidl_return*/) override {
+        return Status::ok();
+    }
+
     Status onTranscodingFinished(
             int32_t in_jobId,
             const ::aidl::android::media::TranscodingResultParcel& /* in_result */) override {
diff --git a/services/oboeservice/AAudioClientTracker.cpp b/services/oboeservice/AAudioClientTracker.cpp
index 6e14434..9d9ca63 100644
--- a/services/oboeservice/AAudioClientTracker.cpp
+++ b/services/oboeservice/AAudioClientTracker.cpp
@@ -106,18 +106,9 @@
 
 aaudio_result_t
 AAudioClientTracker::registerClientStream(pid_t pid, sp<AAudioServiceStreamBase> serviceStream) {
-    aaudio_result_t result = AAUDIO_OK;
     ALOGV("registerClientStream(%d,)\n", pid);
     std::lock_guard<std::mutex> lock(mLock);
-    sp<NotificationClient> notificationClient = mNotificationClients[pid];
-    if (notificationClient == 0) {
-        // This will get called the first time the audio server registers an internal stream.
-        ALOGV("registerClientStream(%d,) unrecognized pid\n", pid);
-        notificationClient = new NotificationClient(pid, nullptr);
-        mNotificationClients[pid] = notificationClient;
-    }
-    notificationClient->registerClientStream(serviceStream);
-    return result;
+    return getNotificationClient_l(pid)->registerClientStream(serviceStream);
 }
 
 // Find the tracker for this process and remove it.
@@ -136,6 +127,33 @@
     return AAUDIO_OK;
 }
 
+void AAudioClientTracker::setExclusiveEnabled(pid_t pid, bool enabled) {
+    ALOGD("%s(%d, %d)\n", __func__, pid, enabled);
+    std::lock_guard<std::mutex> lock(mLock);
+    getNotificationClient_l(pid)->setExclusiveEnabled(enabled);
+}
+
+bool AAudioClientTracker::isExclusiveEnabled(pid_t pid) {
+    std::lock_guard<std::mutex> lock(mLock);
+    return getNotificationClient_l(pid)->isExclusiveEnabled();
+}
+
+sp<AAudioClientTracker::NotificationClient>
+        AAudioClientTracker::getNotificationClient_l(pid_t pid) {
+    sp<NotificationClient> notificationClient = mNotificationClients[pid];
+    if (notificationClient == nullptr) {
+        // This will get called the first time the audio server uses this PID.
+        ALOGV("%s(%d,) unrecognized PID\n", __func__, pid);
+        notificationClient = new AAudioClientTracker::NotificationClient(pid, nullptr);
+        mNotificationClients[pid] = notificationClient;
+    }
+    return notificationClient;
+}
+
+// =======================================
+// AAudioClientTracker::NotificationClient
+// =======================================
+
 AAudioClientTracker::NotificationClient::NotificationClient(pid_t pid, const sp<IBinder>& binder)
         : mProcessId(pid), mBinder(binder) {
 }
diff --git a/services/oboeservice/AAudioClientTracker.h b/services/oboeservice/AAudioClientTracker.h
index 00ff467..943b809 100644
--- a/services/oboeservice/AAudioClientTracker.h
+++ b/services/oboeservice/AAudioClientTracker.h
@@ -58,6 +58,15 @@
     aaudio_result_t unregisterClientStream(pid_t pid,
                                            android::sp<AAudioServiceStreamBase> serviceStream);
 
+    /**
+     * Specify whether a process is allowed to create an EXCLUSIVE MMAP stream.
+     * @param pid
+     * @param enabled
+     */
+    void setExclusiveEnabled(pid_t pid, bool enabled);
+
+    bool isExclusiveEnabled(pid_t pid);
+
     android::AAudioService *getAAudioService() const {
         return mAAudioService;
     }
@@ -84,17 +93,29 @@
 
         aaudio_result_t unregisterClientStream(android::sp<AAudioServiceStreamBase> serviceStream);
 
+        void setExclusiveEnabled(bool enabled) {
+            mExclusiveEnabled = enabled;
+        }
+
+        bool isExclusiveEnabled() {
+            return mExclusiveEnabled;
+        }
+
         // IBinder::DeathRecipient
         virtual     void    binderDied(const android::wp<IBinder>& who);
 
-    protected:
+    private:
         mutable std::mutex                              mLock;
         const pid_t                                     mProcessId;
         std::set<android::sp<AAudioServiceStreamBase>>  mStreams;
         // hold onto binder to receive death notifications
         android::sp<IBinder>                            mBinder;
+        bool                                            mExclusiveEnabled = true;
     };
 
+    // This must be called under mLock
+    android::sp<NotificationClient> getNotificationClient_l(pid_t pid);
+
     mutable std::mutex                               mLock;
     std::map<pid_t, android::sp<NotificationClient>> mNotificationClients;
     android::AAudioService                          *mAAudioService = nullptr;
diff --git a/services/oboeservice/AAudioEndpointManager.cpp b/services/oboeservice/AAudioEndpointManager.cpp
index c9bf72f..9f34153 100644
--- a/services/oboeservice/AAudioEndpointManager.cpp
+++ b/services/oboeservice/AAudioEndpointManager.cpp
@@ -25,6 +25,7 @@
 #include <sstream>
 #include <utility/AAudioUtilities.h>
 
+#include "AAudioClientTracker.h"
 #include "AAudioEndpointManager.h"
 #include "AAudioServiceEndpointShared.h"
 #include "AAudioServiceEndpointMMAP.h"
@@ -174,7 +175,15 @@
                 && !request.isSharingModeMatchRequired()) { // app did not request a shared stream
             ALOGD("%s() endpoint in EXCLUSIVE use. Steal it!", __func__);
             mExclusiveStolenCount++;
-            endpointToSteal = endpoint;
+            // Prevent this process from getting another EXCLUSIVE stream.
+            // This will prevent two clients from colliding after a DISCONNECTION
+            // when they both try to open an exclusive stream at the same time.
+            // That can result in a stream getting disconnected between the OPEN
+            // and START calls. This will help preserve app compatibility.
+            // An app can avoid having this happen by closing their streams when
+            // the app is paused.
+            AAudioClientTracker::getInstance().setExclusiveEnabled(request.getProcessId(), false);
+            endpointToSteal = endpoint; // return it to caller
         }
         return nullptr;
     } else {
diff --git a/services/oboeservice/AAudioService.cpp b/services/oboeservice/AAudioService.cpp
index 82b12d6..22cdb35 100644
--- a/services/oboeservice/AAudioService.cpp
+++ b/services/oboeservice/AAudioService.cpp
@@ -117,7 +117,8 @@
         return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
     }
 
-    if (sharingMode == AAUDIO_SHARING_MODE_EXCLUSIVE) {
+    if (sharingMode == AAUDIO_SHARING_MODE_EXCLUSIVE
+        && AAudioClientTracker::getInstance().isExclusiveEnabled(request.getProcessId())) {
         // only trust audioserver for in service indication
         bool inService = false;
         if (isCallerInService()) {
diff --git a/services/oboeservice/AAudioServiceEndpoint.cpp b/services/oboeservice/AAudioServiceEndpoint.cpp
index 15cbd82..ceefe93 100644
--- a/services/oboeservice/AAudioServiceEndpoint.cpp
+++ b/services/oboeservice/AAudioServiceEndpoint.cpp
@@ -95,9 +95,16 @@
         mRegisteredStreams.swap(streamsDisconnected);
     }
     mConnected.store(false);
+    // We need to stop all the streams before we disconnect them.
+    // Otherwise there is a race condition where the first disconnected app
+    // tries to reopen a stream as MMAP but is blocked by the second stream,
+    // which hasn't stopped yet. Then the first app ends up with a Legacy stream.
     for (const auto &stream : streamsDisconnected) {
-        ALOGD("%s() - stop and disconnect port %d", __func__, stream->getPortHandle());
+        ALOGD("%s() - stop(), port = %d", __func__, stream->getPortHandle());
         stream->stop();
+    }
+    for (const auto &stream : streamsDisconnected) {
+        ALOGD("%s() - disconnect(), port = %d", __func__, stream->getPortHandle());
         stream->disconnect();
     }
     return streamsDisconnected;