[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
This commit is contained in:
Justin Hernandez
2025-09-18 14:55:25 -07:00
committed by GitHub
parent b0ae194584
commit a005bde034
132 changed files with 510 additions and 12824 deletions

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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: |

View File

@@ -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
View File

@@ -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/

View File

@@ -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)

View File

@@ -1,10 +0,0 @@
*.iml
.gradle
/local.properties
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
.DS_Store
/build
/captures
.externalNativeBuild

View File

@@ -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
![alt text](https://github.com/jllarraz/AndroidPassportReader/blob/master/examples/passport_ireland.jpg)
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.

View File

@@ -1 +0,0 @@
/build

View File

@@ -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'
}

View File

@@ -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

View File

@@ -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());
}
}

View File

@@ -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>

View File

@@ -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]*$

View File

@@ -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
}

View File

@@ -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"
}

View File

@@ -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)
}
}
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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()
}

View File

@@ -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
}
}

View File

@@ -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>
}

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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
}
}

View File

@@ -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()
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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}$"
}
}

View File

@@ -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}$"
}
}

View File

@@ -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>)

View File

@@ -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)
}
}

View File

@@ -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)
}
}
}

View File

@@ -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()
}
}

View File

@@ -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>()
}
}

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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] &quot;CVC&quot;
* (a factory for [org.jmrtd.cert.CardVerifiableCertificate] instances)
*
* * [java.security.cert.CertStore] &quot;PKD&quot;
* (LDAP based `CertStore`,
* where the directory contains CSCA and document signer certificates)
*
* * [java.security.cert.CertStore] &quot;JKS&quot;
* (`KeyStore` based `CertStore`,
* where the JKS formatted `KeyStore` contains CSCA certificates)
*
* * [java.security.cert.CertStore] &quot;PKCS12&quot;
* (`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
}
}
}

View File

@@ -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.
*/

View File

@@ -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)
}
}
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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)
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Some files were not shown because too many files have changed in this diff Show More