├── .github
├── dependabot.yml
└── workflows
│ ├── android_test.yml
│ ├── lint.yml
│ ├── release.yml
│ └── unit_test.yml
├── .github_changelog_generator
├── .gitignore
├── CHANGELOG.md
├── CODEOWNERS
├── Contributor License Agreement
├── HISTORY.md
├── LICENSE
├── README.md
├── build.gradle
├── cardscan-demo
├── .gitignore
├── README.md
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── getbouncer
│ │ └── cardscan
│ │ └── demo
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── getbouncer
│ │ │ └── cardscan
│ │ │ └── demo
│ │ │ ├── LaunchActivity.java
│ │ │ └── SingleActivityDemo.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_launch.xml
│ │ └── activity_single_demo.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── getbouncer
│ └── cardscan
│ └── demo
│ └── ExampleUnitTest.kt
├── cardscan-ui
├── .gitignore
├── README.md
├── build.gradle
├── cardscan-ui.iml
├── consumer-rules.pro
├── deploy.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── getbouncer
│ │ └── cardscan
│ │ └── ui
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── getbouncer
│ │ │ └── cardscan
│ │ │ └── ui
│ │ │ ├── CardScanActivity.kt
│ │ │ ├── CardScanBaseActivity.kt
│ │ │ ├── CardScanFlow.kt
│ │ │ ├── CardScanSheet.kt
│ │ │ ├── analyzer
│ │ │ ├── CompletionLoopAnalyzer.kt
│ │ │ └── MainLoopAnalyzer.kt
│ │ │ ├── exception
│ │ │ ├── StripeNetworkException.kt
│ │ │ └── UnknownScanException.kt
│ │ │ └── result
│ │ │ ├── CompletionLoopAggregator.kt
│ │ │ ├── MainLoopAggregator.kt
│ │ │ └── MainLoopStateMachine.kt
│ └── res
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimensions.xml
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── getbouncer
│ └── cardscan
│ └── ui
│ └── result
│ └── MainLoopStateMachineTest.kt
├── docs
└── images
│ └── demo.gif
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── scan-camera
├── .gitignore
├── README.md
├── build.gradle
├── consumer-rules.pro
├── deploy.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── getbouncer
│ └── scan
│ └── camera
│ ├── Camera1Adapter.kt
│ ├── CameraAdapter.kt
│ └── CameraSelector.kt
├── scan-camera2
├── .gitignore
├── build.gradle
├── deploy.gradle
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── getbouncer
│ │ └── scan
│ │ └── camera
│ │ └── extension
│ │ └── UtilInstrumentationTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── getbouncer
│ │ └── scan
│ │ └── camera
│ │ └── extension
│ │ ├── CameraAdapterImpl.kt
│ │ ├── CameraDetails.kt
│ │ └── Util.kt
│ └── test
│ └── java
│ └── com
│ └── getbouncer
│ └── scan
│ └── camera
│ └── extension
│ └── UtilTest.kt
├── scan-camerax
├── .gitignore
├── build.gradle
├── deploy.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── getbouncer
│ └── scan
│ └── camera
│ └── extension
│ ├── CameraAdapterImpl.kt
│ ├── Image.kt
│ └── Util.kt
├── scan-framework
├── .gitignore
├── README.md
├── build.gradle
├── consumer-rules.pro
├── deploy.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ ├── assets
│ │ └── sample_resource.tflite
│ ├── java
│ │ └── com
│ │ │ └── getbouncer
│ │ │ └── scan
│ │ │ └── framework
│ │ │ ├── FetcherTest.kt
│ │ │ ├── LoaderTest.kt
│ │ │ ├── StorageTest.kt
│ │ │ ├── api
│ │ │ └── BouncerApiTest.kt
│ │ │ ├── image
│ │ │ └── BitmapExtensionsTest.kt
│ │ │ ├── layout
│ │ │ └── LayoutTest.kt
│ │ │ └── util
│ │ │ └── AppDetailsTest.kt
│ └── res
│ │ └── drawable
│ │ └── ocr_card_numbers_clear.png
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── getbouncer
│ │ └── scan
│ │ └── framework
│ │ ├── Analyzer.kt
│ │ ├── Config.kt
│ │ ├── Fetcher.kt
│ │ ├── Loader.kt
│ │ ├── Loop.kt
│ │ ├── MachineState.kt
│ │ ├── Result.kt
│ │ ├── Scan.kt
│ │ ├── Stat.kt
│ │ ├── Storage.kt
│ │ ├── TrackedImage.kt
│ │ ├── api
│ │ ├── BouncerApi.kt
│ │ ├── Network.kt
│ │ ├── NetworkResult.kt
│ │ └── dto
│ │ │ ├── AppInfo.kt
│ │ │ ├── BouncerErrorResponse.kt
│ │ │ ├── ClientDevice.kt
│ │ │ ├── ClientStats.kt
│ │ │ ├── ModelDetails.kt
│ │ │ ├── ModelSignedUrlResponse.kt
│ │ │ └── ValidateApiKeyResponse.kt
│ │ ├── exception
│ │ ├── ImageTypeNotSupportedException.kt
│ │ └── InvalidBouncerApiKeyException.kt
│ │ ├── image
│ │ ├── BitmapExtensions.kt
│ │ ├── ImageExtensions.kt
│ │ ├── MLImage.kt
│ │ ├── NV21Image.kt
│ │ └── YuvImageExtensions.kt
│ │ ├── interop
│ │ ├── BlockingAnalyzer.kt
│ │ ├── BlockingResult.kt
│ │ └── JavaContinuation.kt
│ │ ├── ml
│ │ ├── ModelVersionTracker.kt
│ │ ├── NonMaximumSuppression.kt
│ │ ├── TensorFlowLiteAnalyzer.kt
│ │ └── ssd
│ │ │ ├── ClassifierScores.kt
│ │ │ ├── RectForm.kt
│ │ │ └── SizeAndCenter.kt
│ │ ├── time
│ │ ├── Clock.kt
│ │ ├── Coroutine.kt
│ │ ├── Duration.kt
│ │ ├── Rate.kt
│ │ └── Timer.kt
│ │ └── util
│ │ ├── AppDetails.kt
│ │ ├── ArrayExtensions.kt
│ │ ├── Device.kt
│ │ ├── File.kt
│ │ ├── FrameRateTracker.kt
│ │ ├── FrameSaver.kt
│ │ ├── ItemCounter.kt
│ │ ├── Layout.kt
│ │ ├── Memoize.kt
│ │ └── Retry.kt
│ └── test
│ └── java
│ └── com
│ └── getbouncer
│ └── scan
│ └── framework
│ ├── AnalyzerTest.kt
│ ├── LoopTest.kt
│ ├── interop
│ ├── BlockingAnalyzerTest.java
│ └── BlockingResultTest.java
│ ├── time
│ └── DurationTest.kt
│ └── util
│ ├── ArrayExtensionsTest.kt
│ ├── FrameSaverTest.kt
│ ├── ItemCounterTest.kt
│ ├── MemoizeTest.kt
│ └── RetryTest.kt
├── scan-payment-full
├── .gitignore
├── build.gradle
├── deploy.gradle
└── src
│ ├── androidTest
│ ├── java
│ │ └── com
│ │ │ └── getbouncer
│ │ │ └── scan
│ │ │ └── payment
│ │ │ └── ml
│ │ │ ├── CardDetectTest.kt
│ │ │ └── SSDOcrTest.kt
│ └── res
│ │ └── drawable
│ │ ├── card_no_card.png
│ │ ├── card_no_pan.png
│ │ ├── card_pan.png
│ │ ├── ocr_card_numbers.png
│ │ └── ocr_card_numbers_qr.png
│ └── main
│ ├── AndroidManifest.xml
│ └── assets
│ ├── darknite_1_1_1_16.tflite
│ └── ux_0_5_23_16.tflite
├── scan-payment-minimal
├── .gitignore
├── build.gradle
├── deploy.gradle
└── src
│ ├── androidTest
│ ├── java
│ │ └── com
│ │ │ └── getbouncer
│ │ │ └── scan
│ │ │ └── payment
│ │ │ └── ml
│ │ │ ├── CardDetectTest.kt
│ │ │ └── SSDOcrTest.kt
│ └── res
│ │ └── drawable
│ │ ├── card_no_card.png
│ │ ├── card_no_pan.png
│ │ ├── card_pan.png
│ │ ├── ocr_card_numbers.png
│ │ └── ocr_card_numbers_qr.png
│ └── main
│ ├── AndroidManifest.xml
│ └── assets
│ ├── UX.0.25.106.8.tflite
│ └── mb2_brex_metal_synthetic_svhnextra_epoch_3_5_98_8.tflite
├── scan-payment
├── .gitignore
├── README.md
├── build.gradle
├── consumer-rules.pro
├── deploy.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ ├── java
│ │ └── com
│ │ │ └── getbouncer
│ │ │ └── scan
│ │ │ └── payment
│ │ │ ├── ImageTest.kt
│ │ │ ├── PaymentCardAndroidTest.kt
│ │ │ └── ml
│ │ │ ├── CardDetectTest.kt
│ │ │ ├── ExpiryDetectTest.kt
│ │ │ ├── SSDOcrTest.kt
│ │ │ ├── TextDetectTest.kt
│ │ │ └── ssd
│ │ │ └── SSDTest.kt
│ └── res
│ │ ├── drawable
│ │ ├── card_no_card.png
│ │ ├── card_no_pan.png
│ │ ├── card_pan.png
│ │ ├── ocr_card_numbers.png
│ │ └── ocr_card_numbers_qr.png
│ │ └── raw
│ │ └── sample_bitmap.bin
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── getbouncer
│ │ │ └── scan
│ │ │ └── payment
│ │ │ ├── FrameDetails.kt
│ │ │ ├── Image.kt
│ │ │ ├── ModelManager.kt
│ │ │ ├── TextDetectModelManager.kt
│ │ │ ├── analyzer
│ │ │ └── NameAndExpiryAnalyzer.kt
│ │ │ ├── card
│ │ │ ├── CardExpiry.kt
│ │ │ ├── CardIssuer.kt
│ │ │ ├── CardType.kt
│ │ │ ├── PanFormatter.kt
│ │ │ ├── PanValidator.kt
│ │ │ ├── PaymentCard.kt
│ │ │ ├── PaymentCardUtils.kt
│ │ │ └── RequiresMatchingCard.kt
│ │ │ └── ml
│ │ │ ├── AlphabetDetect.kt
│ │ │ ├── AlphabetDetectModelManager.kt
│ │ │ ├── CardDetect.kt
│ │ │ ├── CardDetectModelManager.kt
│ │ │ ├── ExpiryDetect.kt
│ │ │ ├── ExpiryDetectModelManager.kt
│ │ │ ├── SSDOcr.kt
│ │ │ ├── SSDOcrModelManager.kt
│ │ │ ├── TextDetect.kt
│ │ │ ├── ssd
│ │ │ ├── DetectionBox.kt
│ │ │ ├── OcrPriorsGenerator.kt
│ │ │ └── SSD.kt
│ │ │ └── yolo
│ │ │ └── Yolo.kt
│ └── res
│ │ └── raw
│ │ └── payment_card_types.bin
│ └── test
│ └── java
│ └── com
│ └── getbouncer
│ └── scan
│ └── payment
│ └── card
│ └── PaymentCardTest.kt
├── scan-ui
├── .gitignore
├── README.md
├── build.gradle
├── consumer-rules.pro
├── deploy.gradle
├── proguard-rules.pro
├── scan-ui.iml
└── src
│ ├── androidTest
│ ├── java
│ │ └── com
│ │ │ └── getbouncer
│ │ │ └── scan
│ │ │ └── ui
│ │ │ ├── DebugOverlayTest.kt
│ │ │ └── util
│ │ │ └── ViewExtensionsTest.kt
│ └── res
│ │ └── values
│ │ └── colors.xml
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── getbouncer
│ │ │ └── scan
│ │ │ └── ui
│ │ │ ├── DebugOverlay.kt
│ │ │ ├── ScanActivity.kt
│ │ │ ├── ScanFlow.kt
│ │ │ ├── SimpleScanActivity.kt
│ │ │ ├── ViewFinderBackground.kt
│ │ │ └── util
│ │ │ └── ViewExtensions.kt
│ └── res
│ │ ├── drawable
│ │ ├── bouncer_camera_swap_dark.xml
│ │ ├── bouncer_camera_swap_light.xml
│ │ ├── bouncer_card_background_correct.xml
│ │ ├── bouncer_card_background_found.xml
│ │ ├── bouncer_card_background_not_found.xml
│ │ ├── bouncer_card_background_wrong.xml
│ │ ├── bouncer_card_border_correct.xml
│ │ ├── bouncer_card_border_found.xml
│ │ ├── bouncer_card_border_found_long.xml
│ │ ├── bouncer_card_border_not_found.xml
│ │ ├── bouncer_card_border_wrong.xml
│ │ ├── bouncer_close_button_dark.xml
│ │ ├── bouncer_close_button_light.xml
│ │ ├── bouncer_flash_off_dark.xml
│ │ ├── bouncer_flash_off_light.xml
│ │ ├── bouncer_flash_on_dark.xml
│ │ ├── bouncer_flash_on_light.xml
│ │ ├── bouncer_lock_dark.xml
│ │ ├── bouncer_lock_light.xml
│ │ ├── bouncer_logo_dark_background.xml
│ │ └── bouncer_logo_light_background.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimensions.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── getbouncer
│ └── scan
│ └── ui
│ └── ExampleUnitTest.kt
├── settings.gradle
├── settings
└── checkstyle.xml
├── tensorflow-lite-arm-only
├── .gitignore
├── build.gradle
├── deploy.gradle
└── tensorflow-lite-all-models-arm-only.aar
└── tensorflow-lite
├── .gitignore
├── build.gradle
├── deploy.gradle
└── tensorflow-lite-all-models.aar
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: gradle
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 | ignore:
9 | - dependency-name: androidx.camera:camera-lifecycle
10 | versions:
11 | - 1.1.0-alpha04
12 | - dependency-name: androidx.camera:camera-camera2
13 | versions:
14 | - 1.1.0-alpha04
15 | - dependency-name: androidx.camera:camera-view
16 | versions:
17 | - 1.0.0-alpha24
18 | - dependency-name: androidx.camera:camera-core
19 | versions:
20 | - 1.1.0-alpha04
21 | - dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-android
22 | versions:
23 | - 1.4.3-native-mt
24 | - dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-core
25 | versions:
26 | - 1.4.3-native-mt
27 | - dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-test
28 | versions:
29 | - 1.4.3-native-mt
30 | - dependency-name: org.jetbrains.kotlin:kotlin-test
31 | versions:
32 | - 1.4.31
33 | - dependency-name: org.jetbrains.kotlin.plugin.serialization
34 | versions:
35 | - 1.4.30
36 | - 1.4.31
37 | - dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
38 | versions:
39 | - 1.4.30
40 | - 1.4.31
41 |
--------------------------------------------------------------------------------
/.github/workflows/android_test.yml:
--------------------------------------------------------------------------------
1 | name: Instrumentation Tests
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | instrumentation-test:
11 |
12 | runs-on: macOS-latest
13 |
14 | steps:
15 | - name: checkout
16 | uses: actions/checkout@v2
17 |
18 | - name: test
19 | uses: reactivecircus/android-emulator-runner@v2
20 | with:
21 | api-level: 29
22 | script: ./gradlew connectedCheck
23 |
24 | - name: upload-artifacts
25 | uses: actions/upload-artifact@v2
26 | if: failure()
27 | with:
28 | name: test-report
29 | path: ${{ github.workspace }}/*/build/reports/
30 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | lint:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - name: checkout
16 | uses: actions/checkout@v2
17 |
18 | - name: set up JDK 1.8
19 | uses: actions/setup-java@v1
20 | with:
21 | java-version: 1.8
22 |
23 | - name: lint
24 | run: ./gradlew ktlint
25 |
26 | - name: CheckStyle
27 | run: ./gradlew checkJavaStyle
28 |
--------------------------------------------------------------------------------
/.github/workflows/unit_test.yml:
--------------------------------------------------------------------------------
1 | name: Unit Tests
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | test:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - name: checkout
16 | uses: actions/checkout@v2
17 |
18 | - name: set up JDK 1.8
19 | uses: actions/setup-java@v1
20 | with:
21 | java-version: 1.8
22 |
23 | - name: test
24 | run: ./gradlew test
25 |
26 | - name: upload-artifacts
27 | uses: actions/upload-artifact@v2
28 | if: failure()
29 | with:
30 | name: test-report
31 | path: ${{ github.workspace }}/*/build/reports/
32 |
--------------------------------------------------------------------------------
/.github_changelog_generator:
--------------------------------------------------------------------------------
1 | since-tag=2.0.0015
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/compiler.xml
6 | /.idea/libraries
7 | /.idea/modules.xml
8 | /.idea/workspace.xml
9 | /.idea/navEditor.xml
10 | /.idea/assetWizardSettings.xml
11 | /.idea/encodings.xml
12 | /.idea/gradle.xml
13 | /.idea/misc.xml
14 | /.idea/runConfigurations.xml
15 | /.idea/vcs.xml
16 | /.idea/dictionaries
17 | /.idea/codeStyles/Project.xml
18 | /.idea/codeStyles/codeStyleConfig.xml
19 | /.idea/.name
20 | /.idea/checkstyle-idea.xml
21 | /.idea/jarRepositories.xml
22 | .DS_Store
23 | /build
24 | /captures
25 | .externalNativeBuild
26 | langapiconfig.json
27 | *.cxx
28 | github_token
29 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # This is a comment.
2 | # Each line is a file pattern followed by one or more owners.
3 |
4 | # These owners will be the default owners for everything in
5 | # the repo. Unless a later match takes precedence,
6 | # @global-owner1 and @global-owner2 will be requested for
7 | # review when someone opens a pull request.
8 | * @awushensky @xsl @kingst @dxaen @awushensky-stripe
9 |
10 | # Order is important; the last matching pattern takes the most
11 | # precedence. When someone opens a pull request that only
12 | # modifies JS files, only @js-owner and not the global
13 | # owner(s) will be requested for a review.
14 | # *.js @js-owner
15 |
16 | # You can also use email addresses if you prefer. They'll be
17 | # used to look up users just like we do for commit author
18 | # emails.
19 | # *.go docs@example.com
20 |
21 | # In this example, @doctocat owns any files in the build/logs
22 | # directory at the root of the repository and any of its
23 | # subdirectories.
24 | # /build/logs/ @doctocat
25 |
26 | # The `docs/*` pattern will match files like
27 | # `docs/getting-started.md` but not further nested files like
28 | # `docs/build-app/troubleshooting.md`.
29 | # docs/* docs@example.com
30 |
31 | # In this example, @octocat owns any file in an apps directory
32 | # anywhere in your repository.
33 | # apps/ @octocat
34 |
35 | # In this example, @doctocat owns any file in the `/docs`
36 | # directory in the root of your repository.
37 | # /docs/ @doctocat
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2011- Stripe, Inc. (https://stripe.com)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/cardscan-demo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/cardscan-demo/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion 30
6 | buildToolsVersion '30.0.3'
7 |
8 | defaultConfig {
9 | applicationId "com.getbouncer.cardscan.demo"
10 | minSdkVersion 21
11 | targetSdkVersion 30
12 | versionCode 1
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled true
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 |
24 | compileOptions {
25 | sourceCompatibility JavaVersion.VERSION_1_8
26 | targetCompatibility JavaVersion.VERSION_1_8
27 | }
28 |
29 | kotlinOptions {
30 | jvmTarget = JavaVersion.VERSION_1_8.toString()
31 | }
32 |
33 | lintOptions {
34 | enable "Interoperability"
35 | }
36 | }
37 |
38 | dependencies {
39 | implementation fileTree(dir: 'libs', include: ['*.jar'])
40 |
41 | implementation project(":scan-camerax")
42 | implementation project(':scan-payment-full')
43 | implementation project(":cardscan-ui")
44 |
45 | implementation "androidx.appcompat:appcompat:1.3.1"
46 | implementation "androidx.core:core-ktx:1.6.0"
47 | implementation "androidx.constraintlayout:constraintlayout:2.1.0"
48 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
49 | }
50 |
51 | dependencies {
52 | testImplementation "androidx.test:core:1.4.0"
53 | testImplementation "androidx.test:runner:1.4.0"
54 | testImplementation "junit:junit:4.13.2"
55 | testImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
56 |
57 | debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
58 | }
59 |
60 | dependencies {
61 | androidTestImplementation "androidx.test.ext:junit:1.1.3"
62 | androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
63 | androidTestImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
64 | }
65 |
--------------------------------------------------------------------------------
/cardscan-demo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/cardscan-demo/src/androidTest/java/com/getbouncer/cardscan/demo/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.cardscan.demo
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import org.junit.Test
5 | import kotlin.test.assertEquals
6 |
7 | /**
8 | * Instrumented test, which will execute on an Android device.
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleInstrumentedTest {
13 | @Test
14 | fun useAppContext() {
15 | // Context of the app under test.
16 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
17 | assertEquals("com.getbouncer.cardscan.demo", appContext.packageName)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/cardscan-demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
17 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/cardscan-demo/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/cardscan-demo/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/cardscan-demo/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/cardscan-demo/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/cardscan-demo/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/cardscan-demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/cardscan-demo/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/cardscan-demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/cardscan-demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/cardscan-demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CardScan Demo
3 |
4 | Scan Card
5 |
6 | User pressed back
7 | Scan canceled
8 | Enter card manually
9 | Camera permission denied
10 | Single Activity Demo
11 |
12 | Enable Debug
13 | Enable name extraction
14 | Enable expiry extraction
15 | Enable manual entry
16 |
17 | Device architecture is %1$s
18 |
19 |
--------------------------------------------------------------------------------
/cardscan-demo/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/cardscan-demo/src/test/java/com/getbouncer/cardscan/demo/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.cardscan.demo
2 |
3 | import org.junit.Test
4 | import kotlin.test.assertEquals
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/cardscan-ui/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/cardscan-ui/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-parcelize'
4 |
5 | android {
6 | compileSdkVersion 30
7 | buildToolsVersion '30.0.3'
8 |
9 | defaultConfig {
10 | minSdkVersion 21
11 | targetSdkVersion 30
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles 'consumer-rules.pro'
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 |
24 | testOptions {
25 | unitTests.includeAndroidResources = true
26 | }
27 |
28 | lintOptions {
29 | enable "Interoperability"
30 | }
31 |
32 | compileOptions {
33 | sourceCompatibility JavaVersion.VERSION_1_8
34 | targetCompatibility JavaVersion.VERSION_1_8
35 | }
36 |
37 | kotlinOptions {
38 | jvmTarget = JavaVersion.VERSION_1_8.toString()
39 | }
40 | }
41 |
42 | dependencies {
43 | implementation fileTree(dir: 'libs', include: ['*.jar'])
44 | api project(":scan-framework")
45 | api project(':scan-camera')
46 | api project(":scan-payment")
47 | api project(":scan-ui")
48 |
49 | implementation "androidx.appcompat:appcompat:[1.3.0,1.3.1]"
50 | implementation "androidx.core:core-ktx:[1.3.1,1.6.0]"
51 | implementation 'androidx.constraintlayout:constraintlayout:[2.0.4,2.1.0]'
52 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:[1.4.0,1.5.1]"
53 | implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:[1.1.0,1.2.2]"
54 | }
55 |
56 | dependencies {
57 | testImplementation "androidx.test:core:1.4.0"
58 | testImplementation "androidx.test:runner:1.4.0"
59 | testImplementation "junit:junit:4.13.2"
60 | testImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
61 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1"
62 | }
63 |
64 | dependencies {
65 | androidTestImplementation "androidx.test.ext:junit:1.1.3"
66 | androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
67 | androidTestImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
68 | }
69 |
70 | apply from: 'deploy.gradle'
71 |
--------------------------------------------------------------------------------
/cardscan-ui/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/cardscan-ui/consumer-rules.pro
--------------------------------------------------------------------------------
/cardscan-ui/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/cardscan-ui/src/androidTest/java/com/getbouncer/cardscan/ui/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.cardscan.ui
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import org.junit.Test
5 | import kotlin.test.assertEquals
6 |
7 | class ExampleInstrumentedTest {
8 | @Test
9 | fun useAppContext() {
10 | // Context of the app under test.
11 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
12 | assertEquals("com.getbouncer.cardscan.ui.test", appContext.packageName)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/cardscan-ui/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/cardscan-ui/src/main/java/com/getbouncer/cardscan/ui/analyzer/CompletionLoopAnalyzer.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.cardscan.ui.analyzer
2 |
3 | import com.getbouncer.cardscan.ui.SavedFrame
4 | import com.getbouncer.scan.framework.Analyzer
5 | import com.getbouncer.scan.framework.AnalyzerFactory
6 | import com.getbouncer.scan.payment.analyzer.NameAndExpiryAnalyzer
7 |
8 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
9 | class CompletionLoopAnalyzer private constructor(
10 | private val nameAndExpiryAnalyzer: NameAndExpiryAnalyzer?,
11 | ) : Analyzer {
12 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
13 | class Prediction(
14 | val nameAndExpiryResult: NameAndExpiryAnalyzer.Prediction?,
15 | val isNameExtractionAvailable: Boolean,
16 | val isExpiryExtractionAvailable: Boolean,
17 | val enableNameExtraction: Boolean,
18 | val enableExpiryExtraction: Boolean,
19 | )
20 |
21 | override suspend fun analyze(
22 | data: SavedFrame,
23 | state: Unit,
24 | ) = Prediction(
25 | nameAndExpiryResult = nameAndExpiryAnalyzer?.analyze(
26 | NameAndExpiryAnalyzer.Input(data.frame.cameraPreviewImage.image, data.frame.cameraPreviewImage.previewImageBounds, data.frame.cardFinder),
27 | state,
28 | ),
29 | isNameExtractionAvailable = nameAndExpiryAnalyzer?.isNameDetectorAvailable() ?: false,
30 | isExpiryExtractionAvailable = nameAndExpiryAnalyzer?.isExpiryDetectorAvailable() ?: false,
31 | enableNameExtraction = nameAndExpiryAnalyzer?.runNameExtraction ?: false,
32 | enableExpiryExtraction = nameAndExpiryAnalyzer?.runExpiryExtraction ?: false,
33 | )
34 |
35 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
36 | class Factory(
37 | private val nameAndExpiryFactory: AnalyzerFactory,
38 | ) : AnalyzerFactory {
39 | override suspend fun newInstance() = CompletionLoopAnalyzer(
40 | nameAndExpiryAnalyzer = nameAndExpiryFactory.newInstance(),
41 | )
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/cardscan-ui/src/main/java/com/getbouncer/cardscan/ui/analyzer/MainLoopAnalyzer.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.cardscan.ui.analyzer
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.Rect
5 | import com.getbouncer.cardscan.ui.result.MainLoopState
6 | import com.getbouncer.scan.camera.CameraPreviewImage
7 | import com.getbouncer.scan.framework.Analyzer
8 | import com.getbouncer.scan.framework.AnalyzerFactory
9 | import com.getbouncer.scan.payment.ml.CardDetect
10 | import com.getbouncer.scan.payment.ml.SSDOcr
11 |
12 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
13 | class MainLoopAnalyzer(
14 | private val ssdOcr: Analyzer?,
15 | private val cardDetect: Analyzer?,
16 | ) : Analyzer {
17 |
18 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
19 | data class Input(
20 | val cameraPreviewImage: CameraPreviewImage,
21 | val cardFinder: Rect,
22 | )
23 |
24 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
25 | class Prediction(
26 | val ocr: SSDOcr.Prediction?,
27 | val card: CardDetect.Prediction?,
28 | ) {
29 | val isCardVisible = card?.side?.let { it == CardDetect.Prediction.Side.NO_PAN || it == CardDetect.Prediction.Side.PAN }
30 | }
31 |
32 | override suspend fun analyze(data: Input, state: MainLoopState): Prediction {
33 | val cardResult = if (state.runCardDetect) cardDetect?.analyze(CardDetect.cameraPreviewToInput(data.cameraPreviewImage.image, data.cameraPreviewImage.previewImageBounds, data.cardFinder), Unit) else null
34 | val ocrResult = if (state.runOcr) ssdOcr?.analyze(SSDOcr.cameraPreviewToInput(data.cameraPreviewImage.image, data.cameraPreviewImage.previewImageBounds, data.cardFinder), Unit) else null
35 |
36 | return Prediction(
37 | ocr = ocrResult,
38 | card = cardResult,
39 | )
40 | }
41 |
42 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
43 | class Factory(
44 | private val ssdOcrFactory: AnalyzerFactory>,
45 | private val cardDetectFactory: AnalyzerFactory>,
46 | ) : AnalyzerFactory {
47 | override suspend fun newInstance(): MainLoopAnalyzer = MainLoopAnalyzer(
48 | ssdOcr = ssdOcrFactory.newInstance(),
49 | cardDetect = cardDetectFactory.newInstance(),
50 | )
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/cardscan-ui/src/main/java/com/getbouncer/cardscan/ui/exception/StripeNetworkException.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.cardscan.ui.exception
2 |
3 | import java.lang.Exception
4 |
5 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
6 | class StripeNetworkException(message: String) : Exception(message)
7 |
--------------------------------------------------------------------------------
/cardscan-ui/src/main/java/com/getbouncer/cardscan/ui/exception/UnknownScanException.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.cardscan.ui.exception
2 |
3 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
4 | class UnknownScanException(message: String? = null) : Exception(message)
5 |
--------------------------------------------------------------------------------
/cardscan-ui/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #AA000000
4 | @android:color/white
5 |
6 | @android:color/white
7 | @android:color/black
8 |
9 |
--------------------------------------------------------------------------------
/cardscan-ui/src/main/res/values/dimensions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 24sp
4 |
5 | 24dp
6 |
7 | 16dp
8 | 18sp
9 |
10 |
--------------------------------------------------------------------------------
/cardscan-ui/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Processing, please wait
4 |
5 | Enter card manually
6 |
7 |
--------------------------------------------------------------------------------
/docs/images/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/docs/images/demo.gif
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 | version=2.2.0003
23 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Feb 26 15:43:08 PST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/scan-camera/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/scan-camera/README.md:
--------------------------------------------------------------------------------
1 | # Deprecation Notice
2 | Hello from the Stripe (formerly Bouncer) team!
3 |
4 | We're excited to provide an update on the state and future of the [Card Scan OCR](https://github.com/stripe/stripe-android/tree/master/stripecardscan) product! As we continue to build into Stripe's ecosystem, we'll be supporting the mission to continuously improve the end customer experience in many of Stripe's core checkout products.
5 |
6 | This SDK has been [migrated to Stripe](https://github.com/stripe/stripe-android/tree/master/stripecardscan) and is now free for use under the MIT license!
7 |
8 | If you are not currently a Stripe user, and interested in learning more about improving checkout experience through Stripe, please let us know and we can connect you with the team.
9 |
10 | If you are not currently a Stripe user, and want to continue using the existing SDK, you can do so free of charge. Starting January 1, 2022, we will no longer be charging for use of the existing Bouncer Card Scan OCR SDK. For product support on [Android](https://github.com/stripe/stripe-android/issues) and [iOS](https://github.com/stripe/stripe-ios/issues). For billing support, please email [bouncer-support@stripe.com](mailto:bouncer-support@stripe.com).
11 |
12 | For the new product, please visit the [stripe github repository](https://github.com/stripe/stripe-android/tree/master/stripecardscan).
13 |
14 | # Overview
15 | This repository contains the legacy, deprecated open source camera framework to allow scanning cards. [CardScan](https://cardscan.io/) is a relatively small library that provides fast and accurate payment card scanning.
16 |
17 | Note this library does not contain any user interfaces. Another library, [CardScan UI](https://github.com/getbouncer/cardscan-ui-android) builds upon this one any adds simple user interfaces.
18 |
19 | 
20 |
21 | ## Contents
22 | * [Requirements](#requirements)
23 | * [Demo](#demo)
24 | * [Integration](#integration)
25 | * [Using](#using)
26 | * [Developing](#developing)
27 | * [Authors](#authors)
28 | * [License](#license)
29 |
30 | ## Requirements
31 | * Android API level 21 or higher
32 | * Kotlin coroutine compatibility
33 |
34 | Note: Your app does not have to be written in kotlin to integrate scan-camera, but must be able to depend on kotlin functionality.
35 |
36 | ## Demo
37 | An app demonstrating the basic capabilities of CardScan is available in [github](https://github.com/getbouncer/cardscan-demo-android).
38 |
39 | ## Integration
40 | See the [integration documentation](https://docs.getbouncer.com/card-scan/android-integration-guide/android-development-guide) in the Bouncer Docs.
41 |
42 | ## Using
43 | This library is designed to be used with [CardScan UI](https://github.com/getbouncer/cardscan-ui-android), which will provide user interfaces for scanning payment cards. However, it can be used independently.
44 |
45 | For an overview of the architecture and design of the scan framework, see the [architecture documentation](https://docs.getbouncer.com/card-scan/android-integration-guide/android-architecture-overview).
46 |
47 | ### Getting images from the camera
48 | See the [example code](https://docs.getbouncer.com/card-scan/android-integration-guide/android-architecture-overview#example) in the Android architecture documentation.
49 |
50 | ## Developing
51 | See the [development docs](https://docs.getbouncer.com/card-scan/android-integration-guide/android-development-guide) for details on developing this library.
52 |
53 | ## Authors
54 | Adam Wushensky, Sam King, and Zain ul Abi Din
55 |
56 | ## License
57 | This library is available under the MIT license. See the [LICENSE](../LICENSE) file for the full license text.
58 |
--------------------------------------------------------------------------------
/scan-camera/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion 30
6 | buildToolsVersion '30.0.3'
7 |
8 | defaultConfig {
9 | minSdkVersion 21
10 | targetSdkVersion 30
11 |
12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
13 | consumerProguardFiles 'consumer-rules.pro'
14 | }
15 |
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 |
23 | testOptions {
24 | unitTests.includeAndroidResources = true
25 | }
26 |
27 | lintOptions {
28 | enable "Interoperability"
29 | }
30 |
31 | compileOptions {
32 | sourceCompatibility JavaVersion.VERSION_1_8
33 | targetCompatibility JavaVersion.VERSION_1_8
34 | }
35 | }
36 |
37 | dependencies {
38 | implementation fileTree(dir: 'libs', include: ['*.jar'])
39 | implementation project(":scan-framework")
40 |
41 | implementation "androidx.appcompat:appcompat:[1.3.0,1.3.1]"
42 | implementation "androidx.core:core-ktx:[1.3.1,1.6.0]"
43 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:[1.4.0,1.5.1]"
44 | }
45 |
46 | dependencies {
47 | testImplementation "androidx.test:core:1.4.0"
48 | testImplementation "androidx.test:runner:1.4.0"
49 | testImplementation "junit:junit:4.13.2"
50 | testImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
51 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1"
52 | }
53 |
54 | dependencies {
55 | androidTestImplementation "androidx.test.ext:junit:1.1.3"
56 | androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
57 | androidTestImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
58 | }
59 |
60 | apply from: 'deploy.gradle'
61 |
--------------------------------------------------------------------------------
/scan-camera/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-camera/consumer-rules.pro
--------------------------------------------------------------------------------
/scan-camera/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/scan-camera/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/scan-camera/src/main/java/com/getbouncer/scan/camera/CameraSelector.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.camera
2 |
3 | import android.app.Activity
4 | import android.graphics.Bitmap
5 | import android.os.Build
6 | import android.util.Log
7 | import android.util.Size
8 | import android.view.ViewGroup
9 | import com.getbouncer.scan.framework.Config
10 |
11 | /**
12 | * Get the appropriate camera adapter. If the customer has provided an additional camera adapter, use that in place of
13 | * camera 1.
14 | */
15 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
16 | fun getCameraAdapter(
17 | activity: Activity,
18 | previewView: ViewGroup,
19 | minimumResolution: Size,
20 | cameraErrorListener: CameraErrorListener,
21 | ): CameraAdapter> =
22 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
23 | try {
24 | getAlternateCamera(activity, previewView, minimumResolution, cameraErrorListener)
25 | } catch (t: Throwable) {
26 | Log.d(Config.logTag, "No alternative camera implementations supplied, falling back to default", t)
27 | Camera1Adapter(activity, previewView, minimumResolution, cameraErrorListener)
28 | }
29 | } else {
30 | Log.d(Config.logTag, "YUV_420_888 is not supported, falling back to default implementation")
31 | Camera1Adapter(activity, previewView, minimumResolution, cameraErrorListener)
32 | }.apply {
33 | Log.d(Config.logTag, "Using camera implementation ${this.implementationName}")
34 | }
35 |
36 | @Suppress("UNCHECKED_CAST")
37 | @Throws(ClassNotFoundException::class, NoSuchMethodException::class, IllegalAccessException::class)
38 | private fun getAlternateCamera(
39 | activity: Activity,
40 | previewView: ViewGroup,
41 | minimumResolution: Size,
42 | cameraErrorListener: CameraErrorListener,
43 | ): CameraAdapter> =
44 | Class.forName("com.getbouncer.scan.camera.extension.CameraAdapterImpl")
45 | .getConstructor(
46 | Activity::class.java,
47 | ViewGroup::class.java,
48 | Size::class.java,
49 | CameraErrorListener::class.java,
50 | )
51 | .newInstance(
52 | activity,
53 | previewView,
54 | minimumResolution,
55 | cameraErrorListener,
56 | ) as CameraAdapter>
57 |
--------------------------------------------------------------------------------
/scan-camera2/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/scan-camera2/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion 30
6 | buildToolsVersion '30.0.3'
7 |
8 | defaultConfig {
9 | minSdkVersion 21
10 | targetSdkVersion 30
11 |
12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
13 | consumerProguardFiles 'consumer-rules.pro'
14 | }
15 |
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 |
23 | testOptions {
24 | unitTests.includeAndroidResources = true
25 | }
26 |
27 | lintOptions {
28 | enable "Interoperability"
29 | }
30 |
31 | compileOptions {
32 | sourceCompatibility JavaVersion.VERSION_1_8
33 | targetCompatibility JavaVersion.VERSION_1_8
34 | }
35 | }
36 |
37 | dependencies {
38 | implementation fileTree(dir: 'libs', include: ['*.jar'])
39 | implementation project(":scan-framework")
40 | implementation project(':scan-camera')
41 |
42 | implementation "androidx.appcompat:appcompat:[1.3.0,1.3.1]"
43 | implementation "androidx.core:core-ktx:[1.3.1,1.6.0]"
44 | }
45 |
46 | dependencies {
47 | testImplementation "androidx.test:core:1.4.0"
48 | testImplementation "androidx.test:runner:1.4.0"
49 | testImplementation "junit:junit:4.13.2"
50 | testImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
51 | }
52 |
53 | dependencies {
54 | androidTestImplementation "androidx.test.ext:junit:1.1.3"
55 | androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
56 | androidTestImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
57 | }
58 |
59 | apply from: 'deploy.gradle'
60 |
--------------------------------------------------------------------------------
/scan-camera2/src/androidTest/java/com/getbouncer/scan/camera/extension/UtilInstrumentationTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.camera.extension
2 |
3 | import android.util.Size
4 | import android.view.Surface
5 | import androidx.test.filters.SmallTest
6 | import org.junit.Test
7 | import kotlin.test.assertEquals
8 |
9 | class UtilInstrumentationTest {
10 |
11 | @Test
12 | @SmallTest
13 | fun resolutionToSize_perpendicular() {
14 | assertEquals(Size(60, 120), Size(120, 60).resolutionToSize(Surface.ROTATION_0, 90))
15 | assertEquals(Size(60, 120), Size(120, 60).resolutionToSize(Surface.ROTATION_0, 270))
16 | assertEquals(Size(60, 120), Size(120, 60).resolutionToSize(Surface.ROTATION_90, 0))
17 | assertEquals(Size(60, 120), Size(120, 60).resolutionToSize(Surface.ROTATION_90, 180))
18 | assertEquals(Size(60, 120), Size(120, 60).resolutionToSize(Surface.ROTATION_180, 90))
19 | assertEquals(Size(60, 120), Size(120, 60).resolutionToSize(Surface.ROTATION_180, 270))
20 | assertEquals(Size(60, 120), Size(120, 60).resolutionToSize(Surface.ROTATION_270, 0))
21 | assertEquals(Size(60, 120), Size(120, 60).resolutionToSize(Surface.ROTATION_270, 180))
22 | }
23 |
24 | @Test
25 | @SmallTest
26 | fun resolutionToSize_parallel() {
27 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_0, 0))
28 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_0, 180))
29 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_90, 90))
30 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_90, 270))
31 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_180, 0))
32 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_180, 180))
33 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_270, 90))
34 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_270, 270))
35 | }
36 |
37 | @Test
38 | @SmallTest
39 | fun resolutionToSize_oblique() {
40 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_0, 45))
41 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_0, 135))
42 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_90, 45))
43 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_90, 135))
44 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_180, 45))
45 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_180, 135))
46 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_270, 45))
47 | assertEquals(Size(120, 60), Size(120, 60).resolutionToSize(Surface.ROTATION_270, 135))
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/scan-camera2/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/scan-camera2/src/main/java/com/getbouncer/scan/camera/extension/CameraDetails.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.camera.extension
2 |
3 | import android.hardware.camera2.params.StreamConfigurationMap
4 |
5 | /**
6 | * Details about a camera.
7 | */
8 | internal data class CameraDetails(
9 | val cameraId: String,
10 | val flashAvailable: Boolean,
11 | val config: StreamConfigurationMap,
12 | val sensorRotation: Int,
13 | val supportedAutoFocusModes: List,
14 | val lensFacing: Int?,
15 | )
16 |
--------------------------------------------------------------------------------
/scan-camera2/src/test/java/com/getbouncer/scan/camera/extension/UtilTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.camera.extension
2 |
3 | import android.view.Surface
4 | import androidx.test.filters.SmallTest
5 | import org.junit.Test
6 | import kotlin.test.assertEquals
7 |
8 | class UtilTest {
9 |
10 | @Test
11 | @SmallTest
12 | fun calculateImageRotationDegrees_vertical() {
13 | assertEquals(0, calculateImageRotationDegrees(Surface.ROTATION_0, 0))
14 | assertEquals(45, calculateImageRotationDegrees(Surface.ROTATION_0, 45))
15 | assertEquals(315, calculateImageRotationDegrees(Surface.ROTATION_0, -45))
16 | assertEquals(180, calculateImageRotationDegrees(Surface.ROTATION_0, 180))
17 | assertEquals(180, calculateImageRotationDegrees(Surface.ROTATION_0, -180))
18 | }
19 |
20 | @Test
21 | @SmallTest
22 | fun calculateImageRotationDegrees_right() {
23 | assertEquals(270, calculateImageRotationDegrees(Surface.ROTATION_90, 0))
24 | assertEquals(315, calculateImageRotationDegrees(Surface.ROTATION_90, 45))
25 | assertEquals(225, calculateImageRotationDegrees(Surface.ROTATION_90, -45))
26 | assertEquals(90, calculateImageRotationDegrees(Surface.ROTATION_90, 180))
27 | assertEquals(90, calculateImageRotationDegrees(Surface.ROTATION_90, -180))
28 | }
29 |
30 | @Test
31 | @SmallTest
32 | fun calculateImageRotationDegrees_left() {
33 | assertEquals(90, calculateImageRotationDegrees(Surface.ROTATION_270, 0))
34 | assertEquals(135, calculateImageRotationDegrees(Surface.ROTATION_270, 45))
35 | assertEquals(45, calculateImageRotationDegrees(Surface.ROTATION_270, -45))
36 | assertEquals(270, calculateImageRotationDegrees(Surface.ROTATION_270, 180))
37 | assertEquals(270, calculateImageRotationDegrees(Surface.ROTATION_270, -180))
38 | }
39 |
40 | @Test
41 | @SmallTest
42 | fun calculateImageRotationDegrees_inverted() {
43 | assertEquals(180, calculateImageRotationDegrees(Surface.ROTATION_180, 0))
44 | assertEquals(225, calculateImageRotationDegrees(Surface.ROTATION_180, 45))
45 | assertEquals(135, calculateImageRotationDegrees(Surface.ROTATION_180, -45))
46 | assertEquals(0, calculateImageRotationDegrees(Surface.ROTATION_180, 180))
47 | assertEquals(0, calculateImageRotationDegrees(Surface.ROTATION_180, -180))
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/scan-camerax/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/scan-camerax/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion 30
6 | buildToolsVersion '30.0.3'
7 |
8 | defaultConfig {
9 | minSdkVersion 21
10 | targetSdkVersion 30
11 |
12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
13 | consumerProguardFiles 'consumer-rules.pro'
14 | }
15 |
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 |
23 | testOptions {
24 | unitTests.includeAndroidResources = true
25 | }
26 |
27 | lintOptions {
28 | enable "Interoperability"
29 | }
30 |
31 | compileOptions {
32 | sourceCompatibility JavaVersion.VERSION_1_8
33 | targetCompatibility JavaVersion.VERSION_1_8
34 | }
35 |
36 | kotlinOptions {
37 | jvmTarget = JavaVersion.VERSION_1_8.toString()
38 | }
39 | }
40 |
41 | dependencies {
42 | implementation fileTree(dir: 'libs', include: ['*.jar'])
43 | implementation project(":scan-framework")
44 | implementation project(':scan-camera')
45 |
46 | implementation "androidx.appcompat:appcompat:[1.3.0,1.3.1]"
47 | implementation "androidx.camera:camera-camera2:[1.0.0,1.0.1]"
48 | implementation "androidx.camera:camera-core:[1.0.0,1.0.1]"
49 | implementation "androidx.camera:camera-lifecycle:[1.0.0,1.0.1]"
50 | implementation "androidx.camera:camera-view:[1.0.0-alpha26,1.0.0-alpha28)"
51 | implementation "androidx.core:core-ktx:[1.3.1,1.6.0]"
52 | }
53 |
54 | dependencies {
55 | testImplementation "androidx.test:core:1.4.0"
56 | testImplementation "androidx.test:runner:1.4.0"
57 | testImplementation "junit:junit:4.13.2"
58 | testImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
59 | }
60 |
61 | dependencies {
62 | androidTestImplementation "androidx.test.ext:junit:1.1.3"
63 | androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
64 | androidTestImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
65 | }
66 |
67 | apply from: 'deploy.gradle'
68 |
--------------------------------------------------------------------------------
/scan-camerax/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/scan-camerax/src/main/java/com/getbouncer/scan/camera/extension/Image.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.camera.extension
2 |
3 | import android.graphics.ImageFormat
4 | import android.renderscript.RenderScript
5 | import androidx.annotation.CheckResult
6 | import androidx.camera.core.ImageProxy
7 | import com.getbouncer.scan.framework.exception.ImageTypeNotSupportedException
8 | import com.getbouncer.scan.framework.image.NV21Image
9 | import com.getbouncer.scan.framework.image.yuvPlanesToNV21Fast
10 | import com.getbouncer.scan.framework.util.mapArray
11 | import com.getbouncer.scan.framework.util.mapToIntArray
12 | import com.getbouncer.scan.framework.util.toByteArray
13 |
14 | /**
15 | * Convert an ImageProxy to a bitmap.
16 | */
17 | @CheckResult
18 | internal fun ImageProxy.toBitmap(renderScript: RenderScript) = when (format) {
19 | ImageFormat.NV21 -> NV21Image(width, height, planes[0].buffer.toByteArray()).toBitmap(renderScript)
20 | ImageFormat.YUV_420_888 -> NV21Image(
21 | width,
22 | height,
23 | yuvPlanesToNV21Fast(
24 | width,
25 | height,
26 | planes.mapArray { it.buffer },
27 | planes.mapToIntArray { it.rowStride },
28 | planes.mapToIntArray { it.pixelStride },
29 | ),
30 | ).toBitmap(renderScript)
31 | else -> throw ImageTypeNotSupportedException(format)
32 | }
33 |
--------------------------------------------------------------------------------
/scan-camerax/src/main/java/com/getbouncer/scan/camera/extension/Util.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.camera.extension
2 |
3 | import android.util.Size
4 | import kotlin.math.max
5 | import kotlin.math.min
6 |
7 | /**
8 | * Convert a resolution to a size on the screen based only on the display size.
9 | */
10 | internal fun Size.resolutionToSize(displaySize: Size) = when {
11 | displaySize.width >= displaySize.height -> Size(
12 | /* width */
13 | max(width, height),
14 | /* height */
15 | min(width, height),
16 | )
17 | else -> Size(
18 | /* width */
19 | min(width, height),
20 | /* height */
21 | max(width, height),
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/scan-framework/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/scan-framework/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlinx-serialization'
4 |
5 | android {
6 | compileSdkVersion 30
7 | buildToolsVersion '30.0.3'
8 |
9 | defaultConfig {
10 | minSdkVersion 21
11 | targetSdkVersion 30
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles "consumer-rules.pro"
15 |
16 | buildConfigField("String", "SDK_VERSION_STRING", "\"${version}\"")
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
23 | }
24 | }
25 |
26 | testOptions {
27 | unitTests.includeAndroidResources = true
28 | }
29 |
30 | lintOptions {
31 | enable "Interoperability"
32 | }
33 |
34 | packagingOptions {
35 | pickFirst 'META-INF/AL2.0'
36 | pickFirst 'META-INF/LGPL2.1'
37 | }
38 |
39 | compileOptions {
40 | sourceCompatibility JavaVersion.VERSION_1_8
41 | targetCompatibility JavaVersion.VERSION_1_8
42 | }
43 |
44 | kotlinOptions {
45 | jvmTarget = JavaVersion.VERSION_1_8.toString()
46 | }
47 |
48 | aaptOptions {
49 | noCompress "tflite"
50 | }
51 | }
52 |
53 | dependencies {
54 | implementation fileTree(dir: "libs", include: ["*.jar"])
55 |
56 | implementation "androidx.core:core-ktx:[1.3.1,1.6.0]"
57 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:[1.4.0,1.5.1]"
58 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:[1.4.0,1.5.1]"
59 | implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:[1.1.0,1.2.2]"
60 |
61 | // Allow the user to specify their own version of Tensorflow Lite to include
62 | runtimeOnly project(":tensorflow-lite")
63 | compileOnly "org.tensorflow:tensorflow-lite:2.4.0"
64 | }
65 |
66 | dependencies {
67 | testImplementation "androidx.test:core:1.4.0"
68 | testImplementation "androidx.test:runner:1.4.0"
69 | testImplementation "junit:junit:4.13.2"
70 | testImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
71 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1"
72 | }
73 |
74 | dependencies {
75 | androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
76 | androidTestImplementation "androidx.test.ext:junit:1.1.3"
77 | androidTestImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
78 | androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1"
79 | }
80 |
81 | apply from: "deploy.gradle"
82 |
--------------------------------------------------------------------------------
/scan-framework/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-framework/consumer-rules.pro
--------------------------------------------------------------------------------
/scan-framework/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -keep class org.tensorflow.lite.Interpreter { *; }
24 |
--------------------------------------------------------------------------------
/scan-framework/src/androidTest/assets/sample_resource.tflite:
--------------------------------------------------------------------------------
1 | ABC123
2 | DEF456
3 |
--------------------------------------------------------------------------------
/scan-framework/src/androidTest/java/com/getbouncer/scan/framework/LoaderTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework
2 |
3 | import androidx.test.filters.SmallTest
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import kotlinx.coroutines.runBlocking
6 | import org.junit.Test
7 | import java.io.File
8 | import kotlin.test.assertEquals
9 | import kotlin.test.assertNotNull
10 | import kotlin.test.assertTrue
11 |
12 | class LoaderTest {
13 | private val testContext = InstrumentationRegistry.getInstrumentation().context
14 |
15 | @Test
16 | @SmallTest
17 | fun loadData_fromResource_success() = runBlocking {
18 | val fetchedData = FetchedResource(
19 | modelClass = "sample_class",
20 | modelFrameworkVersion = 2049,
21 | modelVersion = "sample_resource",
22 | modelHash = "0dcf3e387c68dfea8dd72a183f1f765478ebaa4d8544cfc09a16e87a795d8ccf",
23 | modelHashAlgorithm = "SHA-256",
24 | assetFileName = "sample_resource.tflite",
25 | )
26 |
27 | val byteBuffer = Loader(testContext).loadData(fetchedData)
28 | assertNotNull(byteBuffer)
29 | assertEquals(14, byteBuffer.limit(), "File is not expected size")
30 | byteBuffer.rewind()
31 |
32 | // ensure not all bytes are zero
33 | var encounteredNonZeroByte = false
34 | while (!encounteredNonZeroByte) {
35 | encounteredNonZeroByte = byteBuffer.get().toInt() != 0
36 | }
37 | assertTrue(encounteredNonZeroByte, "All bytes were zero")
38 |
39 | // ensure bytes are correct
40 | byteBuffer.rewind()
41 | assertEquals('A', byteBuffer.get().toInt().toChar())
42 | assertEquals('B', byteBuffer.get().toInt().toChar())
43 | assertEquals('C', byteBuffer.get().toInt().toChar())
44 | assertEquals('1', byteBuffer.get().toInt().toChar())
45 | }
46 |
47 | @Test
48 | @SmallTest
49 | fun loadData_fromFile_success() = runBlocking {
50 | val sampleFile = File(testContext.cacheDir, "sample_file")
51 | if (sampleFile.exists()) {
52 | sampleFile.delete()
53 | }
54 |
55 | sampleFile.createNewFile()
56 | sampleFile.writeText("ABC123")
57 |
58 | val fetchedData = FetchedFile(
59 | modelClass = "sample_class",
60 | modelFrameworkVersion = 2049,
61 | modelVersion = "sample_file",
62 | modelHash = "133351546614bfadfa68bb66c22a06265972b02791e4ac545ad900f20fe1a796",
63 | modelHashAlgorithm = "SHA-256",
64 | file = sampleFile,
65 | )
66 |
67 | val byteBuffer = Loader(testContext).loadData(fetchedData)
68 | assertNotNull(byteBuffer)
69 | assertEquals(6, byteBuffer.limit(), "File is not expected size")
70 | byteBuffer.rewind()
71 |
72 | // ensure not all bytes are zero
73 | var encounteredNonZeroByte = false
74 | while (!encounteredNonZeroByte) {
75 | encounteredNonZeroByte = byteBuffer.get().toInt() != 0
76 | }
77 | assertTrue(encounteredNonZeroByte, "All bytes were zero")
78 |
79 | // ensure bytes are correct
80 | byteBuffer.rewind()
81 | assertEquals('A', byteBuffer.get().toInt().toChar())
82 | assertEquals('B', byteBuffer.get().toInt().toChar())
83 | assertEquals('C', byteBuffer.get().toInt().toChar())
84 | assertEquals('1', byteBuffer.get().toInt().toChar())
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/scan-framework/src/androidTest/java/com/getbouncer/scan/framework/image/BitmapExtensionsTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.image
2 |
3 | import android.graphics.Rect
4 | import android.util.Size
5 | import androidx.core.graphics.drawable.toBitmap
6 | import androidx.test.filters.SmallTest
7 | import androidx.test.platform.app.InstrumentationRegistry
8 | import com.getbouncer.scan.framework.test.R
9 | import org.junit.Test
10 | import kotlin.test.assertEquals
11 | import kotlin.test.assertNotNull
12 | import kotlin.test.assertTrue
13 |
14 | class BitmapExtensionsTest {
15 |
16 | private val testResources = InstrumentationRegistry.getInstrumentation().context.resources
17 |
18 | @Test
19 | @SmallTest
20 | fun bitmap_scale_isCorrect() {
21 | // read in a sample bitmap file
22 | val bitmap = testResources.getDrawable(R.drawable.ocr_card_numbers_clear, null).toBitmap()
23 | assertNotNull(bitmap)
24 | assertEquals(600, bitmap.width, "Bitmap width is not expected")
25 | assertEquals(375, bitmap.height, "Bitmap height is not expected")
26 |
27 | // scale the bitmap
28 | val scaledBitmap = bitmap.scale(0.2F)
29 |
30 | // check the expected sizes of the images
31 | assertEquals(
32 | Size(bitmap.width / 5, bitmap.height / 5),
33 | Size(scaledBitmap.width, scaledBitmap.height),
34 | "Scaled image is the wrong size"
35 | )
36 |
37 | // check each pixel of the images
38 | var encounteredNonZeroPixel = false
39 | for (x in 0 until scaledBitmap.width) {
40 | for (y in 0 until scaledBitmap.height) {
41 | encounteredNonZeroPixel = encounteredNonZeroPixel || scaledBitmap.getPixel(x, y) != 0
42 | }
43 | }
44 |
45 | assertTrue(encounteredNonZeroPixel, "Pixels were all zero")
46 | }
47 |
48 | @Test
49 | @SmallTest
50 | fun bitmap_crop_isCorrect() {
51 | val bitmap = testResources.getDrawable(R.drawable.ocr_card_numbers_clear, null).toBitmap()
52 | assertNotNull(bitmap)
53 | assertEquals(600, bitmap.width, "Bitmap width is not expected")
54 | assertEquals(375, bitmap.height, "Bitmap height is not expected")
55 |
56 | // crop the bitmap
57 | val croppedBitmap = bitmap.crop(
58 | Rect(
59 | bitmap.width / 4,
60 | bitmap.height / 4,
61 | bitmap.width * 3 / 4,
62 | bitmap.height * 3 / 4
63 | )
64 | )
65 |
66 | // check the expected sizes of the images
67 | assertEquals(
68 | Size(bitmap.width * 3 / 4 - bitmap.width / 4, bitmap.height * 3 / 4 - bitmap.height / 4),
69 | Size(croppedBitmap.width, croppedBitmap.height),
70 | "Cropped image is the wrong size"
71 | )
72 |
73 | // check each pixel of the images
74 | var encounteredNonZeroPixel = false
75 | for (x in 0 until croppedBitmap.width) {
76 | for (y in 0 until croppedBitmap.height) {
77 | val croppedPixel = croppedBitmap.getPixel(x, y)
78 | val originalPixel = bitmap.getPixel(x + bitmap.width / 4, y + bitmap.height / 4)
79 | assertEquals(originalPixel, croppedPixel, "Difference at pixel $x, $y")
80 | encounteredNonZeroPixel = encounteredNonZeroPixel || croppedPixel != 0
81 | }
82 | }
83 |
84 | assertTrue(encounteredNonZeroPixel, "Pixels were all zero")
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/scan-framework/src/androidTest/java/com/getbouncer/scan/framework/util/AppDetailsTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.util
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import org.junit.Test
5 | import kotlin.test.assertEquals
6 | import kotlin.test.assertTrue
7 |
8 | class AppDetailsTest {
9 | private val testContext = InstrumentationRegistry.getInstrumentation().context
10 |
11 | @Test
12 | fun appDetails_full() {
13 | val appDetails = AppDetails.fromContext(testContext)
14 |
15 | assertEquals("com.getbouncer.scan.framework.test", appDetails.appPackageName)
16 | assertEquals("", appDetails.applicationId)
17 | assertEquals("com.getbouncer.scan.framework", appDetails.libraryPackageName)
18 | assertTrue(appDetails.sdkVersion.startsWith("2."), "${appDetails.sdkVersion} does not start with \"2.\"")
19 | assertEquals(-1, appDetails.sdkVersionCode)
20 | assertTrue(appDetails.sdkFlavor.isNotEmpty())
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/scan-framework/src/androidTest/res/drawable/ocr_card_numbers_clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-framework/src/androidTest/res/drawable/ocr_card_numbers_clear.png
--------------------------------------------------------------------------------
/scan-framework/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/Analyzer.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework
2 |
3 | import java.io.Closeable
4 |
5 | /**
6 | * The default number of analyzers to run in parallel.
7 | */
8 | internal const val DEFAULT_ANALYZER_PARALLEL_COUNT = 2
9 |
10 | /**
11 | * An analyzer takes some data as an input, and returns an analyzed output. Analyzers should not
12 | * contain any state. They must define whether they can run on a multithreaded executor, and provide
13 | * a means of analyzing input data to return some form of result.
14 | */
15 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
16 | interface Analyzer {
17 | suspend fun analyze(data: Input, state: State): Output
18 | }
19 |
20 | /**
21 | * A factory to create analyzers.
22 | */
23 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
24 | interface AnalyzerFactory> {
25 | suspend fun newInstance(): AnalyzerType?
26 | }
27 |
28 | /**
29 | * A pool of analyzers.
30 | */
31 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
32 | data class AnalyzerPool(
33 | val desiredAnalyzerCount: Int,
34 | val analyzers: List>
35 | ) {
36 | companion object {
37 | @Deprecated(
38 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
39 | replaceWith = ReplaceWith("StripeCardScan")
40 | )
41 | suspend fun of(
42 | analyzerFactory: AnalyzerFactory>,
43 | desiredAnalyzerCount: Int = DEFAULT_ANALYZER_PARALLEL_COUNT,
44 | ) = AnalyzerPool(
45 | desiredAnalyzerCount = desiredAnalyzerCount,
46 | analyzers = (0 until desiredAnalyzerCount).mapNotNull { analyzerFactory.newInstance() }
47 | )
48 | }
49 |
50 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
51 | fun closeAllAnalyzers() {
52 | // This should be using analyzers.forEach, but doing so seems to require API 24. It's unclear why this won't use
53 | // the kotlin.collections version of `forEach`, but it's not during compile.
54 | for (it in analyzers) { if (it is Closeable) it.close() }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/MachineState.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework
2 |
3 | import android.util.Log
4 | import com.getbouncer.scan.framework.time.Clock
5 | import com.getbouncer.scan.framework.time.ClockMark
6 |
7 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
8 | abstract class MachineState {
9 |
10 | /**
11 | * Keep track of when this state was reached
12 | */
13 | protected open val reachedStateAt: ClockMark = Clock.markNow()
14 |
15 | override fun toString(): String = "${this::class.java.simpleName}(reachedStateAt=$reachedStateAt)"
16 |
17 | init {
18 | if (Config.isDebug) Log.d(Config.logTag, "${this::class.java.simpleName} machine state reached")
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/Scan.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework
2 |
3 | import android.os.Build
4 |
5 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
6 | object Scan {
7 |
8 | /**
9 | * Determine if the device is running an ARM architecture.
10 | */
11 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
12 | fun isDeviceArchitectureArm(): Boolean {
13 | val arch = System.getProperty("os.arch") ?: ""
14 | return "86" !in arch
15 | }
16 |
17 | /**
18 | * Determine the architecture of the device.
19 | */
20 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
21 | fun getDeviceArchitecture(): String? {
22 | // From https://stackoverflow.com/questions/11989629/api-call-to-get-processor-architecture
23 |
24 | // Note that we cannot use System.getProperty("os.arch") since that may give e.g. "aarch64"
25 | // while a 64-bit runtime may not be installed (like on the Samsung Galaxy S5 Neo).
26 | // Instead we search through the supported abi:s on the device, see:
27 | // http://developer.android.com/ndk/guides/abis.html
28 |
29 | // Note that we search for abi:s in preferred order (the ordering of the
30 | // Build.SUPPORTED_ABIS list) to avoid e.g. installing arm on an x86 system where arm
31 | // emulation is available.
32 | for (androidArch in Build.SUPPORTED_ABIS) {
33 | when (androidArch) {
34 | "arm64-v8a" -> return "aarch64"
35 | "armeabi-v7a" -> return "arm"
36 | "x86_64" -> return "x86_64"
37 | "x86" -> return "i686"
38 | }
39 | }
40 | return null
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/TrackedImage.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework
2 |
3 | /**
4 | * An image with a stat tracker.
5 | */
6 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
7 | data class TrackedImage(
8 | val image: ImageType,
9 | val tracker: StatTracker,
10 | )
11 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/api/NetworkResult.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.api
2 |
3 | @Deprecated(
4 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
5 | replaceWith = ReplaceWith("StripeCardScan"),
6 | )
7 | sealed class NetworkResult(open val responseCode: Int) {
8 | @Deprecated(
9 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
10 | replaceWith = ReplaceWith("StripeCardScan"),
11 | )
12 | data class Success(override val responseCode: Int, val body: Success) : NetworkResult(responseCode)
13 | @Deprecated(
14 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
15 | replaceWith = ReplaceWith("StripeCardScan"),
16 | )
17 | data class Error(override val responseCode: Int, val error: Error) : NetworkResult(responseCode)
18 | @Deprecated(
19 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
20 | replaceWith = ReplaceWith("StripeCardScan"),
21 | )
22 | data class Exception(override val responseCode: Int, val exception: Throwable) : NetworkResult(responseCode)
23 | }
24 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/api/dto/AppInfo.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.api.dto
2 |
3 | import androidx.annotation.RestrictTo
4 | import com.getbouncer.scan.framework.util.AppDetails
5 | import kotlinx.serialization.SerialName
6 | import kotlinx.serialization.Serializable
7 |
8 | @Serializable
9 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
10 | @Deprecated(
11 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
12 | replaceWith = ReplaceWith("StripeCardScan"),
13 | )
14 | data class AppInfo(
15 | @SerialName("app_package_name") val appPackageName: String?,
16 | @SerialName("application_id") val applicationId: String,
17 | @SerialName("library_package_name") val libraryPackageName: String,
18 | @SerialName("sdk_version") val sdkVersion: String,
19 | @SerialName("sdk_version_code") val sdkVersionCode: Int,
20 | @SerialName("sdk_flavor") val sdkFlavor: String,
21 | @SerialName("is_debug_build") val isDebugBuild: Boolean
22 | ) {
23 | companion object {
24 | @Deprecated(
25 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
26 | replaceWith = ReplaceWith("StripeCardScan"),
27 | )
28 | fun fromAppDetails(appDetails: AppDetails): AppInfo = AppInfo(
29 | appPackageName = appDetails.appPackageName,
30 | applicationId = appDetails.applicationId,
31 | libraryPackageName = appDetails.libraryPackageName,
32 | sdkVersion = appDetails.sdkVersion,
33 | sdkVersionCode = appDetails.sdkVersionCode,
34 | sdkFlavor = appDetails.sdkFlavor,
35 | isDebugBuild = appDetails.isDebugBuild
36 | )
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/api/dto/BouncerErrorResponse.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.api.dto
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | @Deprecated(
8 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
9 | replaceWith = ReplaceWith("StripeCardScan"),
10 | )
11 | data class BouncerErrorResponse(
12 | @SerialName("status") val status: String,
13 | @SerialName("error_code") val errorCode: String,
14 | @SerialName("error_message") val errorMessage: String,
15 | @SerialName("error_payload") val errorPayload: String?
16 | )
17 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/api/dto/ClientDevice.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.api.dto
2 |
3 | import androidx.annotation.RestrictTo
4 | import com.getbouncer.scan.framework.util.Device
5 | import com.getbouncer.scan.framework.util.DeviceIds
6 | import kotlinx.serialization.SerialName
7 | import kotlinx.serialization.Serializable
8 |
9 | @Serializable
10 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
11 | @Deprecated(
12 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
13 | replaceWith = ReplaceWith("StripeCardScan"),
14 | )
15 | data class ClientDevice(
16 | @SerialName("ids") val ids: ClientDeviceIds,
17 | @SerialName("type") val name: String,
18 | @SerialName("boot_count") val bootCount: Int,
19 | @SerialName("locale") val locale: String?,
20 | @SerialName("carrier") val carrier: String?,
21 | @SerialName("network_operator") val networkOperator: String?,
22 | @SerialName("phone_type") val phoneType: Int?,
23 | @SerialName("phone_count") val phoneCount: Int,
24 | @SerialName("os_version") val osVersion: String,
25 | @SerialName("platform") val platform: String
26 | ) {
27 | companion object {
28 | @Deprecated(
29 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
30 | replaceWith = ReplaceWith("StripeCardScan"),
31 | )
32 | fun fromDevice(device: Device) = ClientDevice(
33 | ids = ClientDeviceIds.fromDeviceIds(device.ids),
34 | name = device.name,
35 | bootCount = device.bootCount,
36 | locale = device.locale,
37 | carrier = device.carrier,
38 | networkOperator = device.networkOperator,
39 | phoneType = device.phoneType,
40 | phoneCount = device.phoneCount,
41 | osVersion = device.osVersion.toString(),
42 | platform = device.platform
43 | )
44 | }
45 | }
46 |
47 | @Serializable
48 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
49 | @Deprecated(
50 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
51 | replaceWith = ReplaceWith("StripeCardScan"),
52 | )
53 | data class ClientDeviceIds(
54 | @SerialName("vendor_id") val androidId: String?
55 | ) {
56 | companion object {
57 | @Deprecated(
58 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
59 | replaceWith = ReplaceWith("StripeCardScan"),
60 | )
61 | fun fromDeviceIds(deviceIds: DeviceIds) = ClientDeviceIds(
62 | androidId = deviceIds.androidId
63 | )
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/api/dto/ModelDetails.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.api.dto
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | @Deprecated(
8 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
9 | replaceWith = ReplaceWith("StripeCardScan"),
10 | )
11 | data class ModelDetailsRequest(
12 | @SerialName("platform") val platform: String,
13 | @SerialName("model_class") val modelClass: String,
14 | @SerialName("model_framework_version") val modelFrameworkVersion: Int,
15 | @SerialName("cached_model_hash") val cachedModelHash: String?,
16 | @SerialName("cached_model_hash_algorithm") val cachedModelHashAlgorithm: String?,
17 | @SerialName("beta_opt_in") val betaOptIn: Boolean?,
18 | )
19 |
20 | @Serializable
21 | @Deprecated(
22 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
23 | replaceWith = ReplaceWith("StripeCardScan"),
24 | )
25 | data class ModelDetailsResponse(
26 | @SerialName("model_url") val url: String?,
27 | @SerialName("model_version") val modelVersion: String,
28 | @SerialName("model_hash") val hash: String,
29 | @SerialName("model_hash_algorithm") val hashAlgorithm: String,
30 | @SerialName("query_again_after_ms") val queryAgainAfterMs: Long? = 0,
31 | )
32 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/api/dto/ModelSignedUrlResponse.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.api.dto
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | @Deprecated(
8 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
9 | replaceWith = ReplaceWith("StripeCardScan"),
10 | )
11 | data class ModelSignedUrlResponse(
12 | @SerialName("model_url") val modelUrl: String
13 | )
14 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/api/dto/ValidateApiKeyResponse.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.api.dto
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | @Deprecated(
8 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
9 | replaceWith = ReplaceWith("StripeCardScan"),
10 | )
11 | data class ValidateApiKeyResponse(
12 | @SerialName("is_valid_api_key") val isApiKeyValid: Boolean,
13 | @SerialName("invalid_key_reason") val keyInvalidReason: String?
14 | )
15 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/exception/ImageTypeNotSupportedException.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.exception
2 |
3 | import java.lang.Exception
4 |
5 | @Deprecated(
6 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
7 | replaceWith = ReplaceWith("StripeCardScan"),
8 | )
9 | class ImageTypeNotSupportedException(val imageType: Int) : Exception()
10 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/exception/InvalidBouncerApiKeyException.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.exception
2 |
3 | @Deprecated(
4 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
5 | replaceWith = ReplaceWith("StripeCardScan"),
6 | )
7 | object InvalidBouncerApiKeyException : Exception()
8 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/image/ImageExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.image
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.BitmapFactory
5 | import android.graphics.ImageFormat
6 | import android.graphics.Rect
7 | import android.media.Image
8 | import android.renderscript.RenderScript
9 | import androidx.annotation.CheckResult
10 | import com.getbouncer.scan.framework.exception.ImageTypeNotSupportedException
11 |
12 | /**
13 | * Determine if this application supports an image format.
14 | */
15 | @CheckResult
16 | @Deprecated(
17 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
18 | replaceWith = ReplaceWith("StripeCardScan"),
19 | )
20 | fun Image.isSupportedFormat() = isSupportedFormat(this.format)
21 |
22 | /**
23 | * Determine if this application supports an image format.
24 | */
25 | @CheckResult
26 | @Deprecated(
27 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
28 | replaceWith = ReplaceWith("StripeCardScan"),
29 | )
30 | fun isSupportedFormat(imageFormat: Int) = when (imageFormat) {
31 | ImageFormat.YUV_420_888, ImageFormat.JPEG -> true
32 | ImageFormat.NV21 -> false // this fails on devices with android API 21.
33 | else -> false
34 | }
35 |
36 | /**
37 | * Convert an image to a bitmap for processing. This will throw an [ImageTypeNotSupportedException]
38 | * if the image type is not supported (see [isSupportedFormat]).
39 | */
40 | @CheckResult
41 | @Throws(ImageTypeNotSupportedException::class)
42 | @Deprecated(
43 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
44 | replaceWith = ReplaceWith("StripeCardScan"),
45 | )
46 | fun Image.toBitmap(
47 | renderScript: RenderScript,
48 | crop: Rect = Rect(
49 | 0,
50 | 0,
51 | this.width,
52 | this.height
53 | ),
54 | ): Bitmap = when (this.format) {
55 | ImageFormat.NV21 -> NV21Image(this).crop(crop).toBitmap(renderScript)
56 | ImageFormat.YUV_420_888 -> NV21Image(this).crop(crop).toBitmap(renderScript)
57 | ImageFormat.JPEG -> jpegToBitmap().crop(crop)
58 | else -> throw ImageTypeNotSupportedException(this.format)
59 | }
60 |
61 | @CheckResult
62 | private fun Image.jpegToBitmap(): Bitmap {
63 | require(format == ImageFormat.JPEG) { "Image is not in JPEG format" }
64 |
65 | val imageBuffer = planes[0].buffer
66 | val imageBytes = ByteArray(imageBuffer.remaining())
67 | imageBuffer.get(imageBytes)
68 | return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
69 | }
70 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/image/YuvImageExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.image
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.BitmapFactory
5 | import android.graphics.Rect
6 | import android.graphics.YuvImage
7 | import androidx.annotation.CheckResult
8 | import java.io.ByteArrayOutputStream
9 |
10 | /**
11 | * Convert a [YuvImage] to a [Bitmap]. This is not an efficient method since it uses an intermediate JPEG compression
12 | * and should be avoided if possible.
13 | */
14 | @CheckResult
15 | @Deprecated("This method is inefficient and should be avoided if possible")
16 | fun YuvImage.toBitmap(
17 | crop: Rect = Rect(
18 | 0,
19 | 0,
20 | this.width,
21 | this.height
22 | ),
23 | quality: Int = 75
24 | ): Bitmap {
25 | val out = ByteArrayOutputStream()
26 | compressToJpeg(crop, quality, out)
27 |
28 | val imageBytes = out.toByteArray()
29 | return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
30 | }
31 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/interop/BlockingAnalyzer.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.interop
2 |
3 | import com.getbouncer.scan.framework.Analyzer
4 | import com.getbouncer.scan.framework.AnalyzerFactory
5 | import com.getbouncer.scan.framework.AnalyzerPool
6 | import com.getbouncer.scan.framework.DEFAULT_ANALYZER_PARALLEL_COUNT
7 | import kotlinx.coroutines.runBlocking
8 |
9 | /**
10 | * An implementation of an analyzer that does not use suspending functions. This allows interoperability with java.
11 | */
12 | @Deprecated(
13 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
14 | replaceWith = ReplaceWith("StripeCardScan"),
15 | )
16 | abstract class BlockingAnalyzer : Analyzer {
17 | override suspend fun analyze(data: Input, state: State): Output = analyzeBlocking(data, state)
18 |
19 | abstract fun analyzeBlocking(data: Input, state: State): Output
20 | }
21 |
22 | /**
23 | * An implementation of an analyzer factory that does not use suspending functions. This allows interoperability with
24 | * java.
25 | */
26 | @Deprecated(
27 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
28 | replaceWith = ReplaceWith("StripeCardScan"),
29 | )
30 | abstract class BlockingAnalyzerFactory> : AnalyzerFactory {
31 | override suspend fun newInstance(): AnalyzerType? = newInstanceBlocking()
32 |
33 | abstract fun newInstanceBlocking(): AnalyzerType?
34 | }
35 |
36 | /**
37 | * An implementation of an analyzer pool factory that does not use suspending functions. This allows interoperability
38 | * with java.
39 | */
40 | @Deprecated(
41 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
42 | replaceWith = ReplaceWith("StripeCardScan"),
43 | )
44 | class BlockingAnalyzerPoolFactory @JvmOverloads constructor(
45 | private val analyzerFactory: AnalyzerFactory>,
46 | private val desiredAnalyzerCount: Int = DEFAULT_ANALYZER_PARALLEL_COUNT
47 | ) {
48 | fun buildAnalyzerPool() = AnalyzerPool(
49 | desiredAnalyzerCount = desiredAnalyzerCount,
50 | analyzers = (0 until desiredAnalyzerCount).mapNotNull {
51 | runBlocking { analyzerFactory.newInstance() }
52 | }
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/interop/JavaContinuation.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("Coroutine")
2 | package com.getbouncer.scan.framework.interop
3 |
4 | import android.util.Log
5 | import com.getbouncer.scan.framework.Config
6 | import kotlinx.coroutines.Dispatchers
7 | import kotlinx.coroutines.runBlocking
8 | import kotlin.coroutines.Continuation
9 | import kotlin.coroutines.CoroutineContext
10 | import kotlin.coroutines.resume
11 | import kotlin.coroutines.resumeWithException
12 |
13 | /**
14 | * A utility class for calling suspend functions from java. This allows listening to a suspend function with callbacks.
15 | */
16 | @Deprecated(
17 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
18 | replaceWith = ReplaceWith("StripeCardScan"),
19 | )
20 | abstract class JavaContinuation @JvmOverloads constructor(
21 | runOn: CoroutineContext = Dispatchers.Default,
22 | private val listenOn: CoroutineContext = Dispatchers.Main
23 | ) : Continuation {
24 | override val context: CoroutineContext = runOn
25 | abstract fun onComplete(value: T)
26 | abstract fun onException(exception: Throwable)
27 | override fun resumeWith(result: Result) = result.fold(
28 | onSuccess = {
29 | runBlocking(listenOn) {
30 | onComplete(it)
31 | }
32 | },
33 | onFailure = {
34 | runBlocking(listenOn) {
35 | onException(it)
36 | }
37 | }
38 | )
39 | }
40 |
41 | /**
42 | * An empty continuation for ignoring results.
43 | */
44 | @Deprecated(
45 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
46 | replaceWith = ReplaceWith("StripeCardScan"),
47 | )
48 | class EmptyJavaContinuation : JavaContinuation() {
49 | override fun onComplete(value: T) { }
50 | override fun onException(exception: Throwable) {
51 | Log.e(Config.logTag, "Error in continuation", exception)
52 | }
53 | }
54 |
55 | /**
56 | * Resume a continuation with a value.
57 | */
58 | @Deprecated(
59 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
60 | replaceWith = ReplaceWith("StripeCardScan"),
61 | )
62 | fun Continuation.resumeJava(value: T) = this.resume(value)
63 |
64 | /**
65 | * Resume a continuation with an exception.
66 | */
67 | @Deprecated(
68 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
69 | replaceWith = ReplaceWith("StripeCardScan"),
70 | )
71 | fun Continuation.resumeWithExceptionJava(exception: Throwable) = this.resumeWithException(exception)
72 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/ml/ModelVersionTracker.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.ml
2 |
3 | private val MODEL_MAP = mutableMapOf>>()
4 |
5 | /**
6 | * Details about a model loaded into memory.
7 | */
8 | @Deprecated(
9 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
10 | replaceWith = ReplaceWith("StripeCardScan"),
11 | )
12 | data class ModelLoadDetails(
13 | val modelClass: String,
14 | val modelVersion: String,
15 | val modelFrameworkVersion: Int,
16 | val success: Boolean
17 | )
18 |
19 | /**
20 | * When a ML model is loaded into memory, track the details of the model.
21 | */
22 | @Deprecated(
23 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
24 | replaceWith = ReplaceWith("StripeCardScan"),
25 | )
26 | fun trackModelLoaded(modelClass: String, modelVersion: String, modelFrameworkVersion: Int, success: Boolean) {
27 | MODEL_MAP.getOrPut(modelClass) { mutableSetOf() }.add(Triple(modelVersion, modelFrameworkVersion, success))
28 | }
29 |
30 | /**
31 | * Get the full list of models that were loaded into memory during this session.
32 | */
33 | @Deprecated(
34 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
35 | replaceWith = ReplaceWith("StripeCardScan"),
36 | )
37 | fun getLoadedModelVersions(): List = MODEL_MAP.flatMap { entry ->
38 | entry.value.map {
39 | ModelLoadDetails(
40 | modelClass = entry.key,
41 | modelVersion = it.first,
42 | modelFrameworkVersion = it.second,
43 | success = it.third
44 | )
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/ml/ssd/ClassifierScores.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.ml.ssd
2 |
3 | import com.getbouncer.scan.framework.util.updateEach
4 | import kotlin.math.exp
5 |
6 | typealias ClassifierScores = FloatArray
7 |
8 | /**
9 | * Compute softmax for the given row. This will replace each row value with a value normalized by
10 | * the sum of all the values in the row.
11 | */
12 | @Deprecated(
13 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
14 | replaceWith = ReplaceWith("StripeCardScan"),
15 | )
16 | fun ClassifierScores.softMax() {
17 | val rowSumExp = this.fold(0F) { acc, element -> acc + exp(element) }
18 | this.updateEach { exp(it) / rowSumExp }
19 | }
20 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/time/Coroutine.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.time
2 |
3 | import kotlin.math.roundToLong
4 |
5 | /**
6 | * Allow delaying for a specified duration
7 | */
8 | @Deprecated(
9 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
10 | replaceWith = ReplaceWith("StripeCardScan"),
11 | )
12 | suspend fun delay(duration: Duration) =
13 | kotlinx.coroutines.delay(duration.inMilliseconds.roundToLong())
14 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/time/Rate.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.time
2 |
3 | /**
4 | * A rate of execution.
5 | */
6 | @Deprecated(
7 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
8 | replaceWith = ReplaceWith("StripeCardScan"),
9 | )
10 | data class Rate(val amount: Long, val duration: Duration) : Comparable {
11 | override fun compareTo(other: Rate): Int {
12 | return (other.duration / other.amount).compareTo(duration / amount)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/time/Timer.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.time
2 |
3 | import android.util.Log
4 | import com.getbouncer.scan.framework.Config
5 | import kotlinx.coroutines.runBlocking
6 |
7 | @Deprecated(
8 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
9 | replaceWith = ReplaceWith("StripeCardScan"),
10 | )
11 | sealed class Timer {
12 |
13 | companion object {
14 | @Deprecated(
15 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
16 | replaceWith = ReplaceWith("StripeCardScan"),
17 | )
18 | fun newInstance(
19 | tag: String,
20 | name: String,
21 | updateInterval: Duration = 2.seconds,
22 | enabled: Boolean = Config.isDebug
23 | ) = if (enabled) {
24 | LoggingTimer(
25 | tag,
26 | name,
27 | updateInterval
28 | )
29 | } else {
30 | NoOpTimer
31 | }
32 | }
33 |
34 | /**
35 | * Log the duration of a single task and return the result from that task.
36 | *
37 | * TODO: use contracts when they are no longer experimental
38 | */
39 | fun measure(taskName: String? = null, task: () -> T): T {
40 | // contract { callsInPlace(task, EXACTLY_ONCE) }
41 | return runBlocking { measureSuspend(taskName) { task() } }
42 | }
43 |
44 | abstract suspend fun measureSuspend(taskName: String? = null, task: suspend () -> T): T
45 | }
46 |
47 | private object NoOpTimer : Timer() {
48 |
49 | // TODO: use contracts when they are no longer experimental
50 | override suspend fun measureSuspend(taskName: String?, task: suspend () -> T): T {
51 | // contract { callsInPlace(task, EXACTLY_ONCE) }
52 | return task()
53 | }
54 | }
55 |
56 | private class LoggingTimer(
57 | private val tag: String,
58 | private val name: String,
59 | private val updateInterval: Duration
60 | ) : Timer() {
61 | private var executionCount = 0
62 | private var executionTotalDuration = Duration.ZERO
63 | private var updateClock = Clock.markNow()
64 |
65 | // TODO: use contracts when they are no longer experimental
66 | override suspend fun measureSuspend(taskName: String?, task: suspend () -> T): T {
67 | // contract { callsInPlace(task, EXACTLY_ONCE) }
68 | val (duration, result) = measureTime { task() }
69 |
70 | executionCount++
71 | executionTotalDuration += duration
72 |
73 | if (updateClock.elapsedSince() > updateInterval) {
74 | updateClock = Clock.markNow()
75 | Log.d(
76 | tag,
77 | "$name${if (!taskName.isNullOrEmpty()) ".$taskName" else ""} executing on " +
78 | "thread ${Thread.currentThread().name} " +
79 | "AT ${executionCount / executionTotalDuration.inSeconds} FPS, " +
80 | "${executionTotalDuration.inMilliseconds / executionCount} MS/F"
81 | )
82 | }
83 | return result
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/util/AppDetails.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.util
2 |
3 | import android.content.Context
4 | import com.getbouncer.scan.framework.BuildConfig
5 |
6 | @Deprecated(
7 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
8 | replaceWith = ReplaceWith("StripeCardScan"),
9 | )
10 | data class AppDetails(
11 | val appPackageName: String?,
12 | val applicationId: String,
13 | val libraryPackageName: String,
14 | val sdkVersion: String,
15 | val sdkVersionCode: Int,
16 | val sdkFlavor: String,
17 | val isDebugBuild: Boolean
18 | ) {
19 | companion object {
20 | @JvmStatic
21 | fun fromContext(context: Context) = AppDetails(
22 | appPackageName = getAppPackageName(context),
23 | applicationId = getApplicationId(),
24 | libraryPackageName = getLibraryPackageName(),
25 | sdkVersion = getSdkVersion(),
26 | sdkVersionCode = getSdkVersionCode(),
27 | sdkFlavor = getSdkFlavor(),
28 | isDebugBuild = isDebugBuild()
29 | )
30 | }
31 | }
32 |
33 | @Deprecated(
34 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
35 | replaceWith = ReplaceWith("StripeCardScan"),
36 | )
37 | fun getAppPackageName(context: Context): String? = context.applicationContext.packageName
38 |
39 | private fun getApplicationId(): String = "" // no longer available in later versions of gradle.
40 |
41 | @Deprecated(
42 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
43 | replaceWith = ReplaceWith("StripeCardScan"),
44 | )
45 | fun getLibraryPackageName(): String = BuildConfig.LIBRARY_PACKAGE_NAME
46 |
47 | @Deprecated(
48 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
49 | replaceWith = ReplaceWith("StripeCardScan"),
50 | )
51 | fun getSdkVersion(): String = BuildConfig.SDK_VERSION_STRING
52 |
53 | private fun getSdkVersionCode(): Int = -1 // no longer available in later versions of gradle.
54 |
55 | @Deprecated(
56 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
57 | replaceWith = ReplaceWith("StripeCardScan"),
58 | )
59 | fun getSdkFlavor(): String = BuildConfig.BUILD_TYPE
60 |
61 | private fun isDebugBuild(): Boolean = BuildConfig.DEBUG
62 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/util/File.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.util
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.withContext
5 | import java.io.File
6 | import java.io.FileInputStream
7 | import java.io.IOException
8 | import java.lang.Exception
9 | import java.security.MessageDigest
10 | import java.security.NoSuchAlgorithmException
11 |
12 | private val illegalFileNameCharacters = setOf('"', '*', '/', ':', '<', '>', '?', '\\', '|', '+', ',', ';', '=', '[', ']')
13 |
14 | /**
15 | * Sanitize the name of a file for storage
16 | */
17 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
18 | fun sanitizeFileName(unsanitized: String) =
19 | unsanitized.map { char -> if (char in illegalFileNameCharacters) "_" else char }.joinToString("")
20 |
21 | /**
22 | * Determine if a [File] matches the expected [hash].
23 | */
24 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
25 | suspend fun fileMatchesHash(localFile: File, hash: String, hashAlgorithm: String) = try {
26 | hash == calculateHash(localFile, hashAlgorithm)
27 | } catch (t: Throwable) {
28 | false
29 | }
30 |
31 | /**
32 | * Calculate the hash of a file using the [hashAlgorithm].
33 | */
34 | @Throws(IOException::class, NoSuchAlgorithmException::class)
35 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
36 | suspend fun calculateHash(file: File, hashAlgorithm: String): String? =
37 | withContext(Dispatchers.IO) {
38 | if (file.exists()) {
39 | val digest = MessageDigest.getInstance(hashAlgorithm)
40 | FileInputStream(file).use { digest.update(it.readBytes()) }
41 | digest.digest().joinToString("") { "%02x".format(it) }
42 | } else {
43 | null
44 | }
45 | }
46 |
47 | /**
48 | * A file does not match the expected hash value.
49 | */
50 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
51 | class HashMismatchException(val algorithm: String, val expected: String, val actual: String?) :
52 | Exception("Invalid hash result for algorithm '$algorithm'. Expected '$expected' but got '$actual'") {
53 | override fun toString() = "HashMismatchException(algorithm='$algorithm', expected='$expected', actual='$actual')"
54 | }
55 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/util/FrameSaver.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.util
2 |
3 | import androidx.annotation.CheckResult
4 | import kotlinx.coroutines.sync.Mutex
5 | import kotlinx.coroutines.sync.withLock
6 | import java.util.LinkedList
7 |
8 | /**
9 | * Save data frames for later retrieval.
10 | */
11 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
12 | abstract class FrameSaver {
13 |
14 | private val saveFrameMutex = Mutex()
15 | private val savedFrames = mutableMapOf>()
16 |
17 | /**
18 | * Determine how frames should be classified using [getSaveFrameIdentifier], and then store them
19 | * in a map of frames based on that identifier.
20 | *
21 | * This method keeps track of the total number of saved frames. If the total number or total
22 | * size exceeds the maximum allowed, the oldest frames will be dropped.
23 | */
24 | suspend fun saveFrame(frame: Frame, metaData: MetaData) {
25 | val identifier = getSaveFrameIdentifier(frame, metaData) ?: return
26 | return saveFrameMutex.withLock {
27 | val maxSavedFrames = getMaxSavedFrames(identifier)
28 |
29 | val frames = savedFrames.getOrPut(identifier) { LinkedList() }
30 | frames.addFirst(frame)
31 |
32 | while (frames.size > maxSavedFrames) {
33 | // saved frames is over size limit, reduce until it's not
34 | removeFrame(identifier, frames)
35 | }
36 | }
37 | }
38 |
39 | /**
40 | * Retrieve a copy of the list of saved frames.
41 | */
42 | @CheckResult
43 | fun getSavedFrames(): Map> = savedFrames.toMap()
44 |
45 | /**
46 | * Clear all saved frames
47 | */
48 | suspend fun reset() = saveFrameMutex.withLock {
49 | savedFrames.clear()
50 | }
51 |
52 | protected abstract fun getMaxSavedFrames(savedFrameIdentifier: Identifier): Int
53 |
54 | /**
55 | * Determine if a data frame should be saved for future processing.
56 | *
57 | * If this method returns a non-null string, the frame will be saved under that identifier.
58 | */
59 | protected abstract fun getSaveFrameIdentifier(frame: Frame, metaData: MetaData): Identifier?
60 |
61 | /**
62 | * Remove a frame from this list. The most recently added frames will be at the beginning of
63 | * this list, while the least recently added frames will be at the end.
64 | */
65 | protected open fun removeFrame(identifier: Identifier, frames: LinkedList) {
66 | frames.removeLast()
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/util/ItemCounter.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.util
2 |
3 | import androidx.annotation.CheckResult
4 | import kotlinx.coroutines.runBlocking
5 | import kotlinx.coroutines.sync.Mutex
6 | import kotlinx.coroutines.sync.withLock
7 | import java.util.LinkedList
8 |
9 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
10 | interface ItemCounter {
11 | suspend fun countItem(item: T): Int
12 |
13 | fun getHighestCountItem(minCount: Int = 1): Pair?
14 |
15 | suspend fun reset()
16 | }
17 |
18 | /**
19 | * A class that counts and saves items.
20 | */
21 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
22 | class ItemTotalCounter(firstValue: T? = null) : ItemCounter {
23 | private val storageMutex = Mutex()
24 | private val items = mutableMapOf()
25 |
26 | init { if (firstValue != null) runBlocking { countItem(firstValue) } }
27 |
28 | /**
29 | * Increment the count for the given item. Return the new count for the given item.
30 | */
31 | override suspend fun countItem(item: T): Int = storageMutex.withLock {
32 | 1 + (items.put(item, 1 + (items[item] ?: 0)) ?: 0)
33 | }
34 |
35 | /**
36 | * Get the item that with the highest count.
37 | *
38 | * @param minCount the minimum times an item must have been counted.
39 | */
40 | @CheckResult
41 | override fun getHighestCountItem(minCount: Int): Pair? =
42 | items
43 | .maxByOrNull { it.value }
44 | ?.let { if (items[it.key] ?: 0 >= minCount) it.value to it.key else null }
45 |
46 | /**
47 | * Reset all item counts.
48 | */
49 | override suspend fun reset() = storageMutex.withLock {
50 | items.clear()
51 | }
52 | }
53 |
54 | /**
55 | * A class that keeps track of [maxItemsToTrack] recent items.
56 | */
57 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
58 | class ItemRecencyCounter(
59 | private val maxItemsToTrack: Int,
60 | firstValue: T? = null
61 | ) : ItemCounter {
62 | private val storageMutex = Mutex()
63 | private val items = LinkedList()
64 |
65 | init { if (firstValue != null) runBlocking { countItem(firstValue) } }
66 |
67 | /**
68 | * Increment the count for the given item. Return the new count for the given item.
69 | */
70 | override suspend fun countItem(item: T): Int = storageMutex.withLock {
71 | items.addFirst(item)
72 |
73 | while (items.size > maxItemsToTrack) {
74 | items.removeLast()
75 | }
76 |
77 | items.count { it == item }
78 | }
79 |
80 | /**
81 | * Get the item that with the highest count.
82 | *
83 | * @param minCount the minimum times an item must have been counted.
84 | */
85 | @CheckResult
86 | override fun getHighestCountItem(minCount: Int): Pair? =
87 | items
88 | .groupingBy { it }
89 | .eachCount()
90 | .filter { it.value >= minCount }
91 | .maxByOrNull { it.value }
92 | ?.let { it.value to it.key }
93 |
94 | /**
95 | * Reset all item counts.
96 | */
97 | override suspend fun reset() = storageMutex.withLock {
98 | items.clear()
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/scan-framework/src/main/java/com/getbouncer/scan/framework/util/Retry.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.util
2 |
3 | import com.getbouncer.scan.framework.time.Duration
4 | import kotlinx.coroutines.delay
5 |
6 | private const val DEFAULT_RETRIES = 3
7 |
8 | /**
9 | * Call a given [task]. If the task throws an exception not included in the [excluding] list, retry
10 | * the task up to [times], each time after a delay of [retryDelay].
11 | *
12 | * @param retryDelay the amount of time between a failed task and the next retry
13 | * @param times the number of times to retry the task
14 | * @param excluding a list of exceptions to fail immediately on
15 | * @param task the task to retry
16 | *
17 | * TODO: use contracts when they're no longer experimental
18 | */
19 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
20 | suspend fun retry(
21 | retryDelay: Duration,
22 | times: Int = DEFAULT_RETRIES,
23 | excluding: List> = emptyList(),
24 | task: suspend () -> T
25 | ): T {
26 | // contract { callsInPlace(task, InvocationKind.AT_LEAST_ONCE) }
27 | var exception: Throwable? = null
28 | for (attempt in 1..times) {
29 | try {
30 | return task()
31 | } catch (t: Throwable) {
32 | exception = t
33 | if (t.javaClass in excluding) {
34 | throw t
35 | }
36 | if (attempt < times) {
37 | delay(retryDelay.inMilliseconds.toLong())
38 | }
39 | }
40 | }
41 |
42 | if (exception != null) {
43 | throw exception
44 | } else {
45 | // This code should never be reached
46 | throw UnexpectedRetryException()
47 | }
48 | }
49 |
50 | /**
51 | * Call a given [task]. If the task throws an exception not included in the [excluding] list, retry
52 | * the task up to [times].
53 | *
54 | * @param times the number of times to retry the task
55 | * @param excluding a list of exceptions to fail immediately on
56 | * @param task the task to retry
57 | *
58 | * TODO: use contracts when they're no longer experimental
59 | */
60 | @Deprecated(message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan")
61 | fun retrySync(
62 | times: Int = DEFAULT_RETRIES,
63 | excluding: List> = emptyList(),
64 | task: () -> T
65 | ): T {
66 | // contract { callsInPlace(task, InvocationKind.AT_LEAST_ONCE) }
67 | var exception: Throwable? = null
68 | for (attempt in 1..times) {
69 | try {
70 | return task()
71 | } catch (t: Throwable) {
72 | exception = t
73 | if (t.javaClass in excluding) {
74 | throw t
75 | }
76 | }
77 | }
78 |
79 | if (exception != null) {
80 | throw exception
81 | } else {
82 | // This code should never be reached
83 | throw UnexpectedRetryException()
84 | }
85 | }
86 |
87 | /**
88 | * This exception should never be thrown, and therefore can be private.
89 | */
90 | private class UnexpectedRetryException : Exception()
91 |
--------------------------------------------------------------------------------
/scan-framework/src/test/java/com/getbouncer/scan/framework/AnalyzerTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework
2 |
3 | import androidx.test.filters.SmallTest
4 | import kotlinx.coroutines.ExperimentalCoroutinesApi
5 | import kotlinx.coroutines.test.runBlockingTest
6 | import org.junit.Test
7 | import kotlin.test.assertEquals
8 |
9 | class AnalyzerTest {
10 |
11 | @Test
12 | @SmallTest
13 | @ExperimentalCoroutinesApi
14 | fun analyzerPoolCreateNormally() = runBlockingTest {
15 | class TestAnalyzerFactory : AnalyzerFactory {
16 | override suspend fun newInstance(): TestAnalyzer? = TestAnalyzer()
17 | }
18 |
19 | val analyzerPool = AnalyzerPool.of(
20 | analyzerFactory = TestAnalyzerFactory(),
21 | desiredAnalyzerCount = 12
22 | )
23 |
24 | assertEquals(12, analyzerPool.desiredAnalyzerCount)
25 | assertEquals(12, analyzerPool.analyzers.size)
26 | assertEquals(3, analyzerPool.analyzers[0].analyze(1, 2))
27 | }
28 |
29 | @Test
30 | @SmallTest
31 | @ExperimentalCoroutinesApi
32 | fun analyzerPoolCreateFailure() = runBlockingTest {
33 | class TestAnalyzerFactory : AnalyzerFactory {
34 | override suspend fun newInstance(): TestAnalyzer? = null
35 | }
36 |
37 | val analyzerPool = AnalyzerPool.of(
38 | analyzerFactory = TestAnalyzerFactory(),
39 | desiredAnalyzerCount = 12
40 | )
41 |
42 | assertEquals(12, analyzerPool.desiredAnalyzerCount)
43 | assertEquals(0, analyzerPool.analyzers.size)
44 | }
45 |
46 | private class TestAnalyzer : Analyzer {
47 | override suspend fun analyze(data: Int, state: Int): Int = data + state
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/scan-framework/src/test/java/com/getbouncer/scan/framework/util/FrameSaverTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.util
2 |
3 | import kotlinx.coroutines.ExperimentalCoroutinesApi
4 | import kotlinx.coroutines.test.runBlockingTest
5 | import org.junit.Test
6 | import kotlin.test.assertEquals
7 |
8 | class FrameSaverTest {
9 |
10 | private class TestFrameSaver : FrameSaver() {
11 | override fun getMaxSavedFrames(savedFrameIdentifier: String): Int = 3
12 |
13 | override fun getSaveFrameIdentifier(frame: Int, metaData: Int): String? = when (metaData) {
14 | 1 -> "one"
15 | 2 -> "two"
16 | else -> "else"
17 | }
18 | }
19 |
20 | @Test
21 | @ExperimentalCoroutinesApi
22 | fun saveFrames() = runBlockingTest {
23 | val frameSaver = TestFrameSaver()
24 |
25 | frameSaver.saveFrame(1, 1)
26 | frameSaver.saveFrame(1, 1)
27 | frameSaver.saveFrame(1, 1)
28 | frameSaver.saveFrame(1, 1)
29 | frameSaver.saveFrame(2, 2)
30 | frameSaver.saveFrame(3, 3)
31 | frameSaver.saveFrame(4, 4)
32 |
33 | assertEquals(
34 | listOf(1, 1, 1),
35 | frameSaver.getSavedFrames()["one"]?.toList()
36 | )
37 |
38 | assertEquals(
39 | listOf(2),
40 | frameSaver.getSavedFrames()["two"]?.toList()
41 | )
42 |
43 | assertEquals(
44 | listOf(4, 3),
45 | frameSaver.getSavedFrames()["else"]?.toList()
46 | )
47 | }
48 |
49 | @Test
50 | @ExperimentalCoroutinesApi
51 | fun doesNotLeakInternalMap() = runBlockingTest {
52 | val frameSaver = TestFrameSaver()
53 |
54 | frameSaver.saveFrame(1, 1)
55 |
56 | val savedFrames = frameSaver.getSavedFrames()
57 |
58 | frameSaver.reset()
59 |
60 | assertEquals(1, savedFrames["one"]?.first)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/scan-framework/src/test/java/com/getbouncer/scan/framework/util/RetryTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.framework.util
2 |
3 | import androidx.test.filters.SmallTest
4 | import com.getbouncer.scan.framework.time.milliseconds
5 | import kotlinx.coroutines.ExperimentalCoroutinesApi
6 | import kotlinx.coroutines.test.runBlockingTest
7 | import org.junit.Test
8 | import kotlin.test.assertEquals
9 | import kotlin.test.assertFailsWith
10 |
11 | class RetryTest {
12 |
13 | @Test
14 | @SmallTest
15 | @ExperimentalCoroutinesApi
16 | fun retry_succeedsFirst() = runBlockingTest {
17 | var executions = 0
18 |
19 | assertEquals(
20 | 1,
21 | retry(1.milliseconds) {
22 | executions++
23 | 1
24 | }
25 | )
26 | assertEquals(1, executions)
27 | }
28 |
29 | @Test
30 | @SmallTest
31 | @ExperimentalCoroutinesApi
32 | fun retry_succeedsSecond() = runBlockingTest {
33 | var executions = 0
34 |
35 | assertEquals(
36 | 1,
37 | retry(1.milliseconds) {
38 | executions++
39 | if (executions == 2) {
40 | 1
41 | } else {
42 | throw RuntimeException()
43 | }
44 | }
45 | )
46 | assertEquals(2, executions)
47 | }
48 |
49 | @Test
50 | @SmallTest
51 | @ExperimentalCoroutinesApi
52 | fun retry_fails() = runBlockingTest {
53 | var executions = 0
54 |
55 | assertFailsWith {
56 | retry(1.milliseconds) {
57 | executions++
58 | throw RuntimeException()
59 | }
60 | }
61 | assertEquals(3, executions)
62 | }
63 |
64 | @Test
65 | @SmallTest
66 | @ExperimentalCoroutinesApi
67 | fun retry_excluding() = runBlockingTest {
68 | var executions = 0
69 |
70 | assertFailsWith {
71 | retry(1.milliseconds, excluding = listOf(RuntimeException::class.java)) {
72 | executions++
73 | throw RuntimeException()
74 | }
75 | }
76 | assertEquals(1, executions)
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/scan-payment-full/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/scan-payment-full/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlinx-serialization'
4 |
5 | android {
6 | compileSdkVersion 30
7 | buildToolsVersion '30.0.3'
8 |
9 | defaultConfig {
10 | minSdkVersion 21
11 | targetSdkVersion 30
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles "consumer-rules.pro"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
21 | }
22 | }
23 |
24 | testOptions {
25 | unitTests.includeAndroidResources = true
26 | }
27 |
28 | lintOptions {
29 | enable "Interoperability"
30 | }
31 |
32 | packagingOptions {
33 | pickFirst 'META-INF/AL2.0'
34 | pickFirst 'META-INF/LGPL2.1'
35 | }
36 |
37 | aaptOptions {
38 | noCompress "tflite"
39 | }
40 | }
41 |
42 | dependencies {
43 | implementation fileTree(dir: "libs", include: ["*.jar"])
44 | api project(":scan-framework")
45 | api project(":scan-payment")
46 |
47 | implementation "androidx.core:core-ktx:[1.3.1,1.6.0]"
48 | }
49 |
50 | dependencies {
51 | testImplementation "androidx.test:core:1.4.0"
52 | testImplementation "androidx.test:runner:1.4.0"
53 | testImplementation "junit:junit:4.13.2"
54 | testImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
55 | }
56 |
57 | dependencies {
58 | androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
59 | androidTestImplementation "androidx.test.ext:junit:1.1.3"
60 | androidTestImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
61 | androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1"
62 | }
63 |
64 | apply from: "deploy.gradle"
65 |
--------------------------------------------------------------------------------
/scan-payment-full/src/androidTest/res/drawable/card_no_card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment-full/src/androidTest/res/drawable/card_no_card.png
--------------------------------------------------------------------------------
/scan-payment-full/src/androidTest/res/drawable/card_no_pan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment-full/src/androidTest/res/drawable/card_no_pan.png
--------------------------------------------------------------------------------
/scan-payment-full/src/androidTest/res/drawable/card_pan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment-full/src/androidTest/res/drawable/card_pan.png
--------------------------------------------------------------------------------
/scan-payment-full/src/androidTest/res/drawable/ocr_card_numbers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment-full/src/androidTest/res/drawable/ocr_card_numbers.png
--------------------------------------------------------------------------------
/scan-payment-full/src/androidTest/res/drawable/ocr_card_numbers_qr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment-full/src/androidTest/res/drawable/ocr_card_numbers_qr.png
--------------------------------------------------------------------------------
/scan-payment-full/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/scan-payment-full/src/main/assets/darknite_1_1_1_16.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment-full/src/main/assets/darknite_1_1_1_16.tflite
--------------------------------------------------------------------------------
/scan-payment-full/src/main/assets/ux_0_5_23_16.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment-full/src/main/assets/ux_0_5_23_16.tflite
--------------------------------------------------------------------------------
/scan-payment-minimal/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/scan-payment-minimal/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlinx-serialization'
4 |
5 | android {
6 | compileSdkVersion 30
7 | buildToolsVersion '30.0.3'
8 |
9 | defaultConfig {
10 | minSdkVersion 21
11 | targetSdkVersion 30
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles "consumer-rules.pro"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
21 | }
22 | }
23 |
24 | testOptions {
25 | unitTests.includeAndroidResources = true
26 | }
27 |
28 | lintOptions {
29 | enable "Interoperability"
30 | }
31 |
32 | packagingOptions {
33 | pickFirst 'META-INF/AL2.0'
34 | pickFirst 'META-INF/LGPL2.1'
35 | }
36 | }
37 |
38 | dependencies {
39 | implementation fileTree(dir: "libs", include: ["*.jar"])
40 | api project(":scan-framework")
41 | api project(":scan-payment")
42 |
43 | implementation "androidx.core:core-ktx:[1.3.1,1.6.0]"
44 | }
45 |
46 | dependencies {
47 | testImplementation "androidx.test:core:1.4.0"
48 | testImplementation "androidx.test:runner:1.4.0"
49 | testImplementation "junit:junit:4.13.2"
50 | testImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
51 | }
52 |
53 | dependencies {
54 | androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
55 | androidTestImplementation "androidx.test.ext:junit:1.1.3"
56 | androidTestImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
57 | androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1"
58 | }
59 |
60 | apply from: "deploy.gradle"
61 |
--------------------------------------------------------------------------------
/scan-payment-minimal/src/androidTest/res/drawable/card_no_card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment-minimal/src/androidTest/res/drawable/card_no_card.png
--------------------------------------------------------------------------------
/scan-payment-minimal/src/androidTest/res/drawable/card_no_pan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment-minimal/src/androidTest/res/drawable/card_no_pan.png
--------------------------------------------------------------------------------
/scan-payment-minimal/src/androidTest/res/drawable/card_pan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment-minimal/src/androidTest/res/drawable/card_pan.png
--------------------------------------------------------------------------------
/scan-payment-minimal/src/androidTest/res/drawable/ocr_card_numbers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment-minimal/src/androidTest/res/drawable/ocr_card_numbers.png
--------------------------------------------------------------------------------
/scan-payment-minimal/src/androidTest/res/drawable/ocr_card_numbers_qr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment-minimal/src/androidTest/res/drawable/ocr_card_numbers_qr.png
--------------------------------------------------------------------------------
/scan-payment-minimal/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/scan-payment-minimal/src/main/assets/UX.0.25.106.8.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment-minimal/src/main/assets/UX.0.25.106.8.tflite
--------------------------------------------------------------------------------
/scan-payment-minimal/src/main/assets/mb2_brex_metal_synthetic_svhnextra_epoch_3_5_98_8.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment-minimal/src/main/assets/mb2_brex_metal_synthetic_svhnextra_epoch_3_5_98_8.tflite
--------------------------------------------------------------------------------
/scan-payment/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/scan-payment/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlinx-serialization'
4 | apply plugin: 'kotlin-parcelize'
5 |
6 | android {
7 | compileSdkVersion 30
8 | buildToolsVersion '30.0.3'
9 |
10 | defaultConfig {
11 | minSdkVersion 21
12 | targetSdkVersion 30
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles "consumer-rules.pro"
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
22 | }
23 | }
24 |
25 | testOptions {
26 | unitTests.includeAndroidResources = true
27 | }
28 |
29 | lintOptions {
30 | enable "Interoperability"
31 | }
32 |
33 | packagingOptions {
34 | pickFirst 'META-INF/AL2.0'
35 | pickFirst 'META-INF/LGPL2.1'
36 | }
37 |
38 | compileOptions {
39 | sourceCompatibility JavaVersion.VERSION_1_8
40 | targetCompatibility JavaVersion.VERSION_1_8
41 | }
42 |
43 | kotlinOptions {
44 | jvmTarget = JavaVersion.VERSION_1_8.toString()
45 | }
46 | }
47 |
48 | dependencies {
49 | implementation fileTree(dir: "libs", include: ["*.jar"])
50 | implementation project(":scan-framework")
51 |
52 | implementation "androidx.core:core-ktx:[1.3.1,1.6.0]"
53 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:[1.4.0,1.5.1]"
54 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:[1.4.0,1.5.1]"
55 | implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:[1.1.0,1.2.2]"
56 |
57 | // Allow the user to specify their own version of Tensorflow Lite to include
58 | runtimeOnly project(":tensorflow-lite")
59 | compileOnly "org.tensorflow:tensorflow-lite:2.4.0"
60 | }
61 |
62 | dependencies {
63 | testImplementation "androidx.test:core:1.4.0"
64 | testImplementation "androidx.test:runner:1.4.0"
65 | testImplementation "junit:junit:4.13.2"
66 | testImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
67 | }
68 |
69 | dependencies {
70 | androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
71 | androidTestImplementation "androidx.test.ext:junit:1.1.3"
72 | androidTestImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
73 | androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1"
74 | }
75 |
76 | apply from: "deploy.gradle"
77 |
--------------------------------------------------------------------------------
/scan-payment/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment/consumer-rules.pro
--------------------------------------------------------------------------------
/scan-payment/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -keep class org.tensorflow.lite.Interpreter { *; }
24 |
--------------------------------------------------------------------------------
/scan-payment/src/androidTest/java/com/getbouncer/scan/payment/PaymentCardAndroidTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment
2 |
3 | import androidx.test.filters.SmallTest
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import com.getbouncer.scan.payment.card.CardType
6 | import com.getbouncer.scan.payment.card.getCardType
7 | import kotlinx.coroutines.ExperimentalCoroutinesApi
8 | import kotlinx.coroutines.runBlocking
9 | import org.junit.Test
10 | import kotlin.test.assertEquals
11 |
12 | class PaymentCardAndroidTest {
13 |
14 | private val appContext = InstrumentationRegistry.getInstrumentation().targetContext
15 |
16 | @Test
17 | @SmallTest
18 | @ExperimentalCoroutinesApi
19 | fun paymentCardTypeDebit() = runBlocking {
20 | assertEquals(CardType.Debit, getCardType(appContext, "349011"))
21 | assertEquals(CardType.Credit, getCardType(appContext, "648298"))
22 | assertEquals(CardType.Credit, getCardType(appContext, "648299"))
23 | assertEquals(CardType.Prepaid, getCardType(appContext, "531306"))
24 | assertEquals(CardType.Unknown, getCardType(appContext, "123456"))
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/scan-payment/src/androidTest/java/com/getbouncer/scan/payment/ml/ExpiryDetectTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment.ml
2 |
3 | import androidx.test.filters.SmallTest
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import com.getbouncer.scan.framework.Config
6 | import kotlinx.coroutines.runBlocking
7 | import org.junit.After
8 | import org.junit.Before
9 | import org.junit.Test
10 | import kotlin.test.assertNotNull
11 |
12 | class ExpiryDetectTest {
13 | private val testContext = InstrumentationRegistry.getInstrumentation().context
14 |
15 | @Before
16 | fun before() {
17 | Config.apiKey = "qOJ_fF-WLDMbG05iBq5wvwiTNTmM2qIn"
18 | }
19 |
20 | @After
21 | fun after() {
22 | Config.apiKey = null
23 | }
24 |
25 | /**
26 | * TODO: this method should use runBlockingTest instead of runBlocking. However, an issue with
27 | * runBlockingTest currently fails when functions under test use withContext(Dispatchers.IO) or
28 | * withContext(Dispatchers.Default).
29 | *
30 | * See https://github.com/Kotlin/kotlinx.coroutines/issues/1204 for details.
31 | */
32 | @Test
33 | @SmallTest
34 | fun createsInterpreter() = runBlocking {
35 | val fetcher = ExpiryDetect.ModelFetcher(testContext)
36 | fetcher.clearCache()
37 |
38 | val factory = ExpiryDetect.Factory(testContext, fetcher.fetchData(forImmediateUse = false, isOptional = false))
39 |
40 | assertNotNull(factory.newInstance())
41 | }.let { }
42 |
43 | @Test
44 | @SmallTest
45 | fun createsValidOutput() {
46 | // TODO: add resources and test the object detector
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/scan-payment/src/androidTest/java/com/getbouncer/scan/payment/ml/TextDetectTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment.ml
2 |
3 | import androidx.test.filters.SmallTest
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import com.getbouncer.scan.framework.Config
6 | import kotlinx.coroutines.runBlocking
7 | import org.junit.After
8 | import org.junit.Before
9 | import org.junit.Test
10 | import kotlin.test.assertNotNull
11 |
12 | class TextDetectTest {
13 | private val testContext = InstrumentationRegistry.getInstrumentation().context
14 |
15 | @Before
16 | fun before() {
17 | Config.apiKey = "qOJ_fF-WLDMbG05iBq5wvwiTNTmM2qIn"
18 | }
19 |
20 | @After
21 | fun after() {
22 | Config.apiKey = null
23 | }
24 |
25 | /**
26 | * TODO: this method should use runBlockingTest instead of runBlocking. However, an issue with
27 | * runBlockingTest currently fails when functions under test use withContext(Dispatchers.IO) or
28 | * withContext(Dispatchers.Default).
29 | *
30 | * See https://github.com/Kotlin/kotlinx.coroutines/issues/1204 for details.
31 | */
32 | @Test
33 | @SmallTest
34 | fun createsInterpreter() = runBlocking {
35 | val fetcher = TextDetect.ModelFetcher(testContext)
36 | fetcher.clearCache()
37 |
38 | val factory = TextDetect.Factory(testContext, fetcher.fetchData(forImmediateUse = false, isOptional = false))
39 |
40 | assertNotNull(factory.newInstance())
41 | }.let { }
42 |
43 | @Test
44 | @SmallTest
45 | fun createsValidOutput() {
46 | // TODO: add resources and test the object detector
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/scan-payment/src/androidTest/res/drawable/card_no_card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment/src/androidTest/res/drawable/card_no_card.png
--------------------------------------------------------------------------------
/scan-payment/src/androidTest/res/drawable/card_no_pan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment/src/androidTest/res/drawable/card_no_pan.png
--------------------------------------------------------------------------------
/scan-payment/src/androidTest/res/drawable/card_pan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment/src/androidTest/res/drawable/card_pan.png
--------------------------------------------------------------------------------
/scan-payment/src/androidTest/res/drawable/ocr_card_numbers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment/src/androidTest/res/drawable/ocr_card_numbers.png
--------------------------------------------------------------------------------
/scan-payment/src/androidTest/res/drawable/ocr_card_numbers_qr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment/src/androidTest/res/drawable/ocr_card_numbers_qr.png
--------------------------------------------------------------------------------
/scan-payment/src/androidTest/res/raw/sample_bitmap.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment/src/androidTest/res/raw/sample_bitmap.bin
--------------------------------------------------------------------------------
/scan-payment/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/scan-payment/src/main/java/com/getbouncer/scan/payment/FrameDetails.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment
2 |
3 | import androidx.annotation.Keep
4 |
5 | @Keep
6 | @Deprecated(
7 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
8 | replaceWith = ReplaceWith("StripeCardScan"),
9 | )
10 | data class FrameDetails(
11 | val panSideConfidence: Float,
12 | val noPanSideConfidence: Float,
13 | val noCardConfidence: Float,
14 | val hasPan: Boolean,
15 | )
16 |
--------------------------------------------------------------------------------
/scan-payment/src/main/java/com/getbouncer/scan/payment/ModelManager.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment
2 |
3 | import android.content.Context
4 | import androidx.annotation.VisibleForTesting
5 | import com.getbouncer.scan.framework.FetchedData
6 | import com.getbouncer.scan.framework.Fetcher
7 | import kotlinx.coroutines.sync.Mutex
8 | import kotlinx.coroutines.sync.withLock
9 |
10 | @Deprecated(
11 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
12 | replaceWith = ReplaceWith("StripeCardScan"),
13 | )
14 | abstract class ModelManager {
15 | private lateinit var fetcher: Fetcher
16 | private val fetcherMutex = Mutex()
17 |
18 | private var onFetch: ((success: Boolean) -> Unit)? = null
19 |
20 | suspend fun fetchModel(context: Context, forImmediateUse: Boolean, isOptional: Boolean = false): FetchedData {
21 | fetcherMutex.withLock {
22 | if (!this::fetcher.isInitialized) {
23 | fetcher = getModelFetcher(context.applicationContext)
24 | }
25 | }
26 | return fetcher.fetchData(forImmediateUse, isOptional).also {
27 | onFetch?.invoke(it.successfullyFetched)
28 | }
29 | }
30 |
31 | suspend fun isReady() = fetcherMutex.withLock {
32 | if (this::fetcher.isInitialized) fetcher.isCached() else false
33 | }
34 |
35 | @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
36 | abstract fun getModelFetcher(context: Context): Fetcher
37 | }
38 |
--------------------------------------------------------------------------------
/scan-payment/src/main/java/com/getbouncer/scan/payment/TextDetectModelManager.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment
2 |
3 | import android.content.Context
4 | import com.getbouncer.scan.framework.Fetcher
5 | import com.getbouncer.scan.payment.ml.TextDetect
6 |
7 | @Deprecated(
8 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
9 | replaceWith = ReplaceWith("StripeCardScan"),
10 | )
11 | object TextDetectModelManager : ModelManager() {
12 | override fun getModelFetcher(context: Context): Fetcher = TextDetect.ModelFetcher(context)
13 | }
14 |
--------------------------------------------------------------------------------
/scan-payment/src/main/java/com/getbouncer/scan/payment/card/CardIssuer.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment.card
2 |
3 | import android.os.Parcelable
4 | import kotlinx.parcelize.Parcelize
5 |
6 | /**
7 | * A list of supported card issuers.
8 | */
9 | @Deprecated(
10 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
11 | replaceWith = ReplaceWith("StripeCardScan"),
12 | )
13 | sealed class CardIssuer(open val displayName: String) : Parcelable {
14 | @Parcelize
15 | @Deprecated(
16 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
17 | replaceWith = ReplaceWith("StripeCardScan"),
18 | )
19 | object AmericanExpress : CardIssuer("American Express")
20 |
21 | @Parcelize
22 | @Deprecated(
23 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
24 | replaceWith = ReplaceWith("StripeCardScan"),
25 | )
26 | data class Custom(override val displayName: String) : CardIssuer(displayName)
27 |
28 | @Parcelize
29 | @Deprecated(
30 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
31 | replaceWith = ReplaceWith("StripeCardScan"),
32 | )
33 | object DinersClub : CardIssuer("Diners Club")
34 |
35 | @Parcelize
36 | @Deprecated(
37 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
38 | replaceWith = ReplaceWith("StripeCardScan"),
39 | )
40 | object Discover : CardIssuer("Discover")
41 |
42 | @Parcelize
43 | @Deprecated(
44 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
45 | replaceWith = ReplaceWith("StripeCardScan"),
46 | )
47 | object JCB : CardIssuer("JCB")
48 |
49 | @Parcelize
50 | @Deprecated(
51 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
52 | replaceWith = ReplaceWith("StripeCardScan"),
53 | )
54 | object MasterCard : CardIssuer("MasterCard")
55 |
56 | @Parcelize
57 | @Deprecated(
58 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
59 | replaceWith = ReplaceWith("StripeCardScan"),
60 | )
61 | object UnionPay : CardIssuer("UnionPay")
62 |
63 | @Parcelize
64 | @Deprecated(
65 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
66 | replaceWith = ReplaceWith("StripeCardScan"),
67 | )
68 | object Unknown : CardIssuer("Unknown")
69 |
70 | @Parcelize
71 | @Deprecated(
72 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
73 | replaceWith = ReplaceWith("StripeCardScan"),
74 | )
75 | object Visa : CardIssuer("Visa")
76 | }
77 |
78 | /**
79 | * Format the card network as a human readable format.
80 | */
81 | @Deprecated(
82 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
83 | replaceWith = ReplaceWith("StripeCardScan"),
84 | )
85 | fun formatIssuer(issuer: CardIssuer): String = issuer.displayName
86 |
--------------------------------------------------------------------------------
/scan-payment/src/main/java/com/getbouncer/scan/payment/card/CardType.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment.card
2 |
3 | import android.content.Context
4 | import androidx.annotation.CheckResult
5 | import androidx.annotation.RawRes
6 | import com.getbouncer.scan.framework.util.cacheFirstResultSuspend
7 | import com.getbouncer.scan.payment.R
8 | import kotlinx.coroutines.Dispatchers
9 | import kotlinx.coroutines.withContext
10 | import java.io.ByteArrayInputStream
11 | import java.io.FileInputStream
12 | import java.nio.ByteBuffer
13 | import java.util.zip.ZipInputStream
14 |
15 | @Deprecated(
16 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
17 | replaceWith = ReplaceWith("StripeCardScan"),
18 | )
19 | sealed class CardType {
20 | object Credit : CardType()
21 | object Debit : CardType()
22 | object Prepaid : CardType()
23 | object Unknown : CardType()
24 | }
25 |
26 | @Deprecated(
27 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
28 | replaceWith = ReplaceWith("StripeCardScan"),
29 | )
30 | val getTypeTable: suspend (Context) -> Map = cacheFirstResultSuspend { context: Context ->
31 | readRawZippedResourceToStringArray(context, R.raw.payment_card_types).map {
32 | val fields = it.split(",")
33 | fields[0].toInt()..fields[1].toInt() to when (fields[2]) {
34 | "CREDIT" -> CardType.Credit
35 | "DEBIT" -> CardType.Debit
36 | "PREPAID" -> CardType.Prepaid
37 | else -> CardType.Unknown
38 | }
39 | }.toMap()
40 | }
41 |
42 | /**
43 | * Read a raw resource into a [ByteBuffer].
44 | */
45 | @CheckResult
46 | private suspend fun readRawZippedResourceToStringArray(context: Context, @RawRes resourceId: Int) =
47 | withContext(Dispatchers.IO) {
48 | val byteArray = context.resources.openRawResourceFd(resourceId).use { fileDescriptor ->
49 | val byteBuffer = ByteBuffer.allocate(fileDescriptor.declaredLength.toInt())
50 | FileInputStream(fileDescriptor.fileDescriptor).use { fileInputStream ->
51 | fileInputStream.channel.read(byteBuffer, fileDescriptor.startOffset)
52 | byteBuffer.rewind()
53 | byteBuffer.array()
54 | }
55 | }
56 |
57 | ByteArrayInputStream(byteArray).use { byteArrayInputStream ->
58 | ZipInputStream(byteArrayInputStream).use { zipInputStream ->
59 | var entry = zipInputStream.nextEntry
60 | while (entry != null && !entry.name.endsWith(".txt")) {
61 | entry = zipInputStream.nextEntry
62 | }
63 | if (entry != null) {
64 | zipInputStream.bufferedReader().readLines()
65 | } else {
66 | emptyList()
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/scan-payment/src/main/java/com/getbouncer/scan/payment/card/PanValidator.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment.card
2 |
3 | /**
4 | * A class that provides a method to determine if a PAN is valid.
5 | */
6 | @Deprecated(
7 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
8 | replaceWith = ReplaceWith("StripeCardScan"),
9 | )
10 | interface PanValidator {
11 | @Deprecated(
12 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
13 | replaceWith = ReplaceWith("StripeCardScan"),
14 | )
15 | fun isValidPan(pan: String): Boolean
16 |
17 | @Deprecated(
18 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
19 | replaceWith = ReplaceWith("StripeCardScan"),
20 | )
21 | operator fun plus(other: PanValidator): PanValidator = CompositePanValidator(this, other)
22 | }
23 |
24 | /**
25 | * A [PanValidator] comprised of two separate validators.
26 | */
27 | private class CompositePanValidator(
28 | private val validator1: PanValidator,
29 | private val validator2: PanValidator
30 | ) : PanValidator {
31 | override fun isValidPan(pan: String): Boolean =
32 | validator1.isValidPan(pan) && validator2.isValidPan(pan)
33 | }
34 |
35 | /**
36 | * A [PanValidator] that ensures the PAN is of a valid length.
37 | */
38 | internal object LengthPanValidator : PanValidator {
39 | override fun isValidPan(pan: String): Boolean {
40 | val iinData = getIssuerData(pan) ?: return false
41 | return pan.length in iinData.panLengths
42 | }
43 | }
44 |
45 | /**
46 | * A [PanValidator] that performs the Luhn check for validation.
47 | *
48 | * see https://en.wikipedia.org/wiki/Luhn_algorithm
49 | */
50 | internal object LuhnPanValidator : PanValidator {
51 | override fun isValidPan(pan: String): Boolean {
52 | if (pan.isEmpty()) {
53 | return false
54 | }
55 |
56 | fun doubleDigit(digit: Int) = if (digit * 2 > 9) digit * 2 - 9 else digit * 2
57 |
58 | var sum = pan.takeLast(1).toInt()
59 | val parity = pan.length % 2
60 |
61 | for (i in 0 until pan.length - 1) {
62 | sum += if (i % 2 == parity) {
63 | doubleDigit(Character.getNumericValue(pan[i]))
64 | } else {
65 | Character.getNumericValue(pan[i])
66 | }
67 | }
68 |
69 | return sum % 10 == 0
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/scan-payment/src/main/java/com/getbouncer/scan/payment/card/PaymentCard.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment.card
2 |
3 | import android.os.Parcelable
4 | import kotlinx.parcelize.Parcelize
5 |
6 | @Parcelize
7 | @Deprecated(
8 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
9 | replaceWith = ReplaceWith("StripeCardScan"),
10 | )
11 | data class PaymentCard(
12 | val pan: String?,
13 | val expiry: PaymentCardExpiry?,
14 | val issuer: CardIssuer?,
15 | val cvc: String?,
16 | val legalName: String?
17 | ) : Parcelable
18 |
19 | @Parcelize
20 | @Deprecated(
21 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
22 | replaceWith = ReplaceWith("StripeCardScan"),
23 | )
24 | data class PaymentCardExpiry(val day: String?, val month: String, val year: String) : Parcelable
25 |
--------------------------------------------------------------------------------
/scan-payment/src/main/java/com/getbouncer/scan/payment/card/RequiresMatchingCard.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment.card
2 |
3 | /**
4 | * An interface for a class that requires a card to match. This provides the methods used to determine if a given card
5 | * matches or does not match the required card.
6 | */
7 | @Deprecated(
8 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
9 | replaceWith = ReplaceWith("StripeCardScan"),
10 | )
11 | interface RequiresMatchingCard {
12 | val requiredIin: String?
13 | val requiredLastFour: String?
14 |
15 | /**
16 | * Returns true if the card matches the [requiredIin] and/or [requiredLastFour], or if the two fields are null.
17 | *
18 | * TODO: use contracts when they are no longer experimental
19 | */
20 | @Deprecated(
21 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
22 | replaceWith = ReplaceWith("StripeCardScan"),
23 | )
24 | fun matchesRequiredCard(pan: String?): Boolean {
25 | // contract { returns(true) implies (pan != null) }
26 | return pan != null && isValidPan(pan) && panMatches(requiredIin, requiredLastFour, pan)
27 | }
28 |
29 | /**
30 | * Returns true if the required card fields are non-null and the given pan does not match.
31 | *
32 | * TODO: use contracts when they are no longer experimental
33 | */
34 | @Deprecated(
35 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
36 | replaceWith = ReplaceWith("StripeCardScan"),
37 | )
38 | fun doesNotMatchRequiredCard(pan: String?): Boolean {
39 | // contract { returns(true) implies (pan != null) }
40 | return pan != null && isValidPan(pan) && !panMatches(requiredIin, requiredLastFour, pan)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/scan-payment/src/main/java/com/getbouncer/scan/payment/ml/AlphabetDetectModelManager.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment.ml
2 |
3 | import android.content.Context
4 | import com.getbouncer.scan.framework.Fetcher
5 | import com.getbouncer.scan.payment.ModelManager
6 |
7 | @Deprecated(
8 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
9 | replaceWith = ReplaceWith("StripeCardScan"),
10 | )
11 | object AlphabetDetectModelManager : ModelManager() {
12 | override fun getModelFetcher(context: Context): Fetcher = AlphabetDetect.ModelFetcher(context)
13 | }
14 |
--------------------------------------------------------------------------------
/scan-payment/src/main/java/com/getbouncer/scan/payment/ml/CardDetectModelManager.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment.ml
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import com.getbouncer.scan.framework.Config
6 | import com.getbouncer.scan.framework.Fetcher
7 | import com.getbouncer.scan.framework.UpdatingModelWebFetcher
8 | import com.getbouncer.scan.framework.UpdatingResourceFetcher
9 | import com.getbouncer.scan.framework.assetFileExists
10 | import com.getbouncer.scan.payment.ModelManager
11 |
12 | private const val CARD_DETECT_ASSET_FULL = "ux_0_5_23_16.tflite"
13 | private const val CARD_DETECT_ASSET_MINIMAL = "UX.0.25.106.8.tflite"
14 |
15 | @Deprecated(
16 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
17 | replaceWith = ReplaceWith("StripeCardScan"),
18 | )
19 | object CardDetectModelManager : ModelManager() {
20 | override fun getModelFetcher(context: Context): Fetcher = when {
21 | assetFileExists(context, CARD_DETECT_ASSET_FULL) -> {
22 | Log.d(Config.logTag, "Full card detect available in assets")
23 | object : UpdatingResourceFetcher(context) {
24 | override val assetFileName: String = CARD_DETECT_ASSET_FULL
25 | override val resourceModelVersion: String = "0.5.23.16"
26 | override val resourceModelHash: String = "ea51ca5c693a4b8733b1cf1a63557a713a13fabf0bcb724385077694e63a51a7"
27 | override val resourceModelHashAlgorithm: String = "SHA-256"
28 | override val modelClass: String = "card_detection"
29 | override val modelFrameworkVersion: Int = 1
30 | }
31 | }
32 | assetFileExists(context, CARD_DETECT_ASSET_MINIMAL) -> {
33 | Log.d(Config.logTag, "Minimal card detect available in assets")
34 | object : UpdatingResourceFetcher(context) {
35 | override val assetFileName: String = CARD_DETECT_ASSET_MINIMAL
36 | override val resourceModelVersion: String = "0.25.106.8"
37 | override val resourceModelHash: String = "c2a39c9034a9f0073933488021676c46910cec0d1bf330ac22a908dcd7dd448a"
38 | override val resourceModelHashAlgorithm: String = "SHA-256"
39 | override val modelClass: String = "card_detection"
40 | override val modelFrameworkVersion: Int = 1
41 | }
42 | }
43 | else -> {
44 | Log.d(Config.logTag, "No card detect available in assets")
45 | object : UpdatingModelWebFetcher(context) {
46 | override val defaultModelFileName: String = "UX.0.5.23.16.tflite"
47 | override val defaultModelVersion: String = "0.5.23.16"
48 | override val defaultModelHash: String = "ea51ca5c693a4b8733b1cf1a63557a713a13fabf0bcb724385077694e63a51a7"
49 | override val defaultModelHashAlgorithm: String = "SHA-256"
50 | override val modelClass: String = "card_detection"
51 | override val modelFrameworkVersion: Int = 1
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/scan-payment/src/main/java/com/getbouncer/scan/payment/ml/ExpiryDetectModelManager.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment.ml
2 |
3 | import android.content.Context
4 | import com.getbouncer.scan.framework.Fetcher
5 | import com.getbouncer.scan.payment.ModelManager
6 |
7 | @Deprecated(
8 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
9 | replaceWith = ReplaceWith("StripeCardScan"),
10 | )
11 | object ExpiryDetectModelManager : ModelManager() {
12 | override fun getModelFetcher(context: Context): Fetcher = ExpiryDetect.ModelFetcher(context)
13 | }
14 |
--------------------------------------------------------------------------------
/scan-payment/src/main/java/com/getbouncer/scan/payment/ml/SSDOcrModelManager.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment.ml
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import com.getbouncer.scan.framework.Config
6 | import com.getbouncer.scan.framework.Fetcher
7 | import com.getbouncer.scan.framework.UpdatingModelWebFetcher
8 | import com.getbouncer.scan.framework.UpdatingResourceFetcher
9 | import com.getbouncer.scan.framework.assetFileExists
10 | import com.getbouncer.scan.payment.ModelManager
11 |
12 | private const val OCR_ASSET_FULL = "darknite_1_1_1_16.tflite"
13 | private const val OCR_ASSET_MINIMAL = "mb2_brex_metal_synthetic_svhnextra_epoch_3_5_98_8.tflite"
14 |
15 | @Deprecated(
16 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
17 | replaceWith = ReplaceWith("StripeCardScan"),
18 | )
19 | object SSDOcrModelManager : ModelManager() {
20 | override fun getModelFetcher(context: Context): Fetcher = when {
21 | assetFileExists(context, OCR_ASSET_FULL) -> {
22 | Log.d(Config.logTag, "Full ocr available in assets")
23 | object : UpdatingResourceFetcher(context) {
24 | override val assetFileName: String = OCR_ASSET_FULL
25 | override val resourceModelVersion: String = "1.1.1.16"
26 | override val resourceModelHash: String = "8d8e3f79aa0783ab0cfa5c8d65d663a9da6ba99401efb2298aaaee387c3b00d6"
27 | override val resourceModelHashAlgorithm: String = "SHA-256"
28 | override val modelClass: String = "ocr"
29 | override val modelFrameworkVersion: Int = 1
30 | }
31 | }
32 | assetFileExists(context, OCR_ASSET_MINIMAL) -> {
33 | Log.d(Config.logTag, "Minimal ocr available in assets")
34 | object : UpdatingResourceFetcher(context) {
35 | override val assetFileName: String = OCR_ASSET_MINIMAL
36 | override val resourceModelVersion: String = "3.5.98.8"
37 | override val resourceModelHash: String = "a4739fa49caa3ff88e7ff1145c9334ee4cbf64354e91131d02d98d7bfd4c35cf"
38 | override val resourceModelHashAlgorithm: String = "SHA-256"
39 | override val modelClass: String = "ocr"
40 | override val modelFrameworkVersion: Int = 1
41 | }
42 | }
43 | else -> {
44 | Log.d(Config.logTag, "No ocr available in assets")
45 | object : UpdatingModelWebFetcher(context) {
46 | override val defaultModelFileName: String = "darknite_1_1_1_16.tflite"
47 | override val defaultModelVersion: String = "1.1.1.16"
48 | override val defaultModelHash: String = "8d8e3f79aa0783ab0cfa5c8d65d663a9da6ba99401efb2298aaaee387c3b00d6"
49 | override val defaultModelHashAlgorithm: String = "SHA-256"
50 | override val modelClass: String = "ocr"
51 | override val modelFrameworkVersion: Int = 1
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/scan-payment/src/main/java/com/getbouncer/scan/payment/ml/ssd/DetectionBox.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment.ml.ssd
2 |
3 | import android.graphics.RectF
4 | import androidx.annotation.RestrictTo
5 |
6 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
7 | @Deprecated(
8 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
9 | replaceWith = ReplaceWith("StripeCardScan"),
10 | )
11 | data class DetectionBox(
12 |
13 | /**
14 | * The rectangle percentage of the original image.
15 | */
16 | val rect: RectF,
17 |
18 | /**
19 | * Confidence value that the label applies to the rectangle.
20 | */
21 | val confidence: Float,
22 |
23 | /**
24 | * The label for this box.
25 | */
26 | val label: Int
27 | )
28 |
--------------------------------------------------------------------------------
/scan-payment/src/main/java/com/getbouncer/scan/payment/ml/ssd/OcrPriorsGenerator.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment.ml.ssd
2 |
3 | import android.util.Size
4 | import com.getbouncer.scan.framework.ml.ssd.SizeAndCenter
5 | import com.getbouncer.scan.framework.ml.ssd.clampAll
6 | import com.getbouncer.scan.framework.ml.ssd.sizeAndCenter
7 | import kotlin.math.sqrt
8 |
9 | private const val NUMBER_OF_PRIORS = 3
10 |
11 | @Deprecated(
12 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
13 | replaceWith = ReplaceWith("StripeCardScan"),
14 | )
15 | fun combinePriors(trainedImageSize: Size): Array {
16 | val priorsOne: Array =
17 | generatePriors(
18 | trainedImageSize = trainedImageSize,
19 | featureMapSize = Size(38, 24),
20 | shrinkage = Size(16, 16),
21 | boxSizeMin = 14F,
22 | boxSizeMax = 30F,
23 | aspectRatio = 3F
24 | )
25 |
26 | val priorsTwo: Array =
27 | generatePriors(
28 | trainedImageSize = trainedImageSize,
29 | featureMapSize = Size(19, 12),
30 | shrinkage = Size(31, 31),
31 | boxSizeMin = 30F,
32 | boxSizeMax = 45F,
33 | aspectRatio = 3F
34 | )
35 |
36 | return (priorsOne + priorsTwo).apply { forEach { it.clampAll(0F, 1F) } }
37 | }
38 |
39 | private fun generatePriors(
40 | trainedImageSize: Size,
41 | featureMapSize: Size,
42 | shrinkage: Size,
43 | boxSizeMin: Float,
44 | boxSizeMax: Float,
45 | aspectRatio: Float
46 | ): Array {
47 | val scaleWidth = trainedImageSize.width.toFloat() / shrinkage.width
48 | val scaleHeight = trainedImageSize.height.toFloat() / shrinkage.height
49 | val ratio = sqrt(aspectRatio)
50 |
51 | fun generatePrior(column: Int, row: Int, sizeFactor: Float, ratio: Float) =
52 | sizeAndCenter(
53 | centerX = (column + 0.5F) / scaleWidth,
54 | centerY = (row + 0.5F) / scaleHeight,
55 | width = sizeFactor / trainedImageSize.width,
56 | height = sizeFactor / trainedImageSize.height * ratio
57 | )
58 |
59 | return Array(featureMapSize.width * featureMapSize.height * NUMBER_OF_PRIORS) { index ->
60 | val row = index / NUMBER_OF_PRIORS / featureMapSize.width
61 | val column = (index / NUMBER_OF_PRIORS) % featureMapSize.width
62 | when (index % NUMBER_OF_PRIORS) {
63 | 0 -> generatePrior(column, row, boxSizeMin, 1F)
64 | 1 -> generatePrior(column, row, sqrt(boxSizeMax * boxSizeMin), ratio)
65 | else -> generatePrior(column, row, boxSizeMin, ratio)
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/scan-payment/src/main/java/com/getbouncer/scan/payment/ml/yolo/Yolo.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.payment.ml.yolo
2 |
3 | import android.graphics.RectF
4 | import android.util.Size
5 | import com.getbouncer.scan.framework.ml.ssd.softMax
6 | import com.getbouncer.scan.framework.util.indexOfMax
7 | import com.getbouncer.scan.payment.ml.ssd.DetectionBox
8 | import kotlin.math.exp
9 |
10 | @Deprecated(
11 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
12 | replaceWith = ReplaceWith("StripeCardScan"),
13 | )
14 | fun sigmoid(x: Float): Float = (1.0f / (1.0f + exp((-x))))
15 |
16 | /**
17 | * Takes a layer from the raw YOLO model output and performs post-processing on it,
18 | * returning a List
19 | */
20 | @Deprecated(
21 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
22 | replaceWith = ReplaceWith("StripeCardScan"),
23 | )
24 | fun processYoloLayer(
25 | layer: Array>,
26 | anchors: Array>,
27 | imageSize: Size,
28 | numClasses: Int,
29 | confidenceThreshold: Float
30 | ): List {
31 | val results = mutableListOf()
32 | for (i in layer.indices) for (j in layer[i].indices) for (k in 0..2) {
33 | val offset = (numClasses + 5) * k
34 | var confidence = sigmoid(layer[i][j][offset + 4])
35 | val confidenceClasses = (0 until numClasses).map { layer[i][j][offset + 5 + it] }.toFloatArray()
36 | confidenceClasses.softMax()
37 |
38 | val objectId = confidenceClasses.indexOfMax() ?: continue
39 | val maxClass = confidenceClasses[objectId]
40 | confidence *= maxClass
41 |
42 | if (confidence > confidenceThreshold) {
43 | val x = (j + sigmoid(layer[i][j][offset])) / layer.size
44 | val y = (i + sigmoid(layer[i][j][offset + 1])) / layer.size
45 | val w =
46 | exp(layer[i][j][offset + 2]) * anchors[k].first / imageSize.width
47 | val h =
48 | exp(layer[i][j][offset + 3]) * anchors[k].second / imageSize.height
49 | val r = RectF(
50 | x - w / 2,
51 | y - h / 2,
52 | x + w / 2,
53 | y + h / 2
54 | )
55 | results.add(
56 | DetectionBox(
57 | rect = r,
58 | confidence = confidence,
59 | label = objectId
60 | )
61 | )
62 | }
63 | }
64 | return results
65 | }
66 |
--------------------------------------------------------------------------------
/scan-payment/src/main/res/raw/payment_card_types.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-payment/src/main/res/raw/payment_card_types.bin
--------------------------------------------------------------------------------
/scan-ui/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/scan-ui/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-parcelize'
4 |
5 | android {
6 | compileSdkVersion 30
7 | buildToolsVersion '30.0.3'
8 |
9 | defaultConfig {
10 | minSdkVersion 21
11 | targetSdkVersion 30
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles 'consumer-rules.pro'
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 |
24 | testOptions {
25 | unitTests.includeAndroidResources = true
26 | }
27 |
28 | lintOptions {
29 | enable "Interoperability"
30 | }
31 |
32 | compileOptions {
33 | sourceCompatibility JavaVersion.VERSION_1_8
34 | targetCompatibility JavaVersion.VERSION_1_8
35 | }
36 | }
37 |
38 | dependencies {
39 | implementation fileTree(dir: 'libs', include: ['*.jar'])
40 | implementation project(":scan-framework")
41 | implementation project(':scan-camera')
42 |
43 | implementation "androidx.appcompat:appcompat:[1.3.0,1.3.1]"
44 | implementation "androidx.constraintlayout:constraintlayout:[2.0.4,2.1.0]"
45 | implementation "androidx.core:core-ktx:[1.3.1,1.6.0]"
46 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:[1.4.0,1.5.1]"
47 | }
48 |
49 | dependencies {
50 | testImplementation "androidx.test:core:1.4.0"
51 | testImplementation "androidx.test:runner:1.4.0"
52 | testImplementation "junit:junit:4.13.2"
53 | testImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
54 | }
55 |
56 | dependencies {
57 | androidTestImplementation "androidx.test.ext:junit:1.1.3"
58 | androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
59 | androidTestImplementation "org.jetbrains.kotlin:kotlin-test:1.5.30"
60 | }
61 |
62 | apply from: 'deploy.gradle'
63 |
--------------------------------------------------------------------------------
/scan-ui/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/scan-ui/consumer-rules.pro
--------------------------------------------------------------------------------
/scan-ui/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/scan-ui/src/androidTest/java/com/getbouncer/scan/ui/DebugOverlayTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.ui
2 |
3 | import android.graphics.RectF
4 | import android.util.Size
5 | import androidx.test.filters.SmallTest
6 | import org.junit.Test
7 | import kotlin.test.assertEquals
8 |
9 | class DebugOverlayTest {
10 |
11 | @Test
12 | @SmallTest
13 | fun scaleRect() {
14 | assertEquals(RectF(2F, 4F, 6F, 8F), RectF(0.05F, 0.10F, 0.15F, 0.20F).scaled(Size(40, 40)))
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/scan-ui/src/androidTest/java/com/getbouncer/scan/ui/util/ViewExtensionsTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.ui.util
2 |
3 | import androidx.test.filters.SmallTest
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import com.getbouncer.scan.ui.test.R
6 | import org.junit.Test
7 | import kotlin.test.assertEquals
8 |
9 | class ViewExtensionsTest {
10 | private val testContext = InstrumentationRegistry.getInstrumentation().context
11 |
12 | @Test
13 | @SmallTest
14 | fun getColorByRes_matches() {
15 | val color = testContext.getColorByRes(R.color.testColor)
16 | val alpha = color shr 24 and 0xFF
17 | val red = color shr 16 and 0xFF
18 | val green = color shr 8 and 0xFF
19 | val blue = color and 0xFF
20 |
21 | assertEquals(0xA1, alpha)
22 | assertEquals(0x1E, red)
23 | assertEquals(0x90, green)
24 | assertEquals(0xFF, blue)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/scan-ui/src/androidTest/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #A11E90FF
4 |
5 |
--------------------------------------------------------------------------------
/scan-ui/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/scan-ui/src/main/java/com/getbouncer/scan/ui/ScanFlow.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.ui
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.graphics.Rect
6 | import androidx.lifecycle.LifecycleOwner
7 | import com.getbouncer.scan.camera.CameraPreviewImage
8 | import kotlinx.coroutines.CoroutineScope
9 | import kotlinx.coroutines.flow.Flow
10 |
11 | /**
12 | * A flow for scanning something. This manages the callbacks and lifecycle of the flow.
13 | */
14 | @Deprecated(
15 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
16 | replaceWith = ReplaceWith("StripeCardScan"),
17 | )
18 | interface ScanFlow {
19 |
20 | /**
21 | * Start the image processing flow for scanning a card.
22 | *
23 | * @param context: The context used to download analyzers if needed
24 | * @param imageStream: The flow of images to process
25 | * @param viewFinder: The location of the view finder in the previewSize
26 | * @param lifecycleOwner: The activity that owns this flow. The flow will pause if the activity
27 | * is paused
28 | * @param coroutineScope: The coroutine scope used to run async tasks for this flow
29 | */
30 | fun startFlow(
31 | context: Context,
32 | imageStream: Flow>,
33 | viewFinder: Rect,
34 | lifecycleOwner: LifecycleOwner,
35 | coroutineScope: CoroutineScope
36 | )
37 |
38 | /**
39 | * In the event that the scan cannot complete, halt the flow to halt analyzers and free up CPU and memory.
40 | */
41 | fun cancelFlow()
42 | }
43 |
--------------------------------------------------------------------------------
/scan-ui/src/main/java/com/getbouncer/scan/ui/ViewFinderBackground.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.ui
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.Paint
6 | import android.graphics.PorterDuff
7 | import android.graphics.PorterDuffXfermode
8 | import android.graphics.Rect
9 | import android.os.Build
10 | import android.util.AttributeSet
11 | import android.view.View
12 | import androidx.annotation.ColorInt
13 | import kotlin.math.roundToInt
14 |
15 | /**
16 | * This class draws a background with a hole in the middle of it.
17 | */
18 | @Deprecated(
19 | message = "Replaced by stripe card scan. See https://github.com/stripe/stripe-android/tree/master/stripecardscan",
20 | replaceWith = ReplaceWith("StripeCardScan"),
21 | )
22 | class ViewFinderBackground(context: Context, attrs: AttributeSet? = null) : View(context, attrs) {
23 | private var viewFinderRect: Rect? = null
24 | private var onDrawListener: (() -> Unit)? = null
25 |
26 | fun setViewFinderRect(viewFinderRect: Rect) {
27 | this.viewFinderRect = viewFinderRect
28 | requestLayout()
29 | }
30 |
31 | fun clearViewFinderRect() {
32 | this.viewFinderRect = null
33 | }
34 |
35 | override fun setBackgroundColor(@ColorInt color: Int) {
36 | paintBackground.color = color
37 | requestLayout()
38 | }
39 |
40 | fun getBackgroundLuminance(): Int {
41 | val color = paintBackground.color
42 | val r = (color shr 16 and 0xff) / 255F
43 | val g = (color shr 8 and 0xff) / 255F
44 | val b = (color and 0xff) / 255F
45 |
46 | return ((0.2126F * r + 0.7152F * g + 0.0722F * b) * 255F).roundToInt()
47 | }
48 |
49 | fun setOnDrawListener(onDrawListener: () -> Unit) {
50 | this.onDrawListener = onDrawListener
51 | }
52 |
53 | fun clearOnDrawListener() {
54 | this.onDrawListener = null
55 | }
56 |
57 | private val theme = context.theme
58 | private val attributes = theme.obtainStyledAttributes(attrs, R.styleable.ViewFinderBackground, 0, 0)
59 | private val backgroundColor =
60 | attributes.getColor(
61 | R.styleable.ViewFinderBackground_backgroundColor,
62 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
63 | resources.getColor(R.color.bouncerNotFoundBackground, theme)
64 | } else {
65 | @Suppress("deprecation")
66 | resources.getColor(R.color.bouncerNotFoundBackground)
67 | }
68 | )
69 |
70 | private var paintBackground = Paint(Paint.ANTI_ALIAS_FLAG).apply {
71 | color = backgroundColor
72 | style = Paint.Style.FILL
73 | }
74 |
75 | private val paintWindow = Paint(Paint.ANTI_ALIAS_FLAG).apply {
76 | xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
77 | style = Paint.Style.FILL
78 | }
79 |
80 | init {
81 | setLayerType(LAYER_TYPE_HARDWARE, null)
82 | }
83 |
84 | override fun onDraw(canvas: Canvas) {
85 | super.onDraw(canvas)
86 | canvas.drawPaint(paintBackground)
87 |
88 | val viewFinderRect = this.viewFinderRect
89 | if (viewFinderRect != null) {
90 | canvas.drawRect(viewFinderRect, paintWindow)
91 | }
92 |
93 | val onDrawListener = this.onDrawListener
94 | if (onDrawListener != null) {
95 | onDrawListener()
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_camera_swap_dark.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_camera_swap_light.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_card_background_correct.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
30 |
31 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_card_background_found.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
30 |
31 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_card_background_not_found.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
30 |
31 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_card_background_wrong.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
30 |
31 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_card_border_correct.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
11 |
26 |
27 |
28 |
29 |
30 |
31 |
38 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_card_border_found.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
24 |
25 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_card_border_found_long.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
11 |
28 |
29 |
30 |
31 |
32 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_card_border_not_found.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
24 |
25 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_card_border_wrong.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
11 |
26 |
27 |
28 |
29 |
30 |
31 |
38 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_close_button_dark.xml:
--------------------------------------------------------------------------------
1 |
6 |
15 |
16 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_close_button_light.xml:
--------------------------------------------------------------------------------
1 |
6 |
15 |
16 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_flash_off_dark.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
18 |
19 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_flash_off_light.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
18 |
19 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_flash_on_dark.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
19 |
20 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_flash_on_light.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
19 |
20 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_lock_dark.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
31 |
32 |
41 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/drawable/bouncer_lock_light.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
31 |
32 |
41 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Scan Your Card
4 | Your card info is secure
5 | Card doesn\'t match
6 |
7 | Please allow camera access to scan your card
8 | OK
9 | Cancel
10 |
11 | Camera Problem
12 | The camera failed to turn on
13 | Permission was denied when turning on the camera
14 | This device does not support the required camera features
15 | Close
16 |
17 | Network Problem
18 | Sorry, this API key is not valid.
19 | OK
20 |
21 | cardscan.io logo
22 | lock icon
23 | card scanning window
24 | card debug window
25 | Close
26 | Torch
27 | Swap Camera
28 | card view finder
29 |
30 |
--------------------------------------------------------------------------------
/scan-ui/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/scan-ui/src/test/java/com/getbouncer/scan/ui/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.getbouncer.scan.ui
2 |
3 | import org.junit.Test
4 | import kotlin.test.assertEquals
5 |
6 | class ExampleUnitTest {
7 |
8 | @Test
9 | fun addition_isCorrect() {
10 | assertEquals(4, 2 + 2)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name='cardscan-android'
2 | include ':cardscan-demo'
3 | include ':cardscan-ui'
4 | include ':scan-camera'
5 | include ':scan-camera2'
6 | include ':scan-camerax'
7 | include ':scan-framework'
8 | include ':scan-payment'
9 | include ':scan-payment-full'
10 | include ':scan-payment-minimal'
11 | include ':scan-ui'
12 | include ':tensorflow-lite'
13 | include ':tensorflow-lite-arm-only'
14 |
--------------------------------------------------------------------------------
/tensorflow-lite-arm-only/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *.asc
3 |
--------------------------------------------------------------------------------
/tensorflow-lite-arm-only/build.gradle:
--------------------------------------------------------------------------------
1 | configurations.maybeCreate("default")
2 | artifacts.add("default", file('tensorflow-lite-all-models-arm-only.aar'))
3 |
4 | apply from: "deploy.gradle"
5 |
--------------------------------------------------------------------------------
/tensorflow-lite-arm-only/tensorflow-lite-all-models-arm-only.aar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/tensorflow-lite-arm-only/tensorflow-lite-all-models-arm-only.aar
--------------------------------------------------------------------------------
/tensorflow-lite/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *.asc
3 |
--------------------------------------------------------------------------------
/tensorflow-lite/build.gradle:
--------------------------------------------------------------------------------
1 | configurations.maybeCreate("default")
2 | artifacts.add("default", file('tensorflow-lite-all-models.aar'))
3 |
4 | apply from: "deploy.gradle"
5 |
--------------------------------------------------------------------------------
/tensorflow-lite/tensorflow-lite-all-models.aar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbouncer/cardscan-android/bf490afe479ec5d2b4c3276e74350fa26e82daa8/tensorflow-lite/tensorflow-lite-all-models.aar
--------------------------------------------------------------------------------