Hanlding Streams and States

Test: Build && `atest android.hardware.automotive.evs-aidl-default-service_cam_state_test`
Bug: 277861838
Change-Id: Idd8fe8e76876dfd93a177d3529ccf118b626001d
diff --git a/automotive/evs/aidl/impl/default/Android.bp b/automotive/evs/aidl/impl/default/Android.bp
index c56fe2b..6b638a3 100644
--- a/automotive/evs/aidl/impl/default/Android.bp
+++ b/automotive/evs/aidl/impl/default/Android.bp
@@ -105,4 +105,21 @@
         "android.hardware.automotive.evs-aidl-default-service-lib",
         "libgmock",
     ],
+    test_suites: [
+        "general-tests",
+    ],
+}
+
+cc_test {
+    name: "android.hardware.automotive.evs-aidl-default-service_cam_state_test",
+    defaults: ["android.hardware.automotive.evs-aidl-default-service-default"],
+    vendor: true,
+    srcs: ["tests/EvsCameraStateTest.cpp"],
+    static_libs: [
+        "android.hardware.automotive.evs-aidl-default-service-lib",
+        "libgmock",
+    ],
+    test_suites: [
+        "general-tests",
+    ],
 }
diff --git a/automotive/evs/aidl/impl/default/include/EvsCamera.h b/automotive/evs/aidl/impl/default/include/EvsCamera.h
index 3663e3d..5774db5 100644
--- a/automotive/evs/aidl/impl/default/include/EvsCamera.h
+++ b/automotive/evs/aidl/impl/default/include/EvsCamera.h
@@ -18,6 +18,7 @@
 
 #include "EvsCameraBase.h"
 
+#include <aidl/android/hardware/automotive/evs/IEvsCameraStream.h>
 #include <cutils/native_handle.h>
 
 #include <cstddef>
@@ -45,11 +46,41 @@
 
     ndk::ScopedAStatus setMaxFramesInFlight(int32_t bufferCount) override;
 
+    ndk::ScopedAStatus startVideoStream(
+            const std::shared_ptr<evs::IEvsCameraStream>& receiver) override;
+
+    ndk::ScopedAStatus stopVideoStream() override;
+
+    ndk::ScopedAStatus pauseVideoStream() override;
+
+    ndk::ScopedAStatus resumeVideoStream() override;
+
   protected:
     virtual ::android::status_t allocateOneFrame(buffer_handle_t* handle) = 0;
 
     virtual void freeOneFrame(const buffer_handle_t handle);
 
+    virtual bool preVideoStreamStart_locked(const std::shared_ptr<evs::IEvsCameraStream>& receiver,
+                                            ndk::ScopedAStatus& status,
+                                            std::unique_lock<std::mutex>& lck);
+
+    virtual bool startVideoStreamImpl_locked(const std::shared_ptr<evs::IEvsCameraStream>& receiver,
+                                             ndk::ScopedAStatus& status,
+                                             std::unique_lock<std::mutex>& lck) = 0;
+
+    virtual bool postVideoStreamStart_locked(const std::shared_ptr<evs::IEvsCameraStream>& receiver,
+                                             ndk::ScopedAStatus& status,
+                                             std::unique_lock<std::mutex>& lck);
+
+    virtual bool preVideoStreamStop_locked(ndk::ScopedAStatus& status,
+                                           std::unique_lock<std::mutex>& lck);
+
+    virtual bool stopVideoStreamImpl_locked(ndk::ScopedAStatus& status,
+                                            std::unique_lock<std::mutex>& lck) = 0;
+
+    virtual bool postVideoStreamStop_locked(ndk::ScopedAStatus& status,
+                                            std::unique_lock<std::mutex>& lck);
+
     void shutdown() override;
 
     void closeAllBuffers_unsafe();
@@ -81,6 +112,15 @@
         bool inUse{false};
     };
 
+    enum class StreamState {
+        STOPPED = 0,
+        RUNNING = 1,
+        STOPPING = 2,
+        DEAD = 3,
+    };
+
+    StreamState mStreamState{StreamState::STOPPED};
+
     std::mutex mMutex;
 
     // Graphics buffers to transfer images, always in the order of:
diff --git a/automotive/evs/aidl/impl/default/src/EvsCamera.cpp b/automotive/evs/aidl/impl/default/src/EvsCamera.cpp
index db3be8f..208a1a4 100644
--- a/automotive/evs/aidl/impl/default/src/EvsCamera.cpp
+++ b/automotive/evs/aidl/impl/default/src/EvsCamera.cpp
@@ -32,6 +32,9 @@
 // Safeguards against unreasonable resource consumption and provides a testable limit
 constexpr std::size_t kMaxBuffersInFlight = 100;
 
+// Minimum number of buffers to run a video stream
+constexpr int kMinimumBuffersInFlight = 1;
+
 EvsCamera::~EvsCamera() {
     shutdown();
 }
@@ -108,6 +111,113 @@
     alloc.free(handle);
 }
 
+bool EvsCamera::preVideoStreamStart_locked(const std::shared_ptr<evs::IEvsCameraStream>& receiver,
+                                           ndk::ScopedAStatus& status,
+                                           std::unique_lock<std::mutex>& /* lck */) {
+    if (!receiver) {
+        LOG(ERROR) << __func__ << ": Null receiver.";
+        status = ndk::ScopedAStatus::fromServiceSpecificError(
+                static_cast<int>(EvsResult::INVALID_ARG));
+        return false;
+    }
+
+    // If we've been displaced by another owner of the camera, then we can't do anything else
+    if (mStreamState == StreamState::DEAD) {
+        LOG(ERROR) << __func__ << ": Ignoring when camera has been lost.";
+        status = ndk::ScopedAStatus::fromServiceSpecificError(
+                static_cast<int>(EvsResult::OWNERSHIP_LOST));
+        return false;
+    }
+
+    if (mStreamState != StreamState::STOPPED) {
+        LOG(ERROR) << __func__ << ": Ignoring when a stream is already running.";
+        status = ndk::ScopedAStatus::fromServiceSpecificError(
+                static_cast<int>(EvsResult::STREAM_ALREADY_RUNNING));
+        return false;
+    }
+
+    // If the client never indicated otherwise, configure ourselves for a single streaming buffer
+    if (mAvailableFrames < kMinimumBuffersInFlight &&
+        !setAvailableFrames_unsafe(kMinimumBuffersInFlight)) {
+        LOG(ERROR) << __func__ << "Failed to because we could not get a graphics buffer.";
+        status = ndk::ScopedAStatus::fromServiceSpecificError(
+                static_cast<int>(EvsResult::BUFFER_NOT_AVAILABLE));
+        return false;
+    }
+    mStreamState = StreamState::RUNNING;
+    return true;
+}
+
+bool EvsCamera::postVideoStreamStart_locked(
+        const std::shared_ptr<evs::IEvsCameraStream>& /* receiver */,
+        ndk::ScopedAStatus& /* status */, std::unique_lock<std::mutex>& /* lck */) {
+    return true;
+}
+
+bool EvsCamera::preVideoStreamStop_locked(ndk::ScopedAStatus& status,
+                                          std::unique_lock<std::mutex>& /* lck */) {
+    if (mStreamState != StreamState::RUNNING) {
+        // Terminate the stop process because a stream is not running.
+        status = ndk::ScopedAStatus::ok();
+        return false;
+    }
+    mStreamState = StreamState::STOPPING;
+    return true;
+}
+
+bool EvsCamera::postVideoStreamStop_locked(ndk::ScopedAStatus& /* status */,
+                                           std::unique_lock<std::mutex>& /* lck */) {
+    mStreamState = StreamState::STOPPED;
+    return true;
+}
+
+ndk::ScopedAStatus EvsCamera::startVideoStream(
+        const std::shared_ptr<evs::IEvsCameraStream>& receiver) {
+    bool needShutdown = false;
+    auto status = ndk::ScopedAStatus::ok();
+    {
+        std::unique_lock lck(mMutex);
+        if (!preVideoStreamStart_locked(receiver, status, lck)) {
+            return status;
+        }
+
+        if ((!startVideoStreamImpl_locked(receiver, status, lck) ||
+             !postVideoStreamStart_locked(receiver, status, lck)) &&
+            !status.isOk()) {
+            needShutdown = true;
+        }
+    }
+    if (needShutdown) {
+        shutdown();
+    }
+    return status;
+}
+
+ndk::ScopedAStatus EvsCamera::stopVideoStream() {
+    bool needShutdown = false;
+    auto status = ndk::ScopedAStatus::ok();
+    {
+        std::unique_lock lck(mMutex);
+        if ((!preVideoStreamStop_locked(status, lck) || !stopVideoStreamImpl_locked(status, lck) ||
+             !postVideoStreamStop_locked(status, lck)) &&
+            !status.isOk()) {
+            needShutdown = true;
+        }
+    }
+    if (needShutdown) {
+        shutdown();
+    }
+    return status;
+}
+
+ndk::ScopedAStatus EvsCamera::pauseVideoStream() {
+    return ndk::ScopedAStatus::fromServiceSpecificError(static_cast<int>(EvsResult::NOT_SUPPORTED));
+}
+
+ndk::ScopedAStatus EvsCamera::resumeVideoStream() {
+    return ndk::ScopedAStatus::fromServiceSpecificError(static_cast<int>(EvsResult::NOT_SUPPORTED));
+}
+
 bool EvsCamera::setAvailableFrames_unsafe(const std::size_t bufferCount) {
     if (bufferCount < 1) {
         LOG(ERROR) << "Ignoring request to set buffer count to zero.";
@@ -159,8 +269,10 @@
 }
 
 void EvsCamera::shutdown() {
+    stopVideoStream();
     std::lock_guard lck(mMutex);
     closeAllBuffers_unsafe();
+    mStreamState = StreamState::DEAD;
 }
 
 void EvsCamera::closeAllBuffers_unsafe() {
diff --git a/automotive/evs/aidl/impl/default/tests/EvsCameraBufferTest.cpp b/automotive/evs/aidl/impl/default/tests/EvsCameraBufferTest.cpp
index 48f0890..3cbc04a 100644
--- a/automotive/evs/aidl/impl/default/tests/EvsCameraBufferTest.cpp
+++ b/automotive/evs/aidl/impl/default/tests/EvsCameraBufferTest.cpp
@@ -77,8 +77,6 @@
                 (const std::string& in_deviceId,
                  ::aidl::android::hardware::automotive::evs::CameraDesc* _aidl_return),
                 (override));
-    MOCK_METHOD(::ndk::ScopedAStatus, pauseVideoStream, (), (override));
-    MOCK_METHOD(::ndk::ScopedAStatus, resumeVideoStream, (), (override));
     MOCK_METHOD(::ndk::ScopedAStatus, setExtendedInfo,
                 (int32_t in_opaqueIdentifier, const std::vector<uint8_t>& in_opaqueValue),
                 (override));
@@ -87,12 +85,13 @@
                  std::vector<int32_t>* _aidl_return),
                 (override));
     MOCK_METHOD(::ndk::ScopedAStatus, setPrimaryClient, (), (override));
-    MOCK_METHOD(::ndk::ScopedAStatus, startVideoStream,
-                (const std::shared_ptr<
-                        ::aidl::android::hardware::automotive::evs::IEvsCameraStream>& in_receiver),
-                (override));
-    MOCK_METHOD(::ndk::ScopedAStatus, stopVideoStream, (), (override));
     MOCK_METHOD(::ndk::ScopedAStatus, unsetPrimaryClient, (), (override));
+    MOCK_METHOD(bool, startVideoStreamImpl_locked,
+                (const std::shared_ptr<evs::IEvsCameraStream>& receiver, ndk::ScopedAStatus& status,
+                 std::unique_lock<std::mutex>& lck),
+                (override));
+    MOCK_METHOD(bool, stopVideoStreamImpl_locked,
+                (ndk::ScopedAStatus & status, std::unique_lock<std::mutex>& lck), (override));
 };
 
 TEST(EvsCameraBufferTest, ChangeBufferPoolSize) {
@@ -118,7 +117,7 @@
     evsCam->checkBufferOrder();
 }
 
-TEST(EvsCameraForTest, UseAndReturn) {
+TEST(EvsCameraBufferTest, UseAndReturn) {
     constexpr std::size_t kNumOfHandles = 20;
     auto evsCam = ndk::SharedRefBase::make<EvsCameraForTest>();
 
diff --git a/automotive/evs/aidl/impl/default/tests/EvsCameraStateTest.cpp b/automotive/evs/aidl/impl/default/tests/EvsCameraStateTest.cpp
new file mode 100644
index 0000000..1925c79
--- /dev/null
+++ b/automotive/evs/aidl/impl/default/tests/EvsCameraStateTest.cpp
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "EvsCamera.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <cstdint>
+#include <unordered_set>
+#include <vector>
+
+namespace aidl::android::hardware::automotive::evs::implementation {
+
+class EvsCameraForTest : public EvsCamera {
+  private:
+    using Base = EvsCamera;
+
+  public:
+    using EvsCamera::mStreamState;
+    using EvsCamera::shutdown;
+    using EvsCamera::StreamState;
+
+    ~EvsCameraForTest() override { shutdown(); }
+
+    ::android::status_t allocateOneFrame(buffer_handle_t* handle) override {
+        static std::intptr_t handle_cnt = 0;
+        *handle = reinterpret_cast<buffer_handle_t>(++handle_cnt);
+        return ::android::OK;
+    }
+
+    void freeOneFrame(const buffer_handle_t /* handle */) override {
+        // Nothing to free because the handles are fake.
+    }
+
+    bool preVideoStreamStart_locked(const std::shared_ptr<evs::IEvsCameraStream>& receiver,
+                                    ndk::ScopedAStatus& status,
+                                    std::unique_lock<std::mutex>& lck) override {
+        mPreStartCalled = true;
+        EXPECT_EQ(mStreamState, StreamState::STOPPED);
+        EXPECT_FALSE(mStreamStarted);
+        EXPECT_FALSE(mStreamStopped);
+        return Base::preVideoStreamStart_locked(receiver, status, lck);
+    }
+
+    bool startVideoStreamImpl_locked(const std::shared_ptr<evs::IEvsCameraStream>& /* receiver */,
+                                     ndk::ScopedAStatus& /* status */,
+                                     std::unique_lock<std::mutex>& /* lck */) override {
+        EXPECT_EQ(mStreamState, StreamState::RUNNING);
+        EXPECT_FALSE(mStreamStarted);
+        EXPECT_FALSE(mStreamStopped);
+        mStreamStarted = true;
+        return true;
+    }
+
+    bool postVideoStreamStart_locked(const std::shared_ptr<evs::IEvsCameraStream>& receiver,
+                                     ndk::ScopedAStatus& status,
+                                     std::unique_lock<std::mutex>& lck) override {
+        mPostStartCalled = true;
+        EXPECT_EQ(mStreamState, StreamState::RUNNING);
+        EXPECT_TRUE(mStreamStarted);
+        EXPECT_FALSE(mStreamStopped);
+        return Base::postVideoStreamStart_locked(receiver, status, lck);
+    }
+
+    bool preVideoStreamStop_locked(ndk::ScopedAStatus& status,
+                                   std::unique_lock<std::mutex>& lck) override {
+        // Skip the check if stop was called before.
+        if (!mPreStopCalled) {
+            mPreStopCalled = true;
+            EXPECT_EQ(mStreamState, StreamState::RUNNING);
+            EXPECT_TRUE(mStreamStarted);
+            EXPECT_FALSE(mStreamStopped);
+        }
+        return Base::preVideoStreamStop_locked(status, lck);
+    }
+
+    bool stopVideoStreamImpl_locked(ndk::ScopedAStatus& /* status */,
+                                    std::unique_lock<std::mutex>& /* lck */) override {
+        EXPECT_EQ(mStreamState, StreamState::STOPPING);
+        EXPECT_TRUE(mStreamStarted);
+        EXPECT_FALSE(mStreamStopped);
+        mStreamStopped = true;
+        return true;
+    }
+
+    bool postVideoStreamStop_locked(ndk::ScopedAStatus& status,
+                                    std::unique_lock<std::mutex>& lck) override {
+        mPostStopCalled = true;
+        const auto ret = Base::postVideoStreamStop_locked(status, lck);
+        EXPECT_EQ(mStreamState, StreamState::STOPPED);
+        EXPECT_TRUE(mStreamStarted);
+        EXPECT_TRUE(mStreamStopped);
+        return ret;
+    }
+
+    MOCK_METHOD(::ndk::ScopedAStatus, forcePrimaryClient,
+                (const std::shared_ptr<::aidl::android::hardware::automotive::evs::IEvsDisplay>&
+                         in_display),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getCameraInfo,
+                (::aidl::android::hardware::automotive::evs::CameraDesc * _aidl_return),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getExtendedInfo,
+                (int32_t in_opaqueIdentifier, std::vector<uint8_t>* _aidl_return), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getIntParameter,
+                (::aidl::android::hardware::automotive::evs::CameraParam in_id,
+                 std::vector<int32_t>* _aidl_return),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getIntParameterRange,
+                (::aidl::android::hardware::automotive::evs::CameraParam in_id,
+                 ::aidl::android::hardware::automotive::evs::ParameterRange* _aidl_return),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getParameterList,
+                (std::vector<::aidl::android::hardware::automotive::evs::CameraParam> *
+                 _aidl_return),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getPhysicalCameraInfo,
+                (const std::string& in_deviceId,
+                 ::aidl::android::hardware::automotive::evs::CameraDesc* _aidl_return),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, setExtendedInfo,
+                (int32_t in_opaqueIdentifier, const std::vector<uint8_t>& in_opaqueValue),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, setIntParameter,
+                (::aidl::android::hardware::automotive::evs::CameraParam in_id, int32_t in_value,
+                 std::vector<int32_t>* _aidl_return),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, setPrimaryClient, (), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, unsetPrimaryClient, (), (override));
+
+    bool mStreamStarted = false;
+    bool mStreamStopped = false;
+    bool mPreStartCalled = false;
+    bool mPostStartCalled = false;
+    bool mPreStopCalled = false;
+    bool mPostStopCalled = false;
+};
+
+class MockEvsCameraStream : public evs::IEvsCameraStream {
+    MOCK_METHOD(::ndk::SpAIBinder, asBinder, (), (override));
+    MOCK_METHOD(bool, isRemote, (), (override));
+    MOCK_METHOD(
+            ::ndk::ScopedAStatus, deliverFrame,
+            (const std::vector<::aidl::android::hardware::automotive::evs::BufferDesc>& in_buffer),
+            (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, notify,
+                (const ::aidl::android::hardware::automotive::evs::EvsEventDesc& in_event),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getInterfaceVersion, (int32_t * _aidl_return), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getInterfaceHash, (std::string * _aidl_return), (override));
+};
+
+using StreamState = EvsCameraForTest::StreamState;
+
+TEST(EvsCameraStateTest, StateChangeHooks) {
+    auto evsCam = ndk::SharedRefBase::make<EvsCameraForTest>();
+    auto mockStream = ndk::SharedRefBase::make<MockEvsCameraStream>();
+    EXPECT_FALSE(evsCam->mPreStartCalled);
+    EXPECT_FALSE(evsCam->mPostStartCalled);
+    EXPECT_FALSE(evsCam->mPreStopCalled);
+    EXPECT_FALSE(evsCam->mPostStopCalled);
+    EXPECT_FALSE(evsCam->mStreamStarted);
+    EXPECT_FALSE(evsCam->mStreamStopped);
+    EXPECT_EQ(evsCam->mStreamState, StreamState::STOPPED);
+    evsCam->startVideoStream(mockStream);
+
+    EXPECT_TRUE(evsCam->mPreStartCalled);
+    EXPECT_TRUE(evsCam->mPostStartCalled);
+    EXPECT_FALSE(evsCam->mPreStopCalled);
+    EXPECT_FALSE(evsCam->mPostStopCalled);
+    EXPECT_TRUE(evsCam->mStreamStarted);
+    EXPECT_FALSE(evsCam->mStreamStopped);
+    EXPECT_EQ(evsCam->mStreamState, StreamState::RUNNING);
+    evsCam->stopVideoStream();
+
+    EXPECT_TRUE(evsCam->mPreStartCalled);
+    EXPECT_TRUE(evsCam->mPostStartCalled);
+    EXPECT_TRUE(evsCam->mPreStopCalled);
+    EXPECT_TRUE(evsCam->mPostStopCalled);
+    EXPECT_TRUE(evsCam->mStreamStarted);
+    EXPECT_TRUE(evsCam->mStreamStopped);
+    EXPECT_EQ(evsCam->mStreamState, StreamState::STOPPED);
+
+    evsCam->shutdown();
+    EXPECT_EQ(evsCam->mStreamState, StreamState::DEAD);
+}
+
+}  // namespace aidl::android::hardware::automotive::evs::implementation