Integração Fortface SDK no seu app Android

Como funciona?

Nossa SDK tem como objetivo efetuar a Captura da foto que será enviada para nossa API Fortface. Ela é independente do fluxo do aplicativo do App o qual irá se integrar. Ela possui uma Activity própria e isolada, o qual pode ser iniciada a qualquer momento por uma Engine. Logo após seu processamento, a ferramenta é encerrada automaticamente.

Nossa SDK não comunica diretamente com a API Fortface, veja abaixo o fluxo de comunicação:

Fluxo de comunicação

Abaixo, seguem as instruções de integração da nossa SDK, fluxo de processos para acessar corretamente nosso serviço e passo-a-passo de como construir uma Engine.

Instalação

Requisito

1. Configuração da Plataforma

Android
Versão mínima: Android 8.0 (Oreo) - SDK 26

Atualmente é possivel utilizar o SDK fortface em aplicações com suporte a versão mínima Android 6 - SDK 24, porém ao fazer isto, o desenvolvedor abre mão de features de segurança importantes da ferramenta, assumindo assim riscos de ataques durante as capturas.

Kotlin

Versão mínima: 1.7.20

Instalação

1. Adicionar o acesso ao repositório Maven ao seu projeto

Primeiramente você precisará configurar o acesso ao repositório Maven em seu projeto. O artefato é servido via CDN, com autenticação por cabeçalho HTTP. Inclua o seguinte bloco em repositories (por exemplo no build.gradle / build.gradle.kts do módulo app, ou em settings.gradle / settings.gradle.kts se o projeto usar dependencyResolutionManagement).

app/build.gradle
repositories {
maven {
url "https://cdn-fortface-sdk.fortface.com.br"
credentials(HttpHeaderCredentials) {
name = "X-Sdk"
value = "ACCOUNT_NAME"
}
authentication {
header(HttpHeaderAuthentication)
}
}
}

Substitua ACCOUNT_NAME pelo identificador da conta fornecido pela nossa equipe.

Dependendo da configuração do seu projeto, pode ser necessário declarar o mesmo repositório em settings.gradle / settings.gradle.kts (por exemplo dentro de dependencyResolutionManagement { repositories { ... } }).

2. Configurar o gradle

Com o repositório configurado, é necessário ativar o viewBinding e adicionar a dependência à nossa SDK. Abaixo você encontra quais alterações devem ser adicionadas no gradle do seu app:

app/build.gradle
android {
...
buildFeatures {
viewBinding true
}
}

dependencies {
implementation (group: 'br.com.fortface', name: 'fortface', version:'1.20.0', ext: 'aar', classifier: 'release'){
transitive = true
}
}

Não se esqueça de sincronizar o seu projeto com os arquivos gradles após alteração.

3. Incluir regras do Proguard

Nossa SDK já possui obfuscação e minificação para garantia de segurança, no entanto, algumas classes do SDK precisam ser mantidas sem obfuscação, para garantir que o seu app funcione corretamente, adicione as regras abaixo no seu arquivo proguard-rules.pro:

-keep class br.com.fortface.sdk.** { *; }

Permissão

1. Adequar o AndroidManifest.xml

Nossa SDK utiliza a câmera, portanto é necessário configurar a permissão de acesso no manifest de seu app. Para isso, adicione as seguintes linhas no arquivo:

app/src/main/AndroidManifest.xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

<application
...

Também contamos com a permissão de localização para coleta de coordenadas geográficas do dispositivo para reforço nos mecanismos antifraude. Caso deseje coletar as coordenadas geográficas do dispositivo, adicione também a permissão de localização no manifest de seu app.

app/src/main/AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<application
...

2. Garantir o acesso à camera

Para que o seu aplicativo solicite permissão de acesso à câmera, adicione o seguinte código em sua activity:

Snippet Kotlin


class YourActivity: AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
// ...
if (!allPermissionsGranted()) {
requestPermission()
}
}

private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(this.baseContext, it) == PackageManager.PERMISSION_GRANTED
}

private fun requestPermission() {
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
}

companion object {
const val REQUEST_CODE_PERMISSIONS = 10
val REQUIRED_PERMISSIONS = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.ACCESS_FINE_LOCATION, // Opcional
Manifest.permission.ACCESS_COARSE_LOCATION // Opcional
)
}

// ...
}

Após essa configuração, seu aplicativo está pronto para acessar a nossa SDK.

Pré-inicialização

Antes de iniciar o processo de captura, é necessário estabelecer a integridade do dispositivo para garantir uma comunicação segura com a API Fortface. Sendo assim, é necessário:

  1. Importar nossa SDK no seu app;
  2. Instanciar FortfaceSdk.

Estabelecer a integridade do dispositivo

Inicie a SDK e obtenha o deviceRequestInfo.

import br.com.fortface.sdk.FortfaceSdk

val deviceRequestInfo = FortfaceSdk.start(appContext, false)
println(deviceRequestInfo)
// Q4ezAXfMdAdW1s0xdwCcg0Jzj9kAjuNwzNrWMip4ADAylFQAGCgBOHT7HJYkPkXUPmLZ3XtJYOgTi/WKh+o2Vstk2MGy4qRz4fcBpCu+raS1ghHa4DDmqUjbSMPd3Fdo2zrcw0sb81FDf/Tos2A5UaIqMhzurkm0aYgC/izKhu3H+RQXJBHjFVKUIq8WPVbC/ITKon2T703C6fgdQtcpknaxAvIN2/eDRxBrsSuS3fbIi1TOoGAIi/Ezwj0utZLYUwhnKImaabvvXFdsvVxp6rHLiq1uHqZbm4tfRWJw4mkk47JHVEfUIJMFjS1ORpqoN1dmZzPQHAo0CtPfYGa9OB8NGnmgRRbWHXWrXPoptaRY31yoaT+HGwBwB/pfL8gUxoVppOYBUfIsvSQoaIQ+6g==

No FortfaceSdk, será usado o método start que possui 2 parâmetros e retorna 1 string:

  • Parâmetro context: representa o contexto do seu aplicativo.
  • Parâmetro isDebugMode: representa um booleano que habilita ou desabilita o modo de depuração. Valor padrão é false
    • Use true para habilitar o modo de depuração;
    • Use false em produção, assim é verificada à integridade do dispositivo e, caso não seja adequado para transações, será lançada uma excepetion do tipo FortfaceException com a mensagem There is something wrong!.
  • Retorno armazenado em deviceRequestInfo: string usado para validar a integridade do dispositivo conectado à nossa SDK. Uma nova deviceRequestInfo é gerada cada vez que o método é chamado (no código acima está um exemplo).

Com deviceRequestInfo em mãos, agora é possível fazer uma chamada de handshake entre seu back-end e API Fortface.

handshake é uma medida de segurança importante para estabelecer uma comunicação confiável entre seu back-end e API Fortface, possibilitando verificar a autenticidade do dispositivo, compartilhar informações sensíveis (chaves de criptografia e sessões) e estabelecer um canal seguro para troca de dados.

Com o sucesso do handshake, haverá a garantia de que o dispositivo está íntegro e seu app tem as informações necessárias para prosseguir.

Captura

Sabendo que a integridade do dispositivo foi estabelecida, vamos entender como iniciar a sessão e utilizar a interface IFortfaceEngine para realizar a captura.

Estabelecer a sessão do dispositivo

Implemente uma classe que extenda IFortfaceEngine.

Captura Biometrica

import br.com.fortface.sdk.FortfaceManagerSession
import br.com.fortface.sdk.IFortfaceEngine
import br.com.fortface.sdk.utils.SessionDetails

class YourEngine(
appContext: Context,
sessionKey: String,
sessionId: String,
private val yourViewModel: ViewModel
) : IFortfaceEngine {

init {
val sessionDetails = SessionDetails(
getGeolocation = true // 👈 Opcional: define se o SDK deve solicitar permissão de localização e coletar coordenadas para reforço antifraude.
)

FortfaceManagerSession.startSessionFaceEngine(
appContext,
this,
sessionId,
sessionKey,
sessionDetails // pode ser omitido se não precisar dos controles adicionais de usabilidade à sessão
)
}
}

Nesta classe, inicialize FortfaceManagerSession e seu método startSessionFaceEngine que possui 5 parâmetros de captura biométrica:

  • context: representa o contexto do seu aplicativo.
  • engine: representa a sua classe Engine que implementa a interfaceIFortfaceEngine da nossa SDK. Isso será explicado logo abaixo, na sessão Criando um Engine.
  • sessionId: id da sessão, gerado na API Fortface.
  • sessionKey: chave de criptografia gerada na API Fortface.
  • sessionDetails: objeto opcional que permite adicionar outros controles de usabilidade à sessão.
    • getGeolocation: define se o SDK deve solicitar permissão de localização e coletar coordenadas para reforço antifraude.

Atenção Apartir da versão 1.14 da SDK o metodo startSessionEnginefoi substituido pelo startSessionFaceEngine devendo ser alterado para possibilitar a utilização da sdk.

Ao inicializar a FortfaceManagerSession usando os parâmetros fornecidos, você configura corretamente a sessão de comunicação segura e garante que os dados transmitidos entre o aplicativo e o servidor sejam devidamente criptografados e protegidos.

Dados mockados para exemplo:

AppContext: Contexto do seu aplicativo

Engine: Referência da sua Engine que implementa o IFortfaceEngine

SessionId: nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2k

SessionKey: -----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2k+o81Oiti4TDezZYD8Q\nzj2RjO5f/YQHGmC1v7CWC4AJpD9+FOXBBO1pImaSg1gb8kOobRFGWbVdiHm35+PF\nTJceH31r+jKnvfc1TshsQVGtYLm7x5E/GFcjpGx9LdSxiXC6cHdCJf3o02/lytBa\nu8NCoH9FpPjyKrdGdSRb2haviAuEV4N1UBd5uZIsacVqZj52i1z+I94gLVKBZ/UV\npaxjLPWWngmaxbyKE0sEbt9JGleiCb2P1kZA9sU4L4Po7WBNk0+QOsvW9QjvS1+6\ntcZwwIDll2LyUceiFgxF6GkuFcHlBZDDtzymDDC2IYrf9/FNBOPeV1JHXON8T6DY\nAwIDAQAB\n-----END PUBLIC KEY-----\n

Manipulação da sessionKey

A string que representa a sessionKey contém caracteres especiais (como a quebra de linha \n) que devem ser preservados exatamente como recebidos da API Fortface. Tenha cuidado com manipulações acidentais que possam ocorrer em:

  • Inputs de formulários
  • Logs e sistemas de monitoramento
  • Processamento de strings no seu backend
  • Transferência entre serviços
  • Armazenamento em bancos de dados

Qualquer alteração nesses caracteres pode fazer com que a validação da chave falhe. Certifique-se de que a chave seja passada para o SDK exatamente como recebida da API Fortface.

Facilitação de desenvolvimento

Para desenvolvimento local do frontend exclusivamente, você pode reutilizar uma mesma sessionKey válida como mock quantas vezes forem necessárias. A restrição de uso único só é aplicada no contexto do desenvolvimento do backend, quando a API Fortface valida a chave.

Entendendo o trecho de código:

  • O método construtor da classe vai fazer a chamada para FortfaceManagerSession.start.
  • A sessão é criada e a câmera é renderizada na tela para captura da face da pessoa usuária.

Criando um Engine

Através da Engine é possível acessar as informações processadas pela nossa SDK. Para criar uma Engine, a classe deve implementar a interface IFortfaceEngine, logo, é necessário a implementação do método finishedSessionEngine. É nesse método que serão recebidos os dados processados pela SDK, o qual é um pacote com uma ação e os dados da imagem capturada.

Logo abaixo, é possível observar a implementação completa de uma captura, por exemplo, a classe YourEngine que implementa a interface IFortfaceEngine, contendo a inicialização da sessão startSessionEngine e a manipulação do callback SessionEngineResult.

Snippet Kotlin

import br.com.fortface.sdk.Action
import br.com.fortface.sdk.SessionEngineResult
import br.com.fortface.sdk.FortfaceManagerSession
import br.com.fortface.sdk.IFortfaceEngine

class YourEngine(appContext: Context, sessionKey: String, sessionId: String, private val yourViewModel: ViewModel) : IFortfaceEngine {

init {
FortfaceManagerSession.startSessionEngine(appContext, this, sessionId, sessionKey)
}

override fun finishedSessionEngine(sessionEngineResult: SessionEngineResult) {
when (sessionEngineResult.action) {
Action.Capture -> yourViewModel.captured(sessionEngineResult) // Aqui encerra o fluxo e esses são os dados para enviar
Action.Cancel -> yourViewModel.canceled() // Aqui a pessoa usuária cancelou a captura e você deve seguir com o seu fluxo de cancelamento
Action.Timeout -> yourViewModel.timeout() // Aqui a captura foi cancelada pois o tempo esgotou. Você deve seguir com fluxo de tempo limite esgotado
Action.Error -> yourViewModel.erro() // Aqui a captura foi cancelada pois ocorreu um erro. Você deve seguir com tratamento mais adequado ao errorCode retornado
else -> {}
}
}
}

Observe que o método finishedSessionEngine é um callback que tem um dado do tipo SessionEngineResult, o callback possui duas informações:

  • action: representa um enumerador contendo:

    • Capture: callback da captura realizada pela pessoa usuária
    • Cancel: callback da captura cancelada pela pessoa usuária
    • Timeout: callback da captura com o tempo limite atingido pela pessoa usuária
      • Essa action representa o tempo que a SDK fica aberta, realizando captura ou não. Quando esgotar, a SDK é encerrada e câmera fechada;
      • O tempo padrão é de 30 segundos;
    • Error: callback da captura com erro durante o processo
      • Essa action representa um erro gerado durante a captura
  • getData: representa o dado da captura criptografado e está no formato JSONObject. O JSON é representado pela seguinte estrutura:

{
key: String,
data: String,
imgData: String,
imgPreview: String
}
  • sessionDetails: é um tipo de dado que possui uma informação errorCode
    • errorCode: representa um código que pode ocorrer durante o processo de captura e/ou upload, as opções de valores são:
      • DocumentNotFound: código retornado quando o arquivo selecionado(PDF/Imagem) não consegue de ser encontrado.
      • FileSizeTooBig: código retornado quando o arquivo selecionado(PDF/Imagem) é maior que o limite permitido de 4MB.
      • FileCorrupted: código retornado quando o arquivo selecionado(PDF/Imagem) esta corrompido.
      • InvalidSessionKey: código retornado quando a sessionKey informada para a sdk é inválida.
      • CameraAccessPermissionError: código retornado quando o o usuário não concede acesso a câmera para a aplicação.
      • ImageClosed: código retornado quando a captura tenta processar um frame cuja imagem está indisponível.
      • OutOfMemory: código retornado quando o dispositivo não possui memória suficiente para que o SDK continue o processamento.

Portanto, como mostra o exemplo acima, é possível orientar cada ação a uma implementação própria do seu aplicativo. Além disso, a partir do momento em que é obtida a informação do método getData, seu aplicativo pode transportá-la da maneira mais conveniente até o seu backend e depois para a API Fortface.

Importante ressaltar que na API da nossa SDK haverá os fluxos de enroll e identify, com isso, fique livre para arquitetar Engines que correspondam a cada um desses fluxos, ou arquitetar um Engine genérico, ou qualquer outra solução desejada.

Utilizando nossa SDK

Na exemplifacação de utilização da SDK, iremos utilizar arquitetura MVVM. Com isso, tem-se a view model:

class YourViewModel: ViewModel() {
fun caputed(sessionEngineResult: SessionEngineResult) {
//Implementação do o que fazer com os dados capturados
}

fun canceled() {
//Implementação cancelamento da SDK
}

fun timeout() {
//Implementação timeout da SDK
}

fun error() {
//Implementação error da SDK
}
}

A SDK é livre para implementação de qualquer estrutura de projeto. Nessa documentação utilizamos o padrão de arquitetura utilizado no mercado.

Abaixo segue um código de exemplo do fluxo que deve ser implementado no seu app para acessar nossa SDK:

import br.com.fortface.sdk.FortfaceSdk
import androidx.activity.viewModels
//...

class YourActivity : AppCompatActivity() {
val yourViewModel: YourViewModel by viewModel()
//...

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

if (!allPermissionsGranted()) {
requestPermission()
}

val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
startEngine()
}
}

fun startEngine() {
val deviceRequestInfo = FortfaceSdk.start(this, false)
// Aqui você envia para o handshake da API Fortface através do seu backend
// ...
// Com a resposta você vai ter o sessionKey e sessionId
val sessionKey = ''; // Resposta da API Fortface
val sessionId = ''; // Resposta da API Fortface
YourEngine(this, sessionKey, sessionId, yourViewModel)
}
}

Versionamento

Para saber qual versão da nossa SDK está sendo utilizada, basta chamar a função FortfaceSDK.getVersion() que retornará uma String com seu valor. Exemplo:

String sdkVersion = FortfaceSDK.getVersion()
System.out.println(sdkVersion);
// 1.17.0

Modo Tela cheia

Por padrão, nossa SDK é exibida em modo tela cheia na orientação vertical (portrait). Essa configuração é válida para todos os dispositivos suportados. No entanto, em tablets, é possível configurar a orientação para horizontal (landscape) ou automática, permitindo que a SDK siga a orientação do dispositivo em uso.

Caso a SDK seja configurada para uma orientação diferente de portrait e executada em um smartphone, ela será exibida automaticamente em portrait, ignorando a personalização realizada. Os possíveis valores de configuração são:

customizer.screenOrientation = ScreenOrientation.Automatic
customizer.screenOrientation = ScreenOrientation.Landscape
customizer.screenOrientation = ScreenOrientation.Portrait
customizer.screenMode = ScreenMode.FullScreen

Modo Modal

Oferecemos a opção de personalizar a exibição do fluxo de captura da SDK em uma modal que sobrepõe a tela do aplicativo cliente. Essa funcionalidade está disponível apenas para tablets. Quando configurada e executada em smartphones, a SDK adotará o comportamento de exibição em tela cheia na orientação portrait. No modo modal, é possível customizar a cor de fundo (overlay) e a opacidade da mesma.

customizer.screenMode = ScreenMode.Modal
customizer.modalBackground.setOverlayColor("#5FC213")
customizer.modalBackground.overlayOpacity = 0.4f

Callback de cancelamento e timeout

Quando uma tentativa de captura do rosto do usuário não é completada com sucesso, por exemplo quando o usuário cancela a captura ou quando a captura não acontece durante o tempo determinado, é importante que o acontecimento deste evento seja enviado ao backend para termos corretas métricas de sucesso ou falhas das capturas.

Para permitir que tenhamos os dados da captura cancelada ou que sofreu timeout, nossa SDK possui callbacks na ação de cancelar captura e na ação de timeout de captura.

O SessionEngineResult recebido na função finishedSessionEngine é um objeto que possui 2 propriedades: data e action (ação realizada na SDK). Anteriormente, o campo data era somente preenchido ao finalizar a captura de face.

Como visto anteriormente no tópico Captura, o data contém dados criptografados e pode ser convertidos em JSON. Entretanto, diferentemente da captura, neste cenário após a conversão para JSON, teremos somente os seguintes campos:

{
key: String,
data: String
}

Lembrete: As chaves key e data são geradas pela SDK, enviadas ao seu back-end e então à API Fortface.

Exemplo de implementação

  1. Identificamos na SDK que houve um cancelamento de captura.

  2. Criamos o objeto criptografado.

  3. Terminamos a SDK e enviamos o payload gerado e a ação realizada.

  4. No app de implementação, dentro da classe que extende o IFortfaceEngine, é detectado que houve uma ação.

import br.com.fortface.sdk.Action
import br.com.fortface.sdk.SessionEngineResult
import br.com.fortface.sdk.FortfaceManagerSession
import br.com.fortface.sdk.IFortfaceEngine

class YourEngine(appContext: Context, sessionKey: String, sessionId: String, private val yourViewModel: ViewModel) : IFortfaceEngine {

...

override fun finishedSessionEngine(sessionEngineResult: SessionEngineResult) {
when (sessionEngineResult.action) {
Action.Capture -> yourViewModel.captured(sessionEngineResult)

// ao detectar a ação de cancelar, é chamada a função canceled
// que recebe a data gerada na SDK.

Action.Cancel -> yourViewModel.canceled(sessionEngineResult)

Action.Timeout -> yourViewModel.timeout(sessionEngineResult)

else -> {}
}
}
}
  1. Dentro do seu ViewModel, é chamada a função canceled()
class YourViewModel: ViewModel() {
...

fun canceled() {
// acionamos que recebemos a ação cancelar
cancelAction.postValue(true)
// criação do payload de cancelamento no formato Metric
createPayload(sessionEngineResult)
}

...

private fun createPayload(sessionEngineResult: SessionEngineResult) {
val encryptedPayload = sessionEngineResult.getData().toString()
if (encryptedPayload != "null") {
// parse do JSON para o objeto SDKData (exemplificado abaixo)
// nesse caso o campo imgData irá nulo
val sdkData = Gson().fromJson(encryptedPayload, SDKData::class.java)
// acionamos que recebemos uma métrica e enviamos o Metrics
sessionEngineResultValue.postValue(Metrics(sdkData))
}
}
}
  1. Objeto Metrics
@Keep
data class Metrics(
@SerializedName("sdkData")
var sdkData: SDKData
)
  1. Objeto SDKData
@Keep
data class SDKData(
@SerializedName("key")
val key: String,
@SerializedName("data")
val data: String,
@SerializedName("imgData")
val imgData: String
)
  1. Após acionar o recebimento da métrica, o Metrics é enviado para a API
class YourFragment : Fragment() {
...
// observer vê que tem nova métrica a ser mandada
// envia dados a API de métricas

viewModel.sessionEngineResultValue.observe(viewLifecycleOwner) {
context?.let { context -> viewModel.sendMetricsToServer(context, it) }
}

Resumo

  1. Verificar a permissão de acesso a câmera
  2. Capturar o deviceRequestInfo
  3. Fazer o Handshake API Fortface (via seu backend)
  4. Iniciar um Engine
  5. Receber os dados no callback da Engine
  6. Enviar para API Fortface (via seu backend)
  7. Receber resposta da API Fortface e seguir com sua regra de negócio