Skip to content
Merged
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
3 changes: 2 additions & 1 deletion app/res/navigation/nav_graph_personalid.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
app:popUpTo="@id/personalid_otp_page" />
<action
android:id="@+id/action_personalid_otp_page_to_personalid_name"
app:destination="@id/personalid_name" />
app:destination="@id/personalid_name"
app:popUpTo="@id/personalid_phone_fragment"/>
<action
android:id="@+id/action_personalid_otp_page_to_personalid_phone_fragment"
app:destination="@id/personalid_phone_fragment" />
Expand Down
5 changes: 5 additions & 0 deletions app/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -610,4 +610,9 @@ License.
<string name="connect_appbar_title_password_verification">Vérification du mot de passe</string>
<string name="add_secondary_phone_number_to_reset_your_recovery_code">Ajoutez un numéro de téléphone secondaire pour réinitialiser votre code de récupération.</string>
<string name="add_secondary_phone_number">Ajouter un numéro de téléphone secondaire</string>
<string name="incorrect_otp">Le mot de passe à usage unique saisi est incorrect. Veuillez réessayer.</string>
<string name="too_many_attempts">Trop de tentatives. Veuillez réessayer plus tard.</string>
<string name="missing_activity">Quelque chose s\'est mal passé</string>
<string name="otp_verification_failed">Échec de la vérification OTP:</string>
<string name="verification_failed">La vérification a échoué, veuillez réessayer.</string>
</resources>
6 changes: 5 additions & 1 deletion app/res/values-pt/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -595,5 +595,9 @@
<string name="connect_job_tile_daily_visits">Visitas diárias</string>
<string name="connect_appbar_title_app_lock">Bloqueio de aplicação</string>
<string name="connect_appbar_title_password_verification">Verificação de palavra-passe</string>

<string name="incorrect_otp">O OTP inserido está incorreto. Tente novamente.</string>
<string name="too_many_attempts">Muitas tentativas. Tente novamente mais tarde.</string>
<string name="missing_activity">Algo deu errado</string>
<string name="otp_verification_failed">Falha na verificação do OTP:</string>
<string name="verification_failed">A verificação falhou. Tente novamente.</string>
</resources>
5 changes: 5 additions & 0 deletions app/res/values-sw/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -614,4 +614,9 @@
<string name="connect_job_tile_daily_visits">Ziara za Kila Siku</string>
<string name="connect_appbar_title_app_lock">Kufuli ya Programu</string>
<string name="connect_appbar_title_password_verification">Uthibitishaji wa Nenosiri</string>
<string name="incorrect_otp">OTP iliyoingizwa si sahihi. Tafadhali jaribu tena.</string>
<string name="too_many_attempts">Majaribio mengi sana. Tafadhali jaribu tena baadaye.</string>
<string name="missing_activity">Hitilafu fulani imetokea</string>
<string name="otp_verification_failed">Uthibitishaji wa OTP haukufaulu:</string>
<string name="verification_failed">Uthibitishaji haukufaulu na tafadhali jaribu tena</string>
</resources>
5 changes: 5 additions & 0 deletions app/res/values-ti/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -580,4 +580,9 @@
<string name="connect_messaging_channel_consent_description">ኣብዚ ቻነል ናይ መልእኽቲ ፈትሊ ክትከፍቱ ትሰማምዑ ዶ?</string>
<string name="connect_messaging_channel_consent_accept">ተቀበል</string>
<string name="connect_messaging_channel_consent_decline">ኣውድቅ</string>
<string name="incorrect_otp">እቲ ዝኣተወ OTP ጌጋ እዩ። በጃኹም ደጊምኩም ፈትኑ።</string>
<string name="too_many_attempts">ብዙሕ ፈተነታት። በጃኹም ድሒርኩም ደጊምኩም ፈትኑ።</string>
<string name="missing_activity">ገለ ነገር ተጋግዩ።</string>
<string name="otp_verification_failed">ምርግጋጽ ኦቲፒ ኣይተዓወተን፤</string>
<string name="verification_failed">ምርግጋጽ ኣይተዓወተን በጃኹም ደጊምኩም ፈትኑ</string>
</resources>
5 changes: 5 additions & 0 deletions app/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -935,4 +935,9 @@
<string name="camera_permission_msg">In order to take a picture, CommCare needs permission to use your device camera.</string>
<string name="capture_photo">Capture Photo</string>
<string name="device_incompatible_version_error">Your device is not compatible with this feature. Please use a device running Android 7 or higher.</string>
<string name="incorrect_otp">The OTP entered is incorrect. Please try again.</string>
<string name="too_many_attempts">Too many attempts. Please try again later.</string>
<string name="missing_activity">Something went wrong</string>
<string name="otp_verification_failed">OTP verification failed:</string>
<string name="verification_failed">Verification failed and please try again</string>
</resources>
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
package org.commcare.fragments.personalId;

import static android.app.Activity.RESULT_OK;
import static android.content.Context.RECEIVER_NOT_EXPORTED;

import android.app.Activity;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.Navigation;

import com.google.android.gms.auth.api.phone.SmsRetriever;
import com.google.android.gms.auth.api.phone.SmsRetrieverClient;
import com.google.firebase.auth.FirebaseUser;
Expand All @@ -35,13 +26,24 @@
import org.commcare.google.services.analytics.AnalyticsParamValue;
import org.commcare.google.services.analytics.FirebaseAnalyticsUtil;
import org.commcare.utils.KeyboardHelper;
import org.commcare.utils.OtpErrorType;
import org.commcare.utils.OtpManager;
import org.commcare.utils.OtpVerificationCallback;
import org.joda.time.DateTime;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.Navigation;

import static android.app.Activity.RESULT_OK;
import static android.content.Context.RECEIVER_NOT_EXPORTED;

public class PersonalIdPhoneVerificationFragment extends Fragment {

private static final int REQ_USER_CONSENT = 200;
Expand All @@ -55,6 +57,8 @@ public class PersonalIdPhoneVerificationFragment extends Fragment {
private final Handler resendTimerHandler = new Handler();
private OtpManager otpManager;
private PersonalIdSessionData personalIdSessionData;
OtpVerificationCallback otpCallback;


private final Runnable resendTimerRunnable = new Runnable() {
@Override
Expand All @@ -77,14 +81,16 @@ public void onCreate(Bundle savedInstanceState) {
}

private void initOtpManager() {
OtpVerificationCallback otpCallback = new OtpVerificationCallback() {
otpCallback = new OtpVerificationCallback() {
@Override
public void onCodeSent(String verificationId) {
if (otpCallback == null) return;
Toast.makeText(requireContext(), getString(R.string.connect_otp_sent), Toast.LENGTH_SHORT).show();
}

@Override
public void onSuccess(FirebaseUser user) {
if (otpCallback == null) return;
logOtpVerification(true);
Toast.makeText(requireContext(), getString(R.string.connect_otp_verified) + user.getPhoneNumber(), Toast.LENGTH_SHORT).show();
user.getIdToken(false).addOnCompleteListener(task -> {
Expand All @@ -96,9 +102,19 @@ public void onSuccess(FirebaseUser user) {
}

@Override
public void onFailure(String errorMessage) {
public void onFailure(OtpErrorType errorType, @Nullable String errorMessage) {
if (otpCallback == null) return;
logOtpVerification(false);
displayOtpError(errorMessage);
String userMessage = switch (errorType) {
case INVALID_CREDENTIAL -> getString(R.string.incorrect_otp);
case TOO_MANY_REQUESTS -> getString(R.string.too_many_attempts);
case MISSING_ACTIVITY -> getString(R.string.missing_activity);
case VERIFICATION_FAILED -> getString(R.string.verification_failed);
default ->
getString(R.string.otp_verification_failed) + errorMessage != null ? errorMessage : "Unknown error";
};
displayOtpError(userMessage);
binding.connectPhoneVerifyButton.setEnabled(false);
}
};

Expand Down Expand Up @@ -164,9 +180,11 @@ private void clearOtpError() {
}

private void displayOtpError(String message) {
binding.connectPhoneVerifyError.setVisibility(View.VISIBLE);
binding.connectPhoneVerifyError.setText(message);
binding.customOtpView.setErrorState(true);
if (message != null && !message.isEmpty()){
binding.connectPhoneVerifyError.setVisibility(View.VISIBLE);
binding.connectPhoneVerifyError.setText(message);
binding.customOtpView.setErrorState(true);
}
}

@Override
Expand Down Expand Up @@ -202,6 +220,7 @@ public void onStop() {
public void onDestroyView() {
super.onDestroyView();
binding = null;
otpCallback = null;
}

@Override
Expand Down Expand Up @@ -247,13 +266,12 @@ private void updateVerificationMessage() {

private void requestOtp() {
clearOtpError();
if (primaryPhone != null && !primaryPhone.isEmpty()){
otpRequestTime = new DateTime();
otpManager.requestOtp(primaryPhone);
}
otpRequestTime = new DateTime();
otpManager.requestOtp(primaryPhone);
}

private void verifyOtp() {
binding.connectPhoneVerifyButton.setEnabled(false);
clearOtpError();
String otpCode = binding.customOtpView.getOtpValue();

Expand Down
59 changes: 36 additions & 23 deletions app/src/org/commcare/utils/FirebaseAuthService.java
Original file line number Diff line number Diff line change
@@ -1,55 +1,50 @@
package org.commcare.utils;

import android.app.Activity;
import android.util.Log;

import androidx.annotation.NonNull;

import com.google.firebase.FirebaseException;
import com.google.firebase.FirebaseTooManyRequestsException;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;
import com.google.firebase.auth.FirebaseAuthMissingActivityForRecaptchaException;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.PhoneAuthCredential;
import com.google.firebase.auth.PhoneAuthOptions;
import com.google.firebase.auth.PhoneAuthProvider;

import java.util.concurrent.TimeUnit;

import androidx.annotation.NonNull;

public class FirebaseAuthService implements OtpAuthService {

private final FirebaseAuth firebaseAuth;
private final OtpVerificationCallback callback;
private PhoneAuthOptions.Builder optionsBuilder;
private String verificationId;

public FirebaseAuthService(Activity activity, OtpVerificationCallback callback) {
public FirebaseAuthService(@NonNull Activity activity, @NonNull OtpVerificationCallback callback) {
this.callback = callback;
this.firebaseAuth = FirebaseAuth.getInstance();

PhoneAuthProvider.OnVerificationStateChangedCallbacks verificationCallbacks =
new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
@Override
public void onVerificationCompleted(@NonNull PhoneAuthCredential credential) {
firebaseAuth.signInWithCredential(credential)
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
FirebaseUser user = task.getResult().getUser();
callback.onSuccess(user);
} else {
callback.onFailure("Verification failed");
}
});
firebaseAuthenticator(credential);
}

@Override
public void onVerificationFailed(@NonNull FirebaseException e) {
callback.onFailure("Verification failed: " + e.getMessage());
handleFirebaseException(e);
}

@Override
public void onCodeSent(@NonNull String verificationId,
public void onCodeSent(@NonNull String verId,
@NonNull PhoneAuthProvider.ForceResendingToken token) {
FirebaseAuthService.this.verificationId = verificationId;
callback.onCodeSent(verificationId);
verificationId = verId;
callback.onCodeSent(verId);
}
};

Expand All @@ -60,28 +55,46 @@ public void onCodeSent(@NonNull String verificationId,
}

@Override
public void requestOtp(String phoneNumber) {
public void requestOtp(@NonNull String phoneNumber) {
optionsBuilder.setPhoneNumber(phoneNumber);
PhoneAuthOptions options = optionsBuilder.build();
PhoneAuthProvider.verifyPhoneNumber(options);
PhoneAuthProvider.verifyPhoneNumber(optionsBuilder.build());
}

@Override
public void verifyOtp(String code) {
public void verifyOtp(@NonNull String code) {
if (verificationId == null) {
callback.onFailure("No verification ID available. Request OTP first.");
callback.onFailure(OtpErrorType.GENERIC_ERROR, "Please request OTP again.");
return;
}

PhoneAuthCredential credential = PhoneAuthProvider.getCredential(verificationId, code);
firebaseAuthenticator(credential);
}

private void firebaseAuthenticator(PhoneAuthCredential credential) {
firebaseAuth.signInWithCredential(credential)
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
FirebaseUser user = task.getResult().getUser();
callback.onSuccess(user);
} else {
callback.onFailure("OTP verification failed.");
handleFirebaseException(task.getException());
}
});
}

private void handleFirebaseException(Exception e) {
if (e instanceof FirebaseAuthInvalidCredentialsException) {
callback.onFailure(OtpErrorType.INVALID_CREDENTIAL, null);
} else if (e instanceof FirebaseTooManyRequestsException) {
callback.onFailure(OtpErrorType.TOO_MANY_REQUESTS, null);
} else if (e instanceof FirebaseAuthMissingActivityForRecaptchaException) {
callback.onFailure(OtpErrorType.MISSING_ACTIVITY, null);
} else if (e instanceof FirebaseAuthException) {
callback.onFailure(OtpErrorType.VERIFICATION_FAILED, null);
} else {
callback.onFailure(OtpErrorType.GENERIC_ERROR,
e != null ? e.getMessage() : "OTP verification failed");
}
}
}
9 changes: 9 additions & 0 deletions app/src/org/commcare/utils/OtpErrorType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.commcare.utils;

public enum OtpErrorType {
INVALID_CREDENTIAL,
TOO_MANY_REQUESTS,
MISSING_ACTIVITY,
GENERIC_ERROR,
VERIFICATION_FAILED
}
11 changes: 3 additions & 8 deletions app/src/org/commcare/utils/OtpManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.app.Activity;


/**
* Manager class that wraps authentication service operations for OTP (One-Time Password) functionality.
*/
Expand All @@ -12,24 +13,18 @@ public class OtpManager {
/**
* Constructs an OtpManager by instantiating a default FirebaseAuthService internally.
*
* @param activity The calling activity, required by FirebaseAuth
* @param callback Callback to handle OTP verification events
* @param activity The calling activity, required by FirebaseAuth
* @param callback Callback to handle OTP verification events
*/
public OtpManager(Activity activity, OtpVerificationCallback callback) {
this.authService = new FirebaseAuthService(activity, callback);
}

public void requestOtp(String phoneNumber) {
if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
throw new IllegalArgumentException("Phone number cannot be null or empty");
}
authService.requestOtp(phoneNumber);
}

public void submitOtp(String code) {
if (code == null || code.trim().isEmpty()) {
throw new IllegalArgumentException("OTP code cannot be null or empty");
}
authService.verifyOtp(code);
}
}
7 changes: 5 additions & 2 deletions app/src/org/commcare/utils/OtpVerificationCallback.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.google.firebase.auth.FirebaseUser;

import androidx.annotation.Nullable;

/**
* Callback interface for OTP (One-Time Password) verification operations.
* <p>
Expand All @@ -27,7 +29,8 @@ public interface OtpVerificationCallback {
/**
* Called when an error occurs during the OTP process.
*
* @param errorMessage A description of the error that occurred
* @param message A description of the error that occurred
*/
void onFailure(String errorMessage);
void onFailure(OtpErrorType errorType, @Nullable String message);

}