Blob Blame History Raw
From 1de79db6506bb388423d518a8bd1d243ee06b631 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= <mail@3v1n0.net>
Date: Wed, 11 Dec 2019 13:40:47 +0100
Subject: [PATCH 106/181] fp-image-device: Move fpi code into its own unit that
 can be compiled a part

In order to be able to test the private device code (used by drivers) we
need to have that split a part in a different .c file so that we can compile
it alone and link with it both the shared library and the test executables.

Redefine fp_image_device_get_instance_private for private usage, not to move
the private struct as part of FpDevice.
---
 libfprint/fp-image-device-private.h |  43 ++
 libfprint/fp-image-device.c         | 585 +--------------------------
 libfprint/fpi-image-device.c        | 595 ++++++++++++++++++++++++++++
 libfprint/meson.build               |   1 +
 4 files changed, 643 insertions(+), 581 deletions(-)
 create mode 100644 libfprint/fp-image-device-private.h
 create mode 100644 libfprint/fpi-image-device.c

diff --git a/libfprint/fp-image-device-private.h b/libfprint/fp-image-device-private.h
new file mode 100644
index 0000000..01454fd
--- /dev/null
+++ b/libfprint/fp-image-device-private.h
@@ -0,0 +1,43 @@
+/*
+ * FpImageDevice - An image based fingerprint reader device
+ * Copyright (C) 2019 Benjamin Berg <bberg@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "fpi-image-device.h"
+
+#define IMG_ENROLL_STAGES 5
+
+typedef struct
+{
+  FpImageDeviceState state;
+  gboolean           active;
+  gboolean           cancelling;
+
+  gboolean           enroll_await_on_pending;
+  gint               enroll_stage;
+
+  guint              pending_activation_timeout_id;
+  gboolean           pending_activation_timeout_waiting_finger_off;
+
+  gint               bz3_threshold;
+} FpImageDevicePrivate;
+
+
+void fpi_image_device_activate (FpImageDevice *image_device);
+void fpi_image_device_deactivate (FpImageDevice *image_device);
diff --git a/libfprint/fp-image-device.c b/libfprint/fp-image-device.c
index 252f414..24d324d 100644
--- a/libfprint/fp-image-device.c
+++ b/libfprint/fp-image-device.c
@@ -20,14 +20,9 @@
 #define FP_COMPONENT "image_device"
 #include "fpi-log.h"
 
-#include "fpi-image-device.h"
-#include "fpi-enums.h"
-#include "fpi-print.h"
-#include "fpi-image.h"
+#include "fp-image-device-private.h"
 
-#define MIN_ACCEPTABLE_MINUTIAE 10
 #define BOZORTH3_DEFAULT_THRESHOLD 40
-#define IMG_ENROLL_STAGES 5
 
 /**
  * SECTION: fp-image-device
@@ -37,30 +32,6 @@
  * This is a helper class for the commonly found image based devices.
  */
 
-/**
- * SECTION: fpi-image-device
- * @title: Internal FpImageDevice
- * @short_description: Internal image device routines
- *
- * See #FpImageDeviceClass for more details. Also see the public
- * #FpImageDevice routines.
- */
-
-typedef struct
-{
-  FpImageDeviceState state;
-  gboolean           active;
-  gboolean           cancelling;
-
-  gboolean           enroll_await_on_pending;
-  gint               enroll_stage;
-
-  guint              pending_activation_timeout_id;
-  gboolean           pending_activation_timeout_waiting_finger_off;
-
-  gint               bz3_threshold;
-} FpImageDevicePrivate;
-
 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (FpImageDevice, fp_image_device, FP_TYPE_DEVICE)
 
 enum {
@@ -87,70 +58,6 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 /* Static helper functions */
 
-static void
-fp_image_device_change_state (FpImageDevice *self, FpImageDeviceState state)
-{
-  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
-
-  /* Cannot change to inactive using this function. */
-  g_assert (state != FP_IMAGE_DEVICE_STATE_INACTIVE);
-
-  /* We might have been waiting for the finger to go OFF to start the
-   * next operation. */
-  g_clear_handle_id (&priv->pending_activation_timeout_id, g_source_remove);
-
-  fp_dbg ("Image device internal state change from %d to %d\n", priv->state, state);
-
-  priv->state = state;
-  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FPI_STATE]);
-  g_signal_emit (self, signals[FPI_STATE_CHANGED], 0, priv->state);
-}
-
-static void
-fp_image_device_activate (FpImageDevice *self)
-{
-  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
-  FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (self);
-
-  g_assert (!priv->active);
-
-  /* We don't have a neutral ACTIVE state, but we always will
-   * go into WAIT_FINGER_ON afterwards. */
-  priv->state = FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON;
-  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FPI_STATE]);
-
-  /* We might have been waiting for deactivation to finish before
-   * starting the next operation. */
-  g_clear_handle_id (&priv->pending_activation_timeout_id, g_source_remove);
-
-  fp_dbg ("Activating image device\n");
-  cls->activate (self);
-}
-
-static void
-fp_image_device_deactivate (FpDevice *device)
-{
-  FpImageDevice *self = FP_IMAGE_DEVICE (device);
-  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
-  FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (device);
-
-  if (!priv->active)
-    {
-      /* XXX: We currently deactivate both from minutiae scan result
-       *      and finger off report. */
-      fp_dbg ("Already deactivated, ignoring request.");
-      return;
-    }
-  if (!priv->cancelling && priv->state == FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON)
-    g_warning ("Deactivating image device while waiting for finger, this should not happen.");
-
-  priv->state = FP_IMAGE_DEVICE_STATE_INACTIVE;
-  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FPI_STATE]);
-
-  fp_dbg ("Deactivating image device\n");
-  cls->deactivate (self);
-}
-
 static gboolean
 pending_activation_timeout (gpointer user_data)
 {
@@ -200,7 +107,7 @@ fp_image_device_close (FpDevice *device)
   if (!priv->active)
     cls->img_close (self);
   else if (priv->state != FP_IMAGE_DEVICE_STATE_INACTIVE)
-    fp_image_device_deactivate (device);
+    fpi_image_device_deactivate (self);
 }
 
 static void
@@ -220,7 +127,7 @@ fp_image_device_cancel_action (FpDevice *device)
       action == FP_DEVICE_ACTION_CAPTURE)
     {
       priv->cancelling = TRUE;
-      fp_image_device_deactivate (FP_DEVICE (self));
+      fpi_image_device_deactivate (self);
       priv->cancelling = FALSE;
 
       /* XXX: Some nicer way of doing this would be good. */
@@ -288,7 +195,7 @@ fp_image_device_start_capture_action (FpDevice *device)
 
   /* And activate the device; we rely on fpi_image_device_activate_complete()
    * to be called when done (or immediately). */
-  fp_image_device_activate (self);
+  fpi_image_device_activate (self);
 }
 
 
@@ -391,488 +298,4 @@ fp_image_device_init (FpImageDevice *self)
   priv->bz3_threshold = BOZORTH3_DEFAULT_THRESHOLD;
   if (cls->bz3_threshold > 0)
     priv->bz3_threshold = cls->bz3_threshold;
-
-}
-
-static void
-fp_image_device_enroll_maybe_await_finger_on (FpImageDevice *self)
-{
-  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
-
-  if (priv->enroll_await_on_pending)
-    {
-      priv->enroll_await_on_pending = FALSE;
-      fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON);
-    }
-  else
-    {
-      priv->enroll_await_on_pending = TRUE;
-    }
-}
-
-static void
-fpi_image_device_minutiae_detected (GObject *source_object, GAsyncResult *res, gpointer user_data)
-{
-  g_autoptr(FpImage) image = FP_IMAGE (source_object);
-  g_autoptr(FpPrint) print = NULL;
-  GError *error = NULL;
-  FpDevice *device = FP_DEVICE (user_data);
-  FpImageDevicePrivate *priv;
-  FpDeviceAction action;
-
-  /* Note: We rely on the device to not disappear during an operation. */
-
-  if (!fp_image_detect_minutiae_finish (image, res, &error))
-    {
-      /* Cancel operation . */
-      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-        {
-          fpi_device_action_error (device, g_steal_pointer (&error));
-          fp_image_device_deactivate (device);
-          return;
-        }
-
-      /* Replace error with a retry condition. */
-      g_warning ("Failed to detect minutiae: %s", error->message);
-      g_clear_pointer (&error, g_error_free);
-
-      error = fpi_device_retry_new_msg (FP_DEVICE_RETRY_GENERAL, "Minutiae detection failed, please retry");
-    }
-
-  priv = fp_image_device_get_instance_private (FP_IMAGE_DEVICE (device));
-  action = fpi_device_get_current_action (device);
-
-  if (action == FP_DEVICE_ACTION_CAPTURE)
-    {
-      fpi_device_capture_complete (device, g_steal_pointer (&image), error);
-      fp_image_device_deactivate (device);
-      return;
-    }
-
-  if (!error)
-    {
-      print = fp_print_new (device);
-      fpi_print_set_type (print, FP_PRINT_NBIS);
-      if (!fpi_print_add_from_image (print, image, &error))
-        g_clear_object (&print);
-    }
-
-  if (action == FP_DEVICE_ACTION_ENROLL)
-    {
-      FpPrint *enroll_print;
-      fpi_device_get_enroll_data (device, &enroll_print);
-
-      if (print)
-        {
-          fpi_print_add_print (enroll_print, print);
-          priv->enroll_stage += 1;
-        }
-
-      fpi_device_enroll_progress (device, priv->enroll_stage,
-                                  g_steal_pointer (&print), error);
-
-      /* Start another scan or deactivate. */
-      if (priv->enroll_stage == IMG_ENROLL_STAGES)
-        {
-          fpi_device_enroll_complete (device, g_object_ref (enroll_print), NULL);
-          fp_image_device_deactivate (device);
-        }
-      else
-        {
-          fp_image_device_enroll_maybe_await_finger_on (FP_IMAGE_DEVICE (device));
-        }
-    }
-  else if (action == FP_DEVICE_ACTION_VERIFY)
-    {
-      FpPrint *template;
-      FpiMatchResult result;
-
-      fpi_device_get_verify_data (device, &template);
-      if (print)
-        result = fpi_print_bz3_match (template, print, priv->bz3_threshold, &error);
-      else
-        result = FPI_MATCH_ERROR;
-
-      fpi_device_verify_complete (device, result, g_steal_pointer (&print), error);
-      fp_image_device_deactivate (device);
-    }
-  else if (action == FP_DEVICE_ACTION_IDENTIFY)
-    {
-      gint i;
-      GPtrArray *templates;
-      FpPrint *result = NULL;
-
-      fpi_device_get_identify_data (device, &templates);
-      for (i = 0; !error && i < templates->len; i++)
-        {
-          FpPrint *template = g_ptr_array_index (templates, i);
-
-          if (fpi_print_bz3_match (template, print, priv->bz3_threshold, &error) == FPI_MATCH_SUCCESS)
-            {
-              result = g_object_ref (template);
-              break;
-            }
-        }
-
-      fpi_device_identify_complete (device, result, g_steal_pointer (&print), error);
-      fp_image_device_deactivate (device);
-    }
-  else
-    {
-      /* XXX: This can be hit currently due to a race condition in the enroll code!
-       *      In that case we scan a further image even though the minutiae for the previous
-       *      one have not yet been detected.
-       *      We need to keep track on the pending minutiae detection and the fact that
-       *      it will finish eventually (or we may need to retry on error and activate the
-       *      device again). */
-      g_assert_not_reached ();
-    }
-}
-
-/*********************************************************/
-/* Private API */
-
-/**
- * fpi_image_device_set_bz3_threshold:
- * @self: a #FpImageDevice imaging fingerprint device
- * @bz3_threshold: BZ3 threshold to use
- *
- * Dynamically adjust the bz3 threshold. This is only needed for drivers
- * that support devices with different properties. It should generally be
- * called from the probe callback, but is acceptable to call from the open
- * callback.
- */
-void
-fpi_image_device_set_bz3_threshold (FpImageDevice *self,
-                                    gint           bz3_threshold)
-{
-  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
-
-  g_return_if_fail (FP_IS_IMAGE_DEVICE (self));
-  g_return_if_fail (bz3_threshold > 0);
-
-  priv->bz3_threshold = bz3_threshold;
-}
-
-/**
- * fpi_image_device_report_finger_status:
- * @self: a #FpImageDevice imaging fingerprint device
- * @present: whether the finger is present on the sensor
- *
- * Reports from the driver whether the user's finger is on
- * the sensor.
- */
-void
-fpi_image_device_report_finger_status (FpImageDevice *self,
-                                       gboolean       present)
-{
-  FpDevice *device = FP_DEVICE (self);
-  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
-  FpDeviceAction action;
-
-  if (priv->state == FP_IMAGE_DEVICE_STATE_INACTIVE)
-    {
-      /* Do we really want to always ignore such reports? We could
-       * also track the state in case the user had the finger on
-       * the device at initialisation time and the driver reports
-       * this early.
-       */
-      g_debug ("Ignoring finger presence report as the device is not active!");
-      return;
-    }
-
-  action = fpi_device_get_current_action (device);
-
-  g_assert (action != FP_DEVICE_ACTION_OPEN);
-  g_assert (action != FP_DEVICE_ACTION_CLOSE);
-
-  g_debug ("Image device reported finger status: %s", present ? "on" : "off");
-
-  if (present && priv->state == FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON)
-    {
-      fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_CAPTURE);
-    }
-  else if (!present && priv->state == FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF)
-    {
-      /* We need to deactivate or continue to await finger */
-
-      /* There are three possible situations:
-       *  1. We are deactivating the device and the action is still in progress
-       *     (minutiae detection).
-       *  2. We are still deactivating the device after an action completed
-       *  3. We were waiting for finger removal to start the new action
-       * Either way, we always end up deactivating except for the enroll case.
-       *
-       * The enroll case is special as AWAIT_FINGER_ON should only happen after
-       * minutiae detection to prevent deactivation (without cancellation)
-       * from the AWAIT_FINGER_ON state.
-       */
-      if (action != FP_DEVICE_ACTION_ENROLL)
-        fp_image_device_deactivate (device);
-      else
-        fp_image_device_enroll_maybe_await_finger_on (self);
-    }
-}
-
-/**
- * fpi_image_device_image_captured:
- * @self: a #FpImageDevice imaging fingerprint device
- * @image: whether the finger is present on the sensor
- *
- * Reports an image capture. Only use this function if the image was
- * captured successfully. If there was an issue where the user should
- * retry, use fpi_image_device_retry_scan() to report the retry condition.
- *
- * In the event of a fatal error for the operation use
- * fpi_image_device_session_error(). This will abort the entire operation
- * including e.g. an enroll operation which captures multiple images during
- * one session.
- */
-void
-fpi_image_device_image_captured (FpImageDevice *self, FpImage *image)
-{
-  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
-  FpDeviceAction action;
-
-  action = fpi_device_get_current_action (FP_DEVICE (self));
-
-  g_return_if_fail (image != NULL);
-  g_return_if_fail (priv->state == FP_IMAGE_DEVICE_STATE_CAPTURE);
-  g_return_if_fail (action == FP_DEVICE_ACTION_ENROLL ||
-                    action == FP_DEVICE_ACTION_VERIFY ||
-                    action == FP_DEVICE_ACTION_IDENTIFY ||
-                    action == FP_DEVICE_ACTION_CAPTURE);
-
-  fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF);
-
-  g_debug ("Image device captured an image");
-
-  /* XXX: We also detect minutiae in capture mode, we solely do this
-   *      to normalize the image which will happen as a by-product. */
-  fp_image_detect_minutiae (image,
-                            fpi_device_get_cancellable (FP_DEVICE (self)),
-                            fpi_image_device_minutiae_detected,
-                            self);
-}
-
-/**
- * fpi_image_device_retry_scan:
- * @self: a #FpImageDevice imaging fingerprint device
- * @retry: The #FpDeviceRetry error code to report
- *
- * Reports a scan failure to the user. This may or may not abort the
- * current session. It is the equivalent of fpi_image_device_image_captured()
- * in the case of a retryable error condition (e.g. short swipe).
- */
-void
-fpi_image_device_retry_scan (FpImageDevice *self, FpDeviceRetry retry)
-{
-  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
-  FpDeviceAction action;
-  GError *error;
-
-  action = fpi_device_get_current_action (FP_DEVICE (self));
-
-  /* We might be waiting for a finger at this point, so just accept
-   * all but INACTIVE */
-  g_return_if_fail (priv->state != FP_IMAGE_DEVICE_STATE_INACTIVE);
-  g_return_if_fail (action == FP_DEVICE_ACTION_ENROLL ||
-                    action == FP_DEVICE_ACTION_VERIFY ||
-                    action == FP_DEVICE_ACTION_IDENTIFY ||
-                    action == FP_DEVICE_ACTION_CAPTURE);
-
-  error = fpi_device_retry_new (retry);
-
-  if (action == FP_DEVICE_ACTION_ENROLL)
-    {
-      g_debug ("Reporting retry during enroll");
-      fpi_device_enroll_progress (FP_DEVICE (self), priv->enroll_stage, NULL, error);
-    }
-  else
-    {
-      /* We abort the operation and let the surrounding code retry in the
-       * non-enroll case (this is identical to a session error). */
-      g_debug ("Abort current operation due to retry (non-enroll case)");
-      fp_image_device_deactivate (FP_DEVICE (self));
-      fpi_device_action_error (FP_DEVICE (self), error);
-    }
-}
-
-/**
- * fpi_image_device_session_error:
- * @self: a #FpImageDevice imaging fingerprint device
- * @error: The #GError to report
- *
- * Report an error while interacting with the device. This effectively
- * aborts the current ongoing action.
- */
-void
-fpi_image_device_session_error (FpImageDevice *self, GError *error)
-{
-  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
-
-  g_return_if_fail (self);
-
-  if (!error)
-    {
-      g_warning ("Driver did not provide an error, generating a generic one");
-      error = g_error_new (FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL, "Driver reported session error without an error");
-    }
-
-  if (!priv->active)
-    {
-      FpDeviceAction action = fpi_device_get_current_action (FP_DEVICE (self));
-      g_warning ("Driver reported session error, but device is inactive.");
-
-      if (action != FP_DEVICE_ACTION_NONE)
-        {
-          g_warning ("Translating to activation failure!");
-          fpi_image_device_activate_complete (self, error);
-          return;
-        }
-    }
-  else if (priv->state == FP_IMAGE_DEVICE_STATE_INACTIVE)
-    {
-      g_warning ("Driver reported session error; translating to deactivation failure.");
-      fpi_image_device_deactivate_complete (self, error);
-      return;
-    }
-
-  if (error->domain == FP_DEVICE_RETRY)
-    g_warning ("Driver should report retries using fpi_image_device_retry_scan!");
-
-  fp_image_device_deactivate (FP_DEVICE (self));
-  fpi_device_action_error (FP_DEVICE (self), error);
-}
-
-/**
- * fpi_image_device_activate_complete:
- * @self: a #FpImageDevice imaging fingerprint device
- * @error: A #GError or %NULL on success
- *
- * Reports completion of device activation.
- */
-void
-fpi_image_device_activate_complete (FpImageDevice *self, GError *error)
-{
-  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
-  FpDeviceAction action;
-
-  action = fpi_device_get_current_action (FP_DEVICE (self));
-
-  g_return_if_fail (priv->active == FALSE);
-  g_return_if_fail (action == FP_DEVICE_ACTION_ENROLL ||
-                    action == FP_DEVICE_ACTION_VERIFY ||
-                    action == FP_DEVICE_ACTION_IDENTIFY ||
-                    action == FP_DEVICE_ACTION_CAPTURE);
-
-  if (error)
-    {
-      g_debug ("Image device activation failed");
-      fpi_device_action_error (FP_DEVICE (self), error);
-      return;
-    }
-
-  g_debug ("Image device activation completed");
-
-  priv->active = TRUE;
-
-  /* We always want to capture at this point, move to AWAIT_FINGER
-   * state. */
-  fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON);
-}
-
-/**
- * fpi_image_device_deactivate_complete:
- * @self: a #FpImageDevice imaging fingerprint device
- * @error: A #GError or %NULL on success
- *
- * Reports completion of device deactivation.
- */
-void
-fpi_image_device_deactivate_complete (FpImageDevice *self, GError *error)
-{
-  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
-  FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (self);
-  FpDeviceAction action;
-
-  g_return_if_fail (priv->active == TRUE);
-  g_return_if_fail (priv->state == FP_IMAGE_DEVICE_STATE_INACTIVE);
-
-  g_debug ("Image device deactivation completed");
-
-  priv->active = FALSE;
-
-  /* Deactivation completed. As we deactivate in the background
-   * there may already be a new task pending. Check whether we
-   * need to do anything. */
-  action = fpi_device_get_current_action (FP_DEVICE (self));
-
-  /* Special case, if we should be closing, but didn't due to a running
-   * deactivation, then do so now. */
-  if (action == FP_DEVICE_ACTION_CLOSE)
-    {
-      cls->img_close (self);
-      return;
-    }
-
-  /* We might be waiting to be able to activate again. */
-  if (priv->pending_activation_timeout_id)
-    {
-      g_clear_handle_id (&priv->pending_activation_timeout_id, g_source_remove);
-      priv->pending_activation_timeout_id =
-        g_idle_add ((GSourceFunc) fp_image_device_activate, self);
-    }
-}
-
-/**
- * fpi_image_device_open_complete:
- * @self: a #FpImageDevice imaging fingerprint device
- * @error: A #GError or %NULL on success
- *
- * Reports completion of open operation.
- */
-void
-fpi_image_device_open_complete (FpImageDevice *self, GError *error)
-{
-  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
-  FpDeviceAction action;
-
-  action = fpi_device_get_current_action (FP_DEVICE (self));
-
-  g_return_if_fail (priv->active == FALSE);
-  g_return_if_fail (action == FP_DEVICE_ACTION_OPEN);
-
-  g_debug ("Image device open completed");
-
-  priv->state = FP_IMAGE_DEVICE_STATE_INACTIVE;
-  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FPI_STATE]);
-
-  fpi_device_open_complete (FP_DEVICE (self), error);
-}
-
-/**
- * fpi_image_device_close_complete:
- * @self: a #FpImageDevice imaging fingerprint device
- * @error: A #GError or %NULL on success
- *
- * Reports completion of close operation.
- */
-void
-fpi_image_device_close_complete (FpImageDevice *self, GError *error)
-{
-  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
-  FpDeviceAction action;
-
-  action = fpi_device_get_current_action (FP_DEVICE (self));
-
-  g_debug ("Image device close completed");
-
-  g_return_if_fail (priv->active == FALSE);
-  g_return_if_fail (action == FP_DEVICE_ACTION_CLOSE);
-
-  priv->state = FP_IMAGE_DEVICE_STATE_INACTIVE;
-  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FPI_STATE]);
-
-  fpi_device_close_complete (FP_DEVICE (self), error);
 }
diff --git a/libfprint/fpi-image-device.c b/libfprint/fpi-image-device.c
new file mode 100644
index 0000000..6e5802e
--- /dev/null
+++ b/libfprint/fpi-image-device.c
@@ -0,0 +1,595 @@
+/*
+ * FpImageDevice - An image based fingerprint reader device - Private APIs
+ * Copyright (C) 2019 Benjamin Berg <bberg@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define FP_COMPONENT "image_device"
+#include "fpi-log.h"
+
+#include "fp-image-device-private.h"
+#include "fp-image-device.h"
+
+/**
+ * SECTION: fpi-image
+ * @title: Internal FpImage
+ * @short_description: Internal image handling routines
+ *
+ * Internal image handling routines. Also see the public <ulink
+ * url="libfprint-FpImage.html">FpImage routines</ulink>.
+ */
+
+/* Manually redefine what G_DEFINE_* macro does */
+static inline gpointer
+fp_image_device_get_instance_private (FpImageDevice *self)
+{
+  FpImageDeviceClass *img_class = g_type_class_peek_static (FP_TYPE_IMAGE_DEVICE);
+
+  return G_STRUCT_MEMBER_P (self,
+                            g_type_class_get_instance_private_offset (img_class));
+}
+
+/* Private shared functions */
+
+void
+fpi_image_device_activate (FpImageDevice *self)
+{
+  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
+  FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (self);
+
+  g_assert (!priv->active);
+
+  /* We don't have a neutral ACTIVE state, but we always will
+   * go into WAIT_FINGER_ON afterwards. */
+  priv->state = FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON;
+  g_object_notify (G_OBJECT (self), "fp-image-device-state");
+
+  /* We might have been waiting for deactivation to finish before
+   * starting the next operation. */
+  g_clear_handle_id (&priv->pending_activation_timeout_id, g_source_remove);
+
+  fp_dbg ("Activating image device\n");
+  cls->activate (self);
+}
+
+void
+fpi_image_device_deactivate (FpImageDevice *self)
+{
+  FpDevice *device = FP_DEVICE (self);
+  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
+  FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (device);
+
+  if (!priv->active)
+    {
+      /* XXX: We currently deactivate both from minutiae scan result
+       *      and finger off report. */
+      fp_dbg ("Already deactivated, ignoring request.");
+      return;
+    }
+  if (!priv->cancelling && priv->state == FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON)
+    g_warning ("Deactivating image device while waiting for finger, this should not happen.");
+
+  priv->state = FP_IMAGE_DEVICE_STATE_INACTIVE;
+  g_object_notify (G_OBJECT (self), "fp-image-device-state");
+
+  fp_dbg ("Deactivating image device\n");
+  cls->deactivate (self);
+}
+
+/* Static helper functions */
+
+static void
+fp_image_device_change_state (FpImageDevice *self, FpImageDeviceState state)
+{
+  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
+
+  /* Cannot change to inactive using this function. */
+  g_assert (state != FP_IMAGE_DEVICE_STATE_INACTIVE);
+
+  /* We might have been waiting for the finger to go OFF to start the
+   * next operation. */
+  g_clear_handle_id (&priv->pending_activation_timeout_id, g_source_remove);
+
+  fp_dbg ("Image device internal state change from %d to %d\n", priv->state, state);
+
+  priv->state = state;
+  g_object_notify (G_OBJECT (self), "fp-image-device-state");
+  g_signal_emit_by_name (self, "fp-image-device-state-changed", priv->state);
+}
+
+static void
+fp_image_device_enroll_maybe_await_finger_on (FpImageDevice *self)
+{
+  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
+
+  if (priv->enroll_await_on_pending)
+    {
+      priv->enroll_await_on_pending = FALSE;
+      fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON);
+    }
+  else
+    {
+      priv->enroll_await_on_pending = TRUE;
+    }
+}
+
+static void
+fpi_image_device_minutiae_detected (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+  g_autoptr(FpImage) image = FP_IMAGE (source_object);
+  g_autoptr(FpPrint) print = NULL;
+  GError *error = NULL;
+  FpImageDevice *self = FP_IMAGE_DEVICE (user_data);
+  FpDevice *device = FP_DEVICE (self);
+  FpImageDevicePrivate *priv;
+  FpDeviceAction action;
+
+  /* Note: We rely on the device to not disappear during an operation. */
+
+  if (!fp_image_detect_minutiae_finish (image, res, &error))
+    {
+      /* Cancel operation . */
+      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        {
+          fpi_device_action_error (device, g_steal_pointer (&error));
+          fpi_image_device_deactivate (self);
+          return;
+        }
+
+      /* Replace error with a retry condition. */
+      g_warning ("Failed to detect minutiae: %s", error->message);
+      g_clear_pointer (&error, g_error_free);
+
+      error = fpi_device_retry_new_msg (FP_DEVICE_RETRY_GENERAL, "Minutiae detection failed, please retry");
+    }
+
+  priv = fp_image_device_get_instance_private (FP_IMAGE_DEVICE (device));
+  action = fpi_device_get_current_action (device);
+
+  if (action == FP_DEVICE_ACTION_CAPTURE)
+    {
+      fpi_device_capture_complete (device, g_steal_pointer (&image), error);
+      fpi_image_device_deactivate (self);
+      return;
+    }
+
+  if (!error)
+    {
+      print = fp_print_new (device);
+      fpi_print_set_type (print, FP_PRINT_NBIS);
+      if (!fpi_print_add_from_image (print, image, &error))
+        g_clear_object (&print);
+    }
+
+  if (action == FP_DEVICE_ACTION_ENROLL)
+    {
+      FpPrint *enroll_print;
+      fpi_device_get_enroll_data (device, &enroll_print);
+
+      if (print)
+        {
+          fpi_print_add_print (enroll_print, print);
+          priv->enroll_stage += 1;
+        }
+
+      fpi_device_enroll_progress (device, priv->enroll_stage,
+                                  g_steal_pointer (&print), error);
+
+      /* Start another scan or deactivate. */
+      if (priv->enroll_stage == IMG_ENROLL_STAGES)
+        {
+          fpi_device_enroll_complete (device, g_object_ref (enroll_print), NULL);
+          fpi_image_device_deactivate (self);
+        }
+      else
+        {
+          fp_image_device_enroll_maybe_await_finger_on (FP_IMAGE_DEVICE (device));
+        }
+    }
+  else if (action == FP_DEVICE_ACTION_VERIFY)
+    {
+      FpPrint *template;
+      FpiMatchResult result;
+
+      fpi_device_get_verify_data (device, &template);
+      if (print)
+        result = fpi_print_bz3_match (template, print, priv->bz3_threshold, &error);
+      else
+        result = FPI_MATCH_ERROR;
+
+      fpi_device_verify_complete (device, result, g_steal_pointer (&print), error);
+      fpi_image_device_deactivate (self);
+    }
+  else if (action == FP_DEVICE_ACTION_IDENTIFY)
+    {
+      gint i;
+      GPtrArray *templates;
+      FpPrint *result = NULL;
+
+      fpi_device_get_identify_data (device, &templates);
+      for (i = 0; !error && i < templates->len; i++)
+        {
+          FpPrint *template = g_ptr_array_index (templates, i);
+
+          if (fpi_print_bz3_match (template, print, priv->bz3_threshold, &error) == FPI_MATCH_SUCCESS)
+            {
+              result = g_object_ref (template);
+              break;
+            }
+        }
+
+      fpi_device_identify_complete (device, result, g_steal_pointer (&print), error);
+      fpi_image_device_deactivate (self);
+    }
+  else
+    {
+      /* XXX: This can be hit currently due to a race condition in the enroll code!
+       *      In that case we scan a further image even though the minutiae for the previous
+       *      one have not yet been detected.
+       *      We need to keep track on the pending minutiae detection and the fact that
+       *      it will finish eventually (or we may need to retry on error and activate the
+       *      device again). */
+      g_assert_not_reached ();
+    }
+}
+
+/*********************************************************/
+/* Private API */
+
+/**
+ * fpi_image_device_set_bz3_threshold:
+ * @self: a #FpImageDevice imaging fingerprint device
+ * @bz3_threshold: BZ3 threshold to use
+ *
+ * Dynamically adjust the bz3 threshold. This is only needed for drivers
+ * that support devices with different properties. It should generally be
+ * called from the probe callback, but is acceptable to call from the open
+ * callback.
+ */
+void
+fpi_image_device_set_bz3_threshold (FpImageDevice *self,
+                                    gint           bz3_threshold)
+{
+  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
+
+  g_return_if_fail (FP_IS_IMAGE_DEVICE (self));
+  g_return_if_fail (bz3_threshold > 0);
+
+  priv->bz3_threshold = bz3_threshold;
+}
+
+/**
+ * fpi_image_device_report_finger_status:
+ * @self: a #FpImageDevice imaging fingerprint device
+ * @present: whether the finger is present on the sensor
+ *
+ * Reports from the driver whether the user's finger is on
+ * the sensor.
+ */
+void
+fpi_image_device_report_finger_status (FpImageDevice *self,
+                                       gboolean       present)
+{
+  FpDevice *device = FP_DEVICE (self);
+  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
+  FpDeviceAction action;
+
+  if (priv->state == FP_IMAGE_DEVICE_STATE_INACTIVE)
+    {
+      /* Do we really want to always ignore such reports? We could
+       * also track the state in case the user had the finger on
+       * the device at initialisation time and the driver reports
+       * this early.
+       */
+      g_debug ("Ignoring finger presence report as the device is not active!");
+      return;
+    }
+
+  action = fpi_device_get_current_action (device);
+
+  g_assert (action != FP_DEVICE_ACTION_OPEN);
+  g_assert (action != FP_DEVICE_ACTION_CLOSE);
+
+  g_debug ("Image device reported finger status: %s", present ? "on" : "off");
+
+  if (present && priv->state == FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON)
+    {
+      fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_CAPTURE);
+    }
+  else if (!present && priv->state == FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF)
+    {
+      /* We need to deactivate or continue to await finger */
+
+      /* There are three possible situations:
+       *  1. We are deactivating the device and the action is still in progress
+       *     (minutiae detection).
+       *  2. We are still deactivating the device after an action completed
+       *  3. We were waiting for finger removal to start the new action
+       * Either way, we always end up deactivating except for the enroll case.
+       *
+       * The enroll case is special as AWAIT_FINGER_ON should only happen after
+       * minutiae detection to prevent deactivation (without cancellation)
+       * from the AWAIT_FINGER_ON state.
+       */
+      if (action != FP_DEVICE_ACTION_ENROLL)
+        fpi_image_device_deactivate (self);
+      else
+        fp_image_device_enroll_maybe_await_finger_on (self);
+    }
+}
+
+/**
+ * fpi_image_device_image_captured:
+ * @self: a #FpImageDevice imaging fingerprint device
+ * @image: whether the finger is present on the sensor
+ *
+ * Reports an image capture. Only use this function if the image was
+ * captured successfully. If there was an issue where the user should
+ * retry, use fpi_image_device_retry_scan() to report the retry condition.
+ *
+ * In the event of a fatal error for the operation use
+ * fpi_image_device_session_error(). This will abort the entire operation
+ * including e.g. an enroll operation which captures multiple images during
+ * one session.
+ */
+void
+fpi_image_device_image_captured (FpImageDevice *self, FpImage *image)
+{
+  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
+  FpDeviceAction action;
+
+  action = fpi_device_get_current_action (FP_DEVICE (self));
+
+  g_return_if_fail (image != NULL);
+  g_return_if_fail (priv->state == FP_IMAGE_DEVICE_STATE_CAPTURE);
+  g_return_if_fail (action == FP_DEVICE_ACTION_ENROLL ||
+                    action == FP_DEVICE_ACTION_VERIFY ||
+                    action == FP_DEVICE_ACTION_IDENTIFY ||
+                    action == FP_DEVICE_ACTION_CAPTURE);
+
+  fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF);
+
+  g_debug ("Image device captured an image");
+
+  /* XXX: We also detect minutiae in capture mode, we solely do this
+   *      to normalize the image which will happen as a by-product. */
+  fp_image_detect_minutiae (image,
+                            fpi_device_get_cancellable (FP_DEVICE (self)),
+                            fpi_image_device_minutiae_detected,
+                            self);
+}
+
+/**
+ * fpi_image_device_retry_scan:
+ * @self: a #FpImageDevice imaging fingerprint device
+ * @retry: The #FpDeviceRetry error code to report
+ *
+ * Reports a scan failure to the user. This may or may not abort the
+ * current session. It is the equivalent of fpi_image_device_image_captured()
+ * in the case of a retryable error condition (e.g. short swipe).
+ */
+void
+fpi_image_device_retry_scan (FpImageDevice *self, FpDeviceRetry retry)
+{
+  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
+  FpDeviceAction action;
+  GError *error;
+
+  action = fpi_device_get_current_action (FP_DEVICE (self));
+
+  /* We might be waiting for a finger at this point, so just accept
+   * all but INACTIVE */
+  g_return_if_fail (priv->state != FP_IMAGE_DEVICE_STATE_INACTIVE);
+  g_return_if_fail (action == FP_DEVICE_ACTION_ENROLL ||
+                    action == FP_DEVICE_ACTION_VERIFY ||
+                    action == FP_DEVICE_ACTION_IDENTIFY ||
+                    action == FP_DEVICE_ACTION_CAPTURE);
+
+  error = fpi_device_retry_new (retry);
+
+  if (action == FP_DEVICE_ACTION_ENROLL)
+    {
+      g_debug ("Reporting retry during enroll");
+      fpi_device_enroll_progress (FP_DEVICE (self), priv->enroll_stage, NULL, error);
+    }
+  else
+    {
+      /* We abort the operation and let the surrounding code retry in the
+       * non-enroll case (this is identical to a session error). */
+      g_debug ("Abort current operation due to retry (non-enroll case)");
+      fpi_image_device_deactivate (self);
+      fpi_device_action_error (FP_DEVICE (self), error);
+    }
+}
+
+/**
+ * fpi_image_device_session_error:
+ * @self: a #FpImageDevice imaging fingerprint device
+ * @error: The #GError to report
+ *
+ * Report an error while interacting with the device. This effectively
+ * aborts the current ongoing action.
+ */
+void
+fpi_image_device_session_error (FpImageDevice *self, GError *error)
+{
+  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
+
+  g_return_if_fail (self);
+
+  if (!error)
+    {
+      g_warning ("Driver did not provide an error, generating a generic one");
+      error = g_error_new (FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL, "Driver reported session error without an error");
+    }
+
+  if (!priv->active)
+    {
+      FpDeviceAction action = fpi_device_get_current_action (FP_DEVICE (self));
+      g_warning ("Driver reported session error, but device is inactive.");
+
+      if (action != FP_DEVICE_ACTION_NONE)
+        {
+          g_warning ("Translating to activation failure!");
+          fpi_image_device_activate_complete (self, error);
+          return;
+        }
+    }
+  else if (priv->state == FP_IMAGE_DEVICE_STATE_INACTIVE)
+    {
+      g_warning ("Driver reported session error; translating to deactivation failure.");
+      fpi_image_device_deactivate_complete (self, error);
+      return;
+    }
+
+  if (error->domain == FP_DEVICE_RETRY)
+    g_warning ("Driver should report retries using fpi_image_device_retry_scan!");
+
+  fpi_image_device_deactivate (self);
+  fpi_device_action_error (FP_DEVICE (self), error);
+}
+
+/**
+ * fpi_image_device_activate_complete:
+ * @self: a #FpImageDevice imaging fingerprint device
+ * @error: A #GError or %NULL on success
+ *
+ * Reports completion of device activation.
+ */
+void
+fpi_image_device_activate_complete (FpImageDevice *self, GError *error)
+{
+  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
+  FpDeviceAction action;
+
+  action = fpi_device_get_current_action (FP_DEVICE (self));
+
+  g_return_if_fail (priv->active == FALSE);
+  g_return_if_fail (action == FP_DEVICE_ACTION_ENROLL ||
+                    action == FP_DEVICE_ACTION_VERIFY ||
+                    action == FP_DEVICE_ACTION_IDENTIFY ||
+                    action == FP_DEVICE_ACTION_CAPTURE);
+
+  if (error)
+    {
+      g_debug ("Image device activation failed");
+      fpi_device_action_error (FP_DEVICE (self), error);
+      return;
+    }
+
+  g_debug ("Image device activation completed");
+
+  priv->active = TRUE;
+
+  /* We always want to capture at this point, move to AWAIT_FINGER
+   * state. */
+  fp_image_device_change_state (self, FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON);
+}
+
+/**
+ * fpi_image_device_deactivate_complete:
+ * @self: a #FpImageDevice imaging fingerprint device
+ * @error: A #GError or %NULL on success
+ *
+ * Reports completion of device deactivation.
+ */
+void
+fpi_image_device_deactivate_complete (FpImageDevice *self, GError *error)
+{
+  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
+  FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (self);
+  FpDeviceAction action;
+
+  g_return_if_fail (priv->active == TRUE);
+  g_return_if_fail (priv->state == FP_IMAGE_DEVICE_STATE_INACTIVE);
+
+  g_debug ("Image device deactivation completed");
+
+  priv->active = FALSE;
+
+  /* Deactivation completed. As we deactivate in the background
+   * there may already be a new task pending. Check whether we
+   * need to do anything. */
+  action = fpi_device_get_current_action (FP_DEVICE (self));
+
+  /* Special case, if we should be closing, but didn't due to a running
+   * deactivation, then do so now. */
+  if (action == FP_DEVICE_ACTION_CLOSE)
+    {
+      cls->img_close (self);
+      return;
+    }
+
+  /* We might be waiting to be able to activate again. */
+  if (priv->pending_activation_timeout_id)
+    {
+      g_clear_handle_id (&priv->pending_activation_timeout_id, g_source_remove);
+      priv->pending_activation_timeout_id =
+        g_idle_add ((GSourceFunc) fpi_image_device_activate, self);
+    }
+}
+
+/**
+ * fpi_image_device_open_complete:
+ * @self: a #FpImageDevice imaging fingerprint device
+ * @error: A #GError or %NULL on success
+ *
+ * Reports completion of open operation.
+ */
+void
+fpi_image_device_open_complete (FpImageDevice *self, GError *error)
+{
+  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
+  FpDeviceAction action;
+
+  action = fpi_device_get_current_action (FP_DEVICE (self));
+
+  g_return_if_fail (priv->active == FALSE);
+  g_return_if_fail (action == FP_DEVICE_ACTION_OPEN);
+
+  g_debug ("Image device open completed");
+
+  priv->state = FP_IMAGE_DEVICE_STATE_INACTIVE;
+  g_object_notify (G_OBJECT (self), "fp-image-device-state");
+
+  fpi_device_open_complete (FP_DEVICE (self), error);
+}
+
+/**
+ * fpi_image_device_close_complete:
+ * @self: a #FpImageDevice imaging fingerprint device
+ * @error: A #GError or %NULL on success
+ *
+ * Reports completion of close operation.
+ */
+void
+fpi_image_device_close_complete (FpImageDevice *self, GError *error)
+{
+  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
+  FpDeviceAction action;
+
+  action = fpi_device_get_current_action (FP_DEVICE (self));
+
+  g_debug ("Image device close completed");
+
+  g_return_if_fail (priv->active == FALSE);
+  g_return_if_fail (action == FP_DEVICE_ACTION_CLOSE);
+
+  priv->state = FP_IMAGE_DEVICE_STATE_INACTIVE;
+  g_object_notify (G_OBJECT (self), "fp-image-device-state");
+
+  fpi_device_close_complete (FP_DEVICE (self), error);
+}
diff --git a/libfprint/meson.build b/libfprint/meson.build
index 4a34cbd..9eb4849 100644
--- a/libfprint/meson.build
+++ b/libfprint/meson.build
@@ -9,6 +9,7 @@ libfprint_sources = [
 libfprint_private_sources = [
     'fpi-assembling.c',
     'fpi-device.c',
+    'fpi-image-device.c',
     'fpi-ssm.c',
     'fpi-usb-transfer.c',
     'fpi-byte-reader.c',
-- 
2.24.1