Add methods to get or set camera parameters

This change adds new methods to set and get camera parameters.  As the
vendor may handle a parameter set request with invalid value, a set
method returns an effective value with a status code.

Bug: 138328396
Test: VTS
Change-Id: I278dad6c285fb9b341be3517cde09359da14cda6
Signed-off-by: Changyeon Jo <changyeon@google.com>
diff --git a/automotive/evs/1.1/IEvsCamera.hal b/automotive/evs/1.1/IEvsCamera.hal
index e7f6bb7..d4263b7 100644
--- a/automotive/evs/1.1/IEvsCamera.hal
+++ b/automotive/evs/1.1/IEvsCamera.hal
@@ -54,4 +54,59 @@
      * @return result Return EvsResult::OK if this call is successful.
      */
     doneWithFrame_1_1(BufferDesc buffer) generates (EvsResult result);
+
+    /**
+     * Requests to be a master client.
+     *
+     * When multiple clients subscribe to a single camera hardware and one of
+     * them adjusts a camera parameter such as the contrast, it may disturb
+     * other clients' operations.  Therefore, the client must call this method
+     * to be a master client.  Once it becomes a master, it will be able to
+     * change camera parameters until either it dies or explicitly gives up the
+     * role.
+     *
+     * @return result EvsResult::OK if a master role is granted.
+     *                EvsResult::OWNERSHIP_LOST if there is already a
+     *                master client.
+     */
+    setMaster() generates (EvsResult result);
+
+
+    /**
+     * Retires from a master client role.
+     *
+     * @return result EvsResult::OK if this call is successful.
+     *                EvsResult::INVALID_ARG if the caller client is not a
+     *                master client.
+     */
+    unsetMaster() generates (EvsResult result);
+
+    /**
+     * Requests to set a camera parameter.
+     *
+     * @param  id             The identifier of camera parameter, CameraParam enum.
+     *         value          A desired parameter value.
+     * @return result         EvsResult::OK if it succeeds to set a parameter.
+     *                        EvsResult::INVALID_ARG if either the request is
+     *                        not made by a master client, or a requested
+     *                        parameter is not supported.
+     *                        EvsResult::UNDERLYING_SERVICE_ERROR if it fails to
+     *                        program a value by any other reason.
+     *         effectiveValue A programmed parameter value.  This may differ
+     *                        from what the client gives if, for example, the
+     *                        driver does not support a target parameter.
+     */
+    setParameter(CameraParam id, int32_t value)
+        generates (EvsResult result, int32_t effectiveValue);
+
+    /**
+     * Retrieves a value of given camera parameter.
+     *
+     * @param  id     The identifier of camera parameter, CameraParam enum.
+     * @return result EvsResult::OK if it succeeds to read a parameter.
+     *                EvsResult::INVALID_ARG if either a requested parameter is
+     *                not supported.
+     *         value  A value of requested camera parameter.
+     */
+    getParameter(CameraParam id) generates(EvsResult result, int32_t value);
 };
diff --git a/automotive/evs/1.1/IEvsCameraStream.hal b/automotive/evs/1.1/IEvsCameraStream.hal
index cd058a5..7c7f832 100644
--- a/automotive/evs/1.1/IEvsCameraStream.hal
+++ b/automotive/evs/1.1/IEvsCameraStream.hal
@@ -19,7 +19,7 @@
 import @1.0::IEvsCameraStream;
 
 /**
- * Implemented on client side to receive asynchronous video frame deliveries.
+ * Implemented on client side to receive asynchronous streaming event deliveries.
  */
 interface IEvsCameraStream extends @1.0::IEvsCameraStream {
     /**
diff --git a/automotive/evs/1.1/default/EvsCamera.cpp b/automotive/evs/1.1/default/EvsCamera.cpp
index 62d9826..ae293b6 100644
--- a/automotive/evs/1.1/default/EvsCamera.cpp
+++ b/automotive/evs/1.1/default/EvsCamera.cpp
@@ -258,6 +258,38 @@
 }
 
 
+Return<EvsResult> EvsCamera::setMaster() {
+    // Default implementation does not expect multiple subscribers and therefore
+    // return a success code always.
+    return EvsResult::OK;
+}
+
+
+Return<EvsResult> EvsCamera::unsetMaster() {
+    // Default implementation does not expect multiple subscribers and therefore
+    // return a success code always.
+    return EvsResult::OK;
+}
+
+
+Return<void> EvsCamera::setParameter(CameraParam id, int32_t value,
+                                     setParameter_cb _hidl_cb) {
+    // Default implementation does not support this.
+    (void)id;
+    (void)value;
+    _hidl_cb(EvsResult::INVALID_ARG, 0);
+    return Void();
+}
+
+
+Return<void> EvsCamera::getParameter(CameraParam id, getParameter_cb _hidl_cb) {
+    // Default implementation does not support this.
+    (void)id;
+    _hidl_cb(EvsResult::INVALID_ARG, 0);
+    return Void();
+}
+
+
 bool EvsCamera::setAvailableFrames_Locked(unsigned bufferCount) {
     if (bufferCount < 1) {
         ALOGE("Ignoring request to set buffer count to zero");
@@ -468,7 +500,9 @@
 
     // If we've been asked to stop, send an event to signal the actual end of stream
     EvsEvent event;
-    event.info(EvsEventType::STREAM_STOPPED);
+    InfoEventDesc desc = {};
+    desc.aType = InfoEventType::STREAM_STOPPED;
+    event.info(desc);
     auto result = mStream->notifyEvent(event);
     if (!result.isOk()) {
         ALOGE("Error delivering end of stream marker");
diff --git a/automotive/evs/1.1/default/EvsCamera.h b/automotive/evs/1.1/default/EvsCamera.h
index 0982464..6cb1cfb 100644
--- a/automotive/evs/1.1/default/EvsCamera.h
+++ b/automotive/evs/1.1/default/EvsCamera.h
@@ -60,6 +60,11 @@
     Return<EvsResult> pauseVideoStream() override;
     Return<EvsResult> resumeVideoStream() override;
     Return<EvsResult> doneWithFrame_1_1(const BufferDesc_1_1& buffer) override;
+    Return<EvsResult> setMaster() override;
+    Return<EvsResult> unsetMaster() override;
+    Return<void>      setParameter(CameraParam id, int32_t value,
+                                   setParameter_cb _hidl_cb) override;
+    Return<void>      getParameter(CameraParam id, getParameter_cb _hidl_cb) override;
 
     // Implementation details
     EvsCamera(const char *id);
diff --git a/automotive/evs/1.1/types.hal b/automotive/evs/1.1/types.hal
index ff6ab4e..2677018 100644
--- a/automotive/evs/1.1/types.hal
+++ b/automotive/evs/1.1/types.hal
@@ -48,9 +48,9 @@
 };
 
 /**
- * EVS event types
+ * Types of informative streaming events
  */
-enum EvsEventType : uint32_t {
+enum InfoEventType : uint32_t {
     /**
      * Video stream is started
      */
@@ -67,6 +67,25 @@
      * Timeout happens
      */
     TIMEOUT,
+    /**
+     * Camera parameter is changed; payload contains a changed parameter ID and
+     * its value
+     */
+    PARAMETER_CHANGED,
+};
+
+/**
+ * Structure that describes informative events occurred during EVS is streaming
+ */
+struct InfoEventDesc {
+    /**
+     * Type of an informative event
+     */
+    InfoEventType aType;
+    /**
+     * Possible additional information
+     */
+    uint32_t[4] payload;
 };
 
 /**
@@ -80,5 +99,74 @@
     /**
      * General streaming events
      */
-    EvsEventType info;
+    InfoEventDesc info;
+};
+
+/**
+ * EVS Camera Parameter
+ */
+enum CameraParam : uint32_t {
+    /**
+     * The brightness of image frames
+     */
+    BRIGHTNESS,
+    /**
+     * The contrast of image frames
+     */
+    CONTRAST,
+    /**
+     * Automatic gain/exposure control
+     */
+    AUTOGAIN,
+    /**
+     * Gain control
+     */
+    GAIN,
+    /**
+     * Mirror the image horizontally
+     */
+    HFLIP,
+    /**
+     * Mirror the image vertically
+     */
+    VFLIP,
+    /**
+     * Automatic Whitebalance
+     */
+    AUTO_WHITE_BALANCE,
+    /**
+     * Manual white balance setting as a color temperature in Kelvin.
+     */
+    WHITE_BALANCE_TEMPERATURE,
+    /**
+     * Image sharpness adjustment
+     */
+    SHARPNESS,
+    /**
+     * Auto Exposure Control modes; auto, manual, shutter priority, or
+     * aperture priority.
+     */
+    AUTO_EXPOSURE,
+    /**
+     * Manual exposure time of the camera
+     */
+    ABSOLUTE_EXPOSURE,
+    /**
+     * When AEC is running in either auto or aperture priority, this parameter
+     * sets whether a frame rate varies.
+     */
+    AUTO_EXPOSURE_PRIORITY,
+    /**
+     * Set the focal point of the camera to the specified position.  This
+     * parameter may not be effective when auto focus is enabled.
+     */
+    ABSOLUTE_FOCUS,
+    /**
+     * Enables continuous automatic focus adjustments.
+     */
+    AUTO_FOCUS,
+    /**
+     * Specify the objective lens focal length as an absolute value.
+     */
+    ABSOLUTE_ZOOM,
 };
diff --git a/automotive/evs/1.1/vts/functional/FrameHandler.cpp b/automotive/evs/1.1/vts/functional/FrameHandler.cpp
index b7c7f21..a39346b 100644
--- a/automotive/evs/1.1/vts/functional/FrameHandler.cpp
+++ b/automotive/evs/1.1/vts/functional/FrameHandler.cpp
@@ -79,7 +79,7 @@
     // Wait until the stream has actually stopped
     std::unique_lock<std::mutex> lock(mLock);
     if (mRunning) {
-        mSignal.wait(lock, [this]() { return !mRunning; });
+        mEventSignal.wait(lock, [this]() { return !mRunning; });
     }
 }
 
@@ -110,7 +110,9 @@
 void FrameHandler::waitForFrameCount(unsigned frameCount) {
     // Wait until we've seen at least the requested number of frames (could be more)
     std::unique_lock<std::mutex> lock(mLock);
-    mSignal.wait(lock, [this, frameCount](){ return mFramesReceived >= frameCount; });
+    mFrameSignal.wait(lock, [this, frameCount](){
+                                return mFramesReceived >= frameCount;
+                            });
 }
 
 
@@ -135,16 +137,21 @@
 
 Return<void> FrameHandler::notifyEvent(const EvsEvent& event) {
     // Local flag we use to keep track of when the stream is stopping
-    bool timeToStop = false;
-
     auto type = event.getDiscriminator();
     if (type == EvsEvent::hidl_discriminator::info) {
-        if (event.info() == EvsEventType::STREAM_STOPPED) {
+        mLock.lock();
+        mLatestEventDesc = event.info();
+        if (mLatestEventDesc.aType == InfoEventType::STREAM_STOPPED) {
             // Signal that the last frame has been received and the stream is stopped
-            timeToStop = true;
+            mRunning = false;
+        } else if (mLatestEventDesc.aType == InfoEventType::PARAMETER_CHANGED) {
+            ALOGD("Camera parameter 0x%X is changed to 0x%X",
+                  mLatestEventDesc.payload[0], mLatestEventDesc.payload[1]);
         } else {
-            ALOGD("Received an event 0x%X", event.info());
+            ALOGD("Received an event 0x%X", mLatestEventDesc.aType);
         }
+        mLock.unlock();
+        mEventSignal.notify_all();
     } else {
         auto bufDesc = event.buffer();
         const AHardwareBuffer_Desc* pDesc =
@@ -207,21 +214,14 @@
             mHeldBuffers.push(bufDesc);
         }
 
+        mLock.lock();
+        ++mFramesReceived;
+        mLock.unlock();
+        mFrameSignal.notify_all();
 
         ALOGD("Frame handling complete");
     }
 
-
-    // Update our received frame count and notify anybody who cares that things have changed
-    mLock.lock();
-    if (timeToStop) {
-        mRunning = false;
-    } else {
-        mFramesReceived++;
-    }
-    mLock.unlock();
-    mSignal.notify_all();
-
     return Void();
 }
 
@@ -338,3 +338,20 @@
         *height = mFrameHeight;
     }
 }
+
+void FrameHandler::waitForEvent(const InfoEventType aTargetEvent,
+                                InfoEventDesc &eventDesc) {
+    // Wait until we get an expected parameter change event.
+    std::unique_lock<std::mutex> lock(mLock);
+    mEventSignal.wait(lock, [this, aTargetEvent, &eventDesc](){
+        bool flag = mLatestEventDesc.aType == aTargetEvent;
+        if (flag) {
+            eventDesc.aType = mLatestEventDesc.aType;
+            eventDesc.payload[0] = mLatestEventDesc.payload[0];
+            eventDesc.payload[1] = mLatestEventDesc.payload[1];
+        }
+
+        return flag;
+    });
+}
+
diff --git a/automotive/evs/1.1/vts/functional/FrameHandler.h b/automotive/evs/1.1/vts/functional/FrameHandler.h
index 49fa736..c5faecd 100644
--- a/automotive/evs/1.1/vts/functional/FrameHandler.h
+++ b/automotive/evs/1.1/vts/functional/FrameHandler.h
@@ -67,6 +67,8 @@
     bool isRunning();
 
     void waitForFrameCount(unsigned frameCount);
+    void waitForEvent(const InfoEventType aTargetEvent,
+                            InfoEventDesc &eventDesc);
     void getFramesCounters(unsigned* received, unsigned* displayed);
     void getFrameDimension(unsigned* width, unsigned* height);
 
@@ -88,7 +90,8 @@
     // we need to protect all member variables that may be modified while we're streaming
     // (ie: those below)
     std::mutex                  mLock;
-    std::condition_variable     mSignal;
+    std::condition_variable     mEventSignal;
+    std::condition_variable     mFrameSignal;
 
     std::queue<BufferDesc_1_1>  mHeldBuffers;
     bool                        mRunning = false;
@@ -96,6 +99,7 @@
     unsigned                    mFramesDisplayed = 0;   // Simple counter -- rolls over eventually!
     unsigned                    mFrameWidth = 0;
     unsigned                    mFrameHeight = 0;
+    InfoEventDesc               mLatestEventDesc;
 };
 
 
diff --git a/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp b/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp
index 4f7082a..09a1bd9 100644
--- a/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp
+++ b/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp
@@ -124,8 +124,8 @@
 
     sp<IEvsEnumerator>        pEnumerator;    // Every test needs access to the service
     std::vector <CameraDesc>  cameraInfo;     // Empty unless/until loadCameraList() is called
-    bool                        mIsHwModule;    // boolean to tell current module under testing
-                                                // is HW module implementation.
+    bool                      mIsHwModule;    // boolean to tell current module under testing
+                                              // is HW module implementation.
 };
 
 
@@ -516,6 +516,317 @@
 }
 
 
+/*
+ * CameraParameter:
+ * Verify that a client can adjust a camera parameter.
+ */
+TEST_F(EvsHidlTest, CameraParameter) {
+    ALOGI("Starting CameraParameter test");
+
+    // Get the camera list
+    loadCameraList();
+
+    // Test each reported camera
+    for (auto&& cam: cameraInfo) {
+        // Create a camera client
+        sp<IEvsCamera_1_1> pCam =
+            IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId))
+            .withDefault(nullptr);
+        ASSERT_NE(pCam, nullptr);
+
+        // Set up per-client frame receiver objects which will fire up its own thread
+        sp<FrameHandler> frameHandler = new FrameHandler(pCam, cam,
+                                                         nullptr,
+                                                         FrameHandler::eAutoReturn);
+        ASSERT_NE(frameHandler, nullptr);
+
+        // Start the camera's video stream
+        bool startResult = frameHandler->startStream();
+        ASSERT_TRUE(startResult);
+
+        // Ensure the stream starts
+        frameHandler->waitForFrameCount(1);
+
+        // Try to program few parameters
+        EvsResult result = EvsResult::OK;
+        int32_t val0 = 100;
+        int32_t val1 = 0;
+
+        result = pCam->setMaster();
+        ASSERT_TRUE(result == EvsResult::OK);
+
+        pCam->setParameter(CameraParam::BRIGHTNESS, val0,
+                           [&result, &val1](auto status, auto effectiveValue) {
+                               result = status;
+                               val1 = effectiveValue;
+                           });
+        ASSERT_TRUE(result == EvsResult::OK ||
+                    result == EvsResult::INVALID_ARG);
+
+        pCam->getParameter(CameraParam::BRIGHTNESS,
+                           [&result, &val1](auto status, auto value) {
+                               result = status;
+                               if (status == EvsResult::OK) {
+                                  val1 = value;
+                               }
+                           });
+        ASSERT_TRUE(result == EvsResult::OK ||
+                    result == EvsResult::INVALID_ARG);
+        ASSERT_EQ(val0, val1) << "Values are not matched.";
+
+        val0 = 80;
+        val1 = 0;
+        pCam->setParameter(CameraParam::CONTRAST, val0,
+                           [&result, &val1](auto status, auto effectiveValue) {
+                               result = status;
+                               val1 = effectiveValue;
+                           });
+        ASSERT_TRUE(result == EvsResult::OK ||
+                    result == EvsResult::INVALID_ARG);
+
+        pCam->getParameter(CameraParam::CONTRAST,
+                           [&result, &val1](auto status, auto value) {
+                               result = status;
+                               if (status == EvsResult::OK) {
+                                  val1 = value;
+                               }
+                           });
+        ASSERT_TRUE(result == EvsResult::OK ||
+                    result == EvsResult::INVALID_ARG);
+        ASSERT_EQ(val0, val1) << "Values are not matched.";
+
+        val0 = 300;
+        val1 = 0;
+        pCam->setParameter(CameraParam::ABSOLUTE_ZOOM, val0,
+                           [&result, &val1](auto status, auto effectiveValue) {
+                               result = status;
+                               val1 = effectiveValue;
+                           });
+        ASSERT_TRUE(result == EvsResult::OK ||
+                    result == EvsResult::INVALID_ARG);
+
+        pCam->getParameter(CameraParam::ABSOLUTE_ZOOM,
+                           [&result, &val1](auto status, auto value) {
+                               result = status;
+                               if (status == EvsResult::OK) {
+                                  val1 = value;
+                               }
+                           });
+        ASSERT_TRUE(result == EvsResult::OK ||
+                    result == EvsResult::INVALID_ARG);
+        ASSERT_EQ(val0, val1) << "Values are not matched.";
+
+        result = pCam->unsetMaster();
+        ASSERT_TRUE(result == EvsResult::OK);
+
+        // Shutdown another
+        frameHandler->shutdown();
+
+        // Explicitly release the camera
+        pEnumerator->closeCamera(pCam);
+    }
+}
+
+
+/*
+ * MultiCameraParameter:
+ * Verify that master and non-master clients behave as expected when they try to adjust
+ * camera parameters.
+ */
+TEST_F(EvsHidlTest, MultiCameraParameter) {
+    ALOGI("Starting CameraParameter test");
+
+    if (mIsHwModule) {
+        // This test is not for HW module implementation.
+        return;
+    }
+
+    // Get the camera list
+    loadCameraList();
+
+    // Test each reported camera
+    for (auto&& cam: cameraInfo) {
+        // Create two camera clients.
+        sp<IEvsCamera_1_1> pCamMaster =
+            IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId))
+            .withDefault(nullptr);
+        ASSERT_NE(pCamMaster, nullptr);
+        sp<IEvsCamera_1_1> pCamNonMaster =
+            IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId))
+            .withDefault(nullptr);
+        ASSERT_NE(pCamNonMaster, nullptr);
+
+        // Set up per-client frame receiver objects which will fire up its own thread
+        sp<FrameHandler> frameHandlerMaster =
+            new FrameHandler(pCamMaster, cam,
+                             nullptr,
+                             FrameHandler::eAutoReturn);
+        ASSERT_NE(frameHandlerMaster, nullptr);
+        sp<FrameHandler> frameHandlerNonMaster =
+            new FrameHandler(pCamNonMaster, cam,
+                             nullptr,
+                             FrameHandler::eAutoReturn);
+        ASSERT_NE(frameHandlerNonMaster, nullptr);
+
+        // Set one client as the master
+        EvsResult result = pCamMaster->setMaster();
+        ASSERT_TRUE(result == EvsResult::OK);
+
+        // Try to set another client as the master.
+        result = pCamNonMaster->setMaster();
+        ASSERT_TRUE(result == EvsResult::OWNERSHIP_LOST);
+
+        // Start the camera's video stream via a master client.
+        bool startResult = frameHandlerMaster->startStream();
+        ASSERT_TRUE(startResult);
+
+        // Ensure the stream starts
+        frameHandlerMaster->waitForFrameCount(1);
+
+        // Start the camera's video stream via another client
+        startResult = frameHandlerNonMaster->startStream();
+        ASSERT_TRUE(startResult);
+
+        // Ensure the stream starts
+        frameHandlerNonMaster->waitForFrameCount(1);
+
+        // Try to program CameraParam::BRIGHTNESS
+        int32_t val0 = 100;
+        int32_t val1 = 0;
+
+        pCamMaster->setParameter(CameraParam::BRIGHTNESS, val0,
+                                 [&result, &val1](auto status, auto effectiveValue) {
+                                     result = status;
+                                     val1 = effectiveValue;
+                                 });
+        ASSERT_TRUE(result == EvsResult::OK ||            // Succeeded to program
+                    result == EvsResult::INVALID_ARG);    // Camera parameter is not supported
+
+        // Non-master client expects to receive a parameter change notification
+        // whenever a master client adjusts it.
+        InfoEventDesc aNotification = {};
+
+        pCamMaster->getParameter(CameraParam::BRIGHTNESS,
+                                 [&result, &val1](auto status, auto value) {
+                                     result = status;
+                                     if (status == EvsResult::OK) {
+                                        val1 = value;
+                                     }
+                                 });
+        ASSERT_TRUE(result == EvsResult::OK ||            // Succeeded to program
+                    result == EvsResult::INVALID_ARG);    // Camera parameter is not supported
+        if (result == EvsResult::OK) {
+            ASSERT_EQ(val0, val1) << "Values are not matched.";
+
+            // Verify a change notification
+            frameHandlerNonMaster->waitForEvent(InfoEventType::PARAMETER_CHANGED, aNotification);
+            ASSERT_EQ(InfoEventType::PARAMETER_CHANGED,
+                      static_cast<InfoEventType>(aNotification.aType));
+            ASSERT_EQ(CameraParam::BRIGHTNESS,
+                      static_cast<CameraParam>(aNotification.payload[0]));
+            ASSERT_EQ(val1,
+                      static_cast<int32_t>(aNotification.payload[1]));
+        }
+
+        // Try to program CameraParam::CONTRAST
+        val0 = 80;
+        val1 = 0;
+        pCamMaster->setParameter(CameraParam::CONTRAST, val0,
+                                 [&result, &val1](auto status, auto effectiveValue) {
+                                     result = status;
+                                     val1 = effectiveValue;
+                                 });
+        ASSERT_TRUE(result == EvsResult::OK ||            // Succeeded to program
+                    result == EvsResult::INVALID_ARG);    // Camera parameter is not supported
+
+        if (result == EvsResult::OK) {
+            pCamMaster->getParameter(CameraParam::CONTRAST,
+                                     [&result, &val1](auto status, auto value) {
+                                         result = status;
+                                         if (status == EvsResult::OK) {
+                                            val1 = value;
+                                         }
+                                     });
+            ASSERT_TRUE(result == EvsResult::OK);
+            ASSERT_EQ(val0, val1) << "Values are not matched.";
+
+
+            // Verify a change notification
+            frameHandlerNonMaster->waitForEvent(InfoEventType::PARAMETER_CHANGED, aNotification);
+            ASSERT_EQ(InfoEventType::PARAMETER_CHANGED,
+                      static_cast<InfoEventType>(aNotification.aType));
+            ASSERT_EQ(CameraParam::CONTRAST,
+                      static_cast<CameraParam>(aNotification.payload[0]));
+            ASSERT_EQ(val1,
+                      static_cast<int32_t>(aNotification.payload[1]));
+        }
+
+        // Try to adjust a parameter via non-master client
+        pCamNonMaster->setParameter(CameraParam::CONTRAST, val0,
+                                    [&result, &val1](auto status, auto effectiveValue) {
+                                        result = status;
+                                        val1 = effectiveValue;
+                                    });
+        ASSERT_TRUE(result == EvsResult::INVALID_ARG);
+
+        // Non-master client attemps to be a master
+        result = pCamNonMaster->setMaster();
+        ASSERT_TRUE(result == EvsResult::OWNERSHIP_LOST);
+
+        // Master client retires from a master role
+        result = pCamMaster->unsetMaster();
+        ASSERT_TRUE(result == EvsResult::OK);
+
+        // Try to adjust a parameter after being retired
+        pCamMaster->setParameter(CameraParam::BRIGHTNESS, val0,
+                                 [&result, &val1](auto status, auto effectiveValue) {
+                                     result = status;
+                                     val1 = effectiveValue;
+                                 });
+        ASSERT_TRUE(result == EvsResult::INVALID_ARG);
+
+        // Non-master client becomes a master
+        result = pCamNonMaster->setMaster();
+        ASSERT_TRUE(result == EvsResult::OK);
+
+        // Try to adjust a parameter via new master client
+        pCamNonMaster->setParameter(CameraParam::BRIGHTNESS, val0,
+                                    [&result, &val1](auto status, auto effectiveValue) {
+                                        result = status;
+                                        val1 = effectiveValue;
+                                    });
+        ASSERT_TRUE(result == EvsResult::OK ||            // Succeeded to program
+                    result == EvsResult::INVALID_ARG);    // Camera parameter is not supported
+
+        // Wait a moment
+        sleep(1);
+
+        // Verify a change notification
+        if (result == EvsResult::OK) {
+            frameHandlerMaster->waitForEvent(InfoEventType::PARAMETER_CHANGED, aNotification);
+            ASSERT_EQ(static_cast<InfoEventType>(aNotification.aType),
+                      InfoEventType::PARAMETER_CHANGED);
+            ASSERT_EQ(static_cast<CameraParam>(aNotification.payload[0]),
+                      CameraParam::BRIGHTNESS);
+            ASSERT_EQ(val1,
+                      static_cast<int32_t>(aNotification.payload[1]));
+        }
+
+        // New master retires from a master role
+        result = pCamNonMaster->unsetMaster();
+        ASSERT_TRUE(result == EvsResult::OK);
+
+        // Shutdown
+        frameHandlerMaster->shutdown();
+        frameHandlerNonMaster->shutdown();
+
+        // Explicitly release the camera
+        pEnumerator->closeCamera(pCamMaster);
+        pEnumerator->closeCamera(pCamNonMaster);
+    }
+}
+
+
 int main(int argc, char** argv) {
     ::testing::AddGlobalTestEnvironment(EvsHidlEnvironment::Instance());
     ::testing::InitGoogleTest(&argc, argv);