Smart Camera Pixelnetica™ Document Scanning SDK for Android

The camera library provides a ready-made camera activity: it watches the live preview for a document, guides the user with an on-screen frame and hints, captures automatically when the shot is good, and hands the captured pages back to your code as a list of image URIs. You launch it with one call and receive results through the standard Activity Result APIs — no camera code of your own.

Include into the Project

Add the dependency to your module-level build script:

   dependencies {
       implementation("com.pixelnetica.sdk:camera:3.2.0")
       // Other dependencies
   }

Or, if you use the Version Catalog:

dependencies {
  implementation(libs.pixelnetica.camera)
  // Other dependencies
}

Usage

Register the CameraContract in your Activity, Fragment, or Composable:

In an Activity or Fragment

private val cameraRequest = registerForActivityResult(CameraContract()) { uriList ->
    // Handle the result
}

In a Composable

val cameraLauncher = rememberLauncherForActivityResult(
    contract = CameraContract()
) { uriList ->
    // Handle the result
}

The camera screen is a regular activity behind an ActivityResultContract, so hosting it in a Compose application needs nothing special — the launcher above is the complete integration.

Launching the Camera Activity

cameraLauncher.launch(CameraContract.CameraParams())

Configuring the Launch

CameraParams controls how the camera screen behaves for this launch. Every field has a working default, so CameraParams() is a complete configuration:

  • outputDir — the directory where captured pages are written. Left null, the activity uses its own storage.
  • tmpPrefix — the file-name prefix for the image files it creates.
  • authority — the authority of a FileProvider your application declares. With it set, results come back as content:// URIs your app can grant to other apps; left null, they arrive as plain file:// URIs, which other apps cannot open. Set it if the captured pages will be shared onward.
  • singleShottrue to close the camera after the first captured page instead of letting the user capture several.
  • shotOnTap — whether a tap on the preview captures a page (true by default).
  • autoShotMode — the conditions for automatic capture, combined from FrameObserver.Observation flags. The default captures when a stable document outline has been found (CUTOUT_READY) and the device is held steady (DEVICE_READY).

Receiving the Results

The callback receives a List<Uri> — one URI per captured page, in capture order. With singleShot the list holds at most one entry. If the user leaves the camera without capturing (the back button, or a cancelled launch), the list is empty; there is no error to handle. Whether the URIs are content:// or file:// follows the authority parameter above.

What the User Sees

The camera screen guides the user with a coloured frame around the detected document:

  • No frame — the camera is still searching for a document.
  • Yellow — a document is detected and being framed; the user should hold steady.
  • Green — automatic capture is imminent; the conditions in autoShotMode are met.
  • Red — shown briefly while the shot is being taken.

The colours follow the same convention as the iOS SDK’s smart camera, so users of an application shipping on both platforms get the same signals. Short hint texts in the on-screen console (“Looking for document” and similar) accompany the frame.

Testing Against the Camera Screen

The camera screen is the SDK’s own UI, so an automated UI test cannot address its controls the way it would address yours. One control carries a supported contract for that purpose: the shutter button publishes the content description Take photo, and its enabled state is the SDK’s readiness signal.

val shutter = device.wait(Until.findObject(By.desc("Take photo")), 15_000)
check(shutter != null && shutter.isEnabled) { "the camera is not ready to capture" }
shutter.click()

Two things this lets a test do that it otherwise cannot. It can wait for readiness rather than guess at it — the camera takes a moment to bind, and a shot requested before then is discarded rather than queued, so a test that taps too early sees nothing happen and no error. And it can address the control without depending on the SDK’s internal view tree, whose resource identifiers carry no compatibility promise.

What the enabled state means precisely: the camera is bound and a shot will be accepted. It does not promise the preview is already delivering frames, nor that a document has been detected. A test that needs those should wait on what it actually needs.

The content description is also the control’s accessibility label, so it is what a screen reader announces.

Permissions and Hardware

The library’s manifest declares the camera permission, and manifest merging carries it into your application — you declare nothing. At runtime the camera screen requests the permission itself the first time it opens.

The library also marks the android.hardware.camera feature as required, which means app stores hide an application that includes the camera module from devices without a camera. If your application must remain installable on such devices, override the <uses-feature> requirement in your own manifest and launch the camera only after checking availability.

Detection quality follows the camera stream: an autofocus rear camera is expected, and a sharp, well-lit preview finds document edges faster. If the device has no usable back camera, or the camera fails to start, the screen stays up, reports “Camera is not available” in its console, and returns the standard cancelled result when the user leaves.

Recipe: Capture a Single Page

For flows that need exactly one page — attaching one document to a form, for example — configure the launch to close after the first capture:

cameraLauncher.launch(
    CameraContract.CameraParams(
        singleShot = true,
    )
)

The result list then holds one URI after a capture, or none if the user backed out.

Top