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