camera: Implement CameraPrivacySwitchMonitor

CameraPrivacySwitchMonitor is used to monitor the camera privacy switch
through v4l2 controls.

BUG=b:167994459
TEST=Build successfully

Disallow-Recycled-Builds: all
Change-Id: Ib43194eae7fd368f59138601c1e63c4dc4baa2b7
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2550973
Tested-by: Jasmine Chen <lnishan@google.com>
Commit-Queue: Jasmine Chen <lnishan@google.com>
Reviewed-by: Shik Chen <shik@chromium.org>
diff --git a/camera/hal/usb/BUILD.gn b/camera/hal/usb/BUILD.gn
index 80c9bc4..3d95845 100644
--- a/camera/hal/usb/BUILD.gn
+++ b/camera/hal/usb/BUILD.gn
@@ -49,6 +49,7 @@
     "camera_client.cc",
     "camera_hal.cc",
     "camera_hal_device_ops.cc",
+    "camera_privacy_switch_monitor.cc",
     "capture_request.cc",
     "cros_device_config.cc",
     "frame_buffer.cc",
diff --git a/camera/hal/usb/camera_client.cc b/camera/hal/usb/camera_client.cc
index ae1986f..83bdacd 100644
--- a/camera/hal/usb/camera_client.cc
+++ b/camera/hal/usb/camera_client.cc
@@ -38,11 +38,12 @@
                            const camera_metadata_t& static_metadata,
                            const camera_metadata_t& request_template,
                            const hw_module_t* module,
-                           hw_device_t** hw_device)
+                           hw_device_t** hw_device,
+                           CameraPrivacySwitchMonitor* privacy_switch_monitor)
     : id_(id),
       device_info_(device_info),
       static_metadata_(clone_camera_metadata(&static_metadata)),
-      device_(new V4L2CameraDevice(device_info)),
+      device_(new V4L2CameraDevice(device_info, privacy_switch_monitor)),
       callback_ops_(nullptr),
       request_thread_("Capture request thread") {
   memset(&camera3_device_, 0, sizeof(camera3_device_));
diff --git a/camera/hal/usb/camera_client.h b/camera/hal/usb/camera_client.h
index 8b7c457..bdfa091 100644
--- a/camera/hal/usb/camera_client.h
+++ b/camera/hal/usb/camera_client.h
@@ -23,6 +23,7 @@
 #include "cros-camera/camera_buffer_manager.h"
 #include "cros-camera/future.h"
 #include "hal/usb/cached_frame.h"
+#include "hal/usb/camera_privacy_switch_monitor.h"
 #include "hal/usb/capture_request.h"
 #include "hal/usb/common_types.h"
 #include "hal/usb/frame_buffer.h"
@@ -61,10 +62,10 @@
                const camera_metadata_t& static_metadata,
                const camera_metadata_t& request_template,
                const hw_module_t* module,
-               hw_device_t** hw_device);
+               hw_device_t** hw_device,
+               CameraPrivacySwitchMonitor* privacy_switch_monitor);
   CameraClient(const CameraClient&) = delete;
   CameraClient& operator=(const CameraClient&) = delete;
-
   ~CameraClient();
 
   // Camera Device Operations from CameraHal.
diff --git a/camera/hal/usb/camera_hal.cc b/camera/hal/usb/camera_hal.cc
index 598c2d5..47a4492 100644
--- a/camera/hal/usb/camera_hal.cc
+++ b/camera/hal/usb/camera_hal.cc
@@ -13,7 +13,6 @@
 #include <base/threading/thread_task_runner_handle.h>
 
 #include "cros-camera/common.h"
-#include "cros-camera/cros_camera_hal.h"
 #include "cros-camera/udev_watcher.h"
 #include "hal/usb/camera_characteristics.h"
 #include "hal/usb/common_types.h"
@@ -160,9 +159,10 @@
                   << cameras_.begin()->first << " is already opened.";
     return -EUSERS;
   }
-  cameras_[id].reset(
-      new CameraClient(id, device_infos_[id], *static_metadata_[id].get(),
-                       *request_template_[id].get(), module, hw_device));
+  cameras_[id].reset(new CameraClient(id, device_infos_[id],
+                                      *static_metadata_[id].get(),
+                                      *request_template_[id].get(), module,
+                                      hw_device, &privacy_switch_monitor_));
   if (cameras_[id]->OpenDevice()) {
     cameras_.erase(id);
     return -ENODEV;
@@ -308,6 +308,11 @@
   mojo_manager_ = nullptr;
 }
 
+void CameraHal::SetPrivacySwitchCallback(
+    PrivacySwitchStateChangeCallback callback) {
+  privacy_switch_monitor_.RegisterCallback(std::move(callback));
+}
+
 void CameraHal::CloseDeviceOnOpsThread(int id) {
   DCHECK(task_runner_);
   auto future = cros::Future<void>::Create(nullptr);
@@ -583,6 +588,11 @@
   CameraHal::GetInstance().TearDown();
 }
 
+static void set_privacy_switch_callback(
+    PrivacySwitchStateChangeCallback callback) {
+  CameraHal::GetInstance().SetPrivacySwitchCallback(std::move(callback));
+}
+
 int camera_device_close(struct hw_device_t* hw_device) {
   camera3_device_t* cam_dev = reinterpret_cast<camera3_device_t*>(hw_device);
   CameraClient* cam = static_cast<CameraClient*>(cam_dev->priv);
@@ -621,4 +631,6 @@
     .reserved = {0}};
 
 cros::cros_camera_hal_t CROS_CAMERA_HAL_INFO_SYM CROS_CAMERA_EXPORT = {
-    .set_up = cros::set_up, .tear_down = cros::tear_down};
+    .set_up = cros::set_up,
+    .tear_down = cros::tear_down,
+    .set_privacy_switch_callback = cros::set_privacy_switch_callback};
diff --git a/camera/hal/usb/camera_hal.h b/camera/hal/usb/camera_hal.h
index 0d086b5..c108bc0 100644
--- a/camera/hal/usb/camera_hal.h
+++ b/camera/hal/usb/camera_hal.h
@@ -18,10 +18,12 @@
 #include <hardware/camera_common.h>
 
 #include "cros-camera/camera_mojo_channel_manager.h"
+#include "cros-camera/cros_camera_hal.h"
 #include "cros-camera/future.h"
 #include "cros-camera/udev_watcher.h"
 #include "hal/usb/camera_characteristics.h"
 #include "hal/usb/camera_client.h"
+#include "hal/usb/camera_privacy_switch_monitor.h"
 #include "hal/usb/common_types.h"
 #include "hal/usb/cros_device_config.h"
 
@@ -54,6 +56,7 @@
   // Implementations for cros_camera_hal_t.
   void SetUp(CameraMojoChannelManager* mojo_manager);
   void TearDown();
+  void SetPrivacySwitchCallback(PrivacySwitchStateChangeCallback callback);
 
   // Runs on device ops thread. Post a task to the thread which is used for
   // OpenDevice.
@@ -68,6 +71,9 @@
   void OnDeviceAdded(ScopedUdevDevicePtr dev) override;
   void OnDeviceRemoved(ScopedUdevDevicePtr dev) override;
 
+  // The monitor for camera privacy switch status changed.
+  CameraPrivacySwitchMonitor privacy_switch_monitor_;
+
   // Cache device information because querying the information is very slow.
   std::map<int, DeviceInfo> device_infos_;
 
diff --git a/camera/hal/usb/camera_privacy_switch_monitor.cc b/camera/hal/usb/camera_privacy_switch_monitor.cc
new file mode 100644
index 0000000..cb931a6
--- /dev/null
+++ b/camera/hal/usb/camera_privacy_switch_monitor.cc
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "hal/usb/camera_privacy_switch_monitor.h"
+
+#include <utility>
+
+#include "cros-camera/common.h"
+
+namespace cros {
+
+CameraPrivacySwitchMonitor::CameraPrivacySwitchMonitor()
+    : state_(PrivacySwitchState::kUnknown) {
+  VLOGF_ENTER();
+}
+
+CameraPrivacySwitchMonitor::~CameraPrivacySwitchMonitor() {
+  VLOGF_ENTER();
+}
+
+void CameraPrivacySwitchMonitor::RegisterCallback(
+    PrivacySwitchStateChangeCallback callback) {
+  callback_ = std::move(callback);
+}
+
+void CameraPrivacySwitchMonitor::OnStatusChanged(PrivacySwitchState state) {
+  if (state == state_) {
+    return;
+  }
+
+  state_ = state;
+  if (!callback_.is_null()) {
+    callback_.Run(state);
+  }
+}
+
+}  // namespace cros
diff --git a/camera/hal/usb/camera_privacy_switch_monitor.h b/camera/hal/usb/camera_privacy_switch_monitor.h
new file mode 100644
index 0000000..ae40773
--- /dev/null
+++ b/camera/hal/usb/camera_privacy_switch_monitor.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef CAMERA_HAL_USB_CAMERA_PRIVACY_SWITCH_MONITOR_H_
+#define CAMERA_HAL_USB_CAMERA_PRIVACY_SWITCH_MONITOR_H_
+
+#include <memory>
+#include <vector>
+
+#include <base/callback.h>
+
+#include "cros-camera/camera_mojo_channel_manager.h"
+#include "cros-camera/cros_camera_hal.h"
+
+namespace cros {
+
+// CameraPrivacySwitchMonitor is a monitor for the status change of camera
+// privacy switch.
+class CameraPrivacySwitchMonitor final {
+ public:
+  CameraPrivacySwitchMonitor();
+  CameraPrivacySwitchMonitor(const CameraPrivacySwitchMonitor&) = delete;
+  CameraPrivacySwitchMonitor& operator=(const CameraPrivacySwitchMonitor&) =
+      delete;
+  ~CameraPrivacySwitchMonitor();
+
+  void RegisterCallback(PrivacySwitchStateChangeCallback callback);
+
+  void OnStatusChanged(PrivacySwitchState state);
+
+ private:
+  PrivacySwitchState state_;
+
+  PrivacySwitchStateChangeCallback callback_;
+};
+
+}  // namespace cros
+
+#endif  // CAMERA_HAL_USB_CAMERA_PRIVACY_SWITCH_MONITOR_H_
diff --git a/camera/hal/usb/v4l2_camera_device.cc b/camera/hal/usb/v4l2_camera_device.cc
index cb63cf0..b46b907 100644
--- a/camera/hal/usb/v4l2_camera_device.cc
+++ b/camera/hal/usb/v4l2_camera_device.cc
@@ -88,6 +88,9 @@
     case kControlWhiteBalanceTemperature:
       return V4L2_CID_WHITE_BALANCE_TEMPERATURE;
 
+    case kControlPrivacy:
+      return V4L2_CID_PRIVACY;
+
     default:
       NOTREACHED() << "Unexpected control type " << type;
       return -1;
@@ -138,6 +141,9 @@
     case kControlWhiteBalanceTemperature:
       return "white balance temperature";
 
+    case kControlPrivacy:
+      return "privacy";
+
     default:
       NOTREACHED() << "Unexpected control type " << type;
       return "N/A";
@@ -197,10 +203,17 @@
 }  // namespace
 
 V4L2CameraDevice::V4L2CameraDevice()
-    : stream_on_(false), device_info_(DeviceInfo()) {}
+    : stream_on_(false),
+      device_info_(DeviceInfo()),
+      event_thread_("V4L2Event") {}
 
-V4L2CameraDevice::V4L2CameraDevice(const DeviceInfo& device_info)
-    : stream_on_(false), device_info_(device_info) {}
+V4L2CameraDevice::V4L2CameraDevice(
+    const DeviceInfo& device_info,
+    CameraPrivacySwitchMonitor* privacy_switch_monitor)
+    : stream_on_(false),
+      device_info_(device_info),
+      event_thread_("V4L2 Event Thread"),
+      privacy_switch_monitor_(privacy_switch_monitor) {}
 
 V4L2CameraDevice::~V4L2CameraDevice() {
   device_fd_.reset();
@@ -369,6 +382,12 @@
     }
   }
 
+  ret = SubscribePrivacySwitchEvent();
+  if (ret != 0) {
+    LOGF(WARNING) << "Failed to subscribe privacy switch event: "
+                  << base::safe_strerror(-ret);
+  }
+
   // Initialize the capabilities.
   if (device_info_.quirks & kQuirkDisableFrameRateSetting) {
     can_update_frame_rate_ = false;
@@ -386,6 +405,14 @@
 void V4L2CameraDevice::Disconnect() {
   base::AutoLock l(lock_);
   stream_on_ = false;
+
+  int ret = UnsubscribePrivacySwitchEvent();
+  if (ret != 0) {
+    LOGF(WARNING) << "Failed to unsubscribe privacy switch event: "
+                  << base::safe_strerror(-ret);
+  }
+  privacy_switch_monitor_->OnStatusChanged(PrivacySwitchState::kUnknown);
+
   device_fd_.reset();
   buffers_at_client_.clear();
 }
@@ -500,6 +527,19 @@
     fds->push_back(std::move(temp_fds[i]));
   }
 
+  // Query for the initial value of privacy button status while streaming on if
+  // it is supported.
+  if (IsControlSupported(kControlPrivacy)) {
+    int32_t value;
+    if (GetControlValue(kControlPrivacy, &value) == 0) {
+      privacy_switch_monitor_->OnStatusChanged(
+          value != 0 ? PrivacySwitchState::kOn : PrivacySwitchState::kOff);
+    } else {
+      LOGF(ERROR)
+          << "Failed to get the initial status of camera privacy switch";
+    }
+  }
+
   stream_on_ = true;
   return 0;
 }
@@ -1419,4 +1459,77 @@
   return device_info_.lens_facing == LensFacing::kExternal;
 }
 
+int V4L2CameraDevice::SubscribePrivacySwitchEvent() {
+  struct v4l2_event_subscription sub = {.type = V4L2_EVENT_CTRL,
+                                        .id = V4L2_CID_PRIVACY};
+  if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_SUBSCRIBE_EVENT, &sub)) < 0) {
+    LOGF(ERROR) << "Unable to subscribe for privacy status change";
+    return -errno;
+  }
+
+  if (!base::CreatePipe(&cancel_fd_, &cancel_pipe_, true)) {
+    LOGF(ERROR) << "Failed to create the cancelation pipe";
+    return -EINVAL;
+  }
+
+  DCHECK(!event_thread_.IsRunning());
+  if (!event_thread_.Start()) {
+    LOGF(ERROR) << "Failed to start V4L2 event thread";
+    return -EINVAL;
+  }
+
+  event_thread_.task_runner()->PostTask(
+      FROM_HERE, base::BindRepeating(&V4L2CameraDevice::RunDequeueEventsLoop,
+                                     base::Unretained(this)));
+  return 0;
+}
+
+int V4L2CameraDevice::UnsubscribePrivacySwitchEvent() {
+  DCHECK(event_thread_.IsRunning());
+  cancel_pipe_.reset();
+  event_thread_.Stop();
+
+  struct v4l2_event_subscription sub = {.type = V4L2_EVENT_CTRL,
+                                        .id = V4L2_CID_PRIVACY};
+  if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_UNSUBSCRIBE_EVENT, &sub)) <
+      0) {
+    LOGF(ERROR) << "Unable to unsubscribe for privacy status change";
+    return -errno;
+  }
+  return 0;
+}
+
+void V4L2CameraDevice::RunDequeueEventsLoop() {
+  while (true) {
+    struct pollfd fds[2] = {
+        {device_fd_.get(), POLLPRI, 0},
+        {cancel_fd_.get(), POLLHUP, 0},
+    };
+
+    if (HANDLE_EINTR(poll(fds, base::size(fds), -1)) <= 0) {
+      LOGF(ERROR) << "Failed to poll to dequeue events";
+      return;
+    }
+
+    if (fds[1].revents > 0) {
+      cancel_fd_.reset();
+      return;
+    }
+
+    if (fds[0].revents > 0) {
+      struct v4l2_event ev = {};
+      if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_DQEVENT, &ev)) == 0) {
+        if (ev.type == V4L2_EVENT_CTRL && ev.id == V4L2_CID_PRIVACY &&
+            privacy_switch_monitor_) {
+          privacy_switch_monitor_->OnStatusChanged(
+              ev.u.ctrl.value != 0 ? PrivacySwitchState::kOn
+                                   : PrivacySwitchState::kOff);
+        }
+      } else {
+        LOGF(ERROR) << "Failed to dequeue event from device";
+      }
+    }
+  }
+}
+
 }  // namespace cros
diff --git a/camera/hal/usb/v4l2_camera_device.h b/camera/hal/usb/v4l2_camera_device.h
index 89c6a13..a9232eb 100644
--- a/camera/hal/usb/v4l2_camera_device.h
+++ b/camera/hal/usb/v4l2_camera_device.h
@@ -16,8 +16,11 @@
 
 #include <base/files/scoped_file.h>
 #include <base/synchronization/lock.h>
+#include <base/threading/thread.h>
 
+#include "cros-camera/cros_camera_hal.h"
 #include "cros-camera/timezone.h"
+#include "hal/usb/camera_privacy_switch_monitor.h"
 #include "hal/usb/common_types.h"
 
 namespace cros {
@@ -51,6 +54,7 @@
   kControlTilt,
   kControlZoom,
   kControlWhiteBalanceTemperature,
+  kControlPrivacy,
 };
 
 constexpr uint32_t kColorTemperatureAuto = 0;
@@ -60,7 +64,8 @@
 class V4L2CameraDevice {
  public:
   V4L2CameraDevice();
-  explicit V4L2CameraDevice(const DeviceInfo& device_info);
+  V4L2CameraDevice(const DeviceInfo& device_info,
+                   CameraPrivacySwitchMonitor* privacy_switch_monitor);
   V4L2CameraDevice(const V4L2CameraDevice&) = delete;
   V4L2CameraDevice& operator=(const V4L2CameraDevice&) = delete;
 
@@ -237,6 +242,17 @@
   // Returns true if the current connected device is an external camera.
   bool IsExternalCamera();
 
+  // Subscribe the camera privacy switch status changed as privacy v4l2-event.
+  // Returns |-errno| if it fails to subscribe.
+  int SubscribePrivacySwitchEvent();
+
+  // Unsubscribe the camera privacy switch status changed as privacy v4l2-event.
+  // Returns |-errno| if it fails to unsubscribe.
+  int UnsubscribePrivacySwitchEvent();
+
+  // Keep dequeuing the v4l2-events from device.
+  void RunDequeueEventsLoop();
+
   // The number of video buffers we want to request in kernel.
   const int kNumVideoBuffers = 4;
 
@@ -267,6 +283,20 @@
   // Current control values.
   std::map<ControlType, int32_t> control_values_;
 
+  // The thread for dequeing v4l2-events.
+  base::Thread event_thread_;
+
+  // The endpoint of cancelation pipe. The main thread should close it before
+  // trying to stop the event thread.
+  base::ScopedFD cancel_pipe_;
+
+  // The endpoint of cancelation pipe. The event thread should poll for it so
+  // that we can notify the thread to leave the loop.
+  base::ScopedFD cancel_fd_;
+
+  // Monitor for the status change of camera privacy switch.
+  CameraPrivacySwitchMonitor* privacy_switch_monitor_;
+
   // Since V4L2CameraDevice may be called on different threads, this is used to
   // guard all variables.
   base::Lock lock_;
diff --git a/camera/hal_adapter/camera_hal_server_impl.cc b/camera/hal_adapter/camera_hal_server_impl.cc
index 2da9b94..2fbd0ae 100644
--- a/camera/hal_adapter/camera_hal_server_impl.cc
+++ b/camera/hal_adapter/camera_hal_server_impl.cc
@@ -64,7 +64,18 @@
   mojo_manager_->GetIpcTaskRunner()->PostTask(
       FROM_HERE,
       base::Bind(&CameraHalServerImpl::IPCBridge::Start,
-                 ipc_bridge_->GetWeakPtr(), camera_hal_adapter_.get()));
+                 ipc_bridge_->GetWeakPtr(), camera_hal_adapter_.get(),
+                 base::BindRepeating(
+                     [](const std::vector<cros_camera_hal_t*>& hals,
+                        PrivacySwitchStateChangeCallback callback) {
+                       for (const auto* hal : hals) {
+                         if (hal->set_privacy_switch_callback != nullptr) {
+                           hal->set_privacy_switch_callback(
+                               std::move(callback));
+                         }
+                       }
+                     },
+                     cros_camera_hals_)));
   return true;
 }
 
@@ -85,7 +96,8 @@
 }
 
 void CameraHalServerImpl::IPCBridge::Start(
-    CameraHalAdapter* camera_hal_adapter) {
+    CameraHalAdapter* camera_hal_adapter,
+    SetPrivacySwitchCallback set_privacy_switch_callback) {
   VLOGF_ENTER();
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
 
@@ -103,7 +115,7 @@
   mojo_manager_->RegisterServer(
       std::move(server_ptr),
       base::BindOnce(&CameraHalServerImpl::IPCBridge::OnServerRegistered,
-                     GetWeakPtr()),
+                     GetWeakPtr(), std::move(set_privacy_switch_callback)),
       base::BindOnce(&CameraHalServerImpl::IPCBridge::OnServiceMojoChannelError,
                      GetWeakPtr()));
 }
@@ -140,7 +152,9 @@
 }
 
 void CameraHalServerImpl::IPCBridge::OnServerRegistered(
-    int32_t result, mojom::CameraHalServerCallbacksPtr callbacks) {
+    SetPrivacySwitchCallback set_privacy_switch_callback,
+    int32_t result,
+    mojom::CameraHalServerCallbacksPtr callbacks) {
   VLOGF_ENTER();
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
 
@@ -150,6 +164,12 @@
     return;
   }
   callbacks_.Bind(callbacks.PassInterface());
+
+  std::move(set_privacy_switch_callback)
+      .Run(base::BindRepeating(
+          &CameraHalServerImpl::IPCBridge::OnPrivacySwitchStatusChanged,
+          base::Unretained(this)));
+
   LOGF(INFO) << "Registered camera HAL";
 }
 
@@ -165,6 +185,19 @@
                             base::Unretained(camera_hal_server_), ECONNRESET));
 }
 
+void CameraHalServerImpl::IPCBridge::OnPrivacySwitchStatusChanged(
+    PrivacySwitchState state) {
+  cros::mojom::CameraPrivacySwitchState state_in_mojo;
+  if (state == PrivacySwitchState::kUnknown) {
+    state_in_mojo = cros::mojom::CameraPrivacySwitchState::UNKNOWN;
+  } else if (state == PrivacySwitchState::kOn) {
+    state_in_mojo = cros::mojom::CameraPrivacySwitchState::ON;
+  } else {  // state == PrivacySwitchState::kOff
+    state_in_mojo = cros::mojom::CameraPrivacySwitchState::OFF;
+  }
+  callbacks_->CameraPrivacySwitchStateChange(state_in_mojo);
+}
+
 void CameraHalServerImpl::LoadCameraHal() {
   VLOGF_ENTER();
   DCHECK(!camera_hal_adapter_);
diff --git a/camera/hal_adapter/camera_hal_server_impl.h b/camera/hal_adapter/camera_hal_server_impl.h
index 0d3f778..31db2cd 100644
--- a/camera/hal_adapter/camera_hal_server_impl.h
+++ b/camera/hal_adapter/camera_hal_server_impl.h
@@ -42,6 +42,9 @@
   bool Start();
 
  private:
+  using SetPrivacySwitchCallback =
+      base::OnceCallback<void(PrivacySwitchStateChangeCallback)>;
+
   // IPCBridge wraps all the IPC-related calls. Most of its methods should/will
   // be run on IPC thread.
   class IPCBridge : public mojom::CameraHalServer {
@@ -51,7 +54,8 @@
 
     ~IPCBridge();
 
-    void Start(CameraHalAdapter* camera_hal_adapter);
+    void Start(CameraHalAdapter* camera_hal_adapter,
+               SetPrivacySwitchCallback set_privacy_switch_callback);
 
     // CameraHalServer Mojo interface implementation.
 
@@ -70,12 +74,17 @@
 
    private:
     // Triggered when the HAL server is registered.
-    void OnServerRegistered(int32_t result,
-                            mojom::CameraHalServerCallbacksPtr callbacks);
+    void OnServerRegistered(
+        SetPrivacySwitchCallback set_privacy_switch_callback,
+        int32_t result,
+        mojom::CameraHalServerCallbacksPtr callbacks);
 
     // Connection error handler for the Mojo connection to CameraHalDispatcher.
     void OnServiceMojoChannelError();
 
+    // Triggers when the camera privacy switch status changed.
+    void OnPrivacySwitchStatusChanged(PrivacySwitchState state);
+
     CameraHalServerImpl* camera_hal_server_;
 
     CameraMojoChannelManager* mojo_manager_;
diff --git a/camera/include/cros-camera/cros_camera_hal.h b/camera/include/cros-camera/cros_camera_hal.h
index 6fe5957..3d1d01a 100644
--- a/camera/include/cros-camera/cros_camera_hal.h
+++ b/camera/include/cros-camera/cros_camera_hal.h
@@ -14,6 +14,15 @@
 
 namespace cros {
 
+enum class PrivacySwitchState {
+  kUnknown,
+  kOn,
+  kOff,
+};
+
+using PrivacySwitchStateChangeCallback =
+    base::RepeatingCallback<void(PrivacySwitchState state)>;
+
 typedef struct cros_camera_hal {
   /**
    * Sets up the camera HAL. The |mojo_manager| can be used for communication
@@ -26,8 +35,14 @@
    */
   void (*tear_down)();
 
+  /**
+   * Registers camera privacy switch observer.
+   */
+  void (*set_privacy_switch_callback)(
+      PrivacySwitchStateChangeCallback callback);
+
   /* reserved for future use */
-  void* reserved[5];
+  void* reserved[4];
 } cros_camera_hal_t;
 
 }  // namespace cros
diff --git a/camera/mojo/cros_camera_service.mojom b/camera/mojo/cros_camera_service.mojom
index 9829be4..12fe2c4 100644
--- a/camera/mojo/cros_camera_service.mojom
+++ b/camera/mojo/cros_camera_service.mojom
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// Next min version: 5
+// Next min version: 6
 
 module cros.mojom;
 
@@ -21,6 +21,22 @@
   PLUGINVM = 4,
 };
 
+// CameraPrivacySwitchState indicates the state of the camera privacy switch.
+enum CameraPrivacySwitchState{
+  // For devices which can only read the privacy switch status while the camera
+  // is streaming, it is possible that the state of privacy switch is currently
+  // unknown.
+  UNKNOWN = 0,
+
+  // State when the privacy switch is on, which means the black frames will be
+  // delivered when streaming.
+  ON = 1,
+
+  // State when the privacy switch is off, which means camera should stream
+  // normally.
+  OFF = 2,
+};
+
 // The CrOS camera HAL v3 Mojo dispatcher.  The dispatcher acts as a proxy and
 // waits for the server and the clients to register.  There can only be one
 // server registered, with multiple clients requesting connections to the
@@ -87,7 +103,7 @@
 // CameraHalDispatcher for any changes on the server side, for example when a
 // CameraHalClient opens or closes a camera device.
 //
-// Next method ID: 1
+// Next method ID: 2
 interface CameraHalServerCallbacks {
   // Fired when a CameraHalClient opens or closes a camera device. When a
   // CameraHalClient loses mojo connection to CameraHalServer, CameraHalServer
@@ -95,6 +111,12 @@
   CameraDeviceActivityChange@0(int32 camera_id,
                                bool opened,
                                CameraClientType type);
+
+  // Fired when the camera privacy switch status is changed. If the device has
+  // such switch, this callback will be fired immediately for once to notify its
+  // current status when the callbacks are registered.
+  [MinVersion=5]
+  CameraPrivacySwitchStateChange@1(CameraPrivacySwitchState state);
 };
 
 // The CrOS camera HAL v3 Mojo client.