Register resource observer and handle resource lost.

bug: 168307955
bug: 154733526
Change-Id: I2d5e39ce23bc873dd3bac090e3f7aa1124d0a579
diff --git a/media/libmediatranscoding/Android.bp b/media/libmediatranscoding/Android.bp
index 2e66df7..128d0d8 100644
--- a/media/libmediatranscoding/Android.bp
+++ b/media/libmediatranscoding/Android.bp
@@ -54,6 +54,7 @@
     srcs: [
         "TranscodingClientManager.cpp",
         "TranscodingJobScheduler.cpp",
+        "TranscodingResourcePolicy.cpp",
         "TranscodingUidPolicy.cpp",
         "TranscoderWrapper.cpp",
     ],
@@ -67,12 +68,16 @@
         "libbinder",
         "libmediandk",
     ],
+    export_shared_lib_headers: [
+        "libmediandk",
+    ],
 
     export_include_dirs: ["include"],
 
     static_libs: [
         "mediatranscoding_aidl_interface-ndk_platform",
         "resourcemanager_aidl_interface-ndk_platform",
+        "resourceobserver_aidl_interface-ndk_platform",
     ],
 
     cflags: [
diff --git a/media/libmediatranscoding/TranscoderWrapper.cpp b/media/libmediatranscoding/TranscoderWrapper.cpp
index bd03671..8062fcf 100644
--- a/media/libmediatranscoding/TranscoderWrapper.cpp
+++ b/media/libmediatranscoding/TranscoderWrapper.cpp
@@ -89,26 +89,40 @@
 }
 
 //static
-const char* TranscoderWrapper::toString(Event::Type type) {
-    switch (type) {
+std::string TranscoderWrapper::toString(const Event& event) {
+    std::string typeStr;
+    switch (event.type) {
     case Event::Start:
-        return "Start";
-    case Event::Pause:
-        return "Pause";
-    case Event::Resume:
-        return "Resume";
-    case Event::Stop:
-        return "Stop";
-    case Event::Finish:
-        return "Finish";
-    case Event::Error:
-        return "Error";
-    case Event::Progress:
-        return "Progress";
-    default:
+        typeStr = "Start";
         break;
+    case Event::Pause:
+        typeStr = "Pause";
+        break;
+    case Event::Resume:
+        typeStr = "Resume";
+        break;
+    case Event::Stop:
+        typeStr = "Stop";
+        break;
+    case Event::Finish:
+        typeStr = "Finish";
+        break;
+    case Event::Error:
+        typeStr = "Error";
+        break;
+    case Event::Progress:
+        typeStr = "Progress";
+        break;
+    default:
+        return "(unknown)";
     }
-    return "(unknown)";
+    std::string result;
+    result = "job {" + std::to_string(event.clientId) + "," + std::to_string(event.jobId) +
+             "}: " + typeStr;
+    if (event.type == Event::Error || event.type == Event::Progress) {
+        result += " " + std::to_string(event.arg);
+    }
+    return result;
 }
 
 class TranscoderWrapper::CallbackImpl : public MediaTranscoder::CallbackInterface {
@@ -128,7 +142,7 @@
                          media_status_t error) override {
         auto owner = mOwner.lock();
         if (owner != nullptr) {
-            owner->onError(mClientId, mJobId, toTranscodingError(error));
+            owner->onError(mClientId, mJobId, error);
         }
     }
 
@@ -160,20 +174,41 @@
     mCallback = cb;
 }
 
+static bool isResourceError(media_status_t err) {
+    return err == AMEDIACODEC_ERROR_RECLAIMED || err == AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE;
+}
+
+void TranscoderWrapper::reportError(ClientIdType clientId, JobIdType jobId, media_status_t err) {
+    auto callback = mCallback.lock();
+    if (callback != nullptr) {
+        if (isResourceError(err)) {
+            // Add a placeholder pause state to mPausedStateMap. This is required when resuming.
+            // TODO: remove this when transcoder pause/resume logic is ready. New logic will
+            // no longer use the pause states.
+            auto it = mPausedStateMap.find(JobKeyType(clientId, jobId));
+            if (it == mPausedStateMap.end()) {
+                mPausedStateMap.emplace(JobKeyType(clientId, jobId),
+                                        std::shared_ptr<const Parcel>());
+            }
+
+            callback->onResourceLost();
+        } else {
+            callback->onError(clientId, jobId, toTranscodingError(err));
+        }
+    }
+}
+
 void TranscoderWrapper::start(ClientIdType clientId, JobIdType jobId,
                               const TranscodingRequestParcel& request,
                               const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
     queueEvent(Event::Start, clientId, jobId, [=] {
-        TranscodingErrorCode err = handleStart(clientId, jobId, request, clientCb);
+        media_status_t err = handleStart(clientId, jobId, request, clientCb);
 
-        auto callback = mCallback.lock();
-        if (err != TranscodingErrorCode::kNoError) {
+        if (err != AMEDIA_OK) {
             cleanup();
-
-            if (callback != nullptr) {
-                callback->onError(clientId, jobId, err);
-            }
+            reportError(clientId, jobId, err);
         } else {
+            auto callback = mCallback.lock();
             if (callback != nullptr) {
                 callback->onStarted(clientId, jobId);
             }
@@ -183,15 +218,15 @@
 
 void TranscoderWrapper::pause(ClientIdType clientId, JobIdType jobId) {
     queueEvent(Event::Pause, clientId, jobId, [=] {
-        TranscodingErrorCode err = handlePause(clientId, jobId);
+        media_status_t err = handlePause(clientId, jobId);
 
         cleanup();
 
-        auto callback = mCallback.lock();
-        if (callback != nullptr) {
-            if (err != TranscodingErrorCode::kNoError) {
-                callback->onError(clientId, jobId, err);
-            } else {
+        if (err != AMEDIA_OK) {
+            reportError(clientId, jobId, err);
+        } else {
+            auto callback = mCallback.lock();
+            if (callback != nullptr) {
                 callback->onPaused(clientId, jobId);
             }
         }
@@ -202,16 +237,13 @@
                                const TranscodingRequestParcel& request,
                                const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
     queueEvent(Event::Resume, clientId, jobId, [=] {
-        TranscodingErrorCode err = handleResume(clientId, jobId, request, clientCb);
+        media_status_t err = handleResume(clientId, jobId, request, clientCb);
 
-        auto callback = mCallback.lock();
-        if (err != TranscodingErrorCode::kNoError) {
+        if (err != AMEDIA_OK) {
             cleanup();
-
-            if (callback != nullptr) {
-                callback->onError(clientId, jobId, err);
-            }
+            reportError(clientId, jobId, err);
         } else {
+            auto callback = mCallback.lock();
             if (callback != nullptr) {
                 callback->onResumed(clientId, jobId);
             }
@@ -225,7 +257,7 @@
             // Cancelling the currently running job.
             media_status_t err = mTranscoder->cancel();
             if (err != AMEDIA_OK) {
-                ALOGE("failed to stop transcoder: %d", err);
+                ALOGW("failed to stop transcoder: %d", err);
             } else {
                 ALOGI("transcoder stopped");
             }
@@ -251,41 +283,43 @@
     });
 }
 
-void TranscoderWrapper::onError(ClientIdType clientId, JobIdType jobId,
-                                TranscodingErrorCode error) {
-    queueEvent(Event::Error, clientId, jobId, [=] {
-        if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
-            cleanup();
-        }
-
-        auto callback = mCallback.lock();
-        if (callback != nullptr) {
-            callback->onError(clientId, jobId, error);
-        }
-    });
+void TranscoderWrapper::onError(ClientIdType clientId, JobIdType jobId, media_status_t error) {
+    queueEvent(
+            Event::Error, clientId, jobId,
+            [=] {
+                if (mTranscoder != nullptr && clientId == mCurrentClientId &&
+                    jobId == mCurrentJobId) {
+                    cleanup();
+                }
+                reportError(clientId, jobId, error);
+            },
+            error);
 }
 
 void TranscoderWrapper::onProgress(ClientIdType clientId, JobIdType jobId, int32_t progress) {
-    queueEvent(Event::Progress, clientId, jobId, [=] {
-        auto callback = mCallback.lock();
-        if (callback != nullptr) {
-            callback->onProgressUpdate(clientId, jobId, progress);
-        }
-    });
+    queueEvent(
+            Event::Progress, clientId, jobId,
+            [=] {
+                auto callback = mCallback.lock();
+                if (callback != nullptr) {
+                    callback->onProgressUpdate(clientId, jobId, progress);
+                }
+            },
+            progress);
 }
 
-TranscodingErrorCode TranscoderWrapper::setupTranscoder(
+media_status_t TranscoderWrapper::setupTranscoder(
         ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
         const std::shared_ptr<ITranscodingClientCallback>& clientCb,
         const std::shared_ptr<const Parcel>& pausedState) {
     if (clientCb == nullptr) {
         ALOGE("client callback is null");
-        return TranscodingErrorCode::kInvalidParameter;
+        return AMEDIA_ERROR_INVALID_PARAMETER;
     }
 
     if (mTranscoder != nullptr) {
         ALOGE("transcoder already running");
-        return TranscodingErrorCode::kInvalidOperation;
+        return AMEDIA_ERROR_INVALID_OPERATION;
     }
 
     Status status;
@@ -293,7 +327,7 @@
     status = clientCb->openFileDescriptor(request.sourceFilePath, "r", &srcFd);
     if (!status.isOk() || srcFd.get() < 0) {
         ALOGE("failed to open source");
-        return TranscodingErrorCode::kErrorIO;
+        return AMEDIA_ERROR_IO;
     }
 
     // Open dest file with "rw", as the transcoder could potentially reuse part of it
@@ -302,7 +336,7 @@
     status = clientCb->openFileDescriptor(request.destinationFilePath, "rw", &dstFd);
     if (!status.isOk() || dstFd.get() < 0) {
         ALOGE("failed to open destination");
-        return TranscodingErrorCode::kErrorIO;
+        return AMEDIA_ERROR_IO;
     }
 
     mCurrentClientId = clientId;
@@ -311,19 +345,19 @@
     mTranscoder = MediaTranscoder::create(mTranscoderCb, pausedState);
     if (mTranscoder == nullptr) {
         ALOGE("failed to create transcoder");
-        return TranscodingErrorCode::kUnknown;
+        return AMEDIA_ERROR_UNKNOWN;
     }
 
     media_status_t err = mTranscoder->configureSource(srcFd.get());
     if (err != AMEDIA_OK) {
         ALOGE("failed to configure source: %d", err);
-        return toTranscodingError(err);
+        return err;
     }
 
     std::vector<std::shared_ptr<AMediaFormat>> trackFormats = mTranscoder->getTrackFormats();
     if (trackFormats.size() == 0) {
         ALOGE("failed to get track formats!");
-        return TranscodingErrorCode::kMalformed;
+        return AMEDIA_ERROR_MALFORMED;
     }
 
     for (int i = 0; i < trackFormats.size(); ++i) {
@@ -341,43 +375,43 @@
         }
         if (err != AMEDIA_OK) {
             ALOGE("failed to configure track format for track %d: %d", i, err);
-            return toTranscodingError(err);
+            return err;
         }
     }
 
     err = mTranscoder->configureDestination(dstFd.get());
     if (err != AMEDIA_OK) {
         ALOGE("failed to configure dest: %d", err);
-        return toTranscodingError(err);
+        return err;
     }
 
-    return TranscodingErrorCode::kNoError;
+    return AMEDIA_OK;
 }
 
-TranscodingErrorCode TranscoderWrapper::handleStart(
+media_status_t TranscoderWrapper::handleStart(
         ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
         const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
-    ALOGI("setting up transcoder for start");
-    TranscodingErrorCode err = setupTranscoder(clientId, jobId, request, clientCb);
-    if (err != TranscodingErrorCode::kNoError) {
+    ALOGI("%s: setting up transcoder for start", __FUNCTION__);
+    media_status_t err = setupTranscoder(clientId, jobId, request, clientCb);
+    if (err != AMEDIA_OK) {
         ALOGI("%s: failed to setup transcoder", __FUNCTION__);
         return err;
     }
 
-    media_status_t status = mTranscoder->start();
-    if (status != AMEDIA_OK) {
+    err = mTranscoder->start();
+    if (err != AMEDIA_OK) {
         ALOGE("%s: failed to start transcoder: %d", __FUNCTION__, err);
-        return toTranscodingError(status);
+        return err;
     }
 
     ALOGI("%s: transcoder started", __FUNCTION__);
-    return TranscodingErrorCode::kNoError;
+    return AMEDIA_OK;
 }
 
-TranscodingErrorCode TranscoderWrapper::handlePause(ClientIdType clientId, JobIdType jobId) {
+media_status_t TranscoderWrapper::handlePause(ClientIdType clientId, JobIdType jobId) {
     if (mTranscoder == nullptr) {
         ALOGE("%s: transcoder is not running", __FUNCTION__);
-        return TranscodingErrorCode::kInvalidOperation;
+        return AMEDIA_ERROR_INVALID_OPERATION;
     }
 
     if (clientId != mCurrentClientId || jobId != mCurrentJobId) {
@@ -385,19 +419,21 @@
               (long long)clientId, jobId, (long long)mCurrentClientId, mCurrentJobId);
     }
 
+    ALOGI("%s: pausing transcoder", __FUNCTION__);
+
     std::shared_ptr<const Parcel> pauseStates;
     media_status_t err = mTranscoder->pause(&pauseStates);
     if (err != AMEDIA_OK) {
         ALOGE("%s: failed to pause transcoder: %d", __FUNCTION__, err);
-        return toTranscodingError(err);
+        return err;
     }
     mPausedStateMap[JobKeyType(clientId, jobId)] = pauseStates;
 
     ALOGI("%s: transcoder paused", __FUNCTION__);
-    return TranscodingErrorCode::kNoError;
+    return AMEDIA_OK;
 }
 
-TranscodingErrorCode TranscoderWrapper::handleResume(
+media_status_t TranscoderWrapper::handleResume(
         ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
         const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
     std::shared_ptr<const Parcel> pausedState;
@@ -407,24 +443,24 @@
         mPausedStateMap.erase(it);
     } else {
         ALOGE("%s: can't find paused state", __FUNCTION__);
-        return TranscodingErrorCode::kInvalidOperation;
+        return AMEDIA_ERROR_INVALID_OPERATION;
     }
 
-    ALOGI("setting up transcoder for resume");
-    TranscodingErrorCode err = setupTranscoder(clientId, jobId, request, clientCb, pausedState);
-    if (err != TranscodingErrorCode::kNoError) {
-        ALOGE("%s: failed to setup transcoder", __FUNCTION__);
+    ALOGI("%s: setting up transcoder for resume", __FUNCTION__);
+    media_status_t err = setupTranscoder(clientId, jobId, request, clientCb, pausedState);
+    if (err != AMEDIA_OK) {
+        ALOGE("%s: failed to setup transcoder: %d", __FUNCTION__, err);
         return err;
     }
 
-    media_status_t status = mTranscoder->resume();
-    if (status != AMEDIA_OK) {
+    err = mTranscoder->resume();
+    if (err != AMEDIA_OK) {
         ALOGE("%s: failed to resume transcoder: %d", __FUNCTION__, err);
-        return toTranscodingError(status);
+        return err;
     }
 
     ALOGI("%s: transcoder resumed", __FUNCTION__);
-    return TranscodingErrorCode::kNoError;
+    return AMEDIA_OK;
 }
 
 void TranscoderWrapper::cleanup() {
@@ -435,12 +471,10 @@
 }
 
 void TranscoderWrapper::queueEvent(Event::Type type, ClientIdType clientId, JobIdType jobId,
-                                   const std::function<void()> runnable) {
-    ALOGV("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)clientId, jobId, toString(type));
-
+                                   const std::function<void()> runnable, int32_t arg) {
     std::scoped_lock lock{mLock};
 
-    mQueue.push_back({type, clientId, jobId, runnable});
+    mQueue.push_back({type, clientId, jobId, runnable, arg});
     mCondition.notify_one();
 }
 
@@ -457,8 +491,7 @@
         Event event = *mQueue.begin();
         mQueue.pop_front();
 
-        ALOGD("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)event.clientId, event.jobId,
-              toString(event.type));
+        ALOGD("%s: %s", __FUNCTION__, toString(event).c_str());
 
         lock.unlock();
         event.runnable();
diff --git a/media/libmediatranscoding/TranscodingJobScheduler.cpp b/media/libmediatranscoding/TranscodingJobScheduler.cpp
index 3e4f319..24ac682 100644
--- a/media/libmediatranscoding/TranscodingJobScheduler.cpp
+++ b/media/libmediatranscoding/TranscodingJobScheduler.cpp
@@ -38,8 +38,13 @@
 
 TranscodingJobScheduler::TranscodingJobScheduler(
         const std::shared_ptr<TranscoderInterface>& transcoder,
-        const std::shared_ptr<UidPolicyInterface>& uidPolicy)
-      : mTranscoder(transcoder), mUidPolicy(uidPolicy), mCurrentJob(nullptr), mResourceLost(false) {
+        const std::shared_ptr<UidPolicyInterface>& uidPolicy,
+        const std::shared_ptr<ResourcePolicyInterface>& resourcePolicy)
+      : mTranscoder(transcoder),
+        mUidPolicy(uidPolicy),
+        mResourcePolicy(resourcePolicy),
+        mCurrentJob(nullptr),
+        mResourceLost(false) {
     // Only push empty offline queue initially. Realtime queues are added when requests come in.
     mUidSortedList.push_back(OFFLINE_UID);
     mOfflineUidIterator = mUidSortedList.begin();
@@ -398,15 +403,24 @@
 }
 
 void TranscodingJobScheduler::onResourceLost() {
-    ALOGV("%s", __FUNCTION__);
+    ALOGI("%s", __FUNCTION__);
 
     std::scoped_lock lock{mLock};
 
+    if (mResourceLost) {
+        return;
+    }
+
     // If we receive a resource loss event, the TranscoderLibrary already paused
     // the transcoding, so we don't need to call onPaused to notify it to pause.
     // Only need to update the job state here.
     if (mCurrentJob != nullptr && mCurrentJob->state == Job::RUNNING) {
         mCurrentJob->state = Job::PAUSED;
+        // Notify the client as a paused event.
+        auto clientCallback = mCurrentJob->callback.lock();
+        if (clientCallback != nullptr) {
+            clientCallback->onTranscodingPaused(mCurrentJob->key.second);
+        }
     }
     mResourceLost = true;
 
@@ -439,10 +453,14 @@
 }
 
 void TranscodingJobScheduler::onResourceAvailable() {
-    ALOGV("%s", __FUNCTION__);
-
     std::scoped_lock lock{mLock};
 
+    if (!mResourceLost) {
+        return;
+    }
+
+    ALOGI("%s", __FUNCTION__);
+
     mResourceLost = false;
     updateCurrentJob_l();
 
diff --git a/media/libmediatranscoding/TranscodingResourcePolicy.cpp b/media/libmediatranscoding/TranscodingResourcePolicy.cpp
new file mode 100644
index 0000000..4fd8338
--- /dev/null
+++ b/media/libmediatranscoding/TranscodingResourcePolicy.cpp
@@ -0,0 +1,169 @@
+/*
+ * 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 "TranscodingResourcePolicy"
+
+#include <aidl/android/media/BnResourceObserver.h>
+#include <aidl/android/media/IResourceObserverService.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <binder/IServiceManager.h>
+#include <media/TranscodingResourcePolicy.h>
+#include <utils/Log.h>
+
+namespace android {
+
+using Status = ::ndk::ScopedAStatus;
+using ::aidl::android::media::BnResourceObserver;
+using ::aidl::android::media::IResourceObserverService;
+using ::aidl::android::media::MediaObservableEvent;
+using ::aidl::android::media::MediaObservableFilter;
+using ::aidl::android::media::MediaObservableParcel;
+using ::aidl::android::media::MediaObservableType;
+
+static std::string toString(const MediaObservableParcel& observable) {
+    return "{" + ::aidl::android::media::toString(observable.type) + ", " +
+           std::to_string(observable.value) + "}";
+}
+
+struct TranscodingResourcePolicy::ResourceObserver : public BnResourceObserver {
+    explicit ResourceObserver(TranscodingResourcePolicy* owner) : mOwner(owner), mPid(getpid()) {}
+
+    // IResourceObserver
+    ::ndk::ScopedAStatus onStatusChanged(
+            MediaObservableEvent event, int32_t uid, int32_t pid,
+            const std::vector<MediaObservableParcel>& observables) override {
+        ALOGD("%s: %s, uid %d, pid %d, %s", __FUNCTION__,
+              ::aidl::android::media::toString(event).c_str(), uid, pid,
+              toString(observables[0]).c_str());
+
+        // Only report kIdle event for codec resources from other processes.
+        if (((uint64_t)event & (uint64_t)MediaObservableEvent::kIdle) != 0 && (pid != mPid)) {
+            for (auto& observable : observables) {
+                if (observable.type == MediaObservableType::kVideoSecureCodec ||
+                    observable.type == MediaObservableType::kVideoNonSecureCodec) {
+                    mOwner->onResourceAvailable();
+                    break;
+                }
+            }
+        }
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    TranscodingResourcePolicy* mOwner;
+    const pid_t mPid;
+};
+
+// static
+void TranscodingResourcePolicy::BinderDiedCallback(void* cookie) {
+    TranscodingResourcePolicy* owner = reinterpret_cast<TranscodingResourcePolicy*>(cookie);
+    if (owner != nullptr) {
+        owner->unregisterSelf();
+    }
+    // TODO(chz): retry to connecting to IResourceObserverService after failure.
+    // Also need to have back-up logic if IResourceObserverService is offline for
+    // Prolonged period of time. A possible alternative could be, during period where
+    // IResourceObserverService is not available, trigger onResourceAvailable() everytime
+    // when top uid changes (in hope that'll free up some codec instances that we could
+    // reclaim).
+}
+
+TranscodingResourcePolicy::TranscodingResourcePolicy()
+      : mRegistered(false), mDeathRecipient(AIBinder_DeathRecipient_new(BinderDiedCallback)) {
+    registerSelf();
+}
+
+TranscodingResourcePolicy::~TranscodingResourcePolicy() {
+    unregisterSelf();
+}
+
+void TranscodingResourcePolicy::registerSelf() {
+    ALOGI("TranscodingResourcePolicy: registerSelf");
+
+    ::ndk::SpAIBinder binder(AServiceManager_getService("media.resource_observer"));
+
+    std::scoped_lock lock{mRegisteredLock};
+
+    if (mRegistered) {
+        return;
+    }
+
+    // TODO(chz): retry to connecting to IResourceObserverService after failure.
+    mService = IResourceObserverService::fromBinder(binder);
+    if (mService == nullptr) {
+        ALOGE("Failed to get IResourceObserverService");
+        return;
+    }
+
+    // Only register filters for codec resource available.
+    mObserver = ::ndk::SharedRefBase::make<ResourceObserver>(this);
+    std::vector<MediaObservableFilter> filters = {
+            {MediaObservableType::kVideoSecureCodec, MediaObservableEvent::kIdle},
+            {MediaObservableType::kVideoNonSecureCodec, MediaObservableEvent::kIdle}};
+
+    Status status = mService->registerObserver(mObserver, filters);
+    if (!status.isOk()) {
+        ALOGE("failed to register: error %d", status.getServiceSpecificError());
+        mService = nullptr;
+        mObserver = nullptr;
+        return;
+    }
+
+    AIBinder_linkToDeath(binder.get(), mDeathRecipient.get(), reinterpret_cast<void*>(this));
+
+    ALOGD("@@@ registered observer");
+    mRegistered = true;
+}
+
+void TranscodingResourcePolicy::unregisterSelf() {
+    ALOGI("TranscodingResourcePolicy: unregisterSelf");
+
+    std::scoped_lock lock{mRegisteredLock};
+
+    if (!mRegistered) {
+        return;
+    }
+
+    ::ndk::SpAIBinder binder = mService->asBinder();
+    if (binder.get() != nullptr) {
+        Status status = mService->unregisterObserver(mObserver);
+        AIBinder_unlinkToDeath(binder.get(), mDeathRecipient.get(), reinterpret_cast<void*>(this));
+    }
+
+    mService = nullptr;
+    mObserver = nullptr;
+    mRegistered = false;
+}
+
+void TranscodingResourcePolicy::setCallback(
+        const std::shared_ptr<ResourcePolicyCallbackInterface>& cb) {
+    std::scoped_lock lock{mCallbackLock};
+    mResourcePolicyCallback = cb;
+}
+
+void TranscodingResourcePolicy::onResourceAvailable() {
+    std::shared_ptr<ResourcePolicyCallbackInterface> cb;
+    {
+        std::scoped_lock lock{mCallbackLock};
+        cb = mResourcePolicyCallback.lock();
+    }
+
+    if (cb != nullptr) {
+        cb->onResourceAvailable();
+    }
+}
+}  // namespace android
diff --git a/media/libmediatranscoding/include/media/ResourcePolicyInterface.h b/media/libmediatranscoding/include/media/ResourcePolicyInterface.h
new file mode 100644
index 0000000..8bd7d6b
--- /dev/null
+++ b/media/libmediatranscoding/include/media/ResourcePolicyInterface.h
@@ -0,0 +1,48 @@
+/*
+ * 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_RESOURCE_POLICY_INTERFACE_H
+#define ANDROID_MEDIA_RESOURCE_POLICY_INTERFACE_H
+#include <memory>
+namespace android {
+
+class ResourcePolicyCallbackInterface;
+
+// Interface for the JobScheduler to control the resource status updates.
+class ResourcePolicyInterface {
+public:
+    // Set the associated callback interface to send the events when resource
+    // status changes. (Set to nullptr will stop the updates.)
+    virtual void setCallback(const std::shared_ptr<ResourcePolicyCallbackInterface>& cb) = 0;
+
+protected:
+    virtual ~ResourcePolicyInterface() = default;
+};
+
+// Interface for notifying the JobScheduler of a change in resource status.
+class ResourcePolicyCallbackInterface {
+public:
+    // Called when codec resources become available. The scheduler may use this
+    // as a signal to attempt restart transcoding jobs that were previously
+    // paused due to temporary resource loss.
+    virtual void onResourceAvailable() = 0;
+
+protected:
+    virtual ~ResourcePolicyCallbackInterface() = default;
+};
+
+}  // namespace android
+#endif  // ANDROID_MEDIA_RESOURCE_POLICY_INTERFACE_H
diff --git a/media/libmediatranscoding/include/media/TranscoderWrapper.h b/media/libmediatranscoding/include/media/TranscoderWrapper.h
index a4c92c5..c956042 100644
--- a/media/libmediatranscoding/include/media/TranscoderWrapper.h
+++ b/media/libmediatranscoding/include/media/TranscoderWrapper.h
@@ -18,6 +18,7 @@
 #define ANDROID_TRANSCODER_WRAPPER_H
 
 #include <android-base/thread_annotations.h>
+#include <media/NdkMediaError.h>
 #include <media/TranscoderInterface.h>
 
 #include <list>
@@ -55,6 +56,7 @@
         ClientIdType clientId;
         JobIdType jobId;
         std::function<void()> runnable;
+        int32_t arg;
     };
     using JobKeyType = std::pair<ClientIdType, JobIdType>;
 
@@ -68,26 +70,27 @@
     ClientIdType mCurrentClientId;
     JobIdType mCurrentJobId;
 
-    static const char* toString(Event::Type type);
+    static std::string toString(const Event& event);
     void onFinish(ClientIdType clientId, JobIdType jobId);
-    void onError(ClientIdType clientId, JobIdType jobId, TranscodingErrorCode error);
+    void onError(ClientIdType clientId, JobIdType jobId, media_status_t status);
     void onProgress(ClientIdType clientId, JobIdType jobId, int32_t progress);
 
-    TranscodingErrorCode handleStart(ClientIdType clientId, JobIdType jobId,
-                                     const TranscodingRequestParcel& request,
-                                     const std::shared_ptr<ITranscodingClientCallback>& callback);
-    TranscodingErrorCode handlePause(ClientIdType clientId, JobIdType jobId);
-    TranscodingErrorCode handleResume(ClientIdType clientId, JobIdType jobId,
-                                      const TranscodingRequestParcel& request,
-                                      const std::shared_ptr<ITranscodingClientCallback>& callback);
-    TranscodingErrorCode setupTranscoder(
-            ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
-            const std::shared_ptr<ITranscodingClientCallback>& callback,
-            const std::shared_ptr<const Parcel>& pausedState = nullptr);
+    media_status_t handleStart(ClientIdType clientId, JobIdType jobId,
+                               const TranscodingRequestParcel& request,
+                               const std::shared_ptr<ITranscodingClientCallback>& callback);
+    media_status_t handlePause(ClientIdType clientId, JobIdType jobId);
+    media_status_t handleResume(ClientIdType clientId, JobIdType jobId,
+                                const TranscodingRequestParcel& request,
+                                const std::shared_ptr<ITranscodingClientCallback>& callback);
+    media_status_t setupTranscoder(ClientIdType clientId, JobIdType jobId,
+                                   const TranscodingRequestParcel& request,
+                                   const std::shared_ptr<ITranscodingClientCallback>& callback,
+                                   const std::shared_ptr<const Parcel>& pausedState = nullptr);
 
     void cleanup();
+    void reportError(ClientIdType clientId, JobIdType jobId, media_status_t err);
     void queueEvent(Event::Type type, ClientIdType clientId, JobIdType jobId,
-                    const std::function<void()> runnable);
+                    const std::function<void()> runnable, int32_t arg = 0);
     void threadLoop();
 };
 
diff --git a/media/libmediatranscoding/include/media/TranscodingJobScheduler.h b/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
index 5ccadad..8f5e2aa 100644
--- a/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
+++ b/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
@@ -18,6 +18,7 @@
 #define ANDROID_MEDIA_TRANSCODING_JOB_SCHEDULER_H
 
 #include <aidl/android/media/TranscodingJobPriority.h>
+#include <media/ResourcePolicyInterface.h>
 #include <media/SchedulerClientInterface.h>
 #include <media/TranscoderInterface.h>
 #include <media/TranscodingRequest.h>
@@ -34,7 +35,8 @@
 
 class TranscodingJobScheduler : public UidPolicyCallbackInterface,
                                 public SchedulerClientInterface,
-                                public TranscoderCallbackInterface {
+                                public TranscoderCallbackInterface,
+                                public ResourcePolicyCallbackInterface {
 public:
     virtual ~TranscodingJobScheduler();
 
@@ -58,9 +60,12 @@
 
     // UidPolicyCallbackInterface
     void onTopUidsChanged(const std::unordered_set<uid_t>& uids) override;
-    void onResourceAvailable() override;
     // ~UidPolicyCallbackInterface
 
+    // ResourcePolicyCallbackInterface
+    void onResourceAvailable() override;
+    // ~ResourcePolicyCallbackInterface
+
 private:
     friend class MediaTranscodingService;
     friend class TranscodingJobSchedulerTest;
@@ -96,13 +101,15 @@
 
     std::shared_ptr<TranscoderInterface> mTranscoder;
     std::shared_ptr<UidPolicyInterface> mUidPolicy;
+    std::shared_ptr<ResourcePolicyInterface> mResourcePolicy;
 
     Job* mCurrentJob;
     bool mResourceLost;
 
     // Only allow MediaTranscodingService and unit tests to instantiate.
     TranscodingJobScheduler(const std::shared_ptr<TranscoderInterface>& transcoder,
-                            const std::shared_ptr<UidPolicyInterface>& uidPolicy);
+                            const std::shared_ptr<UidPolicyInterface>& uidPolicy,
+                            const std::shared_ptr<ResourcePolicyInterface>& resourcePolicy);
 
     Job* getTopJob_l();
     void updateCurrentJob_l();
diff --git a/media/libmediatranscoding/include/media/TranscodingResourcePolicy.h b/media/libmediatranscoding/include/media/TranscodingResourcePolicy.h
new file mode 100644
index 0000000..0836eda
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscodingResourcePolicy.h
@@ -0,0 +1,65 @@
+/*
+ * 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_RESOURCE_POLICY_H
+#define ANDROID_MEDIA_TRANSCODING_RESOURCE_POLICY_H
+
+#include <android/binder_auto_utils.h>
+#include <media/ResourcePolicyInterface.h>
+#include <utils/Condition.h>
+
+#include <mutex>
+namespace aidl {
+namespace android {
+namespace media {
+class IResourceObserverService;
+}
+}  // namespace android
+}  // namespace aidl
+
+namespace android {
+
+using ::aidl::android::media::IResourceObserverService;
+
+class TranscodingResourcePolicy : public ResourcePolicyInterface {
+public:
+    explicit TranscodingResourcePolicy();
+    ~TranscodingResourcePolicy();
+
+    void setCallback(const std::shared_ptr<ResourcePolicyCallbackInterface>& cb) override;
+
+private:
+    struct ResourceObserver;
+    mutable std::mutex mRegisteredLock;
+    bool mRegistered GUARDED_BY(mRegisteredLock);
+    std::shared_ptr<IResourceObserverService> mService GUARDED_BY(mRegisteredLock);
+    std::shared_ptr<ResourceObserver> mObserver;
+
+    mutable std::mutex mCallbackLock;
+    std::weak_ptr<ResourcePolicyCallbackInterface> mResourcePolicyCallback
+            GUARDED_BY(mCallbackLock);
+
+    ::ndk::ScopedAIBinder_DeathRecipient mDeathRecipient;
+
+    static void BinderDiedCallback(void* cookie);
+
+    void registerSelf();
+    void unregisterSelf();
+    void onResourceAvailable();
+};  // class TranscodingUidPolicy
+
+}  // namespace android
+#endif  // ANDROID_MEDIA_TRANSCODING_RESOURCE_POLICY_H
diff --git a/media/libmediatranscoding/include/media/UidPolicyInterface.h b/media/libmediatranscoding/include/media/UidPolicyInterface.h
index dc28027..f88c1ed 100644
--- a/media/libmediatranscoding/include/media/UidPolicyInterface.h
+++ b/media/libmediatranscoding/include/media/UidPolicyInterface.h
@@ -41,19 +41,13 @@
     virtual ~UidPolicyInterface() = default;
 };
 
-// Interface for notifying the scheduler of a change in uid states or
-// transcoding resource availability.
+// Interface for notifying the scheduler of a change in uid states.
 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;
 };
diff --git a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
index d21b595..9b9df87 100644
--- a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
@@ -213,7 +213,8 @@
         ALOGI("TranscodingJobSchedulerTest set up");
         mTranscoder.reset(new TestTranscoder());
         mUidPolicy.reset(new TestUidPolicy());
-        mScheduler.reset(new TranscodingJobScheduler(mTranscoder, mUidPolicy));
+        mScheduler.reset(
+                new TranscodingJobScheduler(mTranscoder, mUidPolicy, nullptr /*resourcePolicy*/));
         mUidPolicy->setCallback(mScheduler);
 
         // Set priority only, ignore other fields for now.
diff --git a/services/mediatranscoding/MediaTranscodingService.cpp b/services/mediatranscoding/MediaTranscodingService.cpp
index ef7d6d2..977995a 100644
--- a/services/mediatranscoding/MediaTranscodingService.cpp
+++ b/services/mediatranscoding/MediaTranscodingService.cpp
@@ -25,6 +25,7 @@
 #include <media/TranscoderWrapper.h>
 #include <media/TranscodingClientManager.h>
 #include <media/TranscodingJobScheduler.h>
+#include <media/TranscodingResourcePolicy.h>
 #include <media/TranscodingUidPolicy.h>
 #include <private/android_filesystem_config.h>
 #include <utils/Log.h>
@@ -57,11 +58,13 @@
 MediaTranscodingService::MediaTranscodingService(
         const std::shared_ptr<TranscoderInterface>& transcoder)
       : mUidPolicy(new TranscodingUidPolicy()),
-        mJobScheduler(new TranscodingJobScheduler(transcoder, mUidPolicy)),
+        mResourcePolicy(new TranscodingResourcePolicy()),
+        mJobScheduler(new TranscodingJobScheduler(transcoder, mUidPolicy, mResourcePolicy)),
         mClientManager(new TranscodingClientManager(mJobScheduler)) {
     ALOGV("MediaTranscodingService is created");
     transcoder->setCallback(mJobScheduler);
     mUidPolicy->setCallback(mJobScheduler);
+    mResourcePolicy->setCallback(mJobScheduler);
 }
 
 MediaTranscodingService::~MediaTranscodingService() {
diff --git a/services/mediatranscoding/MediaTranscodingService.h b/services/mediatranscoding/MediaTranscodingService.h
index 505239c..2ba3d55 100644
--- a/services/mediatranscoding/MediaTranscodingService.h
+++ b/services/mediatranscoding/MediaTranscodingService.h
@@ -32,6 +32,7 @@
 class TranscodingJobScheduler;
 class TranscoderInterface;
 class UidPolicyInterface;
+class ResourcePolicyInterface;
 
 class MediaTranscodingService : public BnMediaTranscodingService {
 public:
@@ -60,6 +61,7 @@
     mutable std::mutex mServiceLock;
 
     std::shared_ptr<UidPolicyInterface> mUidPolicy;
+    std::shared_ptr<ResourcePolicyInterface> mResourcePolicy;
     std::shared_ptr<TranscodingJobScheduler> mJobScheduler;
     std::shared_ptr<TranscodingClientManager> mClientManager;
 };
diff --git a/services/mediatranscoding/tests/Android.bp b/services/mediatranscoding/tests/Android.bp
index 364a198..6497685 100644
--- a/services/mediatranscoding/tests/Android.bp
+++ b/services/mediatranscoding/tests/Android.bp
@@ -25,14 +25,6 @@
     static_libs: [
         "mediatranscoding_aidl_interface-ndk_platform",
     ],
-}
-
-// MediaTranscodingService unit test using simulated transcoder
-cc_test {
-    name: "mediatranscodingservice_simulated_tests",
-    defaults: ["mediatranscodingservice_test_defaults"],
-
-    srcs: ["mediatranscodingservice_simulated_tests.cpp"],
 
     required: [
         "TranscodingUidPolicy_TestAppA",
@@ -41,6 +33,14 @@
     ],
 }
 
+// MediaTranscodingService unit test using simulated transcoder
+cc_test {
+    name: "mediatranscodingservice_simulated_tests",
+    defaults: ["mediatranscodingservice_test_defaults"],
+
+    srcs: ["mediatranscodingservice_simulated_tests.cpp"],
+}
+
 // MediaTranscodingService unit test using real transcoder
 cc_test {
     name: "mediatranscodingservice_real_tests",
@@ -48,3 +48,11 @@
 
     srcs: ["mediatranscodingservice_real_tests.cpp"],
 }
+
+// MediaTranscodingService unit test related to resource management
+cc_test {
+    name: "mediatranscodingservice_resource_tests",
+    defaults: ["mediatranscodingservice_test_defaults"],
+
+    srcs: ["mediatranscodingservice_resource_tests.cpp"],
+}
diff --git a/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h b/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h
index 53fd7ec..b325c5c 100644
--- a/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h
+++ b/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h
@@ -168,6 +168,32 @@
         return mPoppedEvent;
     }
 
+    bool waitForSpecificEventAndPop(const Event& target, std::list<Event>* outEvents,
+                                    int64_t timeoutUs = 0) {
+        std::unique_lock lock(mLock);
+
+        auto startTime = std::chrono::system_clock::now();
+
+        std::list<Event>::iterator it;
+        while (((it = std::find(mEventQueue.begin(), mEventQueue.end(), target)) ==
+                mEventQueue.end()) &&
+               timeoutUs > 0) {
+            std::cv_status status = mCondition.wait_for(lock, std::chrono::microseconds(timeoutUs));
+            if (status == std::cv_status::timeout) {
+                break;
+            }
+            std::chrono::microseconds elapsedTime = std::chrono::system_clock::now() - startTime;
+            timeoutUs -= elapsedTime.count();
+        }
+
+        if (it == mEventQueue.end()) {
+            return false;
+        }
+        *outEvents = std::list<Event>(mEventQueue.begin(), std::next(it));
+        mEventQueue.erase(mEventQueue.begin(), std::next(it));
+        return true;
+    }
+
     // Push 1 event to back.
     void append(const Event& event,
                 const TranscodingErrorCode err = TranscodingErrorCode::kNoError) {
@@ -186,7 +212,7 @@
         mUpdateCount++;
     }
 
-    int getUpdateCount(int *lastProgress) {
+    int getUpdateCount(int* lastProgress) {
         std::unique_lock lock(mLock);
         *lastProgress = mLastProgress;
         return mUpdateCount;
diff --git a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppA.xml b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppA.xml
index 91c14a5..0dff171 100644
--- a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppA.xml
+++ b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppA.xml
@@ -28,6 +28,14 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity android:name="com.android.tests.transcoding.ResourcePolicyTestActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
 
diff --git a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/MainActivity.java b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/MainActivity.java
index 7295073..b79164d 100644
--- a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/MainActivity.java
+++ b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/MainActivity.java
@@ -46,7 +46,7 @@
     // Called before subsequent visible lifetimes
     // for an activity process.
     @Override
-    public void onRestart(){
+    public void onRestart() {
         super.onRestart();
         // Load changes knowing that the Activity has already
         // been visible within this process.
@@ -54,14 +54,14 @@
 
     // Called at the start of the visible lifetime.
     @Override
-    public void onStart(){
+    public void onStart() {
         super.onStart();
         // Apply any required UI change now that the Activity is visible.
     }
 
     // Called at the start of the active lifetime.
     @Override
-    public void onResume(){
+    public void onResume() {
         super.onResume();
         // Resume any paused UI updates, threads, or processes required
         // by the Activity but suspended when it was inactive.
@@ -80,7 +80,7 @@
 
     // Called at the end of the active lifetime.
     @Override
-    public void onPause(){
+    public void onPause() {
         // Suspend UI updates, threads, or CPU intensive processes
         // that don't need to be updated when the Activity isn't
         // the active foreground Activity.
@@ -89,7 +89,7 @@
 
     // Called at the end of the visible lifetime.
     @Override
-    public void onStop(){
+    public void onStop() {
         // Suspend remaining UI updates, threads, or processing
         // that aren't required when the Activity isn't visible.
         // Persist all edits or state changes
@@ -99,10 +99,9 @@
 
     // Sometimes called at the end of the full lifetime.
     @Override
-    public void onDestroy(){
+    public void onDestroy() {
         // Clean up any resources including ending threads,
         // closing database connections etc.
         super.onDestroy();
     }
-
 }
diff --git a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/ResourcePolicyTestActivity.java b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/ResourcePolicyTestActivity.java
new file mode 100644
index 0000000..c9e2ddb
--- /dev/null
+++ b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/ResourcePolicyTestActivity.java
@@ -0,0 +1,272 @@
+/*
+ * 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.
+ */
+
+package com.android.tests.transcoding;
+
+import android.app.Activity;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.os.Bundle;
+import android.util.Log;
+import java.io.IOException;
+import java.util.Vector;
+
+public class ResourcePolicyTestActivity extends Activity {
+    public static final int TYPE_NONSECURE = 0;
+    public static final int TYPE_SECURE = 1;
+    public static final int TYPE_MIX = 2;
+
+    protected String TAG;
+    private static final int FRAME_RATE = 10;
+    private static final int IFRAME_INTERVAL = 10; // 10 seconds between I-frames
+    private static final String MIME = MediaFormat.MIMETYPE_VIDEO_AVC;
+    private static final int TIMEOUT_MS = 5000;
+
+    private Vector<MediaCodec> mCodecs = new Vector<MediaCodec>();
+
+    private class TestCodecCallback extends MediaCodec.Callback {
+        @Override
+        public void onInputBufferAvailable(MediaCodec codec, int index) {
+            Log.d(TAG, "onInputBufferAvailable " + codec.toString());
+        }
+
+        @Override
+        public void onOutputBufferAvailable(
+                MediaCodec codec, int index, MediaCodec.BufferInfo info) {
+            Log.d(TAG, "onOutputBufferAvailable " + codec.toString());
+        }
+
+        @Override
+        public void onError(MediaCodec codec, MediaCodec.CodecException e) {
+            Log.d(TAG, "onError " + codec.toString() + " errorCode " + e.getErrorCode());
+        }
+
+        @Override
+        public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+            Log.d(TAG, "onOutputFormatChanged " + codec.toString());
+        }
+    }
+
+    private MediaCodec.Callback mCallback = new TestCodecCallback();
+
+    private MediaFormat getTestFormat(CodecCapabilities caps, boolean securePlayback) {
+        VideoCapabilities vcaps = caps.getVideoCapabilities();
+        int width = vcaps.getSupportedWidths().getLower();
+        int height = vcaps.getSupportedHeightsFor(width).getLower();
+        int bitrate = vcaps.getBitrateRange().getLower();
+
+        MediaFormat format = MediaFormat.createVideoFormat(MIME, width, height);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+        format.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, securePlayback);
+        return format;
+    }
+
+    private MediaCodecInfo getTestCodecInfo(boolean securePlayback) {
+        // Use avc decoder for testing.
+        boolean isEncoder = false;
+
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (info.isEncoder() != isEncoder) {
+                continue;
+            }
+            CodecCapabilities caps;
+            try {
+                caps = info.getCapabilitiesForType(MIME);
+                boolean securePlaybackSupported =
+                        caps.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback);
+                boolean securePlaybackRequired =
+                        caps.isFeatureRequired(CodecCapabilities.FEATURE_SecurePlayback);
+                if ((securePlayback && securePlaybackSupported)
+                        || (!securePlayback && !securePlaybackRequired)) {
+                    Log.d(TAG, "securePlayback " + securePlayback + " will use " + info.getName());
+                } else {
+                    Log.d(TAG, "securePlayback " + securePlayback + " skip " + info.getName());
+                    continue;
+                }
+            } catch (IllegalArgumentException e) {
+                // mime is not supported
+                continue;
+            }
+            return info;
+        }
+
+        return null;
+    }
+
+    protected int allocateCodecs(int max) {
+        Bundle extras = getIntent().getExtras();
+        int type = TYPE_NONSECURE;
+        if (extras != null) {
+            type = extras.getInt("test-type", type);
+            Log.d(TAG, "type is: " + type);
+        }
+
+        boolean shouldSkip = false;
+        boolean securePlayback;
+        if (type == TYPE_NONSECURE || type == TYPE_MIX) {
+            securePlayback = false;
+            MediaCodecInfo info = getTestCodecInfo(securePlayback);
+            if (info != null) {
+                allocateCodecs(max, info, securePlayback);
+            } else {
+                shouldSkip = true;
+            }
+        }
+
+        if (!shouldSkip) {
+            if (type == TYPE_SECURE || type == TYPE_MIX) {
+                securePlayback = true;
+                MediaCodecInfo info = getTestCodecInfo(securePlayback);
+                if (info != null) {
+                    allocateCodecs(max, info, securePlayback);
+                } else {
+                    shouldSkip = true;
+                }
+            }
+        }
+
+        if (shouldSkip) {
+            Log.d(TAG, "test skipped as there's no supported codec.");
+            finishWithResult(RESULT_OK);
+        }
+
+        Log.d(TAG, "allocateCodecs returned " + mCodecs.size());
+        return mCodecs.size();
+    }
+
+    protected void allocateCodecs(int max, MediaCodecInfo info, boolean securePlayback) {
+        String name = info.getName();
+        CodecCapabilities caps = info.getCapabilitiesForType(MIME);
+        MediaFormat format = getTestFormat(caps, securePlayback);
+        MediaCodec codec = null;
+        for (int i = mCodecs.size(); i < max; ++i) {
+            try {
+                Log.d(TAG, "Create codec " + name + " #" + i);
+                codec = MediaCodec.createByCodecName(name);
+                codec.setCallback(mCallback);
+                Log.d(TAG, "Configure codec " + format);
+                codec.configure(format, null, null, 0);
+                Log.d(TAG, "Start codec " + format);
+                codec.start();
+                mCodecs.add(codec);
+                codec = null;
+            } catch (IllegalArgumentException e) {
+                Log.d(TAG, "IllegalArgumentException " + e.getMessage());
+                break;
+            } catch (IOException e) {
+                Log.d(TAG, "IOException " + e.getMessage());
+                break;
+            } catch (MediaCodec.CodecException e) {
+                Log.d(TAG, "CodecException 0x" + Integer.toHexString(e.getErrorCode()));
+                break;
+            } finally {
+                if (codec != null) {
+                    Log.d(TAG, "release codec");
+                    codec.release();
+                    codec = null;
+                }
+            }
+        }
+    }
+
+    protected void finishWithResult(int result) {
+        for (int i = 0; i < mCodecs.size(); ++i) {
+            Log.d(TAG, "release codec #" + i);
+            mCodecs.get(i).release();
+        }
+        mCodecs.clear();
+        setResult(result);
+        finish();
+        Log.d(TAG, "activity finished");
+    }
+
+    private void doUseCodecs() {
+        int current = 0;
+        try {
+            for (current = 0; current < mCodecs.size(); ++current) {
+                mCodecs.get(current).getName();
+            }
+        } catch (MediaCodec.CodecException e) {
+            Log.d(TAG, "useCodecs got CodecException 0x" + Integer.toHexString(e.getErrorCode()));
+            if (e.getErrorCode() == MediaCodec.CodecException.ERROR_RECLAIMED) {
+                Log.d(TAG, "Remove codec " + current + " from the list");
+                mCodecs.get(current).release();
+                mCodecs.remove(current);
+                mGotReclaimedException = true;
+                mUseCodecs = false;
+            }
+            return;
+        }
+    }
+
+    private Thread mWorkerThread;
+    private volatile boolean mUseCodecs = true;
+    private volatile boolean mGotReclaimedException = false;
+    protected void useCodecs() {
+        mWorkerThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                long start = System.currentTimeMillis();
+                long timeSinceStartedMs = 0;
+                while (mUseCodecs && (timeSinceStartedMs < TIMEOUT_MS)) {
+                    doUseCodecs();
+                    try {
+                        Thread.sleep(50 /* millis */);
+                    } catch (InterruptedException e) {
+                    }
+                    timeSinceStartedMs = System.currentTimeMillis() - start;
+                }
+                if (mGotReclaimedException) {
+                    Log.d(TAG, "Got expected reclaim exception.");
+                }
+                finishWithResult(RESULT_OK);
+            }
+        });
+        mWorkerThread.start();
+    }
+
+    private static final int MAX_INSTANCES = 32;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        TAG = "ResourcePolicyTestActivity";
+
+        Log.d(TAG, "onCreate called.");
+        super.onCreate(savedInstanceState);
+
+        if (allocateCodecs(MAX_INSTANCES) == MAX_INSTANCES) {
+            // haven't reached the limit with MAX_INSTANCES, no need to wait for reclaim exception.
+            //mWaitForReclaim = false;
+            Log.d(TAG, "Didn't hit resource limitation");
+        }
+
+        useCodecs();
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.d(TAG, "onDestroy called.");
+        super.onDestroy();
+    }
+}
diff --git a/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh b/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh
index d66b340..1b42a22 100755
--- a/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh
+++ b/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh
@@ -37,6 +37,11 @@
 #adb shell /data/nativetest64/mediatranscodingservice_real_tests/mediatranscodingservice_real_tests
 adb shell /data/nativetest/mediatranscodingservice_real_tests/mediatranscodingservice_real_tests
 
+echo "[==========] running resource tests"
+adb shell kill -9 `pid media.transcoding`
+#adb shell /data/nativetest64/mediatranscodingservice_resource_tests/mediatranscodingservice_resource_tests
+adb shell /data/nativetest/mediatranscodingservice_resource_tests/mediatranscodingservice_resource_tests
+
 echo "[==========] removing debug properties"
 adb shell setprop debug.transcoding.simulated_transcoder \"\"
 adb shell kill -9 `pid media.transcoding`
diff --git a/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp b/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp
index 1def4b9..45057e2 100644
--- a/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp
+++ b/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp
@@ -45,9 +45,11 @@
 
 class MediaTranscodingServiceRealTest : public MediaTranscodingServiceTestBase {
 public:
-    MediaTranscodingServiceRealTest() {}
+    MediaTranscodingServiceRealTest() { ALOGI("MediaTranscodingServiceResourceTest created"); }
 
-    void deleteFile(const char* path) { unlink(path); }
+    virtual ~MediaTranscodingServiceRealTest() {
+        ALOGI("MediaTranscodingServiceResourceTest destroyed");
+    }
 };
 
 TEST_F(MediaTranscodingServiceRealTest, TestInvalidSource) {
diff --git a/services/mediatranscoding/tests/mediatranscodingservice_resource_tests.cpp b/services/mediatranscoding/tests/mediatranscodingservice_resource_tests.cpp
new file mode 100644
index 0000000..fe2cfa9
--- /dev/null
+++ b/services/mediatranscoding/tests/mediatranscodingservice_resource_tests.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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 MediaTranscodingService.
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaTranscodingServiceRealTest"
+
+#include "MediaTranscodingServiceTestHelper.h"
+
+/*
+ * Tests media transcoding service with real transcoder.
+ *
+ * Uses the same test assets as the MediaTranscoder unit tests. Before running the test,
+ * please make sure to push the test assets to /sdcard:
+ *
+ * adb push $TOP/frameworks/av/media/libmediatranscoding/transcoder/tests/assets /data/local/tmp/TranscodingTestAssets
+ */
+namespace android {
+
+namespace media {
+
+constexpr int64_t kPaddingUs = 400000;
+constexpr int32_t kBitRate = 8 * 1000 * 1000;  // 8Mbs
+
+constexpr const char* kLongSrcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
+
+constexpr const char* kResourcePolicyTestActivity =
+        "/com.android.tests.transcoding.ResourcePolicyTestActivity";
+
+#define OUTPATH(name) "/data/local/tmp/MediaTranscodingService_" #name ".MP4"
+
+class MediaTranscodingServiceResourceTest : public MediaTranscodingServiceTestBase {
+public:
+    MediaTranscodingServiceResourceTest() { ALOGI("MediaTranscodingServiceResourceTest created"); }
+
+    virtual ~MediaTranscodingServiceResourceTest() {
+        ALOGI("MediaTranscodingServiceResourceTest destroyed");
+    }
+};
+
+/**
+ * Basic testing for handling resource lost.
+ *
+ * This test starts a transcoding job (that's somewhat long and takes several seconds),
+ * then launches an activity that allocates video codec instances until it hits insufficient
+ * resource error. Because the activity is running in foreground,
+ * ResourceManager would reclaim codecs from transcoding service which should
+ * cause the job to be paused. The activity will hold the codecs for a few seconds
+ * before releasing them, and the transcoding service should be able to resume
+ * and complete the job.
+ */
+TEST_F(MediaTranscodingServiceResourceTest, TestResourceLost) {
+    ALOGD("TestResourceLost starting...");
+
+    EXPECT_TRUE(ShellHelper::RunCmd("input keyevent KEYCODE_WAKEUP"));
+    EXPECT_TRUE(ShellHelper::RunCmd("wm dismiss-keyguard"));
+    EXPECT_TRUE(ShellHelper::Stop(kClientPackageA));
+
+    registerMultipleClients();
+
+    const char* srcPath0 = kLongSrcPath;
+    const char* dstPath0 = OUTPATH(TestPauseResumeMultiClients_Client0);
+    deleteFile(dstPath0);
+
+    ALOGD("Moving app A to top...");
+    EXPECT_TRUE(ShellHelper::Start(kClientPackageA, kTestActivityName));
+
+    // Submit job to Client1.
+    ALOGD("Submitting job to client1 (app A) ...");
+    EXPECT_TRUE(submit(mClient1, 0, srcPath0, dstPath0, TranscodingJobPriority::kNormal, kBitRate));
+
+    // Client1's job should start immediately.
+    EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+
+    // Launch ResourcePolicyTestActivity, which will try to allocate up to 32
+    // instances, which should trigger insufficient resources on most devices.
+    // (Note that it's possible that the device supports a very high number of
+    // resource instances, in which case we'll simply require that the job completes.)
+    ALOGD("Launch ResourcePolicyTestActivity...");
+    EXPECT_TRUE(ShellHelper::Start(kClientPackageA, kResourcePolicyTestActivity));
+
+    // The basic requirement is that the job should complete. Wait for finish
+    // event to come and pop up all events received.
+    std::list<EventTracker::Event> events;
+    EXPECT_TRUE(mClientCallback1->waitForSpecificEventAndPop(EventTracker::Finished(CLIENT(1), 0),
+                                                             &events, 15000000));
+
+    // If there is only 1 event, it must be finish (otherwise waitForSpecificEventAndPop
+    // woudldn't pop up anything), and we're ok.
+    //
+    // TODO: If there is only 1 event (finish), and no pause/resume happened, we need
+    // to verify that the ResourcePolicyTestActivity actually was able to allocate
+    // all 32 instances without hitting insufficient resources. Otherwise, it could
+    // be that ResourceManager was not able to reclaim codecs from the transcoding
+    // service at all, which means the resource management is broken.
+    if (events.size() > 1) {
+        EXPECT_TRUE(events.size() >= 3);
+        size_t i = 0;
+        for (auto& event : events) {
+            if (i == 0) {
+                EXPECT_EQ(event, EventTracker::Pause(CLIENT(1), 0));
+            } else if (i == events.size() - 2) {
+                EXPECT_EQ(event, EventTracker::Resume(CLIENT(1), 0));
+            } else if (i == events.size() - 1) {
+                EXPECT_EQ(event, EventTracker::Finished(CLIENT(1), 0));
+            } else {
+                EXPECT_TRUE(event == EventTracker::Pause(CLIENT(1), 0) ||
+                            event == EventTracker::Resume(CLIENT(1), 0));
+            }
+            i++;
+        }
+    }
+
+    unregisterMultipleClients();
+
+    EXPECT_TRUE(ShellHelper::Stop(kClientPackageA));
+}
+
+}  // namespace media
+}  // namespace android
diff --git a/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp b/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp
index 42b5877..e9fedd9 100644
--- a/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp
+++ b/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp
@@ -59,7 +59,11 @@
 
 class MediaTranscodingServiceSimulatedTest : public MediaTranscodingServiceTestBase {
 public:
-    MediaTranscodingServiceSimulatedTest() {}
+    MediaTranscodingServiceSimulatedTest() { ALOGI("MediaTranscodingServiceResourceTest created"); }
+
+    virtual ~MediaTranscodingServiceSimulatedTest() {
+        ALOGI("MediaTranscodingServiceResourceTest destroyed");
+    }
 };
 
 TEST_F(MediaTranscodingServiceSimulatedTest, TestRegisterNullClient) {