DocV Android Camera Permissions

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:

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

Camera authorization states

Camera access can be in one of the following states:

State Meaning What you can do
Granted User approved camera access Camera capture can proceed
Denied User denied access You may re-request permission
Denied with “Don’t ask again” User permanently denied access Direct user to App Settings
Restricted Access 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:

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

When to use this approach

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

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

UX and implementation best practices