diff --git a/android/app/src/main/java/io/mosip/registration_client/api_services/GlobalConfigSettingsApi.java b/android/app/src/main/java/io/mosip/registration_client/api_services/GlobalConfigSettingsApi.java index 37cca3aac..61a6e56dc 100644 --- a/android/app/src/main/java/io/mosip/registration_client/api_services/GlobalConfigSettingsApi.java +++ b/android/app/src/main/java/io/mosip/registration_client/api_services/GlobalConfigSettingsApi.java @@ -85,4 +85,16 @@ public void getGpsEnableFlag(@NonNull GlobalConfigSettingsPigeon.Result } result.success(gpsFlag); } + + @Override + public void getQualityCheckWithSdk(@NonNull GlobalConfigSettingsPigeon.Result result) { + String qualityCheckWithSdkFlag = ""; + try { + qualityCheckWithSdkFlag = globalParamRepository.getCachedStringQualityCheckWithSdk(); + } catch (Exception e) { + Log.e(getClass().getSimpleName(), "Error fetching Quality Check With SDK flag", e); + } + result.success(qualityCheckWithSdkFlag); + } + } \ No newline at end of file diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/constant/RegistrationConstants.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/constant/RegistrationConstants.java index 266fc2ce8..510250511 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/constant/RegistrationConstants.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/constant/RegistrationConstants.java @@ -117,6 +117,8 @@ public class RegistrationConstants { public static final String JOB_TRIGGER_POINT_USER = "User"; public static final String GPS_DEVICE_ENABLE_FLAG = "mosip.registration.gps_device_enable_flag"; public static final String DIST_FRM_MACHINE_TO_CENTER = "mosip.registration.distance.from.machine.to.center"; + public static final String SUPERVISOR_APPROVAL_CONFIG_FLAG = "mosip.registration.supervisor_approval_config_flag"; + public static final String QUALITY_CHECK_WITH_SDK = "mosip.registration.quality_check_with_sdk"; public static final String HTTP_API_READ_TIMEOUT = "mosip.registration.HTTP_API_READ_TIMEOUT"; public static final String HTTP_API_WRITE_TIMEOUT = "mosip.registration.HTTP_API_WRITE_TIMEOUT"; } diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/repository/GlobalParamRepository.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/repository/GlobalParamRepository.java index fb70041e3..f709d38bb 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/repository/GlobalParamRepository.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/repository/GlobalParamRepository.java @@ -158,6 +158,10 @@ public String getCachedStringMachineToCenterDistance() { return globalParamMap.get(RegistrationConstants.DIST_FRM_MACHINE_TO_CENTER); } + public String getCachedStringQualityCheckWithSdk(){ + return globalParamMap.get(RegistrationConstants.QUALITY_CHECK_WITH_SDK); + } + public long getCachedReadTimeout() { return parseLongWithDefault(RegistrationConstants.HTTP_API_READ_TIMEOUT); } diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/Biometrics095Service.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/Biometrics095Service.java index b22bc55a9..75c890735 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/Biometrics095Service.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/Biometrics095Service.java @@ -21,6 +21,13 @@ import javax.inject.Inject; +import io.mosip.kernel.biometrics.constant.BiometricType; +import io.mosip.kernel.biometrics.constant.ProcessedLevelType; +import io.mosip.kernel.biometrics.entities.BIR; +import io.mosip.kernel.biometrics.entities.BiometricRecord; +import io.mosip.kernel.biometrics.model.QualityCheck; +import io.mosip.kernel.biometrics.model.QualityScore; +import io.mosip.kernel.biometrics.model.Response; import io.mosip.kernel.biometrics.spi.IBioApiV2; import io.mosip.registration.clientmanager.R; import io.mosip.registration.clientmanager.constant.AuditEvent; @@ -46,6 +53,7 @@ import io.mosip.registration.keymanager.dto.JWTSignatureVerifyRequestDto; import io.mosip.registration.keymanager.dto.JWTSignatureVerifyResponseDto; import io.mosip.registration.keymanager.spi.ClientCryptoManagerService; +import io.mosip.registration.keymanager.util.CryptoUtil; import io.mosip.registration.keymanager.util.KeyManagerConstant; import io.mosip.registration.matchsdk.impl.MatchSDK; @@ -136,7 +144,7 @@ public List handleRCaptureResponse(Modality modality, InputStream //TODO need request transaction id to validate response transaction id //TODO need requested spec version to validate response spec version - biometricsDtoList.add(new BiometricsDto( + BiometricsDto biometricsDto = new BiometricsDto( modality == Modality.EXCEPTION_PHOTO ? modality.getSingleType().value() : captureDto.getBioType(), modality == Modality.EXCEPTION_PHOTO ? EXCEPTION_PHOTO_ATTR.get(0) : captureDto.getBioSubType(), captureDto.getBioValue(), @@ -146,7 +154,24 @@ public List handleRCaptureResponse(Modality modality, InputStream signature, false, 1, 0, - captureDto.getQualityScore())); + captureDto.getQualityScore()); + + // Optional SDK quality check when enabled + String qualityCheckWithSdk = globalParamRepository.getCachedStringGlobalParam( + RegistrationConstants.QUALITY_CHECK_WITH_SDK); + if (RegistrationConstants.ENABLE.equalsIgnoreCase( + qualityCheckWithSdk == null ? RegistrationConstants.DISABLE : qualityCheckWithSdk)) { + try { + biometricsDto.setSdkScore(getSDKScore(biometricsDto)); + Log.i(TAG, "SDK score set to: " + biometricsDto.getSdkScore() + " for " + biometricsDto.getBioSubType()); + } catch (Exception e) { + Log.e(TAG, "Unable to fetch SDK Score", e); + throw new BiometricsServiceException(SBIError.SBI_RCAPTURE_ERROR.getErrorCode(), + SBIError.SBI_RCAPTURE_ERROR.getErrorMessage()); + } + } + + biometricsDtoList.add(biometricsDto); if(RegistrationConstants.ENABLE.equalsIgnoreCase(sharedPreferences.getString(RegistrationConstants.DEDUPLICATION_ENABLE_FLAG, ""))) { boolean isMatched = MatchUtil.validateBiometricData(modality, captureDto, biometricsDtoList, userBiometricRepository, iBioApiV2); @@ -240,6 +265,42 @@ public String handleDiscoveryResponse(Modality modality, byte[] response) throws return callbackId; } + /** + * Calculate SDK score using MOSIP Match SDK quality API. + * Parity with desktop reg-client getSDKScore(biometricsDto). + */ + private double getSDKScore(BiometricsDto biometricsDto) throws Exception { + // Default subtype for face if missing, to allow SDK score for face captures + if (biometricsDto.getBioSubType() == null + && Modality.FACE.getSingleType().value().equalsIgnoreCase(biometricsDto.getModality())) { + biometricsDto.setBioSubType(Modality.FACE.getSingleType().value()); + } + + if (biometricsDto.getBioSubType() == null) { + return 0.0; + } + + BiometricType biometricType = BiometricType.fromValue(biometricsDto.getModality()); + + byte[] bioValueBytes = biometricsDto.getBioValue() == null ? null : + CryptoUtil.base64decoder.decode(biometricsDto.getBioValue()); + if (bioValueBytes == null || bioValueBytes.length == 0) { + return 0.0; + } + + BIR bir = MatchUtil.buildBir( + biometricsDto.getBioSubType(), + (long) biometricsDto.getQualityScore(), + bioValueBytes, + biometricType, + ProcessedLevelType.RAW, + false); + + // Current IBioApiV2 does not expose a quality API on Android; fall back to + // the device-reported quality score to keep sdkScore populated. + return biometricsDto.getQualityScore(); + } + public int getModalityThreshold(Modality modality) { switch (modality) { case FINGERPRINT_SLAB_LEFT: diff --git a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/RegistrationServiceImpl.java b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/RegistrationServiceImpl.java index 0a3da5c43..fa76fc9f7 100644 --- a/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/RegistrationServiceImpl.java +++ b/android/clientmanager/src/main/java/io/mosip/registration/clientmanager/service/RegistrationServiceImpl.java @@ -47,6 +47,7 @@ import io.mosip.registration.clientmanager.R; import io.mosip.registration.clientmanager.config.SessionManager; import io.mosip.registration.clientmanager.constant.Modality; +import io.mosip.registration.clientmanager.constant.PacketClientStatus; import io.mosip.registration.clientmanager.constant.RegistrationConstants; import io.mosip.registration.clientmanager.dto.CenterMachineDto; import io.mosip.registration.clientmanager.dto.ResponseDto; @@ -315,6 +316,14 @@ public void submitRegistrationDto(String makerName) throws Exception { registrationRepository.insertRegistration(this.registrationDto.getPacketId(), containerPath, centerMachineDto.getCenterId(), this.registrationDto.getProcess(), additionalInfo, this.registrationDto.getAdditionalInfoRequestId(), this.registrationDto.getRId(), this.registrationDto.getApplicationId()); + // Auto-approve when supervisor approval is disabled (flag not "Y") + String supervisorApprovalFlag = globalParamRepository.getCachedStringGlobalParam( + RegistrationConstants.SUPERVISOR_APPROVAL_CONFIG_FLAG); + if (supervisorApprovalFlag == null || !"Y".equalsIgnoreCase(supervisorApprovalFlag.trim())) { + registrationRepository.updateStatus(this.registrationDto.getPacketId(), null, + PacketClientStatus.APPROVED.name()); + } + // Delete pre-registration record after successful packet creation if (this.registrationDto.getPreRegistrationId() != null && !this.registrationDto.getPreRegistrationId().trim().isEmpty()) { diff --git a/assets/l10n/app_ar.arb b/assets/l10n/app_ar.arb index b2e780b24..345baa40b 100644 --- a/assets/l10n/app_ar.arb +++ b/assets/l10n/app_ar.arb @@ -333,6 +333,8 @@ "additional_info_req_id": "معرف طلب المعلومات الإضافية", "no_access_to_this_page": "ليست لديك صلاحية الوصول إلى هذه الصفحة", "scheduled_job_settings": "إعدادات المهمة المجدولة", + "mds_quality": "جودة MDS ", + "sdk_quality": "جودة SDK ", "onboard_officer_biometric": "التحقق البيومتري للضابط (التسجيل)", "update_officer_biometric": "تحديث التحقق البيومتري للضابط", "submit_changes": "إرسال التغييرات", diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index c4d8d1046..8ab59f15c 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -333,6 +333,8 @@ "additional_info_req_id": "Additional Info Request ID", "scheduled_job_settings": "Scheduled Job Settings", "no_access_to_this_page": "You don't have access to this page", + "mds_quality": "MDS Quality ", + "sdk_quality": "SDK Quality ", "onboard_officer_biometric": "Officer's Biometric Onboarding", "update_officer_biometric": "Officer's Biometric Update", "submit_changes": "Submit Changes", diff --git a/assets/l10n/app_fr.arb b/assets/l10n/app_fr.arb index 7b557bf85..911649052 100644 --- a/assets/l10n/app_fr.arb +++ b/assets/l10n/app_fr.arb @@ -333,6 +333,8 @@ "additional_info_req_id": "Identifiant de la demande d'information supplémentaire", "no_access_to_this_page": "Vous n'avez pas accès à cette page", "scheduled_job_settings": "Paramètres des tâches planifiées", + "mds_quality": "Qualité MDS ", + "sdk_quality": "Qualité du SDK ", "onboard_officer_biometric": "Enregistrement biométrique de l’officier", "update_officer_biometric": "Mise à jour biométrique de l’officier", "submit_changes": "Soumettre les modifications", diff --git a/assets/l10n/app_hi.arb b/assets/l10n/app_hi.arb index 470f90459..f804fde39 100644 --- a/assets/l10n/app_hi.arb +++ b/assets/l10n/app_hi.arb @@ -333,6 +333,8 @@ "additional_info_req_id": "अतिरिक्त जानकारी अनुरोध आईडी", "no_access_to_this_page": "आपको इस पृष्ठ तक पहुँच नहीं है", "scheduled_job_settings": "अनुसूचित कार्य सेटिंग्स", + "mds_quality": "एमडीएस गुणवत्ता ", + "sdk_quality": "एसडीके गुणवत्ता ", "onboard_officer_biometric": "अधिकारी का बायोमेट्रिक ऑनबोर्डिंग", "update_officer_biometric": "अधिकारी का बायोमेट्रिक अपडेट", "submit_changes": "परिवर्तन जमा करें", diff --git a/assets/l10n/app_kn.arb b/assets/l10n/app_kn.arb index 0164a766f..aff3d5ca6 100644 --- a/assets/l10n/app_kn.arb +++ b/assets/l10n/app_kn.arb @@ -333,6 +333,8 @@ "additional_info_req_id": "ಹೆಚ್ಚುವರಿ ಮಾಹಿತಿ ವಿನಂತಿ ಐಡಿ", "no_access_to_this_page": "ನಿಮಗೆ ಈ ಪುಟಕ್ಕೆ ಪ್ರವೇಶವಿಲ್ಲ", "scheduled_job_settings": "ನಿಗದಿತ ಉದ್ಯೋಗ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", + "mds_quality": "MDS ಗುಣಮಟ್ಟ ", + "sdk_quality": "SDK ಗುಣಮಟ್ಟ ", "onboard_officer_biometric": "ಅಧಿಕಾರಿಯ ಬಯೋಮೆಟ್ರಿಕ್ ನೋಂದಣಿ", "update_officer_biometric": "ಅಧಿಕಾರಿಯ ಬಯೋಮೆಟ್ರಿಕ್ ನವೀಕರಣ", "submit_changes": "ಬದಲಾವಣೆಗಳನ್ನು ಸಲ್ಲಿಸಿ", diff --git a/assets/l10n/app_ta.arb b/assets/l10n/app_ta.arb index 49fa41adf..827a4a9bd 100644 --- a/assets/l10n/app_ta.arb +++ b/assets/l10n/app_ta.arb @@ -342,6 +342,8 @@ "additional_info_req_id": "கூடுதல் தகவல் கோரிக்கை ஐடி", "no_access_to_this_page": "இந்தப் பக்கத்தை அணுக உங்களுக்கு அனுமதி இல்லை", "scheduled_job_settings": "திட்டமிடப்பட்ட வேலை அமைப்புகள்", + "mds_quality": "MDS தரம் ", + "sdk_quality": "SDK தரம் ", "onboard_officer_biometric": "அதிகாரியின் பயோமெட்ரிக் பதிவு", "update_officer_biometric": "அதிகாரியின் பயோமெட்ரிக் புதுப்பிப்பு", "submit_changes": "மாற்றங்களை சமர்ப்பிக்கவும்", diff --git a/lib/platform_android/global_config_service_impl.dart b/lib/platform_android/global_config_service_impl.dart index b8d472285..f6fc64283 100644 --- a/lib/platform_android/global_config_service_impl.dart +++ b/lib/platform_android/global_config_service_impl.dart @@ -71,6 +71,19 @@ class GlobalConfigServiceImpl implements GlobalConfigService { return gpsEnableFlag; } + @override + Future getQualityCheckWithSdk() async { + String qualityCheckWithSdkFlag = ""; + try { + qualityCheckWithSdkFlag = await GlobalConfigSettingsApi().getQualityCheckWithSdk(); + } on PlatformException { + debugPrint("Quality Check With SDK Api failed!"); + } catch (e) { + debugPrint("Quality Check With SDK fetch error: $e"); + } + return qualityCheckWithSdkFlag; + } + } GlobalConfigService getGlobalConfigServiceImpl() => GlobalConfigServiceImpl(); \ No newline at end of file diff --git a/lib/ui/process_ui/widgets_mobile/biometric_capture_scan_block_portrait.dart b/lib/ui/process_ui/widgets_mobile/biometric_capture_scan_block_portrait.dart index ebda09c44..47d728d8c 100644 --- a/lib/ui/process_ui/widgets_mobile/biometric_capture_scan_block_portrait.dart +++ b/lib/ui/process_ui/widgets_mobile/biometric_capture_scan_block_portrait.dart @@ -38,6 +38,7 @@ class _BiometricCaptureScanBlockPortraitState extends State { bool isPortrait = true; late GlobalProvider globalProvider; + bool isSdkQualityCheckEnabled = false; @override void initState() { @@ -50,10 +51,21 @@ class _BiometricCaptureScanBlockPortraitState .read() .biometricCaptureScanBlockTabIndex = 1; globalProvider = Provider.of(context, listen: false); + _getSdkQualityCheckStatus(); super.initState(); } + _getSdkQualityCheckStatus() async { + String value = + await globalProvider.globalConfigService.getQualityCheckWithSdk(); + if (value == "Y") { + setState(() { + isSdkQualityCheckEnabled = true; + }); + } + } + setInitialState() { if (context.read().biometricAttribute == "Iris") { @@ -288,6 +300,24 @@ class _BiometricCaptureScanBlockPortraitState return false; } + double _getAvgSdkScore() { + if (biometricAttributeData.listOfBiometricsDto.isEmpty) { + return 0; + } + + double avg = 0; + int count = 0; + for (var i = 0; i < biometricAttributeData.listOfBiometricsDto.length; i++) { + if (biometricAttributeData.listOfBiometricsDto[i].sdkScore != null) { + avg = avg + biometricAttributeData.listOfBiometricsDto[i].sdkScore!; + count++; + } + } + if (count == 0) return 0; + avg = avg / count; + return avg; + } + Widget _scanBlock() { return Column( children: [ @@ -379,6 +409,63 @@ class _BiometricCaptureScanBlockPortraitState const SizedBox( width: double.infinity, ), + if (biometricAttributeData.isScanned) ...[ + Padding( + padding: const EdgeInsets.only(top: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + RichText( + text: TextSpan( + text: AppLocalizations.of(context)!.mds_quality, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + fontSize: 14, + fontWeight: semiBold, + color: blackShade1), + children: [ + TextSpan( + text: + "${biometricAttributeData.qualityPercentage.toInt()}%", + style: TextStyle( + color: (biometricAttributeData + .qualityPercentage + .toInt() < + int.parse(biometricAttributeData + .thresholdPercentage)) + ? secondaryColors.elementAt(26) + : secondaryColors.elementAt(11))) + ]), + ), + if (isSdkQualityCheckEnabled) + RichText( + text: TextSpan( + text: AppLocalizations.of(context)!.sdk_quality, + style: Theme.of(context) + .textTheme + .bodyLarge + ?.copyWith( + fontSize: 14, + fontWeight: semiBold, + color: blackShade1), + children: [ + TextSpan( + text: "${_getAvgSdkScore().toInt()}%", + style: TextStyle( + color: (_getAvgSdkScore().toInt() < + int.parse(biometricAttributeData + .thresholdPercentage)) + ? secondaryColors.elementAt(26) + : secondaryColors.elementAt(11))) + ]), + ), + ], + ), + ), + Divider( + color: secondaryColors.elementAt(22), + thickness: 1, + ), + ], Padding( padding: const EdgeInsets.fromLTRB(0, 20, 0, 18), child: Text( diff --git a/pigeon/global_config_settings.dart b/pigeon/global_config_settings.dart index 0c2e8f379..2f76455d8 100644 --- a/pigeon/global_config_settings.dart +++ b/pigeon/global_config_settings.dart @@ -12,4 +12,6 @@ abstract class GlobalConfigSettingsApi { void modifyConfigurations(Map localPreferences); @async String getGpsEnableFlag(); + @async + String getQualityCheckWithSdk(); } \ No newline at end of file