Merge "TranscodingService: Enable java generation for transcoding aidl"
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d97975c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+  license_type: NOTICE
+}
diff --git a/camera/ICameraClient.cpp b/camera/ICameraClient.cpp
index 8620f36..487b8b0 100644
--- a/camera/ICameraClient.cpp
+++ b/camera/ICameraClient.cpp
@@ -143,6 +143,11 @@
             if (data.dataAvail() > 0) {
                 metadata = new camera_frame_metadata_t;
                 metadata->number_of_faces = data.readInt32();
+                if (metadata->number_of_faces <= 0 ||
+                        metadata->number_of_faces > (int32_t)(INT32_MAX / sizeof(camera_face_t))) {
+                    ALOGE("%s: Too large face count: %d", __FUNCTION__, metadata->number_of_faces);
+                    return BAD_VALUE;
+                }
                 metadata->faces = (camera_face_t *) data.readInplace(
                         sizeof(camera_face_t) * metadata->number_of_faces);
             }
diff --git a/camera/cameraserver/cameraserver.rc b/camera/cameraserver/cameraserver.rc
index a9aae0b..8f51458 100644
--- a/camera/cameraserver/cameraserver.rc
+++ b/camera/cameraserver/cameraserver.rc
@@ -3,5 +3,5 @@
     user cameraserver
     group audio camera input drmrpc
     ioprio rt 4
-    writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+    task_profiles CameraServiceCapacity MaxPerformance
     rlimit rtprio 10 10
diff --git a/include/private/media/AudioTrackShared.h b/include/private/media/AudioTrackShared.h
index 8aec80d..9cabd8b 100644
--- a/include/private/media/AudioTrackShared.h
+++ b/include/private/media/AudioTrackShared.h
@@ -614,6 +614,8 @@
     // and thus which resulted in an underrun.
     virtual uint32_t    getUnderrunFrames() const { return mCblk->u.mStreaming.mUnderrunFrames; }
 
+    virtual uint32_t    getUnderrunCount() const { return mCblk->u.mStreaming.mUnderrunCount; }
+
     // Return the playback speed and pitch read atomically. Not multi-thread safe on server side.
     AudioPlaybackRate getPlaybackRate();
 
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index e221b95..206f87f 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -17,6 +17,34 @@
                     "include-filter": "android.media.cts.DecodeEditEncodeTest"
                 }
             ]
+        },
+        {
+            "name": "GtsMediaTestCases",
+            "options" : [
+                {
+                    "include-annotation": "android.platform.test.annotations.Presubmit"
+                },
+                {
+                    "include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
+                }
+            ]
+        },
+        {
+            "name": "GtsExoPlayerTestCases",
+            "options" : [
+                {
+                    "include-annotation": "android.platform.test.annotations.SocPresubmit"
+                },
+                {
+                    "include-filter": "com.google.android.exoplayer.gts.DashTest#testWidevine23FpsH264Fixed"
+                }
+            ]
+        }
+    ],
+
+    "imports": [
+        {
+            "path": "frameworks/av/drm/mediadrm/plugins"
         }
     ],
 
diff --git a/media/audioserver/audioserver.rc b/media/audioserver/audioserver.rc
index 38c2750..6cb0c73 100644
--- a/media/audioserver/audioserver.rc
+++ b/media/audioserver/audioserver.rc
@@ -5,7 +5,7 @@
     group audio camera drmrpc media mediadrm net_bt net_bt_admin net_bw_acct wakelock
     capabilities BLOCK_SUSPEND
     ioprio rt 4
-    writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
+    task_profiles ProcessCapacityHigh HighPerformance
 
     onrestart setprop sys.audio.restart.hal 1
 
diff --git a/media/bufferpool/2.0/Accessor.cpp b/media/bufferpool/2.0/Accessor.cpp
index 57b4609..e05b12a 100644
--- a/media/bufferpool/2.0/Accessor.cpp
+++ b/media/bufferpool/2.0/Accessor.cpp
@@ -117,6 +117,10 @@
     Accessor::Impl::createInvalidator();
 }
 
+void Accessor::createEvictor() {
+    Accessor::Impl::createEvictor();
+}
+
 // Methods from ::android::hardware::media::bufferpool::V2_0::IAccessor follow.
 Return<void> Accessor::connect(
         const sp<::android::hardware::media::bufferpool::V2_0::IObserver>& observer,
diff --git a/media/bufferpool/2.0/Accessor.h b/media/bufferpool/2.0/Accessor.h
index 8d02519..8b43301 100644
--- a/media/bufferpool/2.0/Accessor.h
+++ b/media/bufferpool/2.0/Accessor.h
@@ -187,6 +187,8 @@
 
     static void createInvalidator();
 
+    static void createEvictor();
+
 private:
     class Impl;
     std::shared_ptr<Impl> mImpl;
diff --git a/media/bufferpool/2.0/AccessorImpl.cpp b/media/bufferpool/2.0/AccessorImpl.cpp
index 1947656..51d9a69 100644
--- a/media/bufferpool/2.0/AccessorImpl.cpp
+++ b/media/bufferpool/2.0/AccessorImpl.cpp
@@ -39,6 +39,9 @@
 
     static constexpr size_t kMinAllocBytesForEviction = 1024*1024*15;
     static constexpr size_t kMinBufferCountForEviction = 25;
+
+    static constexpr nsecs_t kEvictGranularityNs = 1000000000; // 1 sec
+    static constexpr nsecs_t kEvictDurationNs = 5000000000; // 5 secs
 }
 
 // Buffer structure in bufferpool process
@@ -135,11 +138,18 @@
     return false;
 }
 
-uint32_t Accessor::Impl::sSeqId = time(nullptr);
+#ifdef __ANDROID_VNDK__
+static constexpr uint32_t kSeqIdVndkBit = 1 << 31;
+#else
+static constexpr uint32_t kSeqIdVndkBit = 0;
+#endif
+
+static constexpr uint32_t kSeqIdMax = 0x7fffffff;
+uint32_t Accessor::Impl::sSeqId = time(nullptr) & kSeqIdMax;
 
 Accessor::Impl::Impl(
         const std::shared_ptr<BufferPoolAllocator> &allocator)
-        : mAllocator(allocator) {}
+        : mAllocator(allocator), mScheduleEvictTs(0) {}
 
 Accessor::Impl::~Impl() {
 }
@@ -157,7 +167,7 @@
         std::lock_guard<std::mutex> lock(mBufferPool.mMutex);
         if (newConnection) {
             int32_t pid = getpid();
-            ConnectionId id = (int64_t)pid << 32 | sSeqId;
+            ConnectionId id = (int64_t)pid << 32 | sSeqId | kSeqIdVndkBit;
             status = mBufferPool.mObserver.open(id, statusDescPtr);
             if (status == ResultStatus::OK) {
                 newConnection->initialize(accessor, id);
@@ -167,7 +177,7 @@
                 mBufferPool.mConnectionIds.insert(id);
                 mBufferPool.mInvalidationChannel.getDesc(invDescPtr);
                 mBufferPool.mInvalidation.onConnect(id, observer);
-                if (sSeqId == UINT32_MAX) {
+                if (sSeqId == kSeqIdMax) {
                    sSeqId = 0;
                 } else {
                     ++sSeqId;
@@ -177,6 +187,7 @@
         }
         mBufferPool.processStatusMessages();
         mBufferPool.cleanUp();
+        scheduleEvictIfNeeded();
     }
     return status;
 }
@@ -191,6 +202,7 @@
     // Since close# will be called after all works are finished, it is OK to
     // evict unused buffers.
     mBufferPool.cleanUp(true);
+    scheduleEvictIfNeeded();
     return ResultStatus::OK;
 }
 
@@ -217,6 +229,7 @@
         mBufferPool.handleOwnBuffer(connectionId, *bufferId);
     }
     mBufferPool.cleanUp();
+    scheduleEvictIfNeeded();
     return status;
 }
 
@@ -242,6 +255,7 @@
         }
     }
     mBufferPool.cleanUp();
+    scheduleEvictIfNeeded();
     return ResultStatus::CRITICAL_ERROR;
 }
 
@@ -884,6 +898,88 @@
     }
 }
 
+void Accessor::Impl::evictorThread(
+        std::map<const std::weak_ptr<Accessor::Impl>, nsecs_t, std::owner_less<>> &accessors,
+        std::mutex &mutex,
+        std::condition_variable &cv) {
+    std::list<const std::weak_ptr<Accessor::Impl>> evictList;
+    while (true) {
+        int expired = 0;
+        int evicted = 0;
+        {
+            nsecs_t now = systemTime();
+            std::unique_lock<std::mutex> lock(mutex);
+            if (accessors.size() == 0) {
+                cv.wait(lock);
+            }
+            auto it = accessors.begin();
+            while (it != accessors.end()) {
+                if (now > (it->second + kEvictDurationNs)) {
+                    ++expired;
+                    evictList.push_back(it->first);
+                    it = accessors.erase(it);
+                } else {
+                    ++it;
+                }
+            }
+        }
+        // evict idle accessors;
+        for (auto it = evictList.begin(); it != evictList.end(); ++it) {
+            const std::shared_ptr<Accessor::Impl> accessor = it->lock();
+            if (accessor) {
+                accessor->cleanUp(true);
+                ++evicted;
+            }
+        }
+        if (expired > 0) {
+            ALOGD("evictor expired: %d, evicted: %d", expired, evicted);
+        }
+        evictList.clear();
+        ::usleep(kEvictGranularityNs / 1000);
+    }
+}
+
+Accessor::Impl::AccessorEvictor::AccessorEvictor() {
+    std::thread evictor(
+            evictorThread,
+            std::ref(mAccessors),
+            std::ref(mMutex),
+            std::ref(mCv));
+    evictor.detach();
+}
+
+void Accessor::Impl::AccessorEvictor::addAccessor(
+        const std::weak_ptr<Accessor::Impl> &impl, nsecs_t ts) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    bool notify = mAccessors.empty();
+    auto it = mAccessors.find(impl);
+    if (it == mAccessors.end()) {
+        mAccessors.emplace(impl, ts);
+    } else {
+        it->second = ts;
+    }
+    if (notify) {
+        mCv.notify_one();
+    }
+}
+
+std::unique_ptr<Accessor::Impl::AccessorEvictor> Accessor::Impl::sEvictor;
+
+void Accessor::Impl::createEvictor() {
+    if (!sEvictor) {
+        sEvictor = std::make_unique<Accessor::Impl::AccessorEvictor>();
+    }
+}
+
+void Accessor::Impl::scheduleEvictIfNeeded() {
+    nsecs_t now = systemTime();
+
+    if (now > (mScheduleEvictTs + kEvictGranularityNs)) {
+        mScheduleEvictTs = now;
+        sEvictor->addAccessor(shared_from_this(), now);
+    }
+}
+
 }  // namespace implementation
 }  // namespace V2_0
 }  // namespace bufferpool
diff --git a/media/bufferpool/2.0/AccessorImpl.h b/media/bufferpool/2.0/AccessorImpl.h
index 9888be5..cd1b4d0 100644
--- a/media/bufferpool/2.0/AccessorImpl.h
+++ b/media/bufferpool/2.0/AccessorImpl.h
@@ -20,6 +20,7 @@
 #include <map>
 #include <set>
 #include <condition_variable>
+#include <utils/Timers.h>
 #include "Accessor.h"
 
 namespace android {
@@ -71,6 +72,8 @@
 
     static void createInvalidator();
 
+    static void createEvictor();
+
 private:
     // ConnectionId = pid : (timestamp_created + seqId)
     // in order to guarantee uniqueness for each connection
@@ -78,6 +81,8 @@
 
     const std::shared_ptr<BufferPoolAllocator> mAllocator;
 
+    nsecs_t mScheduleEvictTs;
+
     /**
      * Buffer pool implementation.
      *
@@ -389,6 +394,25 @@
         std::mutex &mutex,
         std::condition_variable &cv,
         bool &ready);
+
+    struct AccessorEvictor {
+        std::map<const std::weak_ptr<Accessor::Impl>, nsecs_t, std::owner_less<>> mAccessors;
+        std::mutex mMutex;
+        std::condition_variable mCv;
+
+        AccessorEvictor();
+        void addAccessor(const std::weak_ptr<Accessor::Impl> &impl, nsecs_t ts);
+    };
+
+    static std::unique_ptr<AccessorEvictor> sEvictor;
+
+    static void evictorThread(
+        std::map<const std::weak_ptr<Accessor::Impl>, nsecs_t, std::owner_less<>> &accessors,
+        std::mutex &mutex,
+        std::condition_variable &cv);
+
+    void scheduleEvictIfNeeded();
+
 };
 
 }  // namespace implementation
diff --git a/media/bufferpool/2.0/ClientManager.cpp b/media/bufferpool/2.0/ClientManager.cpp
index 87ee4e8..54a20b9 100644
--- a/media/bufferpool/2.0/ClientManager.cpp
+++ b/media/bufferpool/2.0/ClientManager.cpp
@@ -484,6 +484,7 @@
         sInstance = new ClientManager();
     }
     Accessor::createInvalidator();
+    Accessor::createEvictor();
     return sInstance;
 }
 
diff --git a/media/codec2/hidl/1.0/vts/functional/Android.bp b/media/codec2/hidl/1.0/vts/functional/Android.bp
index 5f0ba7d..cd3be81 100644
--- a/media/codec2/hidl/1.0/vts/functional/Android.bp
+++ b/media/codec2/hidl/1.0/vts/functional/Android.bp
@@ -89,6 +89,8 @@
         "res/bbb_av1_176_144.av1",
         "res/bbb_av1_640_360.info",
         "res/bbb_av1_176_144.info",
+        "res/bbb_vp9_704x480_280kbps_24fps_altref_2.vp9",
+        "res/bbb_vp9_704x480_280kbps_24fps_altref_2.info",
     ],
 }
 
diff --git a/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.cpp b/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.cpp
index 5d4b001..da8225c 100644
--- a/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/common/media_c2_hidl_test_common.cpp
@@ -115,7 +115,8 @@
             typedef std::unique_lock<std::mutex> ULock;
             ULock l(queueLock);
             workQueue.push_back(std::move(work));
-            if (!flushedIndices.empty()) {
+            if (!flushedIndices.empty() &&
+                (frameIndexIt != flushedIndices.end())) {
                 flushedIndices.erase(frameIndexIt);
             }
             queueCondition.notify_all();
diff --git a/media/codec2/hidl/1.0/vts/functional/component/VtsHalMediaC2V1_0TargetComponentTest.cpp b/media/codec2/hidl/1.0/vts/functional/component/VtsHalMediaC2V1_0TargetComponentTest.cpp
index 119ce14..6122225 100644
--- a/media/codec2/hidl/1.0/vts/functional/component/VtsHalMediaC2V1_0TargetComponentTest.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/component/VtsHalMediaC2V1_0TargetComponentTest.cpp
@@ -330,7 +330,7 @@
 TEST_P(Codec2ComponentInputTests, InputBufferTest) {
     description("Tests for different inputs");
 
-    uint32_t flags = std::stol(std::get<2>(GetParam()));
+    uint32_t flags = std::stoul(std::get<2>(GetParam()));
     bool isNullBuffer = !std::get<3>(GetParam()).compare("true");
     if (isNullBuffer)
         ALOGD("Testing for null input buffer with flag : %u", flags);
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_704x480_280kbps_24fps_altref_2.info b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_704x480_280kbps_24fps_altref_2.info
new file mode 100644
index 0000000..9ea1ffa
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_704x480_280kbps_24fps_altref_2.info
@@ -0,0 +1,352 @@
+73 0 0
+159 16 41000
+103 16 41000
+34 0 41000
+30 0 83000
+30 0 125000
+30 0 166000
+1 0 208000
+105 0 250000
+54 0 291000
+65 0 333000
+1 0 375000
+239 0 416000
+1373 16 458000
+263 16 458000
+133 0 458000
+109 0 500000
+127 0 541000
+1 0 583000
+134 0 625000
+159 0 666000
+131 0 708000
+1 0 750000
+4420 16 791000
+349 16 791000
+151 0 791000
+1 0 833000
+120 0 875000
+1 0 916000
+3203 16 958000
+433 16 958000
+125 0 958000
+1 0 1000000
+107 0 1041000
+1 0 1083000
+3832 16 1125000
+541 16 1125000
+116 0 1125000
+1 0 1166000
+115 0 1208000
+1 0 1250000
+3777 16 1291000
+495 16 1291000
+109 0 1291000
+1 0 1333000
+79 0 1375000
+1 0 1416000
+3906 16 1458000
+539 16 1458000
+149 0 1458000
+1 0 1500000
+112 0 1541000
+1 0 1583000
+5495 16 1625000
+594 16 1625000
+392 0 1625000
+235 0 1666000
+170 0 1708000
+1 0 1750000
+384 0 1791000
+378 0 1833000
+120 0 1875000
+1 0 1916000
+16843 16 1958000
+1951 16 1958000
+774 0 1958000
+831 0 2000000
+560 0 2041000
+489 0 2083000
+345 0 2125000
+243 0 2166000
+260 0 2208000
+1 0 2250000
+498 0 2291000
+463 0 2333000
+414 0 2375000
+341 0 2416000
+262 0 2458000
+194 0 2500000
+85 0 2541000
+1 0 2583000
+11253 16 2625000
+1561 16 2625000
+250 0 2625000
+402 0 2666000
+454 0 2708000
+350 0 2750000
+316 0 2791000
+164 0 2833000
+108 0 2875000
+1 0 2916000
+246 0 2958000
+586 0 3000000
+493 0 3041000
+363 0 3083000
+325 0 3125000
+170 0 3166000
+78 0 3208000
+40 0 3250000
+15630 16 3291000
+2228 16 3291000
+346 0 3291000
+707 0 3333000
+685 0 3375000
+582 0 3416000
+473 0 3458000
+249 0 3500000
+177 0 3541000
+1 0 3583000
+541 0 3625000
+834 0 3666000
+614 0 3708000
+473 0 3750000
+365 0 3791000
+211 0 3833000
+91 0 3875000
+1 0 3916000
+8384 16 3958000
+1138 16 3958000
+256 0 3958000
+377 0 4000000
+316 0 4041000
+267 0 4083000
+119 0 4125000
+1 0 4166000
+319 0 4208000
+390 0 4250000
+243 0 4291000
+203 0 4333000
+110 0 4375000
+1 0 4416000
+11302 16 4458000
+1527 16 4458000
+408 0 4458000
+507 0 4500000
+350 0 4541000
+377 0 4583000
+239 0 4625000
+125 0 4666000
+1 0 4708000
+351 0 4750000
+469 0 4791000
+288 0 4833000
+244 0 4875000
+224 0 4916000
+108 0 4958000
+1 0 5000000
+6016 16 5041000
+561 16 5041000
+235 0 5041000
+213 0 5083000
+118 0 5125000
+1 0 5166000
+299 0 5208000
+213 0 5250000
+129 0 5291000
+1 0 5333000
+7029 16 5375000
+728 16 5375000
+339 0 5375000
+287 0 5416000
+225 0 5458000
+147 0 5500000
+1 0 5541000
+270 0 5583000
+217 0 5625000
+138 0 5666000
+46 0 5708000
+32861 0 5750000
+16095 16 5791000
+2033 16 5791000
+318 0 5791000
+443 0 5833000
+361 0 5875000
+313 0 5916000
+274 0 5958000
+210 0 6000000
+134 0 6041000
+1 0 6083000
+295 0 6125000
+415 0 6166000
+330 0 6208000
+264 0 6250000
+242 0 6291000
+166 0 6333000
+116 0 6375000
+1 0 6416000
+9488 16 6458000
+1466 16 6458000
+378 0 6458000
+419 0 6500000
+335 0 6541000
+290 0 6583000
+315 0 6625000
+199 0 6666000
+131 0 6708000
+1 0 6750000
+342 0 6791000
+421 0 6833000
+306 0 6875000
+320 0 6916000
+245 0 6958000
+205 0 7000000
+143 0 7041000
+1 0 7083000
+16554 16 7125000
+1744 16 7125000
+482 0 7125000
+365 0 7166000
+352 0 7208000
+308 0 7250000
+295 0 7291000
+199 0 7333000
+149 0 7375000
+1 0 7416000
+360 0 7458000
+428 0 7500000
+377 0 7541000
+347 0 7583000
+270 0 7625000
+187 0 7666000
+130 0 7708000
+39 0 7750000
+14637 16 7791000
+1832 16 7791000
+422 0 7791000
+466 0 7833000
+375 0 7875000
+405 0 7916000
+352 0 7958000
+275 0 8000000
+173 0 8041000
+1 0 8083000
+484 0 8125000
+516 0 8166000
+497 0 8208000
+452 0 8250000
+428 0 8291000
+293 0 8333000
+190 0 8375000
+1 0 8416000
+11534 16 8458000
+1655 16 8458000
+446 0 8458000
+531 0 8500000
+465 0 8541000
+495 0 8583000
+402 0 8625000
+330 0 8666000
+227 0 8708000
+1 0 8750000
+568 0 8791000
+694 0 8833000
+382 0 8875000
+422 0 8916000
+280 0 8958000
+305 0 9000000
+203 0 9041000
+1 0 9083000
+17067 16 9125000
+1521 16 9125000
+428 0 9125000
+434 0 9166000
+359 0 9208000
+368 0 9250000
+240 0 9291000
+215 0 9333000
+211 0 9375000
+1 0 9416000
+346 0 9458000
+533 0 9500000
+391 0 9541000
+313 0 9583000
+326 0 9625000
+211 0 9666000
+233 0 9708000
+35 0 9750000
+23743 16 9791000
+1968 16 9791000
+277 0 9791000
+276 0 9833000
+285 0 9875000
+232 0 9916000
+179 0 9958000
+203 0 10000000
+121 0 10041000
+1 0 10083000
+318 0 10125000
+391 0 10166000
+362 0 10208000
+291 0 10250000
+235 0 10291000
+187 0 10333000
+117 0 10375000
+43 0 10416000
+12465 16 10458000
+1607 16 10458000
+280 0 10458000
+295 0 10500000
+244 0 10541000
+211 0 10583000
+171 0 10625000
+159 0 10666000
+106 0 10708000
+1 0 10750000
+216 0 10791000
+195 0 10833000
+180 0 10875000
+164 0 10916000
+156 0 10958000
+96 0 11000000
+1 0 11041000
+54547 0 11083000
+2920 16 11125000
+153 16 11125000
+117 0 11125000
+83 0 11166000
+86 0 11208000
+1 0 11250000
+92 0 11291000
+93 0 11333000
+80 0 11375000
+1 0 11416000
+1314 16 11458000
+311 16 11458000
+79 0 11458000
+65 0 11500000
+74 0 11541000
+61 0 11583000
+1 0 11625000
+65 0 11666000
+64 0 11708000
+49 0 11750000
+51 0 11791000
+1 0 11833000
+19309 0 11875000
+3848 16 11916000
+771 16 11916000
+167 0 11916000
+202 0 11958000
+190 0 12000000
+173 0 12041000
+128 0 12083000
+79 0 12125000
+1 0 12166000
+155 0 12208000
+211 0 12250000
+192 0 12291000
+131 0 12333000
+91 0 12375000
+128 0 12416000
+1 0 12458000
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_704x480_280kbps_24fps_altref_2.vp9 b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_704x480_280kbps_24fps_altref_2.vp9
new file mode 100644
index 0000000..00c7dec
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_704x480_280kbps_24fps_altref_2.vp9
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp
index ec3fd7b..f216429 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp
@@ -267,7 +267,7 @@
 }
 
 // number of elementary streams per component
-#define STREAM_COUNT 2
+#define STREAM_COUNT 3
 // LookUpTable of clips and metadata for component testing
 void GetURLForComponent(Codec2VideoDecHidlTest::standardComp comp, char* mURL, char* info,
                         size_t streamIndex = 1) {
@@ -280,29 +280,31 @@
 
     static const CompToURL kCompToURL[] = {
             {Codec2VideoDecHidlTest::standardComp::avc,
-             {"bbb_avc_176x144_300kbps_60fps.h264", "bbb_avc_640x360_768kbps_30fps.h264"},
-             {"bbb_avc_176x144_300kbps_60fps.info", "bbb_avc_640x360_768kbps_30fps.info"}},
+             {"bbb_avc_176x144_300kbps_60fps.h264", "bbb_avc_640x360_768kbps_30fps.h264", ""},
+             {"bbb_avc_176x144_300kbps_60fps.info", "bbb_avc_640x360_768kbps_30fps.info", ""}},
             {Codec2VideoDecHidlTest::standardComp::hevc,
-             {"bbb_hevc_176x144_176kbps_60fps.hevc", "bbb_hevc_640x360_1600kbps_30fps.hevc"},
-             {"bbb_hevc_176x144_176kbps_60fps.info", "bbb_hevc_640x360_1600kbps_30fps.info"}},
+             {"bbb_hevc_176x144_176kbps_60fps.hevc", "bbb_hevc_640x360_1600kbps_30fps.hevc", ""},
+             {"bbb_hevc_176x144_176kbps_60fps.info", "bbb_hevc_640x360_1600kbps_30fps.info", ""}},
             {Codec2VideoDecHidlTest::standardComp::mpeg2,
-             {"bbb_mpeg2_176x144_105kbps_25fps.m2v", "bbb_mpeg2_352x288_1mbps_60fps.m2v"},
-             {"bbb_mpeg2_176x144_105kbps_25fps.info", "bbb_mpeg2_352x288_1mbps_60fps.info"}},
+             {"bbb_mpeg2_176x144_105kbps_25fps.m2v", "bbb_mpeg2_352x288_1mbps_60fps.m2v", ""},
+             {"bbb_mpeg2_176x144_105kbps_25fps.info", "bbb_mpeg2_352x288_1mbps_60fps.info", ""}},
             {Codec2VideoDecHidlTest::standardComp::h263,
-             {"", "bbb_h263_352x288_300kbps_12fps.h263"},
-             {"", "bbb_h263_352x288_300kbps_12fps.info"}},
+             {"", "bbb_h263_352x288_300kbps_12fps.h263", ""},
+             {"", "bbb_h263_352x288_300kbps_12fps.info", ""}},
             {Codec2VideoDecHidlTest::standardComp::mpeg4,
-             {"", "bbb_mpeg4_352x288_512kbps_30fps.m4v"},
-             {"", "bbb_mpeg4_352x288_512kbps_30fps.info"}},
+             {"", "bbb_mpeg4_352x288_512kbps_30fps.m4v", ""},
+             {"", "bbb_mpeg4_352x288_512kbps_30fps.info", ""}},
             {Codec2VideoDecHidlTest::standardComp::vp8,
-             {"bbb_vp8_176x144_240kbps_60fps.vp8", "bbb_vp8_640x360_2mbps_30fps.vp8"},
-             {"bbb_vp8_176x144_240kbps_60fps.info", "bbb_vp8_640x360_2mbps_30fps.info"}},
+             {"bbb_vp8_176x144_240kbps_60fps.vp8", "bbb_vp8_640x360_2mbps_30fps.vp8", ""},
+             {"bbb_vp8_176x144_240kbps_60fps.info", "bbb_vp8_640x360_2mbps_30fps.info", ""}},
             {Codec2VideoDecHidlTest::standardComp::vp9,
-             {"bbb_vp9_176x144_285kbps_60fps.vp9", "bbb_vp9_640x360_1600kbps_30fps.vp9"},
-             {"bbb_vp9_176x144_285kbps_60fps.info", "bbb_vp9_640x360_1600kbps_30fps.info"}},
+             {"bbb_vp9_176x144_285kbps_60fps.vp9", "bbb_vp9_640x360_1600kbps_30fps.vp9",
+              "bbb_vp9_704x480_280kbps_24fps_altref_2.vp9"},
+             {"bbb_vp9_176x144_285kbps_60fps.info", "bbb_vp9_640x360_1600kbps_30fps.info",
+              "bbb_vp9_704x480_280kbps_24fps_altref_2.info"}},
             {Codec2VideoDecHidlTest::standardComp::av1,
-             {"bbb_av1_640_360.av1", "bbb_av1_176_144.av1"},
-             {"bbb_av1_640_360.info", "bbb_av1_176_144.info"}},
+             {"bbb_av1_640_360.av1", "bbb_av1_176_144.av1", ""},
+             {"bbb_av1_640_360.info", "bbb_av1_176_144.info", ""}},
     };
 
     for (size_t i = 0; i < sizeof(kCompToURL) / sizeof(kCompToURL[0]); ++i) {
@@ -481,7 +483,9 @@
         eleInfo >> flags;
         eleInfo >> timestamp;
         bool codecConfig = flags ? ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0 : 0;
-        if (mTimestampDevTest && !codecConfig) mTimestampUslist.push_back(timestamp);
+        bool nonDisplayFrame = ((flags & FLAG_NON_DISPLAY_FRAME) != 0);
+        if (mTimestampDevTest && !codecConfig && !nonDisplayFrame)
+            mTimestampUslist.push_back(timestamp);
         Info.push_back({bytesCount, flags, timestamp});
     }
     eleInfo.close();
@@ -545,6 +549,10 @@
         strcpy(mURL, sResourceDir.c_str());
         strcpy(info, sResourceDir.c_str());
         GetURLForComponent(mCompName, mURL, info, i % STREAM_COUNT);
+        if (!(strcmp(mURL, sResourceDir.c_str())) || !(strcmp(info, sResourceDir.c_str()))) {
+            ALOGV("Stream not available, skipping this index");
+            continue;
+        }
 
         eleInfo.open(info);
         ASSERT_EQ(eleInfo.is_open(), true) << mURL << " - file not found";
@@ -560,10 +568,12 @@
             Info.push_back({bytesCount, flags, timestamp});
             bool codecConfig =
                     flags ? ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0 : 0;
+            bool nonDisplayFrame = ((flags & FLAG_NON_DISPLAY_FRAME) != 0);
 
             {
                 ULock l(mQueueLock);
-                if (mTimestampDevTest && !codecConfig) mTimestampUslist.push_back(timestamp);
+                if (mTimestampDevTest && !codecConfig && !nonDisplayFrame)
+                    mTimestampUslist.push_back(timestamp);
             }
             if (timestampMax < timestamp) timestampMax = timestamp;
         }
@@ -903,6 +913,10 @@
                 std::make_tuple(std::get<0>(params), std::get<1>(params), "1", "false"));
         kDecodeTestParameters.push_back(
                 std::make_tuple(std::get<0>(params), std::get<1>(params), "1", "true"));
+        kDecodeTestParameters.push_back(
+                std::make_tuple(std::get<0>(params), std::get<1>(params), "2", "false"));
+        kDecodeTestParameters.push_back(
+                std::make_tuple(std::get<0>(params), std::get<1>(params), "2", "true"));
     }
 
     // Set the resource directory based on command line args.
diff --git a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml
index 8761797..63e7a69 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml
+++ b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml
@@ -49,6 +49,8 @@
         <option name="push-file" key="bbb_av1_176_144.av1" value="/data/local/tmp/media/bbb_av1_176_144.av1" />
         <option name="push-file" key="bbb_av1_640_360.info" value="/data/local/tmp/media/bbb_av1_640_360.info" />
         <option name="push-file" key="bbb_av1_176_144.info" value="/data/local/tmp/media/bbb_av1_176_144.info" />
+        <option name="push-file" key="bbb_vp9_704x480_280kbps_24fps_altref_2.vp9" value="/data/local/tmp/media/bbb_vp9_704x480_280kbps_24fps_altref_2.vp9" />
+        <option name="push-file" key="bbb_vp9_704x480_280kbps_24fps_altref_2.info" value="/data/local/tmp/media/bbb_vp9_704x480_280kbps_24fps_altref_2.info" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.GTest" >
diff --git a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoEncTest.cpp b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoEncTest.cpp
index be97383..ecc56f5 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoEncTest.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoEncTest.cpp
@@ -41,7 +41,8 @@
         : C2Buffer({block->share(C2Rect(block->width(), block->height()), ::C2Fence())}) {}
 };
 
-static std::vector<std::tuple<std::string, std::string, std::string>> kEncodeTestParameters;
+static std::vector<std::tuple<std::string, std::string, std::string, std::string>>
+        kEncodeTestParameters;
 static std::vector<std::tuple<std::string, std::string, std::string, std::string>>
         kEncodeResolutionTestParameters;
 
@@ -102,6 +103,7 @@
         mFramesReceived = 0;
         mFailedWorkReceived = 0;
         mTimestampUs = 0u;
+        mOutputSize = 0u;
         mTimestampDevTest = false;
         if (mCompName == unknown_comp) mDisableTest = true;
         if (mDisableTest) std::cout << "[   WARN   ] Test Disabled \n";
@@ -159,6 +161,16 @@
                 }
 
                 if (work->result != C2_OK) mFailedWorkReceived++;
+                if (!work->worklets.front()->output.buffers.empty()) {
+                    mOutputSize += work->worklets.front()
+                                           ->output.buffers[0]
+                                           ->data()
+                                           .linearBlocks()
+                                           .front()
+                                           .map()
+                                           .get()
+                                           .capacity();
+                }
                 workDone(mComponent, work, mFlushedIndices, mQueueLock, mQueueCondition, mWorkQueue,
                          mEos, mCsd, mFramesReceived);
             }
@@ -186,6 +198,7 @@
     uint32_t mFramesReceived;
     uint32_t mFailedWorkReceived;
     uint64_t mTimestampUs;
+    uint64_t mOutputSize;
 
     std::list<uint64_t> mTimestampUslist;
     std::list<uint64_t> mFlushedIndices;
@@ -281,7 +294,6 @@
     uint32_t maxRetry = 0;
     int bytesCount = nWidth * nHeight * 3 >> 1;
     int32_t timestampIncr = ENCODER_TIMESTAMP_INCREMENT;
-    uint64_t timestamp = 0;
     c2_status_t err = C2_OK;
     while (1) {
         if (nFrames == 0) break;
@@ -308,7 +320,7 @@
         }
 
         work->input.flags = (C2FrameData::flags_t)flags;
-        work->input.ordinal.timestamp = timestamp;
+        work->input.ordinal.timestamp = frameID * timestampIncr;
         work->input.ordinal.frameIndex = frameID;
         {
             ULock l(queueLock);
@@ -361,7 +373,6 @@
         ASSERT_EQ(component->queue(&items), C2_OK);
         ALOGV("Frame #%d size = %d queued", frameID, bytesCount);
         nFrames--;
-        timestamp += timestampIncr;
         frameID++;
         maxRetry = 0;
     }
@@ -376,7 +387,8 @@
 
 class Codec2VideoEncEncodeTest
     : public Codec2VideoEncHidlTestBase,
-      public ::testing::WithParamInterface<std::tuple<std::string, std::string, std::string>> {
+      public ::testing::WithParamInterface<
+              std::tuple<std::string, std::string, std::string, std::string>> {
     void getParams() {
         mInstanceName = std::get<0>(GetParam());
         mComponentName = std::get<1>(GetParam());
@@ -391,6 +403,8 @@
     int32_t nWidth = ENC_DEFAULT_FRAME_WIDTH;
     int32_t nHeight = ENC_DEFAULT_FRAME_HEIGHT;
     bool signalEOS = !std::get<2>(GetParam()).compare("true");
+    // Send an empty frame to receive CSD data from encoder.
+    bool sendEmptyFirstFrame = !std::get<3>(GetParam()).compare("true");
 
     strcpy(mURL, sResourceDir.c_str());
     GetURLForComponent(mURL);
@@ -404,24 +418,32 @@
     mTimestampDevTest = true;
     mFlushedIndices.clear();
     mTimestampUslist.clear();
-    uint32_t inputFrames = ENC_NUM_FRAMES;
+    int32_t inputFrames = ENC_NUM_FRAMES + (sendEmptyFirstFrame ? 1 : 0);
     uint32_t timestamp = 0;
+
     // Add input timestamp to timestampUslist
     while (inputFrames) {
         if (mTimestampDevTest) mTimestampUslist.push_back(timestamp);
         timestamp += ENCODER_TIMESTAMP_INCREMENT;
         inputFrames--;
     }
+
     if (!setupConfigParam(nWidth, nHeight)) {
         std::cout << "[   WARN   ] Test Skipped \n";
         return;
     }
     ASSERT_EQ(mComponent->start(), C2_OK);
+
+    if (sendEmptyFirstFrame) {
+        ASSERT_NO_FATAL_FAILURE(testInputBuffer(mComponent, mQueueLock, mWorkQueue, 0, false));
+        inputFrames += 1;
+    }
     ASSERT_NO_FATAL_FAILURE(encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
-                                          mFlushedIndices, mGraphicPool, eleStream, mDisableTest, 0,
-                                          ENC_NUM_FRAMES, nWidth, nHeight, false, signalEOS));
+                                          mFlushedIndices, mGraphicPool, eleStream, mDisableTest,
+                                          inputFrames, ENC_NUM_FRAMES, nWidth, nHeight, false,
+                                          signalEOS));
     // mDisableTest will be set if buffer was not fetched properly.
-    // This may happen when resolution is not proper but config suceeded
+    // This may happen when resolution is not proper but config succeeded
     // In this cases, we skip encoding the input stream
     if (mDisableTest) {
         std::cout << "[   WARN   ] Test Disabled \n";
@@ -430,7 +452,7 @@
     }
 
     // If EOS is not sent, sending empty input with EOS flag
-    inputFrames = ENC_NUM_FRAMES;
+    inputFrames += ENC_NUM_FRAMES;
     if (!signalEOS) {
         ASSERT_NO_FATAL_FAILURE(waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue, 1));
         ASSERT_NO_FATAL_FAILURE(testInputBuffer(mComponent, mQueueLock, mWorkQueue,
@@ -531,7 +553,7 @@
                                           mFlushedIndices, mGraphicPool, eleStream, mDisableTest, 0,
                                           numFramesFlushed, nWidth, nHeight));
     // mDisableTest will be set if buffer was not fetched properly.
-    // This may happen when resolution is not proper but config suceeded
+    // This may happen when resolution is not proper but config succeeded
     // In this cases, we skip encoding the input stream
     if (mDisableTest) {
         std::cout << "[   WARN   ] Test Disabled \n";
@@ -568,7 +590,7 @@
                                           nHeight, true));
     eleStream.close();
     // mDisableTest will be set if buffer was not fetched properly.
-    // This may happen when resolution is not proper but config suceeded
+    // This may happen when resolution is not proper but config succeeded
     // In this cases, we skip encoding the input stream
     if (mDisableTest) {
         std::cout << "[   WARN   ] Test Disabled \n";
@@ -677,7 +699,7 @@
                                           MAX_INPUT_BUFFERS, nWidth, nHeight, false, true));
 
     // mDisableTest will be set if buffer was not fetched properly.
-    // This may happen when resolution is not proper but config suceeded
+    // This may happen when resolution is not proper but config succeeded
     // In this cases, we skip encoding the input stream
     if (mDisableTest) {
         std::cout << "[   WARN   ] Test Disabled \n";
@@ -703,15 +725,105 @@
 INSTANTIATE_TEST_SUITE_P(EncodeTestwithEOS, Codec2VideoEncEncodeTest,
                          ::testing::ValuesIn(kEncodeTestParameters));
 
+TEST_P(Codec2VideoEncHidlTest, AdaptiveBitrateTest) {
+    description("Encodes input file for different bitrates");
+    if (mDisableTest) GTEST_SKIP() << "Test is disabled";
+
+    char mURL[512];
+
+    strcpy(mURL, sResourceDir.c_str());
+    GetURLForComponent(mURL);
+
+    std::ifstream eleStream;
+    eleStream.open(mURL, std::ifstream::binary);
+    ASSERT_EQ(eleStream.is_open(), true) << mURL << " file not found";
+    ALOGV("mURL : %s", mURL);
+
+    mFlushedIndices.clear();
+
+    int32_t nWidth = ENC_DEFAULT_FRAME_WIDTH;
+    int32_t nHeight = ENC_DEFAULT_FRAME_HEIGHT;
+    if (!setupConfigParam(nWidth, nHeight)) {
+        std::cout << "[   WARN   ] Test Skipped \n";
+        return;
+    }
+    ASSERT_EQ(mComponent->start(), C2_OK);
+
+    uint64_t prevOutputSize = 0u;
+    uint32_t bitrateValues[] = {100000, 64000, 200000};
+    uint32_t prevBitrate = 0;
+    int32_t inputFrameId = 0;
+
+    for (uint32_t curBitrate : bitrateValues) {
+        // Configuring bitrate
+        std::vector<std::unique_ptr<C2SettingResult>> failures;
+        C2StreamBitrateInfo::output bitrate(0u, curBitrate);
+        std::vector<C2Param*> configParam{&bitrate};
+        c2_status_t status = mComponent->config(configParam, C2_DONT_BLOCK, &failures);
+        if (status != C2_OK && failures.size() != 0u) {
+            ALOGW("BitRate Config failed, using previous bitrate");
+        }
+
+        ASSERT_NO_FATAL_FAILURE(encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+                                              mFlushedIndices, mGraphicPool, eleStream,
+                                              mDisableTest, inputFrameId, ENC_NUM_FRAMES, nWidth,
+                                              nHeight, false, false));
+        // mDisableTest will be set if buffer was not fetched properly.
+        // This may happen when resolution is not proper but config succeeded
+        // In this cases, we skip encoding the input stream
+        if (mDisableTest) {
+            std::cout << "[   WARN   ] Test Disabled \n";
+            ASSERT_EQ(mComponent->stop(), C2_OK);
+            return;
+        }
+        inputFrameId += ENC_NUM_FRAMES;
+        // blocking call to ensures application to Wait till all the inputs are
+        // consumed
+        ALOGD("Waiting for input consumption");
+        waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+
+        // Change in bitrate may result in different outputSize
+        if (prevBitrate >= curBitrate) {
+            EXPECT_LE(mOutputSize, prevOutputSize);
+        } else {
+            EXPECT_GE(mOutputSize, prevOutputSize);
+        }
+        prevBitrate = curBitrate;
+        prevOutputSize = mOutputSize;
+        // Reset the file pointer and output size
+        mOutputSize = 0;
+        eleStream.seekg(0, eleStream.beg);
+    }
+
+    // Sending empty input with EOS flag
+    ASSERT_NO_FATAL_FAILURE(testInputBuffer(mComponent, mQueueLock, mWorkQueue,
+                                            C2FrameData::FLAG_END_OF_STREAM, false));
+    inputFrameId += 1;
+    waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+
+    eleStream.close();
+    if (mFramesReceived != inputFrameId) {
+        ALOGE("Input buffer count and Output buffer count mismatch");
+        ALOGE("framesReceived : %d inputFrames : %d", mFramesReceived, inputFrameId);
+        ASSERT_TRUE(false);
+    }
+
+    ASSERT_EQ(mComponent->stop(), C2_OK);
+}
+
 }  // anonymous namespace
 
 int main(int argc, char** argv) {
     kTestParameters = getTestParameters(C2Component::DOMAIN_VIDEO, C2Component::KIND_ENCODER);
     for (auto params : kTestParameters) {
         kEncodeTestParameters.push_back(
-                std::make_tuple(std::get<0>(params), std::get<1>(params), "true"));
+                std::make_tuple(std::get<0>(params), std::get<1>(params), "true", "true"));
         kEncodeTestParameters.push_back(
-                std::make_tuple(std::get<0>(params), std::get<1>(params), "false"));
+                std::make_tuple(std::get<0>(params), std::get<1>(params), "true", "false"));
+        kEncodeTestParameters.push_back(
+                std::make_tuple(std::get<0>(params), std::get<1>(params), "false", "true"));
+        kEncodeTestParameters.push_back(
+                std::make_tuple(std::get<0>(params), std::get<1>(params), "false", "false"));
 
         kEncodeResolutionTestParameters.push_back(
                 std::make_tuple(std::get<0>(params), std::get<1>(params), "52", "18"));
@@ -721,6 +833,10 @@
                 std::make_tuple(std::get<0>(params), std::get<1>(params), "484", "362"));
         kEncodeResolutionTestParameters.push_back(
                 std::make_tuple(std::get<0>(params), std::get<1>(params), "244", "488"));
+        kEncodeResolutionTestParameters.push_back(
+                std::make_tuple(std::get<0>(params), std::get<1>(params), "852", "608"));
+        kEncodeResolutionTestParameters.push_back(
+                std::make_tuple(std::get<0>(params), std::get<1>(params), "1400", "442"));
     }
 
     // Set the resource directory based on command line args.
@@ -734,4 +850,4 @@
 
     ::testing::InitGoogleTest(&argc, argv);
     return RUN_ALL_TESTS();
-}
\ No newline at end of file
+}
diff --git a/media/codec2/hidl/1.0/vts/functional/video/media_c2_video_hidl_test_common.h b/media/codec2/hidl/1.0/vts/functional/video/media_c2_video_hidl_test_common.h
index d3a693b..9c1a5cb 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/media_c2_video_hidl_test_common.h
+++ b/media/codec2/hidl/1.0/vts/functional/video/media_c2_video_hidl_test_common.h
@@ -22,6 +22,7 @@
 #define ENC_DEFAULT_FRAME_WIDTH 352
 #define ENC_DEFAULT_FRAME_HEIGHT 288
 #define MAX_ITERATIONS 128
+#define FLAG_NON_DISPLAY_FRAME (1 << 4)
 
 #define ALIGN(_sz, _align) ((_sz + (_align - 1)) & ~(_align - 1))
 
diff --git a/media/codec2/sfplugin/CCodec.cpp b/media/codec2/sfplugin/CCodec.cpp
index ab762d9..d4c723c 100644
--- a/media/codec2/sfplugin/CCodec.cpp
+++ b/media/codec2/sfplugin/CCodec.cpp
@@ -1406,6 +1406,9 @@
         // TODO: convert err into status_t
         mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
     }
+    // Assure buffers are not owned when stop() was called without flush().
+    std::list<std::unique_ptr<C2Work>> flushedWork;
+    mChannel->flush(flushedWork);
 
     {
         Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.cpp b/media/codec2/sfplugin/CCodecBufferChannel.cpp
index e2be991..3773528 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.cpp
+++ b/media/codec2/sfplugin/CCodecBufferChannel.cpp
@@ -44,6 +44,7 @@
 #include <media/stagefright/MediaCodecConstants.h>
 #include <media/stagefright/SkipCutBuffer.h>
 #include <media/MediaCodecBuffer.h>
+#include <mediadrm/ICrypto.h>
 #include <system/window.h>
 
 #include "CCodecBufferChannel.h"
@@ -127,6 +128,97 @@
     count->value = -1;
 }
 
+// CCodecBufferChannel::ReorderStash
+
+CCodecBufferChannel::ReorderStash::ReorderStash() {
+    clear();
+}
+
+void CCodecBufferChannel::ReorderStash::clear() {
+    mPending.clear();
+    mStash.clear();
+    mDepth = 0;
+    mKey = C2Config::ORDINAL;
+}
+
+void CCodecBufferChannel::ReorderStash::flush() {
+    mPending.clear();
+    mStash.clear();
+}
+
+void CCodecBufferChannel::ReorderStash::setDepth(uint32_t depth) {
+    mPending.splice(mPending.end(), mStash);
+    mDepth = depth;
+}
+
+void CCodecBufferChannel::ReorderStash::setKey(C2Config::ordinal_key_t key) {
+    mPending.splice(mPending.end(), mStash);
+    mKey = key;
+}
+
+bool CCodecBufferChannel::ReorderStash::pop(Entry *entry) {
+    if (mPending.empty()) {
+        return false;
+    }
+    entry->buffer     = mPending.front().buffer;
+    entry->timestamp  = mPending.front().timestamp;
+    entry->flags      = mPending.front().flags;
+    entry->ordinal    = mPending.front().ordinal;
+    mPending.pop_front();
+    return true;
+}
+
+void CCodecBufferChannel::ReorderStash::emplace(
+        const std::shared_ptr<C2Buffer> &buffer,
+        int64_t timestamp,
+        int32_t flags,
+        const C2WorkOrdinalStruct &ordinal) {
+    bool eos = flags & MediaCodec::BUFFER_FLAG_EOS;
+    if (!buffer && eos) {
+        // TRICKY: we may be violating ordering of the stash here. Because we
+        // don't expect any more emplace() calls after this, the ordering should
+        // not matter.
+        mStash.emplace_back(buffer, timestamp, flags, ordinal);
+    } else {
+        flags = flags & ~MediaCodec::BUFFER_FLAG_EOS;
+        auto it = mStash.begin();
+        for (; it != mStash.end(); ++it) {
+            if (less(ordinal, it->ordinal)) {
+                break;
+            }
+        }
+        mStash.emplace(it, buffer, timestamp, flags, ordinal);
+        if (eos) {
+            mStash.back().flags = mStash.back().flags | MediaCodec::BUFFER_FLAG_EOS;
+        }
+    }
+    while (!mStash.empty() && mStash.size() > mDepth) {
+        mPending.push_back(mStash.front());
+        mStash.pop_front();
+    }
+}
+
+void CCodecBufferChannel::ReorderStash::defer(
+        const CCodecBufferChannel::ReorderStash::Entry &entry) {
+    mPending.push_front(entry);
+}
+
+bool CCodecBufferChannel::ReorderStash::hasPending() const {
+    return !mPending.empty();
+}
+
+bool CCodecBufferChannel::ReorderStash::less(
+        const C2WorkOrdinalStruct &o1, const C2WorkOrdinalStruct &o2) {
+    switch (mKey) {
+        case C2Config::ORDINAL:   return o1.frameIndex < o2.frameIndex;
+        case C2Config::TIMESTAMP: return o1.timestamp < o2.timestamp;
+        case C2Config::CUSTOM:    return o1.customOrdinal < o2.customOrdinal;
+        default:
+            ALOGD("Unrecognized key; default to timestamp");
+            return o1.frameIndex < o2.frameIndex;
+    }
+}
+
 // Input
 
 CCodecBufferChannel::Input::Input() : extraBuffers("extra") {}
@@ -616,7 +708,7 @@
 
 void CCodecBufferChannel::feedInputBufferIfAvailableInternal() {
     if (mInputMetEos ||
-           mOutput.lock()->buffers->hasPending() ||
+           mReorderStash.lock()->hasPending() ||
            mPipelineWatcher.lock()->pipelineFull()) {
         return;
     } else {
@@ -889,6 +981,17 @@
         return UNKNOWN_ERROR;
     }
 
+    {
+        Mutexed<ReorderStash>::Locked reorder(mReorderStash);
+        reorder->clear();
+        if (reorderDepth) {
+            reorder->setDepth(reorderDepth.value);
+        }
+        if (reorderKey) {
+            reorder->setKey(reorderKey.value);
+        }
+    }
+
     uint32_t inputDelayValue = inputDelay ? inputDelay.value : 0;
     uint32_t pipelineDelayValue = pipelineDelay ? pipelineDelay.value : 0;
     uint32_t outputDelayValue = outputDelay ? outputDelay.value : 0;
@@ -982,7 +1085,7 @@
                 // TODO: handle this without going into array mode
                 forceArrayMode = true;
             } else {
-                input->buffers.reset(new GraphicInputBuffers(numInputSlots, mName));
+                input->buffers.reset(new GraphicInputBuffers(mName));
             }
         } else {
             if (hasCryptoOrDescrambler()) {
@@ -1150,20 +1253,13 @@
             if (outputSurface || !buffersBoundToCodec) {
                 output->buffers.reset(new GraphicOutputBuffers(mName));
             } else {
-                output->buffers.reset(new RawGraphicOutputBuffers(numOutputSlots, mName));
+                output->buffers.reset(new RawGraphicOutputBuffers(mName));
             }
         } else {
             output->buffers.reset(new LinearOutputBuffers(mName));
         }
         output->buffers->setFormat(outputFormat);
 
-        output->buffers->clearStash();
-        if (reorderDepth) {
-            output->buffers->setReorderDepth(reorderDepth.value);
-        }
-        if (reorderKey) {
-            output->buffers->setReorderKey(reorderKey.value);
-        }
 
         // Try to set output surface to created block pool if given.
         if (outputSurface) {
@@ -1331,8 +1427,8 @@
     {
         Mutexed<Output>::Locked output(mOutput);
         output->buffers->flush(flushedWork);
-        output->buffers->flushStash();
     }
+    mReorderStash.lock()->flush();
     mPipelineWatcher.lock()->flush();
 }
 
@@ -1368,34 +1464,45 @@
         std::unique_ptr<C2Work> work,
         const sp<AMessage> &outputFormat,
         const C2StreamInitDataInfo::output *initData) {
-    // Whether the output buffer should be reported to the client or not.
-    bool notifyClient = false;
+    if (outputFormat != nullptr) {
+        Mutexed<Output>::Locked output(mOutput);
+        ALOGD("[%s] onWorkDone: output format changed to %s",
+                mName, outputFormat->debugString().c_str());
+        output->buffers->setFormat(outputFormat);
 
-    if (work->result == C2_OK){
-        notifyClient = true;
-    } else if (work->result == C2_NOT_FOUND) {
-        ALOGD("[%s] flushed work; ignored.", mName);
-    } else {
-        // C2_OK and C2_NOT_FOUND are the only results that we accept for processing
-        // the config update.
-        ALOGD("[%s] work failed to complete: %d", mName, work->result);
-        mCCodecCallback->onError(work->result, ACTION_CODE_FATAL);
-        return false;
+        AString mediaType;
+        if (outputFormat->findString(KEY_MIME, &mediaType)
+                && mediaType == MIMETYPE_AUDIO_RAW) {
+            int32_t channelCount;
+            int32_t sampleRate;
+            if (outputFormat->findInt32(KEY_CHANNEL_COUNT, &channelCount)
+                    && outputFormat->findInt32(KEY_SAMPLE_RATE, &sampleRate)) {
+                output->buffers->updateSkipCutBuffer(sampleRate, channelCount);
+            }
+        }
     }
 
-    if ((work->input.ordinal.frameIndex -
-            mFirstValidFrameIndex.load()).peek() < 0) {
+    if ((work->input.ordinal.frameIndex - mFirstValidFrameIndex.load()).peek() < 0) {
         // Discard frames from previous generation.
         ALOGD("[%s] Discard frames from previous generation.", mName);
-        notifyClient = false;
+        return false;
     }
 
     if (mInputSurface == nullptr && (work->worklets.size() != 1u
             || !work->worklets.front()
-            || !(work->worklets.front()->output.flags &
-                 C2FrameData::FLAG_INCOMPLETE))) {
-        mPipelineWatcher.lock()->onWorkDone(
-                work->input.ordinal.frameIndex.peeku());
+            || !(work->worklets.front()->output.flags & C2FrameData::FLAG_INCOMPLETE))) {
+        mPipelineWatcher.lock()->onWorkDone(work->input.ordinal.frameIndex.peeku());
+    }
+
+    if (work->result == C2_NOT_FOUND) {
+        ALOGD("[%s] flushed work; ignored.", mName);
+        return true;
+    }
+
+    if (work->result != C2_OK) {
+        ALOGD("[%s] work failed to complete: %d", mName, work->result);
+        mCCodecCallback->onError(work->result, ACTION_CODE_FATAL);
+        return false;
     }
 
     // NOTE: MediaCodec usage supposedly have only one worklet
@@ -1431,10 +1538,8 @@
             case C2PortReorderBufferDepthTuning::CORE_INDEX: {
                 C2PortReorderBufferDepthTuning::output reorderDepth;
                 if (reorderDepth.updateFrom(*param)) {
-                    bool secure = mComponent->getName().find(".secure") !=
-                                  std::string::npos;
-                    mOutput.lock()->buffers->setReorderDepth(
-                            reorderDepth.value);
+                    bool secure = mComponent->getName().find(".secure") != std::string::npos;
+                    mReorderStash.lock()->setDepth(reorderDepth.value);
                     ALOGV("[%s] onWorkDone: updated reorder depth to %u",
                           mName, reorderDepth.value);
                     size_t numOutputSlots = mOutput.lock()->numSlots;
@@ -1446,19 +1551,17 @@
                         output->maxDequeueBuffers += numInputSlots;
                     }
                     if (output->surface) {
-                        output->surface->setMaxDequeuedBufferCount(
-                                output->maxDequeueBuffers);
+                        output->surface->setMaxDequeuedBufferCount(output->maxDequeueBuffers);
                     }
                 } else {
-                    ALOGD("[%s] onWorkDone: failed to read reorder depth",
-                          mName);
+                    ALOGD("[%s] onWorkDone: failed to read reorder depth", mName);
                 }
                 break;
             }
             case C2PortReorderKeySetting::CORE_INDEX: {
                 C2PortReorderKeySetting::output reorderKey;
                 if (reorderKey.updateFrom(*param)) {
-                    mOutput.lock()->buffers->setReorderKey(reorderKey.value);
+                    mReorderStash.lock()->setKey(reorderKey.value);
                     ALOGV("[%s] onWorkDone: updated reorder key to %u",
                           mName, reorderKey.value);
                 } else {
@@ -1473,8 +1576,7 @@
                         ALOGV("[%s] onWorkDone: updating pipeline delay %u",
                               mName, pipelineDelay.value);
                         newPipelineDelay = pipelineDelay.value;
-                        (void)mPipelineWatcher.lock()->pipelineDelay(
-                                pipelineDelay.value);
+                        (void)mPipelineWatcher.lock()->pipelineDelay(pipelineDelay.value);
                     }
                 }
                 if (param->forInput()) {
@@ -1483,8 +1585,7 @@
                         ALOGV("[%s] onWorkDone: updating input delay %u",
                               mName, inputDelay.value);
                         newInputDelay = inputDelay.value;
-                        (void)mPipelineWatcher.lock()->inputDelay(
-                                inputDelay.value);
+                        (void)mPipelineWatcher.lock()->inputDelay(inputDelay.value);
                     }
                 }
                 if (param->forOutput()) {
@@ -1492,10 +1593,8 @@
                     if (outputDelay.updateFrom(*param)) {
                         ALOGV("[%s] onWorkDone: updating output delay %u",
                               mName, outputDelay.value);
-                        bool secure = mComponent->getName().find(".secure") !=
-                                      std::string::npos;
-                        (void)mPipelineWatcher.lock()->outputDelay(
-                                outputDelay.value);
+                        bool secure = mComponent->getName().find(".secure") != std::string::npos;
+                        (void)mPipelineWatcher.lock()->outputDelay(outputDelay.value);
 
                         bool outputBuffersChanged = false;
                         size_t numOutputSlots = 0;
@@ -1503,8 +1602,7 @@
                         {
                             Mutexed<Output>::Locked output(mOutput);
                             output->outputDelay = outputDelay.value;
-                            numOutputSlots = outputDelay.value +
-                                             kSmoothnessFactor;
+                            numOutputSlots = outputDelay.value + kSmoothnessFactor;
                             if (output->numSlots < numOutputSlots) {
                                 output->numSlots = numOutputSlots;
                                 if (output->buffers->isArrayMode()) {
@@ -1523,7 +1621,7 @@
                             mCCodecCallback->onOutputBuffersChanged();
                         }
 
-                        uint32_t depth = mOutput.lock()->buffers->getReorderDepth();
+                        uint32_t depth = mReorderStash.lock()->depth();
                         Mutexed<OutputSurface>::Locked output(mOutputSurface);
                         output->maxDequeueBuffers = numOutputSlots + depth + kRenderingDepth;
                         if (!secure) {
@@ -1567,6 +1665,9 @@
         ALOGV("[%s] onWorkDone: output EOS", mName);
     }
 
+    sp<MediaCodecBuffer> outBuffer;
+    size_t index;
+
     // WORKAROUND: adjust output timestamp based on client input timestamp and codec
     // input timestamp. Codec output timestamp (in the timestamp field) shall correspond to
     // the codec input timestamp, but client output timestamp should (reported in timeUs)
@@ -1587,18 +1688,8 @@
           worklet->output.ordinal.timestamp.peekll(),
           timestamp.peekll());
 
-    // csd cannot be re-ordered and will always arrive first.
     if (initData != nullptr) {
         Mutexed<Output>::Locked output(mOutput);
-        if (outputFormat) {
-            output->buffers->updateSkipCutBuffer(outputFormat);
-            output->buffers->setFormat(outputFormat);
-        }
-        if (!notifyClient) {
-            return false;
-        }
-        size_t index;
-        sp<MediaCodecBuffer> outBuffer;
         if (output->buffers->registerCsd(initData, &index, &outBuffer) == OK) {
             outBuffer->meta()->setInt64("timeUs", timestamp.peek());
             outBuffer->meta()->setInt32("flags", MediaCodec::BUFFER_FLAG_CODECCONFIG);
@@ -1614,10 +1705,10 @@
         }
     }
 
-    if (notifyClient && !buffer && !flags) {
-        ALOGV("[%s] onWorkDone: Not reporting output buffer (%lld)",
+    if (!buffer && !flags && outputFormat == nullptr) {
+        ALOGV("[%s] onWorkDone: nothing to report from the work (%lld)",
               mName, work->input.ordinal.frameIndex.peekull());
-        notifyClient = false;
+        return true;
     }
 
     if (buffer) {
@@ -1636,60 +1727,63 @@
     }
 
     {
-        Mutexed<Output>::Locked output(mOutput);
-        output->buffers->pushToStash(
-                buffer,
-                notifyClient,
-                timestamp.peek(),
-                flags,
-                outputFormat,
-                worklet->output.ordinal);
+        Mutexed<ReorderStash>::Locked reorder(mReorderStash);
+        reorder->emplace(buffer, timestamp.peek(), flags, worklet->output.ordinal);
+        if (flags & MediaCodec::BUFFER_FLAG_EOS) {
+            // Flush reorder stash
+            reorder->setDepth(0);
+        }
     }
     sendOutputBuffers();
     return true;
 }
 
 void CCodecBufferChannel::sendOutputBuffers() {
-    OutputBuffers::BufferAction action;
-    size_t index;
+    ReorderStash::Entry entry;
     sp<MediaCodecBuffer> outBuffer;
-    std::shared_ptr<C2Buffer> c2Buffer;
+    size_t index;
 
     while (true) {
+        Mutexed<ReorderStash>::Locked reorder(mReorderStash);
+        if (!reorder->hasPending()) {
+            break;
+        }
+        if (!reorder->pop(&entry)) {
+            break;
+        }
+
         Mutexed<Output>::Locked output(mOutput);
-        action = output->buffers->popFromStashAndRegister(
-                &c2Buffer, &index, &outBuffer);
-        switch (action) {
-        case OutputBuffers::SKIP:
-            return;
-        case OutputBuffers::DISCARD:
-            break;
-        case OutputBuffers::NOTIFY_CLIENT:
-            output.unlock();
-            mCallback->onOutputBufferAvailable(index, outBuffer);
-            break;
-        case OutputBuffers::REALLOCATE: {
+        status_t err = output->buffers->registerBuffer(entry.buffer, &index, &outBuffer);
+        if (err != OK) {
+            bool outputBuffersChanged = false;
+            if (err != WOULD_BLOCK) {
                 if (!output->buffers->isArrayMode()) {
-                    output->buffers =
-                        output->buffers->toArrayMode(output->numSlots);
+                    output->buffers = output->buffers->toArrayMode(output->numSlots);
                 }
-                static_cast<OutputBuffersArray*>(output->buffers.get())->
-                        realloc(c2Buffer);
-                output.unlock();
+                OutputBuffersArray *array = (OutputBuffersArray *)output->buffers.get();
+                array->realloc(entry.buffer);
+                outputBuffersChanged = true;
+            }
+            ALOGV("[%s] sendOutputBuffers: unable to register output buffer", mName);
+            reorder->defer(entry);
+
+            output.unlock();
+            reorder.unlock();
+
+            if (outputBuffersChanged) {
                 mCCodecCallback->onOutputBuffersChanged();
             }
             return;
-        case OutputBuffers::RETRY:
-            ALOGV("[%s] sendOutputBuffers: unable to register output buffer",
-                  mName);
-            return;
-        default:
-            LOG_ALWAYS_FATAL("[%s] sendOutputBuffers: "
-                    "corrupted BufferAction value (%d) "
-                    "returned from popFromStashAndRegister.",
-                    mName, int(action));
-            return;
         }
+        output.unlock();
+        reorder.unlock();
+
+        outBuffer->meta()->setInt64("timeUs", entry.timestamp);
+        outBuffer->meta()->setInt32("flags", entry.flags);
+        ALOGV("[%s] sendOutputBuffers: out buffer index = %zu [%p] => %p + %zu (%lld)",
+                mName, index, outBuffer.get(), outBuffer->data(), outBuffer->size(),
+                (long long)entry.timestamp);
+        mCallback->onOutputBufferAvailable(index, outBuffer);
     }
 }
 
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.h b/media/codec2/sfplugin/CCodecBufferChannel.h
index da15724..0263211 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.h
+++ b/media/codec2/sfplugin/CCodecBufferChannel.h
@@ -296,6 +296,48 @@
 
     Mutexed<PipelineWatcher> mPipelineWatcher;
 
+    class ReorderStash {
+    public:
+        struct Entry {
+            inline Entry() : buffer(nullptr), timestamp(0), flags(0), ordinal({0, 0, 0}) {}
+            inline Entry(
+                    const std::shared_ptr<C2Buffer> &b,
+                    int64_t t,
+                    int32_t f,
+                    const C2WorkOrdinalStruct &o)
+                : buffer(b), timestamp(t), flags(f), ordinal(o) {}
+            std::shared_ptr<C2Buffer> buffer;
+            int64_t timestamp;
+            int32_t flags;
+            C2WorkOrdinalStruct ordinal;
+        };
+
+        ReorderStash();
+
+        void clear();
+        void flush();
+        void setDepth(uint32_t depth);
+        void setKey(C2Config::ordinal_key_t key);
+        bool pop(Entry *entry);
+        void emplace(
+                const std::shared_ptr<C2Buffer> &buffer,
+                int64_t timestamp,
+                int32_t flags,
+                const C2WorkOrdinalStruct &ordinal);
+        void defer(const Entry &entry);
+        bool hasPending() const;
+        uint32_t depth() const { return mDepth; }
+
+    private:
+        std::list<Entry> mPending;
+        std::list<Entry> mStash;
+        uint32_t mDepth;
+        C2Config::ordinal_key_t mKey;
+
+        bool less(const C2WorkOrdinalStruct &o1, const C2WorkOrdinalStruct &o2);
+    };
+    Mutexed<ReorderStash> mReorderStash;
+
     std::atomic_bool mInputMetEos;
     std::once_flag mRenderWarningFlag;
 
diff --git a/media/codec2/sfplugin/CCodecBuffers.cpp b/media/codec2/sfplugin/CCodecBuffers.cpp
index 4ce13aa..a9120c4 100644
--- a/media/codec2/sfplugin/CCodecBuffers.cpp
+++ b/media/codec2/sfplugin/CCodecBuffers.cpp
@@ -21,9 +21,9 @@
 #include <C2PlatformSupport.h>
 
 #include <media/stagefright/foundation/ADebug.h>
-#include <media/stagefright/MediaCodec.h>
 #include <media/stagefright/MediaCodecConstants.h>
 #include <media/stagefright/SkipCutBuffer.h>
+#include <mediadrm/ICrypto.h>
 
 #include "CCodecBuffers.h"
 
@@ -122,6 +122,11 @@
 
 // OutputBuffers
 
+OutputBuffers::OutputBuffers(const char *componentName, const char *name)
+    : CCodecBuffers(componentName, name) { }
+
+OutputBuffers::~OutputBuffers() = default;
+
 void OutputBuffers::initSkipCutBuffer(
         int32_t delay, int32_t padding, int32_t sampleRate, int32_t channelCount) {
     CHECK(mSkipCutBuffer == nullptr);
@@ -150,29 +155,16 @@
     setSkipCutBuffer(delay, padding);
 }
 
-void OutputBuffers::updateSkipCutBuffer(
-        const sp<AMessage> &format, bool notify) {
-    AString mediaType;
-    if (format->findString(KEY_MIME, &mediaType)
-            && mediaType == MIMETYPE_AUDIO_RAW) {
-        int32_t channelCount;
-        int32_t sampleRate;
-        if (format->findInt32(KEY_CHANNEL_COUNT, &channelCount)
-                && format->findInt32(KEY_SAMPLE_RATE, &sampleRate)) {
-            updateSkipCutBuffer(sampleRate, channelCount);
-        }
-    }
-    if (notify) {
-        mUnreportedFormat = nullptr;
-    }
-}
-
 void OutputBuffers::submit(const sp<MediaCodecBuffer> &buffer) {
     if (mSkipCutBuffer != nullptr) {
         mSkipCutBuffer->submit(buffer);
     }
 }
 
+void OutputBuffers::transferSkipCutBuffer(const sp<SkipCutBuffer> &scb) {
+    mSkipCutBuffer = scb;
+}
+
 void OutputBuffers::setSkipCutBuffer(int32_t skip, int32_t cut) {
     if (mSkipCutBuffer != nullptr) {
         size_t prevSize = mSkipCutBuffer->size();
@@ -183,179 +175,13 @@
     mSkipCutBuffer = new SkipCutBuffer(skip, cut, mChannelCount);
 }
 
-void OutputBuffers::clearStash() {
-    mPending.clear();
-    mReorderStash.clear();
-    mDepth = 0;
-    mKey = C2Config::ORDINAL;
-    mUnreportedFormat = nullptr;
-}
-
-void OutputBuffers::flushStash() {
-    for (StashEntry& e : mPending) {
-        e.notify = false;
-    }
-    for (StashEntry& e : mReorderStash) {
-        e.notify = false;
-    }
-}
-
-uint32_t OutputBuffers::getReorderDepth() const {
-    return mDepth;
-}
-
-void OutputBuffers::setReorderDepth(uint32_t depth) {
-    mPending.splice(mPending.end(), mReorderStash);
-    mDepth = depth;
-}
-
-void OutputBuffers::setReorderKey(C2Config::ordinal_key_t key) {
-    mPending.splice(mPending.end(), mReorderStash);
-    mKey = key;
-}
-
-void OutputBuffers::pushToStash(
-        const std::shared_ptr<C2Buffer>& buffer,
-        bool notify,
-        int64_t timestamp,
-        int32_t flags,
-        const sp<AMessage>& format,
-        const C2WorkOrdinalStruct& ordinal) {
-    bool eos = flags & MediaCodec::BUFFER_FLAG_EOS;
-    if (!buffer && eos) {
-        // TRICKY: we may be violating ordering of the stash here. Because we
-        // don't expect any more emplace() calls after this, the ordering should
-        // not matter.
-        mReorderStash.emplace_back(
-                buffer, notify, timestamp, flags, format, ordinal);
-    } else {
-        flags = flags & ~MediaCodec::BUFFER_FLAG_EOS;
-        auto it = mReorderStash.begin();
-        for (; it != mReorderStash.end(); ++it) {
-            if (less(ordinal, it->ordinal)) {
-                break;
-            }
-        }
-        mReorderStash.emplace(it,
-                buffer, notify, timestamp, flags, format, ordinal);
-        if (eos) {
-            mReorderStash.back().flags =
-                mReorderStash.back().flags | MediaCodec::BUFFER_FLAG_EOS;
-        }
-    }
-    while (!mReorderStash.empty() && mReorderStash.size() > mDepth) {
-        mPending.push_back(mReorderStash.front());
-        mReorderStash.pop_front();
-    }
-    ALOGV("[%s] %s: pushToStash -- pending size = %zu", mName, __func__, mPending.size());
-}
-
-OutputBuffers::BufferAction OutputBuffers::popFromStashAndRegister(
-        std::shared_ptr<C2Buffer>* c2Buffer,
-        size_t* index,
-        sp<MediaCodecBuffer>* outBuffer) {
-    if (mPending.empty()) {
-        return SKIP;
-    }
-
-    // Retrieve the first entry.
-    StashEntry &entry = mPending.front();
-
-    *c2Buffer = entry.buffer;
-    sp<AMessage> outputFormat = entry.format;
-
-    // The output format can be processed without a registered slot.
-    if (outputFormat) {
-        ALOGD("[%s] popFromStashAndRegister: output format changed to %s",
-                mName, outputFormat->debugString().c_str());
-        updateSkipCutBuffer(outputFormat, entry.notify);
-    }
-
-    if (entry.notify) {
-        if (outputFormat) {
-            setFormat(outputFormat);
-        } else if (mUnreportedFormat) {
-            outputFormat = mUnreportedFormat->dup();
-            setFormat(outputFormat);
-        }
-        mUnreportedFormat = nullptr;
-    } else {
-        if (outputFormat) {
-            mUnreportedFormat = outputFormat;
-        } else if (!mUnreportedFormat) {
-            mUnreportedFormat = mFormat;
-        }
-    }
-
-    // Flushing mReorderStash because no other buffers should come after output
-    // EOS.
-    if (entry.flags & MediaCodec::BUFFER_FLAG_EOS) {
-        // Flush reorder stash
-        setReorderDepth(0);
-    }
-
-    if (!entry.notify) {
-        mPending.pop_front();
-        return DISCARD;
-    }
-
-    // Try to register the buffer.
-    status_t err = registerBuffer(*c2Buffer, index, outBuffer);
-    if (err != OK) {
-        if (err != WOULD_BLOCK) {
-            return REALLOCATE;
-        }
-        return RETRY;
-    }
-
-    // Append information from the front stash entry to outBuffer.
-    (*outBuffer)->meta()->setInt64("timeUs", entry.timestamp);
-    (*outBuffer)->meta()->setInt32("flags", entry.flags);
-    ALOGV("[%s] popFromStashAndRegister: "
-          "out buffer index = %zu [%p] => %p + %zu (%lld)",
-          mName, *index, outBuffer->get(),
-          (*outBuffer)->data(), (*outBuffer)->size(),
-          (long long)entry.timestamp);
-
-    // The front entry of mPending will be removed now that the registration
-    // succeeded.
-    mPending.pop_front();
-    return NOTIFY_CLIENT;
-}
-
-bool OutputBuffers::popPending(StashEntry *entry) {
-    if (mPending.empty()) {
-        return false;
-    }
-    *entry = mPending.front();
-    mPending.pop_front();
-    return true;
-}
-
-void OutputBuffers::deferPending(const OutputBuffers::StashEntry &entry) {
-    mPending.push_front(entry);
-}
-
-bool OutputBuffers::hasPending() const {
-    return !mPending.empty();
-}
-
-bool OutputBuffers::less(
-        const C2WorkOrdinalStruct &o1, const C2WorkOrdinalStruct &o2) const {
-    switch (mKey) {
-        case C2Config::ORDINAL:   return o1.frameIndex < o2.frameIndex;
-        case C2Config::TIMESTAMP: return o1.timestamp < o2.timestamp;
-        case C2Config::CUSTOM:    return o1.customOrdinal < o2.customOrdinal;
-        default:
-            ALOGD("Unrecognized key; default to timestamp");
-            return o1.frameIndex < o2.frameIndex;
-    }
-}
-
 // LocalBufferPool
 
-std::shared_ptr<LocalBufferPool> LocalBufferPool::Create(size_t poolCapacity) {
-    return std::shared_ptr<LocalBufferPool>(new LocalBufferPool(poolCapacity));
+constexpr size_t kInitialPoolCapacity = kMaxLinearBufferSize;
+constexpr size_t kMaxPoolCapacity = kMaxLinearBufferSize * 32;
+
+std::shared_ptr<LocalBufferPool> LocalBufferPool::Create() {
+    return std::shared_ptr<LocalBufferPool>(new LocalBufferPool(kInitialPoolCapacity));
 }
 
 sp<ABuffer> LocalBufferPool::newBuffer(size_t capacity) {
@@ -375,6 +201,11 @@
             mUsedSize -= mPool.back().capacity();
             mPool.pop_back();
         }
+        while (mUsedSize + capacity > mPoolCapacity && mPoolCapacity * 2 <= kMaxPoolCapacity) {
+            ALOGD("Increasing local buffer pool capacity from %zu to %zu",
+                  mPoolCapacity, mPoolCapacity * 2);
+            mPoolCapacity *= 2;
+        }
         if (mUsedSize + capacity > mPoolCapacity) {
             ALOGD("mUsedSize = %zu, capacity = %zu, mPoolCapacity = %zu",
                     mUsedSize, capacity, mPoolCapacity);
@@ -960,11 +791,10 @@
 // GraphicInputBuffers
 
 GraphicInputBuffers::GraphicInputBuffers(
-        size_t numInputSlots, const char *componentName, const char *name)
+        const char *componentName, const char *name)
     : InputBuffers(componentName, name),
       mImpl(mName),
-      mLocalBufferPool(LocalBufferPool::Create(
-              kMaxLinearBufferSize * numInputSlots)) { }
+      mLocalBufferPool(LocalBufferPool::Create()) { }
 
 bool GraphicInputBuffers::requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) {
     sp<Codec2Buffer> newBuffer = createNewBuffer();
@@ -1125,7 +955,7 @@
         case C2BufferData::GRAPHIC: {
             // This is only called for RawGraphicOutputBuffers.
             mAlloc = [format = mFormat,
-                      lbp = LocalBufferPool::Create(kMaxLinearBufferSize * mImpl.arraySize())] {
+                      lbp = LocalBufferPool::Create()] {
                 return ConstGraphicBlockBuffer::AllocateEmpty(
                         format,
                         [lbp](size_t capacity) {
@@ -1151,16 +981,6 @@
     mImpl.grow(newSize, mAlloc);
 }
 
-void OutputBuffersArray::transferFrom(OutputBuffers* source) {
-    mFormat = source->mFormat;
-    mSkipCutBuffer = source->mSkipCutBuffer;
-    mUnreportedFormat = source->mUnreportedFormat;
-    mPending = std::move(source->mPending);
-    mReorderStash = std::move(source->mReorderStash);
-    mDepth = source->mDepth;
-    mKey = source->mKey;
-}
-
 // FlexOutputBuffers
 
 status_t FlexOutputBuffers::registerBuffer(
@@ -1203,12 +1023,13 @@
     // track of the flushed work.
 }
 
-std::unique_ptr<OutputBuffersArray> FlexOutputBuffers::toArrayMode(size_t size) {
+std::unique_ptr<OutputBuffers> FlexOutputBuffers::toArrayMode(size_t size) {
     std::unique_ptr<OutputBuffersArray> array(new OutputBuffersArray(mComponentName.c_str()));
-    array->transferFrom(this);
+    array->setFormat(mFormat);
+    array->transferSkipCutBuffer(mSkipCutBuffer);
     std::function<sp<Codec2Buffer>()> alloc = getAlloc();
     array->initialize(mImpl, size, alloc);
-    return array;
+    return std::move(array);
 }
 
 size_t FlexOutputBuffers::numClientBuffers() const {
@@ -1271,10 +1092,9 @@
 // RawGraphicOutputBuffers
 
 RawGraphicOutputBuffers::RawGraphicOutputBuffers(
-        size_t numOutputSlots, const char *componentName, const char *name)
+        const char *componentName, const char *name)
     : FlexOutputBuffers(componentName, name),
-      mLocalBufferPool(LocalBufferPool::Create(
-              kMaxLinearBufferSize * numOutputSlots)) { }
+      mLocalBufferPool(LocalBufferPool::Create()) { }
 
 sp<Codec2Buffer> RawGraphicOutputBuffers::wrap(const std::shared_ptr<C2Buffer> &buffer) {
     if (buffer == nullptr) {
diff --git a/media/codec2/sfplugin/CCodecBuffers.h b/media/codec2/sfplugin/CCodecBuffers.h
index cadc4d8..6244acd 100644
--- a/media/codec2/sfplugin/CCodecBuffers.h
+++ b/media/codec2/sfplugin/CCodecBuffers.h
@@ -28,6 +28,8 @@
 
 namespace android {
 
+struct ICrypto;
+class MemoryDealer;
 class SkipCutBuffer;
 
 constexpr size_t kLinearBufferSize = 1048576;
@@ -154,22 +156,15 @@
     DISALLOW_EVIL_CONSTRUCTORS(InputBuffers);
 };
 
-class OutputBuffersArray;
-
 class OutputBuffers : public CCodecBuffers {
 public:
-    OutputBuffers(const char *componentName, const char *name = "Output")
-        : CCodecBuffers(componentName, name) { }
-    virtual ~OutputBuffers() = default;
+    OutputBuffers(const char *componentName, const char *name = "Output");
+    virtual ~OutputBuffers();
 
     /**
      * Register output C2Buffer from the component and obtain corresponding
-     * index and MediaCodecBuffer object.
-     *
-     * Returns:
-     *   OK if registration succeeds.
-     *   NO_MEMORY if all buffers are available but not compatible.
-     *   WOULD_BLOCK if there are compatible buffers, but they are all in use.
+     * index and MediaCodecBuffer object. Returns false if registration
+     * fails.
      */
     virtual status_t registerBuffer(
             const std::shared_ptr<C2Buffer> &buffer,
@@ -204,7 +199,7 @@
      * shall retain the internal state so that it will honor index and
      * buffer from previous calls of registerBuffer().
      */
-    virtual std::unique_ptr<OutputBuffersArray> toArrayMode(size_t size) = 0;
+    virtual std::unique_ptr<OutputBuffers> toArrayMode(size_t size) = 0;
 
     /**
      * Initialize SkipCutBuffer object.
@@ -213,164 +208,6 @@
             int32_t delay, int32_t padding, int32_t sampleRate, int32_t channelCount);
 
     /**
-     * Update SkipCutBuffer from format. The @p format must not be null.
-     * @p notify determines whether the format comes with a buffer that should
-     * be reported to the client or not.
-     */
-    void updateSkipCutBuffer(const sp<AMessage> &format, bool notify = true);
-
-    /**
-     * Output Stash
-     * ============
-     *
-     * The output stash is a place to hold output buffers temporarily before
-     * they are registered to output slots. It has 2 main functions:
-     * 1. Allow reordering of output frames as the codec may produce frames in a
-     *    different order.
-     * 2. Act as a "buffer" between the codec and the client because the codec
-     *    may produce more buffers than available slots. This excess of codec's
-     *    output buffers should be registered to slots later, after the client
-     *    has released some slots.
-     *
-     * The stash consists of 2 lists of buffers: mPending and mReorderStash.
-     * mPending is a normal FIFO queue with not size limit, while mReorderStash
-     * is a sorted list with size limit mDepth.
-     *
-     * The normal flow of a non-csd output buffer is as follows:
-     *
-     *           |----------------OutputBuffers---------------|
-     *           |----------Output stash----------|           |
-     *   Codec --|-> mReorderStash --> mPending --|-> slots --|-> client
-     *           |                                |           |
-     *     pushToStash()                    popFromStashAndRegister()
-     *
-     * The buffer that comes from the codec first enters mReorderStash. The
-     * first buffer in mReorderStash gets moved to mPending when mReorderStash
-     * overflows. Buffers in mPending are registered to slots and given to the
-     * client as soon as slots are available.
-     *
-     * Every output buffer that is not a csd buffer should be put on the stash
-     * by calling pushToStash(), then later registered to a slot by calling
-     * popFromStashAndRegister() before notifying the client with
-     * onOutputBufferAvailable().
-     *
-     * Reordering
-     * ==========
-     *
-     * mReorderStash is a sorted list with a specified size limit. The size
-     * limit can be set by calling setReorderDepth().
-     *
-     * Every buffer in mReorderStash has a C2WorkOrdinalStruct, which contains 3
-     * members, all of which are comparable. Which member of C2WorkOrdinalStruct
-     * should be used for reordering can be chosen by calling setReorderKey().
-     */
-
-    /**
-     * Return the reorder depth---the size of mReorderStash.
-     */
-    uint32_t getReorderDepth() const;
-
-    /**
-     * Set the reorder depth.
-     */
-    void setReorderDepth(uint32_t depth);
-
-    /**
-     * Set the type of "key" to use in comparisons.
-     */
-    void setReorderKey(C2Config::ordinal_key_t key);
-
-    /**
-     * Return whether the output stash has any pending buffers.
-     */
-    bool hasPending() const;
-
-    /**
-     * Flush the stash and reset the depth and the key to their default values.
-     */
-    void clearStash();
-
-    /**
-     * Flush the stash.
-     */
-    void flushStash();
-
-    /**
-     * Push a buffer to the reorder stash.
-     *
-     * @param buffer    C2Buffer object from the returned work.
-     * @param notify    Whether the returned work contains a buffer that should
-     *                  be reported to the client. This may be false if the
-     *                  caller wants to process the buffer without notifying the
-     *                  client.
-     * @param timestamp Buffer timestamp to report to the client.
-     * @param flags     Buffer flags to report to the client.
-     * @param format    Buffer format to report to the client.
-     * @param ordinal   Ordinal used in reordering. This determines when the
-     *                  buffer will be popped from the output stash by
-     *                  `popFromStashAndRegister()`.
-     */
-    void pushToStash(
-            const std::shared_ptr<C2Buffer>& buffer,
-            bool notify,
-            int64_t timestamp,
-            int32_t flags,
-            const sp<AMessage>& format,
-            const C2WorkOrdinalStruct& ordinal);
-
-    enum BufferAction : int {
-        SKIP,
-        DISCARD,
-        NOTIFY_CLIENT,
-        REALLOCATE,
-        RETRY,
-    };
-
-    /**
-     * Try to atomically pop the first buffer from the reorder stash and
-     * register it to an output slot. The function returns a value that
-     * indicates a recommended course of action for the caller.
-     *
-     * If the stash is empty, the function will return `SKIP`.
-     *
-     * If the stash is not empty, the function will peek at the first (oldest)
-     * entry in mPending process the buffer in the entry as follows:
-     * - If the buffer should not be sent to the client, the function will
-     *   return `DISCARD`. The stash entry will be removed.
-     * - If the buffer should be sent to the client, the function will attempt
-     *   to register the buffer to a slot. The registration may have 3 outcomes
-     *   corresponding to the following return values:
-     *   - `NOTIFY_CLIENT`: The buffer is successfully registered to a slot. The
-     *     output arguments @p index and @p outBuffer will contain valid values
-     *     that the caller can use to call onOutputBufferAvailable(). The stash
-     *     entry will be removed.
-     *   - `REALLOCATE`: The buffer is not registered because it is not
-     *     compatible with the current slots (which are available). The caller
-     *     should reallocate the OutputBuffers with slots that can fit the
-     *     returned @p c2Buffer. The stash entry will not be removed
-     *   - `RETRY`: All slots are currently occupied by the client. The caller
-     *     should try to call this function again after the client has released
-     *     some slots.
-     *
-     * @return What the caller should do afterwards.
-     *
-     * @param[out] c2Buffer   Underlying C2Buffer associated to the first buffer
-     *                        on the stash. This value is guaranteed to be valid
-     *                        unless the return value is `SKIP`.
-     * @param[out] index      Slot index. This value is valid only if the return
-     *                        value is `NOTIFY_CLIENT`.
-     * @param[out] outBuffer  Registered buffer. This value is valid only if the
-     *                        return valu is `NOTIFY_CLIENT`.
-     */
-    BufferAction popFromStashAndRegister(
-            std::shared_ptr<C2Buffer>* c2Buffer,
-            size_t* index,
-            sp<MediaCodecBuffer>* outBuffer);
-
-protected:
-    sp<SkipCutBuffer> mSkipCutBuffer;
-
-    /**
      * Update the SkipCutBuffer object. No-op if it's never initialized.
      */
     void updateSkipCutBuffer(int32_t sampleRate, int32_t channelCount);
@@ -380,8 +217,15 @@
      */
     void submit(const sp<MediaCodecBuffer> &buffer);
 
+    /**
+     * Transfer SkipCutBuffer object to the other Buffers object.
+     */
+    void transferSkipCutBuffer(const sp<SkipCutBuffer> &scb);
+
+protected:
+    sp<SkipCutBuffer> mSkipCutBuffer;
+
 private:
-    // SkipCutBuffer
     int32_t mDelay;
     int32_t mPadding;
     int32_t mSampleRate;
@@ -389,78 +233,7 @@
 
     void setSkipCutBuffer(int32_t skip, int32_t cut);
 
-    // Output stash
-
-    // Output format that has not been made available to the client.
-    sp<AMessage> mUnreportedFormat;
-
-    // Struct for an entry in the output stash (mPending and mReorderStash)
-    struct StashEntry {
-        inline StashEntry()
-            : buffer(nullptr),
-              notify(false),
-              timestamp(0),
-              flags(0),
-              format(),
-              ordinal({0, 0, 0}) {}
-        inline StashEntry(
-                const std::shared_ptr<C2Buffer> &b,
-                bool n,
-                int64_t t,
-                int32_t f,
-                const sp<AMessage> &fmt,
-                const C2WorkOrdinalStruct &o)
-            : buffer(b),
-              notify(n),
-              timestamp(t),
-              flags(f),
-              format(fmt),
-              ordinal(o) {}
-        std::shared_ptr<C2Buffer> buffer;
-        bool notify;
-        int64_t timestamp;
-        int32_t flags;
-        sp<AMessage> format;
-        C2WorkOrdinalStruct ordinal;
-    };
-
-    /**
-     * FIFO queue of stash entries.
-     */
-    std::list<StashEntry> mPending;
-    /**
-     * Sorted list of stash entries.
-     */
-    std::list<StashEntry> mReorderStash;
-    /**
-     * Size limit of mReorderStash.
-     */
-    uint32_t mDepth{0};
-    /**
-     * Choice of key to use in ordering of stash entries in mReorderStash.
-     */
-    C2Config::ordinal_key_t mKey{C2Config::ORDINAL};
-
-    /**
-     * Return false if mPending is empty; otherwise, pop the first entry from
-     * mPending and return true.
-     */
-    bool popPending(StashEntry *entry);
-
-    /**
-     * Push an entry as the first entry of mPending.
-     */
-    void deferPending(const StashEntry &entry);
-
-    /**
-     * Comparison of C2WorkOrdinalStruct based on mKey.
-     */
-    bool less(const C2WorkOrdinalStruct &o1,
-              const C2WorkOrdinalStruct &o2) const;
-
     DISALLOW_EVIL_CONSTRUCTORS(OutputBuffers);
-
-    friend OutputBuffersArray;
 };
 
 /**
@@ -471,11 +244,9 @@
     /**
      * Create a new LocalBufferPool object.
      *
-     * \param poolCapacity  max total size of buffers managed by this pool.
-     *
      * \return  a newly created pool object.
      */
-    static std::shared_ptr<LocalBufferPool> Create(size_t poolCapacity);
+    static std::shared_ptr<LocalBufferPool> Create();
 
     /**
      * Return an ABuffer object whose size is at least |capacity|.
@@ -907,8 +678,7 @@
 
 class GraphicInputBuffers : public InputBuffers {
 public:
-    GraphicInputBuffers(
-            size_t numInputSlots, const char *componentName, const char *name = "2D-BB-Input");
+    GraphicInputBuffers(const char *componentName, const char *name = "2D-BB-Input");
     ~GraphicInputBuffers() override = default;
 
     bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override;
@@ -1000,7 +770,7 @@
 
     bool isArrayMode() const final { return true; }
 
-    std::unique_ptr<OutputBuffersArray> toArrayMode(size_t) final {
+    std::unique_ptr<OutputBuffers> toArrayMode(size_t) final {
         return nullptr;
     }
 
@@ -1039,12 +809,6 @@
      */
     void grow(size_t newSize);
 
-    /**
-     * Transfer the SkipCutBuffer and the output stash from another
-     * OutputBuffers.
-     */
-    void transferFrom(OutputBuffers* source);
-
 private:
     BuffersArrayImpl mImpl;
     std::function<sp<Codec2Buffer>()> mAlloc;
@@ -1073,7 +837,7 @@
     void flush(
             const std::list<std::unique_ptr<C2Work>> &flushedWork) override;
 
-    std::unique_ptr<OutputBuffersArray> toArrayMode(size_t size) override;
+    std::unique_ptr<OutputBuffers> toArrayMode(size_t size) override;
 
     size_t numClientBuffers() const final;
 
@@ -1126,8 +890,7 @@
 
 class RawGraphicOutputBuffers : public FlexOutputBuffers {
 public:
-    RawGraphicOutputBuffers(
-            size_t numOutputSlots, const char *componentName, const char *name = "2D-BB-Output");
+    RawGraphicOutputBuffers(const char *componentName, const char *name = "2D-BB-Output");
     ~RawGraphicOutputBuffers() override = default;
 
     sp<Codec2Buffer> wrap(const std::shared_ptr<C2Buffer> &buffer) override;
diff --git a/media/codec2/sfplugin/Codec2Buffer.cpp b/media/codec2/sfplugin/Codec2Buffer.cpp
index 5b3a62f..25e7da9 100644
--- a/media/codec2/sfplugin/Codec2Buffer.cpp
+++ b/media/codec2/sfplugin/Codec2Buffer.cpp
@@ -18,6 +18,8 @@
 #define LOG_TAG "Codec2Buffer"
 #include <utils/Log.h>
 
+#include <android/hardware/cas/native/1.0/types.h>
+#include <android/hardware/drm/1.0/types.h>
 #include <hidlmemory/FrameworkUtils.h>
 #include <media/hardware/HardwareAPI.h>
 #include <media/stagefright/CodecBase.h>
@@ -25,6 +27,7 @@
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/foundation/AUtils.h>
+#include <mediadrm/ICrypto.h>
 #include <nativebase/nativebase.h>
 #include <ui/Fence.h>
 
diff --git a/media/codec2/sfplugin/Codec2Buffer.h b/media/codec2/sfplugin/Codec2Buffer.h
index ff79946..dc788cd 100644
--- a/media/codec2/sfplugin/Codec2Buffer.h
+++ b/media/codec2/sfplugin/Codec2Buffer.h
@@ -20,16 +20,29 @@
 
 #include <C2Buffer.h>
 
-#include <android/hardware/cas/native/1.0/types.h>
-#include <android/hardware/drm/1.0/types.h>
 #include <binder/IMemory.h>
 #include <media/hardware/VideoAPI.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/MediaCodecBuffer.h>
-#include <mediadrm/ICrypto.h>
 
 namespace android {
 
+namespace hardware {
+class HidlMemory;
+namespace cas {
+namespace native {
+namespace V1_0 {
+struct SharedBuffer;
+}  // namespace V1_0
+}  // namespace native
+}  // namespace cas
+namespace drm {
+namespace V1_0 {
+struct SharedBuffer;
+}  // namespace V1_0
+}  // namespace drm
+}  // namespace hardware
+
 /**
  * Copies a graphic view into a media image.
  *
diff --git a/media/codec2/sfplugin/tests/Android.bp b/media/codec2/sfplugin/tests/Android.bp
index fe5fa68..8d1a9c3 100644
--- a/media/codec2/sfplugin/tests/Android.bp
+++ b/media/codec2/sfplugin/tests/Android.bp
@@ -2,12 +2,13 @@
     name: "ccodec_unit_test",
 
     srcs: [
+        "CCodecBuffers_test.cpp",
         "CCodecConfig_test.cpp",
         "ReflectedParamUpdater_test.cpp",
     ],
 
     defaults: [
-        "libcodec2-hidl-defaults@1.0",
+        "libcodec2-impl-defaults",
         "libcodec2-internal-defaults",
     ],
 
@@ -16,14 +17,24 @@
     ],
 
     shared_libs: [
+        "android.hardware.media.bufferpool@2.0",
+        "android.hardware.media.c2@1.0",
         "libcodec2",
         "libcodec2_client",
+        "libhidlbase",
+        "libfmq",
+        "libmedia_omx",
         "libsfplugin_ccodec",
         "libsfplugin_ccodec_utils",
         "libstagefright_foundation",
         "libutils",
     ],
 
+    static_libs: [
+        "libcodec2_hidl@1.0",
+        "libstagefright_bufferpool@2.0",
+    ],
+
     cflags: [
         "-Werror",
         "-Wall",
diff --git a/media/codec2/sfplugin/tests/CCodecBuffers_test.cpp b/media/codec2/sfplugin/tests/CCodecBuffers_test.cpp
new file mode 100644
index 0000000..5bee605
--- /dev/null
+++ b/media/codec2/sfplugin/tests/CCodecBuffers_test.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 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.
+ */
+
+#include "CCodecBuffers.h"
+
+#include <gtest/gtest.h>
+
+#include <media/stagefright/MediaCodecConstants.h>
+
+#include <C2PlatformSupport.h>
+
+namespace android {
+
+TEST(RawGraphicOutputBuffersTest, ChangeNumSlots) {
+    constexpr int32_t kWidth = 3840;
+    constexpr int32_t kHeight = 2160;
+
+    std::shared_ptr<RawGraphicOutputBuffers> buffers =
+        std::make_shared<RawGraphicOutputBuffers>("test");
+    sp<AMessage> format{new AMessage};
+    format->setInt32("width", kWidth);
+    format->setInt32("height", kHeight);
+    buffers->setFormat(format);
+
+    std::shared_ptr<C2BlockPool> pool;
+    ASSERT_EQ(OK, GetCodec2BlockPool(C2BlockPool::BASIC_GRAPHIC, nullptr, &pool));
+
+    // Register 4 buffers
+    std::vector<sp<MediaCodecBuffer>> clientBuffers;
+    auto registerBuffer = [&buffers, &clientBuffers, &pool] {
+        std::shared_ptr<C2GraphicBlock> block;
+        ASSERT_EQ(OK, pool->fetchGraphicBlock(
+                kWidth, kHeight, HAL_PIXEL_FORMAT_YCbCr_420_888,
+                C2MemoryUsage{C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, &block));
+        std::shared_ptr<C2Buffer> c2Buffer = C2Buffer::CreateGraphicBuffer(block->share(
+                block->crop(), C2Fence{}));
+        size_t index;
+        sp<MediaCodecBuffer> clientBuffer;
+        ASSERT_EQ(OK, buffers->registerBuffer(c2Buffer, &index, &clientBuffer));
+        ASSERT_NE(nullptr, clientBuffer);
+        while (clientBuffers.size() <= index) {
+            clientBuffers.emplace_back();
+        }
+        ASSERT_EQ(nullptr, clientBuffers[index]) << "index = " << index;
+        clientBuffers[index] = clientBuffer;
+    };
+    for (int i = 0; i < 4; ++i) {
+        registerBuffer();
+    }
+
+    // Release 2 buffers
+    auto releaseBuffer = [&buffers, &clientBuffers, kWidth, kHeight](int index) {
+        std::shared_ptr<C2Buffer> c2Buffer;
+        ASSERT_TRUE(buffers->releaseBuffer(clientBuffers[index], &c2Buffer))
+                << "index = " << index;
+        clientBuffers[index] = nullptr;
+        // Sanity checks
+        ASSERT_TRUE(c2Buffer->data().linearBlocks().empty());
+        ASSERT_EQ(1u, c2Buffer->data().graphicBlocks().size());
+        C2ConstGraphicBlock block = c2Buffer->data().graphicBlocks().front();
+        ASSERT_EQ(kWidth, block.width());
+        ASSERT_EQ(kHeight, block.height());
+    };
+    for (int i = 0, index = 0; i < 2 && index < clientBuffers.size(); ++index) {
+        if (clientBuffers[index] == nullptr) {
+            continue;
+        }
+        releaseBuffer(index);
+        ++i;
+    }
+
+    // Simulate # of slots 4->16
+    for (int i = 2; i < 16; ++i) {
+        registerBuffer();
+    }
+
+    // Release everything
+    for (int index = 0; index < clientBuffers.size(); ++index) {
+        if (clientBuffers[index] == nullptr) {
+            continue;
+        }
+        releaseBuffer(index);
+    }
+}
+
+} // namespace android
diff --git a/media/codec2/vndk/C2AllocatorGralloc.cpp b/media/codec2/vndk/C2AllocatorGralloc.cpp
index 3ac3d89..e1e1377 100644
--- a/media/codec2/vndk/C2AllocatorGralloc.cpp
+++ b/media/codec2/vndk/C2AllocatorGralloc.cpp
@@ -520,6 +520,22 @@
             break;
         }
 
+        case static_cast<uint32_t>(PixelFormat4::BLOB): {
+            void *pointer = nullptr;
+            // TODO: fence
+            status_t err = GraphicBufferMapper::get().lock(
+                                const_cast<native_handle_t*>(mBuffer), grallocUsage,
+                                { (int32_t)rect.left, (int32_t)rect.top,
+                                  (int32_t)rect.width, (int32_t)rect.height },
+                                &pointer);
+            if (err) {
+                ALOGE("failed transaction: lock(BLOB)");
+                return C2_CORRUPTED;
+            }
+            *addr = (uint8_t *)pointer;
+            break;
+        }
+
         case static_cast<uint32_t>(PixelFormat4::YCBCR_420_888):
             // fall-through
         case static_cast<uint32_t>(PixelFormat4::YV12):
diff --git a/media/extractors/fuzzers/Android.bp b/media/extractors/fuzzers/Android.bp
index 5cae39d..7bac4e1 100644
--- a/media/extractors/fuzzers/Android.bp
+++ b/media/extractors/fuzzers/Android.bp
@@ -112,6 +112,34 @@
 }
 
 cc_fuzz {
+    name: "amr_extractor_fuzzer",
+
+    srcs: [
+        "amr_extractor_fuzzer.cpp",
+    ],
+
+    include_dirs: [
+        "frameworks/av/media/extractors/amr",
+    ],
+
+    static_libs: [
+        "liblog",
+        "libstagefright_foundation",
+        "libmedia",
+        "libextractorfuzzerbase",
+        "libamrextractor",
+    ],
+
+    shared_libs: [
+        "libutils",
+        "libmediandk",
+        "libbinder",
+    ],
+
+    dictionary: "amr_extractor_fuzzer.dict",
+}
+
+cc_fuzz {
     name: "mp3_extractor_fuzzer",
 
     srcs: [
@@ -165,3 +193,35 @@
         "libbinder",
     ],
 }
+
+cc_fuzz {
+    name: "flac_extractor_fuzzer",
+
+    srcs: [
+        "flac_extractor_fuzzer.cpp",
+    ],
+
+    include_dirs: [
+        "frameworks/av/media/extractors/flac",
+    ],
+
+    static_libs: [
+        "liblog",
+        "libstagefright_foundation",
+        "libmedia",
+        "libextractorfuzzerbase",
+        "libstagefright_metadatautils",
+        "libFLAC",
+        "libflacextractor",
+    ],
+
+    shared_libs: [
+        "libutils",
+        "libmediandk",
+        "libbinder",
+        "libbinder_ndk",
+        "libbase",
+    ],
+
+    dictionary: "flac_extractor_fuzzer.dict",
+}
diff --git a/media/extractors/fuzzers/README.md b/media/extractors/fuzzers/README.md
index f09e1c2..96dd545 100644
--- a/media/extractors/fuzzers/README.md
+++ b/media/extractors/fuzzers/README.md
@@ -4,8 +4,10 @@
 + [libextractorfuzzerbase](#ExtractorFuzzerBase)
 + [libmp4extractor](#mp4ExtractorFuzzer)
 + [libwavextractor](#wavExtractorFuzzer)
++ [libamrextractor](#amrExtractorFuzzer)
 + [libmp3extractor](#mp3ExtractorFuzzer)
 + [libaacextractor](#aacExtractorFuzzer)
++ [libflacextractor](#flacExtractor)
 
 # <a name="ExtractorFuzzerBase"></a> Fuzzer for libextractorfuzzerbase
 All the extractors have a common API - creating a data source, extraction
@@ -81,6 +83,40 @@
   $ adb shell /data/fuzz/arm64/wav_extractor_fuzzer/wav_extractor_fuzzer CORPUS_DIR
 ```
 
+# <a name="amrExtractorFuzzer"></a> Fuzzer for libamrextractor
+
+## Plugin Design Considerations
+The fuzzer plugin for AMR extractor uses the `ExtractorFuzzerBase` class and
+implements only the `createExtractor` to create the AMR extractor class.
+
+##### Maximize code coverage
+Dict file (dictionary file) is created for AMR to ensure that the required start
+bytes are present in every input file that goes to the fuzzer.
+This ensures that larger code gets covered.
+
+
+## Build
+
+This describes steps to build amr_extractor_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+  $ mm -j$(nproc) amr_extractor_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some AMR files to that folder
+Push this directory to device.
+
+To run on device
+```
+  $ adb sync data
+  $ adb shell /data/fuzz/arm64/amr_extractor_fuzzer/amr_extractor_fuzzer CORPUS_DIR
+```
+
 # <a name="mp3ExtractorFuzzer"></a> Fuzzer for libmp3extractor
 
 ## Plugin Design Considerations
@@ -139,6 +175,40 @@
   $ adb shell /data/fuzz/arm64/aac_extractor_fuzzer/aac_extractor_fuzzer CORPUS_DIR
 ```
 
+# <a name="flacExtractor"></a> Fuzzer for libflacextractor
+
+## Plugin Design Considerations
+The fuzzer plugin for FLAC extractor uses the `ExtractorFuzzerBase` class and
+implements only the `createExtractor` to create the FLAC extractor object.
+
+##### Maximize code coverage
+Dict file (dictionary file) is created for FLAC to ensure that the required start
+bytes are present in every input file that goes to the fuzzer.
+This ensures that larger code gets covered.
+
+
+## Build
+
+This describes steps to build flac_extractor_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+  $ mm -j$(nproc) flac_extractor_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some flac files to that folder
+Push this directory to device.
+
+To run on device
+```
+  $ adb sync data
+  $ adb shell /data/fuzz/arm64/flac_extractor_fuzzer/flac_extractor_fuzzer CORPUS_DIR
+```
+
 ## References:
  * http://llvm.org/docs/LibFuzzer.html
  * https://github.com/google/oss-fuzz
diff --git a/media/extractors/fuzzers/amr_extractor_fuzzer.cpp b/media/extractors/fuzzers/amr_extractor_fuzzer.cpp
new file mode 100644
index 0000000..b2f9261
--- /dev/null
+++ b/media/extractors/fuzzers/amr_extractor_fuzzer.cpp
@@ -0,0 +1,62 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include "ExtractorFuzzerBase.h"
+
+#include "AMRExtractor.h"
+
+using namespace android;
+
+class AmrExtractor : public ExtractorFuzzerBase {
+ public:
+  AmrExtractor() = default;
+  ~AmrExtractor() = default;
+
+  bool createExtractor();
+};
+
+bool AmrExtractor::createExtractor() {
+  mExtractor = new AMRExtractor(new DataSourceHelper(mDataSource->wrap()));
+  if (!mExtractor) {
+    return false;
+  }
+  mExtractor->name();
+  return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if ((!data) || (size == 0)) {
+    return 0;
+  }
+  AmrExtractor* extractor = new AmrExtractor();
+  if (!extractor) {
+    return 0;
+  }
+  if (extractor->setDataSource(data, size)) {
+    if (extractor->createExtractor()) {
+      extractor->getExtractorDef();
+      extractor->getMetadata();
+      extractor->extractTracks();
+      extractor->getTracksMetadata();
+    }
+  }
+  delete extractor;
+  return 0;
+}
diff --git a/media/extractors/fuzzers/amr_extractor_fuzzer.dict b/media/extractors/fuzzers/amr_extractor_fuzzer.dict
new file mode 100644
index 0000000..bc5726c
--- /dev/null
+++ b/media/extractors/fuzzers/amr_extractor_fuzzer.dict
@@ -0,0 +1,2 @@
+# Start code
+kw1="#!AMR"
diff --git a/media/extractors/fuzzers/flac_extractor_fuzzer.cpp b/media/extractors/fuzzers/flac_extractor_fuzzer.cpp
new file mode 100644
index 0000000..61e41cf
--- /dev/null
+++ b/media/extractors/fuzzers/flac_extractor_fuzzer.cpp
@@ -0,0 +1,62 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************
+ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
+ */
+
+#include "ExtractorFuzzerBase.h"
+
+#include "FLACExtractor.h"
+
+using namespace android;
+
+class FlacExtractor : public ExtractorFuzzerBase {
+ public:
+  FlacExtractor() = default;
+  ~FlacExtractor() = default;
+
+  bool createExtractor();
+};
+
+bool FlacExtractor::createExtractor() {
+  mExtractor = new FLACExtractor(new DataSourceHelper(mDataSource->wrap()));
+  if (!mExtractor) {
+    return false;
+  }
+  mExtractor->name();
+  return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if ((!data) || (size == 0)) {
+    return 0;
+  }
+  FlacExtractor* extractor = new FlacExtractor();
+  if (!extractor) {
+    return 0;
+  }
+  if (extractor->setDataSource(data, size)) {
+    if (extractor->createExtractor()) {
+      extractor->getExtractorDef();
+      extractor->getMetadata();
+      extractor->extractTracks();
+      extractor->getTracksMetadata();
+    }
+  }
+  delete extractor;
+  return 0;
+}
diff --git a/media/extractors/fuzzers/flac_extractor_fuzzer.dict b/media/extractors/fuzzers/flac_extractor_fuzzer.dict
new file mode 100644
index 0000000..53ad44f
--- /dev/null
+++ b/media/extractors/fuzzers/flac_extractor_fuzzer.dict
@@ -0,0 +1,3 @@
+# Start code (bytes 0-3)
+# The below 4 bytes correspond to "fLaC" in ASCII
+kw1="\x66\x4C\x61\x43"
diff --git a/media/extractors/mp3/MP3Extractor.cpp b/media/extractors/mp3/MP3Extractor.cpp
index a838ae6..5165822 100644
--- a/media/extractors/mp3/MP3Extractor.cpp
+++ b/media/extractors/mp3/MP3Extractor.cpp
@@ -227,17 +227,17 @@
 
 private:
     static const size_t kMaxFrameSize;
-    AMediaFormat *mMeta;
-    DataSourceHelper *mDataSource;
-    off64_t mFirstFramePos;
-    uint32_t mFixedHeader;
-    off64_t mCurrentPos;
-    int64_t mCurrentTimeUs;
-    bool mStarted;
-    MP3Seeker *mSeeker;
+    AMediaFormat *mMeta = NULL;
+    DataSourceHelper *mDataSource = NULL;
+    off64_t mFirstFramePos = 0;
+    uint32_t mFixedHeader = 0;
+    off64_t mCurrentPos = 0;
+    int64_t mCurrentTimeUs = 0;
+    bool mStarted = false;
+    MP3Seeker *mSeeker = NULL;
 
-    int64_t mBasisTimeUs;
-    int64_t mSamplesRead;
+    int64_t mBasisTimeUs = 0;
+    int64_t mSamplesRead = 0;
 
     MP3Source(const MP3Source &);
     MP3Source &operator=(const MP3Source &);
@@ -251,11 +251,7 @@
 
 MP3Extractor::MP3Extractor(
         DataSourceHelper *source, Mp3Meta *meta)
-    : mInitCheck(NO_INIT),
-      mDataSource(source),
-      mFirstFramePos(-1),
-      mFixedHeader(0),
-      mSeeker(NULL) {
+    : mDataSource(source) {
 
     off64_t pos = 0;
     off64_t post_id3_pos;
@@ -442,6 +438,7 @@
 //  (8000 samples/sec * 8 bits/byte)) + 1 padding byte/frame = 2881 bytes/frame.
 // Set our max frame size to the nearest power of 2 above this size (aka, 4kB)
 const size_t MP3Source::kMaxFrameSize = (1 << 12); /* 4096 bytes */
+
 MP3Source::MP3Source(
         AMediaFormat *meta, DataSourceHelper *source,
         off64_t first_frame_pos, uint32_t fixed_header,
@@ -450,12 +447,7 @@
       mDataSource(source),
       mFirstFramePos(first_frame_pos),
       mFixedHeader(fixed_header),
-      mCurrentPos(0),
-      mCurrentTimeUs(0),
-      mStarted(false),
-      mSeeker(seeker),
-      mBasisTimeUs(0),
-      mSamplesRead(0) {
+      mSeeker(seeker) {
 }
 
 MP3Source::~MP3Source() {
diff --git a/media/extractors/mp3/MP3Extractor.h b/media/extractors/mp3/MP3Extractor.h
index 1e38ab7..a2345da 100644
--- a/media/extractors/mp3/MP3Extractor.h
+++ b/media/extractors/mp3/MP3Extractor.h
@@ -45,13 +45,13 @@
     virtual const char * name() { return "MP3Extractor"; }
 
 private:
-    status_t mInitCheck;
+    status_t mInitCheck = NO_INIT;
 
-    DataSourceHelper *mDataSource;
-    off64_t mFirstFramePos;
-    AMediaFormat *mMeta;
-    uint32_t mFixedHeader;
-    MP3Seeker *mSeeker;
+    DataSourceHelper *mDataSource = NULL;
+    off64_t mFirstFramePos = -1;
+    AMediaFormat *mMeta = NULL;
+    uint32_t mFixedHeader = 0;
+    MP3Seeker *mSeeker = NULL;
 
     MP3Extractor(const MP3Extractor &);
     MP3Extractor &operator=(const MP3Extractor &);
diff --git a/media/libaaudio/include/aaudio/AAudio.h b/media/libaaudio/include/aaudio/AAudio.h
index edc09a9..a47f189 100644
--- a/media/libaaudio/include/aaudio/AAudio.h
+++ b/media/libaaudio/include/aaudio/AAudio.h
@@ -406,6 +406,7 @@
      * Use this preset for capturing audio meant to be processed in real time
      * and played back for live performance (e.g karaoke).
      * The capture path will minimize latency and coupling with playback path.
+     * Available since API level 29.
      */
     AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE = 10,
 };
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index 17d389e..0644368 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -106,21 +106,23 @@
 }
 
 void AudioStream::logOpen() {
-    LOG_ALWAYS_FATAL_IF(mMetricsId.size() == 0, "mMetricsId is empty!");
-    android::mediametrics::LogItem(mMetricsId)
-            .set(AMEDIAMETRICS_PROP_PERFORMANCEMODE,
-                    AudioGlobal_convertPerformanceModeToText(getPerformanceMode()))
-            .set(AMEDIAMETRICS_PROP_SHARINGMODE,
-                    AudioGlobal_convertSharingModeToText(getSharingMode()))
-            .record();
+    if (mMetricsId.size() > 0) {
+        android::mediametrics::LogItem(mMetricsId)
+                .set(AMEDIAMETRICS_PROP_PERFORMANCEMODE,
+                     AudioGlobal_convertPerformanceModeToText(getPerformanceMode()))
+                .set(AMEDIAMETRICS_PROP_SHARINGMODE,
+                     AudioGlobal_convertSharingModeToText(getSharingMode()))
+                .record();
+    }
 }
 
 void AudioStream::logBufferState() {
-    LOG_ALWAYS_FATAL_IF(mMetricsId.size() == 0, "mMetricsId is empty!");
-    android::mediametrics::LogItem(mMetricsId)
-            .set(AMEDIAMETRICS_PROP_BUFFERSIZEFRAMES, (int32_t) getBufferSize())
-            .set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t) getXRunCount())
-            .record();
+    if (mMetricsId.size() > 0) {
+        android::mediametrics::LogItem(mMetricsId)
+                .set(AMEDIAMETRICS_PROP_BUFFERSIZEFRAMES, (int32_t) getBufferSize())
+                .set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t) getXRunCount())
+                .record();
+    }
 }
 
 aaudio_result_t AudioStream::systemStart() {
diff --git a/media/libmedia/Android.bp b/media/libmedia/Android.bp
index f888752..8a6a5dc 100644
--- a/media/libmedia/Android.bp
+++ b/media/libmedia/Android.bp
@@ -15,6 +15,18 @@
     ],
 }
 
+cc_library_headers {
+    name: "libmedia_datasource_headers",
+    export_include_dirs: ["include"],
+    host_supported: true,
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+    apex_available: ["com.android.media"],
+}
+
 filegroup {
     name: "libmedia_omx_aidl",
     srcs: [
diff --git a/media/libmedia/aidl/android/media/IResourceManagerService.aidl b/media/libmedia/aidl/android/media/IResourceManagerService.aidl
index 3dd0859..1b2d522 100644
--- a/media/libmedia/aidl/android/media/IResourceManagerService.aidl
+++ b/media/libmedia/aidl/android/media/IResourceManagerService.aidl
@@ -94,4 +94,12 @@
      *        remove existing override on originalPid if newPid is -1.
      */
     void overridePid(int originalPid, int newPid);
+
+    /**
+     * Mark a client for pending removal
+     *
+     * @param pid pid from which the client's resources will be removed.
+     * @param clientId clientId within the pid that will be removed.
+     */
+    void markClientForPendingRemoval(int pid, long clientId);
 }
diff --git a/media/libmediametrics/include/MediaMetricsConstants.h b/media/libmediametrics/include/MediaMetricsConstants.h
index 00da69a..ec0e133 100644
--- a/media/libmediametrics/include/MediaMetricsConstants.h
+++ b/media/libmediametrics/include/MediaMetricsConstants.h
@@ -37,6 +37,11 @@
 // They must be appended with another value to make a key.
 #define AMEDIAMETRICS_KEY_PREFIX_AUDIO "audio."
 
+// The AudioMmap key appends the "trackId" to the prefix.
+// This is the AudioFlinger equivalent of the AAudio Stream.
+// TODO: unify with AMEDIAMETRICS_KEY_PREFIX_AUDIO_STREAM
+#define AMEDIAMETRICS_KEY_PREFIX_AUDIO_MMAP  AMEDIAMETRICS_KEY_PREFIX_AUDIO "mmap."
+
 // The AudioRecord key appends the "trackId" to the prefix.
 #define AMEDIAMETRICS_KEY_PREFIX_AUDIO_RECORD AMEDIAMETRICS_KEY_PREFIX_AUDIO "record."
 
@@ -95,6 +100,14 @@
 #define AMEDIAMETRICS_PROP_CHANNELCOUNT   "channelCount"   // int32
 #define AMEDIAMETRICS_PROP_CHANNELMASK    "channelMask"    // int32
 #define AMEDIAMETRICS_PROP_CONTENTTYPE    "contentType"    // string attributes (AudioTrack)
+#define AMEDIAMETRICS_PROP_CUMULATIVETIMENS "cumulativeTimeNs" // int64_t playback/record time
+                                                           // since start
+// DEVICE values are averaged since starting on device
+#define AMEDIAMETRICS_PROP_DEVICELATENCYMS "deviceLatencyMs" // double - avg latency time
+#define AMEDIAMETRICS_PROP_DEVICESTARTUPMS "deviceStartupMs" // double - avg startup time
+#define AMEDIAMETRICS_PROP_DEVICETIMENS   "deviceTimeNs"   // int64_t playback/record time
+#define AMEDIAMETRICS_PROP_DEVICEVOLUME   "deviceVolume"   // double - average device volume
+
 #define AMEDIAMETRICS_PROP_DIRECTION      "direction"      // string AAudio input or output
 #define AMEDIAMETRICS_PROP_DURATIONNS     "durationNs"     // int64 duration time span
 #define AMEDIAMETRICS_PROP_ENCODING       "encoding"       // string value of format
@@ -105,7 +118,9 @@
 
 #define AMEDIAMETRICS_PROP_FRAMECOUNT     "frameCount"     // int32
 #define AMEDIAMETRICS_PROP_INPUTDEVICES   "inputDevices"   // string value
+#define AMEDIAMETRICS_PROP_INTERVALCOUNT  "intervalCount"  // int32
 #define AMEDIAMETRICS_PROP_LATENCYMS      "latencyMs"      // double value
+#define AMEDIAMETRICS_PROP_NAME           "name"           // string value
 #define AMEDIAMETRICS_PROP_ORIGINALFLAGS  "originalFlags"  // int32
 #define AMEDIAMETRICS_PROP_OUTPUTDEVICES  "outputDevices"  // string value
 #define AMEDIAMETRICS_PROP_PERFORMANCEMODE "performanceMode"    // string value, "none", lowLatency"
@@ -129,6 +144,7 @@
 #define AMEDIAMETRICS_PROP_TRACKID        "trackId"        // int32 port id of track/record
 #define AMEDIAMETRICS_PROP_TYPE           "type"           // string (thread type)
 #define AMEDIAMETRICS_PROP_UNDERRUN       "underrun"       // int32
+#define AMEDIAMETRICS_PROP_UNDERRUNFRAMES "underrunFrames" // int64_t from Thread
 #define AMEDIAMETRICS_PROP_USAGE          "usage"          // string attributes (ATrack)
 #define AMEDIAMETRICS_PROP_VOLUME_LEFT    "volume.left"    // double (AudioTrack)
 #define AMEDIAMETRICS_PROP_VOLUME_RIGHT   "volume.right"   // double (AudioTrack)
@@ -140,12 +156,14 @@
 // Values are strings accepted for a given property.
 
 // An event is a general description, which often is a function name.
+#define AMEDIAMETRICS_PROP_EVENT_VALUE_BEGINAUDIOINTERVALGROUP "beginAudioIntervalGroup"
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_CLOSE      "close"
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_CREATE     "create"
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_CREATEAUDIOPATCH "createAudioPatch"
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR       "ctor"
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_DISCONNECT "disconnect"
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_DTOR       "dtor"
+#define AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP "endAudioIntervalGroup"
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_FLUSH      "flush"  // AudioTrack
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_INVALIDATE "invalidate" // server track, record
 #define AMEDIAMETRICS_PROP_EVENT_VALUE_OPEN       "open"
diff --git a/media/libmediaplayerservice/Android.bp b/media/libmediaplayerservice/Android.bp
index 902d846..324f4ae 100644
--- a/media/libmediaplayerservice/Android.bp
+++ b/media/libmediaplayerservice/Android.bp
@@ -16,7 +16,6 @@
         "android.hardware.media.c2@1.0",
         "android.hardware.media.omx@1.0",
         "libbase",
-        "libandroid",
         "libandroid_net",
         "libaudioclient",
         "libbinder",
diff --git a/media/libmediaplayerservice/nuplayer/Android.bp b/media/libmediaplayerservice/nuplayer/Android.bp
index 7206eab..f5e44c7 100644
--- a/media/libmediaplayerservice/nuplayer/Android.bp
+++ b/media/libmediaplayerservice/nuplayer/Android.bp
@@ -48,8 +48,6 @@
     },
 
     shared_libs: [
-        "libandroid",
-        "libandroid_net",
         "libbinder",
         "libdatasource",
         "libui",
diff --git a/media/libmediatranscoding/Android.bp b/media/libmediatranscoding/Android.bp
index a2c1af7..431f0be 100644
--- a/media/libmediatranscoding/Android.bp
+++ b/media/libmediatranscoding/Android.bp
@@ -45,6 +45,7 @@
     srcs: [
         "TranscodingClientManager.cpp",
         "TranscodingJobScheduler.cpp",
+        "TranscodingUidPolicy.cpp",
     ],
 
     shared_libs: [
@@ -53,6 +54,7 @@
         "liblog",
         "libutils",
         "libmediatranscoder",
+        "libbinder",
     ],
 
     export_include_dirs: ["include"],
diff --git a/media/libmediatranscoding/TranscodingClientManager.cpp b/media/libmediatranscoding/TranscodingClientManager.cpp
index bfa08a2..fd87cb4 100644
--- a/media/libmediatranscoding/TranscodingClientManager.cpp
+++ b/media/libmediatranscoding/TranscodingClientManager.cpp
@@ -43,7 +43,8 @@
      * object doesn't get created again, otherwise the binder object pointer
      * may not be unique.
      */
-    SpAIBinder mClientCallback;
+    SpAIBinder mClientBinder;
+    std::shared_ptr<ITranscodingClientCallback> mClientCallback;
     /* A unique id assigned to the client by the service. This number is used
      * by the service for indexing. Here we use the binder object's pointer
      * (casted to int64t_t) as the client id.
@@ -78,8 +79,9 @@
         const std::shared_ptr<ITranscodingClientCallback>& callback, pid_t pid, uid_t uid,
         const std::string& clientName, const std::string& opPackageName,
         TranscodingClientManager* owner)
-      : mClientCallback((callback != nullptr) ? callback->asBinder() : nullptr),
-        mClientId((int64_t)mClientCallback.get()),
+      : mClientBinder((callback != nullptr) ? callback->asBinder() : nullptr),
+        mClientCallback(callback),
+        mClientId((int64_t)mClientBinder.get()),
         mClientPid(pid),
         mClientUid(uid),
         mClientName(clientName),
@@ -98,9 +100,8 @@
 
     int32_t jobId = mNextJobId.fetch_add(1);
 
-    *_aidl_return =
-            mOwner->mJobScheduler->submit(mClientId, jobId, mClientPid, in_request,
-                                          ITranscodingClientCallback::fromBinder(mClientCallback));
+    *_aidl_return = mOwner->mJobScheduler->submit(mClientId, jobId, mClientUid, in_request,
+                                                  mClientCallback);
 
     if (*_aidl_return) {
         out_job->jobId = jobId;
@@ -205,7 +206,7 @@
           (long long)client->mClientId, client->mClientPid, client->mClientUid,
           client->mClientName.c_str(), client->mClientOpPackageName.c_str());
 
-    AIBinder_linkToDeath(client->mClientCallback.get(), mDeathRecipient.get(),
+    AIBinder_linkToDeath(client->mClientBinder.get(), mDeathRecipient.get(),
                          reinterpret_cast<void*>(client.get()));
 
     // Adds the new client to the map.
@@ -227,7 +228,7 @@
         return INVALID_OPERATION;
     }
 
-    SpAIBinder callback = it->second->mClientCallback;
+    SpAIBinder callback = it->second->mClientBinder;
 
     // Check if the client still live. If alive, unlink the death.
     if (callback.get() != nullptr) {
diff --git a/media/libmediatranscoding/TranscodingJobScheduler.cpp b/media/libmediatranscoding/TranscodingJobScheduler.cpp
index 9dd070c..83d7a82 100644
--- a/media/libmediatranscoding/TranscodingJobScheduler.cpp
+++ b/media/libmediatranscoding/TranscodingJobScheduler.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-// #define LOG_NDEBUG 0
+//#define LOG_NDEBUG 0
 #define LOG_TAG "TranscodingJobScheduler"
 
 #define VALIDATE_STATE 1
@@ -27,7 +27,7 @@
 
 namespace android {
 
-constexpr static pid_t OFFLINE_PID = -1;
+constexpr static uid_t OFFLINE_UID = -1;
 
 //static
 String8 TranscodingJobScheduler::jobToString(const JobKeyType& jobKey) {
@@ -36,12 +36,12 @@
 
 TranscodingJobScheduler::TranscodingJobScheduler(
         const std::shared_ptr<TranscoderInterface>& transcoder,
-        const std::shared_ptr<ProcessInfoInterface>& procInfo)
-      : mTranscoder(transcoder), mProcInfo(procInfo), mCurrentJob(nullptr), mResourceLost(false) {
+        const std::shared_ptr<UidPolicyInterface>& uidPolicy)
+      : mTranscoder(transcoder), mUidPolicy(uidPolicy), mCurrentJob(nullptr), mResourceLost(false) {
     // Only push empty offline queue initially. Realtime queues are added when requests come in.
-    mPidSortedList.push_back(OFFLINE_PID);
-    mOfflinePidIterator = mPidSortedList.begin();
-    mJobQueues.emplace(OFFLINE_PID, JobQueueType());
+    mUidSortedList.push_back(OFFLINE_UID);
+    mOfflineUidIterator = mUidSortedList.begin();
+    mJobQueues.emplace(OFFLINE_UID, JobQueueType());
 }
 
 TranscodingJobScheduler::~TranscodingJobScheduler() {}
@@ -50,8 +50,8 @@
     if (mJobMap.empty()) {
         return nullptr;
     }
-    pid_t topPid = *mPidSortedList.begin();
-    JobKeyType topJobKey = *mJobQueues[topPid].begin();
+    uid_t topUid = *mUidSortedList.begin();
+    JobKeyType topJobKey = *mJobQueues[topUid].begin();
     return &mJobMap[topJobKey];
 }
 
@@ -92,20 +92,24 @@
         return;
     }
 
-    // Remove job from pid's queue.
-    const pid_t pid = mJobMap[jobKey].pid;
-    JobQueueType& jobQueue = mJobQueues[pid];
+    // Remove job from uid's queue.
+    const uid_t uid = mJobMap[jobKey].uid;
+    JobQueueType& jobQueue = mJobQueues[uid];
     auto it = std::find(jobQueue.begin(), jobQueue.end(), jobKey);
     if (it == jobQueue.end()) {
-        ALOGE("couldn't find job %s in queue for pid %d", jobToString(jobKey).c_str(), pid);
+        ALOGE("couldn't find job %s in queue for uid %d", jobToString(jobKey).c_str(), uid);
         return;
     }
     jobQueue.erase(it);
 
-    // If this is the last job in a real-time queue, remove this pid's queue.
-    if (pid != OFFLINE_PID && jobQueue.empty()) {
-        mPidSortedList.remove(pid);
-        mJobQueues.erase(pid);
+    // If this is the last job in a real-time queue, remove this uid's queue.
+    if (uid != OFFLINE_UID && jobQueue.empty()) {
+        mUidSortedList.remove(uid);
+        mJobQueues.erase(uid);
+        mUidPolicy->unregisterMonitorUid(uid);
+
+        std::unordered_set<uid_t> topUids = mUidPolicy->getTopUids();
+        moveUidsToTop_l(topUids, false /*preserveTopUid*/);
     }
 
     // Clear current job.
@@ -117,12 +121,65 @@
     mJobMap.erase(jobKey);
 }
 
-bool TranscodingJobScheduler::submit(ClientIdType clientId, int32_t jobId, pid_t pid,
+/**
+ * Moves the set of uids to the front of mUidSortedList (which is used to pick
+ * the next job to run).
+ *
+ * This is called when 1) we received a onTopUidsChanged() callbcak from UidPolicy,
+ * or 2) we removed the job queue for a uid because it becomes empty.
+ *
+ * In case of 1), if there are multiple uids in the set, and the current front
+ * uid in mUidSortedList is still in the set, we try to keep that uid at front
+ * so that current job run is not interrupted. (This is not a concern for case 2)
+ * because the queue for a uid was just removed entirely.)
+ */
+void TranscodingJobScheduler::moveUidsToTop_l(const std::unordered_set<uid_t>& uids,
+                                              bool preserveTopUid) {
+    // If uid set is empty, nothing to do. Do not change the queue status.
+    if (uids.empty()) {
+        return;
+    }
+
+    // Save the current top uid.
+    uid_t curTopUid = *mUidSortedList.begin();
+    bool pushCurTopToFront = false;
+    int32_t numUidsMoved = 0;
+
+    // Go through the sorted uid list once, and move the ones in top set to front.
+    for (auto it = mUidSortedList.begin(); it != mUidSortedList.end();) {
+        uid_t uid = *it;
+
+        if (uid != OFFLINE_UID && uids.count(uid) > 0) {
+            it = mUidSortedList.erase(it);
+
+            // If this is the top we're preserving, don't push it here, push
+            // it after the for-loop.
+            if (uid == curTopUid && preserveTopUid) {
+                pushCurTopToFront = true;
+            } else {
+                mUidSortedList.push_front(uid);
+            }
+
+            // If we found all uids in the set, break out.
+            if (++numUidsMoved == uids.size()) {
+                break;
+            }
+        } else {
+            ++it;
+        }
+    }
+
+    if (pushCurTopToFront) {
+        mUidSortedList.push_front(curTopUid);
+    }
+}
+
+bool TranscodingJobScheduler::submit(ClientIdType clientId, int32_t jobId, uid_t uid,
                                      const TranscodingRequestParcel& request,
                                      const std::weak_ptr<ITranscodingClientCallback>& callback) {
     JobKeyType jobKey = std::make_pair(clientId, jobId);
 
-    ALOGV("%s: job %s, pid %d, prioirty %d", __FUNCTION__, jobToString(jobKey).c_str(), pid,
+    ALOGV("%s: job %s, uid %d, prioirty %d", __FUNCTION__, jobToString(jobKey).c_str(), uid,
           (int32_t)request.priority);
 
     std::scoped_lock lock{mLock};
@@ -135,37 +192,38 @@
     // TODO(chz): only support offline vs real-time for now. All kUnspecified jobs
     // go to offline queue.
     if (request.priority == TranscodingJobPriority::kUnspecified) {
-        pid = OFFLINE_PID;
+        uid = OFFLINE_UID;
     }
 
     // Add job to job map.
     mJobMap[jobKey].key = jobKey;
-    mJobMap[jobKey].pid = pid;
+    mJobMap[jobKey].uid = uid;
     mJobMap[jobKey].state = Job::NOT_STARTED;
     mJobMap[jobKey].request = request;
     mJobMap[jobKey].callback = callback;
 
     // If it's an offline job, the queue was already added in constructor.
-    // If it's a real-time jobs, check if a queue is already present for the pid,
+    // If it's a real-time jobs, check if a queue is already present for the uid,
     // and add a new queue if needed.
-    if (pid != OFFLINE_PID) {
-        if (mJobQueues.count(pid) == 0) {
-            if (mProcInfo->isProcessOnTop(pid)) {
-                mPidSortedList.push_front(pid);
+    if (uid != OFFLINE_UID) {
+        if (mJobQueues.count(uid) == 0) {
+            mUidPolicy->registerMonitorUid(uid);
+            if (mUidPolicy->isUidOnTop(uid)) {
+                mUidSortedList.push_front(uid);
             } else {
                 // Shouldn't be submitting real-time requests from non-top app,
                 // put it in front of the offline queue.
-                mPidSortedList.insert(mOfflinePidIterator, pid);
+                mUidSortedList.insert(mOfflineUidIterator, uid);
             }
-        } else if (pid != *mPidSortedList.begin()) {
-            if (mProcInfo->isProcessOnTop(pid)) {
-                mPidSortedList.remove(pid);
-                mPidSortedList.push_front(pid);
+        } else if (uid != *mUidSortedList.begin()) {
+            if (mUidPolicy->isUidOnTop(uid)) {
+                mUidSortedList.remove(uid);
+                mUidSortedList.push_front(uid);
             }
         }
     }
-    // Append this job to the pid's queue.
-    mJobQueues[pid].push_back(jobKey);
+    // Append this job to the uid's queue.
+    mJobQueues[uid].push_back(jobKey);
 
     updateCurrentJob_l();
 
@@ -222,7 +280,7 @@
     std::scoped_lock lock{mLock};
 
     if (mJobMap.count(jobKey) == 0) {
-        ALOGW("ignoring abort for non-existent job");
+        ALOGW("ignoring finish for non-existent job");
         return;
     }
 
@@ -230,7 +288,7 @@
     // to client if the job is paused. Transcoder could have posted finish when
     // we're pausing it, and the finish arrived after we changed current job.
     if (mJobMap[jobKey].state == Job::NOT_STARTED) {
-        ALOGW("ignoring abort for job that was never started");
+        ALOGW("ignoring finish for job that was never started");
         return;
     }
 
@@ -258,7 +316,7 @@
     std::scoped_lock lock{mLock};
 
     if (mJobMap.count(jobKey) == 0) {
-        ALOGW("ignoring abort for non-existent job");
+        ALOGW("ignoring error for non-existent job");
         return;
     }
 
@@ -266,7 +324,7 @@
     // to client if the job is paused. Transcoder could have posted finish when
     // we're pausing it, and the finish arrived after we changed current job.
     if (mJobMap[jobKey].state == Job::NOT_STARTED) {
-        ALOGW("ignoring abort for job that was never started");
+        ALOGW("ignoring error for job that was never started");
         return;
     }
 
@@ -286,6 +344,34 @@
     validateState_l();
 }
 
+void TranscodingJobScheduler::onProgressUpdate(int64_t clientId, int32_t jobId, int32_t progress) {
+    JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+    ALOGV("%s: job %s, progress %d", __FUNCTION__, jobToString(jobKey).c_str(), progress);
+
+    std::scoped_lock lock{mLock};
+
+    if (mJobMap.count(jobKey) == 0) {
+        ALOGW("ignoring progress for non-existent job");
+        return;
+    }
+
+    // Only ignore if job was never started. In particular, propagate the status
+    // to client if the job is paused. Transcoder could have posted finish when
+    // we're pausing it, and the finish arrived after we changed current job.
+    if (mJobMap[jobKey].state == Job::NOT_STARTED) {
+        ALOGW("ignoring progress for job that was never started");
+        return;
+    }
+
+    {
+        auto clientCallback = mJobMap[jobKey].callback.lock();
+        if (clientCallback != nullptr) {
+            clientCallback->onProgressUpdate(jobId, progress);
+        }
+    }
+}
+
 void TranscodingJobScheduler::onResourceLost() {
     ALOGV("%s", __FUNCTION__);
 
@@ -302,28 +388,25 @@
     validateState_l();
 }
 
-void TranscodingJobScheduler::onTopProcessChanged(pid_t pid) {
-    ALOGV("%s: pid %d", __FUNCTION__, pid);
+void TranscodingJobScheduler::onTopUidsChanged(const std::unordered_set<uid_t>& uids) {
+    if (uids.empty()) {
+        ALOGW("%s: ignoring empty uids", __FUNCTION__);
+        return;
+    }
+
+    std::string uidStr;
+    for (auto it = uids.begin(); it != uids.end(); it++) {
+        if (!uidStr.empty()) {
+            uidStr += ", ";
+        }
+        uidStr += std::to_string(*it);
+    }
+
+    ALOGD("%s: topUids: size %zu, uids: %s", __FUNCTION__, uids.size(), uidStr.c_str());
 
     std::scoped_lock lock{mLock};
 
-    if (pid < 0) {
-        ALOGW("bringProcessToTop: ignoring invalid pid %d", pid);
-        return;
-    }
-    // If this pid doesn't have any jobs, we don't care about it.
-    if (mJobQueues.count(pid) == 0) {
-        ALOGW("bringProcessToTop: ignoring pid %d without any jobs", pid);
-        return;
-    }
-    // If this pid is already top, don't do anything.
-    if (pid == *mPidSortedList.begin()) {
-        ALOGW("pid %d is already top", pid);
-        return;
-    }
-
-    mPidSortedList.remove(pid);
-    mPidSortedList.push_front(pid);
+    moveUidsToTop_l(uids, true /*preserveTopUid*/);
 
     updateCurrentJob_l();
 
@@ -343,26 +426,26 @@
 
 void TranscodingJobScheduler::validateState_l() {
 #ifdef VALIDATE_STATE
-    LOG_ALWAYS_FATAL_IF(mJobQueues.count(OFFLINE_PID) != 1,
+    LOG_ALWAYS_FATAL_IF(mJobQueues.count(OFFLINE_UID) != 1,
                         "mJobQueues offline queue number is not 1");
-    LOG_ALWAYS_FATAL_IF(*mOfflinePidIterator != OFFLINE_PID,
-                        "mOfflinePidIterator not pointing to offline pid");
-    LOG_ALWAYS_FATAL_IF(mPidSortedList.size() != mJobQueues.size(),
-                        "mPidList and mJobQueues size mismatch");
+    LOG_ALWAYS_FATAL_IF(*mOfflineUidIterator != OFFLINE_UID,
+                        "mOfflineUidIterator not pointing to offline uid");
+    LOG_ALWAYS_FATAL_IF(mUidSortedList.size() != mJobQueues.size(),
+                        "mUidList and mJobQueues size mismatch");
 
     int32_t totalJobs = 0;
-    for (auto pidIt = mPidSortedList.begin(); pidIt != mPidSortedList.end(); pidIt++) {
-        LOG_ALWAYS_FATAL_IF(mJobQueues.count(*pidIt) != 1, "mJobQueues count for pid %d is not 1",
-                            *pidIt);
-        for (auto jobIt = mJobQueues[*pidIt].begin(); jobIt != mJobQueues[*pidIt].end(); jobIt++) {
+    for (auto uidIt = mUidSortedList.begin(); uidIt != mUidSortedList.end(); uidIt++) {
+        LOG_ALWAYS_FATAL_IF(mJobQueues.count(*uidIt) != 1, "mJobQueues count for uid %d is not 1",
+                            *uidIt);
+        for (auto jobIt = mJobQueues[*uidIt].begin(); jobIt != mJobQueues[*uidIt].end(); jobIt++) {
             LOG_ALWAYS_FATAL_IF(mJobMap.count(*jobIt) != 1, "mJobs count for job %s is not 1",
                                 jobToString(*jobIt).c_str());
         }
 
-        totalJobs += mJobQueues[*pidIt].size();
+        totalJobs += mJobQueues[*uidIt].size();
     }
     LOG_ALWAYS_FATAL_IF(mJobMap.size() != totalJobs,
-                        "mJobs size doesn't match total jobs counted from pid queues");
+                        "mJobs size doesn't match total jobs counted from uid queues");
 #endif  // VALIDATE_STATE
 }
 
diff --git a/media/libmediatranscoding/TranscodingUidPolicy.cpp b/media/libmediatranscoding/TranscodingUidPolicy.cpp
new file mode 100644
index 0000000..9c8d3fe
--- /dev/null
+++ b/media/libmediatranscoding/TranscodingUidPolicy.cpp
@@ -0,0 +1,247 @@
+/*
+ * 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 "TranscodingUidPolicy"
+
+#include <binder/ActivityManager.h>
+#include <cutils/misc.h>  // FIRST_APPLICATION_UID
+#include <inttypes.h>
+#include <media/TranscodingUidPolicy.h>
+#include <utils/Log.h>
+
+#include <utility>
+
+namespace android {
+
+constexpr static uid_t OFFLINE_UID = -1;
+constexpr static const char* kTranscodingTag = "transcoding";
+
+struct TranscodingUidPolicy::UidObserver : public BnUidObserver,
+                                           public virtual IBinder::DeathRecipient {
+    explicit UidObserver(TranscodingUidPolicy* owner) : mOwner(owner) {}
+
+    // IUidObserver
+    void onUidGone(uid_t uid, bool disabled) override;
+    void onUidActive(uid_t uid) override;
+    void onUidIdle(uid_t uid, bool disabled) override;
+    void onUidStateChanged(uid_t uid, int32_t procState, int64_t procStateSeq,
+                           int32_t capability) override;
+
+    // IBinder::DeathRecipient implementation
+    void binderDied(const wp<IBinder>& who) override;
+
+    TranscodingUidPolicy* mOwner;
+};
+
+void TranscodingUidPolicy::UidObserver::onUidGone(uid_t uid __unused, bool disabled __unused) {}
+
+void TranscodingUidPolicy::UidObserver::onUidActive(uid_t uid __unused) {}
+
+void TranscodingUidPolicy::UidObserver::onUidIdle(uid_t uid __unused, bool disabled __unused) {}
+
+void TranscodingUidPolicy::UidObserver::onUidStateChanged(uid_t uid, int32_t procState,
+                                                          int64_t procStateSeq __unused,
+                                                          int32_t capability __unused) {
+    mOwner->onUidStateChanged(uid, procState);
+}
+
+void TranscodingUidPolicy::UidObserver::binderDied(const wp<IBinder>& /*who*/) {
+    ALOGW("TranscodingUidPolicy: ActivityManager has died");
+    // TODO(chz): this is a rare event (since if the AMS is dead, the system is
+    // probably dead as well). But we should try to reconnect.
+    mOwner->setUidObserverRegistered(false);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+TranscodingUidPolicy::TranscodingUidPolicy()
+      : mAm(std::make_shared<ActivityManager>()),
+        mUidObserver(new UidObserver(this)),
+        mRegistered(false),
+        mTopUidState(ActivityManager::PROCESS_STATE_UNKNOWN) {
+    registerSelf();
+}
+
+TranscodingUidPolicy::~TranscodingUidPolicy() {
+    unregisterSelf();
+}
+
+void TranscodingUidPolicy::registerSelf() {
+    status_t res = mAm->linkToDeath(mUidObserver.get());
+    mAm->registerUidObserver(
+            mUidObserver.get(),
+            ActivityManager::UID_OBSERVER_GONE | ActivityManager::UID_OBSERVER_IDLE |
+                    ActivityManager::UID_OBSERVER_ACTIVE | ActivityManager::UID_OBSERVER_PROCSTATE,
+            ActivityManager::PROCESS_STATE_UNKNOWN, String16(kTranscodingTag));
+
+    if (res == OK) {
+        Mutex::Autolock _l(mUidLock);
+
+        mRegistered = true;
+        ALOGI("TranscodingUidPolicy: Registered with ActivityManager");
+    } else {
+        mAm->unregisterUidObserver(mUidObserver.get());
+    }
+}
+
+void TranscodingUidPolicy::unregisterSelf() {
+    mAm->unregisterUidObserver(mUidObserver.get());
+    mAm->unlinkToDeath(mUidObserver.get());
+
+    Mutex::Autolock _l(mUidLock);
+
+    mRegistered = false;
+
+    ALOGI("TranscodingUidPolicy: Unregistered with ActivityManager");
+}
+
+void TranscodingUidPolicy::setUidObserverRegistered(bool registered) {
+    Mutex::Autolock _l(mUidLock);
+
+    mRegistered = registered;
+}
+
+void TranscodingUidPolicy::setCallback(const std::shared_ptr<UidPolicyCallbackInterface>& cb) {
+    mUidPolicyCallback = cb;
+}
+
+void TranscodingUidPolicy::registerMonitorUid(uid_t uid) {
+    Mutex::Autolock _l(mUidLock);
+    if (uid == OFFLINE_UID) {
+        ALOGW("Ignoring the offline uid");
+        return;
+    }
+    if (mUidStateMap.find(uid) != mUidStateMap.end()) {
+        ALOGE("%s: Trying to register uid: %d which is already monitored!", __FUNCTION__, uid);
+        return;
+    }
+
+    int32_t state = ActivityManager::PROCESS_STATE_UNKNOWN;
+    if (mRegistered && mAm->isUidActiveOrForeground(uid, String16(kTranscodingTag))) {
+        state = mAm->getUidProcessState(uid, String16(kTranscodingTag));
+    }
+
+    ALOGV("%s: inserting new uid: %u, procState %d", __FUNCTION__, uid, state);
+
+    mUidStateMap.emplace(std::pair<uid_t, int32_t>(uid, state));
+    mStateUidMap[state].insert(uid);
+
+    updateTopUid_l();
+}
+
+void TranscodingUidPolicy::unregisterMonitorUid(uid_t uid) {
+    Mutex::Autolock _l(mUidLock);
+
+    auto it = mUidStateMap.find(uid);
+    if (it == mUidStateMap.end()) {
+        ALOGE("%s: Trying to unregister uid: %d which is not monitored!", __FUNCTION__, uid);
+        return;
+    }
+
+    auto stateIt = mStateUidMap.find(it->second);
+    if (stateIt != mStateUidMap.end()) {
+        stateIt->second.erase(uid);
+        if (stateIt->second.empty()) {
+            mStateUidMap.erase(stateIt);
+        }
+    }
+    mUidStateMap.erase(it);
+
+    updateTopUid_l();
+}
+
+bool TranscodingUidPolicy::isUidOnTop(uid_t uid) {
+    Mutex::Autolock _l(mUidLock);
+
+    return mTopUidState != ActivityManager::PROCESS_STATE_UNKNOWN &&
+           mTopUidState == getProcState_l(uid);
+}
+
+std::unordered_set<uid_t> TranscodingUidPolicy::getTopUids() const {
+    Mutex::Autolock _l(mUidLock);
+
+    if (mTopUidState == ActivityManager::PROCESS_STATE_UNKNOWN) {
+        return std::unordered_set<uid_t>();
+    }
+
+    return mStateUidMap.at(mTopUidState);
+}
+
+void TranscodingUidPolicy::onUidStateChanged(uid_t uid, int32_t procState) {
+    ALOGV("onUidStateChanged: %u, procState %d", uid, procState);
+
+    bool topUidSetChanged = false;
+    std::unordered_set<uid_t> topUids;
+    {
+        Mutex::Autolock _l(mUidLock);
+        auto it = mUidStateMap.find(uid);
+        if (it != mUidStateMap.end() && it->second != procState) {
+            // Top set changed if 1) the uid is in the current top uid set, or 2) the
+            // new procState is at least the same priority as the current top uid state.
+            bool isUidCurrentTop = mTopUidState != ActivityManager::PROCESS_STATE_UNKNOWN &&
+                                   mStateUidMap[mTopUidState].count(uid) > 0;
+            bool isNewStateHigherThanTop = procState != ActivityManager::PROCESS_STATE_UNKNOWN &&
+                                           (procState <= mTopUidState ||
+                                            mTopUidState == ActivityManager::PROCESS_STATE_UNKNOWN);
+            topUidSetChanged = (isUidCurrentTop || isNewStateHigherThanTop);
+
+            // Move uid to the new procState.
+            mStateUidMap[it->second].erase(uid);
+            mStateUidMap[procState].insert(uid);
+            it->second = procState;
+
+            if (topUidSetChanged) {
+                updateTopUid_l();
+
+                // Make a copy of the uid set for callback.
+                topUids = mStateUidMap[mTopUidState];
+            }
+        }
+    }
+
+    ALOGV("topUidSetChanged: %d", topUidSetChanged);
+
+    if (topUidSetChanged) {
+        auto callback = mUidPolicyCallback.lock();
+        if (callback != nullptr) {
+            callback->onTopUidsChanged(topUids);
+        }
+    }
+}
+
+void TranscodingUidPolicy::updateTopUid_l() {
+    // Update top uid state.
+    mTopUidState = ActivityManager::PROCESS_STATE_UNKNOWN;
+    for (auto stateIt = mStateUidMap.begin(); stateIt != mStateUidMap.end(); stateIt++) {
+        if (stateIt->first != ActivityManager::PROCESS_STATE_UNKNOWN && !stateIt->second.empty()) {
+            mTopUidState = stateIt->first;
+            break;
+        }
+    }
+
+    ALOGV("%s: top uid state is %d", __FUNCTION__, mTopUidState);
+}
+
+int32_t TranscodingUidPolicy::getProcState_l(uid_t uid) {
+    auto it = mUidStateMap.find(uid);
+    if (it != mUidStateMap.end()) {
+        return it->second;
+    }
+    return ActivityManager::PROCESS_STATE_UNKNOWN;
+}
+
+}  // namespace android
diff --git a/media/libmediatranscoding/include/media/ProcessInfoInterface.h b/media/libmediatranscoding/include/media/ProcessInfoInterface.h
deleted file mode 100644
index ef79266..0000000
--- a/media/libmediatranscoding/include/media/ProcessInfoInterface.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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_PROCESS_INFO_INTERFACE_H
-#define ANDROID_MEDIA_PROCESS_INFO_INTERFACE_H
-
-namespace android {
-
-// Interface for the scheduler to query a process's info.
-class ProcessInfoInterface {
-public:
-    // Determines if a process is currently running as top process.
-    // TODO(chz): this should probably be replaced by a query that determines
-    // which pid has the highest priority among a given set of pids. For now,
-    // we assume that there is a way to determine based on a pid number whether
-    // that pid is on "top", but this may not be possible in some cases, for
-    // example, the client process with highest priority is actually a foreground
-    // service (serving the top-app), but technically is not "top".
-    virtual bool isProcessOnTop(pid_t pid) = 0;
-
-protected:
-    virtual ~ProcessInfoInterface() = default;
-};
-
-// Interface for notifying the scheduler of a change in a process's state or
-// transcoding resource availability.
-class ProcessInfoCallbackInterface {
-public:
-    // Called when a process with pid is brought to top.
-    // TODO(chz): this should probably be replace by a callback when the pid
-    // that was previously identified being the highest priority as in
-    // ProcessInfoInterface::isProcessOnTop() has changed in priority.
-    virtual void onTopProcessChanged(pid_t pid) = 0;
-
-    // Called when resources become available for transcoding use. The scheduler
-    // may use this as a signal to attempt restart transcoding activity that
-    // were previously paused due to temporary resource loss.
-    virtual void onResourceAvailable() = 0;
-
-protected:
-    virtual ~ProcessInfoCallbackInterface() = default;
-};
-
-}  // namespace android
-#endif  // ANDROID_MEDIA_PROCESS_INFO_INTERFACE_H
diff --git a/media/libmediatranscoding/include/media/SchedulerClientInterface.h b/media/libmediatranscoding/include/media/SchedulerClientInterface.h
index b4cf463..6ccf117 100644
--- a/media/libmediatranscoding/include/media/SchedulerClientInterface.h
+++ b/media/libmediatranscoding/include/media/SchedulerClientInterface.h
@@ -31,7 +31,7 @@
 // the status of a job.
 class SchedulerClientInterface {
 public:
-    virtual bool submit(ClientIdType clientId, int32_t jobId, pid_t pid,
+    virtual bool submit(ClientIdType clientId, int32_t jobId, uid_t uid,
                         const TranscodingRequestParcel& request,
                         const std::weak_ptr<ITranscodingClientCallback>& clientCallback) = 0;
 
diff --git a/media/libmediatranscoding/include/media/TranscoderInterface.h b/media/libmediatranscoding/include/media/TranscoderInterface.h
index d74135a..a2afa00 100644
--- a/media/libmediatranscoding/include/media/TranscoderInterface.h
+++ b/media/libmediatranscoding/include/media/TranscoderInterface.h
@@ -43,6 +43,7 @@
     // TODO(chz): determine what parameters are needed here.
     virtual void onFinish(int64_t clientId, int32_t jobId) = 0;
     virtual void onError(int64_t clientId, int32_t jobId, TranscodingErrorCode err) = 0;
+    virtual void onProgressUpdate(int64_t clientId, int32_t jobId, int32_t progress) = 0;
 
     // Called when transcoding becomes temporarily inaccessible due to loss of resource.
     // If there is any job currently running, it will be paused. When resource contention
diff --git a/media/libmediatranscoding/include/media/TranscodingJobScheduler.h b/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
index f4d98ba..0838977 100644
--- a/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
+++ b/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
@@ -18,10 +18,10 @@
 #define ANDROID_MEDIA_TRANSCODING_JOB_SCHEDULER_H
 
 #include <aidl/android/media/TranscodingJobPriority.h>
-#include <media/ProcessInfoInterface.h>
 #include <media/SchedulerClientInterface.h>
 #include <media/TranscoderInterface.h>
 #include <media/TranscodingRequest.h>
+#include <media/UidPolicyInterface.h>
 #include <utils/String8.h>
 
 #include <list>
@@ -32,14 +32,14 @@
 using ::aidl::android::media::TranscodingJobPriority;
 using ::aidl::android::media::TranscodingResultParcel;
 
-class TranscodingJobScheduler : public ProcessInfoCallbackInterface,
+class TranscodingJobScheduler : public UidPolicyCallbackInterface,
                                 public SchedulerClientInterface,
                                 public TranscoderCallbackInterface {
 public:
     virtual ~TranscodingJobScheduler();
 
     // SchedulerClientInterface
-    bool submit(ClientIdType clientId, int32_t jobId, pid_t pid,
+    bool submit(ClientIdType clientId, int32_t jobId, uid_t uid,
                 const TranscodingRequestParcel& request,
                 const std::weak_ptr<ITranscodingClientCallback>& clientCallback) override;
     bool cancel(ClientIdType clientId, int32_t jobId) override;
@@ -49,13 +49,14 @@
     // TranscoderCallbackInterface
     void onFinish(ClientIdType clientId, int32_t jobId) override;
     void onError(int64_t clientId, int32_t jobId, TranscodingErrorCode err) override;
+    void onProgressUpdate(int64_t clientId, int32_t jobId, int32_t progress) override;
     void onResourceLost() override;
     // ~TranscoderCallbackInterface
 
-    // ProcessInfoCallbackInterface
-    void onTopProcessChanged(int32_t pid) override;
+    // UidPolicyCallbackInterface
+    void onTopUidsChanged(const std::unordered_set<uid_t>& uids) override;
     void onResourceAvailable() override;
-    // ~ProcessInfoCallbackInterface
+    // ~UidPolicyCallbackInterface
 
 private:
     friend class MediaTranscodingService;
@@ -66,7 +67,7 @@
 
     struct Job {
         JobKeyType key;
-        pid_t pid;
+        uid_t uid;
         enum JobState {
             NOT_STARTED,
             RUNNING,
@@ -82,27 +83,28 @@
 
     std::map<JobKeyType, Job> mJobMap;
 
-    // Pid->JobQueue map (pid == -1: offline queue)
-    std::map<pid_t, JobQueueType> mJobQueues;
+    // uid->JobQueue map (uid == -1: offline queue)
+    std::map<uid_t, JobQueueType> mJobQueues;
 
-    // Pids, with the head being the most-recently-top app, 2nd item is the
+    // uids, with the head being the most-recently-top app, 2nd item is the
     // previous top app, etc.
-    std::list<pid_t> mPidSortedList;
-    std::list<pid_t>::iterator mOfflinePidIterator;
+    std::list<uid_t> mUidSortedList;
+    std::list<uid_t>::iterator mOfflineUidIterator;
 
     std::shared_ptr<TranscoderInterface> mTranscoder;
-    std::shared_ptr<ProcessInfoInterface> mProcInfo;
+    std::shared_ptr<UidPolicyInterface> mUidPolicy;
 
     Job* mCurrentJob;
     bool mResourceLost;
 
     // Only allow MediaTranscodingService and unit tests to instantiate.
     TranscodingJobScheduler(const std::shared_ptr<TranscoderInterface>& transcoder,
-                            const std::shared_ptr<ProcessInfoInterface>& procInfo);
+                            const std::shared_ptr<UidPolicyInterface>& uidPolicy);
 
     Job* getTopJob_l();
     void updateCurrentJob_l();
     void removeJob_l(const JobKeyType& jobKey);
+    void moveUidsToTop_l(const std::unordered_set<uid_t>& uids, bool preserveTopUid);
 
     // Internal state verifier (debug only)
     void validateState_l();
diff --git a/media/libmediatranscoding/include/media/TranscodingUidPolicy.h b/media/libmediatranscoding/include/media/TranscodingUidPolicy.h
new file mode 100644
index 0000000..27dadd2
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscodingUidPolicy.h
@@ -0,0 +1,71 @@
+/*
+ * 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_TRANSCODING_UID_POLICY_H
+#define ANDROID_MEDIA_TRANSCODING_UID_POLICY_H
+
+#include <aidl/android/media/ITranscodingClient.h>
+#include <aidl/android/media/ITranscodingClientCallback.h>
+#include <media/UidPolicyInterface.h>
+#include <sys/types.h>
+#include <utils/Condition.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include <map>
+#include <mutex>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace android {
+
+class ActivityManager;
+// Observer for UID lifecycle and provide information about the uid's app
+// priority used by the job scheduler.
+class TranscodingUidPolicy : public UidPolicyInterface {
+public:
+    explicit TranscodingUidPolicy();
+    ~TranscodingUidPolicy();
+
+    // UidPolicyInterface
+    bool isUidOnTop(uid_t uid) override;
+    void registerMonitorUid(uid_t uid) override;
+    void unregisterMonitorUid(uid_t uid) override;
+    std::unordered_set<uid_t> getTopUids() const override;
+    void setCallback(const std::shared_ptr<UidPolicyCallbackInterface>& cb) override;
+
+private:
+    void onUidStateChanged(uid_t uid, int32_t procState);
+    void setUidObserverRegistered(bool registerd);
+    void registerSelf();
+    void unregisterSelf();
+    int32_t getProcState_l(uid_t uid) NO_THREAD_SAFETY_ANALYSIS;
+    void updateTopUid_l() NO_THREAD_SAFETY_ANALYSIS;
+
+    struct UidObserver;
+    mutable Mutex mUidLock;
+    std::shared_ptr<ActivityManager> mAm;
+    sp<UidObserver> mUidObserver;
+    bool mRegistered GUARDED_BY(mUidLock);
+    int32_t mTopUidState GUARDED_BY(mUidLock);
+    std::unordered_map<uid_t, int32_t> mUidStateMap GUARDED_BY(mUidLock);
+    std::map<int32_t, std::unordered_set<uid_t>> mStateUidMap GUARDED_BY(mUidLock);
+    std::weak_ptr<UidPolicyCallbackInterface> mUidPolicyCallback;
+};  // class TranscodingUidPolicy
+
+}  // namespace android
+#endif  // ANDROID_MEDIA_TRANSCODING_SERVICE_H
diff --git a/media/libmediatranscoding/include/media/UidPolicyInterface.h b/media/libmediatranscoding/include/media/UidPolicyInterface.h
new file mode 100644
index 0000000..dc28027
--- /dev/null
+++ b/media/libmediatranscoding/include/media/UidPolicyInterface.h
@@ -0,0 +1,62 @@
+/*
+ * 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_UID_POLICY_INTERFACE_H
+#define ANDROID_MEDIA_UID_POLICY_INTERFACE_H
+
+#include <unordered_set>
+
+namespace android {
+
+class UidPolicyCallbackInterface;
+
+// Interface for the scheduler to query a uid's info.
+class UidPolicyInterface {
+public:
+    // Instruct the uid policy to start monitoring a uid.
+    virtual void registerMonitorUid(uid_t uid) = 0;
+    // Instruct the uid policy to stop monitoring a uid.
+    virtual void unregisterMonitorUid(uid_t uid) = 0;
+    // Whether a uid is among the set of uids that's currently top priority.
+    virtual bool isUidOnTop(uid_t uid) = 0;
+    // Retrieves the set of uids that's currently top priority.
+    virtual std::unordered_set<uid_t> getTopUids() const = 0;
+    // Set the associated callback interface to send the events when uid states change.
+    virtual void setCallback(const std::shared_ptr<UidPolicyCallbackInterface>& cb) = 0;
+
+protected:
+    virtual ~UidPolicyInterface() = default;
+};
+
+// Interface for notifying the scheduler of a change in uid states or
+// transcoding resource availability.
+class UidPolicyCallbackInterface {
+public:
+    // Called when the set of uids that's top priority among the uids of interest
+    // has changed. The receiver of this callback should adjust accordingly.
+    virtual void onTopUidsChanged(const std::unordered_set<uid_t>& uids) = 0;
+
+    // Called when resources become available for transcoding use. The scheduler
+    // may use this as a signal to attempt restart transcoding activity that
+    // were previously paused due to temporary resource loss.
+    virtual void onResourceAvailable() = 0;
+
+protected:
+    virtual ~UidPolicyCallbackInterface() = default;
+};
+
+}  // namespace android
+#endif  // ANDROID_MEDIA_UID_POLICY_INTERFACE_H
diff --git a/media/libmediatranscoding/tests/Android.bp b/media/libmediatranscoding/tests/Android.bp
index 904cf9b..b54022a 100644
--- a/media/libmediatranscoding/tests/Android.bp
+++ b/media/libmediatranscoding/tests/Android.bp
@@ -37,6 +37,9 @@
     srcs: ["TranscodingClientManager_tests.cpp"],
 }
 
+//
+// TranscodingJobScheduler unit test
+//
 cc_test {
     name: "TranscodingJobScheduler_tests",
     defaults: ["libmediatranscoding_test_defaults"],
@@ -52,4 +55,4 @@
     defaults: ["libmediatranscoding_test_defaults"],
 
     srcs: ["AdjustableMaxPriorityQueue_tests.cpp"],
-}
\ No newline at end of file
+}
diff --git a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
index acc3082..7e5ae61 100644
--- a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
@@ -120,7 +120,7 @@
 
     virtual ~TestScheduler() { ALOGI("TestScheduler Destroyed"); }
 
-    bool submit(int64_t clientId, int32_t jobId, pid_t /*pid*/,
+    bool submit(int64_t clientId, int32_t jobId, uid_t /*uid*/,
                 const TranscodingRequestParcel& request,
                 const std::weak_ptr<ITranscodingClientCallback>& clientCallback) override {
         JobKeyType jobKey = std::make_pair(clientId, jobId);
diff --git a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
index d062d93..95edf1d 100644
--- a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
@@ -31,6 +31,8 @@
 #include <media/TranscodingJobScheduler.h>
 #include <utils/Log.h>
 
+#include <unordered_set>
+
 namespace android {
 
 using Status = ::ndk::ScopedAStatus;
@@ -40,17 +42,46 @@
 
 constexpr int64_t kClientId = 1000;
 constexpr int32_t kClientJobId = 0;
-constexpr pid_t kClientPid = 5000;
-constexpr pid_t kInvalidPid = -1;
+constexpr uid_t kClientUid = 5000;
+constexpr uid_t kInvalidUid = (uid_t)-1;
 
 #define CLIENT(n) (kClientId + (n))
 #define JOB(n) (kClientJobId + (n))
-#define PID(n) (kClientPid + (n))
+#define UID(n) (kClientUid + (n))
 
-class TestCallback : public TranscoderInterface, public ProcessInfoInterface {
+class TestUidPolicy : public UidPolicyInterface {
 public:
-    TestCallback() : mTopPid(-1), mLastError(TranscodingErrorCode::kUnknown) {}
-    virtual ~TestCallback() {}
+    TestUidPolicy() = default;
+    virtual ~TestUidPolicy() = default;
+
+    // UidPolicyInterface
+    void registerMonitorUid(uid_t /*uid*/) override {}
+    void unregisterMonitorUid(uid_t /*uid*/) override {}
+    bool isUidOnTop(uid_t uid) override { return mTopUids.count(uid) > 0; }
+    std::unordered_set<uid_t> getTopUids() const override { return mTopUids; }
+    void setCallback(const std::shared_ptr<UidPolicyCallbackInterface>& cb) override {
+        mUidPolicyCallback = cb;
+    }
+    void setTop(uid_t uid) {
+        std::unordered_set<uid_t> uids = {uid};
+        setTop(uids);
+    }
+    void setTop(const std::unordered_set<uid_t>& uids) {
+        mTopUids = uids;
+        auto uidPolicyCb = mUidPolicyCallback.lock();
+        if (uidPolicyCb != nullptr) {
+            uidPolicyCb->onTopUidsChanged(mTopUids);
+        }
+    }
+
+    std::unordered_set<uid_t> mTopUids;
+    std::weak_ptr<UidPolicyCallbackInterface> mUidPolicyCallback;
+};
+
+class TestTranscoder : public TranscoderInterface {
+public:
+    TestTranscoder() : mLastError(TranscodingErrorCode::kUnknown) {}
+    virtual ~TestTranscoder() {}
 
     // TranscoderInterface
     void start(int64_t clientId, int32_t jobId) override {
@@ -63,9 +94,6 @@
         mEventQueue.push_back(Resume(clientId, jobId));
     }
 
-    // ProcessInfoInterface
-    bool isProcessOnTop(pid_t pid) override { return pid == mTopPid; }
-
     void onFinished(int64_t clientId, int32_t jobId) {
         mEventQueue.push_back(Finished(clientId, jobId));
     }
@@ -75,8 +103,6 @@
         mEventQueue.push_back(Failed(clientId, jobId));
     }
 
-    void setTop(pid_t pid) { mTopPid = pid; }
-
     TranscodingErrorCode getLastError() {
         TranscodingErrorCode result = mLastError;
         mLastError = TranscodingErrorCode::kUnknown;
@@ -115,16 +141,16 @@
 private:
     Event mPoppedEvent;
     std::list<Event> mEventQueue;
-    pid_t mTopPid;
     TranscodingErrorCode mLastError;
 };
 
-bool operator==(const TestCallback::Event& lhs, const TestCallback::Event& rhs) {
+bool operator==(const TestTranscoder::Event& lhs, const TestTranscoder::Event& rhs) {
     return lhs.type == rhs.type && lhs.clientId == rhs.clientId && lhs.jobId == rhs.jobId;
 }
 
 struct TestClientCallback : public BnTranscodingClientCallback {
-    TestClientCallback(TestCallback* owner, int64_t clientId) : mOwner(owner), mClientId(clientId) {
+    TestClientCallback(TestTranscoder* owner, int64_t clientId)
+          : mOwner(owner), mClientId(clientId) {
         ALOGD("TestClient Created");
     }
 
@@ -152,7 +178,7 @@
     virtual ~TestClientCallback() { ALOGI("TestClient destroyed"); };
 
 private:
-    TestCallback* mOwner;
+    TestTranscoder* mOwner;
     int64_t mClientId;
     TestClientCallback(const TestClientCallback&) = delete;
     TestClientCallback& operator=(const TestClientCallback&) = delete;
@@ -164,27 +190,30 @@
 
     void SetUp() override {
         ALOGI("TranscodingJobSchedulerTest set up");
-        mCallback.reset(new TestCallback());
-        mScheduler.reset(new TranscodingJobScheduler(mCallback, mCallback));
+        mTranscoder.reset(new TestTranscoder());
+        mUidPolicy.reset(new TestUidPolicy());
+        mScheduler.reset(new TranscodingJobScheduler(mTranscoder, mUidPolicy));
+        mUidPolicy->setCallback(mScheduler);
 
         // Set priority only, ignore other fields for now.
         mOfflineRequest.priority = TranscodingJobPriority::kUnspecified;
         mRealtimeRequest.priority = TranscodingJobPriority::kHigh;
         mClientCallback0 =
-                ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(0));
+                ::ndk::SharedRefBase::make<TestClientCallback>(mTranscoder.get(), CLIENT(0));
         mClientCallback1 =
-                ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(1));
+                ::ndk::SharedRefBase::make<TestClientCallback>(mTranscoder.get(), CLIENT(1));
         mClientCallback2 =
-                ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(2));
+                ::ndk::SharedRefBase::make<TestClientCallback>(mTranscoder.get(), CLIENT(2));
         mClientCallback3 =
-                ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(3));
+                ::ndk::SharedRefBase::make<TestClientCallback>(mTranscoder.get(), CLIENT(3));
     }
 
     void TearDown() override { ALOGI("TranscodingJobSchedulerTest tear down"); }
 
     ~TranscodingJobSchedulerTest() { ALOGD("TranscodingJobSchedulerTest destroyed"); }
 
-    std::shared_ptr<TestCallback> mCallback;
+    std::shared_ptr<TestTranscoder> mTranscoder;
+    std::shared_ptr<TestUidPolicy> mUidPolicy;
     std::shared_ptr<TranscodingJobScheduler> mScheduler;
     TranscodingRequestParcel mOfflineRequest;
     TranscodingRequestParcel mRealtimeRequest;
@@ -197,61 +226,59 @@
 TEST_F(TranscodingJobSchedulerTest, TestSubmitJob) {
     ALOGD("TestSubmitJob");
 
-    // Start with PID(1) on top.
-    mCallback->setTop(PID(1));
+    // Start with UID(1) on top.
+    mUidPolicy->setTop(UID(1));
 
-    // Submit offline job to CLIENT(0) in PID(0).
+    // Submit offline job to CLIENT(0) in UID(0).
     // Should start immediately (because this is the only job).
-    mScheduler->submit(CLIENT(0), JOB(0), PID(0), mOfflineRequest, mClientCallback0);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), 0));
+    mScheduler->submit(CLIENT(0), JOB(0), UID(0), mOfflineRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), 0));
 
     // Submit real-time job to CLIENT(0).
-    // Should pause offline job and start new job,  even if PID(0) is not on top.
-    mScheduler->submit(CLIENT(0), JOB(1), PID(0), mRealtimeRequest, mClientCallback0);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(1)));
+    // Should pause offline job and start new job,  even if UID(0) is not on top.
+    mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(1)));
 
     // Submit real-time job to CLIENT(0), should be queued after the previous job.
-    mScheduler->submit(CLIENT(0), JOB(2), PID(0), mRealtimeRequest, mClientCallback0);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    mScheduler->submit(CLIENT(0), JOB(2), UID(0), mRealtimeRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
-    // Submit real-time job to CLIENT(1) in same pid, should be queued after the previous job.
-    mScheduler->submit(CLIENT(1), JOB(0), PID(0), mRealtimeRequest, mClientCallback1);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    // Submit real-time job to CLIENT(1) in same uid, should be queued after the previous job.
+    mScheduler->submit(CLIENT(1), JOB(0), UID(0), mRealtimeRequest, mClientCallback1);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
-    // Submit real-time job to CLIENT(2) in PID(1).
-    // Should pause previous job and start new job, because PID(1) is top.
-    mCallback->setTop(PID(1));
-    mScheduler->submit(CLIENT(2), JOB(0), PID(1), mRealtimeRequest, mClientCallback2);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(1)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(2), JOB(0)));
+    // Submit real-time job to CLIENT(2) in UID(1).
+    // Should pause previous job and start new job, because UID(1) is (has been) top.
+    mScheduler->submit(CLIENT(2), JOB(0), UID(1), mRealtimeRequest, mClientCallback2);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(1)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), JOB(0)));
 
     // Submit offline job, shouldn't generate any event.
-    mScheduler->submit(CLIENT(2), JOB(1), PID(1), mOfflineRequest, mClientCallback2);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    mScheduler->submit(CLIENT(2), JOB(1), UID(1), mOfflineRequest, mClientCallback2);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
-    mCallback->setTop(PID(0));
-    // Submit real-time job to CLIENT(1) in PID(0).
-    // Should pause current job, and resume last job in PID(0).
-    mScheduler->submit(CLIENT(1), JOB(1), PID(0), mRealtimeRequest, mClientCallback1);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(2), JOB(0)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(1)));
+    // Bring UID(0) to top.
+    mUidPolicy->setTop(UID(0));
+    // Should pause current job, and resume last job in UID(0).
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(2), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(1)));
 }
 
 TEST_F(TranscodingJobSchedulerTest, TestCancelJob) {
     ALOGD("TestCancelJob");
 
     // Submit real-time job JOB(0), should start immediately.
-    mScheduler->submit(CLIENT(0), JOB(0), PID(0), mRealtimeRequest, mClientCallback0);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+    mScheduler->submit(CLIENT(0), JOB(0), UID(0), mRealtimeRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
 
     // Submit real-time job JOB(1), should not start.
-    mScheduler->submit(CLIENT(0), JOB(1), PID(0), mRealtimeRequest, mClientCallback0);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Submit offline job JOB(2), should not start.
-    mScheduler->submit(CLIENT(0), JOB(2), PID(0), mOfflineRequest, mClientCallback0);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    mScheduler->submit(CLIENT(0), JOB(2), UID(0), mOfflineRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Cancel queued real-time job.
     // Cancel real-time job JOB(1), should be cancelled.
@@ -262,225 +289,284 @@
     EXPECT_TRUE(mScheduler->cancel(CLIENT(0), JOB(2)));
 
     // Submit offline job JOB(3), shouldn't cause any event.
-    mScheduler->submit(CLIENT(0), JOB(3), PID(0), mOfflineRequest, mClientCallback0);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    mScheduler->submit(CLIENT(0), JOB(3), UID(0), mOfflineRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Cancel running real-time job JOB(0).
     // - Should be paused first then cancelled.
     // - Should also start offline job JOB(2) because real-time queue is empty.
     EXPECT_TRUE(mScheduler->cancel(CLIENT(0), JOB(0)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(3)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(3)));
 }
 
 TEST_F(TranscodingJobSchedulerTest, TestFinishJob) {
     ALOGD("TestFinishJob");
 
-    // Fail without any jobs submitted, should be ignored.
+    // Start with unspecified top UID.
+    // Finish without any jobs submitted, should be ignored.
     mScheduler->onFinish(CLIENT(0), JOB(0));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Submit offline job JOB(0), should start immediately.
-    mScheduler->submit(CLIENT(0), JOB(0), PID(0), mOfflineRequest, mClientCallback0);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+    mScheduler->submit(CLIENT(0), JOB(0), UID(0), mOfflineRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
 
     // Submit real-time job JOB(1), should pause offline job and start immediately.
-    mScheduler->submit(CLIENT(0), JOB(1), PID(0), mRealtimeRequest, mClientCallback0);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(1)));
+    mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(1)));
 
     // Submit real-time job JOB(2), should not start.
-    mScheduler->submit(CLIENT(0), JOB(2), PID(0), mRealtimeRequest, mClientCallback0);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    mScheduler->submit(CLIENT(0), JOB(2), UID(0), mRealtimeRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
-    // Fail when the job never started, should be ignored.
+    // Finish when the job never started, should be ignored.
     mScheduler->onFinish(CLIENT(0), JOB(2));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
-    // PID(1) moves to top.
-    mCallback->setTop(PID(1));
-    // Submit real-time job to CLIENT(1) in PID(1), should pause previous job and start new job.
-    mScheduler->submit(CLIENT(1), JOB(0), PID(1), mRealtimeRequest, mClientCallback1);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(1)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(1), JOB(0)));
+    // UID(1) moves to top.
+    mUidPolicy->setTop(UID(1));
+    // Submit real-time job to CLIENT(1) in UID(1), should pause previous job and start new job.
+    mScheduler->submit(CLIENT(1), JOB(0), UID(1), mRealtimeRequest, mClientCallback1);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(1)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(1), JOB(0)));
 
-    // Simulate Fail that arrived late, after pause issued by scheduler.
+    // Simulate Finish that arrived late, after pause issued by scheduler.
     // Should still be propagated to client, but shouldn't trigger any new start.
     mScheduler->onFinish(CLIENT(0), JOB(1));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(1)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(0), JOB(1)));
 
-    // Fail running real-time job, should start next real-time job in queue.
+    // Finish running real-time job, should start next real-time job in queue.
     mScheduler->onFinish(CLIENT(1), JOB(0));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(1), JOB(0)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(2)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(1), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(2)));
 
-    // Fail running real-time job, should resume next job (offline job) in queue.
+    // Finish running real-time job, should resume next job (offline job) in queue.
     mScheduler->onFinish(CLIENT(0), JOB(2));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(2)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(0), JOB(2)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(0)));
 
-    // Fail running offline job.
+    // Finish running offline job.
     mScheduler->onFinish(CLIENT(0), JOB(0));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(0), JOB(0)));
 
-    // Duplicate fail for last job, should be ignored.
+    // Duplicate finish for last job, should be ignored.
     mScheduler->onFinish(CLIENT(0), JOB(0));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 }
 
 TEST_F(TranscodingJobSchedulerTest, TestFailJob) {
     ALOGD("TestFailJob");
 
+    // Start with unspecified top UID.
     // Fail without any jobs submitted, should be ignored.
     mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kUnknown);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Submit offline job JOB(0), should start immediately.
-    mScheduler->submit(CLIENT(0), JOB(0), PID(0), mOfflineRequest, mClientCallback0);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+    mScheduler->submit(CLIENT(0), JOB(0), UID(0), mOfflineRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
 
     // Submit real-time job JOB(1), should pause offline job and start immediately.
-    mScheduler->submit(CLIENT(0), JOB(1), PID(0), mRealtimeRequest, mClientCallback0);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(1)));
+    mScheduler->submit(CLIENT(0), JOB(1), UID(0), mRealtimeRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(1)));
 
     // Submit real-time job JOB(2), should not start.
-    mScheduler->submit(CLIENT(0), JOB(2), PID(0), mRealtimeRequest, mClientCallback0);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    mScheduler->submit(CLIENT(0), JOB(2), UID(0), mRealtimeRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Fail when the job never started, should be ignored.
     mScheduler->onError(CLIENT(0), JOB(2), TranscodingErrorCode::kUnknown);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
-    // PID(1) moves to top.
-    mCallback->setTop(PID(1));
-    // Submit real-time job to CLIENT(1) in PID(1), should pause previous job and start new job.
-    mScheduler->submit(CLIENT(1), JOB(0), PID(1), mRealtimeRequest, mClientCallback1);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(1)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(1), JOB(0)));
+    // UID(1) moves to top.
+    mUidPolicy->setTop(UID(1));
+    // Submit real-time job to CLIENT(1) in UID(1), should pause previous job and start new job.
+    mScheduler->submit(CLIENT(1), JOB(0), UID(1), mRealtimeRequest, mClientCallback1);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(1)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(1), JOB(0)));
 
     // Simulate Fail that arrived late, after pause issued by scheduler.
     // Should still be propagated to client, but shouldn't trigger any new start.
     mScheduler->onError(CLIENT(0), JOB(1), TranscodingErrorCode::kUnknown);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(0), JOB(1)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Failed(CLIENT(0), JOB(1)));
 
     // Fail running real-time job, should start next real-time job in queue.
     mScheduler->onError(CLIENT(1), JOB(0), TranscodingErrorCode::kUnknown);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(1), JOB(0)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(2)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Failed(CLIENT(1), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(2)));
 
     // Fail running real-time job, should resume next job (offline job) in queue.
     mScheduler->onError(CLIENT(0), JOB(2), TranscodingErrorCode::kUnknown);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(0), JOB(2)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Failed(CLIENT(0), JOB(2)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(0)));
 
     // Fail running offline job, and test error code propagation.
     mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kInvalidBitstream);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(0), JOB(0)));
-    EXPECT_EQ(mCallback->getLastError(), TranscodingErrorCode::kInvalidBitstream);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Failed(CLIENT(0), JOB(0)));
+    EXPECT_EQ(mTranscoder->getLastError(), TranscodingErrorCode::kInvalidBitstream);
 
     // Duplicate fail for last job, should be ignored.
     mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kUnknown);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 }
 
-TEST_F(TranscodingJobSchedulerTest, TestTopProcessChanged) {
-    ALOGD("TestTopProcessChanged");
+TEST_F(TranscodingJobSchedulerTest, TestTopUidChanged) {
+    ALOGD("TestTopUidChanged");
 
+    // Start with unspecified top UID.
     // Submit real-time job to CLIENT(0), job should start immediately.
-    mScheduler->submit(CLIENT(0), JOB(0), PID(0), mRealtimeRequest, mClientCallback0);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+    mScheduler->submit(CLIENT(0), JOB(0), UID(0), mRealtimeRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
 
     // Submit offline job to CLIENT(0), should not start.
-    mScheduler->submit(CLIENT(1), JOB(0), PID(0), mOfflineRequest, mClientCallback1);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    mScheduler->submit(CLIENT(1), JOB(0), UID(0), mOfflineRequest, mClientCallback1);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
-    // Move PID(1) to top.
-    mCallback->setTop(PID(1));
-    // Submit real-time job to CLIENT(2) in different pid PID(1).
+    // Move UID(1) to top.
+    mUidPolicy->setTop(UID(1));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+    // Submit real-time job to CLIENT(2) in different uid UID(1).
     // Should pause previous job and start new job.
-    mScheduler->submit(CLIENT(2), JOB(0), PID(1), mRealtimeRequest, mClientCallback2);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(2), JOB(0)));
+    mScheduler->submit(CLIENT(2), JOB(0), UID(1), mRealtimeRequest, mClientCallback2);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), JOB(0)));
 
-    // Bring PID(0) back to top.
-    mCallback->setTop(PID(0));
-    mScheduler->onTopProcessChanged(PID(0));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(2), JOB(0)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+    // Bring UID(0) back to top.
+    mUidPolicy->setTop(UID(0));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(2), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(0)));
 
-    // Bring invalid process to top.
-    mScheduler->onTopProcessChanged(kInvalidPid);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    // Bring invalid uid to top.
+    mUidPolicy->setTop(kInvalidUid);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Finish job, next real-time job should resume.
     mScheduler->onFinish(CLIENT(0), JOB(0));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(0)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(2), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(0), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(2), JOB(0)));
 
     // Finish job, offline job should start.
     mScheduler->onFinish(CLIENT(2), JOB(0));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(2), JOB(0)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(1), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(2), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(1), JOB(0)));
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestTopUidSetChanged) {
+    ALOGD("TestTopUidChanged_MultipleUids");
+
+    // Start with unspecified top UID.
+    // Submit real-time job to CLIENT(0), job should start immediately.
+    mScheduler->submit(CLIENT(0), JOB(0), UID(0), mRealtimeRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
+
+    // Submit offline job to CLIENT(0), should not start.
+    mScheduler->submit(CLIENT(1), JOB(0), UID(0), mOfflineRequest, mClientCallback1);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+    // Set UID(0), UID(1) to top set.
+    // UID(0) should continue to run.
+    mUidPolicy->setTop({UID(0), UID(1)});
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+    // Submit real-time job to CLIENT(2) in different uid UID(1).
+    // UID(0) should pause and UID(1) should start.
+    mScheduler->submit(CLIENT(2), JOB(0), UID(1), mRealtimeRequest, mClientCallback2);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), JOB(0)));
+
+    // Remove UID(0) from top set, and only leave UID(1) in the set.
+    // UID(1) should continue to run.
+    mUidPolicy->setTop(UID(1));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+    // Set UID(0), UID(2) to top set.
+    // UID(1) should continue to run.
+    mUidPolicy->setTop({UID(1), UID(2)});
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+    // Bring UID(0) back to top.
+    mUidPolicy->setTop(UID(0));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(2), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(0)));
+
+    // Bring invalid uid to top.
+    mUidPolicy->setTop(kInvalidUid);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+    // Finish job, next real-time job from UID(1) should resume, even if UID(1) no longer top.
+    mScheduler->onFinish(CLIENT(0), JOB(0));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(0), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(2), JOB(0)));
+
+    // Finish job, offline job should start.
+    mScheduler->onFinish(CLIENT(2), JOB(0));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Finished(CLIENT(2), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(1), JOB(0)));
 }
 
 TEST_F(TranscodingJobSchedulerTest, TestResourceLost) {
     ALOGD("TestResourceLost");
 
+    // Start with unspecified top UID.
     // Submit real-time job to CLIENT(0), job should start immediately.
-    mScheduler->submit(CLIENT(0), JOB(0), PID(0), mRealtimeRequest, mClientCallback0);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+    mScheduler->submit(CLIENT(0), JOB(0), UID(0), mRealtimeRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), JOB(0)));
 
     // Submit offline job to CLIENT(0), should not start.
-    mScheduler->submit(CLIENT(1), JOB(0), PID(0), mOfflineRequest, mClientCallback1);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    mScheduler->submit(CLIENT(1), JOB(0), UID(0), mOfflineRequest, mClientCallback1);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
-    // Move PID(1) to top.
-    mCallback->setTop(PID(1));
+    // Move UID(1) to top.
+    mUidPolicy->setTop(UID(1));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
-    // Submit real-time job to CLIENT(2) in different pid PID(1).
+    // Submit real-time job to CLIENT(2) in different uid UID(1).
     // Should pause previous job and start new job.
-    mScheduler->submit(CLIENT(2), JOB(0), PID(1), mRealtimeRequest, mClientCallback2);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(2), JOB(0)));
+    mScheduler->submit(CLIENT(2), JOB(0), UID(1), mRealtimeRequest, mClientCallback2);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), JOB(0)));
 
     // Test 1: No queue change during resource loss.
     // Signal resource lost.
     mScheduler->onResourceLost();
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Signal resource available, CLIENT(2) should resume.
     mScheduler->onResourceAvailable();
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(2), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(2), JOB(0)));
 
     // Test 2: Change of queue order during resource loss.
     // Signal resource lost.
     mScheduler->onResourceLost();
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
-    // Move PID(0) back to top, should have no resume due to no resource.
-    mScheduler->onTopProcessChanged(PID(0));
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    // Move UID(0) back to top, should have no resume due to no resource.
+    mUidPolicy->setTop(UID(0));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Signal resource available, CLIENT(0) should resume.
     mScheduler->onResourceAvailable();
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), JOB(0)));
 
     // Test 3: Adding new queue during resource loss.
     // Signal resource lost.
     mScheduler->onResourceLost();
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
-    // Move PID(2) to top.
-    mCallback->setTop(PID(2));
+    // Move UID(2) to top.
+    mUidPolicy->setTop(UID(2));
 
-    // Submit real-time job to CLIENT(3) in PID(2), job shouldn't start due to no resource.
-    mScheduler->submit(CLIENT(3), JOB(0), PID(2), mRealtimeRequest, mClientCallback3);
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+    // Submit real-time job to CLIENT(3) in UID(2), job shouldn't start due to no resource.
+    mScheduler->submit(CLIENT(3), JOB(0), UID(2), mRealtimeRequest, mClientCallback3);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Signal resource available, CLIENT(3)'s job should start.
     mScheduler->onResourceAvailable();
-    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(3), JOB(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(3), JOB(0)));
 }
 
 }  // namespace android
diff --git a/media/libmediatranscoding/transcoder/Android.bp b/media/libmediatranscoding/transcoder/Android.bp
index e352245..44f7959 100644
--- a/media/libmediatranscoding/transcoder/Android.bp
+++ b/media/libmediatranscoding/transcoder/Android.bp
@@ -20,12 +20,15 @@
     srcs: [
         "MediaSampleQueue.cpp",
         "MediaSampleReaderNDK.cpp",
+        "MediaTrackTranscoder.cpp",
+        "VideoTrackTranscoder.cpp",
     ],
 
     shared_libs: [
         "libbase",
         "libcutils",
         "libmediandk",
+        "libnativewindow",
         "libutils",
     ],
 
diff --git a/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp b/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
index d0f117d..a0096c7 100644
--- a/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
+++ b/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
@@ -25,6 +25,10 @@
 
 namespace android {
 
+// Check that the extractor sample flags have the expected NDK meaning.
+static_assert(SAMPLE_FLAG_SYNC_SAMPLE == AMEDIAEXTRACTOR_SAMPLE_FLAG_SYNC,
+              "Sample flag mismatch: SYNC_SAMPLE");
+
 // static
 std::shared_ptr<MediaSampleReader> MediaSampleReaderNDK::createFromFd(int fd, size_t offset,
                                                                       size_t size) {
diff --git a/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
new file mode 100644
index 0000000..1673b5b
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
@@ -0,0 +1,104 @@
+/*
+ * 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 "MediaTrackTranscoder"
+
+#include <android-base/logging.h>
+#include <media/MediaTrackTranscoder.h>
+
+namespace android {
+
+media_status_t MediaTrackTranscoder::configure(
+        const std::shared_ptr<MediaSampleReader>& mediaSampleReader, int trackIndex,
+        const std::shared_ptr<AMediaFormat>& destinationFormat) {
+    std::scoped_lock lock{mStateMutex};
+
+    if (mState != UNINITIALIZED) {
+        LOG(ERROR) << "Configure can only be called once";
+        return AMEDIA_ERROR_UNSUPPORTED;
+    }
+
+    if (mediaSampleReader == nullptr) {
+        LOG(ERROR) << "MediaSampleReader is null";
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+    if (trackIndex < 0 || trackIndex >= mediaSampleReader->getTrackCount()) {
+        LOG(ERROR) << "TrackIndex is invalid " << trackIndex;
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    mMediaSampleReader = mediaSampleReader;
+    mTrackIndex = trackIndex;
+
+    mSourceFormat =
+            std::shared_ptr<AMediaFormat>(mMediaSampleReader->getTrackFormat(mTrackIndex),
+                                          std::bind(AMediaFormat_delete, std::placeholders::_1));
+    if (mSourceFormat == nullptr) {
+        LOG(ERROR) << "Unable to get format for track #" << mTrackIndex;
+        return AMEDIA_ERROR_MALFORMED;
+    }
+
+    media_status_t status = configureDestinationFormat(destinationFormat);
+    if (status != AMEDIA_OK) {
+        LOG(ERROR) << "configure failed with error " << status;
+        return status;
+    }
+
+    mState = CONFIGURED;
+    return AMEDIA_OK;
+}
+
+bool MediaTrackTranscoder::start() {
+    std::scoped_lock lock{mStateMutex};
+
+    if (mState != CONFIGURED) {
+        LOG(ERROR) << "TrackTranscoder must be configured before started";
+        return false;
+    }
+
+    mTranscodingThread = std::thread([this] {
+        media_status_t status = runTranscodeLoop();
+
+        // Notify the client.
+        if (auto callbacks = mTranscoderCallback.lock()) {
+            if (status != AMEDIA_OK) {
+                callbacks->onTrackError(this, status);
+            } else {
+                callbacks->onTrackFinished(this);
+            }
+        }
+    });
+
+    mState = STARTED;
+    return true;
+}
+
+bool MediaTrackTranscoder::stop() {
+    std::scoped_lock lock{mStateMutex};
+
+    if (mState == STARTED) {
+        abortTranscodeLoop();
+        mTranscodingThread.join();
+        mState = STOPPED;
+        return true;
+    }
+
+    LOG(ERROR) << "TrackTranscoder must be started before stopped";
+    return false;
+}
+
+}  // namespace android
\ No newline at end of file
diff --git a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
new file mode 100644
index 0000000..9cd36cf
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
@@ -0,0 +1,335 @@
+/*
+ * 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 "VideoTrackTranscoder"
+
+#include <android-base/logging.h>
+#include <media/VideoTrackTranscoder.h>
+
+namespace android {
+
+// Check that the codec sample flags have the expected NDK meaning.
+static_assert(SAMPLE_FLAG_CODEC_CONFIG == AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG,
+              "Sample flag mismatch: CODEC_CONFIG");
+static_assert(SAMPLE_FLAG_END_OF_STREAM == AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM,
+              "Sample flag mismatch: END_OF_STREAM");
+static_assert(SAMPLE_FLAG_PARTIAL_FRAME == AMEDIACODEC_BUFFER_FLAG_PARTIAL_FRAME,
+              "Sample flag mismatch: PARTIAL_FRAME");
+
+template <typename T>
+void VideoTrackTranscoder::BlockingQueue<T>::push(T const& value, bool front) {
+    {
+        std::unique_lock<std::mutex> lock(mMutex);
+        if (front) {
+            mQueue.push_front(value);
+        } else {
+            mQueue.push_back(value);
+        }
+    }
+    mCondition.notify_one();
+}
+
+template <typename T>
+T VideoTrackTranscoder::BlockingQueue<T>::pop() {
+    std::unique_lock<std::mutex> lock(mMutex);
+    while (mQueue.empty()) {
+        mCondition.wait(lock);
+    }
+    T value = mQueue.front();
+    mQueue.pop_front();
+    return value;
+}
+
+// Dispatch responses to codec callbacks onto the message queue.
+struct AsyncCodecCallbackDispatch {
+    static void onAsyncInputAvailable(AMediaCodec* codec, void* userdata, int32_t index) {
+        VideoTrackTranscoder* transcoder = static_cast<VideoTrackTranscoder*>(userdata);
+        if (codec == transcoder->mDecoder) {
+            transcoder->mCodecMessageQueue.push(
+                    [transcoder, index] { transcoder->enqueueInputSample(index); });
+        }
+    }
+
+    static void onAsyncOutputAvailable(AMediaCodec* codec, void* userdata, int32_t index,
+                                       AMediaCodecBufferInfo* bufferInfoPtr) {
+        VideoTrackTranscoder* transcoder = static_cast<VideoTrackTranscoder*>(userdata);
+        AMediaCodecBufferInfo bufferInfo = *bufferInfoPtr;
+        transcoder->mCodecMessageQueue.push([transcoder, index, codec, bufferInfo] {
+            if (codec == transcoder->mDecoder) {
+                transcoder->transferBuffer(index, bufferInfo);
+            } else if (codec == transcoder->mEncoder) {
+                transcoder->dequeueOutputSample(index, bufferInfo);
+            }
+        });
+    }
+
+    static void onAsyncFormatChanged(AMediaCodec* codec, void* userdata, AMediaFormat* format) {
+        VideoTrackTranscoder* transcoder = static_cast<VideoTrackTranscoder*>(userdata);
+        const char* kCodecName = (codec == transcoder->mDecoder ? "Decoder" : "Encoder");
+        LOG(DEBUG) << kCodecName << " format changed: " << AMediaFormat_toString(format);
+    }
+
+    static void onAsyncError(AMediaCodec* codec, void* userdata, media_status_t error,
+                             int32_t actionCode, const char* detail) {
+        LOG(ERROR) << "Error from codec " << codec << ", userdata " << userdata << ", error "
+                   << error << ", action " << actionCode << ", detail " << detail;
+        VideoTrackTranscoder* transcoder = static_cast<VideoTrackTranscoder*>(userdata);
+        transcoder->mCodecMessageQueue.push(
+                [transcoder, error] {
+                    transcoder->mStatus = error;
+                    transcoder->mStopRequested = true;
+                },
+                true);
+    }
+};
+
+VideoTrackTranscoder::~VideoTrackTranscoder() {
+    if (mDecoder != nullptr) {
+        AMediaCodec_delete(mDecoder);
+    }
+
+    if (mEncoder != nullptr) {
+        AMediaCodec_delete(mEncoder);
+    }
+
+    if (mSurface != nullptr) {
+        ANativeWindow_release(mSurface);
+    }
+}
+
+// Creates and configures the codecs.
+media_status_t VideoTrackTranscoder::configureDestinationFormat(
+        const std::shared_ptr<AMediaFormat>& destinationFormat) {
+    media_status_t status = AMEDIA_OK;
+
+    if (destinationFormat == nullptr) {
+        LOG(ERROR) << "Destination format is null";
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    mDestinationFormat = destinationFormat;
+
+    // Create and configure the encoder.
+    const char* destinationMime = nullptr;
+    bool ok = AMediaFormat_getString(mDestinationFormat.get(), AMEDIAFORMAT_KEY_MIME,
+                                     &destinationMime);
+    if (!ok) {
+        LOG(ERROR) << "Destination MIME type is required for transcoding.";
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    mEncoder = AMediaCodec_createEncoderByType(destinationMime);
+    if (mEncoder == nullptr) {
+        LOG(ERROR) << "Unable to create encoder for type " << destinationMime;
+        return AMEDIA_ERROR_UNSUPPORTED;
+    }
+
+    status = AMediaCodec_configure(mEncoder, mDestinationFormat.get(), NULL /* surface */,
+                                   NULL /* crypto */, AMEDIACODEC_CONFIGURE_FLAG_ENCODE);
+    if (status != AMEDIA_OK) {
+        LOG(ERROR) << "Unable to configure video encoder: " << status;
+        return status;
+    }
+
+    status = AMediaCodec_createInputSurface(mEncoder, &mSurface);
+    if (status != AMEDIA_OK) {
+        LOG(ERROR) << "Unable to create an encoder input surface: %d" << status;
+        return status;
+    }
+
+    // Create and configure the decoder.
+    const char* sourceMime = nullptr;
+    ok = AMediaFormat_getString(mSourceFormat.get(), AMEDIAFORMAT_KEY_MIME, &sourceMime);
+    if (!ok) {
+        LOG(ERROR) << "Source MIME type is required for transcoding.";
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    mDecoder = AMediaCodec_createDecoderByType(sourceMime);
+    if (mDecoder == nullptr) {
+        LOG(ERROR) << "Unable to create decoder for type " << sourceMime;
+        return AMEDIA_ERROR_UNSUPPORTED;
+    }
+
+    status = AMediaCodec_configure(mDecoder, mSourceFormat.get(), mSurface, NULL /* crypto */,
+                                   0 /* flags */);
+    if (status != AMEDIA_OK) {
+        LOG(ERROR) << "Unable to configure video decoder: " << status;
+        return status;
+    }
+
+    // Configure codecs to run in async mode.
+    AMediaCodecOnAsyncNotifyCallback asyncCodecCallbacks = {
+            .onAsyncInputAvailable = AsyncCodecCallbackDispatch::onAsyncInputAvailable,
+            .onAsyncOutputAvailable = AsyncCodecCallbackDispatch::onAsyncOutputAvailable,
+            .onAsyncFormatChanged = AsyncCodecCallbackDispatch::onAsyncFormatChanged,
+            .onAsyncError = AsyncCodecCallbackDispatch::onAsyncError};
+
+    status = AMediaCodec_setAsyncNotifyCallback(mDecoder, asyncCodecCallbacks, this);
+    if (status != AMEDIA_OK) {
+        LOG(ERROR) << "Unable to set decoder to async mode: " << status;
+        return status;
+    }
+
+    status = AMediaCodec_setAsyncNotifyCallback(mEncoder, asyncCodecCallbacks, this);
+    if (status != AMEDIA_OK) {
+        LOG(ERROR) << "Unable to set encoder to async mode: " << status;
+        return status;
+    }
+
+    return AMEDIA_OK;
+}
+
+void VideoTrackTranscoder::enqueueInputSample(int32_t bufferIndex) {
+    media_status_t status = AMEDIA_OK;
+
+    if (mEOSFromSource) {
+        return;
+    }
+
+    status = mMediaSampleReader->getSampleInfoForTrack(mTrackIndex, &mSampleInfo);
+    if (status != AMEDIA_OK && status != AMEDIA_ERROR_END_OF_STREAM) {
+        LOG(ERROR) << "Error getting next sample info: " << status;
+        mStatus = status;
+        return;
+    }
+    const bool endOfStream = (status == AMEDIA_ERROR_END_OF_STREAM);
+
+    if (!endOfStream) {
+        size_t bufferSize = 0;
+        uint8_t* sourceBuffer = AMediaCodec_getInputBuffer(mDecoder, bufferIndex, &bufferSize);
+        if (sourceBuffer == nullptr) {
+            LOG(ERROR) << "Decoder returned a NULL input buffer.";
+            mStatus = AMEDIA_ERROR_UNKNOWN;
+            return;
+        } else if (bufferSize < mSampleInfo.size) {
+            LOG(ERROR) << "Decoder returned an input buffer that is smaller than the sample.";
+            mStatus = AMEDIA_ERROR_UNKNOWN;
+            return;
+        }
+
+        status = mMediaSampleReader->readSampleDataForTrack(mTrackIndex, sourceBuffer,
+                                                            mSampleInfo.size);
+        if (status != AMEDIA_OK) {
+            LOG(ERROR) << "Unable to read next sample data. Aborting transcode.";
+            mStatus = status;
+            return;
+        }
+
+        mMediaSampleReader->advanceTrack(mTrackIndex);
+    } else {
+        LOG(DEBUG) << "EOS from source.";
+        mEOSFromSource = true;
+    }
+
+    status = AMediaCodec_queueInputBuffer(mDecoder, bufferIndex, 0, mSampleInfo.size,
+                                          mSampleInfo.presentationTimeUs, mSampleInfo.flags);
+    if (status != AMEDIA_OK) {
+        LOG(ERROR) << "Unable to queue input buffer for decode: " << status;
+        mStatus = status;
+        return;
+    }
+}
+
+void VideoTrackTranscoder::transferBuffer(int32_t bufferIndex, AMediaCodecBufferInfo bufferInfo) {
+    if (bufferIndex >= 0) {
+        bool needsRender = bufferInfo.size > 0;
+        AMediaCodec_releaseOutputBuffer(mDecoder, bufferIndex, needsRender);
+    }
+
+    if (bufferInfo.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
+        LOG(DEBUG) << "EOS from decoder.";
+        media_status_t status = AMediaCodec_signalEndOfInputStream(mEncoder);
+        if (status != AMEDIA_OK) {
+            LOG(ERROR) << "SignalEOS on encoder returned error: " << status;
+            mStatus = status;
+        }
+    }
+}
+
+void VideoTrackTranscoder::dequeueOutputSample(int32_t bufferIndex,
+                                               AMediaCodecBufferInfo bufferInfo) {
+    if (bufferIndex >= 0) {
+        size_t sampleSize = 0;
+        uint8_t* buffer = AMediaCodec_getOutputBuffer(mEncoder, bufferIndex, &sampleSize);
+
+        std::shared_ptr<MediaSample> sample = MediaSample::createWithReleaseCallback(
+                buffer, bufferInfo.offset, bufferIndex,
+                std::bind(&VideoTrackTranscoder::releaseOutputSample, this, std::placeholders::_1));
+        sample->info.size = bufferInfo.size;
+        sample->info.flags = bufferInfo.flags;
+        sample->info.presentationTimeUs = bufferInfo.presentationTimeUs;
+
+        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?
+            return;
+        }
+    } else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
+        AMediaFormat* newFormat = AMediaCodec_getOutputFormat(mEncoder);
+        LOG(DEBUG) << "Encoder output format changed: " << AMediaFormat_toString(newFormat);
+    }
+
+    if (bufferInfo.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
+        LOG(DEBUG) << "EOS from encoder.";
+        mEOSFromEncoder = true;
+    }
+}
+
+void VideoTrackTranscoder::releaseOutputSample(MediaSample* sample) {
+    AMediaCodec_releaseOutputBuffer(mEncoder, sample->bufferId, false /* render */);
+}
+
+media_status_t VideoTrackTranscoder::runTranscodeLoop() {
+    media_status_t status = AMEDIA_OK;
+
+    status = AMediaCodec_start(mDecoder);
+    if (status != AMEDIA_OK) {
+        LOG(ERROR) << "Unable to start video decoder: " << status;
+        return status;
+    }
+
+    status = AMediaCodec_start(mEncoder);
+    if (status != AMEDIA_OK) {
+        LOG(ERROR) << "Unable to start video encoder: " << status;
+        AMediaCodec_stop(mDecoder);
+        return status;
+    }
+
+    // Process codec events until EOS is reached, transcoding is stopped or an error occurs.
+    while (!mStopRequested && !mEOSFromEncoder && mStatus == AMEDIA_OK) {
+        std::function<void()> message = mCodecMessageQueue.pop();
+        message();
+    }
+
+    // Return error if transcoding was stopped before it finished.
+    if (mStopRequested && !mEOSFromEncoder && mStatus == AMEDIA_OK) {
+        mStatus = AMEDIA_ERROR_UNKNOWN;  // TODO: Define custom error codes?
+    }
+
+    AMediaCodec_stop(mDecoder);
+    AMediaCodec_stop(mEncoder);
+    return mStatus;
+}
+
+void VideoTrackTranscoder::abortTranscodeLoop() {
+    // Push abort message to the front of the codec event queue.
+    mCodecMessageQueue.push([this] { mStopRequested = true; }, true /* front */);
+}
+
+}  // namespace android
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaSample.h b/media/libmediatranscoding/transcoder/include/media/MediaSample.h
index e206a3e..c2cef84 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaSample.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaSample.h
@@ -38,21 +38,6 @@
     SAMPLE_FLAG_PARTIAL_FRAME = 8,
 };
 
-// Check that the sample flags have the expected NDK meaning.
-namespace {
-#include <media/NdkMediaCodec.h>
-#include <media/NdkMediaExtractor.h>
-
-static_assert(SAMPLE_FLAG_SYNC_SAMPLE == AMEDIAEXTRACTOR_SAMPLE_FLAG_SYNC,
-              "Sample flag mismatch: SYNC_SAMPLE");
-static_assert(SAMPLE_FLAG_CODEC_CONFIG == AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG,
-              "Sample flag mismatch: CODEC_CONFIG");
-static_assert(SAMPLE_FLAG_END_OF_STREAM == AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM,
-              "Sample flag mismatch: END_OF_STREAM");
-static_assert(SAMPLE_FLAG_PARTIAL_FRAME == AMEDIACODEC_BUFFER_FLAG_PARTIAL_FRAME,
-              "Sample flag mismatch: PARTIAL_FRAME");
-}  // anonymous namespace
-
 /**
  * MediaSampleInfo is an object that carries information about a compressed media sample without
  * holding any sample data.
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
new file mode 100644
index 0000000..bbdbc1a
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
@@ -0,0 +1,135 @@
+/*
+ * 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_TRACK_TRANSCODER_H
+#define ANDROID_MEDIA_TRACK_TRANSCODER_H
+
+#include <media/MediaSampleQueue.h>
+#include <media/MediaSampleReader.h>
+#include <media/NdkMediaError.h>
+#include <media/NdkMediaFormat.h>
+#include <utils/Mutex.h>
+
+#include <functional>
+#include <memory>
+#include <thread>
+
+namespace android {
+
+class MediaTrackTranscoder;
+
+/** Callback interface for MediaTrackTranscoder. */
+class MediaTrackTranscoderCallback {
+public:
+    /**
+     * Called when the MediaTrackTranscoder instance have finished transcoding all media samples
+     * successfully.
+     * @param transcoder The MediaTrackTranscoder that finished the transcoding.
+     */
+    virtual void onTrackFinished(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);
+
+protected:
+    virtual ~MediaTrackTranscoderCallback() = default;
+};
+
+/**
+ * Base class for all track transcoders. MediaTrackTranscoder operates asynchronously on an internal
+ * thread and communicates through a MediaTrackTranscoderCallback instance. Transcoded samples are
+ * enqueued on the MediaTrackTranscoder's output queue. Samples need to be dequeued from the output
+ * queue or the transcoder will run out of buffers and stall. Once the consumer is done with a
+ * transcoded sample it is the consumer's responsibility to as soon as possible release all
+ * references to that sample in order to return the buffer to the transcoder. MediaTrackTranscoder
+ * is an abstract class and instances are created through one of the concrete subclasses.
+ *
+ * The base class MediaTrackTranscoder is responsible for thread and state management and guarantees
+ * that operations {configure, start, stop} are sent to the derived class in correct order.
+ * MediaTrackTranscoder is also responsible for delivering callback notifications once the
+ * transcoder has been successfully started.
+ */
+class MediaTrackTranscoder {
+public:
+    /**
+     * Configures the track transcoder with an input MediaSampleReader and a destination format.
+     * A track transcoder have to be configured before it is started.
+     * @param mediaSampleReader The MediaSampleReader to read input samples from.
+     * @param trackIndex The index of the track to transcode in mediaSampleReader.
+     * @param destinationFormat The destination format.
+     * @return AMEDIA_OK if the track transcoder was successfully configured.
+     */
+    media_status_t configure(const std::shared_ptr<MediaSampleReader>& mediaSampleReader,
+                             int trackIndex,
+                             const std::shared_ptr<AMediaFormat>& destinationFormat);
+
+    /**
+     * Starts the track transcoder. Once started the track transcoder have to be stopped by calling
+     * {@link #stop}, even after completing successfully. Start should only be called once.
+     * @return True if the track transcoder started, or false if it had already been started.
+     */
+    bool start();
+
+    /**
+     * Stops the track transcoder. Once the transcoding has been stopped it cannot be restarted
+     * again. It is safe to call stop multiple times.
+     * @return True if the track transcoder stopped, or false if it was already stopped.
+     */
+    bool stop();
+
+    /** Sample output queue. */
+    MediaSampleQueue mOutputQueue = {};
+
+protected:
+    MediaTrackTranscoder(const std::weak_ptr<MediaTrackTranscoderCallback>& transcoderCallback)
+          : mTranscoderCallback(transcoderCallback){};
+    virtual ~MediaTrackTranscoder() = default;
+
+    // configureDestinationFormat needs to be implemented by subclasses, and gets called on an
+    // external thread before start.
+    virtual media_status_t configureDestinationFormat(
+            const std::shared_ptr<AMediaFormat>& destinationFormat) = 0;
+
+    // runTranscodeLoop needs to be implemented by subclasses, and gets called on
+    // MediaTrackTranscoder's internal thread when the track transcoder is started.
+    virtual media_status_t runTranscodeLoop() = 0;
+
+    // abortTranscodeLoop needs to be implemented by subclasses, and should request transcoding to
+    // be aborted as soon as possible. It should be safe to call abortTranscodeLoop multiple times.
+    virtual void abortTranscodeLoop() = 0;
+
+    std::shared_ptr<MediaSampleReader> mMediaSampleReader;
+    int mTrackIndex;
+    std::shared_ptr<AMediaFormat> mSourceFormat;
+
+private:
+    const std::weak_ptr<MediaTrackTranscoderCallback> mTranscoderCallback;
+    std::mutex mStateMutex;
+    std::thread mTranscodingThread GUARDED_BY(mStateMutex);
+    enum {
+        UNINITIALIZED,
+        CONFIGURED,
+        STARTED,
+        STOPPED,
+    } mState GUARDED_BY(mStateMutex) = UNINITIALIZED;
+};
+
+}  // namespace android
+#endif  // ANDROID_MEDIA_TRACK_TRANSCODER_H
diff --git a/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
new file mode 100644
index 0000000..7607c12
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
@@ -0,0 +1,91 @@
+/*
+ * 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_VIDEO_TRACK_TRANSCODER_H
+#define ANDROID_VIDEO_TRACK_TRANSCODER_H
+
+#include <android/native_window.h>
+#include <media/MediaTrackTranscoder.h>
+#include <media/NdkMediaCodec.h>
+#include <media/NdkMediaFormat.h>
+
+#include <condition_variable>
+#include <deque>
+#include <mutex>
+
+namespace android {
+
+/**
+ * Track transcoder for video tracks. VideoTrackTranscoder uses AMediaCodec from the Media NDK
+ * internally. The two media codecs are run in asynchronous mode and shares uncompressed buffers
+ * using a native surface (ANativeWindow). Codec callback events are placed on a message queue and
+ * serviced in order on the transcoding thread managed by MediaTrackTranscoder.
+ */
+class VideoTrackTranscoder : public MediaTrackTranscoder {
+public:
+    VideoTrackTranscoder(const std::weak_ptr<MediaTrackTranscoderCallback>& transcoderCallback)
+          : MediaTrackTranscoder(transcoderCallback){};
+    virtual ~VideoTrackTranscoder() override;
+
+private:
+    friend struct AsyncCodecCallbackDispatch;
+
+    // Minimal blocking queue used as a message queue by VideoTrackTranscoder.
+    template <typename T>
+    class BlockingQueue {
+    public:
+        void push(T const& value, bool front = false);
+        T pop();
+
+    private:
+        std::mutex mMutex;
+        std::condition_variable mCondition;
+        std::deque<T> mQueue;
+    };
+
+    // MediaTrackTranscoder
+    media_status_t runTranscodeLoop() override;
+    void abortTranscodeLoop() override;
+    media_status_t configureDestinationFormat(
+            const std::shared_ptr<AMediaFormat>& destinationFormat) override;
+    // ~MediaTrackTranscoder
+
+    // Enqueues an input sample with the decoder.
+    void enqueueInputSample(int32_t bufferIndex);
+
+    // Moves a decoded buffer from the decoder's output to the encoder's input.
+    void transferBuffer(int32_t bufferIndex, AMediaCodecBufferInfo bufferInfo);
+
+    // Dequeues an encoded buffer from the encoder and adds it to the output queue.
+    void dequeueOutputSample(int32_t bufferIndex, AMediaCodecBufferInfo bufferInfo);
+
+    // MediaSample release callback to return a buffer to the codec.
+    void releaseOutputSample(MediaSample* sample);
+
+    AMediaCodec* mDecoder = nullptr;
+    AMediaCodec* mEncoder = nullptr;
+    ANativeWindow* mSurface = nullptr;
+    bool mEOSFromSource = false;
+    bool mEOSFromEncoder = false;
+    bool mStopRequested = false;
+    media_status_t mStatus = AMEDIA_OK;
+    MediaSampleInfo mSampleInfo;
+    BlockingQueue<std::function<void()>> mCodecMessageQueue;
+    std::shared_ptr<AMediaFormat> mDestinationFormat;
+};
+
+}  // namespace android
+#endif  // ANDROID_VIDEO_TRACK_TRANSCODER_H
diff --git a/media/libmediatranscoding/transcoder/tests/Android.bp b/media/libmediatranscoding/transcoder/tests/Android.bp
index a9937d7..48449f8 100644
--- a/media/libmediatranscoding/transcoder/tests/Android.bp
+++ b/media/libmediatranscoding/transcoder/tests/Android.bp
@@ -44,3 +44,17 @@
     defaults: ["testdefaults"],
     srcs: ["MediaSampleQueueTests.cpp"],
 }
+
+// MediaTrackTranscoder unit test
+cc_test {
+    name: "MediaTrackTranscoderTests",
+    defaults: ["testdefaults"],
+    srcs: ["MediaTrackTranscoderTests.cpp"],
+}
+
+// VideoTrackTranscoder unit test
+cc_test {
+    name: "VideoTrackTranscoderTests",
+    defaults: ["testdefaults"],
+    srcs: ["VideoTrackTranscoderTests.cpp"],
+}
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
new file mode 100644
index 0000000..d1f3fee
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
@@ -0,0 +1,264 @@
+/*
+ * 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 MediaTrackTranscoder
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaTrackTranscoderTests"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <media/MediaSampleReaderNDK.h>
+#include <media/MediaTrackTranscoder.h>
+#include <media/VideoTrackTranscoder.h>
+
+#include "TrackTranscoderTestUtils.h"
+
+namespace android {
+
+/** TrackTranscoder types to test. */
+enum TrackTranscoderType {
+    VIDEO,
+};
+
+class MediaTrackTranscoderTests : public ::testing::TestWithParam<TrackTranscoderType> {
+public:
+    MediaTrackTranscoderTests() { LOG(DEBUG) << "MediaTrackTranscoderTests created"; }
+
+    void SetUp() override {
+        LOG(DEBUG) << "MediaTrackTranscoderTests set up";
+
+        mCallback = std::make_shared<TestCallback>();
+
+        switch (GetParam()) {
+        case VIDEO:
+            mTranscoder = std::make_shared<VideoTrackTranscoder>(mCallback);
+            ASSERT_NE(mTranscoder, nullptr);
+            break;
+        }
+        ASSERT_NE(mTranscoder, nullptr);
+
+        initSampleReader();
+    }
+
+    void initSampleReader() {
+        const char* sourcePath =
+                "/data/local/tmp/TranscoderTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+
+        const int sourceFd = open(sourcePath, O_RDONLY);
+        ASSERT_GT(sourceFd, 0);
+
+        const size_t fileSize = lseek(sourceFd, 0, SEEK_END);
+        lseek(sourceFd, 0, SEEK_SET);
+
+        mMediaSampleReader = MediaSampleReaderNDK::createFromFd(sourceFd, 0 /* offset */, fileSize);
+        ASSERT_NE(mMediaSampleReader, nullptr);
+        close(sourceFd);
+
+        for (size_t trackIndex = 0; trackIndex < mMediaSampleReader->getTrackCount();
+             ++trackIndex) {
+            AMediaFormat* trackFormat = mMediaSampleReader->getTrackFormat(trackIndex);
+            ASSERT_NE(trackFormat, nullptr);
+
+            const char* mime = nullptr;
+            AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+            ASSERT_NE(mime, nullptr);
+
+            if (GetParam() == VIDEO && strncmp(mime, "video/", 6) == 0) {
+                mTrackIndex = trackIndex;
+
+                mSourceFormat = std::shared_ptr<AMediaFormat>(
+                        trackFormat, std::bind(AMediaFormat_delete, std::placeholders::_1));
+                ASSERT_NE(mSourceFormat, nullptr);
+
+                mDestinationFormat =
+                        TrackTranscoderTestUtils::getDefaultVideoDestinationFormat(trackFormat);
+                ASSERT_NE(mDestinationFormat, nullptr);
+                break;
+            }
+
+            AMediaFormat_delete(trackFormat);
+        }
+
+        ASSERT_NE(mSourceFormat, nullptr);
+    }
+
+    // Drains the transcoder's output queue in a loop.
+    void drainOutputSampleQueue() {
+        mSampleQueueDrainThread = std::thread{[this] {
+            std::shared_ptr<MediaSample> sample;
+            bool aborted = false;
+            do {
+                aborted = mTranscoder->mOutputQueue.dequeue(&sample);
+            } while (!aborted && !(sample->info.flags & SAMPLE_FLAG_END_OF_STREAM));
+            mQueueWasAborted = aborted;
+            mGotEndOfStream =
+                    sample != nullptr && (sample->info.flags & SAMPLE_FLAG_END_OF_STREAM) != 0;
+        }};
+    }
+
+    void joinDrainThread() {
+        if (mSampleQueueDrainThread.joinable()) {
+            mSampleQueueDrainThread.join();
+        }
+    }
+    void TearDown() override {
+        LOG(DEBUG) << "MediaTrackTranscoderTests tear down";
+        joinDrainThread();
+    }
+
+    ~MediaTrackTranscoderTests() { LOG(DEBUG) << "MediaTrackTranscoderTests destroyed"; }
+
+protected:
+    std::shared_ptr<MediaTrackTranscoder> mTranscoder;
+    std::shared_ptr<TestCallback> mCallback;
+
+    std::shared_ptr<MediaSampleReader> mMediaSampleReader;
+    int mTrackIndex;
+
+    std::shared_ptr<AMediaFormat> mSourceFormat;
+    std::shared_ptr<AMediaFormat> mDestinationFormat;
+
+    std::thread mSampleQueueDrainThread;
+    bool mQueueWasAborted = false;
+    bool mGotEndOfStream = false;
+};
+
+TEST_P(MediaTrackTranscoderTests, WaitNormalOperation) {
+    LOG(DEBUG) << "Testing WaitNormalOperation";
+    EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+              AMEDIA_OK);
+    ASSERT_TRUE(mTranscoder->start());
+    drainOutputSampleQueue();
+    EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+    EXPECT_TRUE(mTranscoder->stop());
+    joinDrainThread();
+    EXPECT_FALSE(mQueueWasAborted);
+    EXPECT_TRUE(mGotEndOfStream);
+}
+
+TEST_P(MediaTrackTranscoderTests, StopNormalOperation) {
+    LOG(DEBUG) << "Testing StopNormalOperation";
+    EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+              AMEDIA_OK);
+    EXPECT_TRUE(mTranscoder->start());
+    EXPECT_TRUE(mTranscoder->stop());
+}
+
+TEST_P(MediaTrackTranscoderTests, StartWithoutConfigure) {
+    LOG(DEBUG) << "Testing StartWithoutConfigure";
+    EXPECT_FALSE(mTranscoder->start());
+}
+
+TEST_P(MediaTrackTranscoderTests, StopWithoutStart) {
+    LOG(DEBUG) << "Testing StopWithoutStart";
+    EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+              AMEDIA_OK);
+    EXPECT_FALSE(mTranscoder->stop());
+}
+
+TEST_P(MediaTrackTranscoderTests, DoubleStartStop) {
+    LOG(DEBUG) << "Testing DoubleStartStop";
+    EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+              AMEDIA_OK);
+    EXPECT_TRUE(mTranscoder->start());
+    EXPECT_FALSE(mTranscoder->start());
+    EXPECT_TRUE(mTranscoder->stop());
+    EXPECT_FALSE(mTranscoder->stop());
+}
+
+TEST_P(MediaTrackTranscoderTests, DoubleConfigure) {
+    LOG(DEBUG) << "Testing DoubleConfigure";
+    EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+              AMEDIA_OK);
+    EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+              AMEDIA_ERROR_UNSUPPORTED);
+}
+
+TEST_P(MediaTrackTranscoderTests, ConfigureAfterFail) {
+    LOG(DEBUG) << "Testing ConfigureAfterFail";
+    EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, -1, mDestinationFormat),
+              AMEDIA_ERROR_INVALID_PARAMETER);
+    EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+              AMEDIA_OK);
+}
+
+TEST_P(MediaTrackTranscoderTests, RestartAfterStop) {
+    LOG(DEBUG) << "Testing RestartAfterStop";
+    EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+              AMEDIA_OK);
+    EXPECT_TRUE(mTranscoder->start());
+    EXPECT_TRUE(mTranscoder->stop());
+    EXPECT_FALSE(mTranscoder->start());
+}
+
+TEST_P(MediaTrackTranscoderTests, RestartAfterFinish) {
+    LOG(DEBUG) << "Testing RestartAfterFinish";
+    EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+              AMEDIA_OK);
+    ASSERT_TRUE(mTranscoder->start());
+    drainOutputSampleQueue();
+    EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+    EXPECT_TRUE(mTranscoder->stop());
+    EXPECT_FALSE(mTranscoder->start());
+
+    joinDrainThread();
+    EXPECT_FALSE(mQueueWasAborted);
+    EXPECT_TRUE(mGotEndOfStream);
+}
+
+TEST_P(MediaTrackTranscoderTests, AbortOutputQueue) {
+    LOG(DEBUG) << "Testing AbortOutputQueue";
+    EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+              AMEDIA_OK);
+    ASSERT_TRUE(mTranscoder->start());
+    mTranscoder->mOutputQueue.abort();
+    drainOutputSampleQueue();
+    EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_ERROR_IO);
+    EXPECT_TRUE(mTranscoder->stop());
+
+    joinDrainThread();
+    EXPECT_TRUE(mQueueWasAborted);
+    EXPECT_FALSE(mGotEndOfStream);
+}
+
+TEST_P(MediaTrackTranscoderTests, NullSampleReader) {
+    LOG(DEBUG) << "Testing NullSampleReader";
+    std::shared_ptr<MediaSampleReader> nullSampleReader;
+    EXPECT_NE(mTranscoder->configure(nullSampleReader, mTrackIndex, mDestinationFormat), AMEDIA_OK);
+    EXPECT_FALSE(mTranscoder->start());
+}
+
+TEST_P(MediaTrackTranscoderTests, InvalidTrackIndex) {
+    LOG(DEBUG) << "Testing InvalidTrackIndex";
+    EXPECT_NE(mTranscoder->configure(mMediaSampleReader, -1, mDestinationFormat), AMEDIA_OK);
+    EXPECT_NE(mTranscoder->configure(mMediaSampleReader, mMediaSampleReader->getTrackCount(),
+                                     mDestinationFormat),
+              AMEDIA_OK);
+}
+
+};  // namespace android
+
+using namespace android;
+
+INSTANTIATE_TEST_SUITE_P(MediaTrackTranscoderTestsAll, MediaTrackTranscoderTests,
+                         ::testing::Values(VIDEO));
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
new file mode 100644
index 0000000..6b9131c
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+#include <media/MediaTrackTranscoder.h>
+
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+
+namespace android {
+
+//
+// This file contains test utilities used by more than one track transcoder test.
+//
+
+class TrackTranscoderTestUtils {
+public:
+    static std::shared_ptr<AMediaFormat> getDefaultVideoDestinationFormat(
+            AMediaFormat* sourceFormat) {
+        // Default video destination format setup.
+        static constexpr float kFrameRate = 30.0f;
+        static constexpr float kIFrameInterval = 30.0f;
+        static constexpr int32_t kBitRate = 2 * 1000 * 1000;
+        static constexpr int32_t kColorFormatSurface = 0x7f000789;
+
+        AMediaFormat* destinationFormat = AMediaFormat_new();
+        AMediaFormat_copy(destinationFormat, sourceFormat);
+        AMediaFormat_setFloat(destinationFormat, AMEDIAFORMAT_KEY_FRAME_RATE, kFrameRate);
+        AMediaFormat_setFloat(destinationFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL,
+                              kIFrameInterval);
+        AMediaFormat_setInt32(destinationFormat, AMEDIAFORMAT_KEY_BIT_RATE, kBitRate);
+        AMediaFormat_setInt32(destinationFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT,
+                              kColorFormatSurface);
+
+        return std::shared_ptr<AMediaFormat>(destinationFormat,
+                                             std::bind(AMediaFormat_delete, std::placeholders::_1));
+    }
+};
+
+class TestCallback : public MediaTrackTranscoderCallback {
+public:
+    TestCallback() = default;
+    ~TestCallback() = default;
+
+    // MediaTrackTranscoderCallback
+    void onTrackFinished(MediaTrackTranscoder* transcoder __unused) {
+        std::unique_lock<std::mutex> lock(mMutex);
+        mTranscodingFinished = true;
+        mCv.notify_all();
+    }
+
+    void onTrackError(MediaTrackTranscoder* transcoder __unused, media_status_t status) {
+        std::unique_lock<std::mutex> lock(mMutex);
+        mTranscodingFinished = true;
+        mStatus = status;
+        mCv.notify_all();
+    }
+    // ~MediaTrackTranscoderCallback
+
+    media_status_t waitUntilFinished() {
+        std::unique_lock<std::mutex> lock(mMutex);
+        while (!mTranscodingFinished) {
+            mCv.wait(lock);
+        }
+        return mStatus;
+    }
+
+private:
+    media_status_t mStatus = AMEDIA_OK;
+    std::mutex mMutex;
+    std::condition_variable mCv;
+    bool mTranscodingFinished = false;
+};
+
+};  // namespace android
diff --git a/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
new file mode 100644
index 0000000..3cec1a1
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
@@ -0,0 +1,160 @@
+/*
+ * 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 VideoTrackTranscoder
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "VideoTrackTranscoderTests"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <media/MediaSampleReaderNDK.h>
+#include <media/VideoTrackTranscoder.h>
+#include <utils/Timers.h>
+
+#include "TrackTranscoderTestUtils.h"
+
+namespace android {
+
+// TODO(b/155304421): Implement more advanced video specific tests:
+//  - Codec conversions (HEVC -> AVC).
+//  - Bitrate validation.
+//  - Output frame validation through PSNR.
+
+class VideoTrackTranscoderTests : public ::testing::Test {
+public:
+    VideoTrackTranscoderTests() { LOG(DEBUG) << "VideoTrackTranscoderTests created"; }
+
+    void SetUp() override {
+        LOG(DEBUG) << "VideoTrackTranscoderTests set up";
+        const char* sourcePath =
+                "/data/local/tmp/TranscoderTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+
+        const int sourceFd = open(sourcePath, O_RDONLY);
+        ASSERT_GT(sourceFd, 0);
+
+        const off_t fileSize = lseek(sourceFd, 0, SEEK_END);
+        lseek(sourceFd, 0, SEEK_SET);
+
+        mMediaSampleReader = MediaSampleReaderNDK::createFromFd(sourceFd, 0, fileSize);
+        ASSERT_NE(mMediaSampleReader, nullptr);
+        close(sourceFd);
+
+        for (size_t trackIndex = 0; trackIndex < mMediaSampleReader->getTrackCount();
+             ++trackIndex) {
+            AMediaFormat* trackFormat = mMediaSampleReader->getTrackFormat(trackIndex);
+            ASSERT_NE(trackFormat, nullptr);
+
+            const char* mime = nullptr;
+            AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+            ASSERT_NE(mime, nullptr);
+
+            if (strncmp(mime, "video/", 6) == 0) {
+                mTrackIndex = trackIndex;
+
+                mSourceFormat = std::shared_ptr<AMediaFormat>(
+                        trackFormat, std::bind(AMediaFormat_delete, std::placeholders::_1));
+                ASSERT_NE(mSourceFormat, nullptr);
+
+                mDestinationFormat =
+                        TrackTranscoderTestUtils::getDefaultVideoDestinationFormat(trackFormat);
+                ASSERT_NE(mDestinationFormat, nullptr);
+                break;
+            }
+
+            AMediaFormat_delete(trackFormat);
+        }
+
+        ASSERT_NE(mSourceFormat, nullptr);
+    }
+
+    void TearDown() override { LOG(DEBUG) << "VideoTrackTranscoderTests tear down"; }
+
+    ~VideoTrackTranscoderTests() { LOG(DEBUG) << "VideoTrackTranscoderTests destroyed"; }
+
+    std::shared_ptr<MediaSampleReader> mMediaSampleReader;
+    int mTrackIndex;
+    std::shared_ptr<AMediaFormat> mSourceFormat;
+    std::shared_ptr<AMediaFormat> mDestinationFormat;
+};
+
+TEST_F(VideoTrackTranscoderTests, SampleSanity) {
+    LOG(DEBUG) << "Testing SampleSanity";
+    std::shared_ptr<TestCallback> callback = std::make_shared<TestCallback>();
+    VideoTrackTranscoder transcoder{callback};
+
+    EXPECT_EQ(transcoder.configure(mMediaSampleReader, mTrackIndex, mDestinationFormat), AMEDIA_OK);
+    ASSERT_TRUE(transcoder.start());
+
+    std::thread sampleConsumerThread{[&transcoder] {
+        uint64_t sampleCount = 0;
+        std::shared_ptr<MediaSample> sample;
+        while (!transcoder.mOutputQueue.dequeue(&sample)) {
+            ASSERT_NE(sample, nullptr);
+            const uint32_t flags = sample->info.flags;
+
+            if (sampleCount == 0) {
+                // Expect first sample to be a codec config.
+                EXPECT_TRUE((flags & SAMPLE_FLAG_CODEC_CONFIG) != 0);
+                EXPECT_TRUE((flags & SAMPLE_FLAG_SYNC_SAMPLE) == 0);
+                EXPECT_TRUE((flags & SAMPLE_FLAG_END_OF_STREAM) == 0);
+                EXPECT_TRUE((flags & SAMPLE_FLAG_PARTIAL_FRAME) == 0);
+            } else if (sampleCount == 1) {
+                // Expect second sample to be a sync sample.
+                EXPECT_TRUE((flags & SAMPLE_FLAG_CODEC_CONFIG) == 0);
+                EXPECT_TRUE((flags & SAMPLE_FLAG_SYNC_SAMPLE) != 0);
+                EXPECT_TRUE((flags & SAMPLE_FLAG_END_OF_STREAM) == 0);
+            }
+
+            if (!(flags & SAMPLE_FLAG_END_OF_STREAM)) {
+                // Expect a valid buffer unless it is EOS.
+                EXPECT_NE(sample->buffer, nullptr);
+                EXPECT_NE(sample->bufferId, 0xBAADF00D);
+                EXPECT_GT(sample->info.size, 0);
+            }
+
+            ++sampleCount;
+            if (sample->info.flags & SAMPLE_FLAG_END_OF_STREAM) {
+                break;
+            }
+            sample.reset();
+        }
+    }};
+
+    EXPECT_EQ(callback->waitUntilFinished(), AMEDIA_OK);
+    EXPECT_TRUE(transcoder.stop());
+
+    sampleConsumerThread.join();
+}
+
+// VideoTrackTranscoder needs a valid destination format.
+TEST_F(VideoTrackTranscoderTests, NullDestinationFormat) {
+    LOG(DEBUG) << "Testing NullDestinationFormat";
+    std::shared_ptr<TestCallback> callback = std::make_shared<TestCallback>();
+    std::shared_ptr<AMediaFormat> nullFormat;
+
+    VideoTrackTranscoder transcoder{callback};
+    EXPECT_EQ(transcoder.configure(mMediaSampleReader, 0 /* trackIndex */, nullFormat),
+              AMEDIA_ERROR_INVALID_PARAMETER);
+}
+
+}  // namespace android
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
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 dbee604..3c30e46 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
@@ -27,3 +27,9 @@
 
 echo "testing MediaSampleQueue"
 adb shell /data/nativetest64/MediaSampleQueueTests/MediaSampleQueueTests
+
+echo "testing MediaTrackTranscoder"
+adb shell /data/nativetest64/MediaTrackTranscoderTests/MediaTrackTranscoderTests
+
+echo "testing VideoTrackTranscoder"
+adb shell /data/nativetest64/VideoTrackTranscoderTests/VideoTrackTranscoderTests
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index b597583..3ba87d6 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -56,6 +56,7 @@
 #include <media/stagefright/CCodec.h>
 #include <media/stagefright/MediaCodec.h>
 #include <media/stagefright/MediaCodecList.h>
+#include <media/stagefright/MediaCodecConstants.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/MediaFilter.h>
@@ -199,6 +200,7 @@
     void addResource(const MediaResourceParcel &resource);
     void removeResource(const MediaResourceParcel &resource);
     void removeClient();
+    void markClientForPendingRemoval();
     bool reclaimResource(const std::vector<MediaResourceParcel> &resources);
 
 private:
@@ -280,6 +282,14 @@
     mService->removeClient(mPid, getId(mClient));
 }
 
+void MediaCodec::ResourceManagerServiceProxy::markClientForPendingRemoval() {
+    Mutex::Autolock _l(mLock);
+    if (mService == nullptr) {
+        return;
+    }
+    mService->markClientForPendingRemoval(mPid, getId(mClient));
+}
+
 bool MediaCodec::ResourceManagerServiceProxy::reclaimResource(
         const std::vector<MediaResourceParcel> &resources) {
     Mutex::Autolock _l(mLock);
@@ -297,6 +307,33 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
+class MediaCodec::ReleaseSurface {
+public:
+    ReleaseSurface() {
+        BufferQueue::createBufferQueue(&mProducer, &mConsumer);
+        mSurface = new Surface(mProducer, false /* controlledByApp */);
+        struct ConsumerListener : public BnConsumerListener {
+            void onFrameAvailable(const BufferItem&) override {}
+            void onBuffersReleased() override {}
+            void onSidebandStreamChanged() override {}
+        };
+        sp<ConsumerListener> listener{new ConsumerListener};
+        mConsumer->consumerConnect(listener, false);
+        mConsumer->setConsumerName(String8{"MediaCodec.release"});
+    }
+
+    const sp<Surface> &getSurface() {
+        return mSurface;
+    }
+
+private:
+    sp<IGraphicBufferProducer> mProducer;
+    sp<IGraphicBufferConsumer> mConsumer;
+    sp<Surface> mSurface;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
 namespace {
 
 enum {
@@ -1432,7 +1469,13 @@
 
 status_t MediaCodec::release() {
     sp<AMessage> msg = new AMessage(kWhatRelease, this);
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
 
+status_t MediaCodec::releaseAsync() {
+    sp<AMessage> msg = new AMessage(kWhatRelease, this);
+    msg->setInt32("async", 1);
     sp<AMessage> response;
     return PostAndAwaitResponse(msg, &response);
 }
@@ -2263,7 +2306,7 @@
                     if (mSurface != nullptr && !mAllowFrameDroppingBySurface) {
                         // signal frame dropping mode in the input format as this may also be
                         // meaningful and confusing for an encoder in a transcoder scenario
-                        mInputFormat->setInt32("allow-frame-drop", mAllowFrameDroppingBySurface);
+                        mInputFormat->setInt32(KEY_ALLOW_FRAME_DROP, mAllowFrameDroppingBySurface);
                     }
                     ALOGV("[%s] configured as input format: %s, output format: %s",
                             mComponentName.c_str(),
@@ -2600,7 +2643,9 @@
 
                     mResourceManagerProxy->removeClient();
 
-                    (new AMessage)->postReply(mReplyID);
+                    if (mReplyID != nullptr) {
+                        (new AMessage)->postReply(mReplyID);
+                    }
                     break;
                 }
 
@@ -2720,7 +2765,7 @@
             }
 
             if (obj != NULL) {
-                if (!format->findInt32("allow-frame-drop", &mAllowFrameDroppingBySurface)) {
+                if (!format->findInt32(KEY_ALLOW_FRAME_DROP, &mAllowFrameDroppingBySurface)) {
                     // allow frame dropping by surface by default
                     mAllowFrameDroppingBySurface = true;
                 }
@@ -2987,6 +3032,26 @@
                 break;
             }
 
+            int32_t async = 0;
+            if (msg->findInt32("async", &async) && async) {
+                if ((mState ==  CONFIGURED || mState == STARTED || mState == FLUSHED)
+                       && mSurface != NULL) {
+                    if (!mReleaseSurface) {
+                        mReleaseSurface.reset(new ReleaseSurface);
+                    }
+                    status_t err = connectToSurface(mReleaseSurface->getSurface());
+                    ALOGW_IF(err != OK, "error connecting to release surface: err = %d", err);
+                    if (err == OK && !(mFlags & kFlagUsesSoftwareRenderer)) {
+                        err = mCodec->setSurface(mReleaseSurface->getSurface());
+                        ALOGW_IF(err != OK, "error setting release surface: err = %d", err);
+                    }
+                    if (err == OK) {
+                        (void)disconnectFromSurface();
+                        mSurface = mReleaseSurface->getSurface();
+                    }
+                }
+            }
+
             mReplyID = replyID;
             setState(msg->what() == kWhatStop ? STOPPING : RELEASING);
 
@@ -2999,6 +3064,12 @@
                 pushBlankBuffersToNativeWindow(mSurface.get());
             }
 
+            if (async) {
+                mResourceManagerProxy->markClientForPendingRemoval();
+                (new AMessage)->postReply(mReplyID);
+                mReplyID = 0;
+            }
+
             break;
         }
 
diff --git a/media/libstagefright/codecs/amrnb/fuzzer/amrnb_dec_fuzzer.cpp b/media/libstagefright/codecs/amrnb/fuzzer/amrnb_dec_fuzzer.cpp
index d4e7e5c..c7a7378 100644
--- a/media/libstagefright/codecs/amrnb/fuzzer/amrnb_dec_fuzzer.cpp
+++ b/media/libstagefright/codecs/amrnb/fuzzer/amrnb_dec_fuzzer.cpp
@@ -26,8 +26,10 @@
 constexpr int32_t kBitsPerSample = 16;
 constexpr int32_t kOutputBufferSize = kSamplesPerFrame * kBitsPerSample / 8;
 const bitstream_format kBitStreamFormats[2] = {MIME_IETF, IF2};
-const int32_t kLocalWmfDecBytesPerFrame[8] = {12, 13, 15, 17, 19, 20, 26, 31};
-const int32_t kLocalIf2DecBytesPerFrame[8] = {13, 14, 16, 18, 19, 21, 26, 31};
+const int32_t kLocalWmfDecBytesPerFrame[16] = {12, 13, 15, 17, 19, 20, 26, 31,
+                                               5,  6,  5,  5,  0,  0,  0,  0};
+const int32_t kLocalIf2DecBytesPerFrame[16] = {13, 14, 16, 18, 19, 21, 26, 31,
+                                               13, 14, 16, 18, 19, 21, 26, 31};
 
 class Codec {
  public:
@@ -52,7 +54,7 @@
     bitstream_format bitsreamFormat = kBitStreamFormats[bit];
     int32_t frameSize = 0;
     /* Find frame type */
-    Frame_Type_3GPP frameType = static_cast<Frame_Type_3GPP>((mode >> 3) & 0x07);
+    Frame_Type_3GPP frameType = static_cast<Frame_Type_3GPP>((mode >> 3) & 0x0f);
     ++data;
     --size;
     if (bit) {
diff --git a/media/libstagefright/codecs/m4v_h263/dec/src/vop.cpp b/media/libstagefright/codecs/m4v_h263/dec/src/vop.cpp
index 679b091..a11f55e 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/src/vop.cpp
+++ b/media/libstagefright/codecs/m4v_h263/dec/src/vop.cpp
@@ -409,7 +409,9 @@
         if (!BitstreamRead1Bits(stream)) return PV_FAIL;
 
         /* video_object_layer_width (13 bits) */
-        video->displayWidth = video->width = (int) BitstreamReadBits16(stream, 13);
+        tmpvar = BitstreamReadBits16(stream, 13);
+        if (!tmpvar) return PV_FAIL;
+        video->displayWidth = video->width = tmpvar;
 
         /* round up to a multiple of MB_SIZE.   08/09/2000 */
         video->width = (video->width + 15) & -16;
@@ -419,7 +421,9 @@
         if (!BitstreamRead1Bits(stream)) return PV_FAIL;
 
         /* video_object_layer_height (13 bits) */
-        video->displayHeight = video->height = (int) BitstreamReadBits16(stream, 13);
+        tmpvar = BitstreamReadBits16(stream, 13);
+        if (!tmpvar) return PV_FAIL;
+        video->displayHeight = video->height = tmpvar;
 
         /* round up to a multiple of MB_SIZE.   08/09/2000 */
         video->height = (video->height + 15) & -16;
diff --git a/media/libstagefright/id3/Android.bp b/media/libstagefright/id3/Android.bp
index c8173cf..02de2c0 100644
--- a/media/libstagefright/id3/Android.bp
+++ b/media/libstagefright/id3/Android.bp
@@ -4,7 +4,9 @@
     srcs: ["ID3.cpp"],
 
     header_libs: [
-        "libmedia_headers",
+        "libmedia_datasource_headers",
+        "libstagefright_foundation_headers",
+        "libstagefright_headers",
         "media_ndk_headers",
     ],
 
@@ -18,6 +20,12 @@
         ],
         cfi: true,
     },
+    host_supported: true,
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
 }
 
 //###############################################################################
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index 736f626..c6b6639 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -146,6 +146,8 @@
     // object.
     status_t release();
 
+    status_t releaseAsync();
+
     status_t flush();
 
     status_t queueInputBuffer(
@@ -516,6 +518,9 @@
                                                  // when low latency is on
     int64_t mInputBufferCounter;  // number of input buffers queued since last reset/flush
 
+    class ReleaseSurface;
+    std::unique_ptr<ReleaseSurface> mReleaseSurface;
+
     sp<BatteryChecker> mBatteryChecker;
 
     void statsBufferSent(int64_t presentationUs);
diff --git a/media/libstagefright/include/media/stagefright/MediaCodecConstants.h b/media/libstagefright/include/media/stagefright/MediaCodecConstants.h
index 178d334..9c67338 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodecConstants.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodecConstants.h
@@ -744,6 +744,7 @@
 constexpr char KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT[] = "aac-max-output-channel_count";
 constexpr char KEY_AAC_PROFILE[] = "aac-profile";
 constexpr char KEY_AAC_SBR_MODE[] = "aac-sbr-mode";
+constexpr char KEY_ALLOW_FRAME_DROP[] = "allow-frame-drop";
 constexpr char KEY_AUDIO_SESSION_ID[] = "audio-session-id";
 constexpr char KEY_BIT_RATE[] = "bitrate";
 constexpr char KEY_BITRATE_MODE[] = "bitrate-mode";
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index 657144c..4bb21fa 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -1153,7 +1153,7 @@
     }
 
     const RangeInfo &info = *mRangeInfos.begin();
-    if (mBuffer->size() < info.mLength) {
+    if (info.mLength == 0 || mBuffer->size() < info.mLength) {
         return NULL;
     }
 
diff --git a/media/libstagefright/rtsp/ASessionDescription.cpp b/media/libstagefright/rtsp/ASessionDescription.cpp
index 4502f62..5b5b4b1 100644
--- a/media/libstagefright/rtsp/ASessionDescription.cpp
+++ b/media/libstagefright/rtsp/ASessionDescription.cpp
@@ -27,6 +27,8 @@
 
 namespace android {
 
+constexpr unsigned kDefaultAs = 960; // kbps?
+
 ASessionDescription::ASessionDescription()
     : mIsValid(false) {
 }
@@ -401,7 +403,7 @@
     sdp.append("\r\n");
 
     sdp.append("b=AS:");
-    sdp.append(as > 0 ? as : 960);
+    sdp.append(as > 0 ? as : kDefaultAs);
     sdp.append("\r\n");
 
     sdp.append("a=rtpmap:");
@@ -420,7 +422,7 @@
         sdp.append("\r\n");
     }
 
-    if (width > 0 && height > 0) {
+    if (!isAudio && width > 0 && height > 0) {
         sdp.append("a=framesize:");
         sdp.append(payloadType);
         sdp.append(" ");
diff --git a/media/libstagefright/rtsp/Android.bp b/media/libstagefright/rtsp/Android.bp
index 84a47af..6179142 100644
--- a/media/libstagefright/rtsp/Android.bp
+++ b/media/libstagefright/rtsp/Android.bp
@@ -22,7 +22,6 @@
     ],
 
     shared_libs: [
-        "libandroid",
         "libandroid_net",
         "libcrypto",
         "libdatasource",
diff --git a/media/mediaserver/mediaserver.rc b/media/mediaserver/mediaserver.rc
index ecb75a9..05373c9 100644
--- a/media/mediaserver/mediaserver.rc
+++ b/media/mediaserver/mediaserver.rc
@@ -6,4 +6,4 @@
     user media
     group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc mediadrm
     ioprio rt 4
-    writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
+    task_profiles ProcessCapacityHigh HighPerformance
diff --git a/media/ndk/Android.bp b/media/ndk/Android.bp
index 80941e0..f365af3 100644
--- a/media/ndk/Android.bp
+++ b/media/ndk/Android.bp
@@ -196,3 +196,41 @@
         "frameworks/av/media/ndk/",
     ],
 }
+
+cc_library_static {
+    name: "libmediandk_format",
+
+    host_supported: true,
+
+    srcs: [
+        "NdkMediaFormat.cpp",
+    ],
+
+    header_libs: [
+        "libstagefright_foundation_headers",
+    ],
+
+    cflags: [
+        "-DEXPORT=__attribute__((visibility(\"default\")))",
+        "-Werror",
+        "-Wall",
+    ],
+
+    export_include_dirs: ["include"],
+
+    sanitize: {
+        misc_undefined: [
+            "unsigned-integer-overflow",
+            "signed-integer-overflow",
+        ],
+        cfi: true,
+    },
+
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+
+    apex_available: ["com.android.media"],
+}
diff --git a/media/ndk/NdkMediaFormat.cpp b/media/ndk/NdkMediaFormat.cpp
index ab0cb63..8680641 100644
--- a/media/ndk/NdkMediaFormat.cpp
+++ b/media/ndk/NdkMediaFormat.cpp
@@ -26,9 +26,6 @@
 #include <utils/StrongPointer.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/AMessage.h>
-#include <android_util_Binder.h>
-
-#include <jni.h>
 
 using namespace android;
 
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 3fd55d4..ef7b1ab 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -88,6 +88,8 @@
 #include "SpdifStreamOut.h"
 #include "AudioHwDevice.h"
 #include "NBAIO_Tee.h"
+#include "ThreadMetrics.h"
+#include "TrackMetrics.h"
 
 #include <android/os/IPowerManager.h>
 
diff --git a/services/audioflinger/PlaybackTracks.h b/services/audioflinger/PlaybackTracks.h
index 1ff03c4..d8eebf3 100644
--- a/services/audioflinger/PlaybackTracks.h
+++ b/services/audioflinger/PlaybackTracks.h
@@ -171,6 +171,16 @@
 
             void    setTeePatches(TeePatches teePatches);
 
+    void tallyUnderrunFrames(size_t frames) override {
+       if (isOut()) { // we expect this from output tracks only
+           mAudioTrackServerProxy->tallyUnderrunFrames(frames);
+           // Fetch absolute numbers from AudioTrackShared as it counts
+           // contiguous underruns as a one -- we want a consistent number.
+           // TODO: isolate this counting into a class.
+           mTrackMetrics.logUnderruns(mAudioTrackServerProxy->getUnderrunCount(),
+                   mAudioTrackServerProxy->getUnderrunFrames());
+       }
+    }
 protected:
     // for numerous
     friend class PlaybackThread;
diff --git a/services/audioflinger/ThreadMetrics.h b/services/audioflinger/ThreadMetrics.h
new file mode 100644
index 0000000..7989de1
--- /dev/null
+++ b/services/audioflinger/ThreadMetrics.h
@@ -0,0 +1,196 @@
+/*
+ * 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_AUDIO_THREADMETRICS_H
+#define ANDROID_AUDIO_THREADMETRICS_H
+
+#include <mutex>
+
+namespace android {
+
+/**
+ * ThreadMetrics handles the AudioFlinger thread log statistics.
+ *
+ * We aggregate metrics for a particular device for proper analysis.
+ * This includes power, performance, and usage metrics.
+ *
+ * This class is thread-safe with a lock for safety.  There is no risk of deadlock
+ * as this class only executes external one-way calls in Mediametrics and does not
+ * call any other AudioFlinger class.
+ *
+ * Terminology:
+ * An AudioInterval is a contiguous playback segment.
+ * An AudioIntervalGroup is a group of continuous playback segments on the same device.
+ *
+ * We currently deliver metrics based on an AudioIntervalGroup.
+ */
+class ThreadMetrics final {
+public:
+    ThreadMetrics(std::string metricsId, bool isOut)
+        : mMetricsId(std::move(metricsId))
+        , mIsOut(isOut)
+        {}
+
+    ~ThreadMetrics() {
+        logEndInterval(); // close any open interval groups
+        std::lock_guard l(mLock);
+        deliverCumulativeMetrics(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP);
+        mediametrics::LogItem(mMetricsId)
+            .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_DTOR)
+            .record();
+    }
+
+    // Called under the following circumstances
+    // 1) Upon a createPatch and we are not in standby
+    // 2) We come out of standby
+    void logBeginInterval() {
+        std::lock_guard l(mLock);
+        if (mDevices != mCreatePatchDevices) {
+            deliverCumulativeMetrics(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP);
+            mDevices = mCreatePatchDevices; // set after endAudioIntervalGroup
+            resetIntervalGroupMetrics();
+            deliverDeviceMetrics(
+                    AMEDIAMETRICS_PROP_EVENT_VALUE_BEGINAUDIOINTERVALGROUP, mDevices.c_str());
+        }
+        if (mIntervalStartTimeNs == 0) {
+            ++mIntervalCount;
+            mIntervalStartTimeNs = systemTime();
+        }
+    }
+
+    void logConstructor(pid_t pid, const char *threadType, int32_t id) const {
+        mediametrics::LogItem(mMetricsId)
+            .setPid(pid)
+            .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR)
+            .set(AMEDIAMETRICS_PROP_TYPE, threadType)
+            .set(AMEDIAMETRICS_PROP_THREADID, id)
+            .record();
+    }
+
+    void logCreatePatch(const std::string& devices) {
+        std::lock_guard l(mLock);
+        mCreatePatchDevices = devices;
+        mediametrics::LogItem(mMetricsId)
+            .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CREATEAUDIOPATCH)
+            .set(AMEDIAMETRICS_PROP_OUTPUTDEVICES, devices)
+            .record();
+    }
+
+    // Called when we are removed from the Thread.
+    void logEndInterval() {
+        std::lock_guard l(mLock);
+        if (mIntervalStartTimeNs != 0) {
+            const int64_t elapsedTimeNs = systemTime() - mIntervalStartTimeNs;
+            mIntervalStartTimeNs = 0;
+            mCumulativeTimeNs += elapsedTimeNs;
+            mDeviceTimeNs += elapsedTimeNs;
+        }
+    }
+
+    void logThrottleMs(double throttleMs) const {
+        mediametrics::LogItem(mMetricsId)
+            // ms units always double
+            .set(AMEDIAMETRICS_PROP_THROTTLEMS, (double)throttleMs)
+            .record();
+    }
+
+    void logLatency(double latencyMs) {
+        mediametrics::LogItem(mMetricsId)
+            .set(AMEDIAMETRICS_PROP_LATENCYMS, latencyMs)
+            .record();
+        std::lock_guard l(mLock);
+        mDeviceLatencyMs.add(latencyMs);
+    }
+
+    // TODO: further implement this.
+    void logUnderrunFrames(size_t count, size_t frames) {
+        std::lock_guard l(mLock);
+        mUnderrunCount = count;
+        mUnderrunFrames = frames;
+    }
+
+    const std::string& getMetricsId() const {
+        return mMetricsId;
+    }
+
+private:
+    // no lock required - all arguments and constants.
+    void deliverDeviceMetrics(const char *eventName, const char *devices) const {
+        mediametrics::LogItem(mMetricsId)
+            .set(AMEDIAMETRICS_PROP_EVENT, eventName)
+            .set(mIsOut ? AMEDIAMETRICS_PROP_OUTPUTDEVICES
+                   : AMEDIAMETRICS_PROP_INPUTDEVICES, devices)
+           .record();
+    }
+
+    void deliverCumulativeMetrics(const char *eventName) const REQUIRES(mLock) {
+        if (mIntervalCount > 0) {
+            mediametrics::LogItem item(mMetricsId);
+            item.set(AMEDIAMETRICS_PROP_CUMULATIVETIMENS, mCumulativeTimeNs)
+                .set(AMEDIAMETRICS_PROP_DEVICETIMENS, mDeviceTimeNs)
+                .set(AMEDIAMETRICS_PROP_EVENT, eventName)
+                .set(AMEDIAMETRICS_PROP_INTERVALCOUNT, (int32_t)mIntervalCount);
+            if (mDeviceLatencyMs.getN() > 0) {
+                item.set(AMEDIAMETRICS_PROP_DEVICELATENCYMS, mDeviceLatencyMs.getMean());
+            }
+            if (mUnderrunCount > 0) {
+                item.set(AMEDIAMETRICS_PROP_UNDERRUN, (int32_t)mUnderrunCount)
+                    .set(AMEDIAMETRICS_PROP_UNDERRUNFRAMES, (int64_t)mUnderrunFrames);
+            }
+            item.record();
+        }
+    }
+
+    void resetIntervalGroupMetrics() REQUIRES(mLock) {
+        // mDevices is not reset by clear
+
+        mIntervalCount = 0;
+        mIntervalStartTimeNs = 0;
+        // mCumulativeTimeNs is not reset by clear.
+        mDeviceTimeNs = 0;
+
+        mDeviceLatencyMs.reset();
+
+        mUnderrunCount = 0;
+        mUnderrunFrames = 0;
+    }
+
+    const std::string mMetricsId;
+    const bool        mIsOut;  // if true, than a playback track, otherwise used for record.
+
+    mutable           std::mutex mLock;
+
+    // Devices in the interval group.
+    std::string       mDevices GUARDED_BY(mLock);
+    std::string       mCreatePatchDevices GUARDED_BY(mLock);
+
+    // Number of intervals and playing time
+    int32_t           mIntervalCount GUARDED_BY(mLock) = 0;
+    int64_t           mIntervalStartTimeNs GUARDED_BY(mLock) = 0;
+    int64_t           mCumulativeTimeNs GUARDED_BY(mLock) = 0;
+    int64_t           mDeviceTimeNs GUARDED_BY(mLock) = 0;
+
+    // latency and startup for each interval.
+    audio_utils::Statistics<double> mDeviceLatencyMs GUARDED_BY(mLock);
+
+    // underrun count and frames
+    int64_t           mUnderrunCount GUARDED_BY(mLock) = 0;
+    int64_t           mUnderrunFrames GUARDED_BY(mLock) = 0;
+};
+
+} // namespace android
+
+#endif // ANDROID_AUDIO_THREADMETRICS_H
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 0e96522..5266192 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -220,6 +220,9 @@
 {
     std::stringstream ss;
     for (size_t i = 0; i < patch->num_sinks; ++i) {
+        if (i > 0) {
+            ss << "|";
+        }
         ss << "(" << toString(patch->sinks[i].ext.device.type)
             << ", " << patch->sinks[i].ext.device.address << ")";
     }
@@ -230,6 +233,9 @@
 {
     std::stringstream ss;
     for (size_t i = 0; i < patch->num_sources; ++i) {
+        if (i > 0) {
+            ss << "|";
+        }
         ss << "(" << toString(patch->sources[i].ext.device.type)
             << ", " << patch->sources[i].ext.device.address << ")";
     }
@@ -486,11 +492,13 @@
 }
 
 AudioFlinger::ThreadBase::ThreadBase(const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
-        type_t type, bool systemReady)
+        type_t type, bool systemReady, bool isOut)
     :   Thread(false /*canCallJava*/),
         mType(type),
         mAudioFlinger(audioFlinger),
-        mMetricsId(std::string(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD) + std::to_string(id)),
+        mThreadMetrics(std::string(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD) + std::to_string(id),
+               isOut),
+        mIsOut(isOut),
         // mSampleRate, mFrameCount, mChannelMask, mChannelCount, mFrameSize, mFormat, mBufferSize
         // are set by PlaybackThread::readOutputParameters_l() or
         // RecordThread::readInputParameters_l()
@@ -502,13 +510,7 @@
         mSystemReady(systemReady),
         mSignalPending(false)
 {
-    mediametrics::LogItem(mMetricsId)
-        .setPid(getpid())
-        .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR)
-        .set(AMEDIAMETRICS_PROP_TYPE, threadTypeToString(type))
-        .set(AMEDIAMETRICS_PROP_THREADID, id)
-        .record();
-
+    mThreadMetrics.logConstructor(getpid(), threadTypeToString(type), id);
     memset(&mPatch, 0, sizeof(struct audio_patch));
 }
 
@@ -525,10 +527,6 @@
     }
 
     sendStatistics(true /* force */);
-
-    mediametrics::LogItem(mMetricsId)
-        .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_DTOR)
-        .record();
 }
 
 status_t AudioFlinger::ThreadBase::readyToRun()
@@ -1685,6 +1683,7 @@
 #ifdef TEE_SINK
     track->dumpTee(-1 /* fd */, "_REMOVE");
 #endif
+    track->logEndInterval(); // log to MediaMetrics
     return index;
 }
 
@@ -1827,7 +1826,7 @@
                                              audio_io_handle_t id,
                                              type_t type,
                                              bool systemReady)
-    :   ThreadBase(audioFlinger, id, type, systemReady),
+    :   ThreadBase(audioFlinger, id, type, systemReady, true /* isOut */),
         mNormalFrameCount(0), mSinkBuffer(NULL),
         mMixerBufferEnabled(AudioFlinger::kEnableExtendedPrecision),
         mMixerBuffer(NULL),
@@ -2552,6 +2551,7 @@
             chain->incActiveTrackCnt();
         }
 
+        track->logBeginInterval(patchSinksToString(&mPatch)); // log to MediaMetrics
         status = NO_ERROR;
     }
 
@@ -2875,7 +2875,7 @@
     }
 
     audio_output_flags_t flags = mOutput->flags;
-    mediametrics::LogItem item(mMetricsId);
+    mediametrics::LogItem item(mThreadMetrics.getMetricsId()); // TODO: method in ThreadMetrics?
     item.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_READPARAMETERS)
         .set(AMEDIAMETRICS_PROP_ENCODING, formatToString(mFormat).c_str())
         .set(AMEDIAMETRICS_PROP_SAMPLERATE, (int32_t)mSampleRate)
@@ -3050,6 +3050,10 @@
 {
     if (!mMasterMute) {
         char value[PROPERTY_VALUE_MAX];
+        if (mOutDeviceTypeAddrs.empty()) {
+            ALOGD("ro.audio.silent is ignored since no output device is set");
+            return;
+        }
         if (isSingleDeviceType(outDeviceTypes(), AUDIO_DEVICE_OUT_REMOTE_SUBMIX)) {
             ALOGD("ro.audio.silent will be ignored for threads on AUDIO_DEVICE_OUT_REMOTE_SUBMIX");
             return;
@@ -3129,7 +3133,10 @@
 
     mNumWrites++;
     mInWrite = false;
-    mStandby = false;
+    if (mStandby) {
+        mThreadMetrics.logBeginInterval();
+        mStandby = false;
+    }
     return bytesWritten;
 }
 
@@ -3668,8 +3675,9 @@
                     // This is where we go into standby
                     if (!mStandby) {
                         LOG_AUDIO_STATE();
+                        mThreadMetrics.logEndInterval();
+                        mStandby = true;
                     }
-                    mStandby = true;
                     sendStatistics(false /* force */);
                 }
 
@@ -3973,10 +3981,7 @@
 
                         const int32_t throttleMs = (int32_t)mHalfBufferMs - deltaMs;
                         if ((signed)mHalfBufferMs >= throttleMs && throttleMs > 0) {
-                            mediametrics::LogItem(mMetricsId)
-                                // ms units always double
-                                .set(AMEDIAMETRICS_PROP_THROTTLEMS, (double)throttleMs)
-                                .record();
+                            mThreadMetrics.logThrottleMs((double)throttleMs);
 
                             usleep(throttleMs * 1000);
                             // notify of throttle start on verbose log
@@ -4226,6 +4231,7 @@
                          (mPatch.sinks[0].id != sinkPortId);
     mPatch = *patch;
     mOutDeviceTypeAddrs = deviceTypeAddrs;
+    checkSilentMode_l();
 
     if (mOutput->audioHwDev->supportsAudioPatches()) {
         sp<DeviceHalInterface> hwDevice = mOutput->audioHwDev->hwDevice();
@@ -4250,10 +4256,16 @@
         status = mOutput->stream->setParameters(param.toString());
         *handle = AUDIO_PATCH_HANDLE_NONE;
     }
-    mediametrics::LogItem(mMetricsId)
-        .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CREATEAUDIOPATCH)
-        .set(AMEDIAMETRICS_PROP_OUTPUTDEVICES, patchSinksToString(patch).c_str())
-        .record();
+    const std::string patchSinksAsString = patchSinksToString(patch);
+
+    mThreadMetrics.logEndInterval();
+    mThreadMetrics.logCreatePatch(patchSinksAsString);
+    mThreadMetrics.logBeginInterval();
+    // also dispatch to active AudioTracks for MediaMetrics
+    for (const auto &track : mActiveTracks) {
+        track->logEndInterval();
+        track->logBeginInterval(patchSinksAsString);
+    }
 
     if (configChanged) {
         sendIoConfigEvent_l(AUDIO_OUTPUT_CONFIG_CHANGED);
@@ -4796,9 +4808,8 @@
     // DeferredOperations handles statistics after setting mixerStatus.
     class DeferredOperations {
     public:
-        DeferredOperations(mixer_state *mixerStatus, const std::string &metricsId)
-            : mMixerStatus(mixerStatus)
-            , mMetricsId(metricsId) {}
+        explicit DeferredOperations(mixer_state *mixerStatus)
+            : mMixerStatus(mixerStatus) {}
 
         // when leaving scope, tally frames properly.
         ~DeferredOperations() {
@@ -4806,19 +4817,9 @@
             // because that is when the underrun occurs.
             // We do not distinguish between FastTracks and NormalTracks here.
             if (*mMixerStatus == MIXER_TRACKS_READY && mUnderrunFrames.size() > 0) {
-                mediametrics::LogItem item(mMetricsId);
-
-                item.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_UNDERRUN);
                 for (const auto &underrun : mUnderrunFrames) {
-                    underrun.first->mAudioTrackServerProxy->tallyUnderrunFrames(
-                            underrun.second);
-
-                    item.set(std::string("[" AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK)
-                            + std::to_string(underrun.first->portId())
-                            + "]" AMEDIAMETRICS_PROP_UNDERRUN,
-                            (int32_t)underrun.second);
+                    underrun.first->tallyUnderrunFrames(underrun.second);
                 }
-                item.record();
             }
         }
 
@@ -4831,9 +4832,8 @@
 
     private:
         const mixer_state * const mMixerStatus;
-        const std::string& mMetricsId;
         std::vector<std::pair<sp<Track>, size_t>> mUnderrunFrames;
-    } deferredOperations(&mixerStatus, mMetricsId);
+    } deferredOperations(&mixerStatus);
     // implicit nested scope for variable capture
 
     bool noFastHapticTrack = true;
@@ -5572,7 +5572,10 @@
         status = mOutput->stream->setParameters(keyValuePair);
         if (!mStandby && status == INVALID_OPERATION) {
             mOutput->standby();
-            mStandby = true;
+            if (!mStandby) {
+                mThreadMetrics.logEndInterval();
+                mStandby = true;
+            }
             mBytesWritten = 0;
             status = mOutput->stream->setParameters(keyValuePair);
         }
@@ -6085,7 +6088,10 @@
         status = mOutput->stream->setParameters(keyValuePair);
         if (!mStandby && status == INVALID_OPERATION) {
             mOutput->standby();
-            mStandby = true;
+            if (!mStandby) {
+                mThreadMetrics.logEndInterval();
+                mStandby = true;
+            }
             mBytesWritten = 0;
             status = mOutput->stream->setParameters(keyValuePair);
         }
@@ -6680,7 +6686,10 @@
 
         // TODO: Report correction for the other output tracks and show in the dump.
     }
-    mStandby = false;
+    if (mStandby) {
+        mThreadMetrics.logBeginInterval();
+        mStandby = false;
+    }
     return (ssize_t)mSinkBufferSize;
 }
 
@@ -6842,7 +6851,7 @@
                                          audio_io_handle_t id,
                                          bool systemReady
                                          ) :
-    ThreadBase(audioFlinger, id, RECORD, systemReady),
+    ThreadBase(audioFlinger, id, RECORD, systemReady, false /* isOut */),
     mInput(input),
     mSource(mInput),
     mActiveTracks(&this->mLocalLog),
@@ -7130,7 +7139,10 @@
 
                 case TrackBase::STARTING_2:
                     doBroadcast = true;
-                    mStandby = false;
+                    if (mStandby) {
+                        mThreadMetrics.logBeginInterval();
+                        mStandby = false;
+                    }
                     activeTrack->mState = TrackBase::ACTIVE;
                     allStopped = false;
                     break;
@@ -7571,6 +7583,7 @@
 {
     if (!mStandby) {
         inputStandBy();
+        mThreadMetrics.logEndInterval();
         mStandby = true;
     }
 }
@@ -7875,6 +7888,9 @@
             sendIoConfigEvent_l(
                 AUDIO_CLIENT_STARTED, recordTrack->creatorPid(), recordTrack->portId());
         }
+
+        recordTrack->logBeginInterval(patchSourcesToString(&mPatch)); // log to MediaMetrics
+
         // Catch up with current buffer indices if thread is already running.
         // This is what makes a new client discard all buffered data.  If the track's mRsmpInFront
         // was initialized to some value closer to the thread's mRsmpInFront, then the track could
@@ -8539,12 +8555,15 @@
         mPatch = *patch;
     }
 
-    mediametrics::LogItem(mMetricsId)
-        .set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_CREATEAUDIOPATCH)
-        .set(AMEDIAMETRICS_PROP_INPUTDEVICES, patchSourcesToString(patch).c_str())
-        .set(AMEDIAMETRICS_PROP_SOURCE, toString(mAudioSource).c_str())
-        .record();
-
+    const std::string pathSourcesAsString = patchSourcesToString(patch);
+    mThreadMetrics.logEndInterval();
+    mThreadMetrics.logCreatePatch(pathSourcesAsString);
+    mThreadMetrics.logBeginInterval();
+    // also dispatch to active AudioRecords
+    for (const auto &track : mActiveTracks) {
+        track->logEndInterval();
+        track->logBeginInterval(pathSourcesAsString);
+    }
     return status;
 }
 
@@ -8651,8 +8670,8 @@
 
 AudioFlinger::MmapThread::MmapThread(
         const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
-        AudioHwDevice *hwDev, sp<StreamHalInterface> stream, bool systemReady)
-    : ThreadBase(audioFlinger, id, MMAP, systemReady),
+        AudioHwDevice *hwDev, sp<StreamHalInterface> stream, bool systemReady, bool isOut)
+    : ThreadBase(audioFlinger, id, MMAP, systemReady, isOut),
       mSessionId(AUDIO_SESSION_NONE),
       mPortId(AUDIO_PORT_HANDLE_NONE),
       mHalStream(stream), mHalDevice(hwDev->hwDevice()), mAudioHwDev(hwDev),
@@ -8735,7 +8754,10 @@
         ALOGE("%s: error mHalStream->start() = %d for first track", __FUNCTION__, ret);
         return ret;
     }
-    mStandby = false;
+    if (mStandby) {
+        mThreadMetrics.logBeginInterval();
+        mStandby = false;
+    }
     return NO_ERROR;
 }
 
@@ -8855,6 +8877,7 @@
         chain->incActiveTrackCnt();
     }
 
+    track->logBeginInterval(patchSinksToString(&mPatch)); // log to MediaMetrics
     *handle = portId;
     broadcast_l();
 
@@ -8923,7 +8946,10 @@
         return INVALID_OPERATION;
     }
     mHalStream->standby();
-    mStandby = true;
+    if (!mStandby) {
+        mThreadMetrics.logEndInterval();
+        mStandby = true;
+    }
     releaseWakeLock();
     return NO_ERROR;
 }
@@ -8940,6 +8966,27 @@
     result = mHalStream->getBufferSize(&mBufferSize);
     LOG_ALWAYS_FATAL_IF(result != OK, "Error retrieving buffer size from HAL: %d", result);
     mFrameCount = mBufferSize / mFrameSize;
+
+    // TODO: make a readHalParameters call?
+    mediametrics::LogItem item(mThreadMetrics.getMetricsId());
+    item.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_READPARAMETERS)
+        .set(AMEDIAMETRICS_PROP_ENCODING, formatToString(mFormat).c_str())
+        .set(AMEDIAMETRICS_PROP_SAMPLERATE, (int32_t)mSampleRate)
+        .set(AMEDIAMETRICS_PROP_CHANNELMASK, (int32_t)mChannelMask)
+        .set(AMEDIAMETRICS_PROP_CHANNELCOUNT, (int32_t)mChannelCount)
+        .set(AMEDIAMETRICS_PROP_FRAMECOUNT, (int32_t)mFrameCount)
+        /*
+        .set(AMEDIAMETRICS_PROP_FLAGS, toString(flags).c_str())
+        .set(AMEDIAMETRICS_PROP_PREFIX_HAPTIC AMEDIAMETRICS_PROP_CHANNELMASK,
+                (int32_t)mHapticChannelMask)
+        .set(AMEDIAMETRICS_PROP_PREFIX_HAPTIC AMEDIAMETRICS_PROP_CHANNELCOUNT,
+                (int32_t)mHapticChannelCount)
+        */
+        .set(AMEDIAMETRICS_PROP_PREFIX_HAL    AMEDIAMETRICS_PROP_ENCODING,
+                formatToString(mHALFormat).c_str())
+        .set(AMEDIAMETRICS_PROP_PREFIX_HAL    AMEDIAMETRICS_PROP_FRAMECOUNT,
+                (int32_t)mFrameCount) // sic - added HAL
+        .record();
 }
 
 bool AudioFlinger::MmapThread::threadLoop()
@@ -9152,6 +9199,7 @@
         if (isOutput()) {
             sendIoConfigEvent_l(AUDIO_OUTPUT_CONFIG_CHANGED);
             mOutDeviceTypeAddrs = sinkDeviceTypeAddrs;
+            checkSilentMode_l();
         } else {
             sendIoConfigEvent_l(AUDIO_INPUT_CONFIG_CHANGED);
             mInDeviceTypeAddr = sourceDeviceTypeAddr;
@@ -9350,7 +9398,7 @@
 AudioFlinger::MmapPlaybackThread::MmapPlaybackThread(
         const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
         AudioHwDevice *hwDev,  AudioStreamOut *output, bool systemReady)
-    : MmapThread(audioFlinger, id, hwDev, output->stream, systemReady),
+    : MmapThread(audioFlinger, id, hwDev, output->stream, systemReady, true /* isOut */),
       mStreamType(AUDIO_STREAM_MUSIC),
       mStreamVolume(1.0),
       mStreamMute(false),
@@ -9561,7 +9609,7 @@
 AudioFlinger::MmapCaptureThread::MmapCaptureThread(
         const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
         AudioHwDevice *hwDev,  AudioStreamIn *input, bool systemReady)
-    : MmapThread(audioFlinger, id, hwDev, input->stream, systemReady),
+    : MmapThread(audioFlinger, id, hwDev, input->stream, systemReady, false /* isOut */),
       mInput(input)
 {
     snprintf(mThreadName, kThreadNameLength, "AudioMmapIn_%X", id);
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index b56eabc..43746b9 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -37,7 +37,7 @@
     static const char *threadTypeToString(type_t type);
 
     ThreadBase(const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
-               type_t type, bool systemReady);
+               type_t type, bool systemReady, bool isOut);
     virtual             ~ThreadBase();
 
     virtual status_t    readyToRun();
@@ -330,7 +330,7 @@
                     return mInDeviceTypeAddr;
                 }
 
-    virtual     bool        isOutput() const = 0;
+                bool        isOutput() const { return mIsOut; }
 
     virtual     sp<StreamHalInterface> stream() const = 0;
 
@@ -524,7 +524,8 @@
                 Condition               mWaitWorkCV;
 
                 const sp<AudioFlinger>  mAudioFlinger;
-                const std::string       mMetricsId;
+                ThreadMetrics           mThreadMetrics;
+                const bool              mIsOut;
 
                 // updated by PlaybackThread::readOutputParameters_l() or
                 // RecordThread::readInputParameters_l()
@@ -911,9 +912,6 @@
 
                 // Return the asynchronous signal wait time.
     virtual     int64_t     computeWaitTimeNs_l() const { return INT64_MAX; }
-
-    virtual     bool        isOutput() const override { return true; }
-
                 // returns true if the track is allowed to be added to the thread.
     virtual     bool        isTrackAllowed_l(
                                     audio_channel_mask_t channelMask __unused,
@@ -1651,7 +1649,6 @@
                             ThreadBase::acquireWakeLock_l();
                             mActiveTracks.updatePowerState(this, true /* force */);
                         }
-    virtual bool        isOutput() const override { return false; }
 
             void        checkBtNrec();
 
@@ -1760,7 +1757,8 @@
 #include "MmapTracks.h"
 
     MmapThread(const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
-               AudioHwDevice *hwDev, sp<StreamHalInterface> stream, bool systemReady);
+               AudioHwDevice *hwDev, sp<StreamHalInterface> stream, bool systemReady,
+               bool isOut);
     virtual     ~MmapThread();
 
     virtual     void        configure(const audio_attributes_t *attr,
@@ -1888,8 +1886,6 @@
     virtual     void        checkSilentMode_l();
                 void        processVolume_l() override;
 
-    virtual     bool        isOutput() const override { return true; }
-
                 void        updateMetadata_l() override;
 
     virtual     void        toAudioPortConfig(struct audio_port_config *config);
@@ -1916,7 +1912,6 @@
                 AudioStreamIn* clearInput();
 
                 status_t       exitStandby() override;
-    virtual     bool           isOutput() const override { return false; }
 
                 void           updateMetadata_l() override;
                 void           processVolume_l() override;
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index e39b944..15c66fb 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -97,10 +97,7 @@
 
     virtual void        invalidate() {
                             if (mIsInvalid) return;
-                            mediametrics::LogItem(mMetricsId)
-                                .set(AMEDIAMETRICS_PROP_EVENT,
-                                     AMEDIAMETRICS_PROP_EVENT_VALUE_INVALIDATE)
-                                .record();
+                            mTrackMetrics.logInvalidate();
                             mIsInvalid = true;
                         }
             bool        isInvalid() const { return mIsInvalid; }
@@ -242,6 +239,20 @@
         }
     }
 
+    // Called by the PlaybackThread to indicate that the track is becoming active
+    // and a new interval should start with a given device list.
+    void logBeginInterval(const std::string& devices) {
+        mTrackMetrics.logBeginInterval(devices);
+    }
+
+    // Called by the PlaybackThread to indicate the track is no longer active.
+    void logEndInterval() {
+        mTrackMetrics.logEndInterval();
+    }
+
+    // Called to tally underrun frames in playback.
+    virtual void tallyUnderrunFrames(size_t /* frames */) {}
+
 protected:
     DISALLOW_COPY_AND_ASSIGN(TrackBase);
 
@@ -367,7 +378,7 @@
     int64_t             mLogStartTimeNs = 0;
     int64_t             mLogStartFrames = 0;
 
-    const std::string   mMetricsId;
+    TrackMetrics        mTrackMetrics;
 
     bool                mServerLatencySupported = false;
     std::atomic<bool>   mServerLatencyFromTrack{}; // latency from track or server timestamp.
diff --git a/services/audioflinger/TrackMetrics.h b/services/audioflinger/TrackMetrics.h
new file mode 100644
index 0000000..399c788
--- /dev/null
+++ b/services/audioflinger/TrackMetrics.h
@@ -0,0 +1,222 @@
+/*
+ * 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_AUDIO_TRACKMETRICS_H
+#define ANDROID_AUDIO_TRACKMETRICS_H
+
+#include <mutex>
+
+namespace android {
+
+/**
+ * TrackMetrics handles the AudioFlinger track metrics.
+ *
+ * We aggregate metrics for a particular device for proper analysis.
+ * This includes power, performance, and usage metrics.
+ *
+ * This class is thread-safe with a lock for safety.  There is no risk of deadlock
+ * as this class only executes external one-way calls in Mediametrics and does not
+ * call any other AudioFlinger class.
+ *
+ * Terminology:
+ * An AudioInterval is a contiguous playback segment.
+ * An AudioIntervalGroup is a group of continuous playback segments on the same device.
+ *
+ * We currently deliver metrics based on an AudioIntervalGroup.
+ */
+class TrackMetrics final {
+public:
+    TrackMetrics(std::string metricsId, bool isOut)
+        : mMetricsId(std::move(metricsId))
+        , mIsOut(isOut)
+        {}  // we don't log a constructor item, we wait for more info in logConstructor().
+
+    ~TrackMetrics() {
+        logEndInterval();
+        std::lock_guard l(mLock);
+        deliverCumulativeMetrics(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP);
+        // we don't log a destructor item here.
+    }
+
+    // Called under the following circumstances
+    // 1) when we are added to the Thread
+    // 2) when we have a createPatch in the Thread.
+    void logBeginInterval(const std::string& devices) {
+        std::lock_guard l(mLock);
+        if (mDevices != devices) {
+            deliverCumulativeMetrics(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP);
+            mDevices = devices;
+            resetIntervalGroupMetrics();
+            deliverDeviceMetrics(
+                    AMEDIAMETRICS_PROP_EVENT_VALUE_BEGINAUDIOINTERVALGROUP, devices.c_str());
+        }
+        ++mIntervalCount;
+        mIntervalStartTimeNs = systemTime();
+    }
+
+    void logConstructor(pid_t creatorPid, uid_t creatorUid) const {
+        // Once this item is logged by the server, the client can add properties.
+        // no lock required, all local or const variables.
+        mediametrics::LogItem(mMetricsId)
+            .setPid(creatorPid)
+            .setUid(creatorUid)
+            .set(AMEDIAMETRICS_PROP_ALLOWUID, (int32_t)creatorUid)
+            .set(AMEDIAMETRICS_PROP_EVENT,
+                    AMEDIAMETRICS_PROP_PREFIX_SERVER AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR)
+            .record();
+    }
+
+    // Called when we are removed from the Thread.
+    void logEndInterval() {
+        std::lock_guard l(mLock);
+        if (mIntervalStartTimeNs != 0) {
+            const int64_t elapsedTimeNs = systemTime() - mIntervalStartTimeNs;
+            mIntervalStartTimeNs = 0;
+            mCumulativeTimeNs += elapsedTimeNs;
+            mDeviceTimeNs += elapsedTimeNs;
+        }
+    }
+
+    void logInvalidate() const {
+        // no lock required, all local or const variables.
+        mediametrics::LogItem(mMetricsId)
+            .set(AMEDIAMETRICS_PROP_EVENT,
+                 AMEDIAMETRICS_PROP_EVENT_VALUE_INVALIDATE)
+            .record();
+    }
+
+    void logLatencyAndStartup(double latencyMs, double startupMs) {
+        mediametrics::LogItem(mMetricsId)
+            .set(AMEDIAMETRICS_PROP_LATENCYMS, latencyMs)
+            .set(AMEDIAMETRICS_PROP_STARTUPMS, startupMs)
+            .record();
+        std::lock_guard l(mLock);
+        mDeviceLatencyMs.add(latencyMs);
+        mDeviceStartupMs.add(startupMs);
+    }
+
+    // may be called multiple times during an interval
+    void logVolume(float volume) {
+        const int64_t timeNs = systemTime();
+        std::lock_guard l(mLock);
+        if (mStartVolumeTimeNs == 0) {
+            mDeviceVolume = mVolume = volume;
+            mLastVolumeChangeTimeNs = mStartVolumeTimeNs = timeNs;
+            return;
+        }
+        mDeviceVolume = (mDeviceVolume * (mLastVolumeChangeTimeNs - mStartVolumeTimeNs) +
+            mVolume * (timeNs - mLastVolumeChangeTimeNs)) / (timeNs - mStartVolumeTimeNs);
+        mVolume = volume;
+        mLastVolumeChangeTimeNs = timeNs;
+    }
+
+    // Use absolute numbers returned by AudioTrackShared.
+    void logUnderruns(size_t count, size_t frames) {
+        std::lock_guard l(mLock);
+        mUnderrunCount = count;
+        mUnderrunFrames = frames;
+        // Consider delivering a message here (also be aware of excessive spam).
+    }
+
+private:
+    // no lock required - all arguments and constants.
+    void deliverDeviceMetrics(const char *eventName, const char *devices) const {
+        mediametrics::LogItem(mMetricsId)
+            .set(AMEDIAMETRICS_PROP_EVENT, eventName)
+            .set(mIsOut ? AMEDIAMETRICS_PROP_OUTPUTDEVICES
+                   : AMEDIAMETRICS_PROP_INPUTDEVICES, devices)
+           .record();
+    }
+
+    void deliverCumulativeMetrics(const char *eventName) const REQUIRES(mLock) {
+        if (mIntervalCount > 0) {
+            mediametrics::LogItem item(mMetricsId);
+            item.set(AMEDIAMETRICS_PROP_CUMULATIVETIMENS, mCumulativeTimeNs)
+                .set(AMEDIAMETRICS_PROP_DEVICETIMENS, mDeviceTimeNs)
+                .set(AMEDIAMETRICS_PROP_EVENT, eventName)
+                .set(AMEDIAMETRICS_PROP_INTERVALCOUNT, (int32_t)mIntervalCount);
+            if (mIsOut) {
+                item.set(AMEDIAMETRICS_PROP_DEVICEVOLUME, mDeviceVolume);
+            }
+            if (mDeviceLatencyMs.getN() > 0) {
+                item.set(AMEDIAMETRICS_PROP_DEVICELATENCYMS, mDeviceLatencyMs.getMean())
+                    .set(AMEDIAMETRICS_PROP_DEVICESTARTUPMS, mDeviceStartupMs.getMean());
+            }
+            if (mUnderrunCount > 0) {
+                item.set(AMEDIAMETRICS_PROP_UNDERRUN,
+                        (int32_t)(mUnderrunCount - mUnderrunCountSinceIntervalGroup))
+                    .set(AMEDIAMETRICS_PROP_UNDERRUNFRAMES,
+                        (int64_t)(mUnderrunFrames - mUnderrunFramesSinceIntervalGroup));
+            }
+            item.record();
+        }
+    }
+
+    void resetIntervalGroupMetrics() REQUIRES(mLock) {
+        // mDevices is not reset by resetIntervalGroupMetrics.
+
+        mIntervalCount = 0;
+        mIntervalStartTimeNs = 0;
+        // mCumulativeTimeNs is not reset by resetIntervalGroupMetrics.
+        mDeviceTimeNs = 0;
+
+        mVolume = 0.f;
+        mDeviceVolume = 0.f;
+        mStartVolumeTimeNs = 0;
+        mLastVolumeChangeTimeNs = 0;
+
+        mDeviceLatencyMs.reset();
+        mDeviceStartupMs.reset();
+
+        mUnderrunCountSinceIntervalGroup = mUnderrunCount;
+        mUnderrunFramesSinceIntervalGroup = mUnderrunFrames;
+        // do not reset mUnderrunCount - it keeps continuously running for tracks.
+    }
+
+    const std::string mMetricsId;
+    const bool        mIsOut;  // if true, than a playback track, otherwise used for record.
+
+    mutable           std::mutex mLock;
+
+    // Devices in the interval group.
+    std::string       mDevices GUARDED_BY(mLock);
+
+    // Number of intervals and playing time
+    int32_t           mIntervalCount GUARDED_BY(mLock) = 0;
+    int64_t           mIntervalStartTimeNs GUARDED_BY(mLock) = 0;
+    int64_t           mCumulativeTimeNs GUARDED_BY(mLock) = 0;
+    int64_t           mDeviceTimeNs GUARDED_BY(mLock) = 0;
+
+    // Average volume
+    double            mVolume GUARDED_BY(mLock) = 0.f;
+    double            mDeviceVolume GUARDED_BY(mLock) = 0.f;
+    int64_t           mStartVolumeTimeNs GUARDED_BY(mLock) = 0;
+    int64_t           mLastVolumeChangeTimeNs GUARDED_BY(mLock) = 0;
+
+    // latency and startup for each interval.
+    audio_utils::Statistics<double> mDeviceLatencyMs GUARDED_BY(mLock);
+    audio_utils::Statistics<double> mDeviceStartupMs GUARDED_BY(mLock);
+
+    // underrun count and frames
+    int64_t           mUnderrunCount GUARDED_BY(mLock) = 0;
+    int64_t           mUnderrunFrames GUARDED_BY(mLock) = 0;
+    int64_t           mUnderrunCountSinceIntervalGroup GUARDED_BY(mLock) = 0;
+    int64_t           mUnderrunFramesSinceIntervalGroup GUARDED_BY(mLock) = 0;
+};
+
+} // namespace android
+
+#endif // ANDROID_AUDIO_TRACKMETRICS_H
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index 66a7b6f..2f29ed8 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -106,7 +106,7 @@
         mThreadIoHandle(thread ? thread->id() : AUDIO_IO_HANDLE_NONE),
         mPortId(portId),
         mIsInvalid(false),
-        mMetricsId(std::move(metricsId)),
+        mTrackMetrics(std::move(metricsId), isOut),
         mCreatorPid(creatorPid)
 {
     const uid_t callingUid = IPCThreadState::self()->getCallingUid();
@@ -602,13 +602,7 @@
     }
 
     // Once this item is logged by the server, the client can add properties.
-    mediametrics::LogItem(mMetricsId)
-        .setPid(creatorPid)
-        .setUid(uid)
-        .set(AMEDIAMETRICS_PROP_ALLOWUID, (int32_t)uid)
-        .set(AMEDIAMETRICS_PROP_EVENT,
-                AMEDIAMETRICS_PROP_PREFIX_SERVER AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR)
-        .record();
+    mTrackMetrics.logConstructor(creatorPid, uid);
 }
 
 AudioFlinger::PlaybackThread::Track::~Track()
@@ -1249,6 +1243,7 @@
     if (mFinalVolume != volume) { // Compare to an epsilon if too many meaningless updates
         mFinalVolume = volume;
         setMetadataHasChanged();
+        mTrackMetrics.logVolume(volume);
     }
 }
 
@@ -1534,10 +1529,7 @@
                     (long long)mLogStartTimeNs,
                     (long long)local.mPosition[ExtendedTimestamp::LOCATION_SERVER],
                     (long long)mLogStartFrames);
-            mediametrics::LogItem(mMetricsId)
-                .set(AMEDIAMETRICS_PROP_LATENCYMS, latencyMs)
-                .set(AMEDIAMETRICS_PROP_STARTUPMS, startUpMs)
-                .record();
+            mTrackMetrics.logLatencyAndStartup(latencyMs, startUpMs);
         }
     }
 }
@@ -2182,12 +2174,7 @@
 #endif
 
     // Once this item is logged by the server, the client can add properties.
-    mediametrics::LogItem(mMetricsId)
-        .setPid(creatorPid)
-        .setUid(uid)
-        .set(AMEDIAMETRICS_PROP_ALLOWUID, (int32_t)uid)
-        .set(AMEDIAMETRICS_PROP_EVENT, "server." AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR)
-        .record();
+    mTrackMetrics.logConstructor(creatorPid, uid);
 }
 
 AudioFlinger::RecordThread::RecordTrack::~RecordTrack()
@@ -2744,9 +2731,12 @@
                   nullptr /* buffer */, (size_t)0 /* bufferSize */,
                   sessionId, creatorPid, uid, isOut,
                   ALLOC_NONE,
-                  TYPE_DEFAULT, portId),
+                  TYPE_DEFAULT, portId,
+                  std::string(AMEDIAMETRICS_KEY_PREFIX_AUDIO_MMAP) + std::to_string(portId)),
         mPid(pid), mSilenced(false), mSilencedNotified(false)
 {
+    // Once this item is logged by the server, the client can add properties.
+    mTrackMetrics.logConstructor(creatorPid, uid);
 }
 
 AudioFlinger::MmapThread::MmapTrack::~MmapTrack()
diff --git a/services/audiopolicy/common/managerdefinitions/src/HwModule.cpp b/services/audiopolicy/common/managerdefinitions/src/HwModule.cpp
index 886e4c9..d31e443 100644
--- a/services/audiopolicy/common/managerdefinitions/src/HwModule.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/HwModule.cpp
@@ -320,7 +320,7 @@
 {
     String8 devAddress = (address == nullptr || !matchAddress) ? String8("") : String8(address);
     // handle legacy remote submix case where the address was not always specified
-    if (device_distinguishes_on_address(deviceType) && (devAddress.length() == 0)) {
+    if (audio_is_remote_submix_device(deviceType) && (devAddress.length() == 0)) {
         devAddress = String8("0");
     }
 
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 5683e69..bcf6f38 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -954,7 +954,8 @@
                                                   primaryMix->mDeviceAddress,
                                                   AUDIO_FORMAT_DEFAULT);
         sp<SwAudioOutputDescriptor> policyDesc = primaryMix->getOutput();
-        if (policyDesc == nullptr || (policyDesc->mFlags & AUDIO_OUTPUT_FLAG_DIRECT)) {
+        if (deviceDesc != nullptr
+                && (policyDesc == nullptr || (policyDesc->mFlags & AUDIO_OUTPUT_FLAG_DIRECT))) {
             audio_io_handle_t newOutput;
             status = openDirectOutput(
                     *stream, session, config,
@@ -3072,7 +3073,7 @@
     for (size_t i = 0; i < devices.size(); i++) {
         sp<DeviceDescriptor> devDesc = mHwModules.getDeviceDescriptor(
                 devices[i].mType, devices[i].mAddress.c_str(), String8(),
-                AUDIO_FORMAT_DEFAULT, false /*allowToCreate*/, false /*matchAddress*/);
+                AUDIO_FORMAT_DEFAULT, false /*allowToCreate*/, true /*matchAddress*/);
         if (devDesc == nullptr || (predicate != nullptr && !predicate(devices[i].mType))) {
             ALOGE("%s: device type %#x address %s not supported or not an output device",
                     context, devices[i].mType, devices[i].mAddress.c_str());
@@ -4837,7 +4838,7 @@
 
                 if (output != AUDIO_IO_HANDLE_NONE) {
                     addOutput(output, desc);
-                    if (device_distinguishes_on_address(deviceType) && address != "0") {
+                    if (audio_is_remote_submix_device(deviceType) && address != "0") {
                         sp<AudioPolicyMix> policyMix;
                         if (mPolicyMixes.getAudioPolicyMix(deviceType, address, policyMix)
                                 == NO_ERROR) {
diff --git a/services/audiopolicy/service/Android.bp b/services/audiopolicy/service/Android.bp
index 6ee573f..8a7a1b2 100644
--- a/services/audiopolicy/service/Android.bp
+++ b/services/audiopolicy/service/Android.bp
@@ -46,6 +46,7 @@
         "-fvisibility=hidden",
         "-Werror",
         "-Wall",
+        "-Wthread-safety",
     ],
 
     export_shared_lib_headers: [
diff --git a/services/audiopolicy/service/AudioPolicyEffects.cpp b/services/audiopolicy/service/AudioPolicyEffects.cpp
index 738a279..1ec0c5e 100644
--- a/services/audiopolicy/service/AudioPolicyEffects.cpp
+++ b/services/audiopolicy/service/AudioPolicyEffects.cpp
@@ -928,7 +928,10 @@
 
     loadProcessingChain(result.parsedConfig->preprocess, mInputSources);
     loadProcessingChain(result.parsedConfig->postprocess, mOutputStreams);
-    loadDeviceProcessingChain(result.parsedConfig->deviceprocess, mDeviceEffects);
+    {
+        Mutex::Autolock _l(mLock);
+        loadDeviceProcessingChain(result.parsedConfig->deviceprocess, mDeviceEffects);
+    }
     // Casting from ssize_t to status_t is probably safe, there should not be more than 2^31 errors
     return result.nbSkippedElement;
 }
diff --git a/services/audiopolicy/service/AudioPolicyService.cpp b/services/audiopolicy/service/AudioPolicyService.cpp
index d743be9..23de08b 100644
--- a/services/audiopolicy/service/AudioPolicyService.cpp
+++ b/services/audiopolicy/service/AudioPolicyService.cpp
@@ -58,8 +58,6 @@
 
 AudioPolicyService::AudioPolicyService()
     : BnAudioPolicyService(),
-      mpAudioPolicyDev(NULL),
-      mpAudioPolicy(NULL),
       mAudioPolicyManager(NULL),
       mAudioPolicyClient(NULL),
       mPhoneState(AUDIO_MODE_INVALID),
@@ -78,21 +76,19 @@
 
         mAudioPolicyClient = new AudioPolicyClient(this);
         mAudioPolicyManager = createAudioPolicyManager(mAudioPolicyClient);
-
-        mSupportedSystemUsages = std::vector<audio_usage_t> {};
     }
     // load audio processing modules
-    sp<AudioPolicyEffects>audioPolicyEffects = new AudioPolicyEffects();
+    sp<AudioPolicyEffects> audioPolicyEffects = new AudioPolicyEffects();
+    sp<UidPolicy> uidPolicy = new UidPolicy(this);
+    sp<SensorPrivacyPolicy> sensorPrivacyPolicy = new SensorPrivacyPolicy(this);
     {
         Mutex::Autolock _l(mLock);
         mAudioPolicyEffects = audioPolicyEffects;
+        mUidPolicy = uidPolicy;
+        mSensorPrivacyPolicy = sensorPrivacyPolicy;
     }
-
-    mUidPolicy = new UidPolicy(this);
-    mUidPolicy->registerSelf();
-
-    mSensorPrivacyPolicy = new SensorPrivacyPolicy(this);
-    mSensorPrivacyPolicy->registerSelf();
+    uidPolicy->registerSelf();
+    sensorPrivacyPolicy->registerSelf();
 }
 
 AudioPolicyService::~AudioPolicyService()
@@ -107,9 +103,9 @@
     mAudioPolicyEffects.clear();
 
     mUidPolicy->unregisterSelf();
-    mUidPolicy.clear();
-
     mSensorPrivacyPolicy->unregisterSelf();
+
+    mUidPolicy.clear();
     mSensorPrivacyPolicy.clear();
 }
 
@@ -172,20 +168,20 @@
 // removeNotificationClient() is called when the client process dies.
 void AudioPolicyService::removeNotificationClient(uid_t uid, pid_t pid)
 {
+    bool hasSameUid = false;
     {
         Mutex::Autolock _l(mNotificationClientsLock);
         int64_t token = ((int64_t)uid<<32) | pid;
         mNotificationClients.removeItem(token);
-    }
-    {
-        Mutex::Autolock _l(mLock);
-        bool hasSameUid = false;
         for (size_t i = 0; i < mNotificationClients.size(); i++) {
             if (mNotificationClients.valueAt(i)->uid() == uid) {
                 hasSameUid = true;
                 break;
             }
         }
+    }
+    {
+        Mutex::Autolock _l(mLock);
         if (mAudioPolicyManager && !hasSameUid) {
             // called from binder death notification: no need to clear caller identity
             mAudioPolicyManager->releaseResourcesForUid(uid);
@@ -381,10 +377,14 @@
             IPCThreadState::self()->getCallingPid());
 }
 
-static bool dumpTryLock(Mutex& mutex)
+static bool dumpTryLock(Mutex& mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
 {
-    status_t err = mutex.timedLock(kDumpLockTimeoutNs);
-    return err == NO_ERROR;
+    return mutex.timedLock(kDumpLockTimeoutNs) == NO_ERROR;
+}
+
+static void dumpReleaseLock(Mutex& mutex, bool locked) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
+{
+    if (locked) mutex.unlock();
 }
 
 status_t AudioPolicyService::dumpInternals(int fd)
@@ -564,7 +564,7 @@
         bool isTopOrLatestSensitive = topSensitiveActive == nullptr ?
                                  false : current->uid == topSensitiveActive->uid;
 
-        auto canCaptureIfInCallOrCommunication = [&](const auto &recordClient) {
+        auto canCaptureIfInCallOrCommunication = [&](const auto &recordClient) REQUIRES(mLock) {
             bool canCaptureCall = recordClient->canCaptureOutput;
             bool canCaptureCommunication = recordClient->canCaptureOutput
                 || recordClient->uid == mPhoneStateOwnerUid
@@ -676,6 +676,7 @@
         case AUDIO_SOURCE_VOICE_CALL:
         case AUDIO_SOURCE_REMOTE_SUBMIX:
         case AUDIO_SOURCE_FM_TUNER:
+        case AUDIO_SOURCE_ECHO_REFERENCE:
             return true;
         default:
             break;
@@ -702,7 +703,7 @@
     if (!dumpAllowed()) {
         dumpPermissionDenial(fd);
     } else {
-        bool locked = dumpTryLock(mLock);
+        const bool locked = dumpTryLock(mLock);
         if (!locked) {
             String8 result(kDeadlockedString);
             write(fd, result.string(), result.size());
@@ -719,7 +720,7 @@
 
         mPackageManager.dump(fd);
 
-        if (locked) mLock.unlock();
+        dumpReleaseLock(mLock, locked);
     }
     return NO_ERROR;
 }
@@ -838,8 +839,16 @@
         return BAD_VALUE;
     }
 
-    mUidPolicy->addOverrideUid(uid, active);
-    return NO_ERROR;
+    sp<UidPolicy> uidPolicy;
+    {
+        Mutex::Autolock _l(mLock);
+        uidPolicy = mUidPolicy;
+    }
+    if (uidPolicy) {
+        uidPolicy->addOverrideUid(uid, active);
+        return NO_ERROR;
+    }
+    return NO_INIT;
 }
 
 status_t AudioPolicyService::handleResetUidState(Vector<String16>& args, int err) {
@@ -859,8 +868,16 @@
         return BAD_VALUE;
     }
 
-    mUidPolicy->removeOverrideUid(uid);
-    return NO_ERROR;
+    sp<UidPolicy> uidPolicy;
+    {
+        Mutex::Autolock _l(mLock);
+        uidPolicy = mUidPolicy;
+    }
+    if (uidPolicy) {
+        uidPolicy->removeOverrideUid(uid);
+        return NO_ERROR;
+    }
+    return NO_INIT;
 }
 
 status_t AudioPolicyService::handleGetUidState(Vector<String16>& args, int out, int err) {
@@ -880,11 +897,15 @@
         return BAD_VALUE;
     }
 
-    if (mUidPolicy->isUidActive(uid)) {
-        return dprintf(out, "active\n");
-    } else {
-        return dprintf(out, "idle\n");
+    sp<UidPolicy> uidPolicy;
+    {
+        Mutex::Autolock _l(mLock);
+        uidPolicy = mUidPolicy;
     }
+    if (uidPolicy) {
+        return dprintf(out, uidPolicy->isUidActive(uid) ? "active\n" : "idle\n");
+    }
+    return NO_INIT;
 }
 
 status_t AudioPolicyService::printHelp(int out) {
@@ -1401,7 +1422,7 @@
     result.append(buffer);
     write(fd, result.string(), result.size());
 
-    bool locked = dumpTryLock(mLock);
+    const bool locked = dumpTryLock(mLock);
     if (!locked) {
         String8 result2(kCmdDeadlockedString);
         write(fd, result2.string(), result2.size());
@@ -1424,7 +1445,7 @@
 
     write(fd, result.string(), result.size());
 
-    if (locked) mLock.unlock();
+    dumpReleaseLock(mLock, locked);
 
     return NO_ERROR;
 }
diff --git a/services/audiopolicy/service/AudioPolicyService.h b/services/audiopolicy/service/AudioPolicyService.h
index f77a481..869a963 100644
--- a/services/audiopolicy/service/AudioPolicyService.h
+++ b/services/audiopolicy/service/AudioPolicyService.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_AUDIOPOLICYSERVICE_H
 #define ANDROID_AUDIOPOLICYSERVICE_H
 
+#include <android-base/thread_annotations.h>
 #include <cutils/misc.h>
 #include <cutils/config_utils.h>
 #include <cutils/compiler.h>
@@ -330,13 +331,13 @@
                         AudioPolicyService() ANDROID_API;
     virtual             ~AudioPolicyService();
 
-            status_t dumpInternals(int fd);
+            status_t dumpInternals(int fd) REQUIRES(mLock);
 
     // Handles binder shell commands
     virtual status_t shellCommand(int in, int out, int err, Vector<String16>& args);
 
     // Sets whether the given UID records only silence
-    virtual void setAppState_l(audio_port_handle_t portId, app_state_t state);
+    virtual void setAppState_l(audio_port_handle_t portId, app_state_t state) REQUIRES(mLock);
 
     // Overrides the UID state as if it is idle
     status_t handleSetUidState(Vector<String16>& args, int err);
@@ -361,9 +362,9 @@
     status_t validateUsage(audio_usage_t usage, pid_t pid, uid_t uid);
 
     void updateUidStates();
-    void updateUidStates_l();
+    void updateUidStates_l() REQUIRES(mLock);
 
-    void silenceAllRecordings_l();
+    void silenceAllRecordings_l() REQUIRES(mLock);
 
     static bool isVirtualSource(audio_source_t source);
 
@@ -420,13 +421,13 @@
         wp<AudioPolicyService> mService;
         Mutex mLock;
         ActivityManager mAm;
-        bool mObserverRegistered;
+        bool mObserverRegistered = false;
         std::unordered_map<uid_t, std::pair<bool, int>> mOverrideUids;
         std::unordered_map<uid_t, std::pair<bool, int>> mCachedUids;
-        uid_t mAssistantUid;
+        uid_t mAssistantUid = -1;
         std::vector<uid_t> mA11yUids;
-        uid_t mCurrentImeUid;
-        bool mRttEnabled;
+        uid_t mCurrentImeUid = -1;
+        bool mRttEnabled = false;
     };
 
     // If sensor privacy is enabled then all apps, including those that are active, should be
@@ -447,7 +448,7 @@
 
         private:
             wp<AudioPolicyService> mService;
-            std::atomic_bool mSensorPrivacyEnabled;
+            std::atomic_bool mSensorPrivacyEnabled = false;
     };
 
     // Thread used to send audio config commands to audio flinger
@@ -880,26 +881,27 @@
     // and possibly back in to audio policy service and acquire mEffectsLock.
     sp<AudioCommandThread> mAudioCommandThread;     // audio commands thread
     sp<AudioCommandThread> mOutputCommandThread;    // process stop and release output
-    struct audio_policy_device *mpAudioPolicyDev;
-    struct audio_policy *mpAudioPolicy;
     AudioPolicyInterface *mAudioPolicyManager;
     AudioPolicyClient *mAudioPolicyClient;
     std::vector<audio_usage_t> mSupportedSystemUsages;
 
-    DefaultKeyedVector< int64_t, sp<NotificationClient> >    mNotificationClients;
-    Mutex mNotificationClientsLock;  // protects mNotificationClients
+    Mutex mNotificationClientsLock;
+    DefaultKeyedVector<int64_t, sp<NotificationClient>> mNotificationClients
+        GUARDED_BY(mNotificationClientsLock);
     // Manage all effects configured in audio_effects.conf
     // never hold AudioPolicyService::mLock when calling AudioPolicyEffects methods as
     // those can call back into AudioPolicyService methods and try to acquire the mutex
-    sp<AudioPolicyEffects> mAudioPolicyEffects;
-    audio_mode_t mPhoneState;
-    uid_t mPhoneStateOwnerUid;
+    sp<AudioPolicyEffects> mAudioPolicyEffects GUARDED_BY(mLock);
+    audio_mode_t mPhoneState GUARDED_BY(mLock);
+    uid_t mPhoneStateOwnerUid GUARDED_BY(mLock);
 
-    sp<UidPolicy> mUidPolicy;
-    sp<SensorPrivacyPolicy> mSensorPrivacyPolicy;
+    sp<UidPolicy> mUidPolicy GUARDED_BY(mLock);
+    sp<SensorPrivacyPolicy> mSensorPrivacyPolicy GUARDED_BY(mLock);
 
-    DefaultKeyedVector< audio_port_handle_t, sp<AudioRecordClient> >   mAudioRecordClients;
-    DefaultKeyedVector< audio_port_handle_t, sp<AudioPlaybackClient> >   mAudioPlaybackClients;
+    DefaultKeyedVector<audio_port_handle_t, sp<AudioRecordClient>> mAudioRecordClients
+        GUARDED_BY(mLock);
+    DefaultKeyedVector<audio_port_handle_t, sp<AudioPlaybackClient>> mAudioPlaybackClients
+        GUARDED_BY(mLock);
 
     MediaPackageManager mPackageManager; // To check allowPlaybackCapture
 
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 2d46122..b00a2d9 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -2344,6 +2344,21 @@
     }
 }
 
+bool Camera3Device::checkAbandonedStreamsLocked() {
+    if ((mInputStream.get() != nullptr) && (mInputStream->isAbandoned())) {
+        return true;
+    }
+
+    for (size_t i = 0; i < mOutputStreams.size(); i++) {
+        auto stream = mOutputStreams[i];
+        if ((stream.get() != nullptr) && (stream->isAbandoned())) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 bool Camera3Device::reconfigureCamera(const CameraMetadata& sessionParams) {
     ATRACE_CALL();
     bool ret = false;
@@ -2352,6 +2367,12 @@
     nsecs_t maxExpectedDuration = getExpectedInFlightDuration();
 
     Mutex::Autolock l(mLock);
+    if (checkAbandonedStreamsLocked()) {
+        ALOGW("%s: Abandoned stream detected, session parameters can't be applied correctly!",
+                __FUNCTION__);
+        return true;
+    }
+
     auto rc = internalPauseAndWaitLocked(maxExpectedDuration);
     if (rc == NO_ERROR) {
         mNeedConfig = true;
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index b373a64..19ecf4b 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -648,6 +648,12 @@
     bool reconfigureCamera(const CameraMetadata& sessionParams);
 
     /**
+     * Return true in case of any output or input abandoned streams,
+     * otherwise return false.
+     */
+    bool checkAbandonedStreamsLocked();
+
+    /**
      * Filter stream session parameters and configure camera HAL.
      */
     status_t filterParamsAndConfigureLocked(const CameraMetadata& sessionParams,
diff --git a/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp b/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
index 4c8366f..603f516 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
+++ b/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
@@ -259,13 +259,15 @@
     }
 
     // Fix up some result metadata to account for HAL-level distortion correction
-    status_t res =
-            states.distortionMappers[states.cameraId.c_str()].correctCaptureResult(
-                    &captureResult.mMetadata);
-    if (res != OK) {
-        SET_ERR("Unable to correct capture result metadata for frame %d: %s (%d)",
-                frameNumber, strerror(-res), res);
-        return;
+    status_t res = OK;
+    auto iter = states.distortionMappers.find(states.cameraId.c_str());
+    if (iter != states.distortionMappers.end()) {
+        res = iter->second.correctCaptureResult(&captureResult.mMetadata);
+        if (res != OK) {
+            SET_ERR("Unable to correct capture result metadata for frame %d: %s (%d)",
+                    frameNumber, strerror(-res), res);
+            return;
+        }
     }
 
     // Fix up result metadata to account for zoom ratio availabilities between
diff --git a/services/camera/libcameraservice/device3/ZoomRatioMapper.cpp b/services/camera/libcameraservice/device3/ZoomRatioMapper.cpp
index 84da45a..a87de77 100644
--- a/services/camera/libcameraservice/device3/ZoomRatioMapper.cpp
+++ b/services/camera/libcameraservice/device3/ZoomRatioMapper.cpp
@@ -243,10 +243,15 @@
             if (weight == 0) {
                 continue;
             }
-            // Top-left is inclusively clamped
-            scaleCoordinates(entry.data.i32 + j, 1, zoomRatio, ClampInclusive);
-            // Bottom-right is exclusively clamped
-            scaleCoordinates(entry.data.i32 + j + 2, 1, zoomRatio, ClampExclusive);
+            // Top left (inclusive)
+            scaleCoordinates(entry.data.i32 + j, 1, zoomRatio, true /*clamp*/);
+            // Bottom right (exclusive): Use adjacent inclusive pixel to
+            // calculate.
+            entry.data.i32[j+2] -= 1;
+            entry.data.i32[j+3] -= 1;
+            scaleCoordinates(entry.data.i32 + j + 2, 1, zoomRatio, true /*clamp*/);
+            entry.data.i32[j+2] += 1;
+            entry.data.i32[j+3] += 1;
         }
     }
 
@@ -258,7 +263,7 @@
     if (isResult) {
         for (auto pts : kResultPointsToCorrectNoClamp) {
             entry = metadata->find(pts);
-            scaleCoordinates(entry.data.i32, entry.count / 2, zoomRatio, ClampOff);
+            scaleCoordinates(entry.data.i32, entry.count / 2, zoomRatio, false /*clamp*/);
         }
     }
 
@@ -282,10 +287,15 @@
             if (weight == 0) {
                 continue;
             }
-            // Top-left is inclusively clamped
-            scaleCoordinates(entry.data.i32 + j, 1, 1.0 / zoomRatio, ClampInclusive);
-            // Bottom-right is exclusively clamped
-            scaleCoordinates(entry.data.i32 + j + 2, 1, 1.0 / zoomRatio, ClampExclusive);
+            // Top-left (inclusive)
+            scaleCoordinates(entry.data.i32 + j, 1, 1.0 / zoomRatio, true /*clamp*/);
+            // Bottom-right (exclusive): Use adjacent inclusive pixel to
+            // calculate.
+            entry.data.i32[j+2] -= 1;
+            entry.data.i32[j+3] -= 1;
+            scaleCoordinates(entry.data.i32 + j + 2, 1, 1.0 / zoomRatio, true /*clamp*/);
+            entry.data.i32[j+2] += 1;
+            entry.data.i32[j+3] += 1;
         }
     }
     for (auto rect : kRectsToCorrect) {
@@ -295,7 +305,7 @@
     if (isResult) {
         for (auto pts : kResultPointsToCorrectNoClamp) {
             entry = metadata->find(pts);
-            scaleCoordinates(entry.data.i32, entry.count / 2, 1.0 / zoomRatio, ClampOff);
+            scaleCoordinates(entry.data.i32, entry.count / 2, 1.0 / zoomRatio, false /*clamp*/);
         }
     }
 
@@ -309,28 +319,31 @@
 }
 
 void ZoomRatioMapper::scaleCoordinates(int32_t* coordPairs, int coordCount,
-        float scaleRatio, ClampMode clamp) {
+        float scaleRatio, bool clamp) {
+    // A pixel's coordinate is represented by the position of its top-left corner.
+    // To avoid the rounding error, we use the coordinate for the center of the
+    // pixel instead:
+    // 1. First shift the coordinate system half pixel both horizontally and
+    // vertically, so that [x, y] is the center of the pixel, not the top-left corner.
+    // 2. Do zoom operation to scale the coordinate relative to the center of
+    // the active array (shifted by 0.5 pixel as well).
+    // 3. Shift the coordinate system back by directly using the pixel center
+    // coordinate.
     for (int i = 0; i < coordCount * 2; i += 2) {
         float x = coordPairs[i];
         float y = coordPairs[i + 1];
-        float xCentered = x - mArrayWidth / 2;
-        float yCentered = y - mArrayHeight / 2;
+        float xCentered = x - (mArrayWidth - 2) / 2;
+        float yCentered = y - (mArrayHeight - 2) / 2;
         float scaledX = xCentered * scaleRatio;
         float scaledY = yCentered * scaleRatio;
-        scaledX += mArrayWidth / 2;
-        scaledY += mArrayHeight / 2;
+        scaledX += (mArrayWidth - 2) / 2;
+        scaledY += (mArrayHeight - 2) / 2;
+        coordPairs[i] = static_cast<int32_t>(std::round(scaledX));
+        coordPairs[i+1] = static_cast<int32_t>(std::round(scaledY));
         // Clamp to within activeArray/preCorrectionActiveArray
-        coordPairs[i] = static_cast<int32_t>(scaledX);
-        coordPairs[i+1] = static_cast<int32_t>(scaledY);
-        if (clamp != ClampOff) {
-            int32_t right, bottom;
-            if (clamp == ClampInclusive) {
-                right = mArrayWidth - 1;
-                bottom = mArrayHeight - 1;
-            } else {
-                right = mArrayWidth;
-                bottom = mArrayHeight;
-            }
+        if (clamp) {
+            int32_t right = mArrayWidth - 1;
+            int32_t bottom = mArrayHeight - 1;
             coordPairs[i] =
                     std::min(right, std::max(0, coordPairs[i]));
             coordPairs[i+1] =
@@ -343,25 +356,25 @@
 void ZoomRatioMapper::scaleRects(int32_t* rects, int rectCount,
         float scaleRatio) {
     for (int i = 0; i < rectCount * 4; i += 4) {
-        // Map from (l, t, width, height) to (l, t, r, b).
-        // [l, t] is inclusive, and [r, b] is exclusive.
+        // Map from (l, t, width, height) to (l, t, l+width-1, t+height-1),
+        // where both top-left and bottom-right are inclusive.
         int32_t coords[4] = {
             rects[i],
             rects[i + 1],
-            rects[i] + rects[i + 2],
-            rects[i + 1] + rects[i + 3]
+            rects[i] + rects[i + 2] - 1,
+            rects[i + 1] + rects[i + 3] - 1
         };
 
         // top-left
-        scaleCoordinates(coords, 1, scaleRatio, ClampInclusive);
+        scaleCoordinates(coords, 1, scaleRatio, true /*clamp*/);
         // bottom-right
-        scaleCoordinates(coords+2, 1, scaleRatio, ClampExclusive);
+        scaleCoordinates(coords+2, 1, scaleRatio, true /*clamp*/);
 
         // Map back to (l, t, width, height)
         rects[i] = coords[0];
         rects[i + 1] = coords[1];
-        rects[i + 2] = coords[2] - coords[0];
-        rects[i + 3] = coords[3] - coords[1];
+        rects[i + 2] = coords[2] - coords[0] + 1;
+        rects[i + 3] = coords[3] - coords[1] + 1;
     }
 }
 
diff --git a/services/camera/libcameraservice/device3/ZoomRatioMapper.h b/services/camera/libcameraservice/device3/ZoomRatioMapper.h
index aa3d913..698f87f 100644
--- a/services/camera/libcameraservice/device3/ZoomRatioMapper.h
+++ b/services/camera/libcameraservice/device3/ZoomRatioMapper.h
@@ -65,14 +65,8 @@
     status_t updateCaptureResult(CameraMetadata *request, bool requestedZoomRatioIs1);
 
   public: // Visible for testing. Do not use concurently.
-    enum ClampMode {
-        ClampOff,
-        ClampInclusive,
-        ClampExclusive,
-    };
-
     void scaleCoordinates(int32_t* coordPairs, int coordCount,
-            float scaleRatio, ClampMode clamp);
+            float scaleRatio, bool clamp);
 
     bool isValid() { return mIsValid; }
   private:
diff --git a/services/camera/libcameraservice/tests/ZoomRatioTest.cpp b/services/camera/libcameraservice/tests/ZoomRatioTest.cpp
index 300da09..4e94991 100644
--- a/services/camera/libcameraservice/tests/ZoomRatioTest.cpp
+++ b/services/camera/libcameraservice/tests/ZoomRatioTest.cpp
@@ -171,35 +171,35 @@
 
     std::array<int32_t, 16> originalCoords = {
             0, 0, // top-left
-            width, 0, // top-right
-            0, height, // bottom-left
-            width, height, // bottom-right
-            width / 2, height / 2, // center
-            width / 4, height / 4, // top-left after 2x
-            width / 3, height * 2 / 3, // bottom-left after 3x zoom
-            width * 7 / 8, height / 2, // middle-right after 1.33x zoom
+            width - 1, 0, // top-right
+            0, height - 1, // bottom-left
+            width - 1, height - 1, // bottom-right
+            (width - 1) / 2, (height - 1) / 2, // center
+            (width - 1) / 4, (height - 1) / 4, // top-left after 2x
+            (width - 1) / 3, (height - 1) * 2 / 3, // bottom-left after 3x zoom
+            (width - 1) * 7 / 8, (height - 1) / 2, // middle-right after 1.33x zoom
     };
 
     // Verify 1.0x zoom doesn't change the coordinates
     auto coords = originalCoords;
-    mapper.scaleCoordinates(coords.data(), coords.size()/2, 1.0f, ZoomRatioMapper::ClampOff);
+    mapper.scaleCoordinates(coords.data(), coords.size()/2, 1.0f, false /*clamp*/);
     for (size_t i = 0; i < coords.size(); i++) {
         EXPECT_EQ(coords[i], originalCoords[i]);
     }
 
     // Verify 2.0x zoom work as expected (no clamping)
     std::array<float, 16> expected2xCoords = {
-            - width / 2.0f, - height / 2.0f,// top-left
-            width * 3 / 2.0f, - height / 2.0f, // top-right
-            - width / 2.0f, height * 3 / 2.0f, // bottom-left
-            width * 3 / 2.0f, height * 3 / 2.0f, // bottom-right
-            width / 2.0f, height / 2.0f, // center
+            - (width - 1) / 2.0f, - (height - 1) / 2.0f,// top-left
+            (width - 1) * 3 / 2.0f, - (height - 1) / 2.0f, // top-right
+            - (width - 1) / 2.0f, (height - 1) * 3 / 2.0f, // bottom-left
+            (width - 1) * 3 / 2.0f, (height - 1) * 3 / 2.0f, // bottom-right
+            (width - 1) / 2.0f, (height - 1) / 2.0f, // center
             0, 0, // top-left after 2x
-            width / 6.0f, height - height / 6.0f, // bottom-left after 3x zoom
-            width + width / 4.0f, height / 2.0f, // middle-right after 1.33x zoom
+            (width - 1) / 6.0f, (height - 1) * 5.0f / 6.0f, // bottom-left after 3x zoom
+            (width - 1) * 5.0f / 4.0f, (height - 1) / 2.0f, // middle-right after 1.33x zoom
     };
     coords = originalCoords;
-    mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, ZoomRatioMapper::ClampOff);
+    mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, false /*clamp*/);
     for (size_t i = 0; i < coords.size(); i++) {
         EXPECT_LE(std::abs(coords[i] - expected2xCoords[i]), kMaxAllowedPixelError);
     }
@@ -207,16 +207,16 @@
     // Verify 2.0x zoom work as expected (with inclusive clamping)
     std::array<float, 16> expected2xCoordsClampedInc = {
             0, 0, // top-left
-            static_cast<float>(width) - 1, 0, // top-right
-            0, static_cast<float>(height) - 1, // bottom-left
-            static_cast<float>(width) - 1, static_cast<float>(height) - 1, // bottom-right
-            width / 2.0f, height / 2.0f, // center
+            width - 1.0f, 0, // top-right
+            0, height - 1.0f, // bottom-left
+            width - 1.0f, height - 1.0f, // bottom-right
+            (width - 1) / 2.0f, (height - 1) / 2.0f, // center
             0, 0, // top-left after 2x
-            width / 6.0f, height - height / 6.0f , // bottom-left after 3x zoom
-            static_cast<float>(width) - 1,  height / 2.0f, // middle-right after 1.33x zoom
+            (width - 1) / 6.0f, (height - 1) * 5.0f / 6.0f , // bottom-left after 3x zoom
+            width - 1.0f,  (height - 1) / 2.0f, // middle-right after 1.33x zoom
     };
     coords = originalCoords;
-    mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, ZoomRatioMapper::ClampInclusive);
+    mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, true /*clamp*/);
     for (size_t i = 0; i < coords.size(); i++) {
         EXPECT_LE(std::abs(coords[i] - expected2xCoordsClampedInc[i]), kMaxAllowedPixelError);
     }
@@ -224,33 +224,33 @@
     // Verify 2.0x zoom work as expected (with exclusive clamping)
     std::array<float, 16> expected2xCoordsClampedExc = {
             0, 0, // top-left
-            static_cast<float>(width), 0, // top-right
-            0, static_cast<float>(height), // bottom-left
-            static_cast<float>(width), static_cast<float>(height), // bottom-right
+            width - 1.0f, 0, // top-right
+            0, height - 1.0f, // bottom-left
+            width - 1.0f, height - 1.0f, // bottom-right
             width / 2.0f, height / 2.0f, // center
             0, 0, // top-left after 2x
-            width / 6.0f, height - height / 6.0f , // bottom-left after 3x zoom
-            static_cast<float>(width),  height / 2.0f, // middle-right after 1.33x zoom
+            (width - 1) / 6.0f, (height - 1) * 5.0f / 6.0f , // bottom-left after 3x zoom
+            width - 1.0f,  height / 2.0f, // middle-right after 1.33x zoom
     };
     coords = originalCoords;
-    mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, ZoomRatioMapper::ClampExclusive);
+    mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, true /*clamp*/);
     for (size_t i = 0; i < coords.size(); i++) {
         EXPECT_LE(std::abs(coords[i] - expected2xCoordsClampedExc[i]), kMaxAllowedPixelError);
     }
 
     // Verify 0.33x zoom work as expected
     std::array<float, 16> expectedZoomOutCoords = {
-            width / 3.0f, height / 3.0f, // top-left
-            width * 2 / 3.0f, height / 3.0f, // top-right
-            width / 3.0f, height * 2 / 3.0f, // bottom-left
-            width * 2 / 3.0f, height * 2 / 3.0f, // bottom-right
-            width / 2.0f, height / 2.0f, // center
-            width * 5 / 12.0f, height * 5 / 12.0f, // top-left after 2x
-            width * 4 / 9.0f, height * 5 / 9.0f, // bottom-left after 3x zoom-in
-            width * 5 / 8.0f, height / 2.0f, // middle-right after 1.33x zoom-in
+            (width - 1) / 3.0f, (height - 1) / 3.0f, // top-left
+            (width - 1) * 2 / 3.0f, (height - 1) / 3.0f, // top-right
+            (width - 1) / 3.0f, (height - 1) * 2 / 3.0f, // bottom-left
+            (width - 1) * 2 / 3.0f, (height - 1) * 2 / 3.0f, // bottom-right
+            (width - 1) / 2.0f, (height - 1) / 2.0f, // center
+            (width - 1) * 5 / 12.0f, (height - 1) * 5 / 12.0f, // top-left after 2x
+            (width - 1) * 4 / 9.0f, (height - 1) * 5 / 9.0f, // bottom-left after 3x zoom-in
+            (width - 1) * 5 / 8.0f, (height - 1) / 2.0f, // middle-right after 1.33x zoom-in
     };
     coords = originalCoords;
-    mapper.scaleCoordinates(coords.data(), coords.size()/2, 1.0f/3, ZoomRatioMapper::ClampOff);
+    mapper.scaleCoordinates(coords.data(), coords.size()/2, 1.0f/3, false /*clamp*/);
     for (size_t i = 0; i < coords.size(); i++) {
         EXPECT_LE(std::abs(coords[i] - expectedZoomOutCoords[i]), kMaxAllowedPixelError);
     }
@@ -323,7 +323,8 @@
     entry = metadata.find(ANDROID_SCALER_CROP_REGION);
     ASSERT_EQ(entry.count, 4U);
     for (int i = 0; i < 4; i++) {
-        EXPECT_EQ(entry.data.i32[i], testDefaultCropSize[index][i]);
+        EXPECT_LE(std::abs(entry.data.i32[i] - testDefaultCropSize[index][i]),
+                kMaxAllowedPixelError);
     }
     entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
     EXPECT_NEAR(entry.data.f[0], 2.0f, kMaxAllowedRatioError);
@@ -335,7 +336,7 @@
     entry = metadata.find(ANDROID_SCALER_CROP_REGION);
     ASSERT_EQ(entry.count, 4U);
     for (int i = 0; i < 4; i++) {
-        EXPECT_EQ(entry.data.i32[i], test2xCropRegion[index][i]);
+        EXPECT_LE(std::abs(entry.data.i32[i] - test2xCropRegion[index][i]), kMaxAllowedPixelError);
     }
 
     // Letter boxing crop region, zoomRatio is 1.0
diff --git a/services/mediametrics/mediametrics.rc b/services/mediametrics/mediametrics.rc
index 1efde5e..2a6c817 100644
--- a/services/mediametrics/mediametrics.rc
+++ b/services/mediametrics/mediametrics.rc
@@ -3,4 +3,4 @@
     user media
     group media
     ioprio rt 4
-    writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
+    task_profiles ProcessCapacityHigh HighPerformance
diff --git a/services/mediaresourcemanager/ResourceManagerService.cpp b/services/mediaresourcemanager/ResourceManagerService.cpp
index be5af00..ff45c87 100644
--- a/services/mediaresourcemanager/ResourceManagerService.cpp
+++ b/services/mediaresourcemanager/ResourceManagerService.cpp
@@ -114,6 +114,7 @@
         info.uid = uid;
         info.clientId = clientId;
         info.client = client;
+        info.pendingRemoval = false;
 
         index = infos.add(clientId, info);
     }
@@ -648,6 +649,36 @@
     return Status::ok();
 }
 
+Status ResourceManagerService::markClientForPendingRemoval(int32_t pid, int64_t clientId) {
+    String8 log = String8::format(
+            "markClientForPendingRemoval(pid %d, clientId %lld)",
+            pid, (long long) clientId);
+    mServiceLog->add(log);
+
+    Mutex::Autolock lock(mLock);
+    if (!mProcessInfo->isValidPid(pid)) {
+        ALOGE("Rejected markClientForPendingRemoval call with invalid pid.");
+        return Status::fromServiceSpecificError(BAD_VALUE);
+    }
+    ssize_t index = mMap.indexOfKey(pid);
+    if (index < 0) {
+        ALOGV("markClientForPendingRemoval: didn't find pid %d for clientId %lld",
+              pid, (long long)clientId);
+        return Status::ok();
+    }
+    ResourceInfos &infos = mMap.editValueAt(index);
+
+    index = infos.indexOfKey(clientId);
+    if (index < 0) {
+        ALOGV("markClientForPendingRemoval: didn't find clientId %lld", (long long) clientId);
+        return Status::ok();
+    }
+
+    ResourceInfo &info = infos.editValueAt(index);
+    info.pendingRemoval = true;
+    return Status::ok();
+}
+
 bool ResourceManagerService::getPriority_l(int pid, int* priority) {
     int newPid = pid;
 
@@ -693,6 +724,12 @@
     int lowestPriorityPid;
     int lowestPriority;
     int callingPriority;
+
+    // Before looking into other processes, check if we have clients marked for
+    // pending removal in the same process.
+    if (getBiggestClient_l(callingPid, type, client, true /* pendingRemovalOnly */)) {
+        return true;
+    }
     if (!getPriority_l(callingPid, &callingPriority)) {
         ALOGE("getLowestPriorityBiggestClient_l: can't get process priority for pid %d",
                 callingPid);
@@ -761,7 +798,8 @@
 }
 
 bool ResourceManagerService::getBiggestClient_l(
-        int pid, MediaResource::Type type, std::shared_ptr<IResourceManagerClient> *client) {
+        int pid, MediaResource::Type type, std::shared_ptr<IResourceManagerClient> *client,
+        bool pendingRemovalOnly) {
     ssize_t index = mMap.indexOfKey(pid);
     if (index < 0) {
         ALOGE("getBiggestClient_l: can't find resource info for pid %d", pid);
@@ -773,6 +811,9 @@
     const ResourceInfos &infos = mMap.valueAt(index);
     for (size_t i = 0; i < infos.size(); ++i) {
         const ResourceList &resources = infos[i].resources;
+        if (pendingRemovalOnly && !infos[i].pendingRemoval) {
+            continue;
+        }
         for (auto it = resources.begin(); it != resources.end(); it++) {
             const MediaResourceParcel &resource = it->second;
             if (resource.type == type) {
diff --git a/services/mediaresourcemanager/ResourceManagerService.h b/services/mediaresourcemanager/ResourceManagerService.h
index 92048c3..49c247e 100644
--- a/services/mediaresourcemanager/ResourceManagerService.h
+++ b/services/mediaresourcemanager/ResourceManagerService.h
@@ -18,6 +18,8 @@
 #ifndef ANDROID_MEDIA_RESOURCEMANAGERSERVICE_H
 #define ANDROID_MEDIA_RESOURCEMANAGERSERVICE_H
 
+#include <map>
+
 #include <aidl/android/media/BnResourceManagerService.h>
 #include <arpa/inet.h>
 #include <media/MediaResource.h>
@@ -50,6 +52,7 @@
     std::shared_ptr<IResourceManagerClient> client;
     sp<DeathNotifier> deathNotifier;
     ResourceList resources;
+    bool pendingRemoval{false};
 };
 
 // TODO: convert these to std::map
@@ -122,6 +125,8 @@
             int originalPid,
             int newPid) override;
 
+    Status markClientForPendingRemoval(int32_t pid, int64_t clientId) override;
+
     Status removeResource(int pid, int64_t clientId, bool checkValid);
 
 private:
@@ -146,7 +151,8 @@
     // Gets the client who owns biggest piece of specified resource type from pid.
     // Returns false if failed. The client will remain unchanged if failed.
     bool getBiggestClient_l(int pid, MediaResource::Type type,
-            std::shared_ptr<IResourceManagerClient> *client);
+            std::shared_ptr<IResourceManagerClient> *client,
+            bool pendingRemovalOnly = false);
 
     bool isCallingPriorityHigher_l(int callingPid, int pid);
 
diff --git a/services/mediaresourcemanager/test/ResourceManagerService_test.cpp b/services/mediaresourcemanager/test/ResourceManagerService_test.cpp
index 5d839fa..702935d 100644
--- a/services/mediaresourcemanager/test/ResourceManagerService_test.cpp
+++ b/services/mediaresourcemanager/test/ResourceManagerService_test.cpp
@@ -472,6 +472,56 @@
         }
     }
 
+    void testMarkClientForPendingRemoval() {
+        bool result;
+
+        {
+            addResource();
+            mService->mSupportsSecureWithNonSecureCodec = true;
+
+            std::vector<MediaResourceParcel> resources;
+            resources.push_back(MediaResource(MediaResource::Type::kNonSecureCodec, 1));
+
+            // Remove low priority clients
+            mService->removeClient(kTestPid1, getId(mTestClient1));
+
+            // no lower priority client
+            CHECK_STATUS_FALSE(mService->reclaimResource(kTestPid2, resources, &result));
+            verifyClients(false /* c1 */, false /* c2 */, false /* c3 */);
+
+            mService->markClientForPendingRemoval(kTestPid2, getId(mTestClient2));
+
+            // client marked for pending removal from the same process got reclaimed
+            CHECK_STATUS_TRUE(mService->reclaimResource(kTestPid2, resources, &result));
+            verifyClients(false /* c1 */, true /* c2 */, false /* c3 */);
+
+            // clean up client 3 which still left
+            mService->removeClient(kTestPid2, getId(mTestClient3));
+        }
+
+        {
+            addResource();
+            mService->mSupportsSecureWithNonSecureCodec = true;
+
+            std::vector<MediaResourceParcel> resources;
+            resources.push_back(MediaResource(MediaResource::Type::kNonSecureCodec, 1));
+
+            mService->markClientForPendingRemoval(kTestPid2, getId(mTestClient2));
+
+            // client marked for pending removal from the same process got reclaimed
+            // first, even though there are lower priority process
+            CHECK_STATUS_TRUE(mService->reclaimResource(kTestPid2, resources, &result));
+            verifyClients(false /* c1 */, true /* c2 */, false /* c3 */);
+
+            // lower priority client got reclaimed
+            CHECK_STATUS_TRUE(mService->reclaimResource(kTestPid2, resources, &result));
+            verifyClients(true /* c1 */, false /* c2 */, false /* c3 */);
+
+            // clean up client 3 which still left
+            mService->removeClient(kTestPid2, getId(mTestClient3));
+        }
+    }
+
     void testRemoveClient() {
         addResource();
 
@@ -900,4 +950,8 @@
     testOverridePid();
 }
 
+TEST_F(ResourceManagerServiceTest, markClientForPendingRemoval) {
+    testMarkClientForPendingRemoval();
+}
+
 } // namespace android
diff --git a/services/mediatranscoding/MediaTranscodingService.cpp b/services/mediatranscoding/MediaTranscodingService.cpp
index 0289613..6c10e3e 100644
--- a/services/mediatranscoding/MediaTranscodingService.cpp
+++ b/services/mediatranscoding/MediaTranscodingService.cpp
@@ -22,6 +22,7 @@
 #include <android/binder_process.h>
 #include <media/TranscodingClientManager.h>
 #include <media/TranscodingJobScheduler.h>
+#include <media/TranscodingUidPolicy.h>
 #include <private/android_filesystem_config.h>
 #include <utils/Log.h>
 #include <utils/Vector.h>
@@ -48,10 +49,9 @@
     }
 }
 
-// DummyTranscoder and DummyProcessInfo are currently used to instantiate
-// MediaTranscodingService on service side for testing, so that we could
-// actually test the IPC calls of MediaTranscodingService to expose some
-// issues that's observable only over IPC.
+// DummyTranscoder is currently used to instantiate MediaTranscodingService on
+// service side for testing, so that we could actually test the IPC calls of
+// MediaTranscodingService to expose some issues that's observable only over IPC.
 class DummyTranscoder : public TranscoderInterface {
     void start(int64_t clientId, int32_t jobId) override {
         (void)clientId;
@@ -67,23 +67,17 @@
     }
 };
 
-class DummyProcessInfo : public ProcessInfoInterface {
-    bool isProcessOnTop(int32_t pid) override {
-        (void)pid;
-        return true;
-    }
-};
-
 MediaTranscodingService::MediaTranscodingService()
       : MediaTranscodingService(std::make_shared<DummyTranscoder>(),
-                                std::make_shared<DummyProcessInfo>()) {}
+                                std::make_shared<TranscodingUidPolicy>()) {}
 
 MediaTranscodingService::MediaTranscodingService(
         const std::shared_ptr<TranscoderInterface>& transcoder,
-        const std::shared_ptr<ProcessInfoInterface>& procInfo)
-      : mJobScheduler(new TranscodingJobScheduler(transcoder, procInfo)),
+        const std::shared_ptr<UidPolicyInterface>& uidPolicy)
+      : mJobScheduler(new TranscodingJobScheduler(transcoder, uidPolicy)),
         mClientManager(new TranscodingClientManager(mJobScheduler)) {
     ALOGV("MediaTranscodingService is created");
+    uidPolicy->setCallback(mJobScheduler);
 }
 
 MediaTranscodingService::~MediaTranscodingService() {
diff --git a/services/mediatranscoding/MediaTranscodingService.h b/services/mediatranscoding/MediaTranscodingService.h
index 36db9b3..f7ac336 100644
--- a/services/mediatranscoding/MediaTranscodingService.h
+++ b/services/mediatranscoding/MediaTranscodingService.h
@@ -31,7 +31,7 @@
 class TranscodingClientManager;
 class TranscodingJobScheduler;
 class TranscoderInterface;
-class ProcessInfoInterface;
+class UidPolicyInterface;
 
 class MediaTranscodingService : public BnMediaTranscodingService {
 public:
@@ -40,7 +40,7 @@
 
     MediaTranscodingService();
     MediaTranscodingService(const std::shared_ptr<TranscoderInterface>& transcoder,
-                            const std::shared_ptr<ProcessInfoInterface>& procInfo);
+                            const std::shared_ptr<UidPolicyInterface>& uidPolicy);
     virtual ~MediaTranscodingService();
 
     static void instantiate();
diff --git a/services/mediatranscoding/mediatranscoding.rc b/services/mediatranscoding/mediatranscoding.rc
index 2dc547f..5bfef59 100644
--- a/services/mediatranscoding/mediatranscoding.rc
+++ b/services/mediatranscoding/mediatranscoding.rc
@@ -3,4 +3,4 @@
     user media
     group media
     ioprio rt 4
-    writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
+    task_profiles ProcessCapacityHigh HighPerformance