transcoder: initial version of pause/resume

- Add pause/resume in TranscoderWrapper, save paused state on pause
  and use it to create new transcoder on resume.

Misc fixes:

- TranscoderWrapper::stop should only cancel transcoder if the stop
  is for the currently running job. Scheduler could call stop to
  cancel a job any time.
- Don't hold TranscoderWrapper lock when running event runnable. If
  the runnable calls back into scheduler, and scheduler may call
  transcoder again and deadlock.
- Don't report abort as error if the transcoder is cancelled explicitly.
- Push decoder/encoder start as msgs, so that they could be skipped too
  if the job is cancelled shortly after starts.

Tests:
Add tests for cancel/pause/resume with real transcoder.

bug: 154734285
bug: 154733948
test: unit testing
Change-Id: I2b7d3da69df53b92ab351db455310799ba0e0e8f
diff --git a/services/mediatranscoding/SimulatedTranscoder.cpp b/services/mediatranscoding/SimulatedTranscoder.cpp
index 5aa325f..97d5f5f 100644
--- a/services/mediatranscoding/SimulatedTranscoder.cpp
+++ b/services/mediatranscoding/SimulatedTranscoder.cpp
@@ -72,7 +72,9 @@
     });
 }
 
-void SimulatedTranscoder::resume(ClientIdType clientId, JobIdType jobId) {
+void SimulatedTranscoder::resume(
+        ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& /*request*/,
+        const std::shared_ptr<ITranscodingClientCallback>& /*clientCallback*/) {
     queueEvent(Event::Resume, clientId, jobId, [=] {
         auto callback = mCallback.lock();
         if (callback != nullptr) {
diff --git a/services/mediatranscoding/SimulatedTranscoder.h b/services/mediatranscoding/SimulatedTranscoder.h
index 030222b..1c359dd 100644
--- a/services/mediatranscoding/SimulatedTranscoder.h
+++ b/services/mediatranscoding/SimulatedTranscoder.h
@@ -54,7 +54,8 @@
     void start(ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
                const std::shared_ptr<ITranscodingClientCallback>& clientCallback) override;
     void pause(ClientIdType clientId, JobIdType jobId) override;
-    void resume(ClientIdType clientId, JobIdType jobId) override;
+    void resume(ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
+                const std::shared_ptr<ITranscodingClientCallback>& clientCallback) override;
     void stop(ClientIdType clientId, JobIdType jobId) override;
     // ~TranscoderInterface
 
diff --git a/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h b/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h
index 4259f54..2f4e74b 100644
--- a/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h
+++ b/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h
@@ -66,6 +66,8 @@
 constexpr const char* kClientPackageB = "com.android.tests.transcoding.testapp.B";
 constexpr const char* kClientPackageC = "com.android.tests.transcoding.testapp.C";
 
+constexpr const char* kTestActivityName = "/com.android.tests.transcoding.MainActivity";
+
 static status_t getUidForPackage(String16 packageName, userid_t userId, /*inout*/ uid_t& uid) {
     PermissionController pc;
     uid = pc.getPackageUid(packageName, 0);
@@ -167,20 +169,28 @@
     }
 
     // Push 1 event to back.
-    void append(const Event& event) {
+    void append(const Event& event,
+                const TranscodingErrorCode err = TranscodingErrorCode::kNoError) {
         ALOGD("%s", toString(event).c_str());
 
         std::unique_lock lock(mLock);
 
         mEventQueue.push_back(event);
+        mLastErr = err;
         mCondition.notify_one();
     }
 
+    TranscodingErrorCode getLastError() {
+        std::unique_lock lock(mLock);
+        return mLastErr;
+    }
+
 private:
     std::mutex mLock;
     std::condition_variable mCondition;
     Event mPoppedEvent;
     std::list<Event> mEventQueue;
+    TranscodingErrorCode mLastErr;
 };
 
 // Operators for GTest macros.
@@ -205,8 +215,14 @@
         ALOGD("@@@ openFileDescriptor: %s", in_fileUri.c_str());
         int fd;
         if (in_mode == "w" || in_mode == "rw") {
-            // Write-only, create file if non-existent, don't overwrite existing file.
-            constexpr int kOpenFlags = O_WRONLY | O_CREAT | O_EXCL;
+            int kOpenFlags;
+            if (in_mode == "w") {
+                // Write-only, create file if non-existent, truncate existing file.
+                kOpenFlags = O_WRONLY | O_CREAT | O_TRUNC;
+            } else {
+                // Read-Write, create if non-existent, no truncate (service will truncate if needed)
+                kOpenFlags = O_RDWR | O_CREAT;
+            }
             // User R+W permission.
             constexpr int kFileMode = S_IRUSR | S_IWUSR;
             fd = open(in_fileUri.c_str(), kOpenFlags, kFileMode);
@@ -239,10 +255,9 @@
         return Status::ok();
     }
 
-    Status onTranscodingFailed(
-            int32_t in_jobId,
-            ::aidl::android::media::TranscodingErrorCode /* in_errorCode */) override {
-        append(Failed(mClientId, in_jobId));
+    Status onTranscodingFailed(int32_t in_jobId,
+                               ::aidl::android::media::TranscodingErrorCode in_errorCode) override {
+        append(Failed(mClientId, in_jobId), in_errorCode);
         return Status::ok();
     }
 
diff --git a/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp b/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp
index 8cecfed..c6368a8 100644
--- a/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp
+++ b/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp
@@ -35,9 +35,13 @@
 
 constexpr int64_t kPaddingUs = 200000;
 constexpr int64_t kJobWithPaddingUs = 10000000 + kPaddingUs;
+constexpr int32_t kBitRate = 8 * 1000 * 1000;  // 8Mbs
 
-constexpr const char* kSrcPath =
+constexpr const char* kShortSrcPath =
         "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+constexpr const char* kLongSrcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
+
+#define OUTPATH(name) "/data/local/tmp/MediaTranscodingService_" #name ".MP4"
 
 class MediaTranscodingServiceRealTest : public MediaTranscodingServiceTestBase {
 public:
@@ -46,16 +50,34 @@
     void deleteFile(const char* path) { unlink(path); }
 };
 
-TEST_F(MediaTranscodingServiceRealTest, TestTranscodePassthru) {
+TEST_F(MediaTranscodingServiceRealTest, TestInvalidSource) {
     registerMultipleClients();
 
-    const char* dstPath = "/data/local/tmp/MediaTranscodingService_Passthru.MP4";
+    const char* srcPath = "bad_file_uri";
+    const char* dstPath = OUTPATH(TestInvalidSource);
     deleteFile(dstPath);
 
     // Submit one job.
-    EXPECT_TRUE(submit(mClient1, 0, kSrcPath, dstPath));
+    EXPECT_TRUE(submit(mClient1, 0, srcPath, dstPath, TranscodingJobPriority::kNormal, kBitRate));
+
+    // Check expected error.
+    EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Failed(CLIENT(1), 0));
+    EXPECT_EQ(mClientCallback1->getLastError(), TranscodingErrorCode::kErrorIO);
+
+    unregisterMultipleClients();
+}
+
+TEST_F(MediaTranscodingServiceRealTest, TestPassthru) {
+    registerMultipleClients();
+
+    const char* dstPath = OUTPATH(TestPassthru);
+    deleteFile(dstPath);
+
+    // Submit one job.
+    EXPECT_TRUE(submit(mClient1, 0, kShortSrcPath, dstPath));
 
     // Wait for job to finish.
+    EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
     EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
 
     unregisterMultipleClients();
@@ -64,20 +86,187 @@
 TEST_F(MediaTranscodingServiceRealTest, TestTranscodeVideo) {
     registerMultipleClients();
 
-    const char* dstPath = "/data/local/tmp/MediaTranscodingService_Video.MP4";
+    const char* dstPath = OUTPATH(TestTranscodeVideo);
     deleteFile(dstPath);
 
-    const int32_t kBitRate = 8 * 1000 * 1000;  // 8Mbs
     // Submit one job.
-    EXPECT_TRUE(submit(mClient1, 0, kSrcPath, dstPath, TranscodingJobPriority::kNormal, kBitRate));
+    EXPECT_TRUE(
+            submit(mClient1, 0, kShortSrcPath, dstPath, TranscodingJobPriority::kNormal, kBitRate));
 
     // Wait for job to finish.
+    EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
     EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
 
-    // TODO: verify output file format.
+    unregisterMultipleClients();
+}
+
+/*
+ * Test cancel immediately after start.
+ */
+TEST_F(MediaTranscodingServiceRealTest, TestCancelImmediately) {
+    registerMultipleClients();
+
+    const char* srcPath0 = kLongSrcPath;
+    const char* srcPath1 = kShortSrcPath;
+    const char* dstPath0 = OUTPATH(TestCancelImmediately_Job0);
+    const char* dstPath1 = OUTPATH(TestCancelImmediately_Job1);
+
+    deleteFile(dstPath0);
+    deleteFile(dstPath1);
+    // Submit one job, should start immediately.
+    EXPECT_TRUE(submit(mClient1, 0, srcPath0, dstPath0, TranscodingJobPriority::kNormal, kBitRate));
+    EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+    EXPECT_TRUE(getJob(mClient1, 0, srcPath0, dstPath0));
+
+    // Test cancel job immediately, getJob should fail after cancel.
+    EXPECT_TRUE(cancel(mClient1, 0));
+    EXPECT_TRUE(getJob<fail>(mClient1, 0, "", ""));
+
+    // Submit new job, new job should start immediately and finish.
+    EXPECT_TRUE(submit(mClient1, 1, srcPath1, dstPath1, TranscodingJobPriority::kNormal, kBitRate));
+    EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 1));
+    EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 1));
 
     unregisterMultipleClients();
 }
 
+/*
+ * Test cancel in the middle of transcoding.
+ */
+TEST_F(MediaTranscodingServiceRealTest, TestCancelWhileRunning) {
+    registerMultipleClients();
+
+    const char* srcPath0 = kLongSrcPath;
+    const char* srcPath1 = kShortSrcPath;
+    const char* dstPath0 = OUTPATH(TestCancelWhileRunning_Job0);
+    const char* dstPath1 = OUTPATH(TestCancelWhileRunning_Job1);
+
+    deleteFile(dstPath0);
+    deleteFile(dstPath1);
+    // Submit two jobs, job 0 should start immediately, job 1 should be queued.
+    EXPECT_TRUE(submit(mClient1, 0, srcPath0, dstPath0, TranscodingJobPriority::kNormal, kBitRate));
+    EXPECT_TRUE(submit(mClient1, 1, srcPath1, dstPath1, TranscodingJobPriority::kNormal, kBitRate));
+    EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+    EXPECT_TRUE(getJob(mClient1, 0, srcPath0, dstPath0));
+    EXPECT_TRUE(getJob(mClient1, 1, srcPath1, dstPath1));
+
+    // Job 0 (longtest) shouldn't finish in 1 seconds.
+    EXPECT_EQ(mClientCallback1->pop(1000000), EventTracker::NoEvent);
+
+    // Now cancel job 0. Job 1 should start immediately and finish.
+    EXPECT_TRUE(cancel(mClient1, 0));
+    EXPECT_TRUE(getJob<fail>(mClient1, 0, "", ""));
+    EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 1));
+    EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 1));
+
+    unregisterMultipleClients();
+}
+
+TEST_F(MediaTranscodingServiceRealTest, TestPauseResumeSingleClient) {
+    registerMultipleClients();
+
+    const char* srcPath0 = kLongSrcPath;
+    const char* srcPath1 = kShortSrcPath;
+    const char* dstPath0 = OUTPATH(TestPauseResumeSingleClient_Job0);
+    const char* dstPath1 = OUTPATH(TestPauseResumeSingleClient_Job1);
+    deleteFile(dstPath0);
+    deleteFile(dstPath1);
+
+    // Submit one offline job, should start immediately.
+    EXPECT_TRUE(submit(mClient1, 0, srcPath0, dstPath0, TranscodingJobPriority::kUnspecified,
+                       kBitRate));
+    EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+    // Test get job after starts.
+    EXPECT_TRUE(getJob(mClient1, 0, srcPath0, dstPath0));
+
+    // Submit one realtime job.
+    EXPECT_TRUE(submit(mClient1, 1, srcPath1, dstPath1, TranscodingJobPriority::kNormal, kBitRate));
+
+    // Offline job should pause.
+    EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Pause(CLIENT(1), 0));
+    EXPECT_TRUE(getJob(mClient1, 0, srcPath0, dstPath0));
+
+    // Realtime job should start immediately, and run to finish.
+    EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 1));
+    EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 1));
+
+    // Test get job after finish fails.
+    EXPECT_TRUE(getJob<fail>(mClient1, 1, "", ""));
+
+    // Then offline job should resume.
+    EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Resume(CLIENT(1), 0));
+    // Test get job after resume.
+    EXPECT_TRUE(getJob(mClient1, 0, srcPath0, dstPath0));
+
+    // Offline job should finish.
+    EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
+    // Test get job after finish fails.
+    EXPECT_TRUE(getJob<fail>(mClient1, 0, "", ""));
+
+    unregisterMultipleClients();
+}
+
+/*
+ * Basic test for pause/resume with two clients, with one job each.
+ * Top app's job should preempt the other app's job.
+ */
+TEST_F(MediaTranscodingServiceRealTest, TestPauseResumeMultiClients) {
+    ALOGD("TestPauseResumeMultiClients starting...");
+
+    EXPECT_TRUE(ShellHelper::RunCmd("input keyevent KEYCODE_WAKEUP"));
+    EXPECT_TRUE(ShellHelper::RunCmd("wm dismiss-keyguard"));
+    EXPECT_TRUE(ShellHelper::Stop(kClientPackageA));
+    EXPECT_TRUE(ShellHelper::Stop(kClientPackageB));
+    EXPECT_TRUE(ShellHelper::Stop(kClientPackageC));
+
+    registerMultipleClients();
+
+    const char* srcPath0 = kLongSrcPath;
+    const char* srcPath1 = kShortSrcPath;
+    const char* dstPath0 = OUTPATH(TestPauseResumeMultiClients_Client0);
+    const char* dstPath1 = OUTPATH(TestPauseResumeMultiClients_Client1);
+    deleteFile(dstPath0);
+    deleteFile(dstPath1);
+
+    ALOGD("Moving app A to top...");
+    EXPECT_TRUE(ShellHelper::Start(kClientPackageA, kTestActivityName));
+
+    // Submit job to Client1.
+    ALOGD("Submitting job to client1 (app A) ...");
+    EXPECT_TRUE(submit(mClient1, 0, srcPath0, dstPath0, TranscodingJobPriority::kNormal, kBitRate));
+
+    // Client1's job should start immediately.
+    EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+
+    ALOGD("Moving app B to top...");
+    EXPECT_TRUE(ShellHelper::Start(kClientPackageB, kTestActivityName));
+
+    // Client1's job should continue to run, since Client2 (app B) doesn't have any job.
+    EXPECT_EQ(mClientCallback1->pop(1000000), EventTracker::NoEvent);
+
+    // Submit job to Client2.
+    ALOGD("Submitting job to client2 (app B) ...");
+    EXPECT_TRUE(submit(mClient2, 0, srcPath1, dstPath1, TranscodingJobPriority::kNormal, kBitRate));
+
+    // Client1's job should pause, client2's job should start.
+    EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Pause(CLIENT(1), 0));
+    EXPECT_EQ(mClientCallback2->pop(kPaddingUs), EventTracker::Start(CLIENT(2), 0));
+
+    // Client2's job should finish, then Client1's job should resume.
+    EXPECT_EQ(mClientCallback2->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(2), 0));
+    EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Resume(CLIENT(1), 0));
+
+    // Client1's job should finish.
+    EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
+
+    unregisterMultipleClients();
+
+    EXPECT_TRUE(ShellHelper::Stop(kClientPackageA));
+    EXPECT_TRUE(ShellHelper::Stop(kClientPackageB));
+    EXPECT_TRUE(ShellHelper::Stop(kClientPackageC));
+
+    ALOGD("TestPauseResumeMultiClients finished.");
+}
+
 }  // namespace media
 }  // namespace android
diff --git a/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp b/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp
index 309e39d..42b5877 100644
--- a/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp
+++ b/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp
@@ -56,7 +56,6 @@
 constexpr int64_t kJobWithPaddingUs = SimulatedTranscoder::kJobDurationUs + kPaddingUs;
 
 constexpr const char* kClientOpPackageName = "TestClientPackage";
-constexpr const char* kTestActivityName = "/com.android.tests.transcoding.MainActivity";
 
 class MediaTranscodingServiceSimulatedTest : public MediaTranscodingServiceTestBase {
 public: