FIDO Client Adapter

HYPR SDK for Android

🚧

Do This First

This article assumes you have already completed the HYPR SDK for Android Quick Start before continuing.

This article describes the steps for integrating the FIDO Client Adapter into an app to facilitate decentralized registration, authentication, and deregistration.

This implementation involves using HYPR SDK for Android to authenticate into an Android application. It is a wrapper around the FIDO Client that replaces the User Agent. It is used for implementations where the customer only wants HYPR to generate the raw FIDO payload. They will then take that raw FIDO payload and manage the server communication themselves. In this implementation there is no direct communication with a HYPR RP Server / HYPR FIDO Server.

Requirements

  • The HYPR .aar files are in your Android project's app/libs directory
  • Gradle version: 4.4
  • Android Plugin version 3.1.3
  • minSdkVersion 23
  • targetSdkVersion 28

πŸ“˜

SDK Build Tools

Since we're using an Android plugin for Gradle 3.0.0+, the project automatically uses a default version of the build tools specified by the plugin.

Update /app/<module_name>/build.gradle

πŸ“˜

Include the HYPR AARs in your build.gradle

Once you have the HYPR .aar files in your app/libs directory, make sure to add them in your app/<module_name>/build.gradle file. An example of how to include them is at the bottom of the file below.

Following is an example of the FCA app/build.gradle:

apply plugin: 'com.android.application'
apply plugin: 'org.sonarqube'
apply plugin: 'org.jetbrains.kotlin.android'


ext {
    hyprVersion = "7.3.0"
    hyprCode = 70300
    cryptoVersion = "3.7.0"
    adpVersion = "3.6.0"
    sensorySmmaVersion = "4.1.2"
    sensoryUtilsVersion = "4.1.2.4"
    sensoryModelVersion = "2.1.0"
    appId = "com.hypr.hyprfidoclientadaptersample"
    keyLocation = "$rootProject.projectDir/app/fidoclientadapter.keystore"
    keyStoreAlias = "HyprFidoClientAdapter"
    keyStorePass = "wod82nfib9wq"
}

android {
    compileSdkVersion 31
    defaultConfig {
        applicationId appId
        minSdkVersion 23
        targetSdkVersion 31
        versionCode hyprCode
        versionName hyprVersion
        version versionName
        vectorDrawables.useSupportLibrary = true
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        project.archivesBaseName = "HyprFidoClientTest"

        ndk {
            abiFilters "armeabi", "armeabi-v7a", "arm64-v8a"
        }

    }

    signingConfigs {
        debug {
            storeFile file(keyLocation)
            storePassword keyStorePass
            keyAlias keyStoreAlias
            keyPassword keyStorePass
        }
        release {
            storeFile file(keyLocation)
            storePassword keyStorePass
            keyAlias keyStoreAlias
            keyPassword keyStorePass
        }
    }

    buildTypes {
        debug {
            signingConfig signingConfigs.debug
            minifyEnabled false
            multiDexEnabled true
            debuggable true
        }
        release {
                minifyEnabled true
                multiDexEnabled true
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                multiDexKeepFile file('multidex-config.txt')
                multiDexKeepProguard file('multidex-config.pro')
                signingConfig signingConfigs.release
        }
    }

    compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }
    
    dexOptions {
        javaMaxHeapSize "10g"
        jumboMode = true
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    // Android
    implementation 'androidx.multidex:multidex:2.0.1'
    implementation 'androidx.appcompat:appcompat:1.3.1' // do not change yet - https://stackoverflow.com/questions/69033022/message-error-resource-androidattr-lstar-not-found
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    implementation 'androidx.core:core-ktx:1.7.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.4.0'
    implementation "androidx.security:security-crypto:1.0.0"

    // GJON / POJO
    implementation 'com.google.code.gson:gson:2.8.6'
    implementation 'org.apache.commons:commons-lang3:3.5'

    // RxAndroid
    implementation group: 'io.reactivex.rxjava2', name: 'rxandroid', version: '2.1.1'
    implementation group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.2.21'
    implementation 'javax.annotation:jsr250-api:1.0'

    // Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'

    implementation(name: 'HyprCommonAdp', version: "${hyprVersion}", ext: 'aar')
    implementation(name: 'HyprBiometricPromptAdp', version: "${hyprVersion}", ext: 'aar')
    implementation(name: 'HyprPinAdp', version: "${hyprVersion}", ext: 'aar')
    implementation(name: 'HyprFaceVoiceAdp', version: "${hyprVersion}", ext: 'aar')
    implementation(name: 'HyprSilentAdp', version: "${hyprVersion}", ext: 'aar')
    implementation(name: 'HyprPresenceAdp', version: "${hyprVersion}", ext: 'aar')

    // Fingerprint
    implementation 'androidx.biometric:biometric:1.1.0'

    // Sensory SMMA
    implementation(name: 'vvutils', version: "${sensoryUtilsVersion}", ext: 'aar')
    implementation(name: 'datautils', version: "${sensoryUtilsVersion}", ext: 'aar')
    implementation(name: 'smma', version: "${sensorySmmaVersion}", classifier: 'android', ext: 'aar')
    implementation(name: 'model', version: "${sensoryModelVersion}", classifier: 'combiner', ext: 'aar')
    implementation(name: 'model', version: "${sensoryModelVersion}", classifier: 'face-pnn', ext: 'aar')
    implementation(name: 'model', version: "${sensoryModelVersion}", classifier: 'voice-tssv-udp_enUS', ext: 'aar')

    // These dependencies are required by Sensory SDK
    implementation 'com.parse.bolts:bolts-android:1.1.4'
    implementation 'org.slf4j:slf4j-api:1.7.25'
    implementation 'commons-io:commons-io:2.5'
    implementation 'com.fasterxml.uuid:java-uuid-generator:3.1.4'

    // These are also required by the SDK, but we are already using a newer version in our demo app
    //implementation 'com.android.support:design:27.0.2'
    //implementation 'org.apache.commons:commons-lang3:3.3.2'

    // These are used in the Sensory SDK, but are NOT required. Since it's commented out, it won't be included
    //implementation 'com.amazonaws:aws-android-sdk-core:2.2.10'
    //implementation 'com.amazonaws:aws-android-sdk-s3:2.2.10'

    // HYPR Crypto
    implementation(name: "crypto", version: "${cryptoVersion}", ext: 'aar') { transitive = true }
  
    // HYPR ADP
    implementation(name: "THPAgent", version: "${adpVersion}", ext: 'aar') { transitive = true }
    implementation(name: "TeeClient", version: "${adpVersion}", ext: 'aar') { transitive = true }
    implementation(name: "caCrypto", version: "${adpVersion}", ext: 'aar') { transitive = true }
}

Basic Architecture

InterfaceHyprInit
FIDO Client Adapter
Related ComponentsFIDO Client Adapter
FIDO Client
ASMs
Authenticators
FunctionalityFIDO Operations

HYPR Setup

HyprInit and HyprFCA must be initiated before any FIDO client calls are made. With that in mind, we suggest initializing during creation of the main FCA activity as described below. The entire FCA MainFCAActivity.java file is shown here:

package com.hypr.hyprfidoclientadaptersample;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.navigation.NavigationView;

import androidx.core.content.FileProvider;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import android.os.StrictMode;
import android.text.TextUtils;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;

import com.hypr.hyprandroidcommon.authenticator.HyprPrefs;
import com.hypr.hyprandroidcommon.fidomodel.data.AaidKeys;
import com.hypr.hyprandroidcommon.test.HyprTestDataModel;
import com.hypr.hyprandroidcommon.uiadapter.adapters.HyprActionCallbacks;
import com.hypr.hyprandroidcommon.uiadapter.adapters.HyprActions;
import com.hypr.hyprandroidcommon.useragent.HyprInit;
import com.hypr.hyprandroidcommon.useragent.HyprSetup;
import com.hypr.hyprandroidcommon.useragent.data.datastruct.HyprGetRegistrationsData;
import com.hypr.hyprandroidcommon.useragent.data.error.HyprStatusExtraData;
import com.hypr.hyprandroidcommon.useragent.data.error.HyprStatusResult;
import com.hypr.hyprandroidcommon.useragent.fido.uaf.fidoclientadapter.HyprFCA;
import com.hypr.hyprandroidcommon.useragent.fido.uaf.fidoclientadapter.HyprFCACallback;
import com.hypr.hyprandroidcommon.utils.HyprSupportLog;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static com.hypr.hyprandroidcommon.useragent.data.datastruct.HyprGetRegistrationsData.EXTRA_DATA_KEY_GET_REGISTRATIONS;

public class MainFCAActivity extends AppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener, AdapterView.OnItemSelectedListener {
    private final String LOG_TAG = "HYPR_TEST_" + ((Object) this).getClass().getSimpleName();

    // These contain one authenticator per selection
    private final static String SAMPLE_FIDO_PAYLOAD_REG_SINGLES  = "[{\"header\":{\"op\":\"Reg\",\"appID\":\"\",\"upv\":{\"major\":1,\"minor\":0},\"serverData\":\"HyprServer\"},\"challenge\":\"NHFkbTNoaDQ3NXBzazY0dWMyZGVnajNiZXU\",\"username\":\"User4e442oqsbi99g31u98rprrvbc3\",\"policy\":{\"accepted\":[[{\"aaid\":[\"0045#0022\"]}],[{\"aaid\":[\"0045#0112\"]}],[{\"aaid\":[\"0045#0122\"]}],[{\"aaid\":[\"0045#0052\"]}],[{\"aaid\":[\"0045#0012\"]}],[{\"aaid\":[\"0045#0032\"]}],[{\"aaid\":[\"0045#0033\"]}]],\"disallowed\":[{\"aaid\":[\"0045#0099\"]}]}}]";
    private final static String SAMPLE_FIDO_PAYLOAD_AUTH_SINGLES = "[{\"header\":{\"op\":\"Auth\",\"appID\":\"\",\"upv\":{\"major\":1,\"minor\":0},\"serverData\":\"HyprServer\"},\"challenge\":\"dTk2dTNnNjY4djlmZXVxY3ZzMGplYmYzYnA\",\"policy\":{\"accepted\":[[{\"aaid\":[\"0045#0022\"]}],[{\"aaid\":[\"0045#0112\"]}],[{\"aaid\":[\"0045#0122\"]}],[{\"aaid\":[\"0045#0052\"]}],[{\"aaid\":[\"0045#0012\"]}],[{\"aaid\":[\"0045#0032\"]}],[{\"aaid\":[\"0045#0033\"]}]],\"disallowed\":[{\"aaid\":[\"0045#0099\"]}]}}]";

    // These contain two authenticators per selection
    private final static String SAMPLE_FIDO_PAYLOAD_REG_DOUBLES  = "[{\"header\":{\"op\":\"Reg\",\"appID\":\"\",\"upv\":{\"major\":1,\"minor\":0},\"serverData\":\"HyprServer\"},\"challenge\":\"NHFkbTNoaDQ3NXBzazY0dWMyZGVnajNiZXU\",\"username\":\"User4e442oqsbi99g31u98rprrvbc3\",\"policy\":{\"accepted\":[[{\"aaid\":[\"0045#0052\"]},{\"aaid\":[\"0045#0012\"]}],[{\"aaid\":[\"0045#0052\"]},{\"aaid\":[\"0045#0032\"]}],[{\"aaid\":[\"0045#0012\"]},{\"aaid\":[\"0045#0032\"]}]]}}]";
    private final static String SAMPLE_FIDO_PAYLOAD_AUTH_DOUBLES = "[{\"header\":{\"op\":\"Auth\",\"appID\":\"\",\"upv\":{\"major\":1,\"minor\":0},\"serverData\":\"HyprServer\"},\"challenge\":\"dTk2dTNnNjY4djlmZXVxY3ZzMGplYmYzYnA\",\"policy\":{\"accepted\":[[{\"aaid\":[\"0045#0012\"]},{\"aaid\":[\"0045#0052\"]}],[{\"aaid\":[\"0045#0032\"]},{\"aaid\":[\"0045#0052\"]}],[{\"aaid\":[\"0045#0012\"]},{\"aaid\":[\"0045#0032\"]}]]}}]";

    // These are specific to the authenticator
    private final static String SAMPLE_FIDO_PAYLOAD_DEREG_SINGLE = "[{\"authenticators\":[{\"aaid\":\"DEREG_AAID\",\"keyID\":\"DEREG_KEY\"}],\"header\":{\"op\":\"Dereg\",\"appID\":\"\",\"upv\":{\"major\":1,\"minor\":0}}}]";
    private final static String SAMPLE_FIDO_PAYLOAD_DEREG_FINGER = "[{\"authenticators\":[{\"aaid\":\"0045#0052\",\"keyID\":\"bm96bi1WbVUtRklYZjVxU3RIM3Rmb3VyNHFZOW4wdEtza1lWNWJzaEhQNA\"}],\"header\":{\"op\":\"Dereg\",\"appID\":\"\",\"upv\":{\"major\":1,\"minor\":0}}}]";
    private final static String SAMPLE_FIDO_PAYLOAD_DEREG_PIN    = "[{\"authenticators\":[{\"aaid\":\"0045#0012\",\"keyID\":\"SjVYWXJUNXZSRzlQYUQ4bWQxeThoQm1PZmFTRXRaQ1ktSno5WjFNcEM2VQ\"}],\"header\":{\"op\":\"Dereg\",\"appID\":\"\",\"upv\":{\"major\":1,\"minor\":0}}}]";
    private final static String SAMPLE_FIDO_PAYLOAD_DEREG_FACE   = "[{\"authenticators\":[{\"aaid\":\"0045#0032\",\"keyID\":\"SjVYWXJUNXZSRzlQYUQ4bWQxeThoQm1PZmFTRXRaQ1ktSno5WjFNcEM2VQ\"}],\"header\":{\"op\":\"Dereg\",\"appID\":\"\",\"upv\":{\"major\":1,\"minor\":0}}}]";
    private final static String SAMPLE_FIDO_PAYLOAD_DEREG_VOICE  = "[{\"authenticators\":[{\"aaid\":\"0045#0033\",\"keyID\":\"SjVYWXJUNXZSRzlQYUQ4bWQxeThoQm1PZmFTRXRaQ1ktSno5WjFNcEM2VQ\"}],\"header\":{\"op\":\"Dereg\",\"appID\":\"\",\"upv\":{\"major\":1,\"minor\":0}}}]";

    public static final List<String> FIDO_USERNAMES = Collections.unmodifiableList(Arrays.asList("User4e442oqsbi99g31u98rprrvbc3", "User55532683258946082357845555", "User666qsbi99g31u98rprrvbc6666"));

    private void setUserProfilePosition(final int userProfilePosition) {
        HyprPrefs.setSettingsParam(this, "HYPR_FCA_USER_PROFILE_POSITION", userProfilePosition);
    }

    private int getUserProfilePosition() {
        return HyprPrefs.getSettingsParam(this, "HYPR_FCA_USER_PROFILE_POSITION", 0);
    }

    @Override
    public void onItemSelected(AdapterView<?> parent,
                               View view,
                               int position,
                               long id) {
        // User Profile Spinner
        Log.d(LOG_TAG, "onItemSelected position: " + position);
        setUserProfilePosition(position);
    }
    @Override
    public void onNothingSelected(AdapterView<?> parent) {
        // User Profile Spinner
    }

    private String getRegPayload() {
        String payload = SAMPLE_FIDO_PAYLOAD_REG_SINGLES;
        int userProfilePosition = getUserProfilePosition();
        String fidoUsername = FIDO_USERNAMES.get(userProfilePosition);
        Log.d(LOG_TAG, "getRegPayload userProfilePosition: " + userProfilePosition + " fidoUsername: " + fidoUsername);
        payload = payload.replace("User4e442oqsbi99g31u98rprrvbc3", fidoUsername);
        return payload;
    }
    private String getAuthPayload() {
        String payload = SAMPLE_FIDO_PAYLOAD_AUTH_SINGLES;
        return payload;
    }
    private String getDeregPayload() {
        String payload = SAMPLE_FIDO_PAYLOAD_DEREG_SINGLE;
        // This will deregister the first aaid and key that is in the mapping
        if (mGetRegistrationsData.getAaidKeys().size() > 0) {
            AaidKeys aaidKeys = mGetRegistrationsData.getAaidKeys().get(0);
            String aaid = aaidKeys.getAaid();
            String key = "";
            if (!TextUtils.isEmpty(aaid)) {
                List<String> keys = aaidKeys.getKeyIds();
                if (keys.size() > 0) {
                    key = keys.get(0);
                    payload = payload.replace("DEREG_AAID", aaid);
                    payload = payload.replace("DEREG_KEY", key);
                }
            }
        }

        return payload;
    }


    private Spinner mUserProfileSpinner;

    private boolean mIsSuccess;
    private boolean mIsUiEnabled;
    private String  mFidoPayloadToSendToServer;
    private String  mResultToString;
    private String  mErrorCode;
    private String  mErrorDisplayText;

    private HyprGetRegistrationsData mGetRegistrationsData;

    private View mSpinnerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(LOG_TAG, "onCreate");
        setContentView(R.layout.activity_main_fca);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.addDrawerListener(toggle);
        toggle.syncState();

        NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);

        mIsUiEnabled = false;
        mIsSuccess = false;
        mFidoPayloadToSendToServer = "";
        mResultToString = "";
        mErrorCode = "";
        mErrorDisplayText = "";
        mGetRegistrationsData = new HyprGetRegistrationsData();

        LinearLayout initView = findViewById(R.id.layout_initializing);
        initView.setVisibility(View.VISIBLE);

        // Initialize ADP/TrustData
        HyprInit.getInstance().initTrustData(this, new HyprInit.InitTrustDataCallback() {
            @Override
            public void onInstallComplete() {
                Log.d(LOG_TAG, "onCreate onInstallComplete");
                // Initialize the FidoClientAdapter
                HyprFCA.getInstance().init(MainFCAActivity.this, true);
                mIsUiEnabled = true;
                LinearLayout initView = findViewById(R.id.layout_initializing);
                initView.setVisibility(View.GONE);
                // as debug build look for:
                // installOrUpdateTA:TEE
                // for type of ADP installation: TEE/SWP
            }

            @Override
            public void onInstallError(@NonNull String error,
                                       @Nullable Throwable throwable) {
                Log.e(LOG_TAG, "onCreate onInstallError error: " + error, throwable);
                LinearLayout initView = findViewById(R.id.layout_initializing);
                TextView initText = findViewById(R.id.text_initializing);
                initText.setText("ADP ERROR ON INIT: " + error);
                // you can choose to check the throwable instance for this custom HYPR Exception:
                // PackageNameMismatchException
                //
                // Sample Code:
                // if (throwable instanceof PackageNameMismatchException) { ... }
                //
                // which indicates that you are using the incorrect ADP AARs.
                // if it is this exception class, then you can handle it in a customized manner.
            }
        });
    }

    @Override
    public void onBackPressed() {
        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        int id = item.getItemId();

        if (mIsUiEnabled) {
            if (id == R.id.nav_upgrade) {
                HyprFCA.getInstance().upgradeSingleUserToMultiUser(this, FIDO_USERNAMES.get(getUserProfilePosition()));

            } else if (id == R.id.nav_get_registrations) {
                mSpinnerView = displaySpinner();
                HyprFCA.getInstance().getRegistrations(this, getRegPayload(), getGetRegistrationsCallback(), FIDO_USERNAMES.get(getUserProfilePosition()));

            } else if (id == R.id.nav_reg) {
                mSpinnerView = displaySpinner();
                HyprFCA.getInstance().registerRequest(this, getRegPayload(), getRegisterCallback());

            } else if (id == R.id.nav_auth) {
                mSpinnerView = displaySpinner();
                HyprFCA.getInstance().authenticateRequest(this, getAuthPayload(), getAuthenticateCallback(), FIDO_USERNAMES.get(getUserProfilePosition()));

            } else if (id == R.id.nav_dereg) {
                mSpinnerView = displaySpinner();
                HyprFCA.getInstance().deregister(this, getDeregPayload(), getDeregisterCallback(), FIDO_USERNAMES.get(getUserProfilePosition()));

            } else if (id == R.id.nav_reset_app) {
                mSpinnerView = displaySpinner();
                HyprFCA.getInstance().reset(this, getResetCallback(), FIDO_USERNAMES.get(getUserProfilePosition()));

            } else if (id == R.id.nav_send_support_log) {
                String supportEmail = "Log File Attached";
                String[] supportEmails = new String[1];
                supportEmails[0] = supportEmail;
                String chooserTitle = "Support Log Email";
                File logFile = HyprSupportLog.getLogsAsFile(this);
                Uri uri = FileProvider.getUriForFile(this, this.getString(R.string.fileprovider_authority), logFile);
                ArrayList<Uri> uris = new ArrayList<Uri>();
                uris.add(uri);
                StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder().build();
                StrictMode.setVmPolicy(policy);
                Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
                intent.setDataAndType(Uri.parse("mailto"), "text/plain");
                intent.putExtra(Intent.EXTRA_EMAIL, supportEmails);
                intent.putExtra(Intent.EXTRA_SUBJECT, "Support Log");
                intent.putExtra(Intent.EXTRA_TEXT, "Support Log Attached");
                intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
                startActivity(Intent.createChooser(intent, chooserTitle));
            }
        } else {
            Log.d(LOG_TAG, "onNavigationItemSelected ADP has not finished initializing yet.");
        }

        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);
        return true;
    }

    @Override
    protected void onResume() {
        Log.d(LOG_TAG, "onResume");

        super.onResume();
        if (android.os.Build.VERSION.SDK_INT < 29) {
            onActivityResume();
        }
    }

    @Override
    public void onTopResumedActivityChanged(boolean onTop) {
        Log.d(LOG_TAG, "onTopResumedActivityChanged onTop: " + onTop);

        if (onTop) {
            onActivityResume();
        }
    }

    /**
     * Due to changes made to the Activity Lifecycle in Android 10, we can no longer rely on onResume() to know when an
     * activity is resumed and in the foreground. onActivityResume() should be used instead of onResume()
     */
    protected void onActivityResume() {
        Log.d(LOG_TAG, "onActivityResume");

        Spinner spinnerUserProfile = findViewById(R.id.spinner_user_profiles);
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
                R.array.user_profile_names_array, android.R.layout.simple_spinner_item);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinnerUserProfile.setAdapter(adapter);
        spinnerUserProfile.setOnItemSelectedListener(this);
        spinnerUserProfile.setSelection(getUserProfilePosition());

        TextView textViewSuccess = findViewById(R.id.textview_result_is_success);
        textViewSuccess.setText(Boolean.toString(mIsSuccess));

        TextView textViewFidoPayload = findViewById(R.id.textview_result_fido_payload);
        textViewFidoPayload.setText(mFidoPayloadToSendToServer);

        TextView textViewErrorCode = findViewById(R.id.textview_error_code_result);
        textViewErrorCode.setText(mErrorCode);

        TextView textViewErrorDisplayText = findViewById(R.id.textview_error_display_text_result);
        textViewErrorDisplayText.setText(mErrorDisplayText);

        TextView textViewResult = findViewById(R.id.textview_result_to_string);
        textViewResult.setText(mResultToString);

        Toolbar toolbar = findViewById(R.id.toolbar);
        String toolbarText = getString(R.string.app_name) + " (v" + BuildConfig.VERSION_NAME + ")";
        toolbar.setTitle(toolbarText);
    }

    private HyprFCACallback getGetRegistrationsCallback() {
        return new HyprFCACallback() {
            @Override
            public void onSuccess(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getRegisterCompleteCallback onSuccess");

                mIsSuccess = true;
                mFidoPayloadToSendToServer = hyprStatusResult.getSuccessPayload();
                mResultToString = hyprStatusResult.toString();
                mErrorCode = "";
                mErrorDisplayText = "";

                Map<String, HyprStatusExtraData.HyprAuthExtraData> extraDataMap = hyprStatusResult.getStatusExtraData().getAuthExtraData();
                mGetRegistrationsData = (HyprGetRegistrationsData) extraDataMap.get(EXTRA_DATA_KEY_GET_REGISTRATIONS);

                // cleanup at end of Fido Operation sequence
                cleanup();
            }

            @Override
            public void onFail(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getRegisterCompleteCallback onFail");

                // cleanup at end of Fido Operation sequence
                cleanup();
            }
        };
    }

    private HyprFCACallback getRegisterCallback() {
        return new HyprFCACallback() {
            @Override
            public void onSuccess(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getRegisterCallback onSuccess");

                // updating ui data
                mIsSuccess = true;
                mFidoPayloadToSendToServer = hyprStatusResult.getSuccessPayload();
                mResultToString = hyprStatusResult.toString();
                mErrorCode = "";
                mErrorDisplayText = "";

                // TODO: Process the payload

                // TODO: Send to Fido server

                // TODO: Get real results back from server
                boolean resultsFromServer = true;

                // send register complete
                HyprFCA.getInstance().registerComplete(MainFCAActivity.this, resultsFromServer, getRegisterCompleteCallback());
            }

            @Override
            public void onFail(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getRegisterCompleteCallback onFail");

                // updating ui data
                mIsSuccess = false;
                mFidoPayloadToSendToServer = "";
                mResultToString = hyprStatusResult.toString();
                mErrorCode = String.valueOf(hyprStatusResult.getLowestLevelDisplayCode());
                mErrorDisplayText = hyprStatusResult.getLowestLevelDisplayText();

                // send register complete
                HyprFCA.getInstance().registerComplete(MainFCAActivity.this, false, getRegisterCompleteCallback());
            }
        };
    }

    private HyprFCACallback getRegisterCompleteCallback() {
        return new HyprFCACallback() {
            @Override
            public void onSuccess(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getRegisterCompleteCallback onSuccess");

                // cleanup at end of Fido Operation sequence
                cleanup();
            }

            @Override
            public void onFail(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getRegisterCompleteCallback onFail");

                // cleanup at end of Fido Operation sequence
                cleanup();
            }
        };
    }

    private HyprFCACallback getAuthenticateCallback() {
        return new HyprFCACallback() {
            @Override
            public void onSuccess(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getAuthenticateCallback onSuccess");

                // updating ui data
                mIsSuccess = true;
                mResultToString = hyprStatusResult.toString();
                mFidoPayloadToSendToServer = hyprStatusResult.getSuccessPayload();
                mErrorCode = "";
                mErrorDisplayText = "";

                // TODO: Process the payload

                // TODO: Send to Fido server

                // TODO: Get real results back from server
                boolean resultsFromServer = true;

                // send authenticate complete
                HyprFCA.getInstance().authenticateComplete(MainFCAActivity.this, resultsFromServer, getAuthenticateCompleteCallback());
            }

            @Override
            public void onFail(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getAuthenticateCallback onFail");

                // updating ui data
                mIsSuccess = false;
                mFidoPayloadToSendToServer = "";
                mResultToString = hyprStatusResult.toString();
                mErrorCode = String.valueOf(hyprStatusResult.getLowestLevelDisplayCode());
                mErrorDisplayText = hyprStatusResult.getLowestLevelDisplayText();

                // send authenticate complete
                HyprFCA.getInstance().authenticateComplete(MainFCAActivity.this, false, getAuthenticateCompleteCallback());
            }
        };
    }

    private HyprFCACallback getAuthenticateCompleteCallback() {
        return new HyprFCACallback() {
            @Override
            public void onSuccess(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getAuthenticateCompleteCallback onSuccess");

                // cleanup at end of Fido Operation sequence
                cleanup();
            }

            @Override
            public void onFail(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getAuthenticateCompleteCallback onFail");

                // cleanup at end of Fido Operation sequence
                cleanup();
            }
        };
    }

    private HyprFCACallback getDeregisterCallback() {
        return new HyprFCACallback() {
            @Override
            public void onSuccess(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getDeregisterCallback onSuccess");

                // updating ui data
                mIsSuccess = true;
                mFidoPayloadToSendToServer = "";
                mResultToString = hyprStatusResult.toString();
                mErrorCode = "";
                mErrorDisplayText = "";

                // cleanup at end of Fido Operation sequence
                cleanup();
            }

            @Override
            public void onFail(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getDeregisterCallback onFail");

                // updating ui data
                mIsSuccess = false;
                mFidoPayloadToSendToServer = "";
                mResultToString = hyprStatusResult.toString();
                mErrorCode = String.valueOf(hyprStatusResult.getLowestLevelDisplayCode());
                mErrorDisplayText = hyprStatusResult.getLowestLevelDisplayText();

                // cleanup at end of Fido Operation sequence
                cleanup();
            }
        };
    }

    private HyprFCACallback getResetCallback() {
        return new HyprFCACallback() {
            @Override
            public void onSuccess(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getResetCallback onSuccess");

                // updating ui data
                mIsSuccess = true;
                mFidoPayloadToSendToServer = "";
                mResultToString = hyprStatusResult.toString();
                mErrorCode = "";
                mErrorDisplayText = "";

                // cleanup at end of Operation sequence
                cleanup();
            }

            @Override
            public void onFail(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getResetCallback onFail");

                // updating ui data
                mIsSuccess = false;
                mFidoPayloadToSendToServer = "";
                mResultToString = hyprStatusResult.toString();
                mErrorCode = String.valueOf(hyprStatusResult.getLowestLevelDisplayCode());
                mErrorDisplayText = hyprStatusResult.getLowestLevelDisplayText();

                // cleanup at end of Operation sequence
                cleanup();
            }
        };
    }

    private void cleanup() {
        Log.d(LOG_TAG, "cleanup");

        HyprFCA.getInstance().cleanup(MainFCAActivity.this);

        if (mSpinnerView != null) {
            mSpinnerView.setVisibility(View.GONE);
        }
    }

    public View displaySpinner() {
        @SuppressLint("InflateParams") View progressBar = getLayoutInflater().inflate(R.layout.hypr_common_view_progress_spinner, null);
        ViewGroup.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        addContentView(progressBar, layoutParams);
        return progressBar;
    }
}

Unintended behavior may occur when backgrounding the app during registration, authentication, deregistration, etc. To prevent this behavior, we have created app/src/main/java/com/hypr/hyprfidoclientadaptersample/App.java to extend MultiDexApplication.

package com.hypr.hyprfidoclientadaptersample;

import androidx.annotation.Keep;
import androidx.multidex.MultiDexApplication;

@Keep
public final class App extends MultiDexApplication {

    @Override
    public void onCreate() {
        super.onCreate();
    }
}

Include .App for the android:name in AndroidManifest.xml.

<manifest>
  <application
      // ...
      android:name=".App" android:extractNativeLibs="true" android:roundIcon="@mipmap/hypr_app_icon_round" android:label="@string/app_name" android:icon="@mipmap/hypr_app_icon" android:allowBackup="false" android:exported="true"
      // ...
</manifest>

Testing Reg/Auth/Dereg

The onNavigationItemSelected menu determines the action taken:

@Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        int id = item.getItemId();

        if (mIsUiEnabled) {
            if (id == R.id.nav_upgrade) {
                HyprFCA.getInstance().upgradeSingleUserToMultiUser(this, FIDO_USERNAMES.get(getUserProfilePosition()));

            } else if (id == R.id.nav_get_registrations) {
                mSpinnerView = displaySpinner();
                HyprFCA.getInstance().getRegistrations(this, getRegPayload(), getGetRegistrationsCallback(), FIDO_USERNAMES.get(getUserProfilePosition()));

            } else if (id == R.id.nav_reg) {
                mSpinnerView = displaySpinner();
                HyprFCA.getInstance().registerRequest(this, getRegPayload(), getRegisterCallback());

            } else if (id == R.id.nav_auth) {
                mSpinnerView = displaySpinner();
                HyprFCA.getInstance().authenticateRequest(this, getAuthPayload(), getAuthenticateCallback(), FIDO_USERNAMES.get(getUserProfilePosition()));

            } else if (id == R.id.nav_dereg) {
                mSpinnerView = displaySpinner();
                HyprFCA.getInstance().deregister(this, getDeregPayload(), getDeregisterCallback(), FIDO_USERNAMES.get(getUserProfilePosition()));

            } else if (id == R.id.nav_reset_app) {
                mSpinnerView = displaySpinner();
                HyprFCA.getInstance().reset(this, getResetCallback(), FIDO_USERNAMES.get(getUserProfilePosition()));

            } else if (id == R.id.nav_send_support_log) {
                String supportEmail = "Log File Attached";
                String[] supportEmails = new String[1];
                supportEmails[0] = supportEmail;
                String chooserTitle = "Support Log Email";
                File logFile = HyprSupportLog.getLogsAsFile(this);
                Uri uri = FileProvider.getUriForFile(this, this.getString(R.string.fileprovider_authority), logFile);
                ArrayList<Uri> uris = new ArrayList<Uri>();
                uris.add(uri);
                StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder().build();
                StrictMode.setVmPolicy(policy);
                Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
                intent.setDataAndType(Uri.parse("mailto"), "text/plain");
                intent.putExtra(Intent.EXTRA_EMAIL, supportEmails);
                intent.putExtra(Intent.EXTRA_SUBJECT, "Support Log");
                intent.putExtra(Intent.EXTRA_TEXT, "Support Log Attached");
                intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
                startActivity(Intent.createChooser(intent, chooserTitle));
            }
        } else {
            Log.d(LOG_TAG, "onNavigationItemSelected ADP has not finished initializing yet.");
        }

        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);
        return true;
    }

πŸ‘

FIDO Payloads

Commands from HyprFCA can be used to process FIDO payloads.

Register

The following snippet represents the registration payload called during the menu choices:

// Registration Payload
private String getRegPayload() {
  String payload = SAMPLE_FIDO_PAYLOAD_REG_SINGLES;
  int userProfilePosition = getUserProfilePosition();
  String fidoUsername = FIDO_USERNAMES.get(userProfilePosition);
  Log.d(LOG_TAG, "getRegPayload userProfilePosition: " + userProfilePosition + " fidoUsername: " + fidoUsername);
  payload = payload.replace("User4e442oqsbi99g31u98rprrvbc3", fidoUsername);
  return payload;
}

// ...

Registration requires a callback to handle the response.

// ...

private HyprFCACallback getGetRegistrationsCallback() {
        return new HyprFCACallback() {
            @Override
            public void onSuccess(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getRegisterCompleteCallback onSuccess");

                mIsSuccess = true;
                mFidoPayloadToSendToServer = hyprStatusResult.getSuccessPayload();
                mResultToString = hyprStatusResult.toString();
                mErrorCode = "";
                mErrorDisplayText = "";

                Map<String, HyprStatusExtraData.HyprAuthExtraData> extraDataMap = hyprStatusResult.getStatusExtraData().getAuthExtraData();
                mGetRegistrationsData = (HyprGetRegistrationsData) extraDataMap.get(EXTRA_DATA_KEY_GET_REGISTRATIONS);

                // cleanup at end of Fido Operation sequence
                cleanup();
            }

            @Override
            public void onFail(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getRegisterCompleteCallback onFail");

                // cleanup at end of Fido Operation sequence
                cleanup();
            }
        };
    }

    private HyprFCACallback getRegisterCallback() {
        return new HyprFCACallback() {
            @Override
            public void onSuccess(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getRegisterCallback onSuccess");

                // updating ui data
                mIsSuccess = true;
                mFidoPayloadToSendToServer = hyprStatusResult.getSuccessPayload();
                mResultToString = hyprStatusResult.toString();
                mErrorCode = "";
                mErrorDisplayText = "";

                // TODO: Process the payload

                // TODO: Send to Fido server

                // TODO: Get real results back from server
                boolean resultsFromServer = true;

                // send register complete
                HyprFCA.getInstance().registerComplete(MainFCAActivity.this, resultsFromServer, getRegisterCompleteCallback());
            }

            @Override
            public void onFail(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getRegisterCompleteCallback onFail");

                // updating ui data
                mIsSuccess = false;
                mFidoPayloadToSendToServer = "";
                mResultToString = hyprStatusResult.toString();
                mErrorCode = String.valueOf(hyprStatusResult.getLowestLevelDisplayCode());
                mErrorDisplayText = hyprStatusResult.getLowestLevelDisplayText();

                // send register complete
                HyprFCA.getInstance().registerComplete(MainFCAActivity.this, false, getRegisterCompleteCallback());
            }
        };
    }

    private HyprFCACallback getRegisterCompleteCallback() {
        return new HyprFCACallback() {
            @Override
            public void onSuccess(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getRegisterCompleteCallback onSuccess");

                // cleanup at end of Fido Operation sequence
                cleanup();
            }

            @Override
            public void onFail(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getRegisterCompleteCallback onFail");

                // cleanup at end of Fido Operation sequence
                cleanup();
            }
        };
    }

Authenticate

The following snippet represents the authentication payload called during the menu choices:

private String getAuthPayload() {
        String payload = SAMPLE_FIDO_PAYLOAD_AUTH_SINGLES;
        return payload;
    }

Authenticate requires a callback to handle the response.

private HyprFCACallback getAuthenticateCallback() {
        return new HyprFCACallback() {
            @Override
            public void onSuccess(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getAuthenticateCallback onSuccess");

                // updating ui data
                mIsSuccess = true;
                mResultToString = hyprStatusResult.toString();
                mFidoPayloadToSendToServer = hyprStatusResult.getSuccessPayload();
                mErrorCode = "";
                mErrorDisplayText = "";

                // TODO: Process the payload

                // TODO: Send to Fido server

                // TODO: Get real results back from server
                boolean resultsFromServer = true;

                // send authenticate complete
                HyprFCA.getInstance().authenticateComplete(MainFCAActivity.this, resultsFromServer, getAuthenticateCompleteCallback());
            }

            @Override
            public void onFail(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getAuthenticateCallback onFail");

                // updating ui data
                mIsSuccess = false;
                mFidoPayloadToSendToServer = "";
                mResultToString = hyprStatusResult.toString();
                mErrorCode = String.valueOf(hyprStatusResult.getLowestLevelDisplayCode());
                mErrorDisplayText = hyprStatusResult.getLowestLevelDisplayText();

                // send authenticate complete
                HyprFCA.getInstance().authenticateComplete(MainFCAActivity.this, false, getAuthenticateCompleteCallback());
            }
        };
    }

    private HyprFCACallback getAuthenticateCompleteCallback() {
        return new HyprFCACallback() {
            @Override
            public void onSuccess(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getAuthenticateCompleteCallback onSuccess");

                // cleanup at end of Fido Operation sequence
                cleanup();
            }

            @Override
            public void onFail(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getAuthenticateCompleteCallback onFail");

                // cleanup at end of Fido Operation sequence
                cleanup();
            }
        };
    }

Deregister

The following snippet represents the deregistration payload called during the menu choices:

private String getDeregPayload() {
        String payload = SAMPLE_FIDO_PAYLOAD_DEREG_SINGLE;
        // This will deregister the first aaid and key that is in the mapping
        if (mGetRegistrationsData.getAaidKeys().size() > 0) {
            AaidKeys aaidKeys = mGetRegistrationsData.getAaidKeys().get(0);
            String aaid = aaidKeys.getAaid();
            String key = "";
            if (!TextUtils.isEmpty(aaid)) {
                List<String> keys = aaidKeys.getKeyIds();
                if (keys.size() > 0) {
                    key = keys.get(0);
                    payload = payload.replace("DEREG_AAID", aaid);
                    payload = payload.replace("DEREG_KEY", key);
                }
            }
        }

        return payload;
    }

Deregistration requires a callback to handle the response.

private HyprFCACallback getDeregisterCallback() {
        return new HyprFCACallback() {
            @Override
            public void onSuccess(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getDeregisterCallback onSuccess");

                // updating ui data
                mIsSuccess = true;
                mFidoPayloadToSendToServer = "";
                mResultToString = hyprStatusResult.toString();
                mErrorCode = "";
                mErrorDisplayText = "";

                // cleanup at end of Fido Operation sequence
                cleanup();
            }

            @Override
            public void onFail(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getDeregisterCallback onFail");

                // updating ui data
                mIsSuccess = false;
                mFidoPayloadToSendToServer = "";
                mResultToString = hyprStatusResult.toString();
                mErrorCode = String.valueOf(hyprStatusResult.getLowestLevelDisplayCode());
                mErrorDisplayText = hyprStatusResult.getLowestLevelDisplayText();

                // cleanup at end of Fido Operation sequence
                cleanup();
            }
        };
    }

Reset FIDO Client Adapter

πŸ“˜

FCA Reset

You can use a command from the HyprFidoClientAdapter to reset the FIDO Client Adapter. This will clear the FIDO Client Adapter database of all authenticators and states.

To reset the FCA completely:

private HyprFCACallback getResetCallback() {
        return new HyprFCACallback() {
            @Override
            public void onSuccess(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getResetCallback onSuccess");

                // updating ui data
                mIsSuccess = true;
                mFidoPayloadToSendToServer = "";
                mResultToString = hyprStatusResult.toString();
                mErrorCode = "";
                mErrorDisplayText = "";

                // cleanup at end of Operation sequence
                cleanup();
            }

            @Override
            public void onFail(@NonNull HyprStatusResult hyprStatusResult) {
                Log.d(LOG_TAG, "getResetCallback onFail");

                // updating ui data
                mIsSuccess = false;
                mFidoPayloadToSendToServer = "";
                mResultToString = hyprStatusResult.toString();
                mErrorCode = String.valueOf(hyprStatusResult.getLowestLevelDisplayCode());
                mErrorDisplayText = hyprStatusResult.getLowestLevelDisplayText();

                // cleanup at end of Operation sequence
                cleanup();
            }
        };
    }

Offline Mode

When there is no network, Offline Mode will be enabled. We determine this by:

((ConnectivityManager) activity.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo().isConnected() == false
  • In Offline Mode, a failure will be returned when:

    • There is no response cache
    • There is cache, but the facetId cannot be verified
  • In Online Mode, any network error will return a failure

Error Handling and HyprStatusResult

See Error Handling for more details on HyprStatusResult, HyprDisplayResult, and HyprDisplayCodes.

Localization

The app/src/main/res/values/strings.xml file in the Sample App contains all of the overridable strings.

<!-- Loading Screen -->
    <string name="hypr_loading_button_skip">Skip</string>
    <string name="hypr_loading_button_learn">Learn About This App</string>

    <!-- Begin Screen -->
    <string name="hypr_begin_text_description">True Password-less Security\nPowered By\nDecentralized Authentication</string>
    <string name="hypr_begin_button_lets_begin">Let\'s Begin</string>

    <!-- Selection License Screen -->
    <string name="hypr_selection_text_description">To complete setup please scan QR code or input 6-digit PIN to pair your mobile device</string>
    <string name="hypr_selection_text_qr">SCAN QR</string>
    <string name="hypr_selection_text_info">ABOUT HYPR</string>
    <string name="hypr_selection_text_pin">ENTER PIN</string>

    <!-- Info Screen -->
    <string name="hypr_settings_manage">Manage</string>
    <string name="hypr_powered_by">POWERED BY</string>
    <string name="hypr_version">APP VERSION&#160;</string>
    <string name="hypr_contact_us">Contact Us</string>
    <string name="hypr_support">Support</string>

    <!-- Oob license accepted -->
    <string name="hypr_oob_license_accepted_title">Stay tuned…</string>
    <string name="hypr_oob_license_accepted_description">We are building your personalized experience</string>

    <!-- Browser Main Screen -->
    <string name="hypr_main_browser_instructions">Begin your demo experience on the web at</string>
    <string name="hypr_main_browser_instructions_1">Login to an online banking portal with Multi-Factor Biometrics.</string>
    <string name="hypr_main_browser_instructions_2">Approve a wire transfer with FIDO-certified Step-up Authentication.</string>
    <string name="hypr_main_browser_instructions_3">Learn how HYPR decentralized authentication eliminates credential reuse, prevents phishing and minimizes the risk of a breach.</string>
    <string name="hypr_main_browser_options">On the website, you can demo a secure password-less experience powered by HYPR:</string>
    <string name="hypr_main_browser_bank_url">demo.hypr.com</string>
    <string name="hypr_main_browser_login_successful_text">Login Successful</string>
    <string name="hypr_action_add_account">Add Account</string>

    <string name="hypr_main_browser_landing_screen_title">True-Passwordless Authentication</string>
    <string name="hypr_main_browser_landing_screen_subtitle">From HYPR</string>

    <!-- Workstation Main Screen -->
    <string name="hypr_main_workstation_title">Paired Workstations</string>

    <!-- Workstation Main No Internet Screen -->
    <string name="hypr_main_workstation_no_internet_one">Seems like you have no internet connection…</string>
    <string name="hypr_main_workstation_no_internet_two">Make sure you are connected to the internet and try again</string>
    <string name="hypr_main_workstation_no_internet_popup">No internet connection available</string>
    <string name="hypr_main_workstation_no_internet_popup_reconnecting">Reconnecting…</string>

    <!-- Workstation No Internet Screen -->
    <string name="hypr_workstation_no_internet_dialog_title">No Internet Connection</string>
    <string name="hypr_workstation_no_internet_dialog_message_delete">Could not delete selected workstation(s)… please check your internet connection and try again</string>
    <string name="hypr_workstation_no_internet_dialog_message_add_workstation">Could not add workstation… please check your internet connection and try again</string>
    <string name="hypr_workstation_no_internet_dialog_message_support">Support email could not be sent… please check your internet connection and try again</string>
    <string name="hypr_workstation_no_internet_dialog_message_reset">Could not reset application… please check your internet connection and try again</string>
    <string name="hypr_workstation_no_internet_dialog_message_workstation">Please check your internet connection and try again</string>

    <!-- Workstation Main None Screen -->
    <string name="hypr_main_workstation_none_add">Add a Workstation</string>

    <!-- Workstation Main Single Screen -->
    <string name="hypr_main_workstation_single_add">Add More Workstations</string>
    <string name="hypr_main_workstation_single_unlock">Tap to Unlock or Long Press for More Options</string>

    <!-- Browser Success Overlay Screen -->
    <string name="hypr_common_nicely_done_text">Nicely Done.</string>
    <string name="hypr_common_logged_in_welcome_text_long">You have successfully enrolled your decentralized biometrics and registered your trusted device with the Highlands Bank Demo Experience</string>

    <!-- Workstation Success Overlay Screen -->
    <string name="hypr_workstation_license_enroll_success_title">Nicely done.</string>
    <string name="hypr_workstation_license_enroll_success_text">Your mobile device is now paired with your workstation.</string>
    <string name="hypr_workstation_added_success_text">Added</string>
    <string name="hypr_workstation_unlocked_success_text">Unlocked</string>
    <string name="hypr_workstation_delete_success_text">Workstation Deleted</string>
    <string name="hypr_workstation_delete_multiple_success_text">Workstations Deleted</string>
    <string name="hypr_workstation_edit_success_text">Edit Successful</string>

    <!-- Workstation Fail Overlay Screen -->
    <string name="hypr_workstation_try">TRY</string>
    <string name="hypr_workstation_again">AGAIN</string>
    <string name="hypr_workstation_edit_fail_text">Edit Failed</string>
    <string name="hypr_workstation_already_exists">This computer is already paired.</string>
    <string name="hypr_workstation_max_limit_fail_text">You have reached the limit of paired workstations. Please remove unused workstations and try again.</string>
    <string name="hypr_workstation_logged_out_state_error">You must be logged in before you can unlock.</string>

    <!-- Workstation Config Overlay Screen -->
    <string name="hypr_overlay_config_edit">EDIT</string>
    <string name="hypr_overlay_config_delete">DELETE</string>
    <string name="hypr_overlay_config_history">HISTORY</string>

    <!-- Workstation Status Update Overlay Screen -->
    <string name="hypr_workstation_status_update">This workstation is already unlocked</string>

    <!-- Browser History Screen -->
    <string name="hypr_history_browser_title_1">Personal Account</string>
    <string name="hypr_history_browser_title_2">Login History</string>
    <string name="hypr_history_browser_no_history">No login history</string>

    <!-- Workstation History Screen -->
    <string name="hypr_history_workstation_title_2">Unlock History</string>
    <string name="hypr_history_workstation_no_history">No unlock history</string>

    <!-- Workstation Edit Screen -->
    <string name="hypr_edit_workstation_title">Edit Workstation Details</string>
    <string name="hypr_edit_workstation_name_hint">New Workstation Name</string>
    <string name="hypr_edit_workstation_default">This is my Default Workstation</string>

    <!-- Workstation Delete Screen -->
    <string name="hypr_delete_workstation_title">Deleting Workstation</string>
    <string name="hypr_delete_workstation_warning">WARNING</string>
    <string name="hypr_delete_workstation_text_1">You are about to delete a workstation that is paired with your mobile device.</string>
    <string name="hypr_delete_workstation_text_2">Please don’t forget to remove this mobile device from this workstation.</string>
    <string name="hypr_delete_workstation_question">Are you sure?</string>

    <!-- Workstation Delete Multiple Screen -->
    <string name="hypr_delete_multiple_workstations_title">Deleting Workstations</string>
    <string name="hypr_delete_multiple_workstations_text_1">You are about to delete %1$d workstations that are paired with your mobile device.</string>
    <string name="hypr_delete_multiple_workstations_text_2">Please don’t forget to remove this mobile device from the following workstations.</string>

    <!-- Workstation Manage Screen -->
    <string name="hypr_manage_workstations_default">default</string>
    <string name="hypr_manage_workstations_title">Manage Workstations</string>
    <string name="hypr_manage_workstations_none">You do not have any paired workstations.</string>

    <!-- Step-Up Authentication Screens -->
    <string name="hypr_transaction_step_up">Step-Up Authentication Required</string>
    <string name="hypr_transaction_higher_level">This transaction requires a higher level of security</string>
    <string name="hypr_transaction_amount_text">$1000.00</string>
    <string name="hypr_transaction_amount">$5,750.00</string>
    <string name="hypr_transaction_success_result">Bank Wire Complete</string>
    <string name="hypr_transaction_generic_error">Something went wrong&#8230;</string>

    <!-- PIN Screens -->
    <string name="hypr_pin_entry_text">Please enter your 6-digit pin to approve authentication.</string>
    <string name="hypr_pin_entry_transaction_text">Please enter your 6-digit pin to approve step-up authentication.</string>
    <string name="hypr_pin_transaction_error">PIN Authentication Failed. Please Try Again.</string>

    <!-- Finger Screens -->
    <string name="hypr_transaction_finger_instructions">Please Authenticate to approve step-up authentication.</string>
    <string name="hypr_transaction_fingerprint_error">Fingerprint Authentication Failed. Please Try Again.</string>

    <!-- Face Screens -->
    <string name="hypr_transaction_face_error">Face Authentication Failed. Please Try Again.</string>

    <!-- Settings Screen -->
    <string name="hypr_info_title">TRUST ANYONE</string>
    <string name="hypr_info_text_1">"HYPR is the leading provider of decentralized authentication with millions of password-less users secured across the Fortune 500. Named a β€œCool Vendor” by Gartner, HYPR is trusted by major enterprises such as Mastercard and Samsung to eliminate fraud, enhance user experience, and minimize the risk of a breach."</string>
    <string name="hypr_info_text_2">"Companies often store user credentials in a centralized database targeted by hackers. Centralized passwords create a single point of failure and have remained the #1 cause of mass breaches and credential reuse – until now."</string>
    <string name="hypr_info_text_3">"The HYPR solution ensures that your users’ credentials always remain safe on personal devices. Eliminating centralized passwords enables HYPR to remove the target and provide a secure password-less experience for your customers and employees."</string>
    <string name="hypr_info_text_4">"By decentralizing user authentication, HYPR minimizes the risk of a breach, eliminates credential reuse, and enables enterprises to <b>Trust Anyone</b>."</string>
    <string name="hypr_info_email">[email protected]</string>

    <!-- Reset App -->
    <string name="hypr_reset_app">Reset Application</string>
    <string name="hypr_reset_app_workstation">Reset App</string>
    <string name="hypr_reset_app_text">Are you sure you want to reset the application?</string>

    <!-- Reset App -->
    <string name="hypr_contact_support_email">[email protected]</string>
    <string name="hypr_contact_support_subject">Questions? Feedback? We\'ll be happy to hear from you!</string>
    <string name="hypr_contact_support_email_title">Send HYPR an email&#8230;</string>
    <string name="hypr_contact_support_email_body_top">-= PLEASE DO NOT TYPE BELOW THIS LINE =-</string>
    <string name="hypr_contact_support_email_body_app_id">App Id:&#160;</string>
    <string name="hypr_contact_support_email_body_app_version">App Version:&#160;</string>
    <string name="hypr_contact_support_email_body_os">OS:&#160;</string>
    <string name="hypr_contact_support_email_body_api">API:&#160;</string>
    <string name="hypr_contact_support_email_body_manufacturer">Manufacturer:&#160;</string>
    <string name="hypr_contact_support_email_body_model">Model:&#160;</string>
    <string name="hypr_contact_support_email_body_rp_url">RP URL:&#160;</string>
    <string name="hypr_contact_support_email_body_rp_app">RP App:&#160;</string>
    <string name="hypr_contact_support_email_body_identifier">Identifier:&#160;</string>