[SELF-747] feat: clone android passport reader during setup (#1080)
* chore: remove android private modules doc * private repo pull * skip private modules * remove unused circuits building * save wip * format * restore tsconfig * fix package install * fix internal repo cloning * unify logic and fix cloning * git clone internal repos efficiently * formatting * run app yarn reinstall from root * coderabbit feedback * coderabbit suggestions * remove skip private modules logic * fix: ensure PAT is passed through yarn-install action and handle missing PAT gracefully - Update yarn-install action to pass SELFXYZ_INTERNAL_REPO_PAT to yarn install - Make setup-private-modules.cjs skip gracefully when PAT is unavailable in CI - Fixes issue where setup script was throwing error instead of skipping for forks * prettier * fix clone ci * clone ci fixes * fix import export sorts * fix instructions * fix: remove SelfAppBuilder re-export to fix duplicate export error - Remove SelfAppBuilder import/export from @selfxyz/qrcode - Update README to import SelfAppBuilder directly from @selfxyz/common - Fixes CI build failure with duplicate export error * fix: unify eslint-plugin-sort-exports version across workspaces - Update mobile-sdk-alpha from 0.8.0 to 0.9.1 to match other workspaces - Removes yarn.lock version conflict causing CI/local behavior mismatch - Fixes quality-checks workflow linting failure * fix: bust qrcode SDK build cache to resolve stale SelfAppBuilder issue - Increment GH_SDK_CACHE_VERSION from v1 to v2 - Forces CI to rebuild artifacts from scratch instead of using cached version - Resolves quality-checks linter error showing removed SelfAppBuilder export * skip job * test yarn cache * bump cache version to try and fix the issue * revert cache version * refactor: use direct re-exports for cleaner qrcode package structure - Replace import-then-export pattern with direct re-exports - Keep SelfAppBuilder export with proper alphabetical sorting (before SelfQRcode) - Maintain API compatibility as documented in README - Eliminates linter sorting issues while keeping clean code structure * fix: separate type and value imports in README examples - Import SelfApp as type since it's an interface - Import SelfAppBuilder as value since it's a class - Follows TypeScript best practices and improves tree shaking
@@ -276,6 +276,9 @@ circuits/ptau/
|
||||
!**/*.sol
|
||||
!**/*.circom
|
||||
|
||||
# Exception for specific private module setup script
|
||||
!app/scripts/setup-private-modules.cjs
|
||||
|
||||
# But exclude generated TypeScript declaration files
|
||||
**/*.d.ts
|
||||
!**/types/*.d.ts
|
||||
|
||||
50
.github/actions/clone-android-passport-reader/action.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Clone android-passport-reader
|
||||
description: "Clones the android-passport-reader repository if it does not exist"
|
||||
|
||||
inputs:
|
||||
working_directory:
|
||||
description: "Working directory path (where android/ subdirectory is located)"
|
||||
required: false
|
||||
default: "."
|
||||
selfxyz_internal_pat:
|
||||
description: "SELFXYZ internal repository PAT for private repository access"
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Clone android-passport-reader
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Check if PAT is available for private module cloning
|
||||
if [ -z "${{ inputs.selfxyz_internal_pat }}" ]; then
|
||||
echo "🔒 Skipping private module cloning (no PAT provided)"
|
||||
echo "ℹ️ This is expected for forked PRs - build will continue without private modules"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd "${{ inputs.working_directory }}"
|
||||
|
||||
if [ ! -d "android/android-passport-reader" ]; then
|
||||
echo "📦 Cloning android-passport-reader for build..."
|
||||
cd android
|
||||
|
||||
# Clone using PAT (embed temporarily, then scrub)
|
||||
if git clone --depth 1 --quiet "https://${{ inputs.selfxyz_internal_pat }}@github.com/selfxyz/android-passport-reader.git"; then
|
||||
echo "✅ android-passport-reader cloned successfully"
|
||||
# Immediately scrub the credential from remote URL for security
|
||||
git -C android-passport-reader remote set-url origin https://github.com/selfxyz/android-passport-reader.git || true
|
||||
else
|
||||
echo "❌ Failed to clone android-passport-reader"
|
||||
echo "Please ensure a valid SELFXYZ internal PAT is provided to this action"
|
||||
exit 1
|
||||
fi
|
||||
elif [ "$CI" = "true" ]; then
|
||||
echo "⚠️ android-passport-reader exists in CI - this is unexpected"
|
||||
echo "📁 Directory contents:"
|
||||
ls -la android/android-passport-reader/ || true
|
||||
else
|
||||
echo "📁 android-passport-reader already exists - preserving existing directory"
|
||||
echo "ℹ️ Local development environment detected - your changes are safe"
|
||||
fi
|
||||
4
.github/actions/mobile-setup/action.yml
vendored
@@ -80,6 +80,10 @@ runs:
|
||||
# Run mobile-specific installation
|
||||
yarn install-app:mobile-deploy
|
||||
|
||||
- name: Install Ruby dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
cd ${{ inputs.app_path }}
|
||||
# Install Ruby gems with bundler (respecting cache)
|
||||
echo "📦 Installing Ruby gems with strict lock file..."
|
||||
if ! bundle install --jobs 4 --retry 3; then
|
||||
|
||||
5
.github/workflows/mobile-ci.yml
vendored
@@ -448,6 +448,11 @@ jobs:
|
||||
run: |
|
||||
echo "Cache miss for built dependencies. Building now..."
|
||||
yarn workspace @selfxyz/mobile-app run build:deps
|
||||
- name: Clone android-passport-reader
|
||||
uses: ./.github/actions/clone-android-passport-reader
|
||||
with:
|
||||
working_directory: ${{ env.APP_PATH }}
|
||||
selfxyz_internal_pat: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
- name: Build Android (with AAPT2 symlink fix)
|
||||
run: yarn android:ci
|
||||
working-directory: ./app
|
||||
|
||||
25
.github/workflows/mobile-deploy.yml
vendored
@@ -223,8 +223,19 @@ jobs:
|
||||
|
||||
echo "✅ Lock files exist"
|
||||
|
||||
- name: Install Mobile Dependencies
|
||||
if: inputs.platform != 'android'
|
||||
- name: Install Mobile Dependencies (main repo)
|
||||
if: inputs.platform != 'android' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||
uses: ./.github/actions/mobile-setup
|
||||
with:
|
||||
app_path: ${{ env.APP_PATH }}
|
||||
node_version: ${{ env.NODE_VERSION }}
|
||||
ruby_version: ${{ env.RUBY_VERSION }}
|
||||
workspace: ${{ env.WORKSPACE }}
|
||||
env:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
|
||||
- name: Install Mobile Dependencies (forked PRs - no secrets)
|
||||
if: inputs.platform != 'android' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
|
||||
uses: ./.github/actions/mobile-setup
|
||||
with:
|
||||
app_path: ${{ env.APP_PATH }}
|
||||
@@ -774,6 +785,9 @@ jobs:
|
||||
node_version: ${{ env.NODE_VERSION }}
|
||||
ruby_version: ${{ env.RUBY_VERSION }}
|
||||
workspace: ${{ env.WORKSPACE }}
|
||||
env:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
PLATFORM: ${{ inputs.platform }}
|
||||
|
||||
# android specific steps
|
||||
|
||||
@@ -840,6 +854,13 @@ jobs:
|
||||
python -m pip install --upgrade pip
|
||||
pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client
|
||||
|
||||
- name: Clone android-passport-reader
|
||||
if: inputs.platform != 'ios'
|
||||
uses: ./.github/actions/clone-android-passport-reader
|
||||
with:
|
||||
working_directory: ${{ env.APP_PATH }}
|
||||
selfxyz_internal_pat: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
|
||||
- name: Build Dependencies (Android)
|
||||
if: inputs.platform != 'ios'
|
||||
run: |
|
||||
|
||||
61
.github/workflows/mobile-e2e.yml
vendored
@@ -27,8 +27,9 @@ on:
|
||||
- ".github/workflows/mobile-e2e.yml"
|
||||
|
||||
jobs:
|
||||
e2e-android:
|
||||
if: false # Temporarily disable Android E2E until emulator disk issue resolved
|
||||
android-build-test:
|
||||
# Currently build-only for Android with private repos. E2E steps are preserved but skipped (if: false).
|
||||
# To re-enable full E2E: change `if: false` to `if: true` on Maestro and emulator steps.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-android-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -65,20 +66,30 @@ jobs:
|
||||
- name: Toggle Yarn hardened mode for trusted PRs
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
|
||||
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
|
||||
- run: yarn install --immutable --silent
|
||||
- name: Install deps (internal PRs and protected branches)
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
run: yarn install --immutable --silent
|
||||
env:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
- name: Install deps (forked PRs - no secrets)
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
|
||||
run: yarn install --immutable --silent
|
||||
- name: Validate Maestro test file
|
||||
if: false # Skip for build-only test - keep logic for future E2E
|
||||
run: |
|
||||
[ -f app/tests/e2e/launch.android.flow.yaml ] || { echo "❌ Android E2E test file missing"; exit 1; }
|
||||
- name: Cache Maestro
|
||||
if: false # Skip for build-only test - keep logic for future E2E
|
||||
id: cache-maestro
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.maestro
|
||||
key: ${{ runner.os }}-maestro-${{ env.MAESTRO_VERSION }}
|
||||
- name: Install Maestro
|
||||
if: steps.cache-maestro.outputs.cache-hit != 'true'
|
||||
if: false # Skip for build-only test - keep logic for future E2E
|
||||
run: curl -Ls "https://get.maestro.mobile.dev" | bash
|
||||
- name: Add Maestro to path
|
||||
if: false # Skip for build-only test - keep logic for future E2E
|
||||
run: echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
|
||||
- name: Setup Java environment
|
||||
uses: actions/setup-java@v4
|
||||
@@ -98,13 +109,44 @@ jobs:
|
||||
echo "Building dependencies..."
|
||||
yarn workspace @selfxyz/mobile-app run build:deps --silent || { echo "❌ Dependency build failed"; exit 1; }
|
||||
echo "✅ Dependencies built successfully"
|
||||
- name: Clone android-passport-reader
|
||||
uses: ./.github/actions/clone-android-passport-reader
|
||||
with:
|
||||
working_directory: app
|
||||
selfxyz_internal_pat: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
- name: Build Android APK
|
||||
run: |
|
||||
echo "Building Android APK..."
|
||||
chmod +x app/android/gradlew
|
||||
(cd app/android && ./gradlew assembleDebug --quiet --parallel --build-cache --no-configuration-cache) || { echo "❌ Android build failed"; exit 1; }
|
||||
echo "✅ Android build succeeded"
|
||||
- name: Verify APK and android-passport-reader integration
|
||||
run: |
|
||||
echo "🔍 Verifying build artifacts..."
|
||||
APK_PATH="app/android/app/build/outputs/apk/debug/app-debug.apk"
|
||||
[ -f "$APK_PATH" ] || { echo "❌ APK not found at $APK_PATH"; exit 1; }
|
||||
echo "✅ APK found at $APK_PATH"
|
||||
|
||||
# Check APK size
|
||||
APK_SIZE=$(stat -f%z "$APK_PATH" 2>/dev/null || stat -c%s "$APK_PATH" 2>/dev/null || echo "unknown")
|
||||
echo "📱 APK size: $APK_SIZE bytes"
|
||||
|
||||
# Verify android-passport-reader was properly integrated (skip for forks)
|
||||
if [ -z "${SELFXYZ_INTERNAL_REPO_PAT:-}" ]; then
|
||||
echo "🔕 No PAT available — skipping private module verification"
|
||||
elif [ -d "app/android/android-passport-reader" ]; then
|
||||
echo "✅ android-passport-reader directory exists"
|
||||
echo "📁 android-passport-reader contents:"
|
||||
ls -la app/android/android-passport-reader/ | head -10
|
||||
else
|
||||
echo "❌ android-passport-reader directory not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🎉 Build verification completed successfully!"
|
||||
echo "ℹ️ Emulator testing is temporarily disabled - build testing only"
|
||||
- name: Install and Test on Android
|
||||
if: false # Skip emulator/E2E for build-only test - keep logic for future E2E
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: ${{ env.ANDROID_API_LEVEL }}
|
||||
@@ -127,7 +169,7 @@ jobs:
|
||||
export MAESTRO_DRIVER_STARTUP_TIMEOUT=180000
|
||||
maestro test app/tests/e2e/launch.android.flow.yaml --format junit --output app/maestro-results.xml
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
if: false # Skip for build-only test - keep logic for future E2E
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: maestro-results-android
|
||||
@@ -177,7 +219,14 @@ jobs:
|
||||
- name: Toggle Yarn hardened mode for trusted PRs
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
|
||||
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
|
||||
- run: yarn install --immutable --silent
|
||||
- name: Install deps (internal PRs and protected branches)
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
|
||||
run: yarn install --immutable --silent
|
||||
env:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
- name: Install deps (forked PRs - no secrets)
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
|
||||
run: yarn install --immutable --silent
|
||||
- name: Validate Maestro test file
|
||||
run: |
|
||||
[ -f app/tests/e2e/launch.ios.flow.yaml ] || { echo "❌ iOS E2E test file missing"; exit 1; }
|
||||
|
||||
3
.gitignore
vendored
@@ -17,3 +17,6 @@ mobile-sdk-alpha-ci.tgz
|
||||
**/mobile-sdk-alpha-*.tgz
|
||||
/tmp/mobile-sdk-alpha*.tgz
|
||||
dataInput.d.ts
|
||||
|
||||
# Private Android modules (cloned at build time)
|
||||
app/android/android-passport-reader/
|
||||
|
||||
@@ -25,7 +25,7 @@ GEM
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1159.0)
|
||||
aws-partitions (1.1161.0)
|
||||
aws-sdk-core (3.232.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
@@ -242,7 +242,7 @@ GEM
|
||||
naturally (2.3.0)
|
||||
netrc (0.11.0)
|
||||
nkf (0.2.0)
|
||||
nokogiri (1.18.9)
|
||||
nokogiri (1.18.10)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
optparse (0.6.0)
|
||||
|
||||
10
app/android/android-passport-reader/.gitignore
vendored
@@ -1,10 +0,0 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
@@ -1,56 +0,0 @@
|
||||
# Passport Reader
|
||||
|
||||
Sample project to read Passports using MRZ or manual entry. Currently I am using ML KIT for the OCR.
|
||||
|
||||
I don't read the the whole MRZ as ML KIT for now it's unable to read it (it's struggling with "<<<"), but I use it to read the second line and after that use a regular expression to match the rigth format.
|
||||
|
||||
You can use the example images stored under `examples` to test the application or download any sample passport document from https://www.consilium.europa.eu/prado/EN/prado-start-page.html
|
||||
|
||||

|
||||
|
||||
|
||||
This project is based in the information and tutorials found in
|
||||
|
||||
- https://developer.android.com/reference/android/hardware/camera2/package-summary
|
||||
- https://github.com/tananaev/passport-reader/blob/master/app/build.gradle
|
||||
- https://techblog.bozho.net/how-to-read-your-passport-with-android/
|
||||
- https://github.com/mercuriete/android-mrz-reader
|
||||
- https://en.wikipedia.org/wiki/Machine-readable_passport
|
||||
- https://jmrtd.org/about.shtml
|
||||
- https://firebase.google.com/docs/ml-kit/recognize-text
|
||||
- https://github.com/tananaev/passport-reader
|
||||
|
||||
|
||||
## Build & Run
|
||||
|
||||
```
|
||||
1. Clone Repository
|
||||
2. Open with Android Studio
|
||||
3. Configure Android SDK
|
||||
4. Launch application
|
||||
```
|
||||
|
||||
## OCR
|
||||
|
||||
You must put your phone horizontal when you try to read the passports MRZ.
|
||||
|
||||
This is are examples of how the app performs.
|
||||
https://youtu.be/ZmRl_-3RH2U (Full read)
|
||||
https://youtu.be/kuIkZ1ZktCk (Just OCR)
|
||||
|
||||
## Country Signing Certificate Authority
|
||||
|
||||
For the CSCA certificates the example points to the Master List provided by the spanish government. You should point it to whatever list your country has.
|
||||
-https://www.dnielectronico.es/PortalDNIe/PRF1_Cons02.action?pag=REF_1093&id_menu=55
|
||||
|
||||
You can find some information in
|
||||
-https://jmrtd.org/certificates.shtml
|
||||
|
||||
## License
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,100 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
compileSdkVersion 35
|
||||
defaultConfig {
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 35
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
// multidex not needed for libraries (and native multidex is default on API 21+)
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
|
||||
// compileOptions {
|
||||
// sourceCompatibility JavaVersion.VERSION_11
|
||||
// targetCompatibility JavaVersion.VERSION_11
|
||||
// }
|
||||
// kotlinOptions {
|
||||
// jvmTarget = "11"
|
||||
// }
|
||||
|
||||
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/proguard/androidx-annotations.pro'
|
||||
exclude 'META-INF/androidx.exifinterface_exifinterface.version'
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
namespace 'example.jllarraz.com.passportreader'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
// ML Kit dependencies
|
||||
implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.2'
|
||||
|
||||
//NFC Passport
|
||||
implementation 'org.jmrtd:jmrtd:0.7.35'
|
||||
// implementation 'org.jmrtd:jmrtd:0.8.1'
|
||||
implementation 'com.madgag.spongycastle:prov:1.58.0.0'
|
||||
implementation 'net.sf.scuba:scuba-sc-android:0.0.23'
|
||||
implementation ('org.ejbca.cvc:cert-cvc:1.4.13'){
|
||||
exclude group: 'org.bouncycastle', module: 'bcprov-jdk15on'
|
||||
}
|
||||
|
||||
//WSQ
|
||||
implementation 'com.github.mhshams:jnbis:2.0.2'
|
||||
|
||||
//Input data Validator
|
||||
implementation 'com.mobsandgeeks:android-saripaar:2.0.3'
|
||||
|
||||
//DatatypeConverter
|
||||
implementation 'commons-codec:commons-codec:1.13'
|
||||
|
||||
//Camera
|
||||
implementation "com.github.fotoapparat:fotoapparat:2.7.0"
|
||||
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
|
||||
//RX
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.19'
|
||||
|
||||
//Annotations
|
||||
implementation "org.androidannotations:androidannotations-api:4.4.0"
|
||||
|
||||
//OpenLDAP
|
||||
//implementation 'com.unboundid:unboundid-ldapsdk:5.0.1@jar'
|
||||
|
||||
//OKHttp
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.4.0'
|
||||
implementation "com.squareup.okhttp3:okhttp-urlconnection:4.4.0"
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:4.4.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'
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -1,26 +0,0 @@
|
||||
package example.jllarraz.com.passportreader;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
assertEquals("example.jllarraz.com.passportreader", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
<uses-feature android:name="android.hardware.camera.flash" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera"/>
|
||||
<uses-feature android:name="android.hardware.screen.landscape"/>
|
||||
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-feature android:name="android.hardware.nfc" android:required="false" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:largeHeap="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:name="androidx.multidex.MultiDexApplication">
|
||||
<meta-data
|
||||
android:name="com.google.mlkit.vision.DEPENDENCIES"
|
||||
android:value="ocr" />
|
||||
|
||||
<activity android:name=".ui.activities.CameraActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar.FullScreen"
|
||||
android:windowSoftInputMode="stateAlwaysHidden" />
|
||||
|
||||
<activity android:name=".ui.activities.NfcActivity"
|
||||
android:keepScreenOn="true" />
|
||||
|
||||
<activity android:name=".ui.activities.SelectionActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,11 +0,0 @@
|
||||
^P<[<A-Z]*
|
||||
^ID[<A-Z0-9]*
|
||||
^[<A-Z0-9]{9}[0-9][A-Z]{3}
|
||||
[A-Z]{3}
|
||||
[0-9]{7}[<MF][0-9]{7}
|
||||
[0-9]{7}[<A-Z0-9]{14}[<0-9]{2}$
|
||||
[0-9]{7}[<MF]
|
||||
^V<[<A-Z]*$
|
||||
^VA[<A-Z]*$
|
||||
^VC[<A-Z]*$
|
||||
^VD[<A-Z]*$
|
||||
@@ -1,12 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.common
|
||||
|
||||
object IntentData {
|
||||
|
||||
const val KEY_MRZ_INFO = "KEY_MRZ_INFO"
|
||||
const val KEY_PASSPORT = "KEY_PASSPORT"
|
||||
const val KEY_IMAGE = "KEY_IMAGE"
|
||||
|
||||
@JvmStatic
|
||||
fun getKeyMrzInfo(): String = KEY_MRZ_INFO
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.common
|
||||
|
||||
object PreferencesKeys {
|
||||
val KEY_OCR_ENGINE_MODE = "KEY_OCR_ENGINE_MODE"
|
||||
val KEY_CONTINUOUS_PREVIEW = "KEY_CONTINUOUS_PREVIEW"
|
||||
|
||||
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.data
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.Date
|
||||
|
||||
class AdditionalDocumentDetails : Parcelable {
|
||||
|
||||
var endorsementsAndObservations: String? = null
|
||||
var dateAndTimeOfPersonalization: String? = null
|
||||
var dateOfIssue: String? = null
|
||||
var imageOfFront: Bitmap? = null
|
||||
var imageOfRear: Bitmap? = null
|
||||
var issuingAuthority: String? = null
|
||||
var namesOfOtherPersons: List<String>? = null
|
||||
var personalizationSystemSerialNumber: String? = null
|
||||
var taxOrExitRequirements: String? = null
|
||||
var tag: Int = 0
|
||||
var tagPresenceList: List<Int>? = null
|
||||
|
||||
|
||||
constructor() {
|
||||
namesOfOtherPersons = ArrayList()
|
||||
tagPresenceList = ArrayList()
|
||||
}
|
||||
|
||||
constructor(`in`: Parcel) {
|
||||
|
||||
namesOfOtherPersons = ArrayList()
|
||||
tagPresenceList = ArrayList()
|
||||
|
||||
this.endorsementsAndObservations = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.dateAndTimeOfPersonalization = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.dateOfIssue = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
|
||||
this.imageOfFront = if (`in`.readInt() == 1) `in`.readParcelable(Bitmap::class.java.classLoader) else null
|
||||
this.imageOfRear = if (`in`.readInt() == 1) `in`.readParcelable(Bitmap::class.java.classLoader) else null
|
||||
this.issuingAuthority = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
|
||||
if (`in`.readInt() == 1) {
|
||||
`in`.readList(namesOfOtherPersons!!, String::class.java.classLoader)
|
||||
}
|
||||
|
||||
this.personalizationSystemSerialNumber = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.taxOrExitRequirements = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
|
||||
tag = `in`.readInt()
|
||||
if (`in`.readInt() == 1) {
|
||||
`in`.readList(tagPresenceList!!, Int::class.java.classLoader)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeInt(if (endorsementsAndObservations != null) 1 else 0)
|
||||
if (endorsementsAndObservations != null) {
|
||||
dest.writeString(endorsementsAndObservations)
|
||||
}
|
||||
|
||||
dest.writeInt(if (dateAndTimeOfPersonalization != null) 1 else 0)
|
||||
if (dateAndTimeOfPersonalization != null) {
|
||||
dest.writeString(dateAndTimeOfPersonalization)
|
||||
}
|
||||
|
||||
dest.writeInt(if (dateOfIssue != null) 1 else 0)
|
||||
if (dateOfIssue != null) {
|
||||
dest.writeString(dateOfIssue)
|
||||
}
|
||||
|
||||
dest.writeInt(if (imageOfFront != null) 1 else 0)
|
||||
if (imageOfFront != null) {
|
||||
dest.writeParcelable(imageOfFront, flags)
|
||||
}
|
||||
|
||||
dest.writeInt(if (imageOfRear != null) 1 else 0)
|
||||
if (imageOfRear != null) {
|
||||
dest.writeParcelable(imageOfRear, flags)
|
||||
}
|
||||
|
||||
dest.writeInt(if (issuingAuthority != null) 1 else 0)
|
||||
if (issuingAuthority != null) {
|
||||
dest.writeString(issuingAuthority)
|
||||
}
|
||||
|
||||
dest.writeInt(if (namesOfOtherPersons != null) 1 else 0)
|
||||
if (namesOfOtherPersons != null) {
|
||||
dest.writeList(namesOfOtherPersons)
|
||||
}
|
||||
|
||||
dest.writeInt(if (personalizationSystemSerialNumber != null) 1 else 0)
|
||||
if (personalizationSystemSerialNumber != null) {
|
||||
dest.writeString(personalizationSystemSerialNumber)
|
||||
}
|
||||
|
||||
dest.writeInt(if (taxOrExitRequirements != null) 1 else 0)
|
||||
if (taxOrExitRequirements != null) {
|
||||
dest.writeString(taxOrExitRequirements)
|
||||
}
|
||||
|
||||
dest.writeInt(tag)
|
||||
dest.writeInt(if (tagPresenceList != null) 1 else 0)
|
||||
if (tagPresenceList != null) {
|
||||
dest.writeList(tagPresenceList)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<*> = object : Parcelable.Creator<AdditionalDocumentDetails> {
|
||||
override fun createFromParcel(pc: Parcel): AdditionalDocumentDetails {
|
||||
return AdditionalDocumentDetails(pc)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<AdditionalDocumentDetails?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.data
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.Date
|
||||
|
||||
class AdditionalPersonDetails : Parcelable {
|
||||
|
||||
var custodyInformation: String? = null
|
||||
var fullDateOfBirth: String? = null
|
||||
var nameOfHolder: String? = null
|
||||
var otherNames: List<String>? = null
|
||||
var otherValidTDNumbers: List<String>? = null
|
||||
var permanentAddress: List<String>? = null
|
||||
var personalNumber: String? = null
|
||||
var personalSummary: String? = null
|
||||
var placeOfBirth: List<String>? = null
|
||||
var profession: String? = null
|
||||
var proofOfCitizenship: ByteArray? = null
|
||||
var tag: Int = 0
|
||||
var tagPresenceList: List<Int>? = null
|
||||
var telephone: String? = null
|
||||
var title: String? = null
|
||||
|
||||
constructor() {
|
||||
otherNames = ArrayList()
|
||||
otherValidTDNumbers = ArrayList()
|
||||
permanentAddress = ArrayList()
|
||||
placeOfBirth = ArrayList()
|
||||
tagPresenceList = ArrayList()
|
||||
}
|
||||
|
||||
constructor(`in`: Parcel) {
|
||||
|
||||
otherNames = ArrayList()
|
||||
otherValidTDNumbers = ArrayList()
|
||||
permanentAddress = ArrayList()
|
||||
placeOfBirth = ArrayList()
|
||||
tagPresenceList = ArrayList()
|
||||
|
||||
this.custodyInformation = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.fullDateOfBirth = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.nameOfHolder = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
if (`in`.readInt() == 1) {
|
||||
`in`.readList(otherNames!!, String::class.java.classLoader)
|
||||
}
|
||||
if (`in`.readInt() == 1) {
|
||||
`in`.readList(otherValidTDNumbers!!, String::class.java.classLoader)
|
||||
}
|
||||
if (`in`.readInt() == 1) {
|
||||
`in`.readList(permanentAddress!!, String::class.java.classLoader)
|
||||
}
|
||||
this.personalNumber = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.personalSummary = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
if (`in`.readInt() == 1) {
|
||||
`in`.readList(placeOfBirth!!, String::class.java.classLoader)
|
||||
}
|
||||
this.profession = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
if (`in`.readInt() == 1) {
|
||||
this.proofOfCitizenship = ByteArray(`in`.readInt())
|
||||
`in`.readByteArray(this.proofOfCitizenship!!)
|
||||
}
|
||||
tag = `in`.readInt()
|
||||
if (`in`.readInt() == 1) {
|
||||
`in`.readList(tagPresenceList!!, Int::class.java.classLoader)
|
||||
}
|
||||
|
||||
this.telephone = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.title = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeInt(if (custodyInformation != null) 1 else 0)
|
||||
if (custodyInformation != null) {
|
||||
dest.writeString(custodyInformation)
|
||||
}
|
||||
|
||||
dest.writeInt(if (fullDateOfBirth != null) 1 else 0)
|
||||
if (fullDateOfBirth != null) {
|
||||
dest.writeString(fullDateOfBirth)
|
||||
}
|
||||
|
||||
|
||||
dest.writeInt(if (nameOfHolder != null) 1 else 0)
|
||||
if (nameOfHolder != null) {
|
||||
dest.writeString(nameOfHolder)
|
||||
}
|
||||
dest.writeInt(if (otherNames != null) 1 else 0)
|
||||
if (otherNames != null) {
|
||||
dest.writeList(otherNames)
|
||||
}
|
||||
|
||||
dest.writeInt(if (otherValidTDNumbers != null) 1 else 0)
|
||||
if (otherValidTDNumbers != null) {
|
||||
dest.writeList(otherValidTDNumbers)
|
||||
}
|
||||
|
||||
dest.writeInt(if (permanentAddress != null) 1 else 0)
|
||||
if (permanentAddress != null) {
|
||||
dest.writeList(permanentAddress)
|
||||
}
|
||||
|
||||
dest.writeInt(if (personalNumber != null) 1 else 0)
|
||||
if (personalNumber != null) {
|
||||
dest.writeString(personalNumber)
|
||||
}
|
||||
|
||||
dest.writeInt(if (personalSummary != null) 1 else 0)
|
||||
if (personalSummary != null) {
|
||||
dest.writeString(personalSummary)
|
||||
}
|
||||
|
||||
dest.writeInt(if (placeOfBirth != null) 1 else 0)
|
||||
if (placeOfBirth != null) {
|
||||
dest.writeList(placeOfBirth)
|
||||
}
|
||||
|
||||
dest.writeInt(if (profession != null) 1 else 0)
|
||||
if (profession != null) {
|
||||
dest.writeString(profession)
|
||||
}
|
||||
|
||||
dest.writeInt(if (proofOfCitizenship != null) 1 else 0)
|
||||
if (proofOfCitizenship != null) {
|
||||
dest.writeInt(proofOfCitizenship!!.size)
|
||||
dest.writeByteArray(proofOfCitizenship)
|
||||
}
|
||||
|
||||
dest.writeInt(tag)
|
||||
dest.writeInt(if (tagPresenceList != null) 1 else 0)
|
||||
if (tagPresenceList != null) {
|
||||
dest.writeList(tagPresenceList)
|
||||
}
|
||||
|
||||
dest.writeInt(if (telephone != null) 1 else 0)
|
||||
if (telephone != null) {
|
||||
dest.writeString(telephone)
|
||||
}
|
||||
|
||||
dest.writeInt(if (title != null) 1 else 0)
|
||||
if (title != null) {
|
||||
dest.writeString(title)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<*> = object : Parcelable.Creator<AdditionalPersonDetails> {
|
||||
override fun createFromParcel(pc: Parcel): AdditionalPersonDetails {
|
||||
return AdditionalPersonDetails(pc)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<AdditionalPersonDetails?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.data
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
import org.jmrtd.FeatureStatus
|
||||
import org.jmrtd.VerificationStatus
|
||||
import org.jmrtd.lds.SODFile
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.HashMap
|
||||
|
||||
class Passport : Parcelable {
|
||||
|
||||
var sodFile: SODFile? = null
|
||||
var face: Bitmap? = null
|
||||
var portrait: Bitmap? = null
|
||||
var signature: Bitmap? = null
|
||||
var fingerprints: List<Bitmap>? = null
|
||||
var personDetails: PersonDetails? = null
|
||||
var additionalPersonDetails: AdditionalPersonDetails? = null
|
||||
var additionalDocumentDetails: AdditionalDocumentDetails? = null
|
||||
var featureStatus: FeatureStatus? = null
|
||||
var verificationStatus: VerificationStatus? = null
|
||||
|
||||
constructor(`in`: Parcel) {
|
||||
|
||||
|
||||
fingerprints = ArrayList()
|
||||
this.face = if (`in`.readInt() == 1) `in`.readParcelable(Bitmap::class.java.classLoader) else null
|
||||
this.portrait = if (`in`.readInt() == 1) `in`.readParcelable(Bitmap::class.java.classLoader) else null
|
||||
this.personDetails = if (`in`.readInt() == 1) `in`.readParcelable(PersonDetails::class.java.classLoader) else null
|
||||
this.additionalPersonDetails = if (`in`.readInt() == 1) `in`.readParcelable(AdditionalPersonDetails::class.java.classLoader) else null
|
||||
|
||||
if (`in`.readInt() == 1) {
|
||||
`in`.readList(fingerprints!!, Bitmap::class.java.classLoader)
|
||||
}
|
||||
|
||||
this.signature = if (`in`.readInt() == 1) `in`.readParcelable(Bitmap::class.java.classLoader) else null
|
||||
this.additionalDocumentDetails = if (`in`.readInt() == 1) `in`.readParcelable(AdditionalDocumentDetails::class.java.classLoader) else null
|
||||
if (`in`.readInt() == 1) {
|
||||
sodFile = `in`.readSerializable() as SODFile
|
||||
}
|
||||
|
||||
if (`in`.readInt() == 1) {
|
||||
featureStatus = `in`.readParcelable(FeatureStatus::class.java.classLoader)
|
||||
}
|
||||
|
||||
if (`in`.readInt() == 1) {
|
||||
featureStatus = `in`.readParcelable(FeatureStatus::class.java.classLoader)
|
||||
}
|
||||
|
||||
if (`in`.readInt() == 1) {
|
||||
verificationStatus = `in`.readParcelable(VerificationStatus::class.java.classLoader)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
constructor() {
|
||||
fingerprints = ArrayList()
|
||||
featureStatus = FeatureStatus()
|
||||
verificationStatus = VerificationStatus()
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeInt(if (face != null) 1 else 0)
|
||||
if (face != null) {
|
||||
dest.writeParcelable(face, flags)
|
||||
}
|
||||
|
||||
dest.writeInt(if (portrait != null) 1 else 0)
|
||||
if (portrait != null) {
|
||||
dest.writeParcelable(portrait, flags)
|
||||
}
|
||||
|
||||
dest.writeInt(if (personDetails != null) 1 else 0)
|
||||
if (personDetails != null) {
|
||||
dest.writeParcelable(personDetails, flags)
|
||||
}
|
||||
|
||||
dest.writeInt(if (additionalPersonDetails != null) 1 else 0)
|
||||
if (additionalPersonDetails != null) {
|
||||
dest.writeParcelable(additionalPersonDetails, flags)
|
||||
}
|
||||
|
||||
dest.writeInt(if (fingerprints != null) 1 else 0)
|
||||
if (fingerprints != null) {
|
||||
dest.writeList(fingerprints)
|
||||
}
|
||||
|
||||
dest.writeInt(if (signature != null) 1 else 0)
|
||||
if (signature != null) {
|
||||
dest.writeParcelable(signature, flags)
|
||||
}
|
||||
|
||||
dest.writeInt(if (additionalDocumentDetails != null) 1 else 0)
|
||||
if (additionalDocumentDetails != null) {
|
||||
dest.writeParcelable(additionalDocumentDetails, flags)
|
||||
}
|
||||
|
||||
dest.writeInt(if (sodFile != null) 1 else 0)
|
||||
if (sodFile != null) {
|
||||
dest.writeSerializable(sodFile)
|
||||
}
|
||||
|
||||
dest.writeInt(if (featureStatus != null) 1 else 0)
|
||||
if (featureStatus != null) {
|
||||
dest.writeParcelable(featureStatus, flags)
|
||||
}
|
||||
|
||||
dest.writeInt(if (verificationStatus != null) 1 else 0)
|
||||
if (verificationStatus != null) {
|
||||
dest.writeParcelable(verificationStatus, flags)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<*> = object : Parcelable.Creator<Passport> {
|
||||
override fun createFromParcel(pc: Parcel): Passport {
|
||||
return Passport(pc)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<Passport?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.data
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
import net.sf.scuba.data.Gender
|
||||
|
||||
import org.jmrtd.lds.icao.MRZInfo
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.Date
|
||||
|
||||
class PersonDetails : Parcelable {
|
||||
|
||||
|
||||
var documentCode: String? = null
|
||||
var issuingState: String? = null
|
||||
var primaryIdentifier: String? = null
|
||||
var secondaryIdentifier: String? = null
|
||||
var nationality: String? = null
|
||||
var documentNumber: String? = null
|
||||
var dateOfBirth: String? = null
|
||||
var dateOfExpiry: String? = null
|
||||
var optionalData1: String? = null /* NOTE: holds personal number for some issuing states (e.g. NL), but is used to hold (part of) document number for others. */
|
||||
var optionalData2: String? = null
|
||||
var gender: Gender? = Gender.UNKNOWN
|
||||
|
||||
constructor()
|
||||
|
||||
constructor(`in`: Parcel) {
|
||||
|
||||
|
||||
this.documentCode = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.issuingState = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.primaryIdentifier = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.secondaryIdentifier = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.nationality = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.documentNumber = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.dateOfBirth = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.dateOfExpiry = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.optionalData1 = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.optionalData2 = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.gender = if (`in`.readInt() == 1) Gender.valueOf(`in`.readString()!!) else Gender.UNKNOWN
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeInt(if (documentCode != null) 1 else 0)
|
||||
if (documentCode != null) {
|
||||
dest.writeString(documentCode)
|
||||
}
|
||||
|
||||
dest.writeInt(if (issuingState != null) 1 else 0)
|
||||
if (issuingState != null) {
|
||||
dest.writeString(issuingState)
|
||||
}
|
||||
|
||||
dest.writeInt(if (primaryIdentifier != null) 1 else 0)
|
||||
if (primaryIdentifier != null) {
|
||||
dest.writeString(primaryIdentifier)
|
||||
}
|
||||
|
||||
dest.writeInt(if (secondaryIdentifier != null) 1 else 0)
|
||||
if (secondaryIdentifier != null) {
|
||||
dest.writeString(secondaryIdentifier)
|
||||
}
|
||||
|
||||
dest.writeInt(if (nationality != null) 1 else 0)
|
||||
if (nationality != null) {
|
||||
dest.writeString(nationality)
|
||||
}
|
||||
|
||||
dest.writeInt(if (documentNumber != null) 1 else 0)
|
||||
if (documentNumber != null) {
|
||||
dest.writeString(documentNumber)
|
||||
}
|
||||
|
||||
dest.writeInt(if (dateOfBirth != null) 1 else 0)
|
||||
if (dateOfBirth != null) {
|
||||
dest.writeString(dateOfBirth)
|
||||
}
|
||||
|
||||
dest.writeInt(if (dateOfExpiry != null) 1 else 0)
|
||||
if (dateOfExpiry != null) {
|
||||
dest.writeString(dateOfExpiry)
|
||||
}
|
||||
|
||||
dest.writeInt(if (optionalData1 != null) 1 else 0)
|
||||
if (optionalData1 != null) {
|
||||
dest.writeString(optionalData1)
|
||||
}
|
||||
|
||||
dest.writeInt(if (optionalData2 != null) 1 else 0)
|
||||
if (optionalData2 != null) {
|
||||
dest.writeString(optionalData2)
|
||||
}
|
||||
|
||||
dest.writeInt(if (gender != null) 1 else 0)
|
||||
if (optionalData2 != null) {
|
||||
dest.writeString(gender!!.name)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<*> = object : Parcelable.Creator<PersonDetails> {
|
||||
override fun createFromParcel(pc: Parcel): PersonDetails {
|
||||
return PersonDetails(pc)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<PersonDetails?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package example.jllarraz.com.passportreader.mlkit
|
||||
|
||||
/** Describing a frame info. */
|
||||
class FrameMetadata private constructor(val width: Int, val height: Int, val rotation: Int, val cameraFacing: Int) {
|
||||
|
||||
/** Builder of [FrameMetadata]. */
|
||||
class Builder {
|
||||
|
||||
private var width: Int = 0
|
||||
private var height: Int = 0
|
||||
private var rotation: Int = 0
|
||||
private var cameraFacing: Int = 0
|
||||
|
||||
fun setWidth(width: Int): Builder {
|
||||
this.width = width
|
||||
return this
|
||||
}
|
||||
|
||||
fun setHeight(height: Int): Builder {
|
||||
this.height = height
|
||||
return this
|
||||
}
|
||||
|
||||
fun setRotation(rotation: Int): Builder {
|
||||
this.rotation = rotation
|
||||
return this
|
||||
}
|
||||
|
||||
fun setCameraFacing(facing: Int): Builder {
|
||||
cameraFacing = facing
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): FrameMetadata {
|
||||
return FrameMetadata(width, height, rotation, cameraFacing)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package example.jllarraz.com.passportreader.mlkit
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
|
||||
|
||||
import java.util.HashSet
|
||||
|
||||
/**
|
||||
* A view which renders a series of custom graphics to be overlayed on top of an associated preview
|
||||
* (i.e., the camera preview). The creator can add graphics objects, update the objects, and remove
|
||||
* them, triggering the appropriate drawing and invalidation within the view.
|
||||
*
|
||||
*
|
||||
* Supports scaling and mirroring of the graphics relative the camera's preview properties. The
|
||||
* idea is that detection items are expressed in terms of a preview size, but need to be scaled up
|
||||
* to the full view size, and also mirrored in the case of the front-facing camera.
|
||||
*
|
||||
*
|
||||
* Associated [Graphic] items should use the following methods to convert to view
|
||||
* coordinates for the graphics that are drawn:
|
||||
*
|
||||
*
|
||||
* 1. [Graphic.scaleX] and [Graphic.scaleY] adjust the size of the
|
||||
* supplied value from the preview scale to the view scale.
|
||||
* 1. [Graphic.translateX] and [Graphic.translateY] adjust the
|
||||
* coordinate from the preview's coordinate system to the view coordinate system.
|
||||
*
|
||||
*/
|
||||
class GraphicOverlay(context: Context, attrs: AttributeSet) : View(context, attrs) {
|
||||
private val mLock = Any()
|
||||
private var mPreviewWidth: Int = 0
|
||||
private var mWidthScaleFactor = 1.0f
|
||||
private var mPreviewHeight: Int = 0
|
||||
private var mHeightScaleFactor = 1.0f
|
||||
private var mIsCameraFacing:Boolean = false
|
||||
private val mGraphics = HashSet<Graphic>()
|
||||
|
||||
/**
|
||||
* Base class for a custom graphics object to be rendered within the graphic overlay. Subclass
|
||||
* this and implement the [Graphic.draw] method to define the
|
||||
* graphics element. Add instances to the overlay using [GraphicOverlay.add].
|
||||
*/
|
||||
abstract class Graphic(private val mOverlay: GraphicOverlay) {
|
||||
|
||||
/**
|
||||
* Draw the graphic on the supplied canvas. Drawing should use the following methods to
|
||||
* convert to view coordinates for the graphics that are drawn:
|
||||
*
|
||||
* 1. [Graphic.scaleX] and [Graphic.scaleY] adjust the size of
|
||||
* the supplied value from the preview scale to the view scale.
|
||||
* 1. [Graphic.translateX] and [Graphic.translateY] adjust the
|
||||
* coordinate from the preview's coordinate system to the view coordinate system.
|
||||
*
|
||||
*
|
||||
* @param canvas drawing canvas
|
||||
*/
|
||||
abstract fun draw(canvas: Canvas)
|
||||
|
||||
/**
|
||||
* Adjusts a horizontal value of the supplied value from the preview scale to the view
|
||||
* scale.
|
||||
*/
|
||||
fun scaleX(horizontal: Float): Float {
|
||||
return horizontal * mOverlay.mWidthScaleFactor
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts a vertical value of the supplied value from the preview scale to the view scale.
|
||||
*/
|
||||
fun scaleY(vertical: Float): Float {
|
||||
return vertical * mOverlay.mHeightScaleFactor
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the x coordinate from the preview's coordinate system to the view coordinate
|
||||
* system.
|
||||
*/
|
||||
fun translateX(x: Float): Float {
|
||||
return if (mOverlay.mIsCameraFacing == true) {
|
||||
mOverlay.width - scaleX(x)
|
||||
} else {
|
||||
scaleX(x)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the y coordinate from the preview's coordinate system to the view coordinate
|
||||
* system.
|
||||
*/
|
||||
fun translateY(y: Float): Float {
|
||||
return scaleY(y)
|
||||
}
|
||||
|
||||
fun postInvalidate() {
|
||||
mOverlay.postInvalidate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all graphics from the overlay.
|
||||
*/
|
||||
fun clear() {
|
||||
synchronized(mLock) {
|
||||
mGraphics.clear()
|
||||
}
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a graphic to the overlay.
|
||||
*/
|
||||
fun add(graphic: Graphic) {
|
||||
synchronized(mLock) {
|
||||
mGraphics.add(graphic)
|
||||
}
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a graphic from the overlay.
|
||||
*/
|
||||
fun remove(graphic: Graphic) {
|
||||
synchronized(mLock) {
|
||||
mGraphics.remove(graphic)
|
||||
}
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the camera attributes for size and facing direction, which informs how to transform
|
||||
* image coordinates later.
|
||||
*/
|
||||
fun setCameraInfo(previewWidth: Int, previewHeight: Int, isCameraFacing: Boolean) {
|
||||
synchronized(mLock) {
|
||||
mPreviewWidth = previewWidth
|
||||
mPreviewHeight = previewHeight
|
||||
mIsCameraFacing = isCameraFacing
|
||||
}
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the overlay with its associated graphic objects.
|
||||
*/
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
|
||||
synchronized(mLock) {
|
||||
if (mPreviewWidth != 0 && mPreviewHeight != 0) {
|
||||
mWidthScaleFactor = canvas.width.toFloat() / mPreviewWidth.toFloat()
|
||||
mHeightScaleFactor = canvas.height.toFloat() / mPreviewHeight.toFloat()
|
||||
}
|
||||
|
||||
for (graphic in mGraphics) {
|
||||
graphic.draw(canvas)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package example.jllarraz.com.passportreader.mlkit
|
||||
|
||||
import android.util.Log
|
||||
|
||||
import com.google.android.gms.tasks.Task
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
import com.google.mlkit.vision.text.Text
|
||||
import com.google.mlkit.vision.text.TextRecognition
|
||||
import com.google.mlkit.vision.text.TextRecognizer
|
||||
import com.google.mlkit.vision.text.latin.TextRecognizerOptions
|
||||
|
||||
import java.io.IOException
|
||||
|
||||
|
||||
/**
|
||||
* A very simple Processor which receives detected TextBlocks and adds them to the overlay
|
||||
* as OcrGraphics.
|
||||
*/
|
||||
class OcrMrzDetectorProcessor() : VisionProcessorBase<Text>() {
|
||||
|
||||
private val detector: TextRecognizer
|
||||
|
||||
init {
|
||||
detector = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
|
||||
|
||||
}
|
||||
override fun stop() {
|
||||
try {
|
||||
detector.close()
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Exception thrown while trying to close Text Detector: $e")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun detectInImage(image: InputImage): Task<Text> {
|
||||
return detector.process(image)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = OcrMrzDetectorProcessor::class.java.simpleName
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package example.jllarraz.com.passportreader.mlkit
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.media.Image
|
||||
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
import io.fotoapparat.preview.Frame
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/** An inferface to process the images with different ML Kit detectors and custom image models. */
|
||||
interface VisionImageProcessor<T> {
|
||||
|
||||
/** Processes the images with the underlying machine learning models. */
|
||||
fun process(data: ByteBuffer, frameMetadata: FrameMetadata, graphicOverlay: GraphicOverlay?=null, isOriginalImageReturned:Boolean = true, listener: VisionProcessorBase.Listener<T>):Boolean
|
||||
|
||||
/** Processes the bitmap images. */
|
||||
fun process(bitmap: Bitmap, rotation: Int = 0, graphicOverlay: GraphicOverlay?=null, isOriginalImageReturned:Boolean = true, convertToNv21:Boolean = true, listener: VisionProcessorBase.Listener<T>):Boolean
|
||||
|
||||
/** Processes the images. */
|
||||
fun process(image: Image, rotation: Int = 0, graphicOverlay: GraphicOverlay?=null, isOriginalImageReturned:Boolean = true, listener: VisionProcessorBase.Listener<T>):Boolean
|
||||
|
||||
/** Processes the bitmap images. */
|
||||
fun process(frame: Frame, rotation:Int = 0, graphicOverlay: GraphicOverlay?=null, isOriginalImageReturned:Boolean = true, listener: VisionProcessorBase.Listener<T>):Boolean
|
||||
|
||||
/** Processes the FirebaseVisionImage */
|
||||
fun process(image: InputImage, metadata: FrameMetadata?, graphicOverlay: GraphicOverlay?, isOriginalImageReturned:Boolean = true, listener: VisionProcessorBase.Listener<T>):Boolean
|
||||
|
||||
/** Stops the underlying machine learning model and release resources. */
|
||||
fun stop()
|
||||
|
||||
fun canHandleNewFrame():Boolean
|
||||
|
||||
fun resetThrottle()
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package example.jllarraz.com.passportreader.mlkit
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.media.Image
|
||||
|
||||
import com.google.android.gms.tasks.Task
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
import example.jllarraz.com.passportreader.utils.ImageUtil
|
||||
import io.fotoapparat.preview.Frame
|
||||
|
||||
|
||||
import org.jmrtd.lds.icao.MRZInfo
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
|
||||
abstract class VisionProcessorBase<T> : VisionImageProcessor<T> {
|
||||
|
||||
// Whether we should ignore process(). This is usually caused by feeding input data faster than
|
||||
// the model can handle.
|
||||
private val shouldThrottle = AtomicBoolean(false)
|
||||
|
||||
override fun canHandleNewFrame():Boolean{
|
||||
return !shouldThrottle.get()
|
||||
}
|
||||
|
||||
override fun resetThrottle(){
|
||||
shouldThrottle.set(false)
|
||||
}
|
||||
|
||||
override fun process(
|
||||
data: ByteBuffer,
|
||||
frameMetadata: FrameMetadata,
|
||||
graphicOverlay: GraphicOverlay?,
|
||||
isOriginalImageReturned:Boolean,
|
||||
listener: VisionProcessorBase.Listener<T>):Boolean {
|
||||
if (shouldThrottle.get()) {
|
||||
return false
|
||||
}
|
||||
shouldThrottle.set(true)
|
||||
try {
|
||||
|
||||
|
||||
/*val metadata = FirebaseVisionImageMetadata.Builder()
|
||||
.setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
|
||||
.setWidth(frameMetadata.width)
|
||||
.setHeight(frameMetadata.height)
|
||||
.setRotation(frameMetadata.rotation)
|
||||
.build()*/
|
||||
|
||||
val inputImage = InputImage.fromByteBuffer(data,
|
||||
frameMetadata.width,
|
||||
frameMetadata.height,
|
||||
frameMetadata.rotation,
|
||||
InputImage.IMAGE_FORMAT_NV21
|
||||
)
|
||||
|
||||
// val firebaseVisionImage = FirebaseVisionImage.fromByteBuffer(data, metadata)
|
||||
return detectInVisionImage(
|
||||
inputImage,
|
||||
frameMetadata,
|
||||
graphicOverlay,
|
||||
if (isOriginalImageReturned) inputImage.bitmapInternal else null,
|
||||
listener)
|
||||
}catch (e:Exception){
|
||||
e.printStackTrace()
|
||||
shouldThrottle.set(false)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Bitmap version
|
||||
override fun process(frame: Frame,
|
||||
rotation:Int,
|
||||
graphicOverlay: GraphicOverlay?,
|
||||
isOriginalImageReturned:Boolean,
|
||||
listener: VisionProcessorBase.Listener<T>):Boolean {
|
||||
if (shouldThrottle.get()) {
|
||||
return false
|
||||
}
|
||||
shouldThrottle.set(true)
|
||||
try{
|
||||
/* var intFirebaseRotation=FirebaseVisionImageMetadata.ROTATION_0
|
||||
when(rotation){
|
||||
0 ->{
|
||||
intFirebaseRotation = FirebaseVisionImageMetadata.ROTATION_0
|
||||
}
|
||||
90 ->{
|
||||
intFirebaseRotation = FirebaseVisionImageMetadata.ROTATION_90
|
||||
}
|
||||
180 ->{
|
||||
intFirebaseRotation = FirebaseVisionImageMetadata.ROTATION_180
|
||||
}
|
||||
270 ->{
|
||||
intFirebaseRotation = FirebaseVisionImageMetadata.ROTATION_270
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
val frameMetadata = FrameMetadata.Builder()
|
||||
.setWidth(frame.size.width)
|
||||
.setHeight(frame.size.height)
|
||||
.setRotation(rotation).build()
|
||||
val inputImage = InputImage.fromByteArray(frame.image,
|
||||
frameMetadata.width,
|
||||
frameMetadata.height,
|
||||
rotation,
|
||||
InputImage.IMAGE_FORMAT_NV21
|
||||
)
|
||||
|
||||
var originalBitmap:Bitmap?=null
|
||||
if(isOriginalImageReturned){
|
||||
try {
|
||||
originalBitmap = inputImage.bitmapInternal
|
||||
if (originalBitmap == null) {
|
||||
val wrap = ByteBuffer.wrap(frame.image)
|
||||
originalBitmap = ImageUtil.rotateBitmap(ImageUtil.getBitmap(wrap, frameMetadata)!!, frameMetadata.rotation.toFloat())
|
||||
}
|
||||
}catch (e:Exception){
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
// val firebaseVisionImage = FirebaseVisionImage.fromByteArray(frame.image, metadata)
|
||||
return detectInVisionImage(inputImage, frameMetadata, graphicOverlay, if(isOriginalImageReturned) originalBitmap else null, listener)
|
||||
}catch (e:Exception){
|
||||
e.printStackTrace()
|
||||
shouldThrottle.set(false)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Bitmap version
|
||||
override fun process(bitmap: Bitmap, rotation: Int, graphicOverlay: GraphicOverlay?, isOriginalImageReturned:Boolean, convertToNv21:Boolean, listener: VisionProcessorBase.Listener<T>):Boolean {
|
||||
if (shouldThrottle.get()) {
|
||||
return false
|
||||
}
|
||||
try{
|
||||
val bitmapToProcess:Bitmap?
|
||||
when(rotation){
|
||||
0 -> {
|
||||
bitmapToProcess = bitmap
|
||||
}
|
||||
else -> {
|
||||
bitmapToProcess = ImageUtil.rotateBitmap(bitmap, rotation.toFloat())
|
||||
}
|
||||
}
|
||||
|
||||
val frameMetadata = FrameMetadata.Builder()
|
||||
.setWidth(bitmapToProcess.width)
|
||||
.setHeight(bitmapToProcess.height)
|
||||
.setRotation(rotation).build()
|
||||
|
||||
|
||||
/*var byteArray:ByteArray
|
||||
if(convertToNv21){
|
||||
byteArray = ImageUtil.toNv21(bitmapToProcess)
|
||||
} else {
|
||||
val size = bitmapToProcess.rowBytes * bitmapToProcess.height
|
||||
val byteBuffer = ByteBuffer.allocate(size)
|
||||
bitmapToProcess.copyPixelsToBuffer(byteBuffer)
|
||||
byteArray = byteBuffer.array()
|
||||
}
|
||||
bitmapToProcess.recycle()
|
||||
val byteBuffer = ByteBuffer.wrap(byteArray)*/
|
||||
|
||||
val inputImage = InputImage.fromBitmap(bitmapToProcess, rotation)
|
||||
|
||||
// val fromBitmap = FirebaseVisionImage.fromBitmap(bitmapToProcess)
|
||||
|
||||
return process(inputImage, frameMetadata, graphicOverlay, isOriginalImageReturned, listener)
|
||||
}catch (e:Exception){
|
||||
e.printStackTrace()
|
||||
shouldThrottle.set(false)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Detects feature from given media.Image
|
||||
*
|
||||
* @return created FirebaseVisionImage
|
||||
*/
|
||||
override fun process(image: Image, rotation: Int, graphicOverlay: GraphicOverlay?, isOriginalImageReturned:Boolean, listener: Listener<T>):Boolean {
|
||||
if (shouldThrottle.get()) {
|
||||
return false
|
||||
}
|
||||
shouldThrottle.set(true)
|
||||
try {
|
||||
// This is for overlay display's usage
|
||||
val frameMetadata = FrameMetadata.Builder().setWidth(image.width).setHeight(image.height).build()
|
||||
val inputImage = InputImage.fromMediaImage(image, rotation)
|
||||
//val fbVisionImage = FirebaseVisionImage.fromMediaImage(image, rotation)
|
||||
|
||||
return detectInVisionImage(inputImage, frameMetadata, graphicOverlay, if (isOriginalImageReturned) inputImage.bitmapInternal else null, listener)
|
||||
}catch (e:Exception){
|
||||
e.printStackTrace()
|
||||
shouldThrottle.set(false)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun detectInVisionImage(
|
||||
image: InputImage,
|
||||
metadata: FrameMetadata?,
|
||||
graphicOverlay: GraphicOverlay?,
|
||||
originalBitmap: Bitmap?=null,
|
||||
listener: Listener<T>
|
||||
):Boolean {
|
||||
val start = System.currentTimeMillis()
|
||||
// val bitmapForDebugging = image.bitmap
|
||||
detectInImage(image)
|
||||
.addOnSuccessListener { results ->
|
||||
val timeRequired = System.currentTimeMillis() - start
|
||||
listener.onSuccess(results, metadata, timeRequired, originalBitmap, graphicOverlay)
|
||||
}
|
||||
.addOnFailureListener { e ->
|
||||
val timeRequired = System.currentTimeMillis() - start
|
||||
listener.onFailure(e, timeRequired)
|
||||
shouldThrottle.set(false)
|
||||
}
|
||||
.addOnCanceledListener {
|
||||
val timeRequired = System.currentTimeMillis() - start
|
||||
listener.onCanceled(timeRequired)
|
||||
shouldThrottle.set(false)
|
||||
}
|
||||
.addOnCompleteListener {
|
||||
val timeRequired = System.currentTimeMillis() - start
|
||||
listener.onCompleted(timeRequired)
|
||||
shouldThrottle.set(false)
|
||||
}
|
||||
// Begin throttling until this frame of input has been processed, either in onSuccess or
|
||||
// onFailure.
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun process(image: InputImage, metadata: FrameMetadata?, graphicOverlay: GraphicOverlay?, isOriginalImageReturned:Boolean, listener: Listener<T>):Boolean{
|
||||
if (shouldThrottle.get()) {
|
||||
return false
|
||||
}
|
||||
shouldThrottle.set(true)
|
||||
try {
|
||||
return detectInVisionImage(image, metadata, graphicOverlay, if (isOriginalImageReturned) image.bitmapInternal else null, listener)
|
||||
}catch (e:Exception){
|
||||
e.printStackTrace()
|
||||
shouldThrottle.set(false)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {}
|
||||
|
||||
protected abstract fun detectInImage(image: InputImage): Task<T>
|
||||
|
||||
|
||||
interface Listener<T> {
|
||||
fun onSuccess(results: T, frameMetadata: FrameMetadata?, timeRequired: Long, bitmap: Bitmap?, graphicOverlay: GraphicOverlay?=null)
|
||||
fun onCanceled(timeRequired:Long)
|
||||
fun onFailure(e: Exception, timeRequired:Long)
|
||||
fun onCompleted(timeRequired:Long)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = VisionProcessorBase::class.java.simpleName
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.network
|
||||
|
||||
import io.reactivex.Single
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.http.*
|
||||
|
||||
interface MasterListApi {
|
||||
@Headers(value = ["Content-type: text/xml; charset=utf-8"])
|
||||
@GET("descargas/mrtd/SpanishMasterList.zip")
|
||||
@Streaming
|
||||
fun getSpanishMasterList(
|
||||
): Single<ResponseBody>
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.network
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.jmrtd.cert.CSCAMasterList
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.nio.charset.Charset
|
||||
import java.security.cert.Certificate
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
class MasterListService constructor(var context: Context, var baseUrl: String) {
|
||||
|
||||
private lateinit var api: MasterListApi
|
||||
init {
|
||||
initRetrofit()
|
||||
}
|
||||
|
||||
fun getSpanishMasterList(): Single<ArrayList<Certificate>> {
|
||||
return api.getSpanishMasterList()
|
||||
.flatMap { result ->
|
||||
val certificates = ArrayList<Certificate>()
|
||||
val byteStream = result.byteStream()
|
||||
val zipInputStream = ZipInputStream(byteStream)
|
||||
var entry = zipInputStream.nextEntry
|
||||
while (entry != null) {
|
||||
val name = entry.name
|
||||
if (!entry.isDirectory) {
|
||||
try {
|
||||
val readBytes = zipInputStream.readBytes()
|
||||
val cscaMasterList = CSCAMasterList(readBytes)
|
||||
certificates.addAll(cscaMasterList.getCertificates())
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
// throw Exception("Unable to extract the zip file: " + name)
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
entry = zipInputStream.nextEntry
|
||||
}
|
||||
|
||||
Single.fromCallable{certificates}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initRetrofit() {
|
||||
|
||||
val httpLoggingInterceptor = HttpLoggingInterceptor()
|
||||
val httpClient = OkHttpClient.Builder()
|
||||
.addInterceptor(httpLoggingInterceptor.apply { httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BASIC })
|
||||
.readTimeout(120, TimeUnit.SECONDS)
|
||||
.connectTimeout(120, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
api = Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.client(httpClient)
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
.create(MasterListApi::class.java)
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package example.jllarraz.com.passportreader.ui.activities
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
import org.jmrtd.lds.icao.MRZInfo
|
||||
|
||||
import example.jllarraz.com.passportreader.R
|
||||
import example.jllarraz.com.passportreader.common.IntentData
|
||||
import example.jllarraz.com.passportreader.ui.fragments.CameraMLKitFragment
|
||||
|
||||
class CameraActivity : AppCompatActivity(), CameraMLKitFragment.CameraMLKitCallback {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_camera)
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, CameraMLKitFragment())
|
||||
.commit()
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onPassportRead(mrzInfo: MRZInfo) {
|
||||
val intent = Intent()
|
||||
intent.putExtra(IntentData.KEY_MRZ_INFO, mrzInfo)
|
||||
setResult(Activity.RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onError() {
|
||||
onBackPressed()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = CameraActivity::class.java.simpleName
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.ui.activities
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.nfc.NfcAdapter
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import android.widget.Toast
|
||||
|
||||
import net.sf.scuba.smartcards.CardServiceException
|
||||
|
||||
|
||||
import org.jmrtd.lds.icao.MRZInfo
|
||||
|
||||
import example.jllarraz.com.passportreader.R
|
||||
import example.jllarraz.com.passportreader.common.IntentData
|
||||
import example.jllarraz.com.passportreader.data.Passport
|
||||
import example.jllarraz.com.passportreader.ui.fragments.NfcFragment
|
||||
import example.jllarraz.com.passportreader.ui.fragments.PassportDetailsFragment
|
||||
import example.jllarraz.com.passportreader.ui.fragments.PassportPhotoFragment
|
||||
|
||||
import example.jllarraz.com.passportreader.common.IntentData.KEY_MRZ_INFO
|
||||
|
||||
class NfcActivity : androidx.fragment.app.FragmentActivity(), NfcFragment.NfcFragmentListener, PassportDetailsFragment.PassportDetailsFragmentListener, PassportPhotoFragment.PassportPhotoFragmentListener {
|
||||
|
||||
private var mrzInfo: MRZInfo? = null
|
||||
|
||||
private var nfcAdapter: NfcAdapter? = null
|
||||
private var pendingIntent: PendingIntent? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_nfc)
|
||||
val intent = intent
|
||||
if (intent.hasExtra(IntentData.KEY_MRZ_INFO)) {
|
||||
mrzInfo = intent.getSerializableExtra(IntentData.KEY_MRZ_INFO) as MRZInfo
|
||||
} else {
|
||||
onBackPressed()
|
||||
}
|
||||
|
||||
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
|
||||
|
||||
if (nfcAdapter == null) {
|
||||
Toast.makeText(this, getString(R.string.warning_no_nfc), Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.getActivity(this, 0, Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_MUTABLE)
|
||||
} else{
|
||||
PendingIntent.getActivity(this, 0, Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
|
||||
}
|
||||
|
||||
|
||||
if (null == savedInstanceState) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, NfcFragment.newInstance(mrzInfo!!), TAG_NFC)
|
||||
.commit()
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
}
|
||||
|
||||
public override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
}
|
||||
|
||||
public override fun onNewIntent(intent: Intent) {
|
||||
if (NfcAdapter.ACTION_TAG_DISCOVERED == intent.action || NfcAdapter.ACTION_TECH_DISCOVERED == intent.action) {
|
||||
// drop NFC events
|
||||
handleIntent(intent)
|
||||
}else{
|
||||
super.onNewIntent(intent)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun handleIntent(intent: Intent) {
|
||||
val fragmentByTag = supportFragmentManager.findFragmentByTag(TAG_NFC)
|
||||
if (fragmentByTag is NfcFragment) {
|
||||
fragmentByTag.handleNfcTag(intent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//
|
||||
// NFC Fragment events
|
||||
//
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
override fun onEnableNfc() {
|
||||
|
||||
|
||||
if (nfcAdapter != null) {
|
||||
if (!nfcAdapter!!.isEnabled)
|
||||
showWirelessSettings()
|
||||
|
||||
nfcAdapter!!.enableForegroundDispatch(this, pendingIntent, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisableNfc() {
|
||||
val nfcAdapter = NfcAdapter.getDefaultAdapter(this)
|
||||
nfcAdapter.disableForegroundDispatch(this)
|
||||
}
|
||||
|
||||
override fun onPassportRead(passport: Passport?) {
|
||||
showFragmentDetails(passport!!)
|
||||
}
|
||||
|
||||
override fun onCardException(cardException: Exception?) {
|
||||
//Toast.makeText(this, cardException.toString(), Toast.LENGTH_SHORT).show();
|
||||
//onBackPressed();
|
||||
}
|
||||
|
||||
private fun showWirelessSettings() {
|
||||
Toast.makeText(this, getString(R.string.warning_enable_nfc), Toast.LENGTH_SHORT).show()
|
||||
val intent = Intent(Settings.ACTION_WIRELESS_SETTINGS)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
|
||||
private fun showFragmentDetails(passport: Passport) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, PassportDetailsFragment.newInstance(passport))
|
||||
.addToBackStack(TAG_PASSPORT_DETAILS)
|
||||
.commit()
|
||||
}
|
||||
|
||||
private fun showFragmentPhoto(bitmap: Bitmap) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, PassportPhotoFragment.newInstance(bitmap))
|
||||
.addToBackStack(TAG_PASSPORT_PICTURE)
|
||||
.commit()
|
||||
}
|
||||
|
||||
|
||||
override fun onImageSelected(bitmap: Bitmap?) {
|
||||
showFragmentPhoto(bitmap!!)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = NfcActivity::class.java.simpleName
|
||||
|
||||
|
||||
private val TAG_NFC = "TAG_NFC"
|
||||
private val TAG_PASSPORT_DETAILS = "TAG_PASSPORT_DETAILS"
|
||||
private val TAG_PASSPORT_PICTURE = "TAG_PASSPORT_PICTURE"
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package example.jllarraz.com.passportreader.ui.activities
|
||||
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import example.jllarraz.com.passportreader.R
|
||||
import example.jllarraz.com.passportreader.common.IntentData
|
||||
import example.jllarraz.com.passportreader.ui.fragments.SelectionFragment
|
||||
import org.jmrtd.lds.icao.MRZInfo
|
||||
|
||||
class SelectionActivity : AppCompatActivity(), SelectionFragment.SelectionFragmentListener {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_camera)
|
||||
if (null == savedInstanceState) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, SelectionFragment(), TAG_SELECTION_FRAGMENT)
|
||||
.commit()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
var data = data
|
||||
if (data == null) {
|
||||
data = Intent()
|
||||
}
|
||||
when (requestCode) {
|
||||
REQUEST_MRZ -> {
|
||||
when (resultCode) {
|
||||
Activity.RESULT_OK -> {
|
||||
onPassportRead(data.getSerializableExtra(IntentData.KEY_MRZ_INFO) as MRZInfo)
|
||||
}
|
||||
Activity.RESULT_CANCELED -> {
|
||||
val fragmentByTag = supportFragmentManager.findFragmentByTag(TAG_SELECTION_FRAGMENT)
|
||||
if (fragmentByTag is SelectionFragment) {
|
||||
fragmentByTag.selectManualToggle()
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val fragmentByTag = supportFragmentManager.findFragmentByTag(TAG_SELECTION_FRAGMENT)
|
||||
if (fragmentByTag is SelectionFragment) {
|
||||
fragmentByTag.selectManualToggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
REQUEST_NFC -> {
|
||||
val fragmentByTag = supportFragmentManager.findFragmentByTag(TAG_SELECTION_FRAGMENT)
|
||||
if (fragmentByTag is SelectionFragment) {
|
||||
fragmentByTag.selectManualToggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
private fun test() {
|
||||
//Method to test NFC without rely into the Camera
|
||||
val TEST_LINE_1 = "P<IRLOSULLIVAN<<LAUREN<<<<<<<<<<<<<<<<<<<<<<"
|
||||
val TEST_LINE_2 = "XN50042162IRL8805049F2309154<<<<<<<<<<<<<<<6"
|
||||
|
||||
val mrzInfo = MRZInfo(TEST_LINE_1 + "\n" + TEST_LINE_2)
|
||||
onPassportRead(mrzInfo)
|
||||
}
|
||||
|
||||
override fun onPassportRead(mrzInfo: MRZInfo) {
|
||||
val intent = Intent(this, NfcActivity::class.java)
|
||||
intent.putExtra(IntentData.KEY_MRZ_INFO, mrzInfo)
|
||||
startActivityForResult(intent, REQUEST_NFC)
|
||||
}
|
||||
|
||||
override fun onMrzRequest() {
|
||||
val intent = Intent(this, CameraActivity::class.java)
|
||||
startActivityForResult(intent, REQUEST_MRZ)
|
||||
}
|
||||
|
||||
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = SelectionActivity::class.java.simpleName
|
||||
private val REQUEST_MRZ = 12
|
||||
private val REQUEST_NFC = 11
|
||||
|
||||
private val TAG_SELECTION_FRAGMENT = "TAG_SELECTION_FRAGMENT"
|
||||
}
|
||||
}
|
||||
@@ -1,416 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.ui.fragments
|
||||
|
||||
import android.Manifest
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.hardware.camera2.CameraCharacteristics
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.MotionEvent
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import example.jllarraz.com.passportreader.R
|
||||
import io.fotoapparat.Fotoapparat
|
||||
import io.fotoapparat.characteristic.LensPosition
|
||||
import io.fotoapparat.configuration.CameraConfiguration
|
||||
import io.fotoapparat.parameter.Zoom
|
||||
import io.fotoapparat.preview.FrameProcessor
|
||||
import io.fotoapparat.selector.*
|
||||
import io.fotoapparat.view.CameraView
|
||||
|
||||
abstract class CameraFragment : androidx.fragment.app.Fragment(), ActivityCompat.OnRequestPermissionsResultCallback {
|
||||
|
||||
|
||||
/**
|
||||
* Camera Manager
|
||||
*/
|
||||
protected var fotoapparat: Fotoapparat? = null
|
||||
protected var hasCameraPermission: Boolean = false
|
||||
protected var rotation: Int = 0
|
||||
|
||||
private var cameraZoom: Zoom.VariableZoom? = null
|
||||
private var zoomProgress: Int = 0
|
||||
|
||||
|
||||
private var mDist: Float = 0.toFloat()
|
||||
|
||||
var configuration = CameraConfiguration(
|
||||
// A full configuration
|
||||
// ...
|
||||
focusMode = firstAvailable(
|
||||
autoFocus()
|
||||
),
|
||||
flashMode = off()
|
||||
)
|
||||
|
||||
|
||||
////////////////////////////////////////
|
||||
|
||||
abstract val callbackFrameProcessor: FrameProcessor
|
||||
abstract val cameraPreview: CameraView
|
||||
abstract val requestedPermissions: ArrayList<String>
|
||||
var initialLensPosition: LensPosition = LensPosition.Back
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
if (savedInstanceState.containsKey(KEY_CURRENT_ZOOM_PROGRESS)) {
|
||||
zoomProgress = savedInstanceState.getInt(KEY_CURRENT_ZOOM_PROGRESS, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putInt(KEY_CURRENT_ZOOM_PROGRESS, zoomProgress)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
fun buildCamera(cameraView: CameraView, lensPosition: LensPosition = LensPosition.Back) {
|
||||
if (fotoapparat == null) {
|
||||
fotoapparat = Fotoapparat
|
||||
.with(context?.applicationContext!!)
|
||||
.into(cameraView)
|
||||
.frameProcessor(
|
||||
callbackFrameProcessor
|
||||
)
|
||||
.lensPosition { lensPosition }
|
||||
.build()
|
||||
|
||||
fotoapparat?.updateConfiguration(configuration)
|
||||
|
||||
}
|
||||
|
||||
cameraView.setOnTouchListener(object : View.OnTouchListener {
|
||||
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
|
||||
return onTouchEvent(event!!)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun configureZoom() {
|
||||
fotoapparat?.getCapabilities()
|
||||
?.whenAvailable { capabilities ->
|
||||
setZoomProperties(capabilities?.zoom as Zoom.VariableZoom)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
rotation = getRotation(context!!, initialLensPosition)
|
||||
buildCamera(cameraPreview!!, initialLensPosition)
|
||||
|
||||
hasCameraPermission = hasCameraPermission()
|
||||
if (hasCameraPermission) {
|
||||
checkPermissions(requestedPermissions)
|
||||
} else {
|
||||
fotoapparat?.start()
|
||||
configureZoom()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
hasCameraPermission = hasCameraPermission()
|
||||
if (!hasCameraPermission) {
|
||||
fotoapparat?.stop()
|
||||
}
|
||||
fotoapparat = null;
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
super.onDetach()
|
||||
|
||||
}
|
||||
|
||||
protected fun setFlash(isEnable: Boolean) {
|
||||
configuration = configuration.copy(flashMode = if (isEnable) torch() else off())
|
||||
fotoapparat?.updateConfiguration(configuration)
|
||||
}
|
||||
|
||||
protected fun setFocusMode(focusModeSelector: FocusModeSelector) {
|
||||
configuration = configuration.copy(focusMode = focusModeSelector)
|
||||
fotoapparat?.updateConfiguration(configuration)
|
||||
}
|
||||
|
||||
private fun setZoomProperties(zoom: Zoom.VariableZoom) {
|
||||
cameraZoom = zoom
|
||||
setZoomProgress(zoomProgress, cameraZoom!!)
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun setZoomProgress(progress: Int, zoom: Zoom.VariableZoom) {
|
||||
zoomProgress = progress
|
||||
fotoapparat?.setZoom(progress.toFloat() / zoom.maxZoom)
|
||||
}
|
||||
|
||||
|
||||
/** Determine the space between the first two fingers */
|
||||
private fun getFingerSpacing(event: MotionEvent): Float {
|
||||
// ...
|
||||
val x = event.getX(0) - event.getX(1)
|
||||
val y = event.getY(0) - event.getY(1)
|
||||
|
||||
return Math.sqrt((x * x + y * y).toDouble()).toFloat()
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Pinch on Zoom Functionality
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
// Get the pointer ID
|
||||
val action = event.action
|
||||
|
||||
|
||||
if (event.pointerCount > 1) {
|
||||
// handle multi-touch events
|
||||
if (action == MotionEvent.ACTION_POINTER_DOWN) {
|
||||
mDist = getFingerSpacing(event)
|
||||
} else if (action == MotionEvent.ACTION_MOVE && cameraZoom != null) {
|
||||
handleZoom(event)
|
||||
}
|
||||
} else {
|
||||
// handle single touch events
|
||||
if (action == MotionEvent.ACTION_UP) {
|
||||
// setFocusMode (previousFocusMode!!)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun handleZoom(event: MotionEvent) {
|
||||
if (cameraZoom == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val maxZoom = cameraZoom?.maxZoom!!
|
||||
var zoom = zoomProgress
|
||||
val newDist = getFingerSpacing(event)
|
||||
if (newDist > mDist) {
|
||||
//zoom in
|
||||
if (zoom < maxZoom)
|
||||
zoom++
|
||||
} else if (newDist < mDist) {
|
||||
//zoom out
|
||||
if (zoom > 0)
|
||||
zoom--
|
||||
}
|
||||
|
||||
if (zoom > maxZoom) {
|
||||
zoom = maxZoom
|
||||
}
|
||||
|
||||
if (zoom < 0) {
|
||||
zoom = 0
|
||||
}
|
||||
|
||||
|
||||
mDist = newDist
|
||||
setZoomProgress(zoom, cameraZoom!!)
|
||||
//zoomProgress = cameraZoom?.zoomRatios!![zoom]
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Permissions
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
|
||||
grantResults: IntArray) {
|
||||
when (requestCode) {
|
||||
REQUEST_PERMISSIONS -> {
|
||||
val permissionsDenied = ArrayList<String>()
|
||||
val permissionsGranted = ArrayList<String>()
|
||||
permissions.forEachIndexed { index, element ->
|
||||
if (grantResults[index] != PackageManager.PERMISSION_GRANTED) {
|
||||
permissionsDenied.add(element)
|
||||
} else {
|
||||
permissionsGranted.add(element)
|
||||
}
|
||||
}
|
||||
|
||||
for (permission in permissionsDenied) {
|
||||
when (permission) {
|
||||
Manifest.permission.CAMERA -> {
|
||||
showErrorCameraPermissionDenied()
|
||||
}
|
||||
}
|
||||
}
|
||||
for (permission in permissionsGranted) {
|
||||
when (permission) {
|
||||
Manifest.permission.CAMERA -> {
|
||||
hasCameraPermission = true
|
||||
fotoapparat?.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onRequestPermissionsResult(permissionsDenied, permissionsGranted)
|
||||
}
|
||||
else -> {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun onRequestPermissionsResult(permissionsDenied: ArrayList<String>, permissionsGranted: ArrayList<String>)
|
||||
|
||||
protected fun showErrorCameraPermissionDenied() {
|
||||
ErrorDialog.newInstance(getString(R.string.permission_camera_rationale))
|
||||
.show(childFragmentManager, FRAGMENT_DIALOG)
|
||||
}
|
||||
|
||||
protected fun hasCameraPermission(): Boolean {
|
||||
return ContextCompat.checkSelfPermission(context!!, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
protected fun checkPermissions(permissions: ArrayList<String> = ArrayList()) {
|
||||
//request permission
|
||||
val hasPermissionCamera = ContextCompat.checkSelfPermission(context!!,
|
||||
Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
|
||||
if (!hasPermissionCamera && !permissions.contains(Manifest.permission.CAMERA)) {
|
||||
permissions.add(Manifest.permission.CAMERA)
|
||||
}
|
||||
|
||||
if (permissions.isNotEmpty()) {
|
||||
requestPermissions(permissions.toArray(arrayOf<String>()),
|
||||
REQUEST_PERMISSIONS)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Dialogs UI
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Shows an error message dialog.
|
||||
*/
|
||||
class ErrorDialog : androidx.fragment.app.DialogFragment() {
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val activity = activity
|
||||
return AlertDialog.Builder(activity)
|
||||
.setMessage(arguments!!.getString(ARG_MESSAGE))
|
||||
.setPositiveButton(android.R.string.ok) { dialogInterface, i -> activity!!.finish() }
|
||||
.create()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val ARG_MESSAGE = "message"
|
||||
|
||||
fun newInstance(message: String): ErrorDialog {
|
||||
val dialog = ErrorDialog()
|
||||
val args = Bundle()
|
||||
args.putString(ARG_MESSAGE, message)
|
||||
dialog.arguments = args
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getRotation(context: Context, lensPosition: LensPosition = LensPosition.Back): Int {
|
||||
|
||||
var facingCamera = 0
|
||||
when (lensPosition) {
|
||||
LensPosition.Front -> {
|
||||
facingCamera = CameraCharacteristics.LENS_FACING_FRONT
|
||||
}
|
||||
LensPosition.Back -> {
|
||||
facingCamera = CameraCharacteristics.LENS_FACING_BACK
|
||||
}
|
||||
LensPosition.External -> {
|
||||
facingCamera = CameraCharacteristics.LENS_FACING_EXTERNAL
|
||||
}
|
||||
}
|
||||
|
||||
val manager = context.getSystemService(Context.CAMERA_SERVICE) as android.hardware.camera2.CameraManager
|
||||
try {
|
||||
for (cameraId in manager.getCameraIdList()) {
|
||||
val characteristics = manager.getCameraCharacteristics(cameraId)
|
||||
val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
|
||||
if (facing != null && facing != facingCamera) {
|
||||
continue
|
||||
}
|
||||
|
||||
val mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!
|
||||
val rotation = (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.rotation
|
||||
var degrees = 0
|
||||
when (rotation) {
|
||||
Surface.ROTATION_0 -> degrees = 0
|
||||
Surface.ROTATION_90 -> degrees = 90
|
||||
Surface.ROTATION_180 -> degrees = 180
|
||||
Surface.ROTATION_270 -> degrees = 270
|
||||
}
|
||||
var result: Int
|
||||
if (facing == CameraCharacteristics.LENS_FACING_FRONT) {
|
||||
result = (mSensorOrientation + degrees - 360) % 360
|
||||
result = (360 + result) % 360 // compensate the mirror
|
||||
} else { // back-facing
|
||||
result = (mSensorOrientation - degrees + 360) % 360
|
||||
}
|
||||
return result
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a [Toast] on the UI thread.
|
||||
*
|
||||
* @param text The message to show
|
||||
*/
|
||||
private fun showToast(text: String) {
|
||||
val activity = activity
|
||||
activity?.runOnUiThread { Toast.makeText(activity, text, Toast.LENGTH_SHORT).show() }
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Tag for the [Log].
|
||||
*/
|
||||
private val TAG = CameraFragment::class.java.simpleName
|
||||
|
||||
private val KEY_CURRENT_ZOOM_PROGRESS = "KEY_CURRENT_ZOOM_PROGRESS"
|
||||
private val REQUEST_PERMISSIONS = 410
|
||||
private val FRAGMENT_DIALOG = TAG
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,422 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package example.jllarraz.com.passportreader.ui.fragments
|
||||
|
||||
import android.Manifest
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import com.google.mlkit.vision.text.Text
|
||||
|
||||
|
||||
import org.jmrtd.lds.icao.MRZInfo
|
||||
|
||||
import example.jllarraz.com.passportreader.R
|
||||
import example.jllarraz.com.passportreader.databinding.FragmentCameraMrzBinding
|
||||
import example.jllarraz.com.passportreader.mlkit.FrameMetadata
|
||||
import example.jllarraz.com.passportreader.mlkit.GraphicOverlay
|
||||
import example.jllarraz.com.passportreader.mlkit.OcrMrzDetectorProcessor
|
||||
import example.jllarraz.com.passportreader.mlkit.VisionProcessorBase
|
||||
import example.jllarraz.com.passportreader.utils.MRZUtil
|
||||
import example.jllarraz.com.passportreader.utils.OcrUtils
|
||||
import io.fotoapparat.preview.Frame
|
||||
import io.fotoapparat.view.CameraView
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
||||
class CameraMLKitFragment : CameraFragment() {
|
||||
|
||||
|
||||
////////////////////////////////////////
|
||||
|
||||
private var cameraMLKitCallback: CameraMLKitCallback? = null
|
||||
private var frameProcessor: OcrMrzDetectorProcessor? = null
|
||||
private val mHandler = Handler(Looper.getMainLooper())
|
||||
var disposable = CompositeDisposable()
|
||||
|
||||
private var isDecoding = false
|
||||
|
||||
private var binding:FragmentCameraMrzBinding?=null
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
binding = FragmentCameraMrzBinding.inflate(inflater, container, false)
|
||||
return binding?.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
override fun onResume() {
|
||||
MRZUtil.cleanStorage()
|
||||
frameProcessor = textProcessor
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun onPause() {
|
||||
frameProcessor?.stop()
|
||||
frameProcessor = null
|
||||
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
if (!disposable.isDisposed()) {
|
||||
disposable.dispose();
|
||||
}
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
val activity = activity
|
||||
if (activity is CameraMLKitCallback) {
|
||||
cameraMLKitCallback = activity
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
cameraMLKitCallback = null
|
||||
super.onDetach()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Events from camera fragment
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
override val callbackFrameProcessor: io.fotoapparat.preview.FrameProcessor
|
||||
get() {
|
||||
val callbackFrameProcessor2 = object : io.fotoapparat.preview.FrameProcessor {
|
||||
override fun process(frame: Frame) {
|
||||
try {
|
||||
if (!isDecoding) {
|
||||
isDecoding = true
|
||||
|
||||
if (frameProcessor != null) {
|
||||
val subscribe = Single.fromCallable({
|
||||
frameProcessor?.process(
|
||||
frame = frame,
|
||||
rotation = rotation,
|
||||
graphicOverlay = null,
|
||||
true,
|
||||
listener = ocrListener
|
||||
)
|
||||
}).subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ success ->
|
||||
//Don't do anything
|
||||
|
||||
},{error->
|
||||
isDecoding = false
|
||||
Toast.makeText(requireContext(), "Error: "+error, Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
disposable.add(subscribe)
|
||||
}
|
||||
}
|
||||
}catch (e:Exception){
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return callbackFrameProcessor2
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Get camera preview
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override val cameraPreview: CameraView
|
||||
get(){
|
||||
return binding?.cameraPreview!!
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Permission requested
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override val requestedPermissions: ArrayList<String>
|
||||
get() {
|
||||
//Nothing as we don't need any other permission than camera and that's managed in the parent fragment
|
||||
return ArrayList<String>()
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(permissionsDenied: ArrayList<String>, permissionsGranted: ArrayList<String>) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Instantiate the text processor to perform OCR
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//OCR listener
|
||||
val ocrListener = object : VisionProcessorBase.Listener<com.google.mlkit.vision.text.Text> {
|
||||
override fun onSuccess(
|
||||
results: Text,
|
||||
frameMetadata: FrameMetadata?,
|
||||
timeRequired: Long,
|
||||
bitmap: Bitmap?,
|
||||
graphicOverlay: GraphicOverlay?
|
||||
) {
|
||||
if (!isAdded) {
|
||||
return
|
||||
}
|
||||
OcrUtils.processOcr(
|
||||
results = results,
|
||||
timeRequired = timeRequired,
|
||||
callback = mrzListener
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCanceled(timeRequired: Long) {
|
||||
if (!isAdded) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(
|
||||
e: Exception,
|
||||
timeRequired: Long
|
||||
) {
|
||||
if (!isAdded) {
|
||||
return
|
||||
}
|
||||
mrzListener.onFailure(e, timeRequired)
|
||||
}
|
||||
|
||||
override fun onCompleted(timeRequired: Long) {
|
||||
if (!isAdded) {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MRZ Listener
|
||||
var mrzListener = object : OcrUtils.MRZCallback {
|
||||
override fun onMRZRead(mrzInfo: MRZInfo, timeRequired: Long) {
|
||||
isDecoding = false
|
||||
if(!isAdded){
|
||||
return
|
||||
}
|
||||
mHandler.post {
|
||||
try {
|
||||
|
||||
binding?.statusViewBottom?.setTextColor(resources.getColor(R.color.status_text))
|
||||
if (cameraMLKitCallback != null) {
|
||||
cameraMLKitCallback!!.onPassportRead(mrzInfo)
|
||||
}
|
||||
|
||||
} catch (e: IllegalStateException) {
|
||||
//The fragment is destroyed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMRZReadFailure(timeRequired: Long) {
|
||||
isDecoding = false
|
||||
if(!isAdded){
|
||||
return
|
||||
}
|
||||
mHandler.post {
|
||||
try {
|
||||
binding?.statusViewBottom?.setTextColor(Color.RED)
|
||||
binding?.statusViewTop?.text = ""
|
||||
} catch (e: IllegalStateException) {
|
||||
//The fragment is destroyed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(e: Exception, timeRequired: Long) {
|
||||
isDecoding = false
|
||||
if(!isAdded){
|
||||
return
|
||||
}
|
||||
e.printStackTrace()
|
||||
mHandler.post {
|
||||
if (cameraMLKitCallback != null) {
|
||||
cameraMLKitCallback!!.onError()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
protected val textProcessor: OcrMrzDetectorProcessor
|
||||
get() = OcrMrzDetectorProcessor()
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Permissions
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private fun requestCameraPermission() {
|
||||
if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
|
||||
ConfirmationDialog().show(childFragmentManager, FRAGMENT_DIALOG)
|
||||
} else {
|
||||
requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
|
||||
grantResults: IntArray) {
|
||||
if (requestCode == REQUEST_CAMERA_PERMISSION) {
|
||||
if (grantResults.size != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
||||
ErrorDialog.newInstance(getString(R.string.permission_camera_rationale))
|
||||
.show(childFragmentManager, FRAGMENT_DIALOG)
|
||||
}
|
||||
} else {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Dialogs UI
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Shows a [Toast] on the UI thread.
|
||||
*
|
||||
* @param text The message to show
|
||||
*/
|
||||
private fun showToast(text: String) {
|
||||
val activity = activity
|
||||
activity?.runOnUiThread { Toast.makeText(activity, text, Toast.LENGTH_SHORT).show() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an error message dialog.
|
||||
*/
|
||||
class ErrorDialog : androidx.fragment.app.DialogFragment() {
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val activity = activity
|
||||
return AlertDialog.Builder(activity)
|
||||
.setMessage(requireArguments().getString(ARG_MESSAGE))
|
||||
.setPositiveButton(android.R.string.ok) { dialogInterface, i -> activity!!.finish() }
|
||||
.create()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val ARG_MESSAGE = "message"
|
||||
|
||||
fun newInstance(message: String): ErrorDialog {
|
||||
val dialog = ErrorDialog()
|
||||
val args = Bundle()
|
||||
args.putString(ARG_MESSAGE, message)
|
||||
dialog.arguments = args
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows OK/Cancel confirmation dialog about camera permission.
|
||||
*/
|
||||
class ConfirmationDialog : androidx.fragment.app.DialogFragment() {
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val parent = parentFragment
|
||||
return AlertDialog.Builder(activity)
|
||||
.setMessage(R.string.permission_camera_rationale)
|
||||
.setPositiveButton(android.R.string.ok) { dialog, which ->
|
||||
parent!!.requestPermissions(arrayOf(Manifest.permission.CAMERA),
|
||||
REQUEST_CAMERA_PERMISSION)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel
|
||||
) { dialog, which ->
|
||||
val activity = parent!!.activity
|
||||
activity?.finish()
|
||||
}
|
||||
.create()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Listener
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
interface CameraMLKitCallback {
|
||||
fun onPassportRead(mrzInfo: MRZInfo)
|
||||
fun onError()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Tag for the [Log].
|
||||
*/
|
||||
private val TAG = CameraMLKitFragment::class.java.simpleName
|
||||
|
||||
private val REQUEST_CAMERA_PERMISSION = 1
|
||||
private val FRAGMENT_DIALOG = "CameraMLKitFragment"
|
||||
|
||||
fun newInstance(): CameraMLKitFragment {
|
||||
return CameraMLKitFragment()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.ui.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
|
||||
import net.sf.scuba.smartcards.CardServiceException
|
||||
import net.sf.scuba.smartcards.ISO7816
|
||||
|
||||
|
||||
import org.jmrtd.AccessDeniedException
|
||||
import org.jmrtd.BACDeniedException
|
||||
import org.jmrtd.PACEException
|
||||
import org.jmrtd.lds.icao.MRZInfo
|
||||
|
||||
|
||||
import java.security.Security
|
||||
|
||||
|
||||
import example.jllarraz.com.passportreader.R
|
||||
import example.jllarraz.com.passportreader.common.IntentData
|
||||
import example.jllarraz.com.passportreader.data.Passport
|
||||
import example.jllarraz.com.passportreader.databinding.FragmentNfcBinding
|
||||
import example.jllarraz.com.passportreader.utils.KeyStoreUtils
|
||||
import example.jllarraz.com.passportreader.utils.NFCDocumentTag
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import org.jmrtd.MRTDTrustStore
|
||||
|
||||
|
||||
class NfcFragment : androidx.fragment.app.Fragment() {
|
||||
|
||||
private var mrzInfo: MRZInfo? = null
|
||||
private var nfcFragmentListener: NfcFragmentListener? = null
|
||||
|
||||
internal var mHandler = Handler(Looper.getMainLooper())
|
||||
var disposable = CompositeDisposable()
|
||||
|
||||
private var binding:FragmentNfcBinding?=null
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
binding = FragmentNfcBinding.inflate(inflater, container, false)
|
||||
return binding?.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val arguments = arguments
|
||||
if (arguments!!.containsKey(IntentData.KEY_MRZ_INFO)) {
|
||||
mrzInfo = arguments.getSerializable(IntentData.KEY_MRZ_INFO) as MRZInfo
|
||||
} else {
|
||||
//error
|
||||
}
|
||||
}
|
||||
|
||||
fun handleNfcTag(intent: Intent?) {
|
||||
if (intent == null || intent.extras == null) {
|
||||
return
|
||||
}
|
||||
val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG) ?: return
|
||||
|
||||
val folder = requireContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)!!
|
||||
val keyStore = KeyStoreUtils().readKeystoreFromFile(folder)
|
||||
|
||||
val mrtdTrustStore = MRTDTrustStore()
|
||||
if(keyStore!=null){
|
||||
val certStore = KeyStoreUtils().toCertStore(keyStore = keyStore)
|
||||
|
||||
mrtdTrustStore.addAsCSCACertStore(certStore)
|
||||
}
|
||||
//mrtdTrustStore.addCSCAStore(readKeystoreFromFile)
|
||||
|
||||
|
||||
val subscribe = NFCDocumentTag().handleTag(requireContext(), tag, mrzInfo!!, mrtdTrustStore, object : NFCDocumentTag.PassportCallback {
|
||||
|
||||
override fun onPassportReadStart() {
|
||||
onNFCSReadStart()
|
||||
}
|
||||
|
||||
override fun onPassportReadFinish() {
|
||||
onNFCReadFinish()
|
||||
}
|
||||
|
||||
override fun onPassportRead(passport: Passport?) {
|
||||
this@NfcFragment.onPassportRead(passport)
|
||||
|
||||
}
|
||||
|
||||
override fun onAccessDeniedException(exception: AccessDeniedException) {
|
||||
Toast.makeText(context, getString(R.string.warning_authentication_failed), Toast.LENGTH_SHORT).show()
|
||||
exception.printStackTrace()
|
||||
this@NfcFragment.onCardException(exception)
|
||||
|
||||
}
|
||||
|
||||
override fun onBACDeniedException(exception: BACDeniedException) {
|
||||
Toast.makeText(context, exception.toString(), Toast.LENGTH_SHORT).show()
|
||||
this@NfcFragment.onCardException(exception)
|
||||
}
|
||||
|
||||
override fun onPACEException(exception: PACEException) {
|
||||
Toast.makeText(context, exception.toString(), Toast.LENGTH_SHORT).show()
|
||||
this@NfcFragment.onCardException(exception)
|
||||
}
|
||||
|
||||
override fun onCardException(exception: CardServiceException) {
|
||||
val sw = exception.sw.toShort()
|
||||
when (sw) {
|
||||
ISO7816.SW_CLA_NOT_SUPPORTED -> {
|
||||
Toast.makeText(context, getString(R.string.warning_cla_not_supported), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
else -> {
|
||||
Toast.makeText(context, exception.toString(), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
this@NfcFragment.onCardException(exception)
|
||||
}
|
||||
|
||||
override fun onGeneralException(exception: Exception?) {
|
||||
Toast.makeText(context, exception!!.toString(), Toast.LENGTH_SHORT).show()
|
||||
this@NfcFragment.onCardException(exception)
|
||||
}
|
||||
})
|
||||
|
||||
disposable.add(subscribe)
|
||||
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
val activity = activity
|
||||
if (activity is NfcFragment.NfcFragmentListener) {
|
||||
nfcFragmentListener = activity
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
nfcFragmentListener = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
binding?.valuePassportNumber?.text = getString(R.string.doc_number, mrzInfo!!.documentNumber)
|
||||
binding?.valueDOB?.text = getString(R.string.doc_dob, mrzInfo!!.dateOfBirth)
|
||||
binding?.valueExpirationDate?.text = getString(R.string.doc_expiry, mrzInfo!!.dateOfExpiry)
|
||||
|
||||
if (nfcFragmentListener != null) {
|
||||
nfcFragmentListener!!.onEnableNfc()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
if (nfcFragmentListener != null) {
|
||||
nfcFragmentListener!!.onDisableNfc()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
if (!disposable.isDisposed()) {
|
||||
disposable.dispose();
|
||||
}
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
protected fun onNFCSReadStart() {
|
||||
Log.d(TAG, "onNFCSReadStart")
|
||||
mHandler.post {
|
||||
binding?.progressBar?.visibility = View.VISIBLE }
|
||||
|
||||
}
|
||||
|
||||
protected fun onNFCReadFinish() {
|
||||
Log.d(TAG, "onNFCReadFinish")
|
||||
mHandler.post { binding?.progressBar?.visibility = View.GONE }
|
||||
}
|
||||
|
||||
protected fun onCardException(cardException: Exception?) {
|
||||
mHandler.post {
|
||||
if (nfcFragmentListener != null) {
|
||||
nfcFragmentListener?.onCardException(cardException)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun onPassportRead(passport: Passport?) {
|
||||
mHandler.post {
|
||||
if (nfcFragmentListener != null) {
|
||||
nfcFragmentListener?.onPassportRead(passport)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface NfcFragmentListener {
|
||||
fun onEnableNfc()
|
||||
fun onDisableNfc()
|
||||
fun onPassportRead(passport: Passport?)
|
||||
fun onCardException(cardException: Exception?)
|
||||
}
|
||||
|
||||
|
||||
|
||||
companion object {
|
||||
private val TAG = NfcFragment::class.java.simpleName
|
||||
|
||||
init {
|
||||
Security.insertProviderAt(org.spongycastle.jce.provider.BouncyCastleProvider(), 1)
|
||||
}
|
||||
fun newInstance(mrzInfo: MRZInfo): NfcFragment {
|
||||
val myFragment = NfcFragment()
|
||||
val args = Bundle()
|
||||
args.putSerializable(IntentData.KEY_MRZ_INFO, mrzInfo)
|
||||
myFragment.arguments = args
|
||||
return myFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,412 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.ui.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Bundle
|
||||
import androidx.core.content.ContextCompat
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import org.jmrtd.FeatureStatus
|
||||
import org.jmrtd.VerificationStatus
|
||||
|
||||
import java.security.MessageDigest
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
import example.jllarraz.com.passportreader.R
|
||||
import example.jllarraz.com.passportreader.common.IntentData
|
||||
import example.jllarraz.com.passportreader.data.Passport
|
||||
import example.jllarraz.com.passportreader.databinding.FragmentPassportDetailsBinding
|
||||
import example.jllarraz.com.passportreader.utils.StringUtils
|
||||
import java.util.*
|
||||
|
||||
class PassportDetailsFragment : androidx.fragment.app.Fragment() {
|
||||
|
||||
private var passportDetailsFragmentListener: PassportDetailsFragmentListener? = null
|
||||
|
||||
internal var simpleDateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH)
|
||||
|
||||
private var passport: Passport? = null
|
||||
|
||||
private var binding:FragmentPassportDetailsBinding?=null
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
binding = FragmentPassportDetailsBinding.inflate(inflater, container, false)
|
||||
|
||||
return binding?.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val arguments = arguments
|
||||
if (arguments!!.containsKey(IntentData.KEY_PASSPORT)) {
|
||||
passport = arguments.getParcelable<Passport>(IntentData.KEY_PASSPORT)
|
||||
} else {
|
||||
//error
|
||||
}
|
||||
|
||||
binding?.iconPhoto?.setOnClickListener {
|
||||
var bitmap = passport?.face
|
||||
if (bitmap == null) {
|
||||
bitmap = passport!!.portrait
|
||||
}
|
||||
if (passportDetailsFragmentListener != null) {
|
||||
passportDetailsFragmentListener?.onImageSelected(bitmap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
refreshData(passport)
|
||||
}
|
||||
|
||||
private fun refreshData(passport: Passport?) {
|
||||
if (passport == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (passport.face != null) {
|
||||
//Add teh face
|
||||
binding?.iconPhoto?.setImageBitmap(passport.face)
|
||||
} else if (passport.portrait != null) {
|
||||
//If we don't have the face, we try with the portrait
|
||||
binding?.iconPhoto?.setImageBitmap(passport.portrait)
|
||||
}
|
||||
|
||||
val personDetails = passport.personDetails
|
||||
if (personDetails != null) {
|
||||
val name = personDetails.primaryIdentifier!!.replace("<", "")
|
||||
val surname = personDetails.secondaryIdentifier!!.replace("<", "")
|
||||
binding?.valueName?.text = getString(R.string.name, name, surname)
|
||||
binding?.valueDOB?.text = personDetails.dateOfBirth
|
||||
binding?.valueGender?.text = personDetails.gender?.name
|
||||
binding?.valuePassportNumber?.text = personDetails.documentNumber
|
||||
binding?.valueExpirationDate?.text = personDetails.dateOfExpiry
|
||||
binding?.valueIssuingState?.text = personDetails.issuingState
|
||||
binding?.valueNationality?.text = personDetails.nationality
|
||||
}
|
||||
|
||||
val additionalPersonDetails = passport.additionalPersonDetails
|
||||
if (additionalPersonDetails != null) {
|
||||
//This object it's not available in the majority of passports
|
||||
binding?.cardViewAdditionalPersonInformation?.visibility = View.VISIBLE
|
||||
|
||||
if (additionalPersonDetails.custodyInformation != null) {
|
||||
binding?.valueCustody?.text = additionalPersonDetails.custodyInformation
|
||||
}
|
||||
if (additionalPersonDetails.fullDateOfBirth != null) {
|
||||
|
||||
binding?.valueDateOfBirth?.text = additionalPersonDetails.fullDateOfBirth
|
||||
}
|
||||
if (additionalPersonDetails.otherNames != null && additionalPersonDetails.otherNames!!.size > 0) {
|
||||
binding?.valueOtherNames?.text = arrayToString(additionalPersonDetails.otherNames!!)
|
||||
}
|
||||
if (additionalPersonDetails.otherValidTDNumbers != null && additionalPersonDetails.otherValidTDNumbers!!.size > 0) {
|
||||
binding?.valueOtherTdNumbers?.text = arrayToString(additionalPersonDetails.otherValidTDNumbers!!)
|
||||
}
|
||||
if (additionalPersonDetails.permanentAddress != null && additionalPersonDetails.permanentAddress!!.size > 0) {
|
||||
binding?.valuePermanentAddress?.text = arrayToString(additionalPersonDetails.permanentAddress!!)
|
||||
}
|
||||
|
||||
if (additionalPersonDetails.personalNumber != null) {
|
||||
binding?.valuePersonalNumber?.text = additionalPersonDetails.personalNumber
|
||||
}
|
||||
|
||||
if (additionalPersonDetails.personalSummary != null) {
|
||||
binding?.valuePersonalSummary?.text = additionalPersonDetails.personalSummary
|
||||
}
|
||||
|
||||
if (additionalPersonDetails.placeOfBirth != null && additionalPersonDetails.placeOfBirth!!.size > 0) {
|
||||
binding?.valuePlaceOfBirth?.text = arrayToString(additionalPersonDetails.placeOfBirth!!)
|
||||
}
|
||||
|
||||
if (additionalPersonDetails.profession != null) {
|
||||
binding?.valueProfession?.text = additionalPersonDetails.profession
|
||||
}
|
||||
|
||||
if (additionalPersonDetails.telephone != null) {
|
||||
binding?.valueTelephone?.text = additionalPersonDetails.telephone
|
||||
}
|
||||
|
||||
if (additionalPersonDetails.title != null) {
|
||||
binding?.valueTitle?.text = additionalPersonDetails.title
|
||||
}
|
||||
} else {
|
||||
binding?.cardViewAdditionalPersonInformation?.visibility = View.GONE
|
||||
}
|
||||
|
||||
val additionalDocumentDetails = passport.additionalDocumentDetails
|
||||
if (additionalDocumentDetails != null) {
|
||||
binding?.cardViewAdditionalDocumentInformation?.visibility = View.VISIBLE
|
||||
|
||||
if (additionalDocumentDetails.dateAndTimeOfPersonalization != null) {
|
||||
binding?.valueDatePersonalization?.text = additionalDocumentDetails.dateAndTimeOfPersonalization
|
||||
}
|
||||
if (additionalDocumentDetails.dateOfIssue != null) {
|
||||
binding?.valueDateIssue?.text = additionalDocumentDetails.dateOfIssue
|
||||
}
|
||||
|
||||
if (additionalDocumentDetails.endorsementsAndObservations != null) {
|
||||
binding?.valueEndorsements?.text = additionalDocumentDetails.endorsementsAndObservations
|
||||
}
|
||||
|
||||
if (additionalDocumentDetails.issuingAuthority != null) {
|
||||
binding?.valueIssuingAuthority?.text = additionalDocumentDetails.issuingAuthority
|
||||
}
|
||||
|
||||
if (additionalDocumentDetails.namesOfOtherPersons != null) {
|
||||
binding?.valueNamesOtherPersons?.text = arrayToString(additionalDocumentDetails.namesOfOtherPersons!!)
|
||||
}
|
||||
|
||||
if (additionalDocumentDetails.personalizationSystemSerialNumber != null) {
|
||||
binding?.valueSystemSerialNumber?.text = additionalDocumentDetails.personalizationSystemSerialNumber
|
||||
}
|
||||
|
||||
if (additionalDocumentDetails.taxOrExitRequirements != null) {
|
||||
binding?.valueTaxExit?.text = additionalDocumentDetails.taxOrExitRequirements
|
||||
}
|
||||
} else {
|
||||
binding?.cardViewAdditionalDocumentInformation?.visibility = View.GONE
|
||||
}
|
||||
|
||||
displayAuthenticationStatus(passport.verificationStatus, passport.featureStatus!!)
|
||||
displayWarningTitle(passport.verificationStatus, passport.featureStatus!!)
|
||||
|
||||
|
||||
val sodFile = passport.sodFile
|
||||
if (sodFile != null) {
|
||||
val countrySigningCertificate = sodFile.issuerX500Principal
|
||||
val dnRFC2253 = countrySigningCertificate.getName(X500Principal.RFC2253)
|
||||
val dnCANONICAL = countrySigningCertificate.getName(X500Principal.CANONICAL)
|
||||
val dnRFC1779 = countrySigningCertificate.getName(X500Principal.RFC1779)
|
||||
|
||||
val name = countrySigningCertificate.name
|
||||
//new X509Certificate(countrySigningCertificate);
|
||||
|
||||
val docSigningCertificate = sodFile.docSigningCertificate
|
||||
|
||||
if (docSigningCertificate != null) {
|
||||
binding?.valueDocumentSigningCertificateSerialNumber?.text = docSigningCertificate.serialNumber.toString()
|
||||
binding?.valueDocumentSigningCertificatePublicKeyAlgorithm?.text = docSigningCertificate.publicKey.algorithm
|
||||
binding?.valueDocumentSigningCertificateSignatureAlgorithm?.text = docSigningCertificate.sigAlgName
|
||||
|
||||
try {
|
||||
binding?.valueDocumentSigningCertificateThumbprint?.text = StringUtils.bytesToHex(MessageDigest.getInstance("SHA-1").digest(
|
||||
docSigningCertificate.encoded)).uppercase()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
binding?.valueDocumentSigningCertificateIssuer?.text = docSigningCertificate.issuerDN.name
|
||||
binding?.valueDocumentSigningCertificateSubject?.text = docSigningCertificate.subjectDN.name
|
||||
binding?.valueDocumentSigningCertificateValidFrom?.text = simpleDateFormat.format(docSigningCertificate.notBefore)
|
||||
binding?.valueDocumentSigningCertificateValidTo?.text = simpleDateFormat.format(docSigningCertificate.notAfter)
|
||||
|
||||
} else {
|
||||
binding?.cardViewDocumentSigningCertificate?.visibility = View.GONE
|
||||
}
|
||||
|
||||
} else {
|
||||
binding?.cardViewDocumentSigningCertificate?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayWarningTitle(verificationStatus: VerificationStatus?, featureStatus: FeatureStatus) {
|
||||
var colorCard = android.R.color.holo_green_light
|
||||
var message = ""
|
||||
var title = ""
|
||||
if (featureStatus.hasCA() == FeatureStatus.Verdict.PRESENT) {
|
||||
if (verificationStatus!!.ca == VerificationStatus.Verdict.SUCCEEDED && verificationStatus.ht == VerificationStatus.Verdict.SUCCEEDED && verificationStatus.cs == VerificationStatus.Verdict.SUCCEEDED) {
|
||||
//Everything is fine
|
||||
colorCard = android.R.color.holo_green_light
|
||||
title = getString(R.string.document_valid_passport)
|
||||
message = getString(R.string.document_chip_content_success)
|
||||
} else if (verificationStatus.ca == VerificationStatus.Verdict.FAILED) {
|
||||
//Chip authentication failed
|
||||
colorCard = android.R.color.holo_red_light
|
||||
title = getString(R.string.document_invalid_passport)
|
||||
message = getString(R.string.document_chip_failure)
|
||||
} else if (verificationStatus.ht == VerificationStatus.Verdict.FAILED) {
|
||||
//Document information
|
||||
colorCard = android.R.color.holo_red_light
|
||||
title = getString(R.string.document_invalid_passport)
|
||||
message = getString(R.string.document_document_failure)
|
||||
} else if (verificationStatus.cs == VerificationStatus.Verdict.FAILED) {
|
||||
//CSCA information
|
||||
colorCard = android.R.color.holo_red_light
|
||||
title = getString(R.string.document_invalid_passport)
|
||||
message = getString(R.string.document_csca_failure)
|
||||
} else {
|
||||
//Unknown
|
||||
colorCard = android.R.color.darker_gray
|
||||
title = getString(R.string.document_unknown_passport_title)
|
||||
message = getString(R.string.document_unknown_passport_message)
|
||||
}
|
||||
} else if (featureStatus.hasCA() == FeatureStatus.Verdict.NOT_PRESENT) {
|
||||
if (verificationStatus!!.ht == VerificationStatus.Verdict.SUCCEEDED) {
|
||||
//Document information is fine
|
||||
colorCard = android.R.color.holo_green_light
|
||||
title = getString(R.string.document_valid_passport)
|
||||
message = getString(R.string.document_content_success)
|
||||
} else if (verificationStatus.ht == VerificationStatus.Verdict.FAILED) {
|
||||
//Document information
|
||||
colorCard = android.R.color.holo_red_light
|
||||
title = getString(R.string.document_invalid_passport)
|
||||
message = getString(R.string.document_document_failure)
|
||||
} else if (verificationStatus.cs == VerificationStatus.Verdict.FAILED) {
|
||||
//CSCA information
|
||||
colorCard = android.R.color.holo_red_light
|
||||
title = getString(R.string.document_invalid_passport)
|
||||
message = getString(R.string.document_csca_failure)
|
||||
} else {
|
||||
//Unknown
|
||||
colorCard = android.R.color.darker_gray
|
||||
title = getString(R.string.document_unknown_passport_title)
|
||||
message = getString(R.string.document_unknown_passport_message)
|
||||
}
|
||||
} else {
|
||||
//Unknown
|
||||
colorCard = android.R.color.darker_gray
|
||||
title = getString(R.string.document_unknown_passport_title)
|
||||
message = getString(R.string.document_unknown_passport_message)
|
||||
}
|
||||
binding?.cardViewWarning?.setCardBackgroundColor(resources.getColor(colorCard))
|
||||
binding?.textWarningTitle?.text = title
|
||||
binding?.textWarningMessage?.text = message
|
||||
}
|
||||
|
||||
|
||||
private fun displayAuthenticationStatus(verificationStatus: VerificationStatus?, featureStatus: FeatureStatus) {
|
||||
|
||||
if (featureStatus.hasBAC() == FeatureStatus.Verdict.PRESENT) {
|
||||
binding?.rowBac?.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding?.rowBac?.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (featureStatus.hasAA() == FeatureStatus.Verdict.PRESENT) {
|
||||
binding?.rowActive?.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding?.rowActive?.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (featureStatus.hasSAC() == FeatureStatus.Verdict.PRESENT) {
|
||||
binding?.rowPace?.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding?.rowPace?.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (featureStatus.hasCA() == FeatureStatus.Verdict.PRESENT) {
|
||||
binding?.rowChip?.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding?.rowChip?.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (featureStatus.hasEAC() == FeatureStatus.Verdict.PRESENT) {
|
||||
binding?.rowEac?.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding?.rowEac?.visibility = View.GONE
|
||||
}
|
||||
|
||||
displayVerificationStatusIcon(binding?.valueBac, verificationStatus!!.bac)
|
||||
displayVerificationStatusIcon(binding?.valuePace, verificationStatus.sac)
|
||||
displayVerificationStatusIcon(binding?.valuePassive, verificationStatus.ht)
|
||||
displayVerificationStatusIcon(binding?.valueActive, verificationStatus.aa)
|
||||
displayVerificationStatusIcon(binding?.valueDocumentSigning, verificationStatus.ds)
|
||||
displayVerificationStatusIcon(binding?.valueCountrySigning, verificationStatus.cs)
|
||||
displayVerificationStatusIcon(binding?.valueChip, verificationStatus.ca)
|
||||
displayVerificationStatusIcon(binding?.valueEac, verificationStatus.eac)
|
||||
}
|
||||
|
||||
private fun displayVerificationStatusIcon(imageView: ImageView?, verdict: VerificationStatus.Verdict?) {
|
||||
var verdict = verdict
|
||||
if (verdict == null) {
|
||||
verdict = VerificationStatus.Verdict.UNKNOWN
|
||||
}
|
||||
val resourceIconId: Int
|
||||
val resourceColorId: Int
|
||||
when (verdict) {
|
||||
VerificationStatus.Verdict.SUCCEEDED -> {
|
||||
resourceIconId = R.drawable.ic_check_circle_outline
|
||||
resourceColorId = android.R.color.holo_green_light
|
||||
}
|
||||
VerificationStatus.Verdict.FAILED -> {
|
||||
resourceIconId = R.drawable.ic_close_circle_outline
|
||||
resourceColorId = android.R.color.holo_red_light
|
||||
}
|
||||
VerificationStatus.Verdict.NOT_PRESENT -> {
|
||||
resourceIconId = R.drawable.ic_close_circle_outline
|
||||
resourceColorId = android.R.color.darker_gray
|
||||
}
|
||||
VerificationStatus.Verdict.NOT_CHECKED -> {
|
||||
resourceIconId = R.drawable.ic_help_circle_outline
|
||||
resourceColorId = android.R.color.holo_orange_light
|
||||
}
|
||||
VerificationStatus.Verdict.UNKNOWN -> {
|
||||
resourceIconId = R.drawable.ic_close_circle_outline
|
||||
resourceColorId = android.R.color.darker_gray
|
||||
}
|
||||
else -> {
|
||||
resourceIconId = R.drawable.ic_close_circle_outline
|
||||
resourceColorId = android.R.color.darker_gray
|
||||
}
|
||||
}
|
||||
|
||||
imageView!!.setImageResource(resourceIconId)
|
||||
imageView.setColorFilter(ContextCompat.getColor(requireActivity(), resourceColorId), android.graphics.PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
val activity = activity
|
||||
if (activity is PassportDetailsFragment.PassportDetailsFragmentListener) {
|
||||
passportDetailsFragmentListener = activity
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
passportDetailsFragmentListener = null
|
||||
super.onDetach()
|
||||
|
||||
}
|
||||
|
||||
interface PassportDetailsFragmentListener {
|
||||
fun onImageSelected(bitmap: Bitmap?)
|
||||
}
|
||||
|
||||
|
||||
private fun arrayToString(array: List<String>): String {
|
||||
var temp = ""
|
||||
val iterator = array.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
temp += iterator.next() + "\n"
|
||||
}
|
||||
if (temp.endsWith("\n")) {
|
||||
temp = temp.substring(0, temp.length - "\n".length)
|
||||
}
|
||||
return temp
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
|
||||
fun newInstance(passport: Passport): PassportDetailsFragment {
|
||||
val myFragment = PassportDetailsFragment()
|
||||
val args = Bundle()
|
||||
args.putParcelable(IntentData.KEY_PASSPORT, passport)
|
||||
myFragment.arguments = args
|
||||
return myFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.ui.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
||||
import example.jllarraz.com.passportreader.common.IntentData
|
||||
import example.jllarraz.com.passportreader.databinding.FragmentPhotoBinding
|
||||
|
||||
class PassportPhotoFragment : androidx.fragment.app.Fragment() {
|
||||
|
||||
private var passportPhotoFragmentListener: PassportPhotoFragmentListener? = null
|
||||
|
||||
private var bitmap: Bitmap? = null
|
||||
|
||||
|
||||
private var binding:FragmentPhotoBinding?=null
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
binding = FragmentPhotoBinding.inflate(inflater, container, false)
|
||||
return binding?.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val arguments = arguments
|
||||
if (arguments!!.containsKey(IntentData.KEY_IMAGE)) {
|
||||
bitmap = arguments.getParcelable<Bitmap>(IntentData.KEY_IMAGE)
|
||||
} else {
|
||||
//error
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
refreshData(bitmap)
|
||||
}
|
||||
|
||||
private fun refreshData(bitmap: Bitmap?) {
|
||||
if (bitmap == null) {
|
||||
return
|
||||
}
|
||||
binding?.image?.setImageBitmap(bitmap)
|
||||
}
|
||||
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
val activity = activity
|
||||
if (activity is PassportPhotoFragmentListener) {
|
||||
passportPhotoFragmentListener = activity
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
passportPhotoFragmentListener = null
|
||||
super.onDetach()
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
interface PassportPhotoFragmentListener
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(bitmap: Bitmap): PassportPhotoFragment {
|
||||
val myFragment = PassportPhotoFragment()
|
||||
val args = Bundle()
|
||||
args.putParcelable(IntentData.KEY_IMAGE, bitmap)
|
||||
myFragment.arguments = args
|
||||
return myFragment
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.ui.fragments
|
||||
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
import com.mobsandgeeks.saripaar.ValidationError
|
||||
import com.mobsandgeeks.saripaar.Validator
|
||||
import example.jllarraz.com.passportreader.R
|
||||
import example.jllarraz.com.passportreader.databinding.FragmentSelectionBinding
|
||||
import example.jllarraz.com.passportreader.network.MasterListService
|
||||
import example.jllarraz.com.passportreader.ui.validators.DateRule
|
||||
import example.jllarraz.com.passportreader.ui.validators.DocumentNumberRule
|
||||
import example.jllarraz.com.passportreader.utils.KeyStoreUtils
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import net.sf.scuba.data.Gender
|
||||
import org.jmrtd.lds.icao.MRZInfo
|
||||
import java.security.Security
|
||||
import java.security.cert.Certificate
|
||||
|
||||
class SelectionFragment : androidx.fragment.app.Fragment(), Validator.ValidationListener {
|
||||
|
||||
private var radioGroup: RadioGroup? = null
|
||||
private var linearLayoutManual: LinearLayout? = null
|
||||
private var linearLayoutAutomatic: LinearLayout? = null
|
||||
private var appCompatEditTextDocumentNumber: AppCompatEditText? = null
|
||||
private var appCompatEditTextDocumentExpiration: AppCompatEditText? = null
|
||||
private var appCompatEditTextDateOfBirth: AppCompatEditText? = null
|
||||
private var buttonReadNFC: Button? = null
|
||||
|
||||
private var mValidator: Validator? = null
|
||||
private var selectionFragmentListener: SelectionFragmentListener? = null
|
||||
var disposable = CompositeDisposable()
|
||||
|
||||
private var binding:FragmentSelectionBinding?=null
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
binding = FragmentSelectionBinding.inflate(inflater, container, false)
|
||||
return binding?.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
radioGroup = view.findViewById(R.id.radioButtonDataEntry)
|
||||
linearLayoutManual = view.findViewById(R.id.layoutManual)
|
||||
linearLayoutAutomatic = view.findViewById(R.id.layoutAutomatic)
|
||||
appCompatEditTextDocumentNumber = view.findViewById(R.id.documentNumber)
|
||||
appCompatEditTextDocumentExpiration = view.findViewById(R.id.documentExpiration)
|
||||
appCompatEditTextDateOfBirth = view.findViewById(R.id.documentDateOfBirth)
|
||||
buttonReadNFC = view.findViewById(R.id.buttonReadNfc)
|
||||
|
||||
radioGroup!!.setOnCheckedChangeListener { group, checkedId ->
|
||||
when (checkedId) {
|
||||
R.id.radioButtonManual -> {
|
||||
linearLayoutManual!!.visibility = View.VISIBLE
|
||||
linearLayoutAutomatic!!.visibility = View.GONE
|
||||
}
|
||||
R.id.radioButtonOcr -> {
|
||||
linearLayoutManual!!.visibility = View.GONE
|
||||
linearLayoutAutomatic!!.visibility = View.VISIBLE
|
||||
if (selectionFragmentListener != null) {
|
||||
selectionFragmentListener!!.onMrzRequest()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buttonReadNFC!!.setOnClickListener { validateFields() }
|
||||
|
||||
mValidator = Validator(this)
|
||||
mValidator!!.setValidationListener(this)
|
||||
|
||||
mValidator!!.put(appCompatEditTextDocumentNumber!!, DocumentNumberRule())
|
||||
mValidator!!.put(appCompatEditTextDocumentExpiration!!, DateRule())
|
||||
mValidator!!.put(appCompatEditTextDateOfBirth!!, DateRule())
|
||||
|
||||
|
||||
binding?.buttonDownloadCSCA?.setOnClickListener {
|
||||
requireDownloadCSCA()
|
||||
}
|
||||
binding?.buttonDeleteCSCA?.setOnClickListener {
|
||||
val subscribe = cleanCSCAFolder()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { result ->
|
||||
Toast.makeText(requireContext(), "CSCA Folder deleted", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
disposable.add(subscribe)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun validateFields() {
|
||||
try {
|
||||
mValidator!!.removeRules(appCompatEditTextDocumentNumber!!)
|
||||
mValidator!!.removeRules(appCompatEditTextDocumentExpiration!!)
|
||||
mValidator!!.removeRules(appCompatEditTextDateOfBirth!!)
|
||||
|
||||
mValidator!!.put(appCompatEditTextDocumentNumber!!, DocumentNumberRule())
|
||||
mValidator!!.put(appCompatEditTextDocumentExpiration!!, DateRule())
|
||||
mValidator!!.put(appCompatEditTextDateOfBirth!!, DateRule())
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
mValidator!!.validate()
|
||||
}
|
||||
|
||||
fun selectManualToggle() {
|
||||
radioGroup!!.check(R.id.radioButtonManual)
|
||||
}
|
||||
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
val activity = activity
|
||||
if (activity is SelectionFragment.SelectionFragmentListener) {
|
||||
selectionFragmentListener = activity
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
selectionFragmentListener = null
|
||||
super.onDetach()
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
||||
if (!disposable.isDisposed) {
|
||||
disposable.dispose()
|
||||
}
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
|
||||
override fun onValidationSucceeded() {
|
||||
|
||||
val documentNumber = appCompatEditTextDocumentNumber!!.text!!.toString()
|
||||
val dateOfBirth = appCompatEditTextDateOfBirth!!.text!!.toString()
|
||||
val documentExpiration = appCompatEditTextDocumentExpiration!!.text!!.toString()
|
||||
|
||||
val mrzInfo = MRZInfo("P",
|
||||
"ESP",
|
||||
"DUMMY",
|
||||
"DUMMY",
|
||||
documentNumber,
|
||||
"ESP",
|
||||
dateOfBirth,
|
||||
Gender.MALE,
|
||||
documentExpiration,
|
||||
"DUMMY"
|
||||
)
|
||||
if (selectionFragmentListener != null) {
|
||||
selectionFragmentListener!!.onPassportRead(mrzInfo)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onValidationFailed(errors: List<ValidationError>) {
|
||||
for (error in errors) {
|
||||
val view = error.view
|
||||
val message = error.getCollatedErrorMessage(context)
|
||||
|
||||
// Display error messages ;)
|
||||
if (view is EditText) {
|
||||
view.error = message
|
||||
} else {
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Listener
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
interface SelectionFragmentListener {
|
||||
fun onPassportRead(mrzInfo: MRZInfo)
|
||||
fun onMrzRequest()
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Download Master List Spanish Certificates
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
fun requireDownloadCSCA(){
|
||||
val downloadsFolder = requireContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)!!
|
||||
val keyStore = KeyStoreUtils().readKeystoreFromFile(downloadsFolder)
|
||||
if(keyStore==null || keyStore.aliases().toList().isNullOrEmpty()){
|
||||
//No certificates downloaded
|
||||
downloadSpanishMasterList()
|
||||
} else{
|
||||
//Certificates in the keystore
|
||||
val dialog = AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.keystore_not_empty_title)
|
||||
.setMessage(R.string.keystore_not_empty_message)
|
||||
.setPositiveButton(android.R.string.ok, object : DialogInterface.OnClickListener {
|
||||
override fun onClick(dialog: DialogInterface?, which: Int) {
|
||||
val subscribe = cleanCSCAFolder()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { result ->
|
||||
downloadSpanishMasterList()
|
||||
}
|
||||
disposable.add(subscribe)
|
||||
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, object : DialogInterface.OnClickListener {
|
||||
override fun onClick(dialog: DialogInterface?, which: Int) {
|
||||
//WE don't do anything
|
||||
}
|
||||
|
||||
})
|
||||
.create()
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun cleanCSCAFolder():Single<Boolean>{
|
||||
return Single.fromCallable {
|
||||
try {
|
||||
val downloadsFolder = requireContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)!!
|
||||
val listFiles = downloadsFolder.listFiles()
|
||||
for (tempFile in listFiles) {
|
||||
tempFile.delete()
|
||||
}
|
||||
val listFiles1 = downloadsFolder.listFiles()
|
||||
true
|
||||
}catch (e:java.lang.Exception){
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadSpanishMasterList(){
|
||||
val masterListService = MasterListService(requireContext(), "https://www.dnielectronico.es/")
|
||||
val subscribe = masterListService.getSpanishMasterList()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ certificates ->
|
||||
saveCertificates(certificates)
|
||||
},
|
||||
{error->
|
||||
Toast.makeText(requireContext(), "No certificates has been download "+error, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
)
|
||||
disposable.add(subscribe)
|
||||
}
|
||||
|
||||
fun saveCertificates(certificates:ArrayList<Certificate>){
|
||||
val subscribe = Single.fromCallable {
|
||||
try {
|
||||
val size = certificates.size
|
||||
Log.d(TAG, "Number of certificates: " + size)
|
||||
val map = KeyStoreUtils().toMap(certificates)
|
||||
val downloadsFolder = requireContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)!!
|
||||
KeyStoreUtils().toKeyStoreFile(map, outputDir = downloadsFolder)
|
||||
size
|
||||
} catch (e: java.lang.Exception) {
|
||||
e.printStackTrace()
|
||||
-1
|
||||
}
|
||||
}.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { result ->
|
||||
if(result>0) {
|
||||
Toast.makeText(requireContext(), "Certificates Downloaded: "+result, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(requireContext(), "No certificates has been download", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
disposable.add(subscribe)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = SelectionFragment::class.java.simpleName
|
||||
init {
|
||||
Security.insertProviderAt(org.spongycastle.jce.provider.BouncyCastleProvider(), 1)
|
||||
}
|
||||
fun newInstance(mrzInfo: MRZInfo, face: Bitmap): PassportDetailsFragment {
|
||||
val myFragment = PassportDetailsFragment()
|
||||
val args = Bundle()
|
||||
myFragment.arguments = args
|
||||
return myFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.ui.validators
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
import android.widget.EditText
|
||||
|
||||
import com.mobsandgeeks.saripaar.QuickRule
|
||||
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
import example.jllarraz.com.passportreader.R
|
||||
|
||||
|
||||
/**
|
||||
* Created by Surface on 15/08/2017.
|
||||
*/
|
||||
|
||||
class DateRule : QuickRule<AppCompatEditText>() {
|
||||
|
||||
override fun isValid(editText: AppCompatEditText): Boolean {
|
||||
val text = editText.text!!.toString().trim { it <= ' ' }
|
||||
val patternDate = Pattern.compile(REGEX)
|
||||
val matcherDate = patternDate.matcher(text)
|
||||
return matcherDate.find()
|
||||
}
|
||||
|
||||
override fun getMessage(context: Context): String {
|
||||
return context.getString(R.string.error_validation_date)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val REGEX = "[0-9]{6}$"
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.ui.validators
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
import android.widget.EditText
|
||||
|
||||
import com.mobsandgeeks.saripaar.QuickRule
|
||||
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
import example.jllarraz.com.passportreader.R
|
||||
|
||||
|
||||
/**
|
||||
* Created by Surface on 15/08/2017.
|
||||
*/
|
||||
|
||||
class DocumentNumberRule : QuickRule<AppCompatEditText>() {
|
||||
|
||||
override fun isValid(editText: AppCompatEditText): Boolean {
|
||||
val text = editText.text!!.toString().trim { it <= ' ' }
|
||||
val patternDate = Pattern.compile(REGEX)
|
||||
val matcherDate = patternDate.matcher(text)
|
||||
return matcherDate.find()
|
||||
}
|
||||
|
||||
override fun getMessage(context: Context): String {
|
||||
return context.getString(R.string.error_validation_document_number)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val REGEX = "[A-Z0-9<]{9}$"
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.utils
|
||||
|
||||
import java.security.PrivateKey
|
||||
import java.security.cert.Certificate
|
||||
|
||||
/**
|
||||
* Encapsulates the terminal key and associated certificate chain for terminal authentication.
|
||||
*/
|
||||
class EACCredentials
|
||||
/**
|
||||
* Creates EAC credentials.
|
||||
*
|
||||
* @param privateKey
|
||||
* @param chain
|
||||
*/
|
||||
(val privateKey: PrivateKey, val chain: Array<Certificate>)
|
||||
@@ -1,157 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.media.Image
|
||||
import android.util.Log
|
||||
import androidx.annotation.Nullable
|
||||
import example.jllarraz.com.passportreader.mlkit.FrameMetadata
|
||||
|
||||
import org.jnbis.internal.WsqDecoder
|
||||
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.DataInputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
import jj2000.j2k.decoder.Decoder
|
||||
import jj2000.j2k.util.ParameterList
|
||||
|
||||
|
||||
import org.jmrtd.lds.ImageInfo.WSQ_MIME_TYPE
|
||||
import kotlin.experimental.and
|
||||
|
||||
object ImageUtil {
|
||||
|
||||
private val TAG = ImageUtil::class.java.simpleName
|
||||
|
||||
var JPEG_MIME_TYPE = "image/jpeg"
|
||||
var JPEG2000_MIME_TYPE = "image/jp2"
|
||||
var JPEG2000_ALT_MIME_TYPE = "image/jpeg2000"
|
||||
var WSQ_MIME_TYPE = "image/x-wsq"
|
||||
|
||||
fun imageToByteArray(image: Image): ByteArray? {
|
||||
var data: ByteArray? = null
|
||||
if (image.format == ImageFormat.JPEG) {
|
||||
val planes = image.planes
|
||||
val buffer = planes[0].buffer
|
||||
data = ByteArray(buffer.capacity())
|
||||
buffer.get(data)
|
||||
return data
|
||||
} else if (image.format == ImageFormat.YUV_420_888) {
|
||||
data = NV21toJPEG(
|
||||
YUV_420_888toNV21(image),
|
||||
image.width, image.height)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
fun YUV_420_888toNV21(image: Image): ByteArray {
|
||||
val nv21: ByteArray
|
||||
val yBuffer = image.planes[0].buffer
|
||||
val uBuffer = image.planes[1].buffer
|
||||
val vBuffer = image.planes[2].buffer
|
||||
|
||||
val ySize = yBuffer.remaining()
|
||||
val uSize = uBuffer.remaining()
|
||||
val vSize = vBuffer.remaining()
|
||||
|
||||
nv21 = ByteArray(ySize + uSize + vSize)
|
||||
|
||||
//U and V are swapped
|
||||
yBuffer.get(nv21, 0, ySize)
|
||||
vBuffer.get(nv21, ySize, vSize)
|
||||
uBuffer.get(nv21, ySize + vSize, uSize)
|
||||
|
||||
return nv21
|
||||
}
|
||||
|
||||
private fun NV21toJPEG(nv21: ByteArray, width: Int, height: Int): ByteArray {
|
||||
val out = ByteArrayOutputStream()
|
||||
val yuv = YuvImage(nv21, ImageFormat.NV21, width, height, null)
|
||||
yuv.compressToJpeg(Rect(0, 0, width, height), 100, out)
|
||||
return out.toByteArray()
|
||||
}
|
||||
|
||||
|
||||
/* IMAGE DECODIFICATION METHODS */
|
||||
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun decodeImage(inputStream: InputStream, imageLength: Int, mimeType: String): Bitmap {
|
||||
var inputStream = inputStream
|
||||
/* DEBUG */
|
||||
synchronized(inputStream) {
|
||||
val dataIn = DataInputStream(inputStream)
|
||||
val bytes = ByteArray(imageLength)
|
||||
dataIn.readFully(bytes)
|
||||
inputStream = ByteArrayInputStream(bytes)
|
||||
}
|
||||
/* END DEBUG */
|
||||
|
||||
if (JPEG2000_MIME_TYPE.equals(mimeType, ignoreCase = true) || JPEG2000_ALT_MIME_TYPE.equals(mimeType, ignoreCase = true)) {
|
||||
val bitmap = org.jmrtd.jj2000.JJ2000Decoder.decode(inputStream)
|
||||
return toAndroidBitmap(bitmap)
|
||||
} else if (WSQ_MIME_TYPE.equals(mimeType, ignoreCase = true)) {
|
||||
//org.jnbis.Bitmap bitmap = WSQDecoder.decode(inputStream);
|
||||
val wsqDecoder = WsqDecoder()
|
||||
val bitmap = wsqDecoder.decode(inputStream.readBytes())
|
||||
val byteData = bitmap.pixels
|
||||
val intData = IntArray(byteData.size)
|
||||
for (j in byteData.indices) {
|
||||
intData[j] = -0x1000000 or ((byteData[j].toInt() and 0xFF) shl 16) or ((byteData[j].toInt() and 0xFF) shl 8) or (byteData[j].toInt() and 0xFF)
|
||||
}
|
||||
return Bitmap.createBitmap(intData, 0, bitmap.width, bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
|
||||
//return toAndroidBitmap(bitmap);
|
||||
} else {
|
||||
return BitmapFactory.decodeStream(inputStream)
|
||||
}
|
||||
}
|
||||
|
||||
fun rotateBitmap(source: Bitmap, angle: Float): Bitmap {
|
||||
val matrix = Matrix()
|
||||
matrix.postRotate(angle)
|
||||
return Bitmap.createBitmap(source, 0, 0, source.width, source.height, matrix, true)
|
||||
}
|
||||
|
||||
// Convert NV21 format byte buffer to bitmap.
|
||||
@Nullable
|
||||
fun getBitmap(data: ByteBuffer, metadata: FrameMetadata): Bitmap? {
|
||||
data.rewind()
|
||||
val imageInBuffer = ByteArray(data.limit())
|
||||
data.get(imageInBuffer, 0, imageInBuffer.size)
|
||||
try {
|
||||
val image = YuvImage(
|
||||
imageInBuffer, ImageFormat.NV21, metadata.width, metadata.height, null
|
||||
)
|
||||
if (image != null) {
|
||||
val stream = ByteArrayOutputStream()
|
||||
image.compressToJpeg(Rect(0, 0, metadata.width, metadata.height), 80, stream)
|
||||
|
||||
val bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size())
|
||||
|
||||
stream.close()
|
||||
return rotateBitmap(bmp, metadata.rotation.toFloat())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("VisionProcessorBase", "Error: " + e.message)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/* ONLY PRIVATE METHODS BELOW */
|
||||
|
||||
private fun toAndroidBitmap(bitmap: org.jmrtd.jj2000.Bitmap): Bitmap {
|
||||
val intData = bitmap.pixels
|
||||
return Bitmap.createBitmap(intData, 0, bitmap.width, bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.utils
|
||||
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import net.sf.scuba.data.Country
|
||||
import org.jmrtd.JMRTDSecurityProvider
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider
|
||||
import java.io.*
|
||||
import java.security.KeyStore
|
||||
import java.security.KeyStoreException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.Security
|
||||
import java.security.cert.*
|
||||
import java.util.*
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
|
||||
class KeyStoreUtils {
|
||||
|
||||
|
||||
@Throws(KeyStoreException::class, NoSuchAlgorithmException::class, CertificateException::class, IOException::class)
|
||||
fun toKeyStore(certificates: Map<String, X509Certificate>): KeyStore? {
|
||||
val jmrtdProvIndex = JMRTDSecurityProvider.beginPreferBouncyCastleProvider()
|
||||
return try {
|
||||
val keyStore: KeyStore = KeyStore.getInstance("PKCS12")
|
||||
keyStore.load(null)
|
||||
for ((alias, certificate) in certificates) {
|
||||
println("DEBUG: adding certificate \"$alias\" to key store.")
|
||||
keyStore.setCertificateEntry(alias, certificate)
|
||||
}
|
||||
keyStore
|
||||
} finally {
|
||||
JMRTDSecurityProvider.endPreferBouncyCastleProvider(jmrtdProvIndex)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(KeyStoreException::class, NoSuchAlgorithmException::class, CertificateException::class, IOException::class, IllegalStateException::class, IllegalArgumentException::class)
|
||||
fun toKeyStoreFile(certificates: Map<String, X509Certificate>, outputDir:File, fileName:String="csca.ks", password:String=""): Uri? {
|
||||
val toKeyStore = toKeyStore(certificates)
|
||||
/* Prepare output directory. */
|
||||
|
||||
if (!outputDir.exists()) {
|
||||
Log.d("", "DEBUG: output dir " + outputDir.path.toString() + " doesn't exist, creating it.")
|
||||
if (!outputDir.mkdirs()) {
|
||||
throw IllegalStateException("Could not create output dir \"" + outputDir.path.toString() + "\"")
|
||||
}
|
||||
}
|
||||
|
||||
if (!outputDir.isDirectory) {
|
||||
throw IllegalArgumentException("Output dir is not a directory")
|
||||
}
|
||||
|
||||
/* Write to keystore. */
|
||||
val outFile = File(outputDir, fileName)
|
||||
val out = FileOutputStream(outFile)
|
||||
toKeyStore?.store(out, "".toCharArray())
|
||||
|
||||
out.flush()
|
||||
out.close()
|
||||
return Uri.fromFile(outFile)
|
||||
}
|
||||
|
||||
fun readKeystoreFromFile(folder:File, fileName:String="csca.ks", password:String=""):KeyStore?{
|
||||
try{
|
||||
val file = File(folder, fileName)
|
||||
val keyStore: KeyStore = KeyStore.getInstance("PKCS12")
|
||||
val fileInputStream = FileInputStream(file)
|
||||
keyStore.load(fileInputStream, password.toCharArray())
|
||||
return keyStore
|
||||
}catch (e:java.lang.Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(CertificateEncodingException::class, IOException::class)
|
||||
fun toCertDir(certificates: Map<String, X509Certificate>, outputDir: String) {
|
||||
for ((alias, certificate) in certificates) {
|
||||
val outFile = File(outputDir, alias)
|
||||
val dataOut = DataOutputStream(FileOutputStream(outFile))
|
||||
dataOut.write(certificate.encoded)
|
||||
dataOut.close()
|
||||
}
|
||||
}
|
||||
|
||||
fun getCountry(principal: X500Principal): Country? {
|
||||
val issuerName: String = principal.getName("RFC1779")
|
||||
val startIndex = issuerName.indexOf("C=")
|
||||
require(startIndex >= 0) { "Could not get country from issuer name, $issuerName" }
|
||||
var endIndex = issuerName.indexOf(",", startIndex)
|
||||
if (endIndex < 0) {
|
||||
endIndex = issuerName.length
|
||||
}
|
||||
val countryCode = issuerName.substring(startIndex + 2, endIndex).trim { it <= ' ' }.uppercase()
|
||||
return try {
|
||||
Country.getInstance(countryCode)
|
||||
} catch (e: Exception) {
|
||||
object : Country() {
|
||||
override fun valueOf(): Int {
|
||||
return -1
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return "Unknown country ($countryCode)"
|
||||
}
|
||||
|
||||
override fun getNationality(): String {
|
||||
return "Unknown nationality ($countryCode)"
|
||||
}
|
||||
|
||||
override fun toAlpha2Code(): String {
|
||||
return countryCode
|
||||
}
|
||||
|
||||
override fun toAlpha3Code(): String {
|
||||
return "X$countryCode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toMap(certificates:List<Certificate>):Map<String, X509Certificate>{
|
||||
val treeMap = TreeMap<String, X509Certificate>()
|
||||
var i = 0
|
||||
for(certificate in certificates){
|
||||
val x509Certificate = certificate as X509Certificate
|
||||
val issuer = x509Certificate.getIssuerX500Principal()
|
||||
val subject = x509Certificate.getSubjectX500Principal()
|
||||
val serial = x509Certificate.getSerialNumber()
|
||||
val country = getCountry(issuer)
|
||||
val isSelfSigned = (issuer == null && subject == null) || subject.equals(issuer)
|
||||
val outName = country!!.toAlpha2Code().lowercase().toString() + "_" + (if (isSelfSigned) "root_" else "link_") + (++i) + ".cer"
|
||||
treeMap.put(outName, x509Certificate)
|
||||
|
||||
}
|
||||
return treeMap
|
||||
}
|
||||
fun toList(keyStore: KeyStore):List<Certificate>{
|
||||
val aliases = keyStore.aliases()
|
||||
val list = ArrayList<Certificate>()
|
||||
for(alias in aliases) {
|
||||
val certificate = keyStore.getCertificate(alias)
|
||||
list.add(certificate)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
fun toCertStore(type:String="Collection", keyStore: KeyStore):CertStore{
|
||||
return CertStore.getInstance(type, CollectionCertStoreParameters(toList(keyStore)))
|
||||
}
|
||||
|
||||
fun toAnchors(certificates: Collection<Certificate>): Set<TrustAnchor>{
|
||||
val anchors = HashSet<TrustAnchor>(certificates.size)
|
||||
for (certificate in certificates) {
|
||||
if (certificate is X509Certificate) {
|
||||
anchors.add(TrustAnchor(certificate, null))
|
||||
}
|
||||
}
|
||||
return anchors
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
companion object{
|
||||
init {
|
||||
Security.insertProviderAt(org.spongycastle.jce.provider.BouncyCastleProvider(), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.utils
|
||||
|
||||
|
||||
import org.jmrtd.lds.icao.MRZInfo
|
||||
|
||||
import java.util.ArrayList
|
||||
|
||||
object MRZUtil {
|
||||
|
||||
val TAG = MRZUtil::class.java.simpleName
|
||||
|
||||
private val PASSPORT_LINE_1 = "[P]{1}[A-Z<]{1}[A-Z<]{3}[A-Z0-9<]{39}$"
|
||||
private val PASSPORT_LINE_2 = "[A-Z0-9<]{9}[0-9]{1}[A-Z<]{3}[0-9]{6}[0-9]{1}[FM<]{1}[0-9]{6}[0-9]{1}[A-Z0-9<]{14}[0-9<]{1}[0-9]{1}$"
|
||||
|
||||
var mLines1 = ArrayList<String>()
|
||||
var mLines2 = ArrayList<String>()
|
||||
|
||||
val mrzInfo: MRZInfo
|
||||
@Throws(IllegalArgumentException::class)
|
||||
get() {
|
||||
val iteratorLine1 = mLines1.iterator()
|
||||
while (iteratorLine1.hasNext()) {
|
||||
val line1 = iteratorLine1.next()
|
||||
val iteratorLine2 = mLines2.iterator()
|
||||
while (iteratorLine2.hasNext()) {
|
||||
val line2 = iteratorLine2.next()
|
||||
try {
|
||||
return MRZInfo(line1 + "\n" + line2)
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
throw IllegalArgumentException("Unable to find a combination of lines that pass MRZ checksum")
|
||||
}
|
||||
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun cleanString(mrz: String): String {
|
||||
val lines = mrz.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
if (lines.size > 2) {
|
||||
return cleanLine1(lines[0]) + "\n" + cleanLine2(lines[1])
|
||||
}
|
||||
throw IllegalArgumentException("Not enough lines")
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun cleanLine1(line: String?): String {
|
||||
if (line == null || line.length != 44) {
|
||||
throw IllegalArgumentException("Line 1 doesnt have the right length")
|
||||
}
|
||||
val group1 = line.substring(0, 2)
|
||||
var group2 = line.substring(2, 5)
|
||||
val group3 = line.substring(5, line.length)
|
||||
|
||||
group2 = replaceNumberWithAlfa(group2)
|
||||
|
||||
|
||||
return group1 + group2 + group3
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun cleanLine2(line: String?): String {
|
||||
if (line == null || line.length != 44) {
|
||||
throw IllegalArgumentException("Line 2 doesnt have the right length")
|
||||
}
|
||||
|
||||
val group1 = line.substring(0, 9)
|
||||
var group2 = line.substring(9, 10)
|
||||
var group3 = line.substring(10, 13)
|
||||
var group4 = line.substring(13, 19)
|
||||
var group5 = line.substring(19, 20)
|
||||
val group6 = line.substring(20, 21)
|
||||
var group7 = line.substring(21, 27)
|
||||
var group8 = line.substring(27, 28)
|
||||
val group9 = line.substring(28, 42)
|
||||
var group10 = line.substring(42, 43)
|
||||
var group11 = line.substring(43, 44)
|
||||
|
||||
group2 = replaceAlfaWithNumber(group2)
|
||||
group3 = replaceNumberWithAlfa(group3)
|
||||
group4 = replaceAlfaWithNumber(group4)
|
||||
group5 = replaceAlfaWithNumber(group5)
|
||||
group7 = replaceAlfaWithNumber(group7)
|
||||
group8 = replaceAlfaWithNumber(group8)
|
||||
group10 = replaceAlfaWithNumber(group10)
|
||||
group11 = replaceAlfaWithNumber(group11)
|
||||
|
||||
return group1 + group2 + group3 + group4 + group5 + group6 + group7 + group8 + group9 + group10 + group11
|
||||
}
|
||||
|
||||
fun replaceNumberWithAlfa(str: String): String {
|
||||
var str = str
|
||||
str = str.replace("0".toRegex(), "O")
|
||||
str = str.replace("1".toRegex(), "I")
|
||||
str = str.replace("2".toRegex(), "Z")
|
||||
str = str.replace("5".toRegex(), "S")
|
||||
return str
|
||||
}
|
||||
|
||||
fun replaceAlfaWithNumber(str: String): String {
|
||||
var str = str
|
||||
str = str.replace("O".toRegex(), "0")
|
||||
str = str.replace("I".toRegex(), "1")
|
||||
str = str.replace("Z".toRegex(), "2")
|
||||
str = str.replace("S".toRegex(), "5")
|
||||
return str
|
||||
}
|
||||
|
||||
fun addLine1(line1: String) {
|
||||
if (!mLines1.contains(line1)) {
|
||||
mLines1.add(line1)
|
||||
}
|
||||
}
|
||||
|
||||
fun addLine2(line2: String) {
|
||||
if (!mLines2.contains(line2)) {
|
||||
mLines2.add(line2)
|
||||
}
|
||||
}
|
||||
|
||||
fun cleanStorage() {
|
||||
mLines1.clear()
|
||||
mLines2.clear()
|
||||
}
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.BitmapFactory
|
||||
import android.nfc.Tag
|
||||
import android.nfc.tech.IsoDep
|
||||
import android.util.Log
|
||||
import example.jllarraz.com.passportreader.data.AdditionalDocumentDetails
|
||||
import example.jllarraz.com.passportreader.data.AdditionalPersonDetails
|
||||
import example.jllarraz.com.passportreader.data.Passport
|
||||
import example.jllarraz.com.passportreader.data.PersonDetails
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import net.sf.scuba.smartcards.CardService
|
||||
import net.sf.scuba.smartcards.CardServiceException
|
||||
import org.jmrtd.*
|
||||
import org.jmrtd.lds.icao.DG1File
|
||||
import org.jmrtd.lds.icao.MRZInfo
|
||||
import java.security.Security
|
||||
|
||||
class NFCDocumentTag {
|
||||
|
||||
fun handleTag(context: Context, tag: Tag, mrzInfo: MRZInfo, mrtdTrustStore: MRTDTrustStore, passportCallback: PassportCallback):Disposable{
|
||||
return Single.fromCallable({
|
||||
var passport: Passport? = null
|
||||
var cardServiceException: Exception? = null
|
||||
|
||||
var ps: PassportService? = null
|
||||
try {
|
||||
val nfc = IsoDep.get(tag)
|
||||
nfc.timeout = Math.max(nfc.timeout, 2000)
|
||||
val cs = CardService.getInstance(nfc)
|
||||
ps = PassportService(cs, PassportNFC.MAX_TRANSCEIVE_LENGTH_FOR_PACE, PassportNFC.MAX_TRANSCEIVE_LENGTH_FOR_SECURE_MESSAGING , PassportNFC.MAX_BLOCK_SIZE, false, true)
|
||||
ps.open()
|
||||
|
||||
val passportNFC = PassportNFC(ps, mrtdTrustStore, mrzInfo, PassportNFC.MAX_BLOCK_SIZE)
|
||||
val verifySecurity = passportNFC.verifySecurity()
|
||||
val features = passportNFC.features
|
||||
|
||||
passport = Passport()
|
||||
|
||||
passport.featureStatus = passportNFC.features
|
||||
passport.verificationStatus = passportNFC.verificationStatus
|
||||
|
||||
|
||||
passport.sodFile = passportNFC.sodFile
|
||||
|
||||
|
||||
//Basic Information
|
||||
if (passportNFC.dg1File != null) {
|
||||
val mrzInfo = (passportNFC.dg1File as DG1File).mrzInfo
|
||||
val personDetails = PersonDetails()
|
||||
personDetails.dateOfBirth = mrzInfo.dateOfBirth
|
||||
personDetails.dateOfExpiry = mrzInfo.dateOfExpiry
|
||||
personDetails.documentCode = mrzInfo.documentCode
|
||||
personDetails.documentNumber = mrzInfo.documentNumber
|
||||
personDetails.optionalData1 = mrzInfo.optionalData1
|
||||
personDetails.optionalData2 = mrzInfo.optionalData2
|
||||
personDetails.issuingState = mrzInfo.issuingState
|
||||
personDetails.primaryIdentifier = mrzInfo.primaryIdentifier
|
||||
personDetails.secondaryIdentifier = mrzInfo.secondaryIdentifier
|
||||
personDetails.nationality = mrzInfo.nationality
|
||||
personDetails.gender = mrzInfo.gender
|
||||
passport.personDetails = personDetails
|
||||
}
|
||||
|
||||
//Picture
|
||||
if (passportNFC.dg2File != null) {
|
||||
//Get the picture
|
||||
try {
|
||||
val faceImage = PassportNfcUtils.retrieveFaceImage(context, passportNFC.dg2File!!)
|
||||
passport.face = faceImage
|
||||
} catch (e: Exception) {
|
||||
//Don't do anything
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//Portrait
|
||||
//Get the picture
|
||||
if (passportNFC.dg5File != null) {
|
||||
//Get the picture
|
||||
try {
|
||||
val faceImage = PassportNfcUtils.retrievePortraitImage(context, passportNFC.dg5File!!)
|
||||
passport.portrait = faceImage
|
||||
} catch (e: Exception) {
|
||||
//Don't do anything
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
val dg11 = passportNFC.dg11File
|
||||
if (dg11 != null) {
|
||||
|
||||
val additionalPersonDetails = AdditionalPersonDetails()
|
||||
additionalPersonDetails.custodyInformation = dg11.custodyInformation
|
||||
additionalPersonDetails.fullDateOfBirth = dg11.fullDateOfBirth
|
||||
additionalPersonDetails.nameOfHolder = dg11.nameOfHolder
|
||||
additionalPersonDetails.otherNames = dg11.otherNames
|
||||
additionalPersonDetails.otherNames = dg11.otherNames
|
||||
additionalPersonDetails.otherValidTDNumbers = dg11.otherValidTDNumbers
|
||||
additionalPersonDetails.permanentAddress = dg11.permanentAddress
|
||||
additionalPersonDetails.personalNumber = dg11.personalNumber
|
||||
additionalPersonDetails.personalSummary = dg11.personalSummary
|
||||
additionalPersonDetails.placeOfBirth = dg11.placeOfBirth
|
||||
additionalPersonDetails.profession = dg11.profession
|
||||
additionalPersonDetails.proofOfCitizenship = dg11.proofOfCitizenship
|
||||
additionalPersonDetails.tag = dg11.tag
|
||||
additionalPersonDetails.tagPresenceList = dg11.tagPresenceList
|
||||
additionalPersonDetails.telephone = dg11.telephone
|
||||
additionalPersonDetails.title = dg11.title
|
||||
|
||||
passport.additionalPersonDetails = additionalPersonDetails
|
||||
}
|
||||
|
||||
|
||||
//Finger prints
|
||||
//Get the pictures
|
||||
if (passportNFC.dg3File != null) {
|
||||
//Get the picture
|
||||
try {
|
||||
val bitmaps = PassportNfcUtils.retrieveFingerPrintImage(context, passportNFC.dg3File!!)
|
||||
passport.fingerprints = bitmaps
|
||||
} catch (e: Exception) {
|
||||
//Don't do anything
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//Signature
|
||||
//Get the pictures
|
||||
if (passportNFC.dg7File != null) {
|
||||
//Get the picture
|
||||
try {
|
||||
val bitmap = PassportNfcUtils.retrieveSignatureImage(context, passportNFC.dg7File!!)
|
||||
passport.signature = bitmap
|
||||
} catch (e: Exception) {
|
||||
//Don't do anything
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Additional Document Details
|
||||
|
||||
val dg12 = passportNFC.dg12File
|
||||
if (dg12 != null) {
|
||||
val additionalDocumentDetails = AdditionalDocumentDetails()
|
||||
additionalDocumentDetails.dateAndTimeOfPersonalization = dg12.dateAndTimeOfPersonalization
|
||||
additionalDocumentDetails.dateOfIssue = dg12.dateOfIssue
|
||||
additionalDocumentDetails.endorsementsAndObservations = dg12.endorsementsAndObservations
|
||||
try {
|
||||
val imageOfFront = dg12.imageOfFront
|
||||
val bitmapImageOfFront = BitmapFactory.decodeByteArray(imageOfFront, 0, imageOfFront.size)
|
||||
additionalDocumentDetails.imageOfFront = bitmapImageOfFront
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Additional document image front: $e")
|
||||
}
|
||||
|
||||
try {
|
||||
val imageOfRear = dg12.imageOfRear
|
||||
val bitmapImageOfRear = BitmapFactory.decodeByteArray(imageOfRear, 0, imageOfRear.size)
|
||||
additionalDocumentDetails.imageOfRear = bitmapImageOfRear
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Additional document image rear: $e")
|
||||
}
|
||||
|
||||
additionalDocumentDetails.issuingAuthority = dg12.issuingAuthority
|
||||
additionalDocumentDetails.namesOfOtherPersons = dg12.namesOfOtherPersons
|
||||
additionalDocumentDetails.personalizationSystemSerialNumber = dg12.personalizationSystemSerialNumber
|
||||
additionalDocumentDetails.taxOrExitRequirements = dg12.taxOrExitRequirements
|
||||
|
||||
passport.additionalDocumentDetails = additionalDocumentDetails
|
||||
}
|
||||
|
||||
//TODO EAC
|
||||
} catch (e: Exception) {
|
||||
cardServiceException = e
|
||||
} finally {
|
||||
try {
|
||||
ps?.close()
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
PassportDTO(passport, cardServiceException)
|
||||
})
|
||||
.doOnSubscribe{
|
||||
passportCallback.onPassportReadStart()
|
||||
}
|
||||
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe({ passportDTO ->
|
||||
if(passportDTO.cardServiceException!=null) {
|
||||
val cardServiceException = passportDTO.cardServiceException
|
||||
if (cardServiceException is AccessDeniedException) {
|
||||
passportCallback.onAccessDeniedException(cardServiceException)
|
||||
} else if (cardServiceException is BACDeniedException) {
|
||||
passportCallback.onBACDeniedException(cardServiceException)
|
||||
} else if (cardServiceException is PACEException) {
|
||||
passportCallback.onPACEException(cardServiceException)
|
||||
} else if (cardServiceException is CardServiceException) {
|
||||
passportCallback.onCardException(cardServiceException)
|
||||
} else {
|
||||
passportCallback.onGeneralException(cardServiceException)
|
||||
}
|
||||
} else {
|
||||
passportCallback.onPassportRead(passportDTO.passport)
|
||||
}
|
||||
passportCallback.onPassportReadFinish()
|
||||
})
|
||||
}
|
||||
|
||||
data class PassportDTO(val passport: Passport? = null, val cardServiceException: Exception? = null)
|
||||
|
||||
interface PassportCallback {
|
||||
fun onPassportReadStart()
|
||||
fun onPassportReadFinish()
|
||||
fun onPassportRead(passport: Passport?)
|
||||
fun onAccessDeniedException(exception: AccessDeniedException)
|
||||
fun onBACDeniedException(exception: BACDeniedException)
|
||||
fun onPACEException(exception: PACEException)
|
||||
fun onCardException(exception: CardServiceException)
|
||||
fun onGeneralException(exception: Exception?)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = NFCDocumentTag::class.java.simpleName
|
||||
|
||||
init {
|
||||
Security.insertProviderAt(org.spongycastle.jce.provider.BouncyCastleProvider(), 1)
|
||||
}
|
||||
|
||||
private val EMPTY_TRIED_BAC_ENTRY_LIST = emptyList<Any>()
|
||||
private val EMPTY_CERTIFICATE_CHAIN = emptyList<Any>()
|
||||
}
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.utils
|
||||
|
||||
import android.util.Log
|
||||
import com.google.mlkit.vision.text.Text
|
||||
import net.sf.scuba.data.Gender
|
||||
import org.jmrtd.lds.icao.MRZInfo
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object OcrUtils {
|
||||
|
||||
private val TAG = OcrUtils::class.java.simpleName
|
||||
|
||||
// TD3 (Passport) format patterns
|
||||
private val REGEX_OLD_PASSPORT = "(?<documentNumber>[A-Z0-9<]{9})(?<checkDigitDocumentNumber>[0-9ILDSOG]{1})(?<nationality>[A-Z<]{3})(?<dateOfBirth>[0-9ILDSOG]{6})(?<checkDigitDateOfBirth>[0-9ILDSOG]{1})(?<sex>[FM<]){1}(?<expirationDate>[0-9ILDSOG]{6})(?<checkDigitExpiration>[0-9ILDSOG]{1})"
|
||||
private val REGEX_OLD_PASSPORT_CLEAN = "(?<documentNumber>[A-Z0-9<]{9})(?<checkDigitDocumentNumber>[0-9]{1})(?<nationality>[A-Z<]{3})(?<dateOfBirth>[0-9]{6})(?<checkDigitDateOfBirth>[0-9]{1})(?<sex>[FM<]){1}(?<expirationDate>[0-9]{6})(?<checkDigitExpiration>[0-9]{1})"
|
||||
private val REGEX_IP_PASSPORT_LINE_1 = "\\bIP[A-Z<]{3}[A-Z0-9<]{9}[0-9]{1}"
|
||||
private val REGEX_IP_PASSPORT_LINE_2 = "[0-9]{6}[0-9]{1}[FM<]{1}[0-9]{6}[0-9]{1}[A-Z<]{3}"
|
||||
|
||||
// TD1 (ID Card) format patterns
|
||||
private val REGEX_TD1_LINE1 = "(?<documentCode>[A-Z]{1}[A-Z0-9<]{1})(?<issuingState>[A-Z<]{3})(?<documentNumber>[A-Z0-9<]{9})(?<checkDigitDocumentNumber>[0-9]{1})(?<optionalData1>[A-Z0-9<]{15})"
|
||||
private val REGEX_TD1_LINE2 = "(?<dateOfBirth>[0-9]{6})(?<checkDigitDateOfBirth>[0-9]{1})(?<sex>[FM<]{1})(?<expirationDate>[0-9]{6})(?<checkDigitExpiration>[0-9]{1})(?<nationality>[A-Z<]{3})(?<optionalData2>[A-Z0-9<]{7})"
|
||||
private val REGEX_TD1_LINE3 ="(?<names>[A-Z<]{30})"
|
||||
|
||||
// TD1 (ID Card)
|
||||
private val REGEX_ID_DOCUMENT_CODE = "(?<documentCode>[IP]{1}[DM<]{1})"
|
||||
private val REGEX_ID_DOCUMENT_NUMBER = "(ID)(?<country>[A-Z<]{3})(?<documentNumber>[A-Z0-9<]{9})(?<checkDigitDocumentNumber>[0-9]{1})"
|
||||
private val REGEX_ID_DATE_OF_BIRTH = "(?<dateOfBirth>[0-9]{6})(?<checkDigitDateOfBirth>[0-9]{1})(?<gender>[FM<]{1})"
|
||||
|
||||
// Belgium TD1 (ID Card) specific pattern
|
||||
private val REGEX_BELGIUM_ID_DOCUMENT_NUMBER = "IDBEL(?<doc9>[A-Z0-9]{9})<(?<doc3>[A-Z0-9]{3})(?<checkDigit>\\d)"
|
||||
private val REGEX_BELGIUM_ID_DATE_OF_BIRTH = "(?<dateOfBirth>[0-9]{6})(?<checkDigitDateOfBirth>[0-9]{1})(?<gender>[FM<]{1})(?<expirationDate>[0-9]{6})(?<checkDigitExpiration>[0-9]{1})"
|
||||
|
||||
private val patternDocumentNumber = Pattern.compile(REGEX_ID_DOCUMENT_NUMBER)
|
||||
private val patternDateOfBirth = Pattern.compile(REGEX_ID_DATE_OF_BIRTH)
|
||||
private val patternDocumentCode = Pattern.compile(REGEX_ID_DOCUMENT_CODE)
|
||||
private val patternBelgiumDocumentNumber = Pattern.compile(REGEX_BELGIUM_ID_DOCUMENT_NUMBER)
|
||||
private val patternBelgiumDateOfBirth = Pattern.compile(REGEX_BELGIUM_ID_DATE_OF_BIRTH)
|
||||
|
||||
|
||||
fun processOcr(
|
||||
results: Text,
|
||||
timeRequired: Long,
|
||||
callback: MRZCallback
|
||||
){
|
||||
var fullRead = ""
|
||||
val blocks = results.textBlocks
|
||||
for (i in blocks.indices) {
|
||||
var temp = ""
|
||||
val lines = blocks[i].lines
|
||||
for (j in lines.indices) {
|
||||
//extract scanned text lines here
|
||||
//temp+=lines.get(j).getText().trim()+"-";
|
||||
temp += lines[j].text + "-"
|
||||
}
|
||||
temp = temp.replace("\r".toRegex(), "").replace("\n".toRegex(), "").replace("\t".toRegex(), "").replace(" ", "")
|
||||
fullRead += "$temp-"
|
||||
}
|
||||
fullRead = fullRead.uppercase()
|
||||
// Log.d(TAG, "Read: $fullRead")
|
||||
|
||||
// We try with TD1 format first (ID Card)
|
||||
val patternTD1Line1 = Pattern.compile(REGEX_TD1_LINE1)
|
||||
val patternTD1Line2 = Pattern.compile(REGEX_TD1_LINE2)
|
||||
val patternTD1Line3 = Pattern.compile(REGEX_TD1_LINE3)
|
||||
|
||||
|
||||
val matcherTD1Line1 = patternTD1Line1.matcher(fullRead)
|
||||
val matcherTD1Line2 = patternTD1Line2.matcher(fullRead)
|
||||
val matcherTD1Line3 = patternTD1Line3.matcher(fullRead)
|
||||
|
||||
val matcherDocumentCode = patternDocumentCode.matcher(fullRead)
|
||||
|
||||
if (matcherDocumentCode.find() && matcherDocumentCode.group("documentCode") == "ID") {
|
||||
Log.d(TAG, "ID card found")
|
||||
|
||||
val matcherDocumentNumber = patternDocumentNumber.matcher(fullRead)
|
||||
val matcherDateOfBirth = patternDateOfBirth.matcher(fullRead)
|
||||
val hasDocumentNumber = matcherDocumentNumber.find()
|
||||
val hasDateOfBirth = matcherDateOfBirth.find()
|
||||
|
||||
// Belgium specific matchers
|
||||
val matcherBelgiumDocumentNumber = patternBelgiumDocumentNumber.matcher(fullRead)
|
||||
val hasBelgiumDocumentNumber = matcherBelgiumDocumentNumber.find()
|
||||
|
||||
val documentNumber = if (hasDocumentNumber) matcherDocumentNumber.group("documentNumber") else null
|
||||
val checkDigitDocumentNumber = if (hasDocumentNumber) matcherDocumentNumber.group("checkDigitDocumentNumber")?.toIntOrNull() else null
|
||||
val countryCode = if (hasDocumentNumber) matcherDocumentNumber.group("country") else null
|
||||
val dateOfBirth = if (hasDateOfBirth) matcherDateOfBirth.group("dateOfBirth") else null
|
||||
|
||||
// Belgium specific values
|
||||
val belgiumCheckDigit = if (hasBelgiumDocumentNumber) matcherBelgiumDocumentNumber.group("checkDigit")?.toIntOrNull() else null
|
||||
val belgiumDateOfBirth = if (hasBelgiumDocumentNumber) {
|
||||
val dateOfBirthMatcher = patternBelgiumDateOfBirth.matcher(fullRead)
|
||||
if (dateOfBirthMatcher.find()) dateOfBirthMatcher.group("dateOfBirth") else null
|
||||
} else null
|
||||
|
||||
// Final values
|
||||
val finalDocumentNumber = if (hasBelgiumDocumentNumber) {
|
||||
val doc9 = matcherBelgiumDocumentNumber.group("doc9")
|
||||
val doc3 = matcherBelgiumDocumentNumber.group("doc3")
|
||||
val checkDigit = matcherBelgiumDocumentNumber.group("checkDigit")
|
||||
cleanBelgiumDocumentNumber(doc9, doc3, checkDigit)
|
||||
} else documentNumber
|
||||
val finalDateOfBirth = if (hasBelgiumDocumentNumber) belgiumDateOfBirth else dateOfBirth
|
||||
val finalCountryCode = if (hasBelgiumDocumentNumber) "BEL" else countryCode
|
||||
val finalCheckDigit = if (hasBelgiumDocumentNumber) belgiumCheckDigit else checkDigitDocumentNumber
|
||||
|
||||
val checkDigitDateOfBirth = if (hasDateOfBirth) matcherDateOfBirth.group("checkDigitDateOfBirth")?.toIntOrNull() else null
|
||||
val gender = if (hasDateOfBirth) matcherDateOfBirth.group("gender") else null
|
||||
|
||||
val expirationDate: String? = if (!finalCountryCode.isNullOrEmpty()) {
|
||||
val expirationDateRegex = "(?<expirationDate>[0-9]{6})(?<checkDigitExpiration>[0-9]{1})" + Pattern.quote(finalCountryCode)
|
||||
// val expirationDateRegex = "(?<expirationDate>[0-9]{6})(?<checkDigitExpiration>[0-9]{1})UTO"
|
||||
val patternExpirationDate = Pattern.compile(expirationDateRegex)
|
||||
val matcherExpirationDate = patternExpirationDate.matcher(fullRead)
|
||||
if (matcherExpirationDate.find()) matcherExpirationDate.group("expirationDate") else null
|
||||
} else null
|
||||
|
||||
// Only proceed if all required fields are present and non-empty
|
||||
if (!finalCountryCode.isNullOrEmpty() && !finalDocumentNumber.isNullOrEmpty() && !finalDateOfBirth.isNullOrEmpty() && !expirationDate.isNullOrEmpty() && finalCheckDigit != null) {
|
||||
val cleanDocumentNumber = cleanDocumentNumber(finalDocumentNumber, finalCheckDigit)
|
||||
// Log.d(TAG, "cleanDocumentNumber")
|
||||
if (cleanDocumentNumber != null) {
|
||||
val mrzInfo = createDummyMrz("ID", finalCountryCode, cleanDocumentNumber, finalDateOfBirth, expirationDate)
|
||||
// Log.d(TAG, "MRZ-TD1: $mrzInfo")
|
||||
callback.onMRZRead(mrzInfo, timeRequired)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if (finalCountryCode.isNullOrEmpty()) Log.d(TAG, "Missing or invalid finalCountryCode")
|
||||
if (finalDocumentNumber.isNullOrEmpty()) Log.d(TAG, "Missing or invalid finalDocumentNumber")
|
||||
if (finalDateOfBirth.isNullOrEmpty()) Log.d(TAG, "Missing or invalid dateOfBirth")
|
||||
if (expirationDate.isNullOrEmpty()) Log.d(TAG, "Missing or invalid expirationDate")
|
||||
if (finalCheckDigit == null) Log.d(TAG, "Missing or invalid finalCheckDigit")
|
||||
}
|
||||
}
|
||||
|
||||
if (matcherTD1Line1.find() && matcherTD1Line2.find()) {
|
||||
Log.d(TAG, "TD1Line1 and TD1Line2 found")
|
||||
val documentNumber = matcherTD1Line1.group("documentNumber")
|
||||
val checkDigitDocumentNumber = matcherTD1Line1.group("checkDigitDocumentNumber").toInt()
|
||||
val dateOfBirth = matcherTD1Line2.group("dateOfBirth")
|
||||
val expirationDate = matcherTD1Line2.group("expirationDate")
|
||||
val documentType = matcherTD1Line1.group("documentCode")
|
||||
val issuingState = matcherTD1Line1.group("issuingState")
|
||||
|
||||
val cleanDocumentNumber = cleanDocumentNumber(documentNumber, checkDigitDocumentNumber)
|
||||
if (cleanDocumentNumber != null) {
|
||||
val mrzInfo = createDummyMrz(documentType, issuingState, cleanDocumentNumber, dateOfBirth, expirationDate)
|
||||
Log.d(TAG, "cleanDocumentNumber")
|
||||
callback.onMRZRead(mrzInfo, timeRequired)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If not TD1 we try with TD3 (Passport) format
|
||||
val patternLineOldPassportType = Pattern.compile(REGEX_OLD_PASSPORT)
|
||||
val matcherLineOldPassportType = patternLineOldPassportType.matcher(fullRead)
|
||||
|
||||
if (matcherLineOldPassportType.find()) {
|
||||
//Old passport format
|
||||
val line2 = matcherLineOldPassportType.group(0)
|
||||
var documentNumber = matcherLineOldPassportType.group(1)
|
||||
val checkDigitDocumentNumber = cleanDate(matcherLineOldPassportType.group(2)).toInt()
|
||||
val dateOfBirthDay = cleanDate(matcherLineOldPassportType.group(4))
|
||||
val expirationDate = cleanDate(matcherLineOldPassportType.group(7))
|
||||
|
||||
val cleanDocumentNumber = cleanDocumentNumber(documentNumber, checkDigitDocumentNumber)
|
||||
if (cleanDocumentNumber!=null){
|
||||
val mrzInfo = createDummyMrz("P", "ESP", cleanDocumentNumber, dateOfBirthDay, expirationDate)
|
||||
// Log.d(TAG, "MRZ: $mrzInfo")
|
||||
callback.onMRZRead(mrzInfo, timeRequired)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//Try with the new IP passport type
|
||||
val patternLineIPassportTypeLine1 = Pattern.compile(REGEX_IP_PASSPORT_LINE_1)
|
||||
val matcherLineIPassportTypeLine1 = patternLineIPassportTypeLine1.matcher(fullRead)
|
||||
val patternLineIPassportTypeLine2 = Pattern.compile(REGEX_IP_PASSPORT_LINE_2)
|
||||
val matcherLineIPassportTypeLine2 = patternLineIPassportTypeLine2.matcher(fullRead)
|
||||
if (matcherLineIPassportTypeLine1.find() && matcherLineIPassportTypeLine2.find()) {
|
||||
val line1 = matcherLineIPassportTypeLine1.group(0)
|
||||
val line2 = matcherLineIPassportTypeLine2.group(0)
|
||||
val documentNumber = line1.substring(5, 14)
|
||||
val checkDigitDocumentNumber = line1.substring(14, 15).toInt()
|
||||
val dateOfBirthDay = line2.substring(0, 6)
|
||||
val expirationDate = line2.substring(8, 14)
|
||||
|
||||
val cleanDocumentNumber = cleanDocumentNumber(documentNumber, checkDigitDocumentNumber)
|
||||
if (cleanDocumentNumber != null) {
|
||||
val mrzInfo = createDummyMrz("P", "ESP", cleanDocumentNumber, dateOfBirthDay, expirationDate)
|
||||
callback.onMRZRead(mrzInfo, timeRequired)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//No success with any format
|
||||
callback.onMRZReadFailure(timeRequired)
|
||||
}
|
||||
|
||||
private fun cleanDocumentNumber(documentNumber: String, checkDigit:Int):String?{
|
||||
//first we replace all O per 0
|
||||
var tempDcumentNumber = documentNumber.replace("O".toRegex(), "0")
|
||||
//Calculate check digit of the document number
|
||||
var checkDigitCalculated = MRZInfo.checkDigit(tempDcumentNumber).toString().toInt()
|
||||
if (checkDigit == checkDigitCalculated) {
|
||||
//If check digits match we return the document number
|
||||
return tempDcumentNumber
|
||||
}
|
||||
//if no match, we try to replace once at a time the first 0 per O as the alpha part comes first, and check if the digits match
|
||||
var indexOfZero = tempDcumentNumber.indexOf("0")
|
||||
while (indexOfZero>-1) {
|
||||
checkDigitCalculated = MRZInfo.checkDigit(tempDcumentNumber).toString().toInt()
|
||||
if (checkDigit != checkDigitCalculated) {
|
||||
//Some countries like Spain uses a letter O before the numeric part
|
||||
indexOfZero = tempDcumentNumber.indexOf("0")
|
||||
tempDcumentNumber = tempDcumentNumber.replaceFirst("0", "O")
|
||||
}else{
|
||||
return tempDcumentNumber
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun cleanBelgiumDocumentNumber(doc9: String, doc3: String, checkDigit: String): String? {
|
||||
// For Belgium TD1 format: IDBEL000001115<7027
|
||||
// doc9 = "000001115" (9 digits)
|
||||
// doc3 = "702" (3 digits after <)
|
||||
// checkDigit = "7" (single check digit)
|
||||
|
||||
var cleanDoc9 = doc9
|
||||
cleanDoc9 = cleanDoc9.substring(3)
|
||||
|
||||
val fullDocumentNumber = cleanDoc9 + doc3
|
||||
|
||||
val checkDigitCalculated = MRZInfo.checkDigit(fullDocumentNumber).toString().toInt()
|
||||
val expectedCheckDigit = checkDigit.toInt()
|
||||
|
||||
if (checkDigitCalculated == expectedCheckDigit) {
|
||||
return fullDocumentNumber
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun createDummyMrz(
|
||||
documentType: String,
|
||||
issuingState: String = "ESP",
|
||||
documentNumber: String,
|
||||
dateOfBirthDay: String,
|
||||
expirationDate: String,
|
||||
nationality: String = "ESP"
|
||||
): MRZInfo {
|
||||
return MRZInfo(
|
||||
documentType,
|
||||
issuingState,
|
||||
"DUMMY",
|
||||
"DUMMY",
|
||||
documentNumber,
|
||||
"ESP",
|
||||
dateOfBirthDay,
|
||||
Gender.MALE,
|
||||
expirationDate,
|
||||
""
|
||||
)
|
||||
}
|
||||
|
||||
private fun cleanDate(date:String):String{
|
||||
var tempDate = date
|
||||
tempDate = tempDate.replace("I".toRegex(), "1")
|
||||
tempDate = tempDate.replace("L".toRegex(), "1")
|
||||
tempDate = tempDate.replace("D".toRegex(), "0")
|
||||
tempDate = tempDate.replace("O".toRegex(), "0")
|
||||
tempDate = tempDate.replace("S".toRegex(), "5")
|
||||
tempDate = tempDate.replace("G".toRegex(), "6")
|
||||
return tempDate
|
||||
}
|
||||
|
||||
interface MRZCallback {
|
||||
fun onMRZRead(mrzInfo: MRZInfo, timeRequired: Long)
|
||||
fun onMRZReadFailure(timeRequired: Long)
|
||||
fun onFailure(e: Exception, timeRequired: Long)
|
||||
}
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.util.Log
|
||||
|
||||
import org.jmrtd.cert.CVCPrincipal
|
||||
import org.jmrtd.cert.CardVerifiableCertificate
|
||||
import org.jmrtd.lds.icao.DG2File
|
||||
import org.jmrtd.lds.icao.DG3File
|
||||
import org.jmrtd.lds.icao.DG5File
|
||||
import org.jmrtd.lds.icao.DG7File
|
||||
import org.jmrtd.lds.iso19794.FaceImageInfo
|
||||
import org.jmrtd.lds.iso19794.FingerImageInfo
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.DataInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
import java.math.BigInteger
|
||||
import java.security.GeneralSecurityException
|
||||
import java.security.KeyStore
|
||||
import java.security.PrivateKey
|
||||
import java.security.Security
|
||||
import java.security.cert.CertPathBuilder
|
||||
import java.security.cert.CertPathBuilderException
|
||||
import java.security.cert.CertStore
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CollectionCertStoreParameters
|
||||
import java.security.cert.PKIXBuilderParameters
|
||||
import java.security.cert.PKIXCertPathBuilderResult
|
||||
import java.security.cert.TrustAnchor
|
||||
import java.security.cert.X509CertSelector
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.ArrayList
|
||||
import java.util.Arrays
|
||||
import java.util.Collections
|
||||
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
object PassportNfcUtils {
|
||||
|
||||
private val TAG = PassportNfcUtils::class.java.simpleName
|
||||
|
||||
|
||||
private val IS_PKIX_REVOCATION_CHECING_ENABLED = false
|
||||
|
||||
init {
|
||||
Security.addProvider(BouncyCastleProvider())
|
||||
}
|
||||
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun retrieveFaceImage(context: Context, dg2File: DG2File): Bitmap {
|
||||
val allFaceImageInfos = ArrayList<FaceImageInfo>()
|
||||
val faceInfos = dg2File.faceInfos
|
||||
for (faceInfo in faceInfos) {
|
||||
allFaceImageInfos.addAll(faceInfo.faceImageInfos)
|
||||
}
|
||||
|
||||
if (!allFaceImageInfos.isEmpty()) {
|
||||
val faceImageInfo = allFaceImageInfos.iterator().next()
|
||||
return toBitmap(faceImageInfo.imageLength, faceImageInfo.imageInputStream, faceImageInfo.mimeType)
|
||||
}
|
||||
throw IOException("Unable to decodeImage Image")
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun retrievePortraitImage(context: Context, dg5File: DG5File): Bitmap {
|
||||
val faceInfos = dg5File.images
|
||||
if (!faceInfos.isEmpty()) {
|
||||
val faceImageInfo = faceInfos.iterator().next()
|
||||
return toBitmap(faceImageInfo.imageLength, faceImageInfo.imageInputStream, faceImageInfo.mimeType)
|
||||
}
|
||||
throw IOException("Unable to decodeImage Image")
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun retrieveSignatureImage(context: Context, dg7File: DG7File): Bitmap {
|
||||
val displayedImageInfos = dg7File.images
|
||||
if (!displayedImageInfos.isEmpty()) {
|
||||
val displayedImageInfo = displayedImageInfos.iterator().next()
|
||||
return toBitmap(displayedImageInfo.imageLength, displayedImageInfo.imageInputStream, displayedImageInfo.mimeType)
|
||||
}
|
||||
throw IOException("Unable to decodeImage Image")
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun retrieveFingerPrintImage(context: Context, dg3File: DG3File): List<Bitmap> {
|
||||
val allFingerImageInfos = ArrayList<FingerImageInfo>()
|
||||
val fingerInfos = dg3File.fingerInfos
|
||||
|
||||
val fingerprintsImage = ArrayList<Bitmap>()
|
||||
for (fingerInfo in fingerInfos) {
|
||||
allFingerImageInfos.addAll(fingerInfo.fingerImageInfos)
|
||||
}
|
||||
|
||||
val iterator = allFingerImageInfos.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val fingerImageInfo = iterator.next()
|
||||
val bitmap = toBitmap(fingerImageInfo.imageLength, fingerImageInfo.imageInputStream, fingerImageInfo.mimeType)
|
||||
fingerprintsImage.add(bitmap)
|
||||
}
|
||||
|
||||
if (fingerprintsImage.isEmpty()) {
|
||||
throw IOException("Unable to decodeImage Finger print Image")
|
||||
}
|
||||
return fingerprintsImage
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun toBitmap(imageLength: Int, inputStream: InputStream, mimeType: String): Bitmap {
|
||||
val dataInputStream = DataInputStream(inputStream)
|
||||
val buffer = ByteArray(imageLength)
|
||||
dataInputStream.readFully(buffer, 0, imageLength)
|
||||
val byteArrayInputStream = ByteArrayInputStream(buffer, 0, imageLength)
|
||||
return ImageUtil.decodeImage(byteArrayInputStream, imageLength, mimeType)
|
||||
}
|
||||
|
||||
|
||||
@Throws(GeneralSecurityException::class)
|
||||
fun getEACCredentials(caReference: CVCPrincipal, cvcaStores: List<KeyStore>): EACCredentials? {
|
||||
for (cvcaStore in cvcaStores) {
|
||||
val eacCredentials = getEACCredentials(caReference, cvcaStore)
|
||||
if (eacCredentials != null) {
|
||||
return eacCredentials
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the key store for a relevant terminal key and associated certificate chain.
|
||||
*
|
||||
* @param caReference
|
||||
* @param cvcaStore should contain a single key with certificate chain
|
||||
* @return
|
||||
* @throws GeneralSecurityException
|
||||
*/
|
||||
@Throws(GeneralSecurityException::class)
|
||||
private fun getEACCredentials(caReference: CVCPrincipal?, cvcaStore: KeyStore): EACCredentials? {
|
||||
if (caReference == null) {
|
||||
throw IllegalArgumentException("CA reference cannot be null")
|
||||
}
|
||||
|
||||
var privateKey: PrivateKey? = null
|
||||
var chain: Array<Certificate>? = null
|
||||
|
||||
val aliases = Collections.list(cvcaStore.aliases())
|
||||
for (alias in aliases) {
|
||||
if (cvcaStore.isKeyEntry(alias)) {
|
||||
val key = cvcaStore.getKey(alias, "".toCharArray())
|
||||
if (key is PrivateKey) {
|
||||
privateKey = key
|
||||
} else {
|
||||
Log.w(TAG, "skipping non-private key $alias")
|
||||
continue
|
||||
}
|
||||
chain = cvcaStore.getCertificateChain(alias)
|
||||
return EACCredentials(privateKey, chain!!)
|
||||
} else if (cvcaStore.isCertificateEntry(alias)) {
|
||||
val certificate = cvcaStore.getCertificate(alias) as CardVerifiableCertificate
|
||||
val authRef = certificate.authorityReference
|
||||
val holderRef = certificate.holderReference
|
||||
if (caReference != authRef) {
|
||||
continue
|
||||
}
|
||||
/* See if we have a private key for that certificate. */
|
||||
privateKey = cvcaStore.getKey(holderRef.name, "".toCharArray()) as PrivateKey
|
||||
chain = cvcaStore.getCertificateChain(holderRef.name)
|
||||
if (privateKey == null) {
|
||||
continue
|
||||
}
|
||||
Log.i(TAG, "found a key, privateKey = $privateKey")
|
||||
return EACCredentials(privateKey, chain!!)
|
||||
}
|
||||
if (privateKey == null || chain == null) {
|
||||
Log.e(TAG, "null chain or key for entry " + alias + ": chain = " + Arrays.toString(chain) + ", privateKey = " + privateKey)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a certificate chain to an anchor using the PKIX algorithm.
|
||||
*
|
||||
* @param docSigningCertificate the start certificate
|
||||
* @param sodIssuer the issuer of the start certificate (ignored unless `docSigningCertificate` is `null`)
|
||||
* @param sodSerialNumber the serial number of the start certificate (ignored unless `docSigningCertificate` is `null`)
|
||||
*
|
||||
* @return the certificate chain
|
||||
*/
|
||||
fun getCertificateChain(docSigningCertificate: X509Certificate?,
|
||||
sodIssuer: X500Principal,
|
||||
sodSerialNumber: BigInteger,
|
||||
cscaStores: List<CertStore>,
|
||||
cscaTrustAnchors: Set<TrustAnchor>): List<Certificate> {
|
||||
val chain = ArrayList<Certificate>()
|
||||
val selector = X509CertSelector()
|
||||
try {
|
||||
|
||||
if (docSigningCertificate != null) {
|
||||
selector.certificate = docSigningCertificate
|
||||
} else {
|
||||
selector.issuer = sodIssuer
|
||||
selector.serialNumber = sodSerialNumber
|
||||
}
|
||||
|
||||
val docStoreParams = CollectionCertStoreParameters(setOf<Certificate>(docSigningCertificate as Certificate))
|
||||
val docStore = CertStore.getInstance("Collection", docStoreParams)
|
||||
|
||||
val builder = CertPathBuilder.getInstance("PKIX", "SC")//Spungy castle
|
||||
val buildParams = PKIXBuilderParameters(cscaTrustAnchors, selector)
|
||||
buildParams.addCertStore(docStore)
|
||||
for (trustStore in cscaStores) {
|
||||
buildParams.addCertStore(trustStore)
|
||||
}
|
||||
buildParams.isRevocationEnabled = IS_PKIX_REVOCATION_CHECING_ENABLED /* NOTE: set to false for checking disabled. */
|
||||
|
||||
var result: PKIXCertPathBuilderResult? = null
|
||||
|
||||
try {
|
||||
result = builder.build(buildParams) as PKIXCertPathBuilderResult
|
||||
} catch (cpbe: CertPathBuilderException) {
|
||||
cpbe.printStackTrace()
|
||||
/* NOTE: ignore, result remain null */
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
val pkixCertPath = result.certPath
|
||||
if (pkixCertPath != null) {
|
||||
chain.addAll(pkixCertPath.certificates)
|
||||
}
|
||||
}
|
||||
if (docSigningCertificate != null && !chain.contains(docSigningCertificate)) {
|
||||
/* NOTE: if doc signing certificate not in list, we add it ourselves. */
|
||||
Log.w(TAG, "Adding doc signing certificate after PKIXBuilder finished")
|
||||
chain.add(0, docSigningCertificate)
|
||||
}
|
||||
if (result != null) {
|
||||
val trustAnchorCertificate = result.trustAnchor.trustedCert
|
||||
if (trustAnchorCertificate != null && !chain.contains(trustAnchorCertificate)) {
|
||||
/* NOTE: if trust anchor not in list, we add it ourselves. */
|
||||
Log.w(TAG, "Adding trust anchor certificate after PKIXBuilder finished")
|
||||
chain.add(trustAnchorCertificate)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.i(TAG, "Building a chain failed (" + e.message + ").")
|
||||
}
|
||||
|
||||
return chain
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package example.jllarraz.com.passportreader.utils
|
||||
|
||||
object StringUtils {
|
||||
private val hexArray = "0123456789ABCDEF".toCharArray()
|
||||
fun bytesToHex(bytes: ByteArray): String {
|
||||
val hexChars = CharArray(bytes.size * 2)
|
||||
for (j in bytes.indices) {
|
||||
val v = bytes[j].toInt() and 0xFF
|
||||
hexChars[j * 2] = hexArray[v.ushr(4)]
|
||||
hexChars[j * 2 + 1] = hexArray[v and 0x0F]
|
||||
}
|
||||
return String(hexChars)
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package org.jmrtd
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
/**
|
||||
* Security features of this identity document.
|
||||
*
|
||||
* @author The JMRTD team (info@jmrtd.org)
|
||||
*
|
||||
* @version $Revision: 1559 $
|
||||
*/
|
||||
class FeatureStatus : Parcelable {
|
||||
|
||||
private var hasSAC: Verdict? = null
|
||||
private var hasBAC: Verdict? = null
|
||||
private var hasAA: Verdict? = null
|
||||
private var hasEAC: Verdict? = null
|
||||
private var hasCA: Verdict? = null
|
||||
|
||||
/**
|
||||
* Outcome of a feature presence check.
|
||||
*
|
||||
* @author The JMRTD team (info@jmrtd.org)
|
||||
*
|
||||
* @version $Revision: 1559 $
|
||||
*/
|
||||
enum class Verdict {
|
||||
UNKNOWN, /* Presence unknown */
|
||||
PRESENT, /* Present */
|
||||
NOT_PRESENT
|
||||
/* Not present */
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.hasSAC = Verdict.UNKNOWN
|
||||
this.hasBAC = Verdict.UNKNOWN
|
||||
this.hasAA = Verdict.UNKNOWN
|
||||
this.hasEAC = Verdict.UNKNOWN
|
||||
this.hasCA = Verdict.UNKNOWN
|
||||
}
|
||||
|
||||
fun setSAC(hasSAC: Verdict) {
|
||||
this.hasSAC = hasSAC
|
||||
}
|
||||
|
||||
fun hasSAC(): Verdict? {
|
||||
return hasSAC
|
||||
}
|
||||
|
||||
|
||||
fun setBAC(hasBAC: Verdict) {
|
||||
this.hasBAC = hasBAC
|
||||
}
|
||||
|
||||
fun hasBAC(): Verdict? {
|
||||
return hasBAC
|
||||
}
|
||||
|
||||
fun setAA(hasAA: Verdict) {
|
||||
this.hasAA = hasAA
|
||||
}
|
||||
|
||||
fun hasAA(): Verdict? {
|
||||
return hasAA
|
||||
}
|
||||
|
||||
fun setEAC(hasEAC: Verdict) {
|
||||
this.hasEAC = hasEAC
|
||||
}
|
||||
|
||||
fun hasEAC(): Verdict? {
|
||||
return hasEAC
|
||||
}
|
||||
|
||||
fun setCA(hasCA: Verdict) {
|
||||
this.hasCA = hasCA
|
||||
}
|
||||
|
||||
fun hasCA(): Verdict? {
|
||||
return hasCA
|
||||
}
|
||||
|
||||
constructor(`in`: Parcel) {
|
||||
this.hasSAC = if(`in`.readInt() == 1){ Verdict.valueOf(`in`.readString()!!) } else { null }
|
||||
this.hasBAC = if(`in`.readInt() == 1){Verdict.valueOf(`in`.readString()!!) } else { null }
|
||||
this.hasAA = if(`in`.readInt() == 1){Verdict.valueOf(`in`.readString()!!) } else { null }
|
||||
this.hasEAC = if(`in`.readInt() == 1){Verdict.valueOf(`in`.readString()!!) } else { null }
|
||||
this.hasCA = if(`in`.readInt() == 1){Verdict.valueOf(`in`.readString()!!) } else { null }
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeInt(if(this.hasSAC!=null) 1 else 0)
|
||||
if(this.hasSAC!=null) {
|
||||
dest.writeString(this.hasSAC?.name)
|
||||
}
|
||||
dest.writeInt(if(this.hasBAC!=null) 1 else 0)
|
||||
if(this.hasBAC!=null) {
|
||||
dest.writeString(this.hasBAC?.name)
|
||||
}
|
||||
dest.writeInt(if(this.hasAA!=null) 1 else 0)
|
||||
if(this.hasAA!=null) {
|
||||
dest.writeString(this.hasAA?.name)
|
||||
}
|
||||
dest.writeInt(if(this.hasEAC!=null) 1 else 0)
|
||||
if(this.hasEAC!=null) {
|
||||
dest.writeString(this.hasEAC?.name)
|
||||
}
|
||||
dest.writeInt(if(this.hasCA!=null) 1 else 0)
|
||||
if(this.hasCA!=null) {
|
||||
dest.writeString(this.hasCA?.name)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<*> = object : Parcelable.Creator<FeatureStatus> {
|
||||
override fun createFromParcel(pc: Parcel): FeatureStatus {
|
||||
return FeatureStatus(pc)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<FeatureStatus?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
/*
|
||||
* JMRTD - A Java API for accessing machine readable travel documents.
|
||||
*
|
||||
* Copyright (C) 2006 - 2013 The JMRTD team
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* $Id: $
|
||||
*/
|
||||
|
||||
package org.jmrtd
|
||||
|
||||
import java.security.Provider
|
||||
import java.security.Security
|
||||
import java.util.ArrayList
|
||||
import java.util.Arrays
|
||||
import java.util.Collections
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* Security provider for JMRTD specific implementations.
|
||||
* Main motivation is to make JMRTD less dependent on the BouncyCastle provider.
|
||||
* Provides:
|
||||
*
|
||||
* * [java.security.cert.CertificateFactory] "CVC"
|
||||
* (a factory for [org.jmrtd.cert.CardVerifiableCertificate] instances)
|
||||
*
|
||||
* * [java.security.cert.CertStore] "PKD"
|
||||
* (LDAP based `CertStore`,
|
||||
* where the directory contains CSCA and document signer certificates)
|
||||
*
|
||||
* * [java.security.cert.CertStore] "JKS"
|
||||
* (`KeyStore` based `CertStore`,
|
||||
* where the JKS formatted `KeyStore` contains CSCA certificates)
|
||||
*
|
||||
* * [java.security.cert.CertStore] "PKCS12"
|
||||
* (`KeyStore` based `CertStore`,
|
||||
* where the PKCS#12 formatted `KeyStore` contains CSCA certificates)
|
||||
*
|
||||
*
|
||||
*
|
||||
* @author The JMRTD team (info@jmrtd.org)
|
||||
*
|
||||
* @version $Revision: $
|
||||
*/
|
||||
class JMRTDSecurityProvider private constructor() : Provider("JMRTD", 0.1, "JMRTD Security Provider") {
|
||||
|
||||
init {
|
||||
put("CertificateFactory.CVC", "org.jmrtd.cert.CVCertificateFactorySpi")
|
||||
put("CertStore.PKD", "org.jmrtd.cert.PKDCertStoreSpi")
|
||||
put("CertStore.JKS", "org.jmrtd.cert.KeyStoreCertStoreSpi")
|
||||
put("CertStore.BKS", "org.jmrtd.cert.KeyStoreCertStoreSpi")
|
||||
put("CertStore.PKCS12", "org.jmrtd.cert.KeyStoreCertStoreSpi")
|
||||
|
||||
if (BC_PROVIDER != null) {
|
||||
/* Replicate BC algorithms... */
|
||||
|
||||
/* FIXME: this won't work, our provider is not signed! */
|
||||
// replicateFromProvider("Cipher", "DESede/CBC/NoPadding", getBouncyCastleProvider());
|
||||
// replicateFromProvider("Cipher", "RSA/ECB/PKCS1Padding", getBouncyCastleProvider());
|
||||
// replicateFromProvider("Cipher", "RSA/NONE/NoPadding", getBouncyCastleProvider());
|
||||
// replicateFromProvider("KeyFactory", "RSA", getBouncyCastleProvider());
|
||||
// replicateFromProvider("KeyFactory", "DH", getBouncyCastleProvider());
|
||||
// replicateFromProvider("Mac", "ISO9797ALG3MAC", getBouncyCastleProvider());
|
||||
// replicateFromProvider("Mac", "ISO9797ALG3WITHISO7816-4PADDING", getBouncyCastleProvider());
|
||||
// replicateFromProvider("SecretKeyFactory", "DESede", getBouncyCastleProvider());
|
||||
|
||||
/* But these work fine. */
|
||||
replicateFromProvider("CertificateFactory", "X.509", bouncyCastleProvider!!)
|
||||
replicateFromProvider("CertStore", "Collection", bouncyCastleProvider!!)
|
||||
// replicateFromProvider("KeyStore", "JKS", SUN_PROVIDER);
|
||||
replicateFromProvider("MessageDigest", "SHA1", bouncyCastleProvider!!)
|
||||
replicateFromProvider("Signature", "SHA1withRSA/ISO9796-2", bouncyCastleProvider!!)
|
||||
replicateFromProvider("Signature", "MD2withRSA", bouncyCastleProvider!!)
|
||||
replicateFromProvider("Signature", "MD4withRSA", bouncyCastleProvider!!)
|
||||
replicateFromProvider("Signature", "MD5withRSA", bouncyCastleProvider!!)
|
||||
replicateFromProvider("Signature", "SHA1withRSA", bouncyCastleProvider!!)
|
||||
replicateFromProvider("Signature", "SHA1withRSA/ISO9796-2", bouncyCastleProvider!!)
|
||||
replicateFromProvider("Signature", "SHA256withRSA", bouncyCastleProvider!!)
|
||||
replicateFromProvider("Signature", "SHA256withRSA/ISO9796-2", bouncyCastleProvider!!)
|
||||
replicateFromProvider("Signature", "SHA384withRSA", bouncyCastleProvider!!)
|
||||
replicateFromProvider("Signature", "SHA384withRSA/ISO9796-2", bouncyCastleProvider!!)
|
||||
replicateFromProvider("Signature", "SHA512withRSA", bouncyCastleProvider!!)
|
||||
replicateFromProvider("Signature", "SHA512withRSA/ISO9796-2", bouncyCastleProvider!!)
|
||||
replicateFromProvider("Signature", "SHA224withRSA", bouncyCastleProvider!!)
|
||||
replicateFromProvider("Signature", "SHA224withRSA/ISO9796-2", bouncyCastleProvider!!)
|
||||
|
||||
replicateFromProvider("Signature", "SHA256withRSA/PSS", bouncyCastleProvider!!)
|
||||
|
||||
|
||||
/* Testing 0.4.7 -- MO */
|
||||
// replicateFromProvider("KeyStore", "UBER", getBouncyCastleProvider());
|
||||
// replicateFromProvider("KeyPairGenerator", "ECDHC", getBouncyCastleProvider());
|
||||
// replicateFromProvider("KeyPairGenerator", "ECDSA", getBouncyCastleProvider());
|
||||
// replicateFromProvider("X509StreamParser", "CERTIFICATE", getBouncyCastleProvider());
|
||||
|
||||
put("Alg.Alias.Mac.ISO9797Alg3Mac", "ISO9797ALG3MAC")
|
||||
put("Alg.Alias.CertificateFactory.X509", "X.509")
|
||||
}
|
||||
}
|
||||
|
||||
private fun replicateFromProvider(serviceName: String, algorithmName: String, provider: Provider) {
|
||||
val name = "$serviceName.$algorithmName"
|
||||
val service = provider[name]
|
||||
if (service != null) {
|
||||
put(name, service)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val serialVersionUID = -2881416441551680704L
|
||||
|
||||
private val LOGGER = Logger.getLogger("org.jmrtd")
|
||||
|
||||
private val SUN_PROVIDER_CLASS_NAME = "sun.security.provider.Sun"
|
||||
private val BC_PROVIDER_CLASS_NAME = "org.bouncycastle.jce.provider.BouncyCastleProvider"
|
||||
private val SC_PROVIDER_CLASS_NAME = "org.spongycastle.jce.provider.BouncyCastleProvider"
|
||||
|
||||
// private static final Provider SUN_PROVIDER = null; // getProviderOrNull(SUN_PROVIDER_CLASS_NAME);
|
||||
private val BC_PROVIDER = org.bouncycastle.jce.provider.BouncyCastleProvider()
|
||||
// getProviderOrNull(BC_PROVIDER_CLASS_NAME);
|
||||
private val SC_PROVIDER = org.spongycastle.jce.provider.BouncyCastleProvider()
|
||||
// getProviderOrNull(SC_PROVIDER_CLASS_NAME);
|
||||
val instance: Provider = JMRTDSecurityProvider()
|
||||
|
||||
init {
|
||||
Security.insertProviderAt(org.spongycastle.jce.provider.BouncyCastleProvider(), 1)
|
||||
/*
|
||||
if (BC_PROVIDER != null) { Security.insertProviderAt(BC_PROVIDER, 1); }
|
||||
if (SC_PROVIDER != null) { Security.insertProviderAt(SC_PROVIDER, 2); }
|
||||
if (JMRTD_PROVIDER != null) { Security.insertProviderAt(JMRTD_PROVIDER, 3); }*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily puts the BC provider on number one in the list of
|
||||
* providers, until caller calls [.endPreferBouncyCastleProvider].
|
||||
*
|
||||
* @return the index of BC, if it was present, in the list of providers
|
||||
*
|
||||
* @see .endPreferBouncyCastleProvider
|
||||
*/
|
||||
fun beginPreferBouncyCastleProvider(): Int {
|
||||
val bcProvider = bouncyCastleProvider ?: return -1
|
||||
val providers = Security.getProviders()
|
||||
for (i in providers.indices) {
|
||||
val provider = providers[i]
|
||||
if (bcProvider.javaClass.canonicalName == provider.javaClass.canonicalName) {
|
||||
Security.removeProvider(provider.name)
|
||||
Security.insertProviderAt(bcProvider, 1)
|
||||
return i + 1
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the BC provider from the number one position and puts it back
|
||||
* at its original position, after a call to [.beginPreferBouncyCastleProvider].
|
||||
*
|
||||
* @param i the original index of the BC provider
|
||||
*
|
||||
* @see .beginPreferBouncyCastleProvider
|
||||
*/
|
||||
fun endPreferBouncyCastleProvider(i: Int) {
|
||||
val bcProvider = bouncyCastleProvider
|
||||
Security.removeProvider(bcProvider!!.name)
|
||||
if (i > 0) {
|
||||
Security.insertProviderAt(bcProvider, i)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the BC provider, if present.
|
||||
*
|
||||
* @return the BC provider, the SC provider, or `null`
|
||||
*/
|
||||
val bouncyCastleProvider: Provider?
|
||||
get() {
|
||||
if (BC_PROVIDER != null) {
|
||||
return BC_PROVIDER
|
||||
}
|
||||
if (SC_PROVIDER != null) {
|
||||
return SC_PROVIDER
|
||||
}
|
||||
LOGGER.severe("No Bouncy or Spongy provider")
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SC provider, if present.
|
||||
*
|
||||
* @return the SC provider, the BC provider, or `null`
|
||||
*/
|
||||
val spongyCastleProvider: Provider?
|
||||
get() {
|
||||
if (SC_PROVIDER != null) {
|
||||
return SC_PROVIDER
|
||||
}
|
||||
if (BC_PROVIDER != null) {
|
||||
return BC_PROVIDER
|
||||
}
|
||||
LOGGER.severe("No Bouncy or Spongy provider")
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getProvider(serviceName: String, algorithmName: String): Provider? {
|
||||
val providers = getProviders(serviceName, algorithmName)
|
||||
return if (providers != null && providers.size > 0) {
|
||||
providers[0]
|
||||
} else null
|
||||
}
|
||||
|
||||
private fun getProviders(serviceName: String, algorithmName: String): List<Provider>? {
|
||||
if (Security.getAlgorithms(serviceName).contains(algorithmName)) {
|
||||
val providers = Security.getProviders("$serviceName.$algorithmName")
|
||||
return ArrayList(Arrays.asList(*providers))
|
||||
}
|
||||
if (BC_PROVIDER != null && BC_PROVIDER.getService(serviceName, algorithmName) != null) {
|
||||
return ArrayList(listOf<Provider>(BC_PROVIDER))
|
||||
}
|
||||
if (SC_PROVIDER != null && SC_PROVIDER.getService(serviceName, algorithmName) != null) {
|
||||
return ArrayList(listOf<Provider>(SC_PROVIDER))
|
||||
}
|
||||
return if (instance != null && instance.getService(serviceName, algorithmName) != null) {
|
||||
ArrayList(listOf(instance))
|
||||
} else null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,381 +0,0 @@
|
||||
/*
|
||||
* JMRTD - A Java API for accessing machine readable travel documents.
|
||||
*
|
||||
* Copyright (C) 2006 - 2013 The JMRTD team
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* $Id: $
|
||||
*/
|
||||
|
||||
package org.jmrtd
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URI
|
||||
import java.net.URLConnection
|
||||
import java.security.cert.CertSelector
|
||||
import java.security.cert.CertStore
|
||||
import java.security.cert.CertStoreException
|
||||
import java.security.cert.CertStoreParameters
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.CollectionCertStoreParameters
|
||||
import java.security.cert.TrustAnchor
|
||||
import java.security.cert.X509CertSelector
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.ArrayList
|
||||
import java.util.Collections
|
||||
import java.util.HashSet
|
||||
import java.util.logging.Logger
|
||||
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
import org.jmrtd.cert.KeyStoreCertStoreParameters
|
||||
import org.jmrtd.cert.PKDCertStoreParameters
|
||||
import org.jmrtd.cert.PKDMasterListCertStoreParameters
|
||||
import java.security.*
|
||||
|
||||
/**
|
||||
* Provides lookup for certificates, keys, CRLs used in
|
||||
* document validation and access control for data groups.
|
||||
*
|
||||
* @author The JMRTD team (info@jmrtd.org)
|
||||
*
|
||||
* @version $Revision: $
|
||||
*/
|
||||
class MRTDTrustStore
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param cscaAnchors the root certificates for document validation
|
||||
* @param cscaStores the certificates used in document validation
|
||||
* @param cvcaStores the certificates used for access to EAC protected data groups
|
||||
*/
|
||||
@JvmOverloads constructor(var cscaAnchors: MutableSet<TrustAnchor>? = HashSet(), var cscaStores: MutableList<CertStore>? = ArrayList(), var cvcaStores: MutableList<KeyStore>? = ArrayList()) {
|
||||
|
||||
fun clear() {
|
||||
this.cscaAnchors = HashSet()
|
||||
this.cscaStores = ArrayList()
|
||||
this.cvcaStores = ArrayList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root certificates for document validation.
|
||||
*
|
||||
* @return the cscaAnchors
|
||||
*/
|
||||
fun getCSCAAnchors(): Set<TrustAnchor>? {
|
||||
return cscaAnchors
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the certificates used in document validation.
|
||||
*
|
||||
* @return the cscaStores
|
||||
*/
|
||||
fun getCSCAStores(): List<CertStore>? {
|
||||
return cscaStores
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the certificates used for access to EAC protected data groups.
|
||||
*
|
||||
* @return the cvcaStores
|
||||
*/
|
||||
fun getCVCAStores(): List<KeyStore>? {
|
||||
return cvcaStores
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a root certificate for document validation.
|
||||
*
|
||||
* @param trustAnchor a trustAnchor
|
||||
*/
|
||||
fun addCSCAAnchor(trustAnchor: TrustAnchor) {
|
||||
cscaAnchors!!.add(trustAnchor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds root certificates for document validation.
|
||||
*
|
||||
* @param trustAnchors a collection of trustAnchors
|
||||
*/
|
||||
fun addCSCAAnchors(trustAnchors: Collection<TrustAnchor>) {
|
||||
cscaAnchors!!.addAll(trustAnchors)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a certificate store for document validation based on a URI.
|
||||
*
|
||||
* @param uri the URI
|
||||
*/
|
||||
fun addCSCAStore(uri: URI?) {
|
||||
if (uri == null) {
|
||||
LOGGER.severe("uri == null")
|
||||
return
|
||||
}
|
||||
val scheme = uri.scheme
|
||||
if (scheme == null) {
|
||||
LOGGER.severe("scheme == null, location = $uri")
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (scheme.equals("ldap", ignoreCase = true)) {
|
||||
addAsPKDStoreCSCACertStore(uri)
|
||||
} else {
|
||||
/* The scheme is probably "file" or "http"? Going to just open a connection. */
|
||||
try {
|
||||
addAsKeyStoreCSCACertStore(uri)
|
||||
} catch (kse: Exception) {
|
||||
try {
|
||||
addAsSingletonCSCACertStore(uri)
|
||||
} catch (e: Exception) {
|
||||
LOGGER.warning("Failed to open " + uri.toASCIIString() + " both as a keystore and as a DER certificate file")
|
||||
kse.printStackTrace()
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
} catch (gse: GeneralSecurityException) {
|
||||
gse.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds multiple certificate stores for document validation based on URIs.
|
||||
*
|
||||
* @param uris the URIs
|
||||
*/
|
||||
fun addCSCAStores(uris: List<URI>?) {
|
||||
if (uris == null) {
|
||||
LOGGER.severe("uris == null")
|
||||
return
|
||||
}
|
||||
for (uri in uris) {
|
||||
addCSCAStore(uri)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a key store for access to EAC protected data groups based on a URI.
|
||||
*
|
||||
* @param uri the URI
|
||||
*/
|
||||
fun addCVCAStore(uri: URI) {
|
||||
try {
|
||||
addAsCVCAKeyStore(uri)
|
||||
} catch (e: Exception) {
|
||||
LOGGER.warning("Exception in addCVCAStore: " + e.message)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds multiple key stores for access to EAC protected data groups based on URIs.
|
||||
*
|
||||
* @param uris the URIs
|
||||
*/
|
||||
fun addCVCAStores(uris: List<URI>) {
|
||||
for (uri in uris) {
|
||||
addCVCAStore(uri)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a certificate store for document validation.
|
||||
*
|
||||
* @param certStore the certificate store
|
||||
*/
|
||||
fun addCSCAStore(certStore: CertStore) {
|
||||
cscaStores!!.add(certStore)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a key store for access to EAC protected data groups.
|
||||
*
|
||||
* @param keyStore the key store
|
||||
*/
|
||||
fun addCVCAStore(keyStore: KeyStore) {
|
||||
cvcaStores!!.add(keyStore)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a trust anchor for document validation.
|
||||
*
|
||||
* @param trustAnchor the trust anchor
|
||||
*/
|
||||
fun removeCSCAAnchor(trustAnchor: TrustAnchor) {
|
||||
cscaAnchors!!.remove(trustAnchor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a certificate store for document validation.
|
||||
*
|
||||
* @param certStore the certificate store
|
||||
*/
|
||||
fun removeCSCAStore(certStore: CertStore) {
|
||||
cscaStores!!.remove(certStore)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a key store for access to EAC protected data groups.
|
||||
*
|
||||
* @param keyStore the key store
|
||||
*/
|
||||
fun removeCVCAStore(keyStore: KeyStore) {
|
||||
cvcaStores!!.remove(keyStore)
|
||||
}
|
||||
|
||||
/* ONLY PRIVATE METHODS BELOW */
|
||||
|
||||
@Throws(MalformedURLException::class, IOException::class, CertificateException::class, InvalidAlgorithmParameterException::class, NoSuchAlgorithmException::class, CertStoreException::class)
|
||||
private fun addAsSingletonCSCACertStore(uri: URI) {
|
||||
val urlConnection = uri.toURL().openConnection()
|
||||
val inputStream = urlConnection.getInputStream()
|
||||
val certFactory = CertificateFactory.getInstance("X.509", JMRTD_PROVIDER)
|
||||
val certificate = certFactory.generateCertificate(inputStream) as X509Certificate
|
||||
inputStream.close()
|
||||
val params = CollectionCertStoreParameters(setOf(certificate))
|
||||
val cscaStore = CertStore.getInstance("Collection", params)
|
||||
cscaStores!!.add(cscaStore)
|
||||
val rootCerts = cscaStore.getCertificates(SELF_SIGNED_X509_CERT_SELECTOR)
|
||||
addCSCAAnchors(getAsAnchors(rootCerts))
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the CVCA key store located at `uri`.
|
||||
*
|
||||
* @param uri a URI with a key store
|
||||
*/
|
||||
private fun addAsCVCAKeyStore(uri: URI) {
|
||||
addCVCAStore(getKeyStore(uri))
|
||||
}
|
||||
|
||||
@Throws(NoSuchAlgorithmException::class, InvalidAlgorithmParameterException::class, CertStoreException::class)
|
||||
private fun addAsPKDStoreCSCACertStore(uri: URI) {
|
||||
/* PKD store */
|
||||
val server = uri.host
|
||||
val port = uri.port
|
||||
val params = if (port < 0) PKDCertStoreParameters(server) else PKDCertStoreParameters(server, port)
|
||||
val cscaParams = if (port < 0) PKDMasterListCertStoreParameters(server) else PKDMasterListCertStoreParameters(server, port)
|
||||
val certStore = CertStore.getInstance("PKD", params)
|
||||
if (certStore != null) {
|
||||
addCSCAStore(certStore)
|
||||
}
|
||||
val cscaStore = CertStore.getInstance("PKD", cscaParams)
|
||||
if (cscaStore != null) {
|
||||
addCSCAStore(cscaStore)
|
||||
}
|
||||
val rootCerts = cscaStore!!.getCertificates(SELF_SIGNED_X509_CERT_SELECTOR)
|
||||
addCSCAAnchors(getAsAnchors(rootCerts))
|
||||
}
|
||||
|
||||
@Throws(KeyStoreException::class, InvalidAlgorithmParameterException::class, NoSuchAlgorithmException::class, CertStoreException::class)
|
||||
private fun addAsKeyStoreCSCACertStore(uri: URI) {
|
||||
val keyStore = getKeyStore(uri)
|
||||
val params = KeyStoreCertStoreParameters(keyStore)
|
||||
val certStore = CertStore.getInstance(keyStore.type, params)
|
||||
addCSCAStore(certStore)
|
||||
val rootCerts = certStore.getCertificates(SELF_SIGNED_X509_CERT_SELECTOR)
|
||||
addCSCAAnchors(getAsAnchors(rootCerts))
|
||||
}
|
||||
|
||||
@Throws(KeyStoreException::class, InvalidAlgorithmParameterException::class, NoSuchAlgorithmException::class, CertStoreException::class)
|
||||
fun addAsCSCACertStore(certStore: CertStore) {
|
||||
addCSCAStore(certStore)
|
||||
val rootCerts = certStore.getCertificates(SELF_SIGNED_X509_CERT_SELECTOR)
|
||||
addCSCAAnchors(getAsAnchors(rootCerts))
|
||||
}
|
||||
|
||||
|
||||
private fun getKeyStore(uri: URI): KeyStore {
|
||||
/*
|
||||
* We have to try all store types, only Bouncy Castle Store (BKS)
|
||||
* knows about unnamed EC keys.
|
||||
*/
|
||||
val storeTypes = arrayOf("JKS", "BKS", "PKCS12")
|
||||
for (storeType in storeTypes) {
|
||||
try {
|
||||
val keyStore = KeyStore.getInstance(storeType)
|
||||
val urlConnection = uri.toURL().openConnection()
|
||||
val inputStream = urlConnection.getInputStream()
|
||||
keyStore.load(inputStream, "".toCharArray())
|
||||
inputStream.close()
|
||||
return keyStore
|
||||
} catch (e: Exception) {
|
||||
// LOGGER.warning("Could not initialize CVCA key store with type " + storeType + ": " + e.getMessage());
|
||||
// e.printStackTrace();
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
throw IllegalArgumentException("Not a supported keystore")
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
init {
|
||||
Security.insertProviderAt(org.spongycastle.jce.provider.BouncyCastleProvider(), 1)
|
||||
}
|
||||
|
||||
private val JMRTD_PROVIDER = JMRTDSecurityProvider.instance
|
||||
|
||||
private val LOGGER = Logger.getLogger("org.jmrtd")
|
||||
|
||||
private val SELF_SIGNED_X509_CERT_SELECTOR = object : X509CertSelector() {
|
||||
override fun match(cert: Certificate): Boolean {
|
||||
if (cert !is X509Certificate) {
|
||||
return false
|
||||
}
|
||||
val issuer = cert.issuerX500Principal
|
||||
val subject = cert.subjectX500Principal
|
||||
return issuer == null && subject == null || subject == issuer
|
||||
}
|
||||
|
||||
override fun clone(): Any {
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of trust anchors based on the X509 certificates in `certificates`.
|
||||
*
|
||||
* @param certificates a collection of X509 certificates
|
||||
*
|
||||
* @return a set of trust anchors
|
||||
*/
|
||||
private fun getAsAnchors(certificates: Collection<Certificate>): Set<TrustAnchor> {
|
||||
val anchors = HashSet<TrustAnchor>(certificates.size)
|
||||
for (certificate in certificates) {
|
||||
if (certificate is X509Certificate) {
|
||||
anchors.add(TrustAnchor(certificate, null))
|
||||
}
|
||||
}
|
||||
return anchors
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,612 +0,0 @@
|
||||
package org.jmrtd
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
import net.sf.scuba.util.Hex
|
||||
|
||||
import org.jmrtd.protocol.EACCAResult
|
||||
import org.jmrtd.protocol.EACTAResult
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
import java.io.Serializable
|
||||
import java.security.cert.Certificate
|
||||
import java.util.ArrayList
|
||||
import java.util.Arrays
|
||||
import java.util.TreeMap
|
||||
|
||||
/**
|
||||
* A data type for communicating document verification check information.
|
||||
*
|
||||
* @author The JMRTD team (info@jmrtd.org)
|
||||
*
|
||||
* @version $Revision: 1559 $
|
||||
*/
|
||||
class VerificationStatus : Parcelable {
|
||||
|
||||
/* Verdict for this verification feature. */
|
||||
/**
|
||||
* Gets the AA verdict.
|
||||
*
|
||||
* @return the AA status
|
||||
*/
|
||||
var aa: Verdict? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the BAC verdict.
|
||||
*
|
||||
* @return the BAC status
|
||||
*/
|
||||
var bac: Verdict? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the SAC verdict.
|
||||
*
|
||||
* @return the SAC verdict
|
||||
*/
|
||||
var sac: Verdict? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the CS verdict.
|
||||
*
|
||||
* @return the CS status
|
||||
*/
|
||||
var cs: Verdict? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the hash table verdict.
|
||||
*
|
||||
* @return a verdict
|
||||
*/
|
||||
var ht: Verdict? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the DS verdict.
|
||||
*
|
||||
* @return the DS status
|
||||
*/
|
||||
var ds: Verdict? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the EAC verdict.
|
||||
*
|
||||
* @return the EAC status
|
||||
*/
|
||||
var eac: Verdict? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the CA verdict.
|
||||
*
|
||||
* @return the CA status
|
||||
*/
|
||||
var ca: Verdict? = null
|
||||
private set
|
||||
|
||||
/* Textual reason for the verdict. */
|
||||
/**
|
||||
* Gets the AA reason string.
|
||||
*
|
||||
* @return a reason string
|
||||
*/
|
||||
var aaReason: String? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the BAC verdict string.
|
||||
*
|
||||
* @return a verdict string
|
||||
*/
|
||||
var bacReason: String? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the SAC reason.
|
||||
*
|
||||
* @return a reason string
|
||||
*/
|
||||
var sacReason: String? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the country signature reason string.
|
||||
*
|
||||
* @return a reason string
|
||||
*/
|
||||
var csReason: String? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the hash table reason string.
|
||||
*
|
||||
* @return a reason string
|
||||
*/
|
||||
var htReason: String? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the document signature verdict reason string.
|
||||
*
|
||||
* @return a reason string
|
||||
*/
|
||||
var dsReason: String? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the EAC reason string.
|
||||
*
|
||||
* @return a reasons string
|
||||
*/
|
||||
var eacReason: String? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the CA reason string.
|
||||
*
|
||||
* @return a reasons string
|
||||
*/
|
||||
var caReason: String? = null
|
||||
private set
|
||||
|
||||
/* By products of the verification process that may be useful for relying parties to display. */
|
||||
private var triedBACEntries: List<BACKey>? = null /* As a result of BAC testing, this contains all tried BAC entries. */
|
||||
var hashResults: MutableMap<Int, HashMatchResult>? = null /* As a result of HT testing, this contains stored and computed hashes. */
|
||||
private var certificateChain: List<Certificate>? = null /* As a result of CS testing, this contains certificate chain from DSC to CSCA. */
|
||||
/**
|
||||
* Gets the EAC result.
|
||||
*
|
||||
* @return the EAC result
|
||||
*/
|
||||
var eacResult: EACTAResult? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the CA result.
|
||||
*
|
||||
* @return the CA result
|
||||
*/
|
||||
var caResult: EACCAResult? = null
|
||||
private set
|
||||
|
||||
/**
|
||||
* Outcome of a verification process.
|
||||
*
|
||||
* @author The JMRTD team (info@jmrtd.org)
|
||||
*
|
||||
* @version $Revision: 1559 $
|
||||
*/
|
||||
enum class Verdict {
|
||||
UNKNOWN, /* Unknown */
|
||||
NOT_PRESENT, /* Not present */
|
||||
NOT_CHECKED, /* Present, not checked */
|
||||
FAILED, /* Present, checked, and not ok */
|
||||
SUCCEEDED
|
||||
/* Present, checked, and ok */
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new status with all verdicts
|
||||
* set to UNKNOWN.
|
||||
*/
|
||||
constructor() {
|
||||
setAll(Verdict.UNKNOWN, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the AA verdict.
|
||||
*
|
||||
* @param v the status to set
|
||||
* @param reason a reason string
|
||||
*/
|
||||
fun setAA(v: Verdict, reason: String?) {
|
||||
this.aa = v
|
||||
this.aaReason = reason
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tried BAC entries.
|
||||
*
|
||||
* @return a list of BAC keys
|
||||
*/
|
||||
fun getTriedBACEntries(): List<*>? {
|
||||
return triedBACEntries
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the BAC verdict.
|
||||
*
|
||||
* @param v the status to set
|
||||
* @param reason a reason string
|
||||
* @param triedBACEntries the list of BAC entries that were tried
|
||||
*/
|
||||
fun setBAC(v: Verdict, reason: String?, triedBACEntries: List<BACKey>?) {
|
||||
this.bac = v
|
||||
this.bacReason = reason
|
||||
this.triedBACEntries = triedBACEntries
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the SAC verdict and reason string.
|
||||
*
|
||||
* @param v a verdict
|
||||
* @param reason a reason string
|
||||
*/
|
||||
fun setSAC(v: Verdict, reason: String) {
|
||||
this.sac = v
|
||||
this.sacReason = reason
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the certificate chain between DS and CSCA.
|
||||
*
|
||||
* @return a certificate chain
|
||||
*/
|
||||
fun getCertificateChain(): List<*>? {
|
||||
return certificateChain
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CS verdict.
|
||||
*
|
||||
* @param v the status to set
|
||||
* @param reason the reason string
|
||||
* @param certificateChain the certificate chain between DS and CSCA
|
||||
*/
|
||||
fun setCS(v: Verdict, reason: String?, certificateChain: List<Certificate>?) {
|
||||
this.cs = v
|
||||
this.csReason = reason
|
||||
this.certificateChain = certificateChain
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the DS verdict.
|
||||
*
|
||||
* @param v the status to set
|
||||
* @param reason reason string
|
||||
*/
|
||||
fun setDS(v: Verdict, reason: String?) {
|
||||
this.ds = v
|
||||
this.dsReason = reason
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hash match results.
|
||||
*
|
||||
* @return a list of hash match results
|
||||
*/
|
||||
/*fun getHashResults(): MutableMap<Int, HashMatchResult>? {
|
||||
return hashResults
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Sets the hash table status.
|
||||
*
|
||||
* @param v a verdict
|
||||
* @param reason the reason string
|
||||
* @param hashResults the hash match results
|
||||
*/
|
||||
fun setHT(v: Verdict, reason: String?, hashResults: MutableMap<Int, HashMatchResult>?) {
|
||||
this.ht = v
|
||||
this.htReason = reason
|
||||
this.hashResults = hashResults
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the EAC verdict.
|
||||
*
|
||||
* @param v the status to set
|
||||
* @param eacResult the EAC result
|
||||
* @param reason reason string
|
||||
*/
|
||||
fun setEAC(v: Verdict, reason: String?, eacResult: EACTAResult?) {
|
||||
this.eac = v
|
||||
this.eacReason = reason
|
||||
this.eacResult = eacResult
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CA verdict.
|
||||
*
|
||||
* @param v the status to set
|
||||
* @param eaccaResult the CA result
|
||||
* @param reason reason string
|
||||
*/
|
||||
fun setCA(v: Verdict, reason: String, eaccaResult: EACCAResult?) {
|
||||
this.ca = v
|
||||
this.caReason = reason
|
||||
this.caResult = eaccaResult
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all vedicts to v.
|
||||
*
|
||||
* @param verdict the status to set
|
||||
* @param reason reason string
|
||||
*/
|
||||
fun setAll(verdict: Verdict, reason: String?) {
|
||||
setAA(verdict, reason)
|
||||
setBAC(verdict, reason, null)
|
||||
setCS(verdict, reason, null)
|
||||
setDS(verdict, reason)
|
||||
setHT(verdict, reason, null)
|
||||
setEAC(verdict, reason, null)
|
||||
}
|
||||
|
||||
|
||||
constructor(`in`: Parcel) {
|
||||
this.aa = if (`in`.readInt() == 1) Verdict.valueOf(`in`.readString()!!) else null
|
||||
this.bac = if (`in`.readInt() == 1) Verdict.valueOf(`in`.readString()!!) else null
|
||||
this.sac = if (`in`.readInt() == 1) Verdict.valueOf(`in`.readString()!!) else null
|
||||
this.cs = if (`in`.readInt() == 1) Verdict.valueOf(`in`.readString()!!) else null
|
||||
this.ht = if (`in`.readInt() == 1) Verdict.valueOf(`in`.readString()!!) else null
|
||||
this.ds = if (`in`.readInt() == 1) Verdict.valueOf(`in`.readString()!!) else null
|
||||
this.eac = if (`in`.readInt() == 1) Verdict.valueOf(`in`.readString()!!) else null
|
||||
this.ca = if (`in`.readInt() == 1) Verdict.valueOf(`in`.readString()!!) else null
|
||||
|
||||
this.aaReason = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.bacReason = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.sacReason = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.csReason = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.htReason = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.dsReason = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.eacReason = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
this.caReason = if (`in`.readInt() == 1) `in`.readString() else null
|
||||
|
||||
if (`in`.readInt() == 1) {
|
||||
triedBACEntries = ArrayList()
|
||||
`in`.readList(triedBACEntries!!, BACKey::class.java.classLoader)
|
||||
}
|
||||
|
||||
if (`in`.readInt() == 1) {
|
||||
hashResults = TreeMap()
|
||||
val size = `in`.readInt()
|
||||
for (i in 0 until size) {
|
||||
val key = `in`.readInt()
|
||||
val value = `in`.readSerializable() as HashMatchResult
|
||||
hashResults!![key] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (`in`.readInt() == 1) {
|
||||
certificateChain = ArrayList()
|
||||
`in`.readList(certificateChain!!, Certificate::class.java.classLoader)
|
||||
}
|
||||
|
||||
if (`in`.readInt() == 1) {
|
||||
eacResult = `in`.readSerializable() as EACTAResult
|
||||
}
|
||||
|
||||
if (`in`.readInt() == 1) {
|
||||
caResult = `in`.readSerializable() as EACCAResult
|
||||
}
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeInt(if(this.aa!=null) 1 else 0)
|
||||
if(aa!=null) {
|
||||
dest.writeString(aa?.name)
|
||||
}
|
||||
dest.writeInt(if(this.bac!=null) 1 else 0)
|
||||
if(bac!=null) {
|
||||
dest.writeString(bac?.name)
|
||||
}
|
||||
dest.writeInt(if(this.sac!=null) 1 else 0)
|
||||
if(sac!=null) {
|
||||
dest.writeString(sac?.name)
|
||||
}
|
||||
dest.writeInt(if(this.cs!=null) 1 else 0)
|
||||
if(cs!=null) {
|
||||
dest.writeString(cs?.name)
|
||||
}
|
||||
dest.writeInt(if(this.ht!=null) 1 else 0)
|
||||
if(ht!=null) {
|
||||
dest.writeString(ht?.name)
|
||||
}
|
||||
dest.writeInt(if(this.ds!=null) 1 else 0)
|
||||
if(ds!=null) {
|
||||
dest.writeString(ds?.name)
|
||||
}
|
||||
dest.writeInt(if(this.eac!=null) 1 else 0)
|
||||
if(eac!=null) {
|
||||
dest.writeString(eac?.name)
|
||||
}
|
||||
dest.writeInt(if(this.ca!=null) 1 else 0)
|
||||
if(ca!=null) {
|
||||
dest.writeString(ca?.name)
|
||||
}
|
||||
|
||||
dest.writeInt(if (aaReason != null) 1 else 0)
|
||||
if (aaReason != null) {
|
||||
dest.writeString(aaReason)
|
||||
}
|
||||
|
||||
dest.writeInt(if (bacReason != null) 1 else 0)
|
||||
if (bacReason != null) {
|
||||
dest.writeString(bacReason)
|
||||
}
|
||||
|
||||
dest.writeInt(if (sacReason != null) 1 else 0)
|
||||
if (sacReason != null) {
|
||||
dest.writeString(sacReason)
|
||||
}
|
||||
|
||||
dest.writeInt(if (csReason != null) 1 else 0)
|
||||
if (csReason != null) {
|
||||
dest.writeString(csReason)
|
||||
}
|
||||
|
||||
dest.writeInt(if (htReason != null) 1 else 0)
|
||||
if (htReason != null) {
|
||||
dest.writeString(htReason)
|
||||
}
|
||||
|
||||
dest.writeInt(if (dsReason != null) 1 else 0)
|
||||
if (dsReason != null) {
|
||||
dest.writeString(dsReason)
|
||||
}
|
||||
|
||||
dest.writeInt(if (eacReason != null) 1 else 0)
|
||||
if (eacReason != null) {
|
||||
dest.writeString(eacReason)
|
||||
}
|
||||
|
||||
dest.writeInt(if (caReason != null) 1 else 0)
|
||||
if (caReason != null) {
|
||||
dest.writeString(caReason)
|
||||
}
|
||||
|
||||
dest.writeInt(if (triedBACEntries != null) 1 else 0)
|
||||
if (triedBACEntries != null) {
|
||||
dest.writeList(triedBACEntries)
|
||||
}
|
||||
|
||||
dest.writeInt(if (hashResults != null) 1 else 0)
|
||||
if (hashResults != null) {
|
||||
dest.writeInt(hashResults!!.size)
|
||||
for ((key, value) in hashResults!!) {
|
||||
dest.writeInt(key)
|
||||
dest.writeSerializable(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dest.writeInt(if (certificateChain != null) 1 else 0)
|
||||
if (certificateChain != null) {
|
||||
dest.writeList(certificateChain)
|
||||
}
|
||||
|
||||
dest.writeInt(if (eacResult != null) 1 else 0)
|
||||
if (eacResult != null) {
|
||||
dest.writeSerializable(eacResult)
|
||||
}
|
||||
|
||||
dest.writeInt(if (caResult != null) 1 else 0)
|
||||
if (caResult != null) {
|
||||
dest.writeSerializable(caResult)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The result of matching the stored and computed hashes of a single datagroup.
|
||||
*
|
||||
* FIXME: perhaps that boolean should be more like verdict, including a reason for mismatch if known (e.g. access denied for EAC datagroup) -- MO
|
||||
*/
|
||||
class HashMatchResult
|
||||
/**
|
||||
* Use null for computed hash if access was denied.
|
||||
*
|
||||
* @param storedHash the hash stored in SOd
|
||||
* @param computedHash the computed hash
|
||||
*/
|
||||
(storedHash: ByteArray, computedHash: ByteArray?) : Serializable {
|
||||
|
||||
/**
|
||||
* Gets the stored hash.
|
||||
*
|
||||
* @return a hash
|
||||
*/
|
||||
var storedHash: ByteArray? = null
|
||||
private set
|
||||
/**
|
||||
* Gets the computed hash.
|
||||
*
|
||||
* @return a hash
|
||||
*/
|
||||
var computedHash: ByteArray? = null
|
||||
private set
|
||||
|
||||
/**
|
||||
* Whether the hashes match.
|
||||
*
|
||||
* @return a boolean
|
||||
*/
|
||||
val isMatch: Boolean
|
||||
get() = Arrays.equals(storedHash, computedHash)
|
||||
|
||||
init {
|
||||
this.storedHash = storedHash
|
||||
this.computedHash = computedHash
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "HashResult [" + isMatch + ", stored: " + Hex.bytesToHexString(storedHash) + ", computed: " + Hex.bytesToHexString(computedHash)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return 11 + 3 * Arrays.hashCode(storedHash) + 5 * Arrays.hashCode(computedHash)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other == null) {
|
||||
return false
|
||||
}
|
||||
if (other === this) {
|
||||
return true
|
||||
}
|
||||
if (other.javaClass != this.javaClass) {
|
||||
return false
|
||||
}
|
||||
val otherHashResult = other as HashMatchResult?
|
||||
return Arrays.equals(otherHashResult!!.computedHash, computedHash) && Arrays.equals(otherHashResult.storedHash, storedHash)
|
||||
}
|
||||
|
||||
/* NOTE: Part of our serializable implementation. */
|
||||
@Throws(IOException::class, ClassNotFoundException::class)
|
||||
private fun readObject(inputStream: ObjectInputStream) {
|
||||
// inputStream.defaultReadObject();
|
||||
storedHash = readBytes(inputStream)
|
||||
computedHash = readBytes(inputStream)
|
||||
}
|
||||
|
||||
/* NOTE: Part of our serializable implementation. */
|
||||
@Throws(IOException::class)
|
||||
private fun writeObject(outputStream: ObjectOutputStream) {
|
||||
// outputStream.defaultWriteObject();
|
||||
writeByteArray(storedHash, outputStream)
|
||||
writeByteArray(computedHash, outputStream)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun readBytes(inputStream: ObjectInputStream): ByteArray? {
|
||||
val length = inputStream.readInt()
|
||||
if (length < 0) {
|
||||
return null
|
||||
}
|
||||
val bytes = ByteArray(length)
|
||||
for (i in 0 until length) {
|
||||
val b = inputStream.readInt()
|
||||
bytes[i] = b.toByte()
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun writeByteArray(bytes: ByteArray?, outputStream: ObjectOutputStream) {
|
||||
if (bytes == null) {
|
||||
outputStream.writeInt(-1)
|
||||
} else {
|
||||
outputStream.writeInt(bytes.size)
|
||||
for (b in bytes) {
|
||||
outputStream.writeInt(b.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val serialVersionUID = 263961258911936111L
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<*> = object : Parcelable.Creator<VerificationStatus> {
|
||||
override fun createFromParcel(pc: Parcel): VerificationStatus {
|
||||
return VerificationStatus(pc)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<VerificationStatus?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
package org.jmrtd.cert
|
||||
|
||||
|
||||
import org.spongycastle.asn1.*
|
||||
import org.spongycastle.asn1.pkcs.SignedData
|
||||
import org.spongycastle.jce.provider.X509CertificateObject
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.security.cert.CertSelector
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.X509CertSelector
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
|
||||
class CSCAMasterList private constructor() {
|
||||
private val certificates: MutableList<Certificate>
|
||||
|
||||
/**
|
||||
* Constructs a master lsit from a collection of certificates.
|
||||
*
|
||||
* @param certificates a collection of certificates
|
||||
*/
|
||||
constructor(certificates: Collection<Certificate>?) : this() {
|
||||
this.certificates.addAll(certificates!!)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
constructor(binary: ByteArray, selector: CertSelector = IDENTITY_SELECTOR) : this() {
|
||||
certificates.addAll(searchCertificates(binary, selector))
|
||||
}
|
||||
|
||||
fun getCertificates(): List<Certificate> {
|
||||
return certificates
|
||||
}
|
||||
|
||||
companion object {
|
||||
/** Use this to get all certificates, including link certificates. */
|
||||
private val IDENTITY_SELECTOR: CertSelector = object : X509CertSelector() {
|
||||
override fun match(cert: Certificate): Boolean {
|
||||
return if (cert !is X509Certificate) {
|
||||
false
|
||||
} else true
|
||||
}
|
||||
|
||||
override fun clone(): Any {
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/** Use this to get self-signed certificates only. (Excludes link certificates.) */
|
||||
private val SELF_SIGNED_SELECTOR: CertSelector = object : X509CertSelector() {
|
||||
override fun match(cert: Certificate): Boolean {
|
||||
if (cert !is X509Certificate) {
|
||||
return false
|
||||
}
|
||||
val x509Cert = cert
|
||||
val issuer = x509Cert.issuerX500Principal
|
||||
val subject = x509Cert.subjectX500Principal
|
||||
return issuer == null && subject == null || subject == issuer
|
||||
}
|
||||
|
||||
override fun clone(): Any {
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/* PRIVATE METHODS BELOW */
|
||||
private fun searchCertificates(binary: ByteArray, selector: CertSelector): List<Certificate> {
|
||||
val result: MutableList<Certificate> = ArrayList()
|
||||
try {
|
||||
val sequence = ASN1Sequence.getInstance(binary) as ASN1Sequence
|
||||
val signedDataList: List<SignedData>? = getSignedDataFromDERObject(sequence, null)
|
||||
for (signedData in signedDataList!!) {
|
||||
|
||||
// ASN1Set certificatesASN1Set = signedData.getCertificates();
|
||||
// Enumeration certificatesEnum = certificatesASN1Set.getObjects();
|
||||
// while (certificatesEnum.hasMoreElements()) {
|
||||
// Object certificateObject = certificatesEnum.nextElement();
|
||||
// // TODO: interpret certificateObject, and check signature
|
||||
// }
|
||||
val contentInfo = signedData.contentInfo
|
||||
val content: Any = contentInfo.content
|
||||
val certificates: Collection<Certificate>? = getCertificatesFromDERObject(content, null)
|
||||
for (certificate in certificates!!) {
|
||||
if (selector.match(certificate)) {
|
||||
result.add(certificate)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getSignedDataFromDERObject(o: Any, result: MutableList<SignedData>?): MutableList<SignedData>? {
|
||||
var result = result
|
||||
if (result == null) {
|
||||
result = ArrayList()
|
||||
}
|
||||
try {
|
||||
val signedData = SignedData.getInstance(o)
|
||||
if (signedData != null) {
|
||||
result.add(signedData)
|
||||
}
|
||||
return result
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
if (o is DERTaggedObject) {
|
||||
val childObject = o.getObject()
|
||||
return getSignedDataFromDERObject(childObject, result)
|
||||
} else if (o is ASN1Sequence) {
|
||||
val derObjects = o.objects
|
||||
while (derObjects.hasMoreElements()) {
|
||||
val nextObject = derObjects.nextElement()
|
||||
result = getSignedDataFromDERObject(nextObject, result)
|
||||
}
|
||||
return result
|
||||
} else if (o is ASN1Set) {
|
||||
val derObjects = o.objects
|
||||
while (derObjects.hasMoreElements()) {
|
||||
val nextObject = derObjects.nextElement()
|
||||
result = getSignedDataFromDERObject(nextObject, result)
|
||||
}
|
||||
return result
|
||||
} else if (o is DEROctetString) {
|
||||
val octets = o.octets
|
||||
val derInputStream = ASN1InputStream(ByteArrayInputStream(octets))
|
||||
try {
|
||||
while (true) {
|
||||
val derObject = derInputStream.readObject() ?: break
|
||||
result = getSignedDataFromDERObject(derObject, result)
|
||||
}
|
||||
derInputStream.close()
|
||||
} catch (ioe: IOException) {
|
||||
ioe.printStackTrace()
|
||||
}
|
||||
return result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getCertificatesFromDERObject(o: Any, certificates: MutableCollection<Certificate>?): MutableCollection<Certificate>? {
|
||||
var certificates = certificates
|
||||
if (certificates == null) {
|
||||
certificates = ArrayList()
|
||||
}
|
||||
try {
|
||||
val certAsASN1Object = org.spongycastle.asn1.x509.Certificate.getInstance(o)
|
||||
certificates.add(X509CertificateObject(certAsASN1Object)) // NOTE: >= BC 1.48
|
||||
// certificates.add(new X509CertificateObject(X509CertificateStructure.getInstance(certAsASN1Object))); // NOTE: <= BC 1.47
|
||||
return certificates
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
if (o is DERTaggedObject) {
|
||||
val childObject = o.getObject()
|
||||
return getCertificatesFromDERObject(childObject, certificates)
|
||||
} else if (o is ASN1Sequence) {
|
||||
val derObjects = o.objects
|
||||
while (derObjects.hasMoreElements()) {
|
||||
val nextObject = derObjects.nextElement()
|
||||
certificates = getCertificatesFromDERObject(nextObject, certificates)
|
||||
}
|
||||
return certificates
|
||||
} else if (o is ASN1Set) {
|
||||
val derObjects = o.objects
|
||||
while (derObjects.hasMoreElements()) {
|
||||
val nextObject = derObjects.nextElement()
|
||||
certificates = getCertificatesFromDERObject(nextObject, certificates)
|
||||
}
|
||||
return certificates
|
||||
} else if (o is DEROctetString) {
|
||||
val octets = o.octets
|
||||
val derInputStream = ASN1InputStream(ByteArrayInputStream(octets))
|
||||
try {
|
||||
while (true) {
|
||||
val derObject = derInputStream.readObject() ?: break
|
||||
certificates = getCertificatesFromDERObject(derObject, certificates)
|
||||
}
|
||||
} catch (ioe: IOException) {
|
||||
ioe.printStackTrace()
|
||||
}
|
||||
return certificates
|
||||
} else if (o is SignedData) {
|
||||
// ASN1Set certificatesASN1Set = signedData.getCertificates();
|
||||
// Enumeration certificatesEnum = certificatesASN1Set.getObjects();
|
||||
// while (certificatesEnum.hasMoreElements()) {
|
||||
// Object certificateObject = certificatesEnum.nextElement();
|
||||
// // TODO: interpret certificateObject, and check signature
|
||||
// }
|
||||
val contentInfo = o.contentInfo
|
||||
val content: Any = contentInfo.content
|
||||
return getCertificatesFromDERObject(content, certificates)
|
||||
}
|
||||
return certificates
|
||||
}
|
||||
}
|
||||
|
||||
/** Private constructor, only used locally. */
|
||||
init {
|
||||
certificates = ArrayList(256)
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* JMRTD - A Java API for accessing machine readable travel documents.
|
||||
*
|
||||
* Copyright (C) 2006 - 2013 The JMRTD team
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* $Id: $
|
||||
*/
|
||||
|
||||
package org.jmrtd.cert
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.net.URI
|
||||
import java.net.URLConnection
|
||||
import java.security.KeyStore
|
||||
import java.security.KeyStoreException
|
||||
import java.security.cert.CertStoreParameters
|
||||
import java.util.logging.Logger
|
||||
|
||||
import org.jmrtd.JMRTDSecurityProvider
|
||||
|
||||
/**
|
||||
* Parameters for key store backed certificate store.
|
||||
*
|
||||
* @author The JMRTD team (info@jmrtd.org)
|
||||
*
|
||||
* @version $Revision: $
|
||||
*/
|
||||
class KeyStoreCertStoreParameters(val keyStore: KeyStore) : Cloneable, CertStoreParameters {
|
||||
|
||||
@Throws(KeyStoreException::class)
|
||||
constructor(uri: URI, password: CharArray) : this(uri, DEFAULT_ALGORITHM, password)
|
||||
|
||||
@Throws(KeyStoreException::class)
|
||||
@JvmOverloads
|
||||
constructor(uri: URI, algorithm: String = DEFAULT_ALGORITHM, password: CharArray = DEFAULT_PASSWORD) : this(readKeyStore(uri, algorithm, password))
|
||||
|
||||
/**
|
||||
* Makes a shallow copy of this object as this
|
||||
* class is immutable.
|
||||
*
|
||||
* @return a shallow copy of this object
|
||||
*/
|
||||
override fun clone(): Any {
|
||||
return KeyStoreCertStoreParameters(keyStore)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val LOGGER = Logger.getLogger("org.jmrtd")
|
||||
|
||||
private val DEFAULT_ALGORITHM = "JKS"
|
||||
private val DEFAULT_PASSWORD = "".toCharArray()
|
||||
|
||||
@Throws(KeyStoreException::class)
|
||||
private fun readKeyStore(location: URI, keyStoreType: String, password: CharArray): KeyStore {
|
||||
try {
|
||||
val n = JMRTDSecurityProvider.beginPreferBouncyCastleProvider()
|
||||
val uc = location.toURL().openConnection()
|
||||
val inputStream = uc.getInputStream()
|
||||
var ks: KeyStore? = null
|
||||
ks = KeyStore.getInstance(keyStoreType)
|
||||
try {
|
||||
LOGGER.info("KeystoreCertStore will use provider for KeyStore: " + ks!!.provider.javaClass.canonicalName!!)
|
||||
ks.load(inputStream, password)
|
||||
} catch (ioe: IOException) {
|
||||
LOGGER.warning("Cannot read this file \"$location\" as keystore")
|
||||
// ioe.printStackTrace();
|
||||
}
|
||||
|
||||
inputStream.close()
|
||||
JMRTDSecurityProvider.endPreferBouncyCastleProvider(n)
|
||||
return ks
|
||||
} catch (e: Exception) {
|
||||
// e.printStackTrace();
|
||||
throw KeyStoreException("Error getting keystore: " + e.message)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* JMRTD - A Java API for accessing machine readable travel documents.
|
||||
*
|
||||
* Copyright (C) 2006 - 2013 The JMRTD team
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* $Id: $
|
||||
*/
|
||||
|
||||
package org.jmrtd.cert
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.KeyStore
|
||||
import java.security.KeyStoreException
|
||||
import java.security.cert.CRL
|
||||
import java.security.cert.CRLSelector
|
||||
import java.security.cert.CertSelector
|
||||
import java.security.cert.CertStoreException
|
||||
import java.security.cert.CertStoreParameters
|
||||
import java.security.cert.CertStoreSpi
|
||||
import java.security.cert.Certificate
|
||||
import java.util.ArrayList
|
||||
import java.util.Enumeration
|
||||
|
||||
/**
|
||||
* Certificate store backed by key store.
|
||||
*
|
||||
* @author The JMRTD team (info@jmrtd.org)
|
||||
*
|
||||
* @version $Revision: $
|
||||
*/
|
||||
class KeyStoreCertStoreSpi @Throws(InvalidAlgorithmParameterException::class)
|
||||
constructor(params: CertStoreParameters) : CertStoreSpi(params) {
|
||||
|
||||
private val keyStore: KeyStore
|
||||
|
||||
init {
|
||||
keyStore = (params as KeyStoreCertStoreParameters).keyStore
|
||||
}
|
||||
|
||||
@Throws(CertStoreException::class)
|
||||
override fun engineGetCertificates(selector: CertSelector): Collection<Certificate> {
|
||||
try {
|
||||
val certificates = ArrayList<Certificate>(keyStore.size())
|
||||
val aliases = keyStore.aliases()
|
||||
while (aliases.hasMoreElements()) {
|
||||
val alias = aliases.nextElement() as String
|
||||
if (keyStore.isCertificateEntry(alias)) {
|
||||
val certificate = keyStore.getCertificate(alias)
|
||||
if (selector.match(certificate)) {
|
||||
certificates.add(certificate)
|
||||
}
|
||||
}
|
||||
}
|
||||
return certificates
|
||||
} catch (kse: KeyStoreException) {
|
||||
throw CertStoreException(kse.message)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(CertStoreException::class)
|
||||
override fun engineGetCRLs(selector: CRLSelector): Collection<CRL> {
|
||||
return ArrayList(0)
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* JMRTD - A Java API for accessing machine readable travel documents.
|
||||
*
|
||||
* Copyright (C) 2006 - 2013 The JMRTD team
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* $Id: $
|
||||
*/
|
||||
|
||||
package org.jmrtd.cert
|
||||
|
||||
import java.security.cert.CertStoreParameters
|
||||
|
||||
/**
|
||||
* Parameters for PKD backed certificate store.
|
||||
*
|
||||
* @author The JMRTD team (info@jmrtd.org)
|
||||
*
|
||||
* @version $Revision: $
|
||||
*/
|
||||
open class PKDCertStoreParameters @JvmOverloads constructor(
|
||||
/**
|
||||
* @return the serverName
|
||||
*/
|
||||
val serverName: String = DEFAULT_SERVER_NAME,
|
||||
/**
|
||||
* @return the port
|
||||
*/
|
||||
val port: Int = DEFAULT_PORT,
|
||||
/**
|
||||
* @return the baseDN
|
||||
*/
|
||||
val baseDN: String = DEFAULT_BASE_DN) : Cloneable, CertStoreParameters {
|
||||
|
||||
constructor(serverName: String, baseDN: String) : this(serverName, DEFAULT_PORT, baseDN)
|
||||
|
||||
/**
|
||||
* Makes a copy of this object.
|
||||
*
|
||||
* @return a copy of this object
|
||||
*/
|
||||
override fun clone(): Any {
|
||||
return PKDCertStoreParameters(serverName, port, baseDN)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "PKDCertStoreParameters [$serverName:$port/$baseDN]"
|
||||
}
|
||||
|
||||
override fun equals(otherObj: Any?): Boolean {
|
||||
if (otherObj == null) {
|
||||
return false
|
||||
}
|
||||
if (otherObj === this) {
|
||||
return true
|
||||
}
|
||||
if (this.javaClass != otherObj.javaClass) {
|
||||
return false
|
||||
}
|
||||
val otherParams = otherObj as PKDCertStoreParameters?
|
||||
return (otherParams!!.serverName == this.serverName
|
||||
&& otherParams.port == this.port
|
||||
&& otherParams.baseDN == this.baseDN)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return (serverName.hashCode() + port + baseDN.hashCode()) * 2 + 303
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val DEFAULT_SERVER_NAME = "localhost"
|
||||
private val DEFAULT_PORT = 389
|
||||
private val DEFAULT_BASE_DN = "dc=data,dc=pkdDownload"
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* JMRTD - A Java API for accessing machine readable travel documents.
|
||||
*
|
||||
* Copyright (C) 2006 - 2013 The JMRTD team
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* $Id: $
|
||||
*/
|
||||
|
||||
package org.jmrtd.cert
|
||||
|
||||
/**
|
||||
* Parameters for PKD backed certificate store, selecting certificates provided
|
||||
* in CSCA master lists.
|
||||
*
|
||||
* @author The JMRTD team (info@jmrtd.org)
|
||||
*
|
||||
* @version $Revision: $
|
||||
*/
|
||||
class PKDMasterListCertStoreParameters : PKDCertStoreParameters {
|
||||
|
||||
constructor() : super()
|
||||
|
||||
@JvmOverloads
|
||||
constructor(serverName: String, baseDN: String = DEFAULT_BASE_DN) : super(serverName, baseDN)
|
||||
|
||||
@JvmOverloads
|
||||
constructor(serverName: String, port: Int, baseDN: String = DEFAULT_BASE_DN) : super(serverName, port, baseDN)
|
||||
|
||||
companion object {
|
||||
|
||||
private val DEFAULT_BASE_DN = "dc=CSCAMasterList,dc=pkdDownload"
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
||||
@@ -1,8 +0,0 @@
|
||||
<!-- drawable/check_circle_outline.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M11,16.5L6.5,12L7.91,10.59L11,13.67L16.59,8.09L18,9.5L11,16.5Z" />
|
||||
</vector>
|
||||
@@ -1,8 +0,0 @@
|
||||
<!-- drawable/close_circle_outline.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M14.59,8L12,10.59L9.41,8L8,9.41L10.59,12L8,14.59L9.41,16L12,13.41L14.59,16L16,14.59L13.41,12L16,9.41L14.59,8Z" />
|
||||
</vector>
|
||||
@@ -1,8 +0,0 @@
|
||||
<!-- drawable/help_circle_outline.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M11,18H13V16H11V18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,6A4,4 0 0,0 8,10H10A2,2 0 0,1 12,8A2,2 0 0,1 14,10C14,12 11,11.75 11,15H13C13,12.75 16,12.5 16,10A4,4 0 0,0 12,6Z" />
|
||||
</vector>
|
||||
@@ -1,170 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
</vector>
|
||||
@@ -1,8 +0,0 @@
|
||||
<!-- drawable/passport.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M6,2A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V4A2,2 0 0,0 18,2H6M12,5A5,5 0 0,1 17,10A5,5 0 0,1 12,15A5,5 0 0,1 7,10A5,5 0 0,1 12,5M12,6C11.59,6.62 11.25,7.29 11.04,8H12.96C12.75,7.29 12.42,6.62 12,6M10.7,6.22C9.78,6.53 9,7.17 8.54,8H10C10.18,7.38 10.4,6.78 10.7,6.22M13.29,6.22C13.59,6.78 13.82,7.38 14,8H15.46C15,7.17 14.21,6.54 13.29,6.22M8.13,9C8.05,9.32 8,9.65 8,10C8,10.35 8.05,10.68 8.13,11H9.82C9.78,10.67 9.75,10.34 9.75,10C9.75,9.66 9.78,9.33 9.82,9H8.13M10.83,9C10.78,9.32 10.75,9.66 10.75,10C10.75,10.34 10.78,10.67 10.83,11H13.17C13.21,10.67 13.25,10.34 13.25,10C13.25,9.66 13.21,9.32 13.17,9H10.83M14.18,9C14.22,9.33 14.25,9.66 14.25,10C14.25,10.34 14.22,10.67 14.18,11H15.87C15.95,10.68 16,10.35 16,10C16,9.65 15.95,9.32 15.87,9H14.18M8.54,12C9,12.83 9.78,13.46 10.7,13.78C10.4,13.22 10.18,12.63 10,12H8.54M11.04,12C11.25,12.72 11.59,13.38 12,14C12.42,13.38 12.75,12.72 12.96,12H11.04M14,12C13.82,12.63 13.59,13.22 13.29,13.78C14.21,13.46 15,12.83 15.46,12H14M7,17H17V19H7V17Z" />
|
||||
</vector>
|
||||
@@ -1,8 +0,0 @@
|
||||
<!-- drawable/account.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z" />
|
||||
</vector>
|
||||
@@ -1,8 +0,0 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="5dp" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/grey_2" />
|
||||
</shape>
|
||||
@@ -1,19 +0,0 @@
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checked="true">
|
||||
<shape>
|
||||
<corners android:topLeftRadius="5dp" />
|
||||
<corners android:bottomLeftRadius="5dp" />
|
||||
<solid android:color="@color/blue_light"></solid>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_pressed="true">
|
||||
<shape>
|
||||
<solid android:color="@color/white"></solid>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@android:color/transparent"></solid>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
@@ -1,19 +0,0 @@
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checked="true">
|
||||
<shape>
|
||||
<corners android:topRightRadius="5dp" />
|
||||
<corners android:bottomRightRadius="5dp" />
|
||||
<solid android:color="@color/blue_light"></solid>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_pressed="true">
|
||||
<shape>
|
||||
<solid android:color="@color/white"></solid>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@android:color/transparent"></solid>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
@@ -1,4 +0,0 @@
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checked="true" android:color="@color/toogle_text_color"/>
|
||||
<item android:state_checked="false" android:color="@color/toogle_text_color"/>
|
||||
</selector>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<font
|
||||
android:fontStyle="normal"
|
||||
android:fontWeight="700"
|
||||
android:font="@font/roboto_bold"
|
||||
app:fontStyle="normal"
|
||||
app:fontWeight="700"
|
||||
app:font="@font/roboto_bold" />
|
||||
<font
|
||||
android:fontStyle="italic"
|
||||
android:fontWeight="700"
|
||||
android:font="@font/roboto_bold_italic"
|
||||
app:fontStyle="italic"
|
||||
app:fontWeight="700"
|
||||
app:font="@font/roboto_bold_italic" />
|
||||
</font-family>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<font
|
||||
android:fontStyle="normal"
|
||||
android:fontWeight="500"
|
||||
android:font="@font/roboto_medium"
|
||||
app:fontStyle="normal"
|
||||
app:fontWeight="500"
|
||||
app:font="@font/roboto_medium" />
|
||||
<font
|
||||
android:fontStyle="italic"
|
||||
android:fontWeight="500"
|
||||
android:font="@font/roboto_medium_italic"
|
||||
app:fontStyle="italic"
|
||||
app:fontWeight="500"
|
||||
app:font="@font/roboto_medium_italic" />
|
||||
</font-family>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<font
|
||||
android:fontStyle="normal"
|
||||
android:fontWeight="400"
|
||||
android:font="@font/roboto_regular"
|
||||
app:fontStyle="normal"
|
||||
app:fontWeight="400"
|
||||
app:font="@font/roboto_regular" />
|
||||
<font
|
||||
android:fontStyle="italic"
|
||||
android:fontWeight="400"
|
||||
android:font="@font/roboto_italic"
|
||||
app:fontStyle="italic"
|
||||
app:fontWeight="400"
|
||||
app:font="@font/roboto_italic" />
|
||||
</font-family>
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:mContext="example.jllarraz.com.passportreader.ui.activities.CameraActivity" />
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:mContext="example.jllarraz.com.passportreader.ui.activities.NfcActivity" />
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:mContext="example.jllarraz.com.passportreader.ui.activities.PhotoActivity" />
|
||||
@@ -1,51 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/fragment_decoder_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#000">
|
||||
|
||||
<io.fotoapparat.view.CameraView
|
||||
android:id="@+id/camera_preview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<TextView android:id="@+id/status_view_top"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@+id/status_view_bottom"
|
||||
android:layout_margin="14dp"
|
||||
android:background="#0000"
|
||||
android:text=""
|
||||
android:textColor="@color/status_text"
|
||||
android:textSize="14sp"
|
||||
android:autoLink="web"
|
||||
android:clickable="true" />
|
||||
|
||||
<TextView android:id="@+id/status_view_bottom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_margin="14dp"
|
||||
android:background="#0000"
|
||||
android:text=""
|
||||
android:textColor="@color/status_text"
|
||||
android:textSize="14sp"
|
||||
android:autoLink="web"
|
||||
android:clickable="true" />
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -1,122 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TableLayout
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_small"
|
||||
android:stretchColumns="*"
|
||||
android:paddingBottom="@dimen/margin_medium"
|
||||
android:layout_above="@+id/progressBar">
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/margin_small">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/iconNfc"
|
||||
android:layout_width="@dimen/item_photo_width"
|
||||
android:layout_height="@dimen/item_photo_height"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_passport"
|
||||
android:layout_marginEnd="@dimen/margin_medium"
|
||||
/>
|
||||
|
||||
<TableLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/margin_medium"
|
||||
android:shrinkColumns="*"
|
||||
android:layout_weight="1">
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/value_passport_number"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_tiny"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/value_DOB"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_tiny"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/value_expiration_date"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_tiny"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@+id/textViewNfcTitle"
|
||||
android:layout_alignRight="@+id/textViewNfcTitle"
|
||||
android:layout_above="@+id/textViewNfcTitle"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewNfcTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/nfc_title"
|
||||
android:gravity="center"
|
||||
android:layout_centerInParent="true"
|
||||
android:textSize="14sp"
|
||||
android:layout_margin="14dp"/>
|
||||
|
||||
<TextView android:id="@+id/status_view_bottom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_margin="14dp"
|
||||
android:background="#0000"
|
||||
android:text=""
|
||||
android:textColor="@color/status_text"
|
||||
android:textSize="14sp"
|
||||
android:autoLink="web"
|
||||
android:clickable="true" />
|
||||
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/recent_activity_layout"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:clipChildren="false"
|
||||
android:clickable="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<example.jllarraz.com.passportreader.ui.views.TouchImageView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerInside"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,148 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:clipChildren="false"
|
||||
android:scrollbars="none"
|
||||
|
||||
>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/titleRadioGroup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="22dp"
|
||||
android:text="@string/selection_title"
|
||||
style="@style/InputLabel" />
|
||||
|
||||
<RadioGroup
|
||||
android:checkedButton="@+id/radioButtonManual"
|
||||
android:id="@+id/radioButtonDataEntry"
|
||||
style="@style/ToogleGroup"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatRadioButton
|
||||
android:layout_marginLeft="1dp"
|
||||
android:id="@+id/radioButtonManual"
|
||||
android:text="@string/selection_manual"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatRadioButton
|
||||
android:layout_marginRight="1dp"
|
||||
android:id="@+id/radioButtonOcr"
|
||||
android:text="@string/selection_automatic"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
</RadioGroup>
|
||||
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutManual"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="22dp"
|
||||
>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textInputLayoutDocumentNumber"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/documentNumber"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_document_number"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="textCapCharacters"
|
||||
android:maxLength="9"
|
||||
android:maxLines="3" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textInputLayoutDocumentExpiration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/documentExpiration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_document_expiration"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="number"
|
||||
android:maxLength="6"
|
||||
android:maxLines="3" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textInputLayoutDateOfBirth"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/documentDateOfBirth"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_document_date_of_birth"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="number"
|
||||
android:maxLength="6"
|
||||
android:maxLines="3" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonReadNfc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/read_nfc"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonDownloadCSCA"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/download_csca"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonDeleteCSCA"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/delete_csca"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutAutomatic"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="22dp"
|
||||
android:visibility="gone"
|
||||
>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |