Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

biometric design changes #2770

Open
wants to merge 3 commits into
base: connect_qa
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
458 changes: 205 additions & 253 deletions app/AndroidManifest.xml

Large diffs are not rendered by default.

Binary file added app/res/drawable/arow_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/res/drawable/commcare_diamgi_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/res/drawable/dialpad.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions app/res/drawable/grey_small_button.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="135dp"
android:height="40dp"
android:viewportWidth="135"
android:viewportHeight="40"
>
<group>
<clip-path
android:pathData="M20 0H115C126.046 0 135 8.95431 135 20C135 31.0457 126.046 40 115 40H20C8.95431 40 0 31.0457 0 20C0 8.95431 8.95431 0 20 0Z"
/>
<path
android:pathData="M0 0V40H135V0"
android:fillColor="#D9D9D9"
/>
</group>
</vector>
Binary file added app/res/drawable/lock.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions app/res/drawable/rounded_rect.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- res/drawable/rounded_background.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#09666666"/> <!-- Background color, white in this case -->
<corners android:radius="16dp"/> <!-- Adjust the radius as needed -->
<padding
android:left="16dp"
android:top="16dp"
android:right="16dp"
android:bottom="16dp"/>
<stroke
android:width="1dp"
android:color="#09666666"/> <!-- Border color -->
</shape>
3 changes: 0 additions & 3 deletions app/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,10 @@ License.
<string name="connect_results_summary_verifications_title">Statut de vérification</string>
<string name="connect_results_summary_verifications_description">Vérification en attente: %d\nNot Approved: %d\Approved: %d</string>
<string name="connect_results_summary_payments_title">Statut de paiement</string>
<string name="connect_results_summary_payments_description">Montant gagné: %s\nMontant transféré: %s</string>
<string name="connect_results_payment_description">Payé %s</string>
<string name="connect_results_payment_confirmed">Confirmé</string>
<string name="connect_results_payment_not_confirmed">Non confirmé</string>
<string name="connect_results_payment_date">Émis %s</string>


<string name="connect_last_update">Mis à jour: %s</string>
<string name="connect_job_none_training">Vous ne suivez aucune formation pour un emploi pour le moment</string>
<string name="connect_job_none_active">Vous n\'avez aucun emploi actif pour le moment</string>
Expand Down
2 changes: 2 additions & 0 deletions app/res/values/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,6 @@
<!-- Button colors -->
<color name="button_primary">#005ab2</color>
<color name="button_disabled">#d0e3ff</color>

<color name="pattern_color">#1c1b1f</color>
</resources>
10 changes: 7 additions & 3 deletions app/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
<string name="expirenotification">CommCare Login Expired</string>
<string name="submission_notification_title">Submitting Data</string>
<string name="submission_logs_title">Submitting CommCare Logs</string>
<string name="remote_form_payload_url"/>
<string name="remote_form_payload_url" />

<item name="login_screen_hide_all_cuttoff" type="integer">200</item>
<item name="login_screen_hide_banner_cuttoff" type="integer">250</item>
Expand Down Expand Up @@ -467,7 +467,7 @@
<string name="area_format" cc:translatable="true">%1s sq m</string>
<string name="parse_coordinates_failure" cc:translatable="true">Could not parse input coordinates</string>
<string name="location_provider_disabled" cc:translatable="true">Turn on your location to receive location updates</string>
<string name="wait_for_location_fix" cc:translatable="true">CommCare is still trying to get your location. Please wait or click Cancel to abort.</string>
<string name="wait_for_location_fix" cc:translatable="true">CommCare is still trying to get your location. Please wait or click Cancel to abort.</string>
<string name="invalid_case_property_length" cc:translatable="true">Invalid %1s, value must be 255 characters or less</string>
<string name="invalid_case_graph_error" cc:translatable="true">This form introduces an invalid case relationship</string>
<string name="root_detected_title" cc:translatable="true">Rooted Device Detected</string>
Expand Down Expand Up @@ -786,7 +786,7 @@
<string name="login_menu_connect_sign_in">Sign up for ConnectID</string>
<string name="login_menu_connect_sign_out">Sign out of ConnectID</string>
<string name="login_menu_connect_forget">Forget ConnectID user</string>

<string name="connect_app_install_unknown_error">App install failed due to an unknown error</string>
<string name="connect_app_installed">App installed</string>
<string name="app_with_id_not_found">Required CommCare App is not installed on device</string>
Expand All @@ -797,4 +797,8 @@
<string name="user_suspended">User is Suspended. Please contact admin.</string>
<string name="select_phone_number">Select Phone Number</string>
<string name="connect_job_list_api_failure">An error occurred while connecting to the server.</string>
<string name="app_lock">App Lock</string>
pm-dimagi marked this conversation as resolved.
Show resolved Hide resolved
<string name="unlock_with_biometric">Unlock with biometric</string>
<string name="set_up_pin">Setup 6 Digit PIN</string>
<string name="unlock_with_biometric_text">When enabled, you’ll need to use fingerprint, face or other unique identifiers to open the CommCare App</string>
</resources>
58 changes: 46 additions & 12 deletions app/src/org/commcare/activities/LoginActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt;
import androidx.core.app.ActivityCompat;
import androidx.core.util.Pair;
import androidx.preference.PreferenceManager;
Expand Down Expand Up @@ -51,6 +53,7 @@
import org.commcare.tasks.ManageKeyRecordTask;
import org.commcare.tasks.PullTaskResultReceiver;
import org.commcare.tasks.ResultAndError;
import org.commcare.utils.BiometricsHelper;
import org.commcare.utils.ConsumerAppsUtil;
import org.commcare.utils.CrashUtil;
import org.commcare.utils.Permissions;
Expand Down Expand Up @@ -112,6 +115,10 @@ public class LoginActivity extends CommCareActivity<LoginActivity>
private String presetAppId;
private boolean appLaunchedFromConnect;
private boolean connectLaunchPerformed;
private BiometricPrompt.AuthenticationCallback biometricPromptCallbacks;
private BiometricManager biometricManager;



@Override
protected void onCreate(Bundle savedInstanceState) {
Expand All @@ -134,6 +141,8 @@ protected void onCreate(Bundle savedInstanceState) {
presetAppId = getIntent().getStringExtra(EXTRA_APP_ID);
appLaunchedFromConnect = ConnectManager.wasAppLaunchedFromConnect(presetAppId);
connectLaunchPerformed = false;
biometricManager = BiometricManager.from(this);
biometricPromptCallbacks = preparePromptCallbacks();

if (savedInstanceState == null) {
// Only restore last user on the initial creation
Expand Down Expand Up @@ -197,17 +206,40 @@ protected void onSaveInstanceState(Bundle savedInstanceState) {
}
}



private BiometricPrompt.AuthenticationCallback preparePromptCallbacks() {
return new BiometricPrompt.AuthenticationCallback() {


@Override
public void onAuthenticationSucceeded(
@NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
ConnectManager.goToConnectJobsList();
setResult(RESULT_OK);
finish();

}

@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
Toast.makeText(getApplicationContext(), "Authentication failed",
Toast.LENGTH_SHORT)
.show();
}
};
}

/**
* @param restoreSession Indicates if CommCare should attempt to restore the saved session
* upon successful login
*/
protected void initiateLoginAttempt(boolean restoreSession) {
if(isConnectJobsSelected()) {
ConnectManager.unlockConnect(this, success -> {
if(success) {
ConnectManager.goToConnectJobsList();
}
});
boolean allowOtherOptions = BiometricsHelper.isPinConfigured(this, biometricManager);
BiometricsHelper.authenticateFingerprint(this, biometricManager, allowOtherOptions, biometricPromptCallbacks);
} else {
LoginMode loginMode = uiController.getLoginMode();

Expand Down Expand Up @@ -469,13 +501,15 @@ private void setResultAndFinish(boolean goToJobInfo) {

public void handleConnectButtonPress() {
selectedAppIndex = -1;
ConnectManager.unlockConnect(this, success -> {
if(success) {
ConnectManager.goToConnectJobsList();
setResult(RESULT_OK);
finish();
}
});
boolean allowOtherOptions = BiometricsHelper.isPinConfigured(this, biometricManager);
BiometricsHelper.authenticateFingerprint(this, biometricManager, allowOtherOptions, biometricPromptCallbacks);
// ConnectManager.unlockConnect(this, success -> {
// if(success) {
// ConnectManager.goToConnectJobsList();
// setResult(RESULT_OK);
// finish();
// }
// });
}

public boolean handleConnectSignIn() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
package org.commcare.activities.connect;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt;

import org.commcare.activities.CommCareActivity;
import org.commcare.connect.ConnectConstants;
import org.commcare.dalvik.R;
import org.commcare.google.services.analytics.AnalyticsParamValue;
import org.commcare.google.services.analytics.FirebaseAnalyticsUtil;
import org.commcare.interfaces.CommCareActivityUIController;
import org.commcare.interfaces.WithUIController;
import org.commcare.utils.BiometricsHelper;
import org.commcare.views.dialogs.CustomProgressDialog;
import org.javarosa.core.services.Logger;

import androidx.biometric.BiometricManager;

Expand All @@ -24,22 +33,26 @@ public class ConnectIdBiometricConfigActivity extends CommCareActivity<ConnectId
private ConnectIdBiometricConfigActivityUiController uiController;
private BiometricManager biometricManager;

private BiometricPrompt.AuthenticationCallback biometricPromptCallbacks;

private boolean attemptingFingerprint = false;
private boolean allowPassword = false;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setTitle(getString(R.string.connect_verify_title));
biometricManager = BiometricManager.from(this);

biometricPromptCallbacks = preparePromptCallbacks();
uiController.setupUI();

BiometricsHelper.ConfigurationStatus fingerprint = BiometricsHelper.checkFingerprintStatus(this,
biometricManager);
BiometricsHelper.ConfigurationStatus pin = BiometricsHelper.checkPinStatus(this, biometricManager);
if (fingerprint == BiometricsHelper.ConfigurationStatus.NotAvailable &&
pin == BiometricsHelper.ConfigurationStatus.NotAvailable) {
//Skip to password-only workflow
finish(true, true);
pm-dimagi marked this conversation as resolved.
Show resolved Hide resolved
finish(true, true,false);
} else {
updateState(fingerprint, pin);
}
Expand Down Expand Up @@ -116,34 +129,83 @@ public void updateState(BiometricsHelper.ConfigurationStatus fingerprintStatus,
}

public void handleFingerprintButton() {
BiometricsHelper.ConfigurationStatus fingerprint = BiometricsHelper.checkFingerprintStatus(this,
biometricManager);
if (fingerprint == BiometricsHelper.ConfigurationStatus.Configured) {
finish(true, false);
} else if (!BiometricsHelper.configureFingerprint(this)) {
finish(true, true);

if (BiometricsHelper.isFingerprintConfigured(this, biometricManager)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic should only handle fingerprint. Also, it needs to call BiometricsHelper.configureFingerprint if the fingerprint isn't configured yet (that function sends the user to the Android fingerprint config).

//Automatically try fingerprint first
performFingerprintUnlock();
} else if (BiometricsHelper.isPinConfigured(this, biometricManager)) {
performPinUnlock();
} else {
Logger.exception("No unlock method available when trying to unlock ConnectID", new Exception("No unlock option"));
}
}

public void performFingerprintUnlock() {
attemptingFingerprint = true;
boolean allowOtherOptions = BiometricsHelper.isPinConfigured(this, biometricManager) ||
allowPassword;
BiometricsHelper.authenticateFingerprint(this, biometricManager, allowOtherOptions, biometricPromptCallbacks);
}

public void performPinUnlock() {
BiometricsHelper.authenticatePin(this, biometricManager, biometricPromptCallbacks);
}

private BiometricPrompt.AuthenticationCallback preparePromptCallbacks() {
final Context context = this;
return new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode,
@NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
if (attemptingFingerprint) {
attemptingFingerprint = false;
if (BiometricsHelper.isPinConfigured(context, biometricManager) &&
allowPassword) {
//Automatically try password, it's the only option
performPinUnlock();
}
}
}

@Override
public void onAuthenticationSucceeded(
@NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
logSuccess();
finish(true, false, false);
}

@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
Toast.makeText(getApplicationContext(), "Authentication failed",
Toast.LENGTH_SHORT)
.show();
}
};
}

private void logSuccess() {
String method = attemptingFingerprint ? AnalyticsParamValue.CCC_SIGN_IN_METHOD_FINGERPRINT
: AnalyticsParamValue.CCC_SIGN_IN_METHOD_PIN;
FirebaseAnalyticsUtil.reportCccSignIn(method);
}

public void handlePinButton() {
pm-dimagi marked this conversation as resolved.
Show resolved Hide resolved
BiometricsHelper.ConfigurationStatus pin = BiometricsHelper.checkPinStatus(this, biometricManager);
if (pin == BiometricsHelper.ConfigurationStatus.Configured) {
finish(true, false);
finish(true, false,false);
} else if (!BiometricsHelper.configurePin(this)) {
finish(true, true);
finish(true, true,false);
}
}

public void finish(boolean success, boolean failedEnrollment) {
private void finish(boolean success, boolean password, boolean recover) {
Intent intent = new Intent(getIntent());

BiometricsHelper.ConfigurationStatus fingerprint = BiometricsHelper.checkFingerprintStatus(this,
biometricManager);
BiometricsHelper.ConfigurationStatus pin = BiometricsHelper.checkPinStatus(this, biometricManager);
boolean configured = fingerprint == BiometricsHelper.ConfigurationStatus.Configured ||
pin == BiometricsHelper.ConfigurationStatus.Configured;

intent.putExtra(ConnectConstants.ENROLL_FAIL, failedEnrollment || !configured);
intent.putExtra(ConnectConstants.PASSWORD, password);
intent.putExtra(ConnectConstants.RECOVER, recover);

setResult(success ? RESULT_OK : RESULT_CANCELED, intent);
finish();
Expand Down
Loading