Android Settings

Please go through session creation instructions given here before proceeding.

  1. Allow camera and internet permissions in your AndroidManifest.xml

    <uses-feature android:name="android.hardware.camera" android:required="false" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.INTERNET" />
    
  2. Create file_paths.xml in /res/xml/ (this is required for the Android camera app to work)

    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <external-files-path name="my_images" path="Pictures" />
    </paths>
    
  3. Now in your manifest, create a provider for this file path:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    
        <!-- Permissions here -->
      
        <application
        	android:icon="@mipmap/ic_launcher"
          android:label="@string/app_name" 
        >
          	<!-- Add this to your manifest -->
            <provider
                android:name="androidx.core.content.FileProvider"
                android:authorities="${applicationId}.provider"
                android:exported="false"
                android:grantUriPermissions="true">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/file_paths" />
            </provider>
    
            <!-- Your activities go here -->
        </application>
    </manifest>
    
  4. Now you can create an activity that will host your webview with your session URL. A working example of the same is given below...

Full Working Example:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <WebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>
package com.example.aipriseandroidtest

import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.webkit.JavascriptInterface
import android.webkit.PermissionRequest
import android.webkit.ValueCallback
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

// JS Interface to listen to events from the WebView
class WebAppInterface(private val activity: MyActivity) {
    @JavascriptInterface
    fun postMessage(message: String) {
        // Here you can listen to the success, complete and error events...
        if (message == "AiPriseVerification:Success") {
            // User reached the last page of verification
        } else if (message == "AiPriseVerification:Complete") {
            // User clicked on the 'continue` button on the last page. Time to hide the webview!
        } else if (message.startsWith("AiPriseVerification:Error:")) {
            // Happens when the session URL is invalid or the session is completed / expired.
            // Extract error code from the end of message string and check.
        }
        
        // This informs Android if we have to trigger a file picker or the camera app when the user clicks any file upload button.
        // Copy this as it is. Don't modify!
        else if (message == "AiPriseFilePickerSource:file") {
            activity.mode = "file"
        } else if (message == "AiPriseFilePickerSource:camera") {
            activity.mode = "camera"
        }
    }
}

// Your activity...
class MyActivity : AppCompatActivity() {

    private lateinit var myWebView: WebView
  
    // To handle runtime camera permissions
    private var currentPermissionRequest: PermissionRequest? = null
    private val CAMERA_PERMISSION_REQUEST_CODE = 1

    // To handle file picker + camera app capture
    private var cameraImageUri: Uri? = null
    private lateinit var cameraLauncher: ActivityResultLauncher<Intent>
    private lateinit var fileChooserLauncher: ActivityResultLauncher<Intent>
    private var filePathCallback: ValueCallback<Array<Uri>>? = null

    var mode: String = "file"; // Flag that decides if we open file picker or the camera app

    // Handle activity creation
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)

        // Create launcher to open file picker when user clicks on upload button in the webview
        fileChooserLauncher = registerForActivityResult(
            ActivityResultContracts.StartActivityForResult()
        ) { result ->
            if (result.resultCode == Activity.RESULT_OK) {
                val resultData = result.data
                val resultUris = if (resultData == null || resultData.data == null) null else arrayOf(resultData.data!!)
                filePathCallback?.onReceiveValue(resultUris)
            } else {
                filePathCallback?.onReceiveValue(null)
            }
            filePathCallback = null
        }

        // Create launcher to open phone's camera app when user clicks on capture in the webview
        cameraLauncher = registerForActivityResult(
            ActivityResultContracts.StartActivityForResult()
        ) { result ->
            if (result.resultCode == Activity.RESULT_OK) {
                val resultUris = if (cameraImageUri != null) arrayOf(cameraImageUri!!) else null
                filePathCallback?.onReceiveValue(resultUris)
            } else {
                filePathCallback?.onReceiveValue(null)
            }
            filePathCallback = null
        }

        // Find the webview and create clients...
        
        myWebView = findViewById(R.id.webview)

        myWebView.webViewClient = WebViewClient()
        myWebView.webChromeClient = object : WebChromeClient() {
            // Handle camera permission request
            override fun onPermissionRequest(request: PermissionRequest) {
                if (request.resources.contains(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
                    if (ContextCompat.checkSelfPermission(this@MyActivity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                        currentPermissionRequest = request
                        ActivityCompat.requestPermissions(this@MyActivity, arrayOf(Manifest.permission.CAMERA), CAMERA_PERMISSION_REQUEST_CODE)
                    } else {
                        request.grant(request.resources)
                    }
                }
            }

            // Handle file chooser request (decides if camera should be opened or file picker)
            override fun onShowFileChooser(
                webView: WebView?,
                newFilePathCallback: ValueCallback<Array<Uri>>?,
                fileChooserParams: FileChooserParams?
            ): Boolean {
                filePathCallback = newFilePathCallback

                // Open camera app
                if (mode == "camera") {
                    val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
                    cameraImageUri = FileProvider.getUriForFile(
                        this@MyActivity,
                        "${applicationContext.packageName}.provider",
                        createImageFile()
                    )
                    intent.putExtra(MediaStore.EXTRA_OUTPUT, cameraImageUri)
                    cameraLauncher.launch(intent)
                }
                // Open file picker
                else {
                    val intent = Intent(Intent.ACTION_GET_CONTENT)
                    intent.addCategory(Intent.CATEGORY_OPENABLE)
                    intent.type = "*/*"
                    fileChooserLauncher.launch(intent)
                }
                return true
            }
        }

        // Update webview settings
        myWebView.settings.javaScriptEnabled = true
        myWebView.settings.allowFileAccess = true
        myWebView.settings.domStorageEnabled = true
        myWebView.settings.mediaPlaybackRequiresUserGesture = false
      
        // Attach the interface to your webview to listen to events
        myWebView.addJavascriptInterface(WebAppInterface(this), "Android")

        // Load the URL returned by our API
        myWebView.loadUrl("YOUR_SESSION_URL_HERE")
    }

    // Handle response from permission request
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
            if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                currentPermissionRequest?.grant(currentPermissionRequest?.resources)
            } else {
                currentPermissionRequest?.deny()
            }
            currentPermissionRequest = null
        }
    }

    // Just a helper function that gets us the output of the camera capture as a `File` instance
    private fun createImageFile(): File {
        val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
        val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
        return File.createTempFile("JPEG_${timeStamp}_", ".jpg", storageDir)
    }
}