transcoding: add SimulatedTranscoder to test service

- Add SimulatedTranscoder (which is an upgraded version of
  DummyTranscoder) to allow more testing of the service.

- Add unit test that launches dummy test apps from shell
  to simulate uid policy change.

bug: 154734285
bug: 145233472
test: unit tests

Change-Id: Ic169757d64ad8da7eebd0e1febdcbfb467fe81f4
diff --git a/services/mediatranscoding/SimulatedTranscoder.cpp b/services/mediatranscoding/SimulatedTranscoder.cpp
new file mode 100644
index 0000000..7fbfb99
--- /dev/null
+++ b/services/mediatranscoding/SimulatedTranscoder.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SimulatedTranscoder"
+#include "SimulatedTranscoder.h"
+
+#include <utils/Log.h>
+
+#include <thread>
+
+namespace android {
+
+//static
+const char* SimulatedTranscoder::toString(Event::Type type) {
+    switch (type) {
+    case Event::Start:
+        return "Start";
+    case Event::Pause:
+        return "Pause";
+    case Event::Resume:
+        return "Resume";
+    default:
+        break;
+    }
+    return "(unknown)";
+}
+
+SimulatedTranscoder::SimulatedTranscoder() {
+    std::thread(&SimulatedTranscoder::threadLoop, this).detach();
+}
+
+void SimulatedTranscoder::setCallback(const std::shared_ptr<TranscoderCallbackInterface>& cb) {
+    mCallback = cb;
+}
+
+void SimulatedTranscoder::start(int64_t clientId, int32_t jobId) {
+    queueEvent(Event::Start, clientId, jobId);
+}
+
+void SimulatedTranscoder::pause(int64_t clientId, int32_t jobId) {
+    queueEvent(Event::Pause, clientId, jobId);
+}
+
+void SimulatedTranscoder::resume(int64_t clientId, int32_t jobId) {
+    queueEvent(Event::Resume, clientId, jobId);
+}
+
+void SimulatedTranscoder::queueEvent(Event::Type type, int64_t clientId, int32_t jobId) {
+    ALOGV("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)clientId, jobId, toString(type));
+
+    auto lock = std::scoped_lock(mLock);
+
+    mQueue.push_back({type, clientId, jobId});
+    mCondition.notify_one();
+}
+
+void SimulatedTranscoder::threadLoop() {
+    bool running = false;
+    std::chrono::microseconds remainingUs(kJobDurationUs);
+    std::chrono::system_clock::time_point lastRunningTime;
+    Event lastRunningEvent;
+
+    std::unique_lock<std::mutex> lock(mLock);
+    // SimulatedTranscoder currently lives in the transcoding service, as long as
+    // MediaTranscodingService itself.
+    while (true) {
+        // Wait for the next event.
+        while (mQueue.empty()) {
+            if (!running) {
+                mCondition.wait(lock);
+                continue;
+            }
+            // If running, wait for the remaining life of this job. Report finish if timed out.
+            std::cv_status status = mCondition.wait_for(lock, remainingUs);
+            if (status == std::cv_status::timeout) {
+                running = false;
+
+                auto callback = mCallback.lock();
+                if (callback != nullptr) {
+                    lock.unlock();
+                    callback->onFinish(lastRunningEvent.clientId, lastRunningEvent.jobId);
+                    lock.lock();
+                }
+            } else {
+                // Advance last running time and remaining time. This is needed to guard
+                // against bad events (which will be ignored) or spurious wakeups, in that
+                // case we don't want to wait for the same time again.
+                auto now = std::chrono::system_clock::now();
+                remainingUs -= (now - lastRunningTime);
+                lastRunningTime = now;
+            }
+        }
+
+        // Handle the events, adjust state and send updates to client accordingly.
+        while (!mQueue.empty()) {
+            Event event = *mQueue.begin();
+            mQueue.pop_front();
+
+            ALOGV("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)event.clientId, event.jobId,
+                  toString(event.type));
+
+            if (!running && (event.type == Event::Start || event.type == Event::Resume)) {
+                running = true;
+                lastRunningTime = std::chrono::system_clock::now();
+                lastRunningEvent = event;
+                if (event.type == Event::Start) {
+                    remainingUs = std::chrono::microseconds(kJobDurationUs);
+                }
+            } else if (running && event.type == Event::Pause) {
+                running = false;
+                remainingUs -= (std::chrono::system_clock::now() - lastRunningTime);
+            } else {
+                ALOGW("%s: discarding bad event: job {%lld, %d}: %s", __FUNCTION__,
+                        (long long)event.clientId, event.jobId, toString(event.type));
+                continue;
+            }
+
+            auto callback = mCallback.lock();
+            if (callback != nullptr) {
+                lock.unlock();
+                callback->onProgressUpdate(event.clientId, event.jobId, event.type);
+                lock.lock();
+            }
+        }
+    }
+}
+
+}  // namespace android