Android Camera Permissions

Predictive Document Verification (DocV) Android SDKs that capture documents or images require access to the device camera. On Android, camera access is controlled by the operating system and gated by both manifest declarations and (on modern Android versions) runtime user consent.

If camera access isn’t granted, document capture cannot proceed.


How Android camera permissions work

Android camera access is gated by multiple layers, all of which must be satisfied before document capture can begin:

  1. Manifest permission (android.permission.CAMERA)
    Required for any camera usage. If this permission is missing, the system will block access immediately.

  2. Runtime permission (Android 6.0 / API 23+)
    The system permission prompt appears only when your app explicitly requests camera access.

  3. User decisions and device policy
    Camera access may still be unavailable depending on user action or system restrictions:

    • If the user denies permission, your app may usually request it again.
    • If the user denies permission and selects Don’t ask again (or the OS implicitly applies it), the system will not show the prompt again. In this state, the user must manually re-enable access from App Settings.
    • On some devices, camera access may be restricted by parental controls, MDM, or work profile policies, making it unavailable regardless of user action.

If camera access is unavailable, the host app is responsible for:

  • Detecting the denied or restricted state
  • Guiding the user to Settings when permission can no longer be requested

Without camera access, the DocV document capture flow cannot be completed.


Camera authorization states

Camera access can be in one of the following states:

StateMeaningWhat you can do
GrantedUser approved camera accessCamera capture can proceed
DeniedUser denied accessYou may re-request permission
Denied with “Don’t ask again”User permanently denied accessDirect user to App Settings
RestrictedAccess blocked by system policy (MDM, work profile, parental controls)Cannot be resolved programmatically

Required configuration

Before using any DocV SDK feature that accesses the camera, your app must be correctly configured at the manifest level.


1) Declare the camera permission

Add the camera permission to your app’s AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />

If this permission is missing:

  • The system will block all camera access.
  • The DocV SDK will not be able to start a capture session.

2) Declare camera hardware requirement (recommended)

If your document capture flow requires a camera (typical for DocV), declare the camera as required:

<uses-feature
    android:name="android.hardware.camera"
    android:required="true" />

This ensures your app is only installable on devices with camera hardware.

If you want your app to remain installable on devices without a camera (not typical for DocV capture), set:

android:required="false"

In this case, your app must detect the absence of camera hardware and prevent users from starting a document capture flow.


Recommended integration patterns

Option 1 — Handle denied permission after SDK launch

If the DocV SDK returns an error indicating camera permission was declined or unavailable, the host app should:

  1. Detect the permission-related error from the SDK result.
  2. Explain why camera access is required.
  3. Provide a clear path to App Settings.

Suggested UI copy

  • Title: Camera access needed
  • Body: Camera access is required to complete document capture. Please enable it in Settings to continue.
  • Action: Open Settings

When to use this approach

  • When permission denial is expected to be rare.
  • When you prefer a simpler integration with recovery handled after failure.

Option 2 — Pre-check and request permission before launching the SDK (recommended)

This approach prevents starting a DocV session when camera access is unavailable.

Host app flow

  1. Check whether camera permission is granted.
  2. If not granted:
    • If rationale should be shown, present explanation UI, then request permission.
    • Otherwise, request permission directly.
  3. If permission is blocked or restricted, show app-owned UI and provide Open Settings.
  4. Launch the DocV SDK only after permission is granted.

Benefits

  • Avoids launching the SDK into an unrecoverable state.
  • Improves conversion by preventing dead-end sessions.
  • Gives the host app full control over permission UX.

Example implementation

The following example demonstrates a complete pre-check and request flow before launching a DocV session using the Activity Result API and the Predictive DocV SDK.

package com.socure.docv.sdk.sample

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.socure.docv.capturesdk.api.SocureDocVContext
import com.socure.docv.capturesdk.api.SocureSdk
import com.socure.docv.capturesdk.common.utils.SocureDocVFailure
import com.socure.docv.capturesdk.common.utils.SocureDocVSuccess

private const val TAG = "MainActivity"
private const val PUBLIC_KEY = "YOUR_PUBLIC_KEY_HERE"

class MainActivity : AppCompatActivity() {

    private val requestCameraPermission =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
            if (granted) {
                startDocVFlow()
            } else {
                val canAskAgain = ActivityCompat.shouldShowRequestPermissionRationale(
                    this,
                    Manifest.permission.CAMERA
                )
                showCameraDeniedDialog(canAskAgain)
            }
        }

    private val startForResult =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
            val data = result.data ?: return@registerForActivityResult

            SocureSdk.getResult(data) { sdkResult ->
                Log.d(TAG, "SDK result: $sdkResult")

                when (sdkResult) {
                    is SocureDocVSuccess -> {
                        Toast.makeText(
                            this,
                            "Success. deviceSessionToken=${sdkResult.deviceSessionToken}",
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                    is SocureDocVFailure -> {
                        Toast.makeText(
                            this,
                            "Error. deviceSessionToken=${sdkResult.deviceSessionToken}, error=${sdkResult.error}",
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                    else -> {
                        Toast.makeText(this, "Unknown result", Toast.LENGTH_SHORT).show()
                    }
                }
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<Button>(R.id.actionStartSocureDocvSDK).setOnClickListener {
            checkAndRequestCameraPermission()
        }
    }

    private fun checkAndRequestCameraPermission() {
        val granted = ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.CAMERA
        ) == PackageManager.PERMISSION_GRANTED

        if (granted) {
            startDocVFlow()
            return
        }

        val shouldShowRationale = ActivityCompat.shouldShowRequestPermissionRationale(
            this,
            Manifest.permission.CAMERA
        )

        if (shouldShowRationale) {
            showRationaleThenRequest()
        } else {
            requestCameraPermission.launch(Manifest.permission.CAMERA)
        }
    }

    private fun showRationaleThenRequest() {
        AlertDialog.Builder(this)
            .setTitle("Camera access needed")
            .setMessage("Camera access is required to capture your document.")
            .setPositiveButton("Continue") { _, _ ->
                requestCameraPermission.launch(Manifest.permission.CAMERA)
            }
            .setNegativeButton("Cancel", null)
            .show()
    }

    private fun showCameraDeniedDialog(canAskAgain: Boolean) {
        val message = if (canAskAgain) {
            "Camera permission is required to continue."
        } else {
            "Camera permission is disabled. Please enable it in Settings to continue."
        }

        AlertDialog.Builder(this)
            .setTitle("Camera access needed")
            .setMessage(message)
            .setPositiveButton(if (canAskAgain) "Try again" else "Open Settings") { _, _ ->
                if (canAskAgain) {
                    requestCameraPermission.launch(Manifest.permission.CAMERA)
                } else {
                    openAppSettings()
                }
            }
            .setNegativeButton("Cancel", null)
            .show()
    }

    private fun openAppSettings() {
        startActivity(
            Intent(
                Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                Uri.fromParts("package", packageName, null)
            ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        )
    }

    private fun startDocVFlow() {
        // Launch Socure DocV SDK (token is a placeholder for documentation)
        val intent = SocureSdk.getIntent(
            this,
            SocureDocVContext(
                // placeholder token (normally fetched from RiskOS backend)
                "DOCV_TRANSACTION_TOKEN_VALUE",
                PUBLIC_KEY,
                false,
                null,
                null
            )
        )
        startForResult.launch(intent)
    }
}

Key points in the example

  • Checks permission first using ContextCompat.checkSelfPermission(...) to avoid launching the SDK when camera access is unavailable.

  • Requests permission using the Activity Result API via ActivityResultContracts.RequestPermission(), ensuring permission handling is lifecycle-aware and resilient to configuration changes.

  • Shows rationale when appropriate using shouldShowRequestPermissionRationale(...) to explain why camera access is required before re-requesting permission.

  • Differentiates “ask again” vs “blocked” states:

    • canAskAgain == true
      Show in-app UX and re-request permission.
    • canAskAgain == false
      Permission is likely blocked (“Don’t ask again”) or restricted by policy. Deep-link the user to App Settings.
  • Deep-links to App Settings when permission can no longer be requested programmatically:

    • Settings.ACTION_APPLICATION_DETAILS_SETTINGS
    • Uri.fromParts("package", packageName, null)
  • Launches the DocV SDK only after permission is granted, preventing unrecoverable or dead-end capture sessions.

  • Receives SDK results from SocureSdk.getResult(data) and surfaces success or failure states to the user via Toasts or other app-owned UI.


UX and implementation best practices

  • Keep permission explanations short and contextual.
  • Use consistent CTAs:
    • Re-requesting permission → Continue or Try again
    • Blocked permission → Open Settings
  • Re-check permission after returning from Settings (commonly in onResume).
  • Consider tracking analytics events such as:
    • camera_permission_denied
    • camera_permission_blocked
    • opened_settings
    • docv_started
    • docv_success and docv_failure