transcoding: add job scheduler and unit tests

bug: 145233472

Change-Id: Id0244ff553e9fa963ccc7623cc6198dfd9db2564
diff --git a/media/libmediatranscoding/Android.bp b/media/libmediatranscoding/Android.bp
index 0f413ef..6d8c03d 100644
--- a/media/libmediatranscoding/Android.bp
+++ b/media/libmediatranscoding/Android.bp
@@ -21,7 +21,7 @@
     srcs: [
         "aidl/android/media/IMediaTranscodingService.aidl",
         "aidl/android/media/ITranscodingClient.aidl",
-        "aidl/android/media/ITranscodingClientListener.aidl",
+        "aidl/android/media/ITranscodingClientCallback.aidl",
         "aidl/android/media/TranscodingErrorCode.aidl",
         "aidl/android/media/TranscodingJobPriority.aidl",
         "aidl/android/media/TranscodingType.aidl",
@@ -36,7 +36,8 @@
     name: "libmediatranscoding",
 
     srcs: [
-        "TranscodingClientManager.cpp"
+        "TranscodingClientManager.cpp",
+        "TranscodingJobScheduler.cpp",
     ],
 
     shared_libs: [
diff --git a/media/libmediatranscoding/TranscodingClientManager.cpp b/media/libmediatranscoding/TranscodingClientManager.cpp
index 0f99983..b05e3a6 100644
--- a/media/libmediatranscoding/TranscodingClientManager.cpp
+++ b/media/libmediatranscoding/TranscodingClientManager.cpp
@@ -21,6 +21,7 @@
 #include <android/binder_ibinder.h>
 #include <inttypes.h>
 #include <media/TranscodingClientManager.h>
+#include <media/TranscodingRequest.h>
 #include <utils/Log.h>
 
 namespace android {
@@ -37,71 +38,101 @@
  * ClientImpl implements a single client and contains all its information.
  */
 struct TranscodingClientManager::ClientImpl : public BnTranscodingClient {
-    /* The remote client listener that this ClientInfo is associated with.
+    /* The remote client callback that this ClientInfo is associated with.
      * Once the ClientInfo is created, we hold an SpAIBinder so that the binder
      * object doesn't get created again, otherwise the binder object pointer
      * may not be unique.
      */
-    SpAIBinder mClientListener;
+    SpAIBinder 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.
      */
     ClientIdType mClientId;
-    int32_t mClientPid;
-    int32_t mClientUid;
+    pid_t mClientPid;
+    uid_t mClientUid;
     std::string mClientName;
     std::string mClientOpPackageName;
+
+    // Next jobId to assign
+    std::atomic<std::int32_t> mNextJobId;
+    // Pointer to the client manager for this client
     TranscodingClientManager* mOwner;
 
-    ClientImpl(const std::shared_ptr<ITranscodingClientListener>& listener,
-               int32_t pid, int32_t uid,
-               const std::string& clientName,
-               const std::string& opPackageName,
+    ClientImpl(const std::shared_ptr<ITranscodingClientCallback>& callback, pid_t pid,
+            uid_t uid, const std::string& clientName, const std::string& opPackageName,
                TranscodingClientManager* owner);
 
     Status submitRequest(const TranscodingRequestParcel& /*in_request*/,
-            TranscodingJobParcel* /*out_job*/, int32_t* /*_aidl_return*/) override;
+                         TranscodingJobParcel* /*out_job*/, bool* /*_aidl_return*/) override;
 
     Status cancelJob(int32_t /*in_jobId*/, bool* /*_aidl_return*/) override;
 
-    Status getJobWithId(int32_t /*in_jobId*/,
-            TranscodingJobParcel* /*out_job*/, bool* /*_aidl_return*/) override;
+    Status getJobWithId(int32_t /*in_jobId*/, TranscodingJobParcel* /*out_job*/,
+                        bool* /*_aidl_return*/) override;
 
     Status unregister() override;
 };
 
 TranscodingClientManager::ClientImpl::ClientImpl(
-        const std::shared_ptr<ITranscodingClientListener>& listener,
-        int32_t pid, int32_t uid,
-        const std::string& clientName,
-        const std::string& opPackageName,
+        const std::shared_ptr<ITranscodingClientCallback>& callback, pid_t pid, uid_t uid,
+        const std::string& clientName, const std::string& opPackageName,
         TranscodingClientManager* owner)
-: mClientListener((listener != nullptr) ? listener->asBinder() : nullptr),
-  mClientId((int64_t)mClientListener.get()),
-  mClientPid(pid),
-  mClientUid(uid),
-  mClientName(clientName),
-  mClientOpPackageName(opPackageName),
-  mOwner(owner) {}
+      : mClientCallback((callback != nullptr) ? callback->asBinder() : nullptr),
+        mClientId((int64_t)mClientCallback.get()),
+        mClientPid(pid),
+        mClientUid(uid),
+        mClientName(clientName),
+        mClientOpPackageName(opPackageName),
+        mNextJobId(0),
+        mOwner(owner) {}
 
 Status TranscodingClientManager::ClientImpl::submitRequest(
-        const TranscodingRequestParcel& /*in_request*/,
-        TranscodingJobParcel* /*out_job*/, int32_t* /*_aidl_return*/) {
+        const TranscodingRequestParcel& in_request, TranscodingJobParcel* out_job,
+        bool* _aidl_return) {
+    if (in_request.fileName.empty()) {
+        // This is the only error we check for now.
+        *_aidl_return = false;
+        return Status::ok();
+    }
+
+    int32_t jobId = mNextJobId.fetch_add(1);
+
+    *_aidl_return =
+            mOwner->mJobScheduler->submit(mClientId, jobId, mClientPid, in_request,
+                                          ITranscodingClientCallback::fromBinder(mClientCallback));
+
+    if (*_aidl_return) {
+        out_job->jobId = jobId;
+
+        // TODO(chz): is some of this coming from JobScheduler?
+        *(TranscodingRequest*)&out_job->request = in_request;
+        out_job->awaitNumberOfJobs = 0;
+    }
     return Status::ok();
 }
 
-Status TranscodingClientManager::ClientImpl::cancelJob(
-        int32_t /*in_jobId*/, bool* /*_aidl_return*/) {
+Status TranscodingClientManager::ClientImpl::cancelJob(int32_t in_jobId, bool* _aidl_return) {
+    *_aidl_return = mOwner->mJobScheduler->cancel(mClientId, in_jobId);
     return Status::ok();
 }
 
-Status TranscodingClientManager::ClientImpl::getJobWithId(int32_t /*in_jobId*/,
-        TranscodingJobParcel* /*out_job*/, bool* /*_aidl_return*/) {
+Status TranscodingClientManager::ClientImpl::getJobWithId(int32_t in_jobId,
+                                                          TranscodingJobParcel* out_job,
+                                                          bool* _aidl_return) {
+    *_aidl_return = mOwner->mJobScheduler->getJob(mClientId, in_jobId, &out_job->request);
+
+    if (*_aidl_return) {
+        out_job->jobId = in_jobId;
+        out_job->awaitNumberOfJobs = 0;
+    }
     return Status::ok();
 }
 
 Status TranscodingClientManager::ClientImpl::unregister() {
+    // TODO(chz): Decide what to do about this client's jobs.
+    // If app crashed, it could be relaunched later. Do we want to keep the
+    // jobs around for that?
     mOwner->removeClient(mClientId);
     return Status::ok();
 }
@@ -109,22 +140,15 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 // static
-TranscodingClientManager& TranscodingClientManager::getInstance() {
-    static TranscodingClientManager gInstance{};
-    return gInstance;
-}
-
-// static
 void TranscodingClientManager::BinderDiedCallback(void* cookie) {
-    ClientIdType clientId = static_cast<ClientIdType>(reinterpret_cast<intptr_t>(cookie));
-    ALOGD("Client %lld is dead", (long long) clientId);
-    // Don't check for pid validity since we know it's already dead.
-    TranscodingClientManager& manager = TranscodingClientManager::getInstance();
-    manager.removeClient(clientId);
+    ClientImpl* client = static_cast<ClientImpl*>(cookie);
+    ALOGD("Client %lld is dead", (long long)client->mClientId);
+    client->unregister();
 }
 
-TranscodingClientManager::TranscodingClientManager()
-    : mDeathRecipient(AIBinder_DeathRecipient_new(BinderDiedCallback)) {
+TranscodingClientManager::TranscodingClientManager(
+        const std::shared_ptr<SchedulerClientInterface>& scheduler)
+      : mDeathRecipient(AIBinder_DeathRecipient_new(BinderDiedCallback)), mJobScheduler(scheduler) {
     ALOGD("TranscodingClientManager started");
 }
 
@@ -151,8 +175,8 @@
     }
 
     for (const auto& iter : mClientIdToClientMap) {
-        snprintf(buffer, SIZE, "    -- Client id: %lld  name: %s\n",
-                (long long)iter.first, iter.second->mClientName.c_str());
+        snprintf(buffer, SIZE, "    -- Client id: %lld  name: %s\n", (long long)iter.first,
+                 iter.second->mClientName.c_str());
         result.append(buffer);
     }
 
@@ -160,22 +184,18 @@
 }
 
 status_t TranscodingClientManager::addClient(
-        const std::shared_ptr<ITranscodingClientListener>& listener,
-        int32_t pid, int32_t uid,
-        const std::string& clientName,
-        const std::string& opPackageName,
+        const std::shared_ptr<ITranscodingClientCallback>& callback, pid_t pid, uid_t uid,
+        const std::string& clientName, const std::string& opPackageName,
         std::shared_ptr<ITranscodingClient>* outClient) {
     // Validate the client.
-    if (listener == nullptr || pid < 0 || uid < 0 ||
-            clientName.empty() || opPackageName.empty()) {
+    if (callback == nullptr || pid < 0 || clientName.empty() || opPackageName.empty()) {
         ALOGE("Invalid client");
         return BAD_VALUE;
     }
 
     // Creates the client and uses its process id as client id.
-    std::shared_ptr<ClientImpl> client =
-            ::ndk::SharedRefBase::make<ClientImpl>(
-                    listener, pid, uid, clientName, opPackageName, this);
+    std::shared_ptr<ClientImpl> client = ::ndk::SharedRefBase::make<ClientImpl>(
+            callback, pid, uid, clientName, opPackageName, this);
 
     std::scoped_lock lock{mLock};
 
@@ -185,14 +205,11 @@
     }
 
     ALOGD("Adding client id %lld, pid %d, uid %d, name %s, package %s",
-            (long long)client->mClientId,
-            client->mClientPid,
-            client->mClientUid,
-            client->mClientName.c_str(),
-            client->mClientOpPackageName.c_str());
+          (long long)client->mClientId, client->mClientPid, client->mClientUid,
+          client->mClientName.c_str(), client->mClientOpPackageName.c_str());
 
-    AIBinder_linkToDeath(client->mClientListener.get(), mDeathRecipient.get(),
-                         reinterpret_cast<void*>(client->mClientId));
+    AIBinder_linkToDeath(client->mClientCallback.get(), mDeathRecipient.get(),
+                         reinterpret_cast<void*>(client.get()));
 
     // Adds the new client to the map.
     mClientIdToClientMap[client->mClientId] = client;
@@ -202,7 +219,6 @@
     return OK;
 }
 
-
 status_t TranscodingClientManager::removeClient(ClientIdType clientId) {
     ALOGD("Removing client id %lld", (long long)clientId);
     std::scoped_lock lock{mLock};
@@ -214,12 +230,12 @@
         return INVALID_OPERATION;
     }
 
-    SpAIBinder listener = it->second->mClientListener;
+    SpAIBinder callback = it->second->mClientCallback;
 
     // Check if the client still live. If alive, unlink the death.
-    if (listener.get() != nullptr) {
-        AIBinder_unlinkToDeath(listener.get(), mDeathRecipient.get(),
-                               reinterpret_cast<void*>(clientId));
+    if (callback.get() != nullptr) {
+        AIBinder_unlinkToDeath(callback.get(), mDeathRecipient.get(),
+                               reinterpret_cast<void*>(it->second.get()));
     }
 
     // Erase the entry.
diff --git a/media/libmediatranscoding/TranscodingJobScheduler.cpp b/media/libmediatranscoding/TranscodingJobScheduler.cpp
new file mode 100644
index 0000000..9dd070c
--- /dev/null
+++ b/media/libmediatranscoding/TranscodingJobScheduler.cpp
@@ -0,0 +1,369 @@
+/*
+ * 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 "TranscodingJobScheduler"
+
+#define VALIDATE_STATE 1
+
+#include <inttypes.h>
+#include <media/TranscodingJobScheduler.h>
+#include <utils/Log.h>
+
+#include <utility>
+
+namespace android {
+
+constexpr static pid_t OFFLINE_PID = -1;
+
+//static
+String8 TranscodingJobScheduler::jobToString(const JobKeyType& jobKey) {
+    return String8::format("{client:%lld, job:%d}", (long long)jobKey.first, jobKey.second);
+}
+
+TranscodingJobScheduler::TranscodingJobScheduler(
+        const std::shared_ptr<TranscoderInterface>& transcoder,
+        const std::shared_ptr<ProcessInfoInterface>& procInfo)
+      : mTranscoder(transcoder), mProcInfo(procInfo), 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());
+}
+
+TranscodingJobScheduler::~TranscodingJobScheduler() {}
+
+TranscodingJobScheduler::Job* TranscodingJobScheduler::getTopJob_l() {
+    if (mJobMap.empty()) {
+        return nullptr;
+    }
+    pid_t topPid = *mPidSortedList.begin();
+    JobKeyType topJobKey = *mJobQueues[topPid].begin();
+    return &mJobMap[topJobKey];
+}
+
+void TranscodingJobScheduler::updateCurrentJob_l() {
+    Job* topJob = getTopJob_l();
+    Job* curJob = mCurrentJob;
+    ALOGV("updateCurrentJob: topJob is %s, curJob is %s",
+          topJob == nullptr ? "null" : jobToString(topJob->key).c_str(),
+          curJob == nullptr ? "null" : jobToString(curJob->key).c_str());
+
+    // If we found a topJob that should be run, and it's not already running,
+    // take some actions to ensure it's running.
+    if (topJob != nullptr && (topJob != curJob || topJob->state != Job::RUNNING)) {
+        // If another job is currently running, pause it first.
+        if (curJob != nullptr && curJob->state == Job::RUNNING) {
+            mTranscoder->pause(curJob->key.first, curJob->key.second);
+            curJob->state = Job::PAUSED;
+        }
+        // If we are not experiencing resource loss, we can start or resume
+        // the topJob now.
+        if (!mResourceLost) {
+            if (topJob->state == Job::NOT_STARTED) {
+                mTranscoder->start(topJob->key.first, topJob->key.second);
+            } else if (topJob->state == Job::PAUSED) {
+                mTranscoder->resume(topJob->key.first, topJob->key.second);
+            }
+            topJob->state = Job::RUNNING;
+        }
+    }
+    mCurrentJob = topJob;
+}
+
+void TranscodingJobScheduler::removeJob_l(const JobKeyType& jobKey) {
+    ALOGV("%s: job %s", __FUNCTION__, jobToString(jobKey).c_str());
+
+    if (mJobMap.count(jobKey) == 0) {
+        ALOGE("job %s doesn't exist", jobToString(jobKey).c_str());
+        return;
+    }
+
+    // Remove job from pid's queue.
+    const pid_t pid = mJobMap[jobKey].pid;
+    JobQueueType& jobQueue = mJobQueues[pid];
+    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);
+        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);
+    }
+
+    // Clear current job.
+    if (mCurrentJob == &mJobMap[jobKey]) {
+        mCurrentJob = nullptr;
+    }
+
+    // Remove job from job map.
+    mJobMap.erase(jobKey);
+}
+
+bool TranscodingJobScheduler::submit(ClientIdType clientId, int32_t jobId, pid_t pid,
+                                     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,
+          (int32_t)request.priority);
+
+    std::scoped_lock lock{mLock};
+
+    if (mJobMap.count(jobKey) > 0) {
+        ALOGE("job %s already exists", jobToString(jobKey).c_str());
+        return false;
+    }
+
+    // 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;
+    }
+
+    // Add job to job map.
+    mJobMap[jobKey].key = jobKey;
+    mJobMap[jobKey].pid = pid;
+    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,
+    // and add a new queue if needed.
+    if (pid != OFFLINE_PID) {
+        if (mJobQueues.count(pid) == 0) {
+            if (mProcInfo->isProcessOnTop(pid)) {
+                mPidSortedList.push_front(pid);
+            } else {
+                // Shouldn't be submitting real-time requests from non-top app,
+                // put it in front of the offline queue.
+                mPidSortedList.insert(mOfflinePidIterator, pid);
+            }
+        } else if (pid != *mPidSortedList.begin()) {
+            if (mProcInfo->isProcessOnTop(pid)) {
+                mPidSortedList.remove(pid);
+                mPidSortedList.push_front(pid);
+            }
+        }
+    }
+    // Append this job to the pid's queue.
+    mJobQueues[pid].push_back(jobKey);
+
+    updateCurrentJob_l();
+
+    validateState_l();
+    return true;
+}
+
+bool TranscodingJobScheduler::cancel(ClientIdType clientId, int32_t jobId) {
+    JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+    ALOGV("%s: job %s", __FUNCTION__, jobToString(jobKey).c_str());
+
+    std::scoped_lock lock{mLock};
+
+    if (mJobMap.count(jobKey) == 0) {
+        ALOGE("job %s doesn't exist", jobToString(jobKey).c_str());
+        return false;
+    }
+    // If the job is running, pause it first.
+    if (mJobMap[jobKey].state == Job::RUNNING) {
+        mTranscoder->pause(clientId, jobId);
+    }
+
+    // Remove the job.
+    removeJob_l(jobKey);
+
+    // Start next job.
+    updateCurrentJob_l();
+
+    validateState_l();
+    return true;
+}
+
+bool TranscodingJobScheduler::getJob(ClientIdType clientId, int32_t jobId,
+                                     TranscodingRequestParcel* request) {
+    JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+    std::scoped_lock lock{mLock};
+
+    if (mJobMap.count(jobKey) == 0) {
+        ALOGE("job %s doesn't exist", jobToString(jobKey).c_str());
+        return false;
+    }
+
+    *(TranscodingRequest*)request = mJobMap[jobKey].request;
+    return true;
+}
+
+void TranscodingJobScheduler::onFinish(ClientIdType clientId, int32_t jobId) {
+    JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+    ALOGV("%s: job %s", __FUNCTION__, jobToString(jobKey).c_str());
+
+    std::scoped_lock lock{mLock};
+
+    if (mJobMap.count(jobKey) == 0) {
+        ALOGW("ignoring abort 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 abort for job that was never started");
+        return;
+    }
+
+    {
+        auto clientCallback = mJobMap[jobKey].callback.lock();
+        if (clientCallback != nullptr) {
+            clientCallback->onTranscodingFinished(jobId, TranscodingResultParcel({jobId, 0}));
+        }
+    }
+
+    // Remove the job.
+    removeJob_l(jobKey);
+
+    // Start next job.
+    updateCurrentJob_l();
+
+    validateState_l();
+}
+
+void TranscodingJobScheduler::onError(int64_t clientId, int32_t jobId, TranscodingErrorCode err) {
+    JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+    ALOGV("%s: job %s, err %d", __FUNCTION__, jobToString(jobKey).c_str(), (int32_t)err);
+
+    std::scoped_lock lock{mLock};
+
+    if (mJobMap.count(jobKey) == 0) {
+        ALOGW("ignoring abort 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 abort for job that was never started");
+        return;
+    }
+
+    {
+        auto clientCallback = mJobMap[jobKey].callback.lock();
+        if (clientCallback != nullptr) {
+            clientCallback->onTranscodingFailed(jobId, err);
+        }
+    }
+
+    // Remove the job.
+    removeJob_l(jobKey);
+
+    // Start next job.
+    updateCurrentJob_l();
+
+    validateState_l();
+}
+
+void TranscodingJobScheduler::onResourceLost() {
+    ALOGV("%s", __FUNCTION__);
+
+    std::scoped_lock lock{mLock};
+
+    // 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;
+    }
+    mResourceLost = true;
+
+    validateState_l();
+}
+
+void TranscodingJobScheduler::onTopProcessChanged(pid_t pid) {
+    ALOGV("%s: pid %d", __FUNCTION__, pid);
+
+    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);
+
+    updateCurrentJob_l();
+
+    validateState_l();
+}
+
+void TranscodingJobScheduler::onResourceAvailable() {
+    ALOGV("%s", __FUNCTION__);
+
+    std::scoped_lock lock{mLock};
+
+    mResourceLost = false;
+    updateCurrentJob_l();
+
+    validateState_l();
+}
+
+void TranscodingJobScheduler::validateState_l() {
+#ifdef VALIDATE_STATE
+    LOG_ALWAYS_FATAL_IF(mJobQueues.count(OFFLINE_PID) != 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");
+
+    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++) {
+            LOG_ALWAYS_FATAL_IF(mJobMap.count(*jobIt) != 1, "mJobs count for job %s is not 1",
+                                jobToString(*jobIt).c_str());
+        }
+
+        totalJobs += mJobQueues[*pidIt].size();
+    }
+    LOG_ALWAYS_FATAL_IF(mJobMap.size() != totalJobs,
+                        "mJobs size doesn't match total jobs counted from pid queues");
+#endif  // VALIDATE_STATE
+}
+
+}  // namespace android
diff --git a/media/libmediatranscoding/aidl/android/media/IMediaTranscodingService.aidl b/media/libmediatranscoding/aidl/android/media/IMediaTranscodingService.aidl
index 9888456..40ca2c2 100644
--- a/media/libmediatranscoding/aidl/android/media/IMediaTranscodingService.aidl
+++ b/media/libmediatranscoding/aidl/android/media/IMediaTranscodingService.aidl
@@ -17,7 +17,7 @@
 package android.media;
 
 import android.media.ITranscodingClient;
-import android.media.ITranscodingClientListener;
+import android.media.ITranscodingClientCallback;
 import android.media.TranscodingJobParcel;
 import android.media.TranscodingRequestParcel;
 
@@ -54,7 +54,7 @@
      * ITranscodingClient interface object. The client should save and use it
      * for all future transactions with the service.
      *
-     * @param listener client interface for the MediaTranscodingService to call
+     * @param callback client interface for the MediaTranscodingService to call
      *        the client.
      * @param clientName name of the client.
      * @param opPackageName op package name of the client.
@@ -64,7 +64,7 @@
      *         failure to register.
      */
     ITranscodingClient registerClient(
-            in ITranscodingClientListener listener,
+            in ITranscodingClientCallback callback,
             in String clientName,
             in String opPackageName,
             in int clientUid,
diff --git a/media/libmediatranscoding/aidl/android/media/ITranscodingClient.aidl b/media/libmediatranscoding/aidl/android/media/ITranscodingClient.aidl
index a3852c2..37b5147 100644
--- a/media/libmediatranscoding/aidl/android/media/ITranscodingClient.aidl
+++ b/media/libmediatranscoding/aidl/android/media/ITranscodingClient.aidl
@@ -32,11 +32,10 @@
      *
      * @param request a TranscodingRequest contains transcoding configuration.
      * @param job(output variable) a TranscodingJob generated by the MediaTranscodingService.
-     * @return a unique positive jobId for the client generated by the
-     *         MediaTranscodingService, -1 means failure.
+     * @return true if success, false otherwise.
      */
-    int submitRequest(in TranscodingRequestParcel request,
-                      out TranscodingJobParcel job);
+    boolean submitRequest(in TranscodingRequestParcel request,
+                          out TranscodingJobParcel job);
 
     /**
      * Cancels a transcoding job.
diff --git a/media/libmediatranscoding/aidl/android/media/ITranscodingClientListener.aidl b/media/libmediatranscoding/aidl/android/media/ITranscodingClientCallback.aidl
similarity index 97%
rename from media/libmediatranscoding/aidl/android/media/ITranscodingClientListener.aidl
rename to media/libmediatranscoding/aidl/android/media/ITranscodingClientCallback.aidl
index a57bb54..e810f1e 100644
--- a/media/libmediatranscoding/aidl/android/media/ITranscodingClientListener.aidl
+++ b/media/libmediatranscoding/aidl/android/media/ITranscodingClientCallback.aidl
@@ -21,13 +21,13 @@
 import android.media.TranscodingResultParcel;
 
 /**
- * ITranscodingClientListener
+ * ITranscodingClientCallback
  *
  * Interface for the MediaTranscodingService to communicate with the client.
  *
  * {@hide}
  */
-interface ITranscodingClientListener {
+interface ITranscodingClientCallback {
 
     /**
     * Called when the transcoding associated with the jobId finished.
diff --git a/media/libmediatranscoding/aidl/android/media/TranscodingRequestParcel.aidl b/media/libmediatranscoding/aidl/android/media/TranscodingRequestParcel.aidl
index 7b7986d..5857482 100644
--- a/media/libmediatranscoding/aidl/android/media/TranscodingRequestParcel.aidl
+++ b/media/libmediatranscoding/aidl/android/media/TranscodingRequestParcel.aidl
@@ -39,12 +39,12 @@
     /**
      * Input source file descriptor.
      */
-    ParcelFileDescriptor inFd;
+    @nullable ParcelFileDescriptor inFd;
 
     /**
      * Output transcoded file descriptor.
      */
-    ParcelFileDescriptor outFd;
+    @nullable ParcelFileDescriptor outFd;
 
     /**
      * Priority of this transcoding. Service will schedule the transcoding based on the priority.
diff --git a/media/libmediatranscoding/include/media/AdjustableMaxPriorityQueue.h b/media/libmediatranscoding/include/media/AdjustableMaxPriorityQueue.h
index 0e8dcfd..9ca2ee9 100644
--- a/media/libmediatranscoding/include/media/AdjustableMaxPriorityQueue.h
+++ b/media/libmediatranscoding/include/media/AdjustableMaxPriorityQueue.h
@@ -38,7 +38,7 @@
  */
 template <class T, class Comparator = std::less<T>>
 class AdjustableMaxPriorityQueue {
-   public:
+public:
     typedef typename std::vector<T>::iterator iterator;
     typedef typename std::vector<T>::const_iterator const_iterator;
 
@@ -104,7 +104,7 @@
     /* Return the backbone storage of this PriorityQueue. Mainly used for debugging. */
     const std::vector<T>& getStorage() const { return mHeap; };
 
-   private:
+private:
     std::vector<T> mHeap;
 
     /* Implementation shared by both public push() methods. */
diff --git a/media/libmediatranscoding/include/media/ProcessInfoInterface.h b/media/libmediatranscoding/include/media/ProcessInfoInterface.h
new file mode 100644
index 0000000..ef79266
--- /dev/null
+++ b/media/libmediatranscoding/include/media/ProcessInfoInterface.h
@@ -0,0 +1,58 @@
+/*
+ * 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
new file mode 100644
index 0000000..b4cf463
--- /dev/null
+++ b/media/libmediatranscoding/include/media/SchedulerClientInterface.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_SCHEDULER_CLIENT_INTERFACE_H
+#define ANDROID_MEDIA_SCHEDULER_CLIENT_INTERFACE_H
+
+#include <aidl/android/media/ITranscodingClientCallback.h>
+#include <aidl/android/media/TranscodingRequestParcel.h>
+
+namespace android {
+
+using ::aidl::android::media::ITranscodingClientCallback;
+using ::aidl::android::media::TranscodingRequestParcel;
+
+using ClientIdType = int64_t;
+
+// Interface for a client to call the scheduler to schedule or retrieve
+// the status of a job.
+class SchedulerClientInterface {
+public:
+    virtual bool submit(ClientIdType clientId, int32_t jobId, pid_t pid,
+                        const TranscodingRequestParcel& request,
+                        const std::weak_ptr<ITranscodingClientCallback>& clientCallback) = 0;
+
+    virtual bool cancel(ClientIdType clientId, int32_t jobId) = 0;
+
+    virtual bool getJob(ClientIdType clientId, int32_t jobId,
+                        TranscodingRequestParcel* request) = 0;
+
+protected:
+    virtual ~SchedulerClientInterface() = default;
+};
+
+}  // namespace android
+#endif  // ANDROID_MEDIA_SCHEDULER_CLIENT_INTERFACE_H
diff --git a/media/libmediatranscoding/include/media/TranscoderInterface.h b/media/libmediatranscoding/include/media/TranscoderInterface.h
new file mode 100644
index 0000000..d74135a
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscoderInterface.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_TRANSCODER_INTERFACE_H
+#define ANDROID_MEDIA_TRANSCODER_INTERFACE_H
+
+#include <aidl/android/media/TranscodingErrorCode.h>
+
+namespace android {
+
+using ::aidl::android::media::TranscodingErrorCode;
+
+// Interface for the scheduler to call the transcoder to take actions.
+class TranscoderInterface {
+public:
+    // TODO(chz): determine what parameters are needed here.
+    // For now, always pass in clientId&jobId.
+    virtual void start(int64_t clientId, int32_t jobId) = 0;
+    virtual void pause(int64_t clientId, int32_t jobId) = 0;
+    virtual void resume(int64_t clientId, int32_t jobId) = 0;
+
+protected:
+    virtual ~TranscoderInterface() = default;
+};
+
+// Interface for the transcoder to notify the scheduler of the status of
+// the currently running job, or temporary loss of transcoding resources.
+class TranscoderCallbackInterface {
+public:
+    // 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;
+
+    // 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
+    // is solved, the scheduler should call TranscoderInterface's to either start a new job,
+    // or resume a paused job.
+    virtual void onResourceLost() = 0;
+
+protected:
+    virtual ~TranscoderCallbackInterface() = default;
+};
+
+}  // namespace android
+#endif  // ANDROID_MEDIA_TRANSCODER_INTERFACE_H
diff --git a/media/libmediatranscoding/include/media/TranscodingClientManager.h b/media/libmediatranscoding/include/media/TranscodingClientManager.h
index 645e0a7..d8176bf 100644
--- a/media/libmediatranscoding/include/media/TranscodingClientManager.h
+++ b/media/libmediatranscoding/include/media/TranscodingClientManager.h
@@ -18,7 +18,7 @@
 #define ANDROID_MEDIA_TRANSCODING_CLIENT_MANAGER_H
 
 #include <aidl/android/media/ITranscodingClient.h>
-#include <aidl/android/media/ITranscodingClientListener.h>
+#include <aidl/android/media/ITranscodingClientCallback.h>
 #include <sys/types.h>
 #include <utils/Condition.h>
 #include <utils/String8.h>
@@ -27,11 +27,12 @@
 #include <mutex>
 #include <unordered_map>
 
+#include "SchedulerClientInterface.h"
+
 namespace android {
 
 using ::aidl::android::media::ITranscodingClient;
-using ::aidl::android::media::ITranscodingClientListener;
-class MediaTranscodingService;
+using ::aidl::android::media::ITranscodingClientCallback;
 
 /*
  * TranscodingClientManager manages all the transcoding clients across different processes.
@@ -46,15 +47,16 @@
  */
 class TranscodingClientManager {
 public:
+    virtual ~TranscodingClientManager();
 
     /**
      * Adds a new client to the manager.
      *
-     * The client must have valid listener, pid, uid, clientName and opPackageName.
-     * Otherwise, this will return a non-zero errorcode. If the client listener has
+     * The client must have valid callback, pid, uid, clientName and opPackageName.
+     * Otherwise, this will return a non-zero errorcode. If the client callback has
      * already been added, it will also return non-zero errorcode.
      *
-     * @param listener client listener for the service to call this client.
+     * @param callback client callback for the service to call this client.
      * @param pid client's process id.
      * @param uid client's user id.
      * @param clientName client's name.
@@ -63,12 +65,9 @@
      *        to use for subsequent communications with the service.
      * @return 0 if client is added successfully, non-zero errorcode otherwise.
      */
-    status_t addClient(
-            const std::shared_ptr<ITranscodingClientListener>& listener,
-            int32_t pid, int32_t uid,
-            const std::string& clientName,
-            const std::string& opPackageName,
-            std::shared_ptr<ITranscodingClient>* client);
+    status_t addClient(const std::shared_ptr<ITranscodingClientCallback>& callback, pid_t pid,
+                       uid_t uid, const std::string& clientName, const std::string& opPackageName,
+                       std::shared_ptr<ITranscodingClient>* client);
 
     /**
      * Gets the number of clients.
@@ -83,12 +82,10 @@
 private:
     friend class MediaTranscodingService;
     friend class TranscodingClientManagerTest;
-
-    typedef int64_t ClientIdType;
     struct ClientImpl;
 
-    TranscodingClientManager();
-    virtual ~TranscodingClientManager();
+    // Only allow MediaTranscodingService and unit tests to instantiate.
+    TranscodingClientManager(const std::shared_ptr<SchedulerClientInterface>& scheduler);
 
     /**
      * Checks if a client with clientId is already registered.
@@ -105,9 +102,6 @@
      */
     status_t removeClient(ClientIdType clientId);
 
-    /** Get the singleton instance of the TranscodingClientManager. */
-    static TranscodingClientManager& getInstance();
-
     static void BinderDiedCallback(void* cookie);
 
     mutable std::mutex mLock;
@@ -115,6 +109,8 @@
             GUARDED_BY(mLock);
 
     ::ndk::ScopedAIBinder_DeathRecipient mDeathRecipient;
+
+    std::shared_ptr<SchedulerClientInterface> mJobScheduler;
 };
 
 }  // namespace android
diff --git a/media/libmediatranscoding/include/media/TranscodingJobScheduler.h b/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
new file mode 100644
index 0000000..f4d98ba
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
@@ -0,0 +1,114 @@
+/*
+ * 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_JOB_SCHEDULER_H
+#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 <utils/String8.h>
+
+#include <list>
+#include <map>
+#include <mutex>
+
+namespace android {
+using ::aidl::android::media::TranscodingJobPriority;
+using ::aidl::android::media::TranscodingResultParcel;
+
+class TranscodingJobScheduler : public ProcessInfoCallbackInterface,
+                                public SchedulerClientInterface,
+                                public TranscoderCallbackInterface {
+public:
+    virtual ~TranscodingJobScheduler();
+
+    // SchedulerClientInterface
+    bool submit(ClientIdType clientId, int32_t jobId, pid_t pid,
+                const TranscodingRequestParcel& request,
+                const std::weak_ptr<ITranscodingClientCallback>& clientCallback) override;
+    bool cancel(ClientIdType clientId, int32_t jobId) override;
+    bool getJob(ClientIdType clientId, int32_t jobId, TranscodingRequestParcel* request) override;
+    // ~SchedulerClientInterface
+
+    // TranscoderCallbackInterface
+    void onFinish(ClientIdType clientId, int32_t jobId) override;
+    void onError(int64_t clientId, int32_t jobId, TranscodingErrorCode err) override;
+    void onResourceLost() override;
+    // ~TranscoderCallbackInterface
+
+    // ProcessInfoCallbackInterface
+    void onTopProcessChanged(int32_t pid) override;
+    void onResourceAvailable() override;
+    // ~ProcessInfoCallbackInterface
+
+private:
+    friend class MediaTranscodingService;
+    friend class TranscodingJobSchedulerTest;
+
+    using JobKeyType = std::pair<ClientIdType, int32_t /*jobId*/>;
+    using JobQueueType = std::list<JobKeyType>;
+
+    struct Job {
+        JobKeyType key;
+        pid_t pid;
+        enum JobState {
+            NOT_STARTED,
+            RUNNING,
+            PAUSED,
+        } state;
+        TranscodingRequest request;
+        std::weak_ptr<ITranscodingClientCallback> callback;
+    };
+
+    // TODO(chz): call transcoder without global lock.
+    // Use mLock for all entrypoints for now.
+    mutable std::mutex mLock;
+
+    std::map<JobKeyType, Job> mJobMap;
+
+    // Pid->JobQueue map (pid == -1: offline queue)
+    std::map<pid_t, JobQueueType> mJobQueues;
+
+    // Pids, 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::shared_ptr<TranscoderInterface> mTranscoder;
+    std::shared_ptr<ProcessInfoInterface> mProcInfo;
+
+    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);
+
+    Job* getTopJob_l();
+    void updateCurrentJob_l();
+    void removeJob_l(const JobKeyType& jobKey);
+
+    // Internal state verifier (debug only)
+    void validateState_l();
+
+    static String8 jobToString(const JobKeyType& jobKey);
+};
+
+}  // namespace android
+#endif  // ANDROID_MEDIA_TRANSCODING_JOB_SCHEDULER_H
diff --git a/media/libmediatranscoding/include/media/TranscodingRequest.h b/media/libmediatranscoding/include/media/TranscodingRequest.h
new file mode 100644
index 0000000..1337af3
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscodingRequest.h
@@ -0,0 +1,49 @@
+/*
+ * 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_REQUEST_H
+#define ANDROID_MEDIA_TRANSCODING_REQUEST_H
+
+#include <aidl/android/media/TranscodingRequestParcel.h>
+
+namespace android {
+
+using ::aidl::android::media::TranscodingRequestParcel;
+
+// Helper class for duplicating a TranscodingRequestParcel
+class TranscodingRequest : public TranscodingRequestParcel {
+public:
+    TranscodingRequest() = default;
+    TranscodingRequest(const TranscodingRequestParcel& parcel) { setTo(parcel); }
+    TranscodingRequest& operator=(const TranscodingRequest& request) {
+        setTo(request);
+        return *this;
+    }
+
+private:
+    void setTo(const TranscodingRequestParcel& parcel) {
+        fileName = parcel.fileName;
+        transcodingType = parcel.transcodingType;
+        // TODO: determine if the fds need dup
+        inFd.set(dup(parcel.inFd.get()));
+        outFd.set(dup(parcel.outFd.get()));
+        priority = parcel.priority;
+        requestUpdate = parcel.requestUpdate;
+    }
+};
+
+}  // namespace android
+#endif  // ANDROID_MEDIA_TRANSCODING_REQUEST_H
diff --git a/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp b/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp
index d58af4e..2e49f32 100644
--- a/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp
+++ b/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp
@@ -36,7 +36,7 @@
 namespace android {
 
 class IntUniquePtrComp {
-   public:
+public:
     bool operator()(const std::unique_ptr<int>& lhs, const std::unique_ptr<int>& rhs) const {
         return *lhs < *rhs;
     }
@@ -233,7 +233,7 @@
     // The job is arranging according to priority with highest priority comes first.
     // For the job with the same priority, the job with early createTime will come first.
     class TranscodingJobComp {
-       public:
+    public:
         bool operator()(const std::unique_ptr<TranscodingJob>& lhs,
                         const std::unique_ptr<TranscodingJob>& rhs) const {
             if (lhs->priority != rhs->priority) {
diff --git a/media/libmediatranscoding/tests/Android.bp b/media/libmediatranscoding/tests/Android.bp
index 8191b00..904cf9b 100644
--- a/media/libmediatranscoding/tests/Android.bp
+++ b/media/libmediatranscoding/tests/Android.bp
@@ -37,6 +37,13 @@
     srcs: ["TranscodingClientManager_tests.cpp"],
 }
 
+cc_test {
+    name: "TranscodingJobScheduler_tests",
+    defaults: ["libmediatranscoding_test_defaults"],
+
+    srcs: ["TranscodingJobScheduler_tests.cpp"],
+}
+
 //
 // AdjustableMaxPriorityQueue unit test
 //
diff --git a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
index d23ea87..b58c827 100644
--- a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
@@ -19,45 +19,52 @@
 // #define LOG_NDEBUG 0
 #define LOG_TAG "TranscodingClientManagerTest"
 
-#include <aidl/android/media/BnTranscodingClientListener.h>
+#include <aidl/android/media/BnTranscodingClientCallback.h>
 #include <aidl/android/media/IMediaTranscodingService.h>
 #include <android-base/logging.h>
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
 #include <gtest/gtest.h>
+#include <media/SchedulerClientInterface.h>
 #include <media/TranscodingClientManager.h>
+#include <media/TranscodingRequest.h>
 #include <utils/Log.h>
 
+#include <list>
+
 namespace android {
 
 using Status = ::ndk::ScopedAStatus;
-using aidl::android::media::BnTranscodingClientListener;
-using aidl::android::media::IMediaTranscodingService;
+using ::aidl::android::media::BnTranscodingClientCallback;
+using ::aidl::android::media::IMediaTranscodingService;
+using ::aidl::android::media::TranscodingErrorCode;
+using ::aidl::android::media::TranscodingJobParcel;
+using ::aidl::android::media::TranscodingRequestParcel;
+using ::aidl::android::media::TranscodingResultParcel;
 
-constexpr int32_t kInvalidClientPid = -1;
-constexpr int32_t kInvalidClientUid = -1;
+constexpr pid_t kInvalidClientPid = -1;
 constexpr const char* kInvalidClientName = "";
 constexpr const char* kInvalidClientPackage = "";
 
-constexpr int32_t kClientPid = 2;
-constexpr int32_t kClientUid = 3;
+constexpr pid_t kClientPid = 2;
+constexpr uid_t kClientUid = 3;
 constexpr const char* kClientName = "TestClientName";
 constexpr const char* kClientPackage = "TestClientPackage";
 
-struct TestClient : public BnTranscodingClientListener {
-    TestClient() {
-        ALOGD("TestClient Created");
-    }
+struct TestClientCallback : public BnTranscodingClientCallback {
+    TestClientCallback() { ALOGI("TestClientCallback Created"); }
 
-    Status onTranscodingFinished(
-            int32_t /* in_jobId */,
-            const ::aidl::android::media::TranscodingResultParcel& /* in_result */) override {
+    virtual ~TestClientCallback() { ALOGI("TestClientCallback destroyed"); };
+
+    Status onTranscodingFinished(int32_t in_jobId,
+                                 const TranscodingResultParcel& in_result) override {
+        EXPECT_EQ(in_jobId, in_result.jobId);
+        mEventQueue.push_back(Finished(in_jobId));
         return Status::ok();
     }
 
-    Status onTranscodingFailed(
-            int32_t /* in_jobId */,
-            ::aidl::android::media::TranscodingErrorCode /*in_errorCode */) override {
+    Status onTranscodingFailed(int32_t in_jobId, TranscodingErrorCode /*in_errorCode */) override {
+        mEventQueue.push_back(Failed(in_jobId));
         return Status::ok();
     }
 
@@ -70,142 +77,371 @@
         return Status::ok();
     }
 
-    virtual ~TestClient() { ALOGI("TestClient destroyed"); };
+    struct Event {
+        enum {
+            NoEvent,
+            Finished,
+            Failed,
+        } type;
+        int32_t jobId;
+    };
+
+    static constexpr Event NoEvent = {Event::NoEvent, 0};
+#define DECLARE_EVENT(action) \
+    static Event action(int32_t jobId) { return {Event::action, jobId}; }
+
+    DECLARE_EVENT(Finished);
+    DECLARE_EVENT(Failed);
+
+    const Event& popEvent() {
+        if (mEventQueue.empty()) {
+            mPoppedEvent = NoEvent;
+        } else {
+            mPoppedEvent = *mEventQueue.begin();
+            mEventQueue.pop_front();
+        }
+        return mPoppedEvent;
+    }
 
 private:
-    TestClient(const TestClient&) = delete;
-    TestClient& operator=(const TestClient&) = delete;
+    Event mPoppedEvent;
+    std::list<Event> mEventQueue;
+
+    TestClientCallback(const TestClientCallback&) = delete;
+    TestClientCallback& operator=(const TestClientCallback&) = delete;
+};
+
+bool operator==(const TestClientCallback::Event& lhs, const TestClientCallback::Event& rhs) {
+    return lhs.type == rhs.type && lhs.jobId == rhs.jobId;
+}
+
+struct TestScheduler : public SchedulerClientInterface {
+    TestScheduler() { ALOGI("TestScheduler Created"); }
+
+    virtual ~TestScheduler() { ALOGI("TestScheduler Destroyed"); }
+
+    bool submit(int64_t clientId, int32_t jobId, pid_t /*pid*/,
+                const TranscodingRequestParcel& request,
+                const std::weak_ptr<ITranscodingClientCallback>& clientCallback) override {
+        JobKeyType jobKey = std::make_pair(clientId, jobId);
+        if (mJobs.count(jobKey) > 0) {
+            return false;
+        }
+
+        // This is the secret name we'll check, to test error propagation from
+        // the scheduler back to client.
+        if (request.fileName == "bad_file") {
+            return false;
+        }
+
+        mJobs[jobKey].request = request;
+        mJobs[jobKey].callback = clientCallback;
+
+        mLastJob = jobKey;
+        return true;
+    }
+
+    bool cancel(int64_t clientId, int32_t jobId) override {
+        JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+        if (mJobs.count(jobKey) == 0) {
+            return false;
+        }
+        mJobs.erase(jobKey);
+        return true;
+    }
+
+    bool getJob(int64_t clientId, int32_t jobId, TranscodingRequestParcel* request) override {
+        JobKeyType jobKey = std::make_pair(clientId, jobId);
+        if (mJobs.count(jobKey) == 0) {
+            return false;
+        }
+
+        *(TranscodingRequest*)request = mJobs[jobKey].request;
+        return true;
+    }
+
+    void finishLastJob() {
+        auto it = mJobs.find(mLastJob);
+        if (it == mJobs.end()) {
+            return;
+        }
+        {
+            auto clientCallback = it->second.callback.lock();
+            if (clientCallback != nullptr) {
+                clientCallback->onTranscodingFinished(
+                        mLastJob.second, TranscodingResultParcel({mLastJob.second, 0}));
+            }
+        }
+        mJobs.erase(it);
+    }
+
+    void abortLastJob() {
+        auto it = mJobs.find(mLastJob);
+        if (it == mJobs.end()) {
+            return;
+        }
+        {
+            auto clientCallback = it->second.callback.lock();
+            if (clientCallback != nullptr) {
+                clientCallback->onTranscodingFailed(
+                        mLastJob.second, TranscodingErrorCode::kUnknown);
+            }
+        }
+        mJobs.erase(it);
+    }
+
+    struct Job {
+        TranscodingRequest request;
+        std::weak_ptr<ITranscodingClientCallback> callback;
+    };
+
+    typedef std::pair<int64_t, int32_t> JobKeyType;
+    std::map<JobKeyType, Job> mJobs;
+    JobKeyType mLastJob;
 };
 
 class TranscodingClientManagerTest : public ::testing::Test {
-   public:
-    TranscodingClientManagerTest() : mClientManager(TranscodingClientManager::getInstance()) {
+public:
+    TranscodingClientManagerTest()
+          : mScheduler(new TestScheduler()),
+            mClientManager(new TranscodingClientManager(mScheduler)) {
         ALOGD("TranscodingClientManagerTest created");
     }
 
     void SetUp() override {
-        mClientListener = ::ndk::SharedRefBase::make<TestClient>();
-        mClientListener2 = ::ndk::SharedRefBase::make<TestClient>();
-        mClientListener3 = ::ndk::SharedRefBase::make<TestClient>();
+        mClientCallback1 = ::ndk::SharedRefBase::make<TestClientCallback>();
+        mClientCallback2 = ::ndk::SharedRefBase::make<TestClientCallback>();
+        mClientCallback3 = ::ndk::SharedRefBase::make<TestClientCallback>();
     }
 
-    void TearDown() override {
-        ALOGI("TranscodingClientManagerTest tear down");
-    }
+    void TearDown() override { ALOGI("TranscodingClientManagerTest tear down"); }
 
     ~TranscodingClientManagerTest() { ALOGD("TranscodingClientManagerTest destroyed"); }
 
-    TranscodingClientManager& mClientManager;
-    std::shared_ptr<ITranscodingClientListener> mClientListener;
-    std::shared_ptr<ITranscodingClientListener> mClientListener2;
-    std::shared_ptr<ITranscodingClientListener> mClientListener3;
+    void addMultipleClients() {
+        EXPECT_EQ(mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+                                            kClientPackage, &mClient1),
+                  OK);
+        EXPECT_NE(mClient1, nullptr);
+
+        EXPECT_EQ(mClientManager->addClient(mClientCallback2, kClientPid, kClientUid, kClientName,
+                                            kClientPackage, &mClient2),
+                  OK);
+        EXPECT_NE(mClient2, nullptr);
+
+        EXPECT_EQ(mClientManager->addClient(mClientCallback3, kClientPid, kClientUid, kClientName,
+                                            kClientPackage, &mClient3),
+                  OK);
+        EXPECT_NE(mClient3, nullptr);
+
+        EXPECT_EQ(mClientManager->getNumOfClients(), 3);
+    }
+
+    void unregisterMultipleClients() {
+        EXPECT_TRUE(mClient1->unregister().isOk());
+        EXPECT_TRUE(mClient2->unregister().isOk());
+        EXPECT_TRUE(mClient3->unregister().isOk());
+        EXPECT_EQ(mClientManager->getNumOfClients(), 0);
+    }
+
+    std::shared_ptr<TestScheduler> mScheduler;
+    std::shared_ptr<TranscodingClientManager> mClientManager;
+    std::shared_ptr<ITranscodingClient> mClient1;
+    std::shared_ptr<ITranscodingClient> mClient2;
+    std::shared_ptr<ITranscodingClient> mClient3;
+    std::shared_ptr<TestClientCallback> mClientCallback1;
+    std::shared_ptr<TestClientCallback> mClientCallback2;
+    std::shared_ptr<TestClientCallback> mClientCallback3;
 };
 
-TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientListener) {
-    // Add a client with null listener and expect failure.
+TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientCallback) {
+    // Add a client with null callback and expect failure.
     std::shared_ptr<ITranscodingClient> client;
-    status_t err = mClientManager.addClient(nullptr,
-            kClientPid, kClientUid, kClientName, kClientPackage, &client);
+    status_t err = mClientManager->addClient(nullptr, kClientPid, kClientUid, kClientName,
+                                             kClientPackage, &client);
     EXPECT_EQ(err, BAD_VALUE);
 }
 
 TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientPid) {
     // Add a client with invalid Pid and expect failure.
     std::shared_ptr<ITranscodingClient> client;
-    status_t err = mClientManager.addClient(mClientListener,
-            kInvalidClientPid, kClientUid, kClientName, kClientPackage, &client);
-    EXPECT_EQ(err, BAD_VALUE);
-}
-
-TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientUid) {
-    // Add a client with invalid Uid and expect failure.
-    std::shared_ptr<ITranscodingClient> client;
-    status_t err = mClientManager.addClient(mClientListener,
-            kClientPid, kInvalidClientUid, kClientName, kClientPackage, &client);
+    status_t err = mClientManager->addClient(mClientCallback1, kInvalidClientPid, kClientUid,
+                                             kClientName, kClientPackage, &client);
     EXPECT_EQ(err, BAD_VALUE);
 }
 
 TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientName) {
     // Add a client with invalid name and expect failure.
     std::shared_ptr<ITranscodingClient> client;
-    status_t err = mClientManager.addClient(mClientListener,
-            kClientPid, kClientUid, kInvalidClientName, kClientPackage, &client);
+    status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid,
+                                             kInvalidClientName, kClientPackage, &client);
     EXPECT_EQ(err, BAD_VALUE);
 }
 
 TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientPackageName) {
     // Add a client with invalid packagename and expect failure.
     std::shared_ptr<ITranscodingClient> client;
-    status_t err = mClientManager.addClient(mClientListener,
-            kClientPid, kClientUid, kClientName, kInvalidClientPackage, &client);
+    status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+                                             kInvalidClientPackage, &client);
     EXPECT_EQ(err, BAD_VALUE);
 }
 
 TEST_F(TranscodingClientManagerTest, TestAddingValidClient) {
-    // Add a valid client, should succeed
+    // Add a valid client, should succeed.
     std::shared_ptr<ITranscodingClient> client;
-    status_t err = mClientManager.addClient(mClientListener,
-            kClientPid, kClientUid, kClientName, kClientPackage, &client);
+    status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+                                             kClientPackage, &client);
     EXPECT_EQ(err, OK);
     EXPECT_NE(client.get(), nullptr);
-    EXPECT_EQ(mClientManager.getNumOfClients(), 1);
+    EXPECT_EQ(mClientManager->getNumOfClients(), 1);
 
-    // unregister client, should succeed
+    // Unregister client, should succeed.
     Status status = client->unregister();
     EXPECT_TRUE(status.isOk());
-    EXPECT_EQ(mClientManager.getNumOfClients(), 0);
+    EXPECT_EQ(mClientManager->getNumOfClients(), 0);
 }
 
 TEST_F(TranscodingClientManagerTest, TestAddingDupliacteClient) {
     std::shared_ptr<ITranscodingClient> client;
-    status_t err = mClientManager.addClient(mClientListener,
-            kClientPid, kClientUid, kClientName, kClientPackage, &client);
+    status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+                                             kClientPackage, &client);
     EXPECT_EQ(err, OK);
     EXPECT_NE(client.get(), nullptr);
-    EXPECT_EQ(mClientManager.getNumOfClients(), 1);
+    EXPECT_EQ(mClientManager->getNumOfClients(), 1);
 
     std::shared_ptr<ITranscodingClient> dupClient;
-    err = mClientManager.addClient(mClientListener,
-            kClientPid, kClientUid, "dupClient", "dupPackage", &dupClient);
+    err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, "dupClient",
+                                    "dupPackage", &dupClient);
     EXPECT_EQ(err, ALREADY_EXISTS);
     EXPECT_EQ(dupClient.get(), nullptr);
-    EXPECT_EQ(mClientManager.getNumOfClients(), 1);
+    EXPECT_EQ(mClientManager->getNumOfClients(), 1);
 
     Status status = client->unregister();
     EXPECT_TRUE(status.isOk());
-    EXPECT_EQ(mClientManager.getNumOfClients(), 0);
+    EXPECT_EQ(mClientManager->getNumOfClients(), 0);
 
-    err = mClientManager.addClient(mClientListener,
-            kClientPid, kClientUid, "dupClient", "dupPackage", &dupClient);
+    err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, "dupClient",
+                                    "dupPackage", &dupClient);
     EXPECT_EQ(err, OK);
     EXPECT_NE(dupClient.get(), nullptr);
-    EXPECT_EQ(mClientManager.getNumOfClients(), 1);
+    EXPECT_EQ(mClientManager->getNumOfClients(), 1);
 
     status = dupClient->unregister();
     EXPECT_TRUE(status.isOk());
-    EXPECT_EQ(mClientManager.getNumOfClients(), 0);
+    EXPECT_EQ(mClientManager->getNumOfClients(), 0);
 }
 
 TEST_F(TranscodingClientManagerTest, TestAddingMultipleClient) {
-    std::shared_ptr<ITranscodingClient> client1, client2, client3;
+    addMultipleClients();
+    unregisterMultipleClients();
+}
 
-    EXPECT_EQ(mClientManager.addClient(mClientListener,
-            kClientPid, kClientUid, kClientName, kClientPackage, &client1), OK);
-    EXPECT_NE(client1, nullptr);
+TEST_F(TranscodingClientManagerTest, TestSubmitCancelGetJobs) {
+    addMultipleClients();
 
-    EXPECT_EQ(mClientManager.addClient(mClientListener2,
-            kClientPid, kClientUid, kClientName, kClientPackage, &client2), OK);
-    EXPECT_NE(client2, nullptr);
+    // Test jobId assignment.
+    TranscodingRequestParcel request;
+    request.fileName = "test_file_0";
+    TranscodingJobParcel job;
+    bool result;
+    EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+    EXPECT_TRUE(result);
+    EXPECT_EQ(job.jobId, 0);
 
-    EXPECT_EQ(mClientManager.addClient(mClientListener3,
-            kClientPid, kClientUid, kClientName, kClientPackage, &client3), OK);
-    EXPECT_NE(client3, nullptr);
+    request.fileName = "test_file_1";
+    EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+    EXPECT_TRUE(result);
+    EXPECT_EQ(job.jobId, 1);
 
-    EXPECT_EQ(mClientManager.getNumOfClients(), 3);
-    EXPECT_TRUE(client1->unregister().isOk());
-    EXPECT_TRUE(client2->unregister().isOk());
-    EXPECT_TRUE(client3->unregister().isOk());
-    EXPECT_EQ(mClientManager.getNumOfClients(), 0);
+    request.fileName = "test_file_2";
+    EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+    EXPECT_TRUE(result);
+    EXPECT_EQ(job.jobId, 2);
+
+    // Test submit bad request (no valid fileName) fails.
+    TranscodingRequestParcel badRequest;
+    badRequest.fileName = "bad_file";
+    EXPECT_TRUE(mClient1->submitRequest(badRequest, &job, &result).isOk());
+    EXPECT_FALSE(result);
+
+    // Test get jobs by id.
+    EXPECT_TRUE(mClient1->getJobWithId(2, &job, &result).isOk());
+    EXPECT_EQ(job.jobId, 2);
+    EXPECT_EQ(job.request.fileName, "test_file_2");
+    EXPECT_TRUE(result);
+
+    // Test get jobs by invalid id fails.
+    EXPECT_TRUE(mClient1->getJobWithId(100, &job, &result).isOk());
+    EXPECT_FALSE(result);
+
+    // Test cancel non-existent job fail.
+    EXPECT_TRUE(mClient2->cancelJob(100, &result).isOk());
+    EXPECT_FALSE(result);
+
+    // Test cancel valid jobId in arbitrary order.
+    EXPECT_TRUE(mClient1->cancelJob(2, &result).isOk());
+    EXPECT_TRUE(result);
+
+    EXPECT_TRUE(mClient1->cancelJob(0, &result).isOk());
+    EXPECT_TRUE(result);
+
+    EXPECT_TRUE(mClient1->cancelJob(1, &result).isOk());
+    EXPECT_TRUE(result);
+
+    // Test cancel job again fails.
+    EXPECT_TRUE(mClient1->cancelJob(1, &result).isOk());
+    EXPECT_FALSE(result);
+
+    // Test get job after cancel fails.
+    EXPECT_TRUE(mClient1->getJobWithId(2, &job, &result).isOk());
+    EXPECT_FALSE(result);
+
+    // Test jobId independence for each client.
+    EXPECT_TRUE(mClient2->submitRequest(request, &job, &result).isOk());
+    EXPECT_TRUE(result);
+    EXPECT_EQ(job.jobId, 0);
+
+    EXPECT_TRUE(mClient2->submitRequest(request, &job, &result).isOk());
+    EXPECT_TRUE(result);
+    EXPECT_EQ(job.jobId, 1);
+
+    unregisterMultipleClients();
+}
+
+TEST_F(TranscodingClientManagerTest, TestClientCallback) {
+    addMultipleClients();
+
+    TranscodingRequestParcel request;
+    request.fileName = "test_file_name";
+    TranscodingJobParcel job;
+    bool result;
+    EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+    EXPECT_TRUE(result);
+    EXPECT_EQ(job.jobId, 0);
+
+    mScheduler->finishLastJob();
+    EXPECT_EQ(mClientCallback1->popEvent(), TestClientCallback::Finished(job.jobId));
+
+    EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+    EXPECT_TRUE(result);
+    EXPECT_EQ(job.jobId, 1);
+
+    mScheduler->abortLastJob();
+    EXPECT_EQ(mClientCallback1->popEvent(), TestClientCallback::Failed(job.jobId));
+
+    EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+    EXPECT_TRUE(result);
+    EXPECT_EQ(job.jobId, 2);
+
+    EXPECT_TRUE(mClient2->submitRequest(request, &job, &result).isOk());
+    EXPECT_TRUE(result);
+    EXPECT_EQ(job.jobId, 0);
+
+    mScheduler->finishLastJob();
+    EXPECT_EQ(mClientCallback2->popEvent(), TestClientCallback::Finished(job.jobId));
+
+    unregisterMultipleClients();
 }
 
 }  // namespace android
diff --git a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
new file mode 100644
index 0000000..d062d93
--- /dev/null
+++ b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
@@ -0,0 +1,486 @@
+/*
+ * 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 TranscodingJobScheduler
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "TranscodingJobSchedulerTest"
+
+#include <aidl/android/media/BnTranscodingClientCallback.h>
+#include <aidl/android/media/IMediaTranscodingService.h>
+#include <aidl/android/media/ITranscodingClient.h>
+#include <aidl/android/media/ITranscodingClientCallback.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <gtest/gtest.h>
+#include <media/TranscodingClientManager.h>
+#include <media/TranscodingJobScheduler.h>
+#include <utils/Log.h>
+
+namespace android {
+
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::media::BnTranscodingClientCallback;
+using aidl::android::media::IMediaTranscodingService;
+using aidl::android::media::ITranscodingClient;
+
+constexpr int64_t kClientId = 1000;
+constexpr int32_t kClientJobId = 0;
+constexpr pid_t kClientPid = 5000;
+constexpr pid_t kInvalidPid = -1;
+
+#define CLIENT(n) (kClientId + (n))
+#define JOB(n) (kClientJobId + (n))
+#define PID(n) (kClientPid + (n))
+
+class TestCallback : public TranscoderInterface, public ProcessInfoInterface {
+public:
+    TestCallback() : mTopPid(-1), mLastError(TranscodingErrorCode::kUnknown) {}
+    virtual ~TestCallback() {}
+
+    // TranscoderInterface
+    void start(int64_t clientId, int32_t jobId) override {
+        mEventQueue.push_back(Start(clientId, jobId));
+    }
+    void pause(int64_t clientId, int32_t jobId) override {
+        mEventQueue.push_back(Pause(clientId, jobId));
+    }
+    void resume(int64_t clientId, int32_t jobId) override {
+        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));
+    }
+
+    void onFailed(int64_t clientId, int32_t jobId, TranscodingErrorCode err) {
+        mLastError = err;
+        mEventQueue.push_back(Failed(clientId, jobId));
+    }
+
+    void setTop(pid_t pid) { mTopPid = pid; }
+
+    TranscodingErrorCode getLastError() {
+        TranscodingErrorCode result = mLastError;
+        mLastError = TranscodingErrorCode::kUnknown;
+        return result;
+    }
+
+    struct Event {
+        enum { NoEvent, Start, Pause, Resume, Finished, Failed } type;
+        int64_t clientId;
+        int32_t jobId;
+    };
+
+    static constexpr Event NoEvent = {Event::NoEvent, 0, 0};
+
+#define DECLARE_EVENT(action)                              \
+    static Event action(int64_t clientId, int32_t jobId) { \
+        return {Event::action, clientId, jobId};           \
+    }
+
+    DECLARE_EVENT(Start);
+    DECLARE_EVENT(Pause);
+    DECLARE_EVENT(Resume);
+    DECLARE_EVENT(Finished);
+    DECLARE_EVENT(Failed);
+
+    const Event& popEvent() {
+        if (mEventQueue.empty()) {
+            mPoppedEvent = NoEvent;
+        } else {
+            mPoppedEvent = *mEventQueue.begin();
+            mEventQueue.pop_front();
+        }
+        return mPoppedEvent;
+    }
+
+private:
+    Event mPoppedEvent;
+    std::list<Event> mEventQueue;
+    pid_t mTopPid;
+    TranscodingErrorCode mLastError;
+};
+
+bool operator==(const TestCallback::Event& lhs, const TestCallback::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) {
+        ALOGD("TestClient Created");
+    }
+
+    Status onTranscodingFinished(int32_t in_jobId,
+                                 const TranscodingResultParcel& in_result) override {
+        EXPECT_EQ(in_jobId, in_result.jobId);
+        mOwner->onFinished(mClientId, in_jobId);
+        return Status::ok();
+    }
+
+    Status onTranscodingFailed(int32_t in_jobId, TranscodingErrorCode in_errorCode) override {
+        mOwner->onFailed(mClientId, in_jobId, in_errorCode);
+        return Status::ok();
+    }
+
+    Status onAwaitNumberOfJobsChanged(int32_t /* in_jobId */, int32_t /* in_oldAwaitNumber */,
+                                      int32_t /* in_newAwaitNumber */) override {
+        return Status::ok();
+    }
+
+    Status onProgressUpdate(int32_t /* in_jobId */, int32_t /* in_progress */) override {
+        return Status::ok();
+    }
+
+    virtual ~TestClientCallback() { ALOGI("TestClient destroyed"); };
+
+private:
+    TestCallback* mOwner;
+    int64_t mClientId;
+    TestClientCallback(const TestClientCallback&) = delete;
+    TestClientCallback& operator=(const TestClientCallback&) = delete;
+};
+
+class TranscodingJobSchedulerTest : public ::testing::Test {
+public:
+    TranscodingJobSchedulerTest() { ALOGI("TranscodingJobSchedulerTest created"); }
+
+    void SetUp() override {
+        ALOGI("TranscodingJobSchedulerTest set up");
+        mCallback.reset(new TestCallback());
+        mScheduler.reset(new TranscodingJobScheduler(mCallback, mCallback));
+
+        // 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));
+        mClientCallback1 =
+                ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(1));
+        mClientCallback2 =
+                ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(2));
+        mClientCallback3 =
+                ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(3));
+    }
+
+    void TearDown() override { ALOGI("TranscodingJobSchedulerTest tear down"); }
+
+    ~TranscodingJobSchedulerTest() { ALOGD("TranscodingJobSchedulerTest destroyed"); }
+
+    std::shared_ptr<TestCallback> mCallback;
+    std::shared_ptr<TranscodingJobScheduler> mScheduler;
+    TranscodingRequestParcel mOfflineRequest;
+    TranscodingRequestParcel mRealtimeRequest;
+    std::shared_ptr<TestClientCallback> mClientCallback0;
+    std::shared_ptr<TestClientCallback> mClientCallback1;
+    std::shared_ptr<TestClientCallback> mClientCallback2;
+    std::shared_ptr<TestClientCallback> mClientCallback3;
+};
+
+TEST_F(TranscodingJobSchedulerTest, TestSubmitJob) {
+    ALOGD("TestSubmitJob");
+
+    // Start with PID(1) on top.
+    mCallback->setTop(PID(1));
+
+    // Submit offline job to CLIENT(0) in PID(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));
+
+    // 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)));
+
+    // 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);
+
+    // 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(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 offline job, shouldn't generate any event.
+    mScheduler->submit(CLIENT(2), JOB(1), PID(1), mOfflineRequest, mClientCallback2);
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::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)));
+}
+
+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)));
+
+    // 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);
+
+    // Submit offline job JOB(2), should not start.
+    mScheduler->submit(CLIENT(0), JOB(2), PID(0), mOfflineRequest, mClientCallback0);
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+    // Cancel queued real-time job.
+    // Cancel real-time job JOB(1), should be cancelled.
+    EXPECT_TRUE(mScheduler->cancel(CLIENT(0), JOB(1)));
+
+    // Cancel queued offline job.
+    // Cancel offline job JOB(2), should be cancelled.
+    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);
+
+    // 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)));
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestFinishJob) {
+    ALOGD("TestFinishJob");
+
+    // Fail without any jobs submitted, should be ignored.
+    mScheduler->onFinish(CLIENT(0), JOB(0));
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::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)));
+
+    // 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)));
+
+    // 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);
+
+    // Fail when the job never started, should be ignored.
+    mScheduler->onFinish(CLIENT(0), JOB(2));
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::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)));
+
+    // Simulate Fail 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)));
+
+    // Fail 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)));
+
+    // Fail 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)));
+
+    // Fail running offline job.
+    mScheduler->onFinish(CLIENT(0), JOB(0));
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(0)));
+
+    // Duplicate fail for last job, should be ignored.
+    mScheduler->onFinish(CLIENT(0), JOB(0));
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestFailJob) {
+    ALOGD("TestFailJob");
+
+    // Fail without any jobs submitted, should be ignored.
+    mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kUnknown);
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::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)));
+
+    // 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)));
+
+    // 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);
+
+    // Fail when the job never started, should be ignored.
+    mScheduler->onError(CLIENT(0), JOB(2), TranscodingErrorCode::kUnknown);
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::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)));
+
+    // 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)));
+
+    // 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)));
+
+    // 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)));
+
+    // 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);
+
+    // Duplicate fail for last job, should be ignored.
+    mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kUnknown);
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestTopProcessChanged) {
+    ALOGD("TestTopProcessChanged");
+
+    // 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)));
+
+    // 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);
+
+    // Move PID(1) to top.
+    mCallback->setTop(PID(1));
+    // Submit real-time job to CLIENT(2) in different pid PID(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)));
+
+    // 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 invalid process to top.
+    mScheduler->onTopProcessChanged(kInvalidPid);
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::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)));
+
+    // 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)));
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestResourceLost) {
+    ALOGD("TestResourceLost");
+
+    // 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)));
+
+    // 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);
+
+    // Move PID(1) to top.
+    mCallback->setTop(PID(1));
+
+    // Submit real-time job to CLIENT(2) in different pid PID(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)));
+
+    // Test 1: No queue change during resource loss.
+    // Signal resource lost.
+    mScheduler->onResourceLost();
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+    // Signal resource available, CLIENT(2) should resume.
+    mScheduler->onResourceAvailable();
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::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);
+
+    // Move PID(0) back to top, should have no resume due to no resource.
+    mScheduler->onTopProcessChanged(PID(0));
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+    // Signal resource available, CLIENT(0) should resume.
+    mScheduler->onResourceAvailable();
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+
+    // Test 3: Adding new queue during resource loss.
+    // Signal resource lost.
+    mScheduler->onResourceLost();
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+    // Move PID(2) to top.
+    mCallback->setTop(PID(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);
+
+    // Signal resource available, CLIENT(3)'s job should start.
+    mScheduler->onResourceAvailable();
+    EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(3), JOB(0)));
+}
+
+}  // namespace android
diff --git a/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh b/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh
index ec15a21..7ab92ae 100644
--- a/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh
+++ b/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh
@@ -27,3 +27,7 @@
 #adb shell /data/nativetest64/AdjustableMaxPriorityQueue_tests/AdjustableMaxPriorityQueue_tests
 adb shell /data/nativetest/AdjustableMaxPriorityQueue_tests/AdjustableMaxPriorityQueue_tests
 
+echo "testing TranscodingJobScheduler"
+#adb shell /data/nativetest64/TranscodingJobScheduler_tests/TranscodingJobScheduler_tests
+adb shell /data/nativetest/TranscodingJobScheduler_tests/TranscodingJobScheduler_tests
+