# Android 🤖

Our `CardScanActivity` Kotlin Activity makes it easy to add insurance card scanning to any Android application in **5 minutes or less.**

### Requirements

* Android API level 23 or higher
* AndroidX compatibility
* Kotlin coroutine compatibility

### Installation

The library is published in the [maven central repository](https://search.maven.org/search?q=g:ai.cardscan), so installation is as simple as adding the dependency to your `build.gradle`

```
dependencies {
    implementation 'ai.cardscan:insurance-cardscan:0.3.5'   
}
```

### Usage

Import the necessary classes from the library into your activity or fragment:

```kotlin
import ai.cardscan.insurance.CardScanActivity
// import ai.cardscan.insurance.data.*
import ai.cardscan.insurance.data.CardScanHelper
import ai.cardscan.insurance.data.CardScanConfig
```

#### Basic usage Example:

```kotlin
import ai.cardscan.insurance.CardScanActivity
import ai.cardscan.insurance.data.CardScanConfig
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity

class OnboardingActivity : AppCompatActivity() {

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

        findViewById<View>(R.id.btnScanCard).setOnClickListener {
            setUpCardScanHelper()
            launchCardScanActivity()
        }
    }

    private fun launchCardScanActivity() {
        CardScanActivity.launch(
            // Pass an activity to the launcher
            this@MainActivity,
            // A CardScanConfig instance to connect to the server and customize UI
            CardScanConfig(
                // your token goes here
                sessionToken = "xxx",
            ),
            // And other optional parameters to alter behavior
            closeAfterSuccess = true, // Close the scanner after successful scan? 
                                    // defaults to true
            closeAfterSuccessDelay = 1500 // If set to close automatically, 
                                // how long to wait after scan to close? In milliseconds
            closeAfterErrorDelay = 2000 // Cardscan activity is killed automatically after an error
                                // You can decide between 0 and 30 seconds, how long to wait before closing                    
        )
    }
    
    
    private fun setUpCardScanHelper() {
        // Helper class to setup observers and create the appropiate callbacks
        // CardScanHelper takes in a lifecycleOwner, 
        // "this" in the case of setup within an activity,
        // or the host activity in the case of a fragment, or viewLifecycleOwner
        cardScanHelper = CardScanHelper(this).apply {
            onSuccess { card: ScannedCard ->
                Toast.makeText(this@MainActivity, "Card Scanned: ${card.cardId}", Toast.LENGTH_SHORT).show()
            }
            onError { error: CardError? ->
                Toast.makeText(this@MainActivity, "Error encountered!", Toast.LENGTH_SHORT).show()
                print(error?.message)
            }
            onRetry {
                Toast.makeText(this@MainActivity, "User retried after failed scan!", Toast.LENGTH_SHORT).show()
            }
            onCancel {
                Toast.makeText(this@MainActivity, "User closed the scanner with close button!", Toast.LENGTH_SHORT).show()
            }
            onEligibilitySuccess { eligibility:Eligibility ->
                Toast.makeText(this@MainActivity, "Eligibility info found!", Toast.LENGTH_SHORT).show()
            }
            onEligibilityError { eligibilityError:EligibilityError? ->
                Toast.makeText(this@MainActivity, "Error during eligibility check!", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

```

### Available Properties

When launching`CardScanActivity`, a `CardScanConfig` instance should be passed with properties for server connection and UI customization.

```kotlin
CardScanConfig(
  // Required
  sessionToken: token,
  live: false,

  // Optional
  backsideSupport: scanBackside,
  eligibility = EligibilityRequest( // We now support eligibility checks!
                    Subscriber(
                        firstName = "Subscriber's first name",
                        lastName = "Subscriber's last name",
                        dateOfBirth = "19020403" // Always in format YYYYMMDD
                    ),
                    Provider(
                        firstName = "Provider's first name",
                        lastName = "Provider's last name",
                        npi = "12345677"
                    )
                )

  // UI Customization
  messages = CardScanMessages(
                    autoCaptureTitle = "My Auto Capture Title",
                    manualCaptureTitle = "My Manual Capture Title",
                    processingTitle = "My Processing Title",
                    frontSideCompletedTitle = "My FrontSide Completed Title",
                    completedTitle = "My Completed Title",
                    retryTitle = "Me Retry Title",
                    errorTitle = "My Error Title",
                    cameraErrorTitle = "My Camera Error Title"
                )
  messageFontSize: messageFontSize, // Float
  messageTextColor: messageTextColor, // Int
  messageBackgroundColor: messageBackgroundColor, // Int
  autoSwitchActiveColor: autoSwitchActiveColor, // Int
  autoSwitchInactiveColor: autoSwitchInactiveColor, // Int
  progressBarColor: progressBarColor, // Int
  widgetBackgroundColor: widgetBackgroundColor, // Int
)
```

***

### Main Config Props

| Prop            | Required | Type               | Description                                                                                        |
| --------------- | -------- | ------------------ | -------------------------------------------------------------------------------------------------- |
| live            | false    | Boolean            | Toggle the production or sandbox version. **Default**: false                                       |
| sessionToken    | true     | String             | A JWT token for the current user, see [Authentication](https://docs.cardscan.ai/authentication)    |
| backsideSupport | false    | Boolean            | <p>Enable scanning of the front and back side of the card.<br><strong>Default</strong>: false</p>  |
| eligibility     | false    | EligibilityRequest | Send an EligibilityRequest object with the properties shown above to perform an eligibility check. |

### UI/UX Customization Props

The card scanner view is designed to be customizable. Please see the [customization](https://docs.cardscan.ai/ui-components/customization "mention") section of UI Components to adjust these elements to match your application's branding and theme:

{% hint style="info" %}
**Note:** All UI/UX Props are optional
{% endhint %}

| Prop                    | Type   | Description                                                                               |
| ----------------------- | ------ | ----------------------------------------------------------------------------------------- |
| messages                | Object | Customize the text displayed by the UI.                                                   |
| messageFontSize         | Float  | Set the size of the text displayed by the UI.                                             |
| messageTextColor        | Int    | Set the color of the text displayed by the UI.                                            |
| messageBackgroundColor  | Int    | Set the background color of the text displayed by the UI.                                 |
| autoSwitchActiveColor   | Int    | Set the color of the auto scan switch.                                                    |
| autoSwitchInactiveColor | Int    | Set the color of the disabled auto scan switch.                                           |
| progressBarColor        | Int    | Set the color of the progress bars or bounding box that surrounds the card scanning area. |
| widgetBackgroundColor   | Int    | Set the main background color for the widget.                                             |

### Cardscan activity launcher props

| Prop                   | Type           | Description                                                                                 |
| ---------------------- | -------------- | ------------------------------------------------------------------------------------------- |
| activity               | Activity       | The activity from which the CardScan activity will be started. Usually MainActivity         |
| properties             | CardScanConfig | Object to set the parameters for the scanner                                                |
| closeAfterSuccess      | Boolean        | Whether to automatically finish Cardscan activity after successful scan.                    |
| closeAfterSuccessDelay | Long           | Time in milliseconds to wait before finishing Cardscan activity, after a successful scan.   |
| closeAfterErrorDelay   | Long           | Time in milliseconds to wait before finishing Cardscan activity, after an error was thrown. |

### onSuccess Callback

`onSuccess` triggers when card scanning process completes successfully. This function receives the scanned card data as an argument.

#### Usage

To use the `onSuccess` callback, pass a function that receives the scanned card data to the onSuccess function in CardScanHelper:

```kotlin
fun handleCardScanSuccess(card: ScannedCard?) {
  print("Card scanned successfully: $card")
}

 private fun setUpCardScanHelper() {
        cardScanHelper = CardScanHelper(this).apply {
            onSuccess { card: ScannedCard ->
                handleCardScanSuccess(card)
            }
        }
    }
)
```

In this example, the `handleCardScanSuccess` function logs the scanned card data to the console when the scanning process is completed successfully and your MainActivity or fragment resumes.

### onError Callback

`onError` triggers after a failure occurs during the card scanning process. This function receives an error object as an argument.

#### Usage

Pass a function that receives the error object or implement your callback directly inside the helper:

```kotlin
fun handleCardScanError(card: CardError?) {
  print("Scanning failed: $card")
}

 private fun setUpCardScanHelper() {
        cardScanHelper = CardScanHelper(this).apply {
            onSuccess { card: ScannedCard ->
                handleCardScanSuccess(card)
            }
            onError { error: CardError? -> 
                handleCardScanError(error)
            }
        }
    }
)
```

In this example, the `handleCardScanError` logs the error object when a scanning failure occurs.

By using the `onSuccess` and `onError` callbacks, you can handle successful and failed scanning event with custom actions or display appropriate messages to the user.

### onCancel and onRetry Callbacks

`onCancel` expects a function that will trigger after users cancel the card scanning process. This could be useful for tracking user behavior or displaying an appropriate message. `onRetry` expects a function that will trigger when your activity or fragment resumes, if the user had a retry of a scanning process.

```kotlin
private fun setUpCardScanHelper() {
        cardScanHelper = CardScanHelper(this).apply {
            onSuccess { card: ScannedCard ->
                handleCardScanSuccess(card)
            }
            onError { error: CardError? -> 
                handleCardScanError(error)
            }
            onCancel {
                // Handle a user closing the scanner with the close button!
            }
            onRetry {
                // Triggered after the user retried a scan
            }
        }
    }
```

### onEligibilitySuccess and onEligibilityError Callbacks

Both callbacks expect a function that will trigger in either case. `onElibiligitySuccess` callback will pass an `Eligibility` object and `onEligibilityError` will pas an `EligibilityError` object. Expect the same behavior as the other callbacks, this objects will be passed once your activity or fragment resumes.

```kotlin
private fun setUpCardScanHelper() {
        cardScanHelper = CardScanHelper(this).apply {
            onSuccess { card: ScannedCard ->
                handleCardScanSuccess(card)
            }
            onError { error: CardError? -> 
                handleCardScanError(error)
            }
            onCancel {
                // Handle a user closing the scanner with the close button!
            }
            onRetry {
                // Triggered after the user retried a scan
            }
            onEligibilitySuccess { eligibility:Eligibility ->
                handleEligibilitySuccess(eligibility)
            }
            onEligibilityError { eligibilityError:EligibilityError? ->
                handleEligibilityError(eligibilityError)
            }
        }
    }
```

### Camera Permissions 📸

Our SDK requires camera access to scan insurance cards. While the CardScan widget automatically requests camera permissions during widget load, it does not present any UI for handling permissions or manage permission failures, particularly on mobile devices.

**Requirement:** We require that developers handle camera permission requests ***before*** loading the CardScan widget. This will allow you to manage the permission flow consistently within your application, providing custom error handling and user feedback if permission is denied.

**Recommendation:** We recommend following best practices when requesting camera permissions:

* **Pre-flight the request:**

  Within your app, prompt the user with a clear explanation of why camera access is needed and ask for confirmation (Yes/No). This improves transparency, builds trust, and can reduce the likelihood of the user denying the request.
* **Handle permission rejection gracefully:**

  * **Soft rejection (user-level):** If the user declines the camera access within your app, provide a helpful message that explains the impact of this choice and give them the option to reconsider later.
  * **Hard rejection (system-level):** In case the user has previously denied the camera permission at the system level, guide them to the device settings where they can manually enable the camera access. Display an appropriate message explaining how to do this and the importance of enabling the permission for the app's functionality.

  For Android applications, we recommend taking a dive into the official docs: <https://developer.android.com/training/permissions/requesting>

#### Suggestion for code to include with Cardscan launcher:

```kotlin
// Permission launcher, displays dialog asking for permission
val requestPermissionLauncher =
    registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted: Boolean ->
        if (isGranted) {
            // Permission is granted, you can launch Cardscan activity safely
            launchCardScanActivity()
        } else {
            // Permission denied or dismissed.
            // Explain to user that feature is unavailable, because permission
            // is needed to use it.
        }
    }   
```

```kotlin
// Check permission status and act on it 
     private fun handleScannerOpen() {
        when {
            ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.CAMERA
            ) == PackageManager.PERMISSION_GRANTED -> {
                // Permission is granted, you can start
                // Cardscan activity safely.
                launchCardScanActivity()
            }

            ActivityCompat.shouldShowRequestPermissionRationale(
                this,
                Manifest.permission.CAMERA
            ) -> {
                // Show UI to app user to tell them why you need the permission.
                // This is a good place to take them to settings too
                // so they can grant permission there.
            }

            else -> {
                // Here you can directly ask for the permission
                requestPermissionLauncher.launch(Manifest.permission.CAMERA)
            }
        }
    }
```
