diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp
index fa12ce5b0133..01b3acd93c4b 100644
--- a/src/android/camera_device.cpp
+++ b/src/android/camera_device.cpp
@@ -756,6 +756,42 @@ void CameraDevice::close()
 	camera_->release();
 }
 
+/*
+ * Flush is similar to stop() but sets the camera state to 'flushing' and wait
+ * until all the in-flight requests have been returned.
+ */
+void CameraDevice::flush()
+{
+	{
+		MutexLocker cameraLock(cameraMutex_);
+
+		if (state_ != CameraRunning)
+			return;
+
+		state_ = CameraFlushing;
+
+		/*
+		 * Stop the camera and set the state to flushing to prevent any
+		 * new request to be queued from this point on.
+		 */
+		worker_.stop();
+		camera_->stop();
+	}
+
+	/*
+	 * Now wait for all the in-flight requests to be completed before
+	 * returning. Stopping the Camera guarantees that all in-flight requests
+	 * are completed in error state.
+	 */
+	{
+		MutexLocker flushLock(flushMutex_);
+		flushing_.wait(flushLock, [&] { return descriptors_.empty(); });
+	}
+
+	MutexLocker cameraLock(cameraMutex_);
+	state_ = CameraStopped;
+}
+
 void CameraDevice::stop()
 {
 	MutexLocker cameraLock(cameraMutex_);
@@ -2019,6 +2055,26 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques
 	if (ret)
 		return ret;
 
+
+	/*
+	 * Just before queuing the request, make sure flush() has not
+	 * been called after this function has been executed. In that
+	 * case, immediately return the request with errors.
+	 */
+	MutexLocker cameraLock(cameraMutex_);
+	if (state_ == CameraFlushing || state_ == CameraStopped) {
+		for (camera3_stream_buffer_t &buffer : descriptor.buffers_) {
+			buffer.status = CAMERA3_BUFFER_STATUS_ERROR;
+			buffer.release_fence = buffer.acquire_fence;
+		}
+
+		notifyError(descriptor.frameNumber_,
+			    descriptor.buffers_[0].stream,
+			    CAMERA3_MSG_ERROR_REQUEST);
+
+		return 0;
+	}
+
 	worker_.queueRequest(descriptor.request_.get());
 
 	{
@@ -2052,6 +2108,13 @@ void CameraDevice::requestComplete(Request *request)
 	}
 	Camera3RequestDescriptor &descriptor = node.mapped();
 
+	/* Release flush if all the pending requests have been completed. */
+	{
+		MutexLocker flushLock(flushMutex_);
+		if (descriptors_.empty())
+			flushing_.notify_one();
+	}
+
 	/*
 	 * Prepare the capture result for the Android camera stack.
 	 *
diff --git a/src/android/camera_device.h b/src/android/camera_device.h
index ed992ae56d5d..4a9ef495b776 100644
--- a/src/android/camera_device.h
+++ b/src/android/camera_device.h
@@ -7,6 +7,7 @@
 #ifndef __ANDROID_CAMERA_DEVICE_H__
 #define __ANDROID_CAMERA_DEVICE_H__
 
+#include <condition_variable>
 #include <map>
 #include <memory>
 #include <mutex>
@@ -42,6 +43,7 @@ public:
 
 	int open(const hw_module_t *hardwareModule);
 	void close();
+	void flush();
 
 	unsigned int id() const { return id_; }
 	camera3_device_t *camera3Device() { return &camera3Device_; }
@@ -92,6 +94,7 @@ private:
 	enum State {
 		CameraStopped,
 		CameraRunning,
+		CameraFlushing,
 	};
 
 	void stop();
@@ -124,6 +127,9 @@ private:
 	libcamera::Mutex cameraMutex_; /* Protects access to the camera state. */
 	State state_;
 
+	libcamera::Mutex flushMutex_; /* Protects the flushing condition variable. */
+	std::condition_variable flushing_;
+
 	std::shared_ptr<libcamera::Camera> camera_;
 	std::unique_ptr<libcamera::CameraConfiguration> config_;
 
diff --git a/src/android/camera_ops.cpp b/src/android/camera_ops.cpp
index 696e80436821..8a3cfa175ff5 100644
--- a/src/android/camera_ops.cpp
+++ b/src/android/camera_ops.cpp
@@ -66,8 +66,14 @@ static void hal_dev_dump([[maybe_unused]] const struct camera3_device *dev,
 {
 }
 
-static int hal_dev_flush([[maybe_unused]] const struct camera3_device *dev)
+static int hal_dev_flush(const struct camera3_device *dev)
 {
+	if (!dev)
+		return -EINVAL;
+
+	CameraDevice *camera = reinterpret_cast<CameraDevice *>(dev->priv);
+	camera->flush();
+
 	return 0;
 }
 
