Customization

Learn how to customize our SDK and make it look like your app.

Custom layout creation

To create a new layout we recommend that you use the default templates from the SDKs and make the desired changes.

Step-by-step

  1. Declare the dependency on CameraView in your app-level gradle file.

// CameraX core library using the camera2 implementation
implementation "androidx.camera:camera-view:1.3.4"

2. Create a layout file in your project's layout directory using the CAF template.

3. Create your views, and parameterize the visibility and call methods of the ViewModel of each corresponding SDK according to the following tables. Example:

<layout>
...
    <androidx.camera.view.PreviewView
        android:id="@id/cameraImageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="@{viewModel.cameraVisibility ? View.VISIBLE : View.GONE}"
    />
    ...
</layout>

Variables and methods used in layout

All the methods and variables described below are accessed from the SDKViewModel class.

Methods

Method
Description
Return
SDK

takePhoto()

Responsible for initiating image capture in MANUAL mode

Void

DocumentDetector, PassiveFaceLiveness

close()

Responsible for closing the SDK.

Void

DocumentDetector, PassiveFaceLiveness, FaceAuthenticator

switchCamera()

Responsible for reversing the camera.

Void

DocumentDetector v7.x or below, PassiveFaceLiveness, FaceAuthenticator

State variables

The Layout used in the SDK is composed of several state variables, these variables are responsible for identifying the state that the SDK is in at each moment of its execution:

Variable
Description
Type
SDK

loadingStatus

Indicates the 'loading' state

Boolean

DocumentDetector, PassiveFaceLiveness, FaceAuthenticator

readyToCaptureStatus

Indicates that the SDK is ready to capture, after performing all validations for sensors, framing, face, etc.

Boolean

DocumentDetector, PassiveFaceLiveness, FaceAuthenticator

stepDoneSuccessfullyStatus

Indicates that the capture step ended successfully

Boolean

DocumentDetector, PassiveFaceLiveness, FaceAuthenticator

validationFailureStatus

Indicates if there are any faulty sensor checks, quality, framing, face distance, etc.

Boolean

DocumentDetector, PassiveFaceLiveness, FaceAuthenticator

validationFailureId

Indicates what type of error has occurred. See the table below

ValidationFailure

DocumentDetector, PassiveFaceLiveness, FaceAuthenticator

captureModeStatus

Indicates the capture mode enabled. May vary between AUTOMATIC and MANUAL

CaptureMode

DocumentDetector, PassiveFaceLiveness

maskStatus

Indicates the status of the mask. Can range from NORMAL, SUCCESS to ERROR

Mask

DocumentDetector v7.x or below, PassiveFaceLiveness, FaceAuthenticator

maskLayout

Responsible for returning the Drawable Resource Id used to define the mask.

Integer

DocumentDetector v7.x or below, PassiveFaceLiveness, FaceAuthenticator

previousStepName

Name of the previous step that was performed. If not, the value will be null. Example: Back of ID.

String

DocumentDetector v7.x or below

Visibility variables

Variable
Description
Type
SDK

popUpVisibility

Indicates the visibility of the step initialization popup

Boolean

DocumentDetector

manualCaptureButtonVisibility

Indicates visibility of the manual capture button

Boolean

DocumentDetector, PassiveFaceLiveness

switchCameraButtonVisibility

Indicates the visibility of the reverse camera button

Boolean

DocumentDetector v7.x or below, PassiveFaceLiveness, FaceAuthenticator

statusVisibility

Responsible for the visibility of the SDK status message.

Boolean

DocumentDetector v7.x or below, PassiveFaceLiveness, FaceAuthenticator

statusMessage

Returns the status message. Customize with MessageSettings

String

DocumentDetector v7.x or below, PassiveFaceLiveness, FaceAuthenticator

feedbackStatusMessage

Returns the status message. Customize with MessageSettings

String

DocumentDetector

cameraVisibility

Responsible for camera visibility.

Boolean

DocumentDetector, PassiveFaceLiveness, FaceAuthenticator

maskVisibility

Indicates mask visibility.

Boolean

DocumentDetector v7.x or below, PassiveFaceLiveness, FaceAuthenticator

switchButtonVisibility

Responsible for the visibility of the camera flip button.

This variable has been deprecated, we recommend using the switchCameraButtonVisibility variable.

Boolean

DocumentDetector v7.x or below, PassiveFaceLiveness, FaceAuthenticator

serverRequesting

Responsible for the visibility of the loading displayed by the SDK.

This variable has been deprecated, we recommend using the loadingStatus variable.

Boolean

DocumentDetector v7.x or below, PassiveFaceLiveness, FaceAuthenticator

buttonVisibility

Sets the visibility of the manual capture button.

This variable has been deprecated, use manualCaptureButtonVisibility.

Boolean

DocumentDetector v7.x or below, PassiveFaceLiveness

ValidationFailure

Each SDK contains a number of validation errors that can occur while running. These mostly generate the "error mask" state and prevent the capture from being performed:

Error
Description
SDK

SENSOR_LUMINOSITY_FAILURE

Brightness sensor. The environment is too dark

DocumentDetector, PassiveFaceLiveness

SENSOR_ORIENTATION_FAILURE

Orientation sensor. Device not in correct position

DocumentDetector, PassiveFaceLiveness

SENSOR_STABILITY_FAILURE

Stability sensor. The device is in motion

DocumentDetector, PassiveFaceLiveness, FaceAuthenticator

FRAMING_FAILURE

Document or face-framing

DocumentDetector, PassiveFaceLiveness, FaceAuthenticator

EYES_CLOSED_FAILURE

A face with closed eyes was identified

PassiveFaceLiveness, FaceAuthenticator

FACE_NOT_FOUND

No face was found

PassiveFaceLiveness, FaceAuthenticator

FACE_TOO_FAR

Face too far away.

PassiveFaceLiveness, FaceAuthenticator

FACE_TOO_CLOSE

Very close face.

PassiveFaceLiveness, FaceAuthenticator

ANGULATION_X_FAILURE

Incorrect face angle on the X axis.

PassiveFaceLiveness

ANGULATION_Y_FAILURE

Incorrect face angle on the Y axis.

PassiveFaceLiveness

ANGULATION_Z_FAILURE

Incorrect face angle on the Z axis.

PassiveFaceLiveness

MULTIPLE_FACES_FAILURE

Multiple faces detected.

PassiveFaceLiveness, FaceAuthenticator

QUALITY_FAILURE

Quality of the document capture is too low.

DocumentDetector

LIVENESS_FAILURE

Error in the proof of life. It is a probable fraud attempt

PassiveFaceLiveness, FaceAuthenticator

TYPIFICATION_FAILURE

Document type or document side was not expected.

DocumentDetector

PASSPORT_COUNTRY_CODE_FAILURE

Passaport country code (or issuing code) is not allowed.

DocumentDetector

Using layout in Builder

After creating the desired files, create an object of type DocumentDetector. This object is for you to configure all your business rules for the SDK, including the interface customization attributes:

DocumentDetector mDocumentDetector = new DocumentDetector.Builder(String mobileToken)
    // see table below
    .build();

Builder method

Parameter
Required
Compatibility

.setLayout(@LayoutRes Integer layoutId)

Replaces the SDK's default layout. Create a file in your project's layout folder, copy the standard layout template corresponding to the SDK you are integrating and make the desired changes.

No. Here is the default for each SDK.

Latest versions

.setStyle(@StyleRes int styleResourceId)

Replaces the SDK's default style. In your project's styles.xml file, copy the default template and edit it.

No. Here is the default for each SDK.

Latest versions

.setMask(@DrawableRes Integer greenMask, @DrawableRes Integer whiteMask, @DrawableRes Integer redMask)

Replaces the masks for capturing a document or face: SUCCESS, NORMAL, and FAIL, in that order. If you use this option, use masks with the same detection area of the document and face, as this region is very important for the algorithm to capture.

No. Here is the default for each SDK.

DocumentDetector v7.x or below

.setMask(MaskType type)

Defines which group of masks predefined in the product will be used by the SDK:

  • MaskType.DEFAULT, with the dotted pattern in the document format or face;

  • MaskType.DETAILED, which displays an illustration of the requested document - CNH or RG - along with the dotted mask (only in DocumentDetector);

  • MaskType.NONE, which removes the mask entirely.

No. The default masks are used.

DocumentDetector v7.x or below

Using the methods

DocumentDetector mDocumentDetector = new DocumentDetector.Builder(String mobileToken)
    .setLayout(R.layout.customLayout)
    .setStyle(R.style.customStyle)
    .setMask(MaskType.DETAILED)
    .build();

Different uses .setMask() method

DocumentDetector mDocumentDetector = new DocumentDetector.Builder(String mobileToken)
    // Using Customized Masks
    .setMask(R.drawable.customGreenMask, R.drawable.customWhiteMask, R.drawable.customRedMask)
    // Using masks already offered by the SDK
    .setMask(MaskType.DETAILED)
    .build();

Custom style creation

To create a new style, we recommend that you use the same template that we use, this way it will be easier to perform customizations.

Customization of masks

To customize the masks, first create a drawable resource in your project. You can customize whiteMask, greenMask, and redMask in any way you like. We have provided generic document and face masks that you can use for reference. See the setMask method definition and examples in DocumentDetector.Builder. And see also examples of custom mask integration.

Default templates

Activity (setLayout)

<?xml version="1.0" encoding="utf-8"?>
<layout>

    <data>
        <import type="android.view.View"/>

        <variable
            name="viewModel"
            type="com.combateafraude.documentdetector.controller.viewmodel.SDKViewModel" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:keepScreenOn="true">

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guidelineStart"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_percent="0.1" />

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guidelineEnd"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_percent="0.9" />

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guidelineTop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            app:layout_constraintGuide_percent="0.05" />

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guidelineStatus"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            app:layout_constraintGuide_percent="0.78" />

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guidelineBottom"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            app:layout_constraintGuide_percent="0.97" />

        <androidx.camera.view.PreviewView
            android:id="@id/cameraImageView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="@{viewModel.cameraVisibility ? View.VISIBLE : View.GONE}"
            />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@{viewModel.previewBitMap}"
            android:visibility="@{viewModel.previewVisibility ? View.VISIBLE : View.GONE}">
        </ImageView>

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:adjustViewBounds="true"
            android:contentDescription="@string/photo_mask_caf"
            android:scaleType="fitXY"
            android:visibility="@{viewModel.maskVisibility ? View.VISIBLE : View.GONE}"
            android:src="@{context.getDrawable(viewModel.maskLayout)}" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:adjustViewBounds="true"
            android:contentDescription="@string/close_caf"
            android:onClick="@{() -> viewModel.close()}"
            android:src="@drawable/ic_back_caf"
            app:layout_constraintStart_toStartOf="@id/guidelineStart"
            app:layout_constraintTop_toTopOf="@id/guidelineTop" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:adjustViewBounds="true"
            android:contentDescription="@string/switch_caf"
            android:onClick="@{() -> viewModel.switchCamera()}"
            android:visibility="@{viewModel.switchCameraButtonVisibility ? View.VISIBLE : View.GONE}"
            android:src="@drawable/ic_camera_switch"
            app:layout_constraintEnd_toEndOf="@id/guidelineEnd"
            app:layout_constraintTop_toTopOf="@id/guidelineTop" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="16dp"
            android:visibility="@{viewModel.manualCaptureButtonVisibility ? View.VISIBLE : View.GONE}"
            android:onClick="@{() -> viewModel.takePhoto()}"
            app:layout_constraintStart_toStartOf="@id/guidelineStart"
            app:layout_constraintEnd_toEndOf="@id/guidelineEnd"
            app:layout_constraintTop_toTopOf="@id/guidelineStatus"
            android:contentDescription="@string/take_picture"
            app:backgroundTint="?attr/colorPrimary"
            app:tint="#FFF"
            app:srcCompat="@drawable/ic_camera_caf"/>

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:visibility="@{viewModel.statusVisibility ? View.VISIBLE : View.GONE}"
            app:layout_constraintStart_toStartOf="@id/guidelineStart"
            app:layout_constraintEnd_toEndOf="@id/guidelineEnd"
            app:layout_constraintTop_toTopOf="@id/guidelineStatus">

            <TextView
                android:id="@+id/statusMessage"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:background="@drawable/bg_radius_caf"
                android:gravity="center_horizontal"
                android:lineSpacingExtra="5sp"
                android:padding="8dp"
                android:layout_marginTop="10dp"
                android:textAlignment="center"
                android:textColor="#606060"
                android:textSize="15sp"
                android:textStyle="bold"
                android:text="@{viewModel.statusMessage}"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/triangle_caf"
                android:rotation="270"
                android:adjustViewBounds="true"
                android:contentDescription="@string/nothing_caf"
                app:layout_constraintEnd_toEndOf="@id/statusMessage"
                app:layout_constraintStart_toStartOf="@id/statusMessage"
                app:layout_constraintTop_toTopOf="@id/statusMessage"
                app:layout_constraintBottom_toTopOf="@id/statusMessage" />

        </androidx.constraintlayout.widget.ConstraintLayout>

        <TextView
            android:id="@+id/tvCurrentStepName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toTopOf="@id/tvPreviousStepName"
            app:layout_constraintStart_toStartOf="@id/guidelineStart"
            app:layout_constraintEnd_toEndOf="@id/guidelineEnd"
            android:layout_marginBottom="5dp"
            android:textSize="18sp"
            android:textColor="#ffffff"
            android:letterSpacing="0.06"
            android:text="@{viewModel.currentStepName}"
            android:gravity="center_horizontal" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:adjustViewBounds="true"
            android:visibility="@{viewModel.currentStepDone ? View.VISIBLE : View.GONE}"
            android:contentDescription="@string/check_caf"
            app:layout_constraintTop_toTopOf="@id/tvCurrentStepName"
            app:layout_constraintBottom_toBottomOf="@id/tvCurrentStepName"
            app:layout_constraintEnd_toStartOf="@id/tvCurrentStepName"
            android:layout_marginEnd="8dp"
            android:src="@drawable/ic_check_caf" />

        <TextView
            android:id="@+id/tvPreviousStepName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="@id/guidelineStart"
            app:layout_constraintEnd_toEndOf="@id/guidelineEnd"
            app:layout_constraintBottom_toBottomOf="@id/guidelineBottom"
            android:textSize="16sp"
            android:textColor="#66FFFFFF"
            android:letterSpacing="0.06"
            android:text="@{viewModel.previousStepName}"
            android:gravity="center_horizontal" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:adjustViewBounds="true"
            android:visibility="@{viewModel.previousStepDone ? View.VISIBLE : View.GONE}"
            android:contentDescription="@string/check_caf"
            app:layout_constraintTop_toTopOf="@id/tvPreviousStepName"
            app:layout_constraintBottom_toBottomOf="@id/tvPreviousStepName"
            app:layout_constraintEnd_toStartOf="@id/tvPreviousStepName"
            android:layout_marginEnd="8dp"
            android:alpha="0.4"
            android:src="@drawable/ic_check_caf" />

        <ProgressBar
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:indeterminate="true"
            android:indeterminateTint="?attr/colorPrimary"
            android:indeterminateTintMode="src_atop"
            android:visibility="@{viewModel.loadingStatus ? View.VISIBLE : View.GONE}"
            app:layout_constraintVertical_bias="0.45"
            app:layout_constraintBottom_toBottomOf="@id/guidelineBottom"
            app:layout_constraintTop_toTopOf="@id/guidelineTop"
            app:layout_constraintStart_toStartOf="@id/guidelineStart"
            app:layout_constraintEnd_toEndOf="@id/guidelineEnd" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Styles (setStyle)

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="windowProperties" parent="Theme.MaterialComponents.Light">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
        <item name="android:windowTranslucentStatus">true</item>
        <item name="android:windowActivityTransitions">true</item>
        <item name="android:colorControlActivated">#606060</item>
    </style>

    <style name="defaultStyle" parent="windowProperties">
        <item name="colorPrimary">#4CD964</item>
    </style>

    <style name="defaultButtonStyle" parent="Widget.MaterialComponents.Button">
        <item name="android:layout_width">0dp</item>
        <item name="android:layout_height">60dp</item>
        <item name="android:gravity">center</item>
        <item name="android:textColor">#FFFFFF</item>
        <item name="android:textSize">18sp</item>
        <item name="android:fontFamily">sans-serif</item>
        <item name="android:textAllCaps">false</item>
    </style>

    <style name="transparentButton" parent="Widget.MaterialComponents.Button">
        <item name="android:layout_width">0dp</item>
        <item name="android:layout_height">60dp</item>
        <item name="android:gravity">center</item>
        <item name="android:textColor">#323232</item>
        <item name="android:textSize">18sp</item>
        <item name="android:textStyle">normal</item>
        <item name="android:fontFamily">@font/roboto</item>
        <item name="android:textAllCaps">false</item>
        <item name="android:background">#00FFFFFF</item>
    </style>

    <style name="textPreview" parent="windowProperties">
        <item name="android:textColor">#323232</item>
    </style>

</resources>

Mask (setMask)

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="375dp"
    android:height="667dp"
    android:viewportWidth="375"
    android:viewportHeight="667">
  <path
      android:pathData="M375,0V667H0V0H375ZM341,85H35V516H341V85Z"
      android:strokeAlpha="0.35"
      android:fillColor="#000000"
      android:fillType="evenOdd"
      android:fillAlpha="0.35"/>
  <!-- 
      The color of the mask can be changed by the android:fillColor attribute of the element below.
      The default colors are: #22CB7B (green), #E74C3C (red), #ffffff (white)
  -->
  <path
      android:pathData="--image-byte-array-"
      android:fillColor="#22CB7B" 
      android:fillType="evenOdd"/>
</vector>

Last updated

Logo

2023 © Caf. - All rights reserved