Compare commits

..

2 commits

Author SHA1 Message Date
ligi
bb72a8d889 Use zxing 3.3.0 2016-10-10 13:30:02 +02:00
ligi
2e1479ca0b Try zxing 3.3.0 2016-10-10 13:30:02 +02:00
329 changed files with 7175 additions and 8633 deletions

View file

@ -1,22 +0,0 @@
{
"type": "android",
"stages": [
{
"name": "testWithMapsWithAnalyticsForPlayDebugComposer",
"needsEmulator": true
},
{
"name": "lint",
"needsEmulator": false
},
{
"name": "test",
"needsEmulator": false
},
{
"name": "assembleRelease",
"needsEmulator": false
}
]
}

12
.github/FUNDING.yml vendored
View file

@ -1,12 +0,0 @@
# These are supported funding model platforms
github: ligi
patreon: ligi
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View file

@ -2,6 +2,7 @@
Great that you are thinking about submitting a PullRequest!
Please keep them small if not otherwise possible. Contact me *before* you are creating a big PR to avoid unnecessary work and rebasing of big PRs. Also try to add tests - I am not dogmatic about that but prefer PRs backed by tests. This project has everything setup for Espresso UI and Unit-tests. Also the existing unit and UI-tests muss pass before submitting a PullRequest.
Please base the PullRequests on the branch named dev if one currently exists - otherwise use master
Please base the PullRequests on the branch named dev

3
.gitignore vendored
View file

@ -5,6 +5,7 @@ build
bin
gen
project.properties
gradle.properties
local.properties
*iml
/build.gradle

12
Jenkinsfile vendored
View file

@ -1,4 +1,3 @@
node {
def flavorCombination='WithMapsWithAnalyticsForPlay'
@ -12,8 +11,8 @@ node {
} catch(err) {
currentBuild.result = FAILURE
} finally {
publishHTML(target:[allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "android/build/spoon", reportFiles: '*/debug/index.html', reportName: 'Spoon'])
step([$class: 'JUnitResultArchiver', testResults: 'android/build/spoon/*/debug/junit-reports/*.xml'])
publishHTML(target:[allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "android/build/spoon-output/${flavorCombination}DebugAndroidTest", reportFiles: 'index.html', reportName: 'Spoon'])
step([$class: 'JUnitResultArchiver', testResults: 'android/build/spoon-output/*/junit-reports/*.xml'])
}
}
@ -23,8 +22,9 @@ node {
} catch(err) {
currentBuild.result = FAILURE
} finally {
publishHTML(target:[allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'android/build/outputs/', reportFiles: "lint-results-*Release.html", reportName: 'Lint'])
androidLint canComputeNew: false, defaultEncoding: '', healthy: '', pattern: '', unHealthy: ''
}
}
stage 'test'
try {
@ -32,8 +32,8 @@ node {
} catch(err) {
currentBuild.result = FAILURE
} finally {
step([$class: 'JUnitResultArchiver', testResults: 'android/build/test-results/*/*.xml'])
publishHTML(target:[allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'android/build/reports/tests/', reportFiles: "*/index.html", reportName: 'UnitTest'])
step([$class: 'JUnitResultArchiver', testResults: 'android/build/test-results/*/*/*.xml'])
publishHTML(target:[allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'android/build/reports/tests/', reportFiles: "*/*/index.html", reportName: 'UnitTest'])
}
stage 'assemble'

View file

@ -1,24 +1,15 @@
[![on Google Play](https://ligi.de/img/play_badge.png)](https://play.google.com/store/apps/details?id=org.ligi.passandroid)
[![on FDroid](https://ligi.de/img/fdroid_badge.png)](https://f-droid.org/repository/browse/?fdid=org.ligi.passandroid)
[![on Amazon](https://ligi.de/img/amazon_badge.png)](https://www.amazon.com/ligi-Passandroid/dp/B01LX9DMSQ)
[![Android app on Google Play](http://ligi.de/img/play_badge.png)](https://play.google.com/store/apps/details?id=org.ligi.passandroid)
[![Android app on FDroid](http://ligi.de/img/fdroid_badge.png)](https://f-droid.org/repository/browse/?fdid=org.ligi.passandroid)
# PassAndroid
PassAndroid
===========
Android App to view Passes (e.g. event tickets, coupons, loyalty cards, boarding passes, ...)
Android App to view Passes ( e.g. event tickets, coupons, loyalty cards, boarding passes, .. )
![Screenshots](https://ligi.de/img/passandroid_screenshots.png)
<img src="http://ligi.de/img/passandroid_screenshots.png"/>
Displays [esPass](https://espass.it) (`*.esPass`) & Passbook (`*.pkpass`) files, shows the Barcode (QR, PDF417, AZTEC, Code 39 and Code 128 format) and is also usable offline.
When preparing for the Chaos Communication Congress 2012 #29c3 I stumbled upon a passbook file for the first time.
I really like the idea of paperless tickets as it saves time and trees which are both very valuable to me.
The problem was that I was unable to find an app to open and use the downloaded passbook files with, that's why I wrote my own one.
## Legal
This project is licensed under the [GNU General Public License v3.0](COPYING).
We are not affiliated with Apple - Passbook might be trademarked by Apple, but it's introduced like a standard so this should be okay.
Displays [esPass (*.esPass)](http://espass.it) and Passbook ( *.pkpass ) files & shows the Barcode ( QR, PDF417, AZTEC, Code 39 and Code 128 format ). It useable offline.
When preparing for the Chaos Communication Congress 2012 ( #29c3 ) I stumbled upon a passbook file for the first time. As I really like the idea of paperless tickets as it saves time and trees which both are very valuable to me. The problem was that I found no app with which I could use the downloaded passbook file.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
@ -26,3 +17,7 @@ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Status
------
[![Build Status](https://snap-ci.com/ligi/PassAndroid/branch/master/build_image)](https://snap-ci.com/ligi/PassAndroid/branch/master)

View file

@ -1,29 +1,56 @@
buildscript {
ext {
support_version = '24.2.1'
dagger_version = '2.7'
kotlin_version = '1.0.4'
butterknife_version = '8.4.0'
play_version = '9.6.1'
}
repositories {
jcenter()
maven { url 'https://jitpack.io' }
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools.build:gradle:2.2.1'
classpath 'de.felixschulze.gradle:gradle-spoon-plugin:2.7.3'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
classpath 'de.mobilej.unmock:UnMockPlugin:0.5.0'
// http://stackoverflow.com/a/33889117/322642
classpath 'com.github.JakeWharton:sdk-manager-plugin:220bf7a88a7072df3ed16dc8466fb144f2817070'
}
}
apply plugin: 'android-sdk-manager'
apply plugin: 'com.android.application'
apply plugin: 'com.trevjonez.composer'
apply plugin: 'de.felixschulze.gradle.spoon'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'de.mobilej.unmock'
apply plugin: 'com.github.triplet.play'
apply plugin: 'com.github.ben-manes.versions'
repositories {
jcenter()
mavenLocal()
google()
maven { url 'https://www.jitpack.io' }
maven { url 'https://jitpack.io' }
}
android {
compileSdkVersion 29
compileSdkVersion 24
buildToolsVersion "24.0.2"
defaultConfig {
versionCode 356
versionName "3.5.6"
minSdkVersion 14
targetSdkVersion 29
versionCode 326
versionName "3.2.6"
minSdkVersion 9
targetSdkVersion 24
applicationId "org.ligi.passandroid"
testInstrumentationRunner "org.ligi.passandroid.AppReplacingRunner"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
archivesBaseName = "PassAndroid-$versionName"
vectorDrawables.useSupportLibrary = true
}
@ -55,22 +82,6 @@ android {
forPlay {
dimension "distribution"
}
forAmazon {
dimension "distribution"
}
}
android.variantFilter { variant ->
def maps = variant.getFlavors().get(0).name
def analytics = variant.getFlavors().get(1).name
def distribution = variant.getFlavors().get(2).name
variant.setIgnore((project.hasProperty("singleFlavor") && (distribution != 'forPlay'))
|| ((distribution == 'forAmazon' || distribution == 'forPlay') && analytics == 'noAnalytics')
|| ((distribution == 'forAmazon' || distribution == 'forPlay') && maps == 'noMaps')
|| (distribution == 'forFDroid' && analytics == 'withAnalytics')
|| (distribution == 'forFDroid' && maps == 'withMaps'))
}
packagingOptions {
@ -84,11 +95,15 @@ android {
exclude 'META-INF/maven/com.google.guava/guava/pom.properties'
exclude 'META-INF/maven/com.google.guava/guava/pom.xml'
}
lintOptions {
warning 'MissingTranslation'
warning 'InvalidPackage'
// TODO: remove when this is solved: http://stackoverflow.com/questions/39714487/kapt2-and-butterknife-produce-lint-error-expected-resource-of-type-id
warning 'ResourceType'
}
buildTypes {
@ -97,99 +112,98 @@ android {
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
}
debug {
multiDexEnabled true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'org.permissionsdispatcher:permissionsdispatcher:4.6.0'
kapt 'org.permissionsdispatcher:permissionsdispatcher-processor:4.6.0'
kapt "com.google.dagger:dagger-compiler:$dagger_version"
kapt "com.jakewharton:butterknife-compiler:$butterknife_version"
implementation "org.koin:koin-android:2.1.2"
provided 'org.glassfish:javax.annotation:10.0-b28'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
kaptAndroidTest "com.google.dagger:dagger-compiler:$dagger_version"
androidTestCompile "com.android.support:support-annotations:$support_version"
androidTestImplementation 'com.github.ligi:trulesk:0.31'
androidTestUtil 'com.linkedin.testbutler:test-butler-app:2.1.0'
androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.2') {
// http://stackoverflow.com/questions/30578243/why-would-adding-espresso-contrib-cause-an-inflateexception
exclude group: 'com.android.support', module: 'appcompat'
exclude group: 'com.android.support', module: 'support-v4'
exclude group: 'javax.inject'
exclude module: 'recyclerview-v7'
}
androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
androidTestImplementation 'com.squareup.assertj:assertj-android:1.2.0'
androidTestImplementation "org.mockito:mockito-core:$mockito_version"
androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.25.1'
androidTestImplementation 'com.google.code.findbugs:jsr305:3.0.2'
androidTestImplementation 'org.threeten:threetenbp:1.4.1'
androidTestImplementation 'com.android.support:multidex:1.0.3'
androidTestCompile('com.android.support.test.espresso:espresso-web:2.2.2') {
// http://stackoverflow.com/questions/30578243/why-would-adding-espresso-contrib-cause-an-inflateexception
exclude group: 'javax.inject'
}
androidTestCompile 'com.squareup.spoon:spoon-client:1.7.0'
androidTestCompile 'com.squareup.assertj:assertj-android:1.1.1'
implementation 'com.github.ligi:TouchImageView:2.1'
implementation 'com.github.ligi:ExtraCompats:1.0'
implementation 'net.lingala.zip4j:zip4j:2.3.2'
implementation 'com.jakewharton.threetenabp:threetenabp:1.2.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
androidTestCompile 'org.mockito:mockito-core:1.9.5'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'net.i2p.android.ext:floatingactionbutton:1.10.1'
androidTestCompile 'com.jraska:falcon-spoon-compat:1.0.3'
implementation 'com.github.ligi:KAXT:1.0'
implementation 'com.github.ligi:KAXTUI:1.0'
implementation 'com.github.ligi:loadtoast:1.10.11'
implementation 'com.github.ligi:tracedroid:3.0'
compile 'net.lingala.zip4j:zip4j:1.3.2'
compile "com.jakewharton:butterknife:$butterknife_version"
compile 'com.jakewharton.threetenabp:threetenabp:1.0.4'
compile 'org.greenrobot:eventbus:3.0.0'
forPlayImplementation 'com.github.ligi.snackengage:snackengage-playrate:0.24'
forFDroidImplementation 'com.github.ligi.snackengage:snackengage-playrate:0.24'
forAmazonImplementation 'com.github.ligi.snackengage:snackengage-amazonrate:0.24'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-runtime:$kotlin_version"
// https://medium.com/square-corner-blog/okhttp-3-13-requires-android-5-818bb78d07ce
// Don't update to >=3.13 before minSDK 21 + Java 8
//noinspection GradleDependency
implementation 'com.squareup.okhttp3:okhttp:3.12.1'
compile "com.android.support:support-annotations:$support_version"
compile "com.android.support:recyclerview-v7:$support_version"
compile "com.android.support:appcompat-v7:$support_version"
compile "com.android.support:cardview-v7:$support_version"
compile "com.android.support:design:$support_version"
compile "com.android.support:preference-v7:$support_version"
implementation 'com.larswerkman:HoloColorPicker:1.5'
implementation 'com.google.code.findbugs:jsr305:3.0.2'
androidTestCompile "com.android.support:appcompat-v7:$support_version"
androidTestCompile "com.android.support:design:$support_version"
implementation 'com.squareup.okio:okio:2.2.2'
implementation 'com.squareup.moshi:moshi:1.9.2'
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.9.2")
compile 'net.i2p.android.ext:floatingactionbutton:1.10.1'
implementation 'com.chibatching.kotpref:kotpref:2.10.0'
implementation 'com.chibatching.kotpref:initializer:2.10.0'
testImplementation 'androidx.annotation:annotation:1.1.0'
testImplementation 'com.squareup.assertj:assertj-android:1.2.0'
testImplementation 'junit:junit:4.12'
testImplementation "org.mockito:mockito-core:$mockito_version"
testImplementation 'org.threeten:threetenbp:1.4.1'
compile 'org.ligi:AXT:0.37'
compile 'org.ligi:tracedroid:1.4'
compile 'com.github.ligi:snackengage:0.8'
compile 'com.squareup.okhttp3:okhttp:3.4.1'
compile 'com.larswerkman:HoloColorPicker:1.5'
compile 'com.google.code.findbugs:jsr305:3.0.1'
compile 'com.squareup.moshi:moshi:1.2.0'
compile 'com.chibatching:kotpref:1.4.0'
// https://github.com/ligi/PassAndroid/issues/181
// Don't upgrade before minSDK 19 - or replace zxing
//noinspection GradleDependency
implementation 'com.google.zxing:core:3.3.0'
compile 'net.steamcrafted:load-toast:1.0.10'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
compile "com.google.dagger:dagger:$dagger_version"
// requires minSDK 16 according to docs, not sure if it causes an issue
withAnalyticsImplementation 'com.google.android.gms:play-services-analytics:17.0.0'
withMapsImplementation 'com.google.android.gms:play-services-maps:17.0.0'
androidTestCompile 'com.google.code.findbugs:jsr305:3.0.1'
testCompile "com.android.support:support-annotations:$support_version"
testCompile 'com.squareup.assertj:assertj-android:1.1.1'
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.9.5'
testCompile 'org.threeten:threetenbp:1.3.2'
// cannot use this new version: https://github.com/zxing/zxing/issues/165
// WARNING: might work on some devices or the emulator - but fails on others
compile 'com.google.zxing:core:3.3.0'
//compile fileTree(dir: 'libs', include: 'zxing-core-2.3.0-SNAPSHOT.jar')
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4'
withAnalyticsCompile "com.google.android.gms:play-services-analytics:$play_version"
withMapsCompile "com.google.android.gms:play-services-maps:$play_version"
compile 'com.github.hotchemi:permissionsdispatcher:2.2.0'
kapt 'com.github.hotchemi:permissionsdispatcher-processor:2.2.0'
}
play {
jsonFile = file('/media/ligi/USBCRED/play.json')
uploadImages = true
}
spoon {
debug = true
}

Binary file not shown.

1
android/lombok.config Normal file
View file

@ -0,0 +1 @@
lombok.addGeneratedAnnotation = false

View file

@ -20,24 +20,31 @@
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
# optimize
-optimizationpasses 2
-optimizations !code/simplification/arithmetic
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-optimizationpasses 2
-optimizations !code/simplification/arithmetic
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
# Keep line numbers to alleviate debugging stack traces
# AppCompat
-renamesourcefileattribute SourceFile
-dontwarn android.support.v7.**
-keep class android.support.v7.** { *; }
-keep interface android.support.v7.** { *; }
# Keep line numbers to alleviate debugging stack traces
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
### for api client
-keepattributes Signature,RuntimeVisibleAnnotations,AnnotationDefault
-keepclassmembers class * {
@ -47,6 +54,7 @@
# Needed by Guava
# See https://groups.google.com/forum/#!topic/guava-discuss/YCZzeCiIVoI
-dontwarn sun.misc.Unsafe
-dontwarn com.google.common.collect.MinMaxPriorityQueue
@ -64,6 +72,9 @@
-keep class **$$ViewBinder { *; }
-keepnames class * { @butterknife.Bind *;}
#### for support 22
-dontwarn android.support.**
#### for guava
-dontwarn javax.annotation.**
-dontwarn javax.inject.**
@ -94,20 +105,23 @@
-dontwarn java.nio.file.OpenOption
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-keep public class com.google.android.gms.**
-dontwarn com.google.android.gms.**
## New rules for EventBus 3.0.x ##
# http://greenrobot.org/eventbus/documentation/proguard/
-keepattributes *Annotation*
-keepclassmembers class * {
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
}
### for moshi
@ -139,17 +153,3 @@
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
}
## okhttp
# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*
# OkHttp platform used only on JVM and when Conscrypt dependency is available.
-dontwarn okhttp3.internal.platform.ConscryptPlatform

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest
xmlns:tools="http://schemas.android.com/tools"
package="org.ligi.passandroid">
<uses-sdk tools:overrideLibrary="com.jraska.falcon.spooncompat , com.jraska.falcon"/>
</manifest>

View file

@ -15,7 +15,7 @@
"message":"",
"transfer":"barcode_QR",
"alternative": {
"order":1
"order":1,
}
},

View file

@ -1,9 +0,0 @@
package org.ligi.passandroid
import org.ligi.trulesk.AppReplacingRunnerBase
class AppReplacingRunner : AppReplacingRunnerBase() {
override fun testAppClass() = TestApp::class.java
}

View file

@ -0,0 +1,37 @@
package org.ligi.passandroid;
import android.app.Activity;
import android.test.ActivityInstrumentationTestCase2;
import android.view.WindowManager;
import org.ligi.passandroid.reporting.SpooningFailureHandler;
import static android.support.test.espresso.Espresso.setFailureHandler;
public abstract class BaseIntegration<T extends Activity> extends ActivityInstrumentationTestCase2<T> {
public BaseIntegration(Class<T> activityClass) {
super(activityClass);
}
@Override
public void setUp() throws Exception {
super.setUp();
setFailureHandler(new SpooningFailureHandler(getInstrumentation()));
}
@Override
public T getActivity() {
final T activity = super.getActivity();
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON |
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
}
});
return activity;
}
}

View file

@ -0,0 +1,18 @@
package org.ligi.passandroid
import android.app.Activity
import android.support.test.InstrumentationRegistry
import android.support.test.espresso.Espresso.setFailureHandler
import android.view.WindowManager
import org.ligi.passandroid.reporting.SpooningFailureHandler
abstract class BaseUnitTest {
fun setUp(activity: Activity) {
setFailureHandler(SpooningFailureHandler(InstrumentationRegistry.getInstrumentation()))
activity.runOnUiThread { activity.window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD) }
}
}

View file

@ -1,58 +0,0 @@
package org.ligi.passandroid
import org.koin.core.module.Module
import org.koin.dsl.module
import org.ligi.passandroid.injections.FixedPassListPassStore
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.Settings
import org.ligi.passandroid.model.comparator.PassSortOrder
import org.ligi.passandroid.model.pass.BarCode
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.model.pass.PassBarCodeFormat
import org.ligi.passandroid.model.pass.PassImpl
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import java.io.File
import java.util.*
class TestApp : App() {
override fun createKoin(): Module {
return module {
single { passStore as PassStore }
single { settings }
single { tracker }
}
}
companion object {
val tracker = mock(Tracker::class.java)
val passStore = FixedPassListPassStore(emptyList())
val settings = mock(Settings::class.java).apply {
`when`(getSortOrder()).thenReturn(PassSortOrder.DATE_ASC)
`when`(getPassesDir()).thenReturn(File(""))
`when`(doTraceDroidEmailSend()).thenReturn(false)
}
fun populatePassStoreWithSinglePass() {
val passList = ArrayList<Pass>()
val pass = PassImpl(UUID.randomUUID().toString())
pass.description = "description"
pass.barCode = BarCode(PassBarCodeFormat.AZTEC, "messageprobe")
passList.add(pass)
fixedPassListPassStore().setList(passList)
passStore.classifier.moveToTopic(pass, "test")
}
fun emptyPassStore() {
fixedPassListPassStore().setList(emptyList())
}
private fun fixedPassListPassStore() = passStore as FixedPassListPassStore
}
}

View file

@ -0,0 +1,23 @@
package org.ligi.passandroid
import dagger.Component
import javax.inject.Singleton
@Singleton
@Component(modules = arrayOf(TestModule::class))
interface TestComponent : AppComponent {
fun inject(theFullscreenBarcodeActivity: TheFullscreenBarcodeActivity)
fun inject(thePassEditActivity: ThePassEditActivity)
fun inject(thePassViewActivity: ThePassViewActivity)
fun inject(thePastLocationsStore: ThePastLocationsStore)
fun inject(theBarCodeEditing: TheBarCodeEditing)
fun inject(thePassListSwiping: ThePassListSwiping)
fun inject(theFieldListEditFragment: TheFieldListEditFragment)
}

View file

@ -0,0 +1,83 @@
package org.ligi.passandroid
import dagger.Module
import dagger.Provides
import org.greenrobot.eventbus.EventBus
import org.ligi.passandroid.injections.FixedPassListPassStore
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.Settings
import org.ligi.passandroid.model.State
import org.ligi.passandroid.model.comparator.PassSortOrder
import org.ligi.passandroid.model.pass.BarCode
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.model.pass.PassBarCodeFormat
import org.ligi.passandroid.model.pass.PassImpl
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import java.io.File
import java.util.*
import javax.inject.Singleton
@Module
class TestModule {
private val passList: MutableList<Pass>
constructor() {
passList = ArrayList<Pass>()
val pass = PassImpl(UUID.randomUUID().toString())
pass.description = "description"
pass.barCode = BarCode(PassBarCodeFormat.AZTEC, "messageprobe")
passList.add(pass)
}
constructor(passList: MutableList<Pass>) {
this.passList = passList
}
@Singleton
@Provides
fun providePassStore(): PassStore {
val fixedPassListPassStore = FixedPassListPassStore(passList)
if (!passList.isEmpty()) {
fixedPassListPassStore.currentPass = passList[0]
}
for (pass in passList) {
fixedPassListPassStore.classifier.moveToTopic(pass, "test")
}
return fixedPassListPassStore
}
@Singleton
@Provides
fun provideSettings(): Settings {
val mock = mock(Settings::class.java)
`when`(mock.sortOrder).thenReturn(PassSortOrder.DATE_ASC)
`when`(mock.passesDir).thenReturn(File(""))
`when`(mock.doTraceDroidEmailSend()).thenReturn(false)
return mock
}
@Singleton
@Provides
fun provideBus(): EventBus {
return mock(EventBus::class.java)
}
@Singleton
@Provides
fun provideTracker(): Tracker {
return mock(Tracker::class.java)
}
@Singleton
@Provides
fun provideState(): State {
return State()
}
}

View file

@ -1,164 +0,0 @@
package org.ligi.passandroid
import android.app.Activity.RESULT_CANCELED
import android.app.Instrumentation
import android.provider.CalendarContract
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra
import androidx.test.espresso.intent.matcher.IntentMatchers.hasType
import androidx.test.espresso.matcher.ViewMatchers.*
import org.hamcrest.CoreMatchers.not
import org.hamcrest.Matchers.allOf
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.functions.DEFAULT_EVENT_LENGTH_IN_HOURS
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.ui.PassListActivity
import org.ligi.trulesk.TruleskIntentRule
import org.threeten.bp.ZonedDateTime
class TheAddToCalendar {
private val time = ZonedDateTime.now()
private val time2 = ZonedDateTime.now().plusHours(3)
@get:Rule
var rule = TruleskIntentRule(PassListActivity::class.java, false)
@Test
fun testIfWeOnlyHaveCalendarStartDate() {
TestApp.populatePassStoreWithSinglePass()
TestApp.passStore.currentPass!!.calendarTimespan = PassImpl.TimeSpan(time)
rule.launchActivity()
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
onView(withId(R.id.timeButton)).perform(click())
intended(allOf(
hasType("vnd.android.cursor.item/event"),
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.toEpochSecond() * 1000),
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time.plusHours(DEFAULT_EVENT_LENGTH_IN_HOURS).toEpochSecond() * 1000),
hasExtra("title", TestApp.passStore.currentPass!!.description)
))
}
@Test
fun testIfWeOnlyHaveCalendarEndDate() {
TestApp.populatePassStoreWithSinglePass()
TestApp.passStore.currentPass!!.calendarTimespan = PassImpl.TimeSpan(to = time)
rule.launchActivity()
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
onView(withId(R.id.timeButton)).perform(click())
intended(allOf(
hasType("vnd.android.cursor.item/event"),
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.minusHours(DEFAULT_EVENT_LENGTH_IN_HOURS).toEpochSecond() * 1000),
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time.toEpochSecond() * 1000),
hasExtra("title", TestApp.passStore.currentPass!!.description)
))
}
@Test
fun testIfWeOnlyHaveCalendarStartAndEndDate() {
TestApp.populatePassStoreWithSinglePass()
TestApp.passStore.currentPass!!.calendarTimespan = PassImpl.TimeSpan(time, time2)
rule.launchActivity()
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
onView(withId(R.id.timeButton)).perform(click())
intended(allOf(
hasType("vnd.android.cursor.item/event"),
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.toEpochSecond() * 1000),
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time2.toEpochSecond() * 1000),
hasExtra("title", TestApp.passStore.currentPass!!.description)
))
}
@Test
fun testIfWeOnlyHaveExpirationDate() {
TestApp.populatePassStoreWithSinglePass()
(TestApp.passStore.currentPass as PassImpl).validTimespans = listOf(PassImpl.TimeSpan(time))
rule.launchActivity()
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
onView(withId(R.id.timeButton)).perform(click())
onView(withText(R.string.expiration_date_to_calendar_warning_title)).check(matches(isDisplayed()))
onView(withText(android.R.string.ok)).perform(click())
intended(allOf(
hasType("vnd.android.cursor.item/event"),
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.toEpochSecond() * 1000),
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time.plusHours(DEFAULT_EVENT_LENGTH_IN_HOURS).toEpochSecond() * 1000),
hasExtra("title", TestApp.passStore.currentPass!!.description)
))
}
@Test
fun testIfWeOnlyHaveExpirationEndDate() {
TestApp.populatePassStoreWithSinglePass()
(TestApp.passStore.currentPass as PassImpl).validTimespans = listOf(PassImpl.TimeSpan(to = time))
rule.launchActivity()
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
onView(withId(R.id.timeButton)).perform(click())
onView(withText(R.string.expiration_date_to_calendar_warning_title)).check(matches(isDisplayed()))
onView(withText(android.R.string.ok)).perform(click())
intended(allOf(
hasType("vnd.android.cursor.item/event"),
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.minusHours(DEFAULT_EVENT_LENGTH_IN_HOURS).toEpochSecond() * 1000),
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time.toEpochSecond() * 1000),
hasExtra("title", TestApp.passStore.currentPass!!.description)
))
}
@Test
fun testIfWeOnlyHaveExpirationStartAndEndDate() {
TestApp.populatePassStoreWithSinglePass()
(TestApp.passStore.currentPass as PassImpl).validTimespans = listOf(PassImpl.TimeSpan(time, time2))
rule.launchActivity()
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
onView(withId(R.id.timeButton)).perform(click())
onView(withText(R.string.expiration_date_to_calendar_warning_title)).check(matches(isDisplayed()))
onView(withText(android.R.string.ok)).perform(click())
intended(allOf(
hasType("vnd.android.cursor.item/event"),
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.toEpochSecond() * 1000),
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time2.toEpochSecond() * 1000),
hasExtra("title", TestApp.passStore.currentPass!!.description)
))
}
@Test
fun testThereIsNoButtonWithNoDate() {
TestApp.populatePassStoreWithSinglePass()
rule.launchActivity()
onView(withId(R.id.timeButton)).check(matches(not(isDisplayed())))
}
}

View file

@ -0,0 +1,71 @@
package org.ligi.passandroid;
import android.app.Instrumentation;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import java.io.File;
import java.io.InputStream;
import org.junit.Before;
import org.ligi.passandroid.model.InputStreamWithSource;
import org.ligi.passandroid.model.PassStore;
import org.ligi.passandroid.model.pass.Pass;
import org.ligi.passandroid.reader.AppleStylePassReader;
import org.ligi.passandroid.ui.UnzipPassController;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.assertj.core.api.Fail.fail;
import static org.ligi.passandroid.ui.UnzipPassController.InputStreamUnzipControllerSpec;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
public class TheAppleStyleBarcodeReaderBase {
@Mock
UnzipPassController.FailCallback failCallback;
@Mock
PassStore passStore;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
private File getTestTargetPath(final Context context) {
return new File(context.getCacheDir() , "test_passes");
}
void loadPassFromAsset(final String asset, final OnPassLoadCallback callback) {
try {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
final InputStream inputStream = instrumentation.getContext().getResources().getAssets().open(asset);
final InputStreamWithSource inputStreamWithSource = new InputStreamWithSource("none", inputStream);
final InputStreamUnzipControllerSpec spec = new InputStreamUnzipControllerSpec(inputStreamWithSource, instrumentation.getTargetContext(),passStore,
new UnzipPassController.SuccessCallback() {
@Override
public void call(String uuid) {
callback.onPassLoad(AppleStylePassReader.INSTANCE.read(new File(getTestTargetPath(instrumentation.getTargetContext()), uuid), "en",
instrumentation.getTargetContext()));
}
}
, failCallback
);
spec.setOverwrite(true);
spec.setTargetPath(getTestTargetPath(spec.getContext()));
UnzipPassController.INSTANCE.processInputStream(spec);
verify(failCallback, never()).fail(any(String.class));
} catch (Exception e) {
fail("should be able to load file ", e);
}
}
interface OnPassLoadCallback {
void onPassLoad(Pass pass);
}
}

View file

@ -1,16 +1,15 @@
package org.ligi.passandroid
import android.Manifest
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.closeSoftKeyboard
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import com.linkedin.android.testbutler.TestButler
import android.support.test.espresso.Espresso.closeSoftKeyboard
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.*
import android.support.test.espresso.assertion.ViewAssertions.doesNotExist
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.*
import android.support.test.filters.SdkSuppress
import android.support.test.rule.ActivityTestRule
import android.support.test.runner.AndroidJUnit4
import com.squareup.spoon.Spoon
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
@ -19,29 +18,33 @@ import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.pass.PassBarCodeFormat
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.ui.PassEditActivity
import org.ligi.trulesk.TruleskActivityRule
import javax.inject.Inject
@RunWith(AndroidJUnit4::class)
class TheBarCodeEditing {
class TheBarCodeEditing : BaseUnitTest() {
@get:Rule
val rule = TruleskActivityRule(PassEditActivity::class.java, false)
val rule: ActivityTestRule<PassEditActivity> = ActivityTestRule(PassEditActivity::class.java, true, false)
val passStore: PassStore = TestApp.passStore
@Inject
lateinit var passStore: PassStore
private lateinit var currentPass: PassImpl
lateinit var currentPass: PassImpl
private fun start(setupPass: (pass: PassImpl) -> Unit = {}) {
TestApp.populatePassStoreWithSinglePass()
private val passEditActivity by lazy { rule.launchActivity(null) }
fun start(setupPass: (pass: PassImpl) -> Unit) {
val build = DaggerTestComponent.create()
build.inject(this)
App.setComponent(build)
currentPass = passStore.currentPass as PassImpl
setupPass(currentPass)
TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
setUp(passEditActivity)
rule.launchActivity(null)
closeSoftKeyboard()
}
@ -52,7 +55,7 @@ class TheBarCodeEditing {
it.barCode = null
}
rule.screenShot("no_barcode")
Spoon.screenshot(passEditActivity!!, "no_barcode")
onView(withId(R.id.add_barcode_button)).perform(scrollTo())
onView(withId(R.id.add_barcode_button)).check(matches(isDisplayed()))
@ -78,31 +81,30 @@ class TheBarCodeEditing {
@SdkSuppress(minSdkVersion = 14)
@Test
fun testCanSetToAllBarcodeTypes() {
start()
start {}
for (passBarCodeFormat in PassBarCodeFormat.values()) {
onView(withId(R.id.barcode_img)).perform(scrollTo(), click())
onView(withText(passBarCodeFormat.name)).perform(scrollTo(), click())
onView(withId(R.id.randomButton)).perform(click())
closeSoftKeyboard()
onView(withText(android.R.string.ok)).perform(click())
assertThat(currentPass.barCode!!.format).isEqualTo(passBarCodeFormat)
rule.screenShot("edit_set_" + passBarCodeFormat.name)
Spoon.screenshot(passEditActivity!!, "edit_set_" + passBarCodeFormat.name)
}
}
@Test
fun testCanSetMessage() {
start()
start {}
onView(withId(R.id.barcode_img)).perform(click())
onView(withId(R.id.messageInput)).perform(clearText())
onView(withId(R.id.messageInput)).perform(replaceText("msg foo txt ;-)"))
onView(withId(R.id.messageInput)).perform(typeText("msg foo txt ;-)"))
closeSoftKeyboard()
@ -111,18 +113,18 @@ class TheBarCodeEditing {
onView(withText(R.string.edit_barcode_dialog_title)).check(doesNotExist())
assertThat(passStore.currentPass!!.barCode!!.message).isEqualTo("msg foo txt ;-)")
rule.screenShot("edit_set_msg")
Spoon.screenshot(passEditActivity!!, "edit_set_msg")
}
@Test
fun testCanSetAltMessage() {
start()
start {}
onView(withId(R.id.barcode_img)).perform(click())
onView(withId(R.id.alternativeMessageInput)).perform(clearText())
onView(withId(R.id.alternativeMessageInput)).perform(replaceText("alt bar txt ;-)"))
onView(withId(R.id.alternativeMessageInput)).perform(typeText("alt bar txt ;-)"))
closeSoftKeyboard()
@ -131,12 +133,12 @@ class TheBarCodeEditing {
onView(withText(R.string.edit_barcode_dialog_title)).check(doesNotExist())
assertThat(passStore.currentPass!!.barCode!!.alternativeText).isEqualTo("alt bar txt ;-)")
rule.screenShot("edit_set_altmsg")
Spoon.screenshot(passEditActivity!!, "edit_set_altmsg")
}
@Test
fun testThatRandomChangesMessage() {
start()
start {}
onView(withId(R.id.barcode_img)).perform(click())
@ -150,4 +152,5 @@ class TheBarCodeEditing {
assertThat(oldMessage).isNotEqualTo(passStore.currentPass!!.barCode!!.message)
}
}

View file

@ -0,0 +1,69 @@
package org.ligi.passandroid;
import android.graphics.Bitmap;
import android.support.test.runner.AndroidJUnit4;
import com.google.zxing.common.BitMatrix;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ligi.passandroid.helper.BarcodeHelper;
import org.ligi.passandroid.model.pass.PassBarCodeFormat;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Fail.fail;
@RunWith(AndroidJUnit4.class)
public class TheBarcodeHelper {
@Test
public void testQRBitMatrixHasCorrectSize() throws Exception {
testBitMatrixSizeIsSane(PassBarCodeFormat.QR_CODE);
}
@Test
public void testQRBitmapHasCorrectSize() throws Exception {
testBitmapSizeIsSane(PassBarCodeFormat.QR_CODE);
}
@Test
public void testPDF417BitmapHasCorrectSize() {
testBitmapSizeIsSane(PassBarCodeFormat.PDF_417);
}
@Test
public void testPDF417BitMatrixHasCorrectSize() {
testBitMatrixSizeIsSane(PassBarCodeFormat.PDF_417);
}
@Test
public void testAZTECBitmapHasCorrectSize() {
testBitmapSizeIsSane(PassBarCodeFormat.AZTEC);
}
@Test
public void testAZTECBitMatrixHasCorrectSize() {
testBitMatrixSizeIsSane(PassBarCodeFormat.AZTEC);
}
public void testBitMatrixSizeIsSane(final PassBarCodeFormat format) {
try {
BitMatrix tested = BarcodeHelper.getBitMatrix("foo-data", format);
assertThat(tested.getWidth()).isGreaterThan(3);
} catch (Exception e) {
fail("could not create barcode", e);
}
}
public void testBitmapSizeIsSane(final PassBarCodeFormat format) {
try {
Bitmap tested2 = BarcodeHelper.generateBarCodeBitmap("foo-data", format);
assert tested2 != null;
assertThat(tested2.getWidth()).isGreaterThan(3);
} catch (Exception e) {
fail("could not create barcode" ,e);
}
}
}

View file

@ -1,61 +0,0 @@
package org.ligi.passandroid
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Fail.fail
import org.junit.Test
import org.ligi.passandroid.functions.generateBarCodeBitmap
import org.ligi.passandroid.functions.getBitMatrix
import org.ligi.passandroid.model.pass.PassBarCodeFormat
class TheBarcodeHelper {
@Test
fun testQRBitMatrixHasCorrectSize() {
testBitMatrixSizeIsSane(PassBarCodeFormat.QR_CODE)
}
@Test
fun testQRBitmapHasCorrectSize() {
testBitmapSizeIsSane(PassBarCodeFormat.QR_CODE)
}
@Test
fun testPDF417BitmapHasCorrectSize() {
testBitmapSizeIsSane(PassBarCodeFormat.PDF_417)
}
@Test
fun testPDF417BitMatrixHasCorrectSize() {
testBitMatrixSizeIsSane(PassBarCodeFormat.PDF_417)
}
@Test
fun testAZTECBitmapHasCorrectSize() {
testBitmapSizeIsSane(PassBarCodeFormat.AZTEC)
}
@Test
fun testAZTECBitMatrixHasCorrectSize() {
testBitMatrixSizeIsSane(PassBarCodeFormat.AZTEC)
}
fun testBitMatrixSizeIsSane(format: PassBarCodeFormat) {
try {
val tested = getBitMatrix("foo-data", format)
assertThat(tested.width).isGreaterThan(3)
} catch (e: Exception) {
fail("could not create barcode", e)
}
}
fun testBitmapSizeIsSane(format: PassBarCodeFormat) {
try {
val tested2 = generateBarCodeBitmap("foo-data", format)!!
assertThat(tested2.width).isGreaterThan(3)
} catch (e: Exception) {
fail("could not create barcode", e)
}
}
}

View file

@ -0,0 +1,25 @@
package org.ligi.passandroid;
import java.util.HashSet;
import java.util.Set;
import org.junit.Test;
import org.ligi.passandroid.helper.CategoryHelper;
import org.ligi.passandroid.model.PassDefinitions;
import org.ligi.passandroid.model.pass.PassType;
import static org.assertj.core.api.Assertions.assertThat;
public class TheCategoryHelper {
@Test
public void testAllCategoriesAreTranslated() {
final Set<Integer> probe = new HashSet<>();
for (PassType type : PassDefinitions.INSTANCE.getTYPES().keySet()) {
probe.add(CategoryHelper.INSTANCE.getHumanCategoryString(type));
}
assertThat(probe.size()).isEqualTo(PassDefinitions.INSTANCE.getTYPES().keySet().size());
}
}

View file

@ -1,20 +0,0 @@
package org.ligi.passandroid
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.ligi.passandroid.functions.getHumanCategoryString
import org.ligi.passandroid.model.PassDefinitions
class TheCategoryHelper {
@Test
fun testAllCategoriesAreTranslated() {
val allTranslationSet = PassDefinitions.TYPE_TO_NAME.keys
.map(::getHumanCategoryString)
.toSet()
assertThat(allTranslationSet.size).isEqualTo(PassDefinitions.TYPE_TO_NAME.keys.size)
}
}

View file

@ -1,54 +0,0 @@
package org.ligi.passandroid
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import org.hamcrest.CoreMatchers.containsString
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.model.pass.PassField
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.ui.PassListActivity
import org.ligi.trulesk.TruleskActivityRule
import org.mockito.Mockito.`when`
import org.threeten.bp.ZoneId
import org.threeten.bp.ZonedDateTime
class TheCondensedPassViewMode {
@get:Rule
var rule = TruleskActivityRule(PassListActivity::class.java, false) {
TestApp.populatePassStoreWithSinglePass()
val currentPass = TestApp.passStore.currentPass as PassImpl
currentPass.calendarTimespan = PassImpl.TimeSpan(ZonedDateTime.of(2016, 11, 23, 20, 42, 42, 5, ZoneId.systemDefault()))
currentPass.fields = mutableListOf(PassField("textprobe", "bar", "yo", false))
}
@Test
fun testDateShowsForCondensedOff() {
`when`(TestApp.settings.isCondensedModeEnabled()).thenReturn(false)
rule.launchActivity()
onView(withId(R.id.date)).check(matches(withText(containsString("23"))))
onView(withId(R.id.timeButton)).check(matches(withText(R.string.pass_to_calendar)))
rule.screenShot("condensed_off")
}
@Test
fun testFieldShowsForCondensedOn() {
`when`(TestApp.settings.isCondensedModeEnabled()).thenReturn(true)
rule.launchActivity()
onView(withId(R.id.date)).check(matches(withText(containsString("bar"))))
onView(withId(R.id.timeButton)).check(matches(withText(containsString("23"))))
rule.screenShot("condensed_on")
}
}

View file

@ -0,0 +1,42 @@
package org.ligi.passandroid;
import android.support.test.filters.MediumTest;
import com.squareup.spoon.Spoon;
import java.util.ArrayList;
import org.ligi.passandroid.model.pass.Pass;
import org.ligi.passandroid.ui.PassListActivity;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static org.ligi.passandroid.steps.HelpSteps.checkThatHelpIsThere;
public class TheEmptyPassList extends BaseIntegration<PassListActivity> {
public TheEmptyPassList() {
super(PassListActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
App.setComponent(DaggerTestComponent.builder().testModule(new TestModule(new ArrayList<Pass>())).build());
getActivity();
}
@MediumTest
public void testEmptyViewIsThereWhenThereAreNoPasses() {
Spoon.screenshot(getActivity(), "empty_view");
onView(withId(R.id.emptyView)).check(matches(isDisplayed()));
}
@MediumTest
public void testHelpGoesToHelp() {
onView(withId(R.id.menu_help)).perform(click());
checkThatHelpIsThere();
}
}

View file

@ -1,36 +0,0 @@
package org.ligi.passandroid
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.R.id.emptyView
import org.ligi.passandroid.functions.checkThatHelpIsThere
import org.ligi.passandroid.ui.PassListActivity
import org.ligi.trulesk.TruleskIntentRule
class TheEmptyPassList {
@get:Rule
var rule = TruleskIntentRule(PassListActivity::class.java) {
TestApp.emptyPassStore()
}
@Test
fun testEmptyViewIsThereWhenThereAreNoPasses() {
rule.screenShot("empty_view")
onView(withId(emptyView)).check(matches(isDisplayed()))
}
@Test
fun testHelpGoesToHelp() {
onView(withId(R.id.menu_help)).perform(click())
checkThatHelpIsThere()
}
}

View file

@ -1,34 +1,54 @@
package org.ligi.passandroid
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.action.ViewActions.scrollTo
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import android.app.Activity
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.replaceText
import android.support.test.espresso.action.ViewActions.scrollTo
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.*
import android.support.test.rule.ActivityTestRule
import android.support.test.runner.AndroidJUnit4
import com.squareup.spoon.Spoon
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.pass.PassField
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.ui.PassEditActivity
import org.ligi.trulesk.TruleskIntentRule
import java.util.*
import javax.inject.Inject
class TheFieldListEditFragment {
@RunWith(AndroidJUnit4::class)
class TheFieldListEditFragment : BaseUnitTest(){
@get:Rule
val rule = TruleskIntentRule(PassEditActivity::class.java) {
TestApp.passStore.currentPass = PassImpl(UUID.randomUUID().toString()).apply {
fields = arrayListOf(field)
}
}
val rule: ActivityTestRule<PassEditActivity> = ActivityTestRule(PassEditActivity::class.java, true, false)
@Inject
lateinit var passStore: PassStore
private val field: PassField = PassField(null, "labelfieldcontent", "valuefieldcontent", false)
fun start(): Activity {
val build = DaggerTestComponent.create()
build.inject(this)
App.setComponent(build)
val currentPass = passStore.currentPass as PassImpl
currentPass.fields = arrayListOf(field)
val activity = rule.launchActivity(null)
setUp(activity)
return activity
}
@Test
fun testFieldDetailsArePreFilled() {
rule.screenShot("one_field")
Spoon.screenshot(start(), "one_field")
onView(withId(R.id.label_field_edit)).perform(scrollTo())
onView(withId(R.id.label_field_edit)).check(matches(isDisplayed()))
@ -43,6 +63,8 @@ class TheFieldListEditFragment {
@Test
fun testThatChangingLabelWorks() {
start()
onView(withId(R.id.label_field_edit)).perform(scrollTo())
onView(withId(R.id.label_field_edit)).perform(replaceText("newlabel"))
assertThat(field.label).isEqualTo("newlabel")
@ -51,6 +73,8 @@ class TheFieldListEditFragment {
@Test
fun testThatChangingValueWorks() {
start()
onView(withId(R.id.value_field_edit)).perform(scrollTo())
onView(withId(R.id.value_field_edit)).perform(replaceText("newvalue"))
assertThat(field.value).isEqualTo("newvalue")

View file

@ -0,0 +1,111 @@
package org.ligi.passandroid;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.support.test.filters.MediumTest;
import android.widget.ImageView;
import butterknife.ButterKnife;
import com.squareup.spoon.Spoon;
import java.util.UUID;
import javax.inject.Inject;
import org.ligi.passandroid.helper.BarcodeDecoder;
import org.ligi.passandroid.model.PassStore;
import org.ligi.passandroid.model.pass.BarCode;
import org.ligi.passandroid.model.pass.PassBarCodeFormat;
import org.ligi.passandroid.model.pass.PassImpl;
import org.ligi.passandroid.ui.FullscreenBarcodeActivity;
import org.ligi.tracedroid.TraceDroid;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static org.assertj.core.api.Assertions.assertThat;
public class TheFullscreenBarcodeActivity extends BaseIntegration<FullscreenBarcodeActivity> {
@Inject
PassStore passStore;
public static final String BARCODE_MESSAGE = "2323";
public TheFullscreenBarcodeActivity() {
super(FullscreenBarcodeActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
TestComponent component = DaggerTestComponent.create();
component.inject(this);
App.setComponent(component);
TraceDroid.deleteStacktraceFiles();
}
@MediumTest
public void testPDF417BarcodeIsShown() {
testWithBarcodeFormat(PassBarCodeFormat.PDF_417);
Spoon.screenshot(getActivity(), "pdf417_barcode");
}
@MediumTest
public void testAztecBarcodeIsShown() {
testWithBarcodeFormat(PassBarCodeFormat.AZTEC);
Spoon.screenshot(getActivity(), "aztec_barcode");
}
@MediumTest
public void testQRCodeIsShown() {
testWithBarcodeFormat(PassBarCodeFormat.QR_CODE);
Spoon.screenshot(getActivity(), "qr_barcode");
}
@MediumTest
public void testCode128CodeIsShown() {
testWithBarcodeFormat(PassBarCodeFormat.CODE_128);
Spoon.screenshot(getActivity(), "code128_barcode");
}
@MediumTest
public void testCode39CodeIsShown() {
testWithBarcodeFormat(PassBarCodeFormat.CODE_39);
Spoon.screenshot(getActivity(), "code39_barcode");
}
private void testWithBarcodeFormat(final PassBarCodeFormat format) {
final PassImpl pass = new PassImpl(UUID.randomUUID().toString());
pass.setBarCode(new BarCode(format, BARCODE_MESSAGE));
passStore.setCurrentPass(pass);
getActivity();
onView(withId(R.id.fullscreen_barcode)).check(matches(isDisplayed()));
final ImageView viewById = ButterKnife.findById(getActivity(), R.id.fullscreen_barcode);
BitmapDrawable bitmapDrawable = (BitmapDrawable) viewById.getDrawable();
final Bitmap bitmap = bitmapDrawable.getBitmap();
final Bitmap bitmapToTest;
if (format == PassBarCodeFormat.AZTEC) {
// not sure why - but for the decoder to pick up AZTEC it must have moar pixelz - smells like a zxing bug
bitmapToTest = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth() * 2, bitmap.getHeight() * 2, false);
} else {
bitmapToTest = bitmap;
}
assertThat(BarcodeDecoder.INSTANCE.decodeBitmap(bitmapToTest)).isEqualTo(BARCODE_MESSAGE);
}
}

View file

@ -1,91 +0,0 @@
package org.ligi.passandroid
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.widget.ImageView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.functions.decodeBarCode
import org.ligi.passandroid.model.pass.BarCode
import org.ligi.passandroid.model.pass.PassBarCodeFormat
import org.ligi.passandroid.model.pass.PassBarCodeFormat.*
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.ui.FullscreenBarcodeActivity
import org.ligi.trulesk.TruleskIntentRule
import java.util.*
private const val BARCODE_MESSAGE = "2323"
class TheFullscreenBarcodeActivity {
@get:Rule
var rule = TruleskIntentRule(FullscreenBarcodeActivity::class.java, false)
@Test
fun testPDF417BarcodeIsShown() {
testWithBarcodeFormat(PDF_417)
rule.screenShot("pdf417_barcode")
}
@Test
fun testAztecBarcodeIsShown() {
testWithBarcodeFormat(AZTEC)
rule.screenShot("aztec_barcode")
}
@Test
fun testQRCodeIsShown() {
testWithBarcodeFormat(QR_CODE)
rule.screenShot("qr_barcode")
}
@Test
fun testCode128CodeIsShown() {
testWithBarcodeFormat(CODE_128)
rule.screenShot("code128_barcode")
}
@Test
fun testCode39CodeIsShown() {
testWithBarcodeFormat(CODE_39)
rule.screenShot("code39_barcode")
}
private fun testWithBarcodeFormat(format: PassBarCodeFormat) {
val pass = PassImpl(UUID.randomUUID().toString())
pass.barCode = BarCode(format, BARCODE_MESSAGE)
TestApp.passStore.currentPass = pass
rule.launchActivity(null)
onView(withId(R.id.fullscreen_barcode)).check(matches(isDisplayed()))
val viewById = rule.activity.findViewById(R.id.fullscreen_barcode) as ImageView
val bitmapDrawable = viewById.drawable as BitmapDrawable
val bitmap = bitmapDrawable.bitmap
val bitmapToTest: Bitmap
bitmapToTest = if (format === PassBarCodeFormat.AZTEC) {
// not sure why - but for the decoder to pick up AZTEC it must have moar pixelz - smells like a zxing bug
Bitmap.createScaledBitmap(bitmap, bitmap.width * 2, bitmap.height * 2, false)
} else {
bitmap
}
assertThat(bitmapToTest.decodeBarCode()).isEqualTo(BARCODE_MESSAGE)
}
}

View file

@ -1,32 +1,32 @@
package org.ligi.passandroid
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import org.assertj.core.api.Assertions.assertThat
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.*
import android.support.test.rule.ActivityTestRule
import junit.framework.Assert
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.functions.checkThatHelpIsThere
import org.ligi.passandroid.helper.ScreenshotTaker
import org.ligi.passandroid.steps.HelpSteps.checkThatHelpIsThere
import org.ligi.passandroid.ui.HelpActivity
import org.ligi.trulesk.TruleskActivityRule
class TheHelpActivity {
@get:Rule
val rule = TruleskActivityRule(HelpActivity::class.java)
val rule: ActivityTestRule<HelpActivity> = ActivityTestRule(HelpActivity::class.java)
@Test
fun testHelpIsThere() {
checkThatHelpIsThere()
rule.screenShot("help")
ScreenshotTaker.takeScreenshot(rule.activity, "help")
}
@Test
fun test_that_help_finishes_on_home() {
onView(withContentDescription(R.string.abc_action_bar_up_description)).perform(click())
assertThat(rule.activity.isFinishing).isTrue()
Assert.assertTrue(rule.activity.isFinishing)
}
@Test

View file

@ -1,109 +0,0 @@
package org.ligi.passandroid
import android.annotation.TargetApi
import android.app.Activity.RESULT_CANCELED
import android.app.Instrumentation.ActivityResult
import android.content.Intent.ACTION_SEND
import android.content.Intent.ACTION_VIEW
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.DrawerActions.open
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers.*
import androidx.test.espresso.matcher.ViewMatchers.*
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.not
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.R.string.*
import org.ligi.passandroid.ui.PassListActivity
import org.ligi.passandroid.ui.PreferenceActivity
import org.ligi.trulesk.TruleskIntentRule
@TargetApi(14)
class TheNavigationDrawer {
@get:Rule
var rule = TruleskIntentRule(PassListActivity::class.java)
@Test
fun testNavigationDrawerIsUsuallyNotShown() {
onView(withId(R.id.navigationView)).check(matches(not(isDisplayed())))
}
@Test
fun testThatNavigationDrawerOpens() {
onView(withId(R.id.drawer_layout)).perform(open())
onView(withId(R.id.navigationView)).check(matches(isDisplayed()))
}
@Test
fun testThatNavigationDrawerClosesOnBackPress() {
testThatNavigationDrawerOpens()
pressBack()
onView(withId(R.id.navigationView)).check(matches(not(isDisplayed())))
}
@Test
fun testBetatestClick() {
testThatNavigationDrawerOpens()
rule.screenShot("open_drawer")
intending(hasAction(ACTION_VIEW)).respondWith(ActivityResult(RESULT_CANCELED, null))
onView(withText(nav_betatest_opt_in_out)).perform(click())
intended(allOf(hasAction(ACTION_VIEW), hasData("https://play.google.com/apps/testing/org.ligi.passandroid")))
}
@Test
fun testGitHubClick() {
testThatNavigationDrawerOpens()
rule.screenShot("open_drawer")
intending(hasAction(ACTION_VIEW)).respondWith(ActivityResult(RESULT_CANCELED, null))
onView(withText(nav_github)).perform(click())
intended(allOf(hasAction(ACTION_VIEW), hasData("https://github.com/ligi/PassAndroid")))
}
@Test
fun testImproveTranslationsClick() {
testThatNavigationDrawerOpens()
rule.screenShot("open_drawer")
intending(hasAction(ACTION_VIEW)).respondWith(ActivityResult(RESULT_CANCELED, null))
onView(withText(nav_improve_translation)).perform(click())
intended(allOf(hasAction(ACTION_VIEW), hasData("https://transifex.com/projects/p/passandroid")))
}
@Test
fun testShareClick() {
testThatNavigationDrawerOpens()
rule.screenShot("open_drawer")
intending(hasAction(ACTION_SEND)).respondWith(ActivityResult(RESULT_CANCELED, null))
onView(withText(nav_share)).perform(click())
intended(allOf(hasAction(ACTION_SEND), hasType("text/plain")))
}
@Test
fun testSettings() {
testThatNavigationDrawerOpens()
rule.screenShot("open_drawer")
onView(withText(nav_settings)).perform(click())
intended(hasComponent(PreferenceActivity::class.java.name))
}
}

View file

@ -0,0 +1,85 @@
package org.ligi.passandroid;
import android.annotation.TargetApi;
import android.support.test.filters.MediumTest;
import com.squareup.spoon.Spoon;
import javax.inject.Inject;
import org.ligi.passandroid.model.PassStore;
import org.ligi.passandroid.model.pass.PassType;
import org.ligi.passandroid.ui.PassEditActivity;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.clearText;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.assertj.core.api.Assertions.assertThat;
@TargetApi(14)
public class ThePassEditActivity extends BaseIntegration<PassEditActivity> {
@Inject
PassStore passStore;
public ThePassEditActivity() {
super(PassEditActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
final TestComponent build = DaggerTestComponent.create();
build.inject(this);
App.setComponent(build);
getActivity();
}
@MediumTest
public void testSetToEventWorks() {
onView(withId(R.id.categoryView)).perform(click());
onView(withText(R.string.select_category_dialog_title)).perform(click());
onView(withText(R.string.category_event)).perform(click());
assertThat(passStore.getCurrentPass().getType()).isEqualTo(PassType.EVENT);
Spoon.screenshot(getActivity(), "edit_set_event");
}
@MediumTest
public void testSetToCouponWorks() {
onView(withId(R.id.categoryView)).perform(click());
onView(withText(R.string.select_category_dialog_title)).perform(click());
onView(withText(R.string.category_coupon)).perform(click());
assertThat(passStore.getCurrentPass().getType()).isEqualTo(PassType.COUPON);
Spoon.screenshot(getActivity(), "edit_set_coupon");
}
@MediumTest
public void testSetDescriptionWorks() {
onView(withId(R.id.title)).perform(clearText(),typeText("test description"));
assertThat(passStore.getCurrentPass().getDescription()).isEqualTo("test description");
Spoon.screenshot(getActivity(), "edit_set_description");
}
@MediumTest
public void testColorWheelIsThere() {
onView(withId(R.id.categoryView)).perform(click());
onView(withText(R.string.change_color_dialog_title)).perform(click());
onView(withId(R.id.colorPicker)).check(matches(isDisplayed()));
Spoon.screenshot(getActivity(), "edit_set_color");
}
}

View file

@ -1,109 +0,0 @@
package org.ligi.passandroid
import android.Manifest
import android.annotation.TargetApi
import android.app.Activity
import android.app.Instrumentation
import android.content.Intent
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.platform.app.InstrumentationRegistry
import com.linkedin.android.testbutler.TestButler
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.model.pass.PassType.COUPON
import org.ligi.passandroid.model.pass.PassType.EVENT
import org.ligi.passandroid.ui.PassEditActivity
import org.ligi.trulesk.TruleskIntentRule
@TargetApi(14)
class ThePassEditActivity {
val passStore = TestApp.passStore
@get:Rule
var rule = TruleskIntentRule(PassEditActivity::class.java) {
TestApp.populatePassStoreWithSinglePass()
TestButler.grantPermission(InstrumentationRegistry.getInstrumentation().targetContext, Manifest.permission.READ_EXTERNAL_STORAGE)
TestButler.grantPermission(InstrumentationRegistry.getInstrumentation().targetContext, Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
@Test
fun testSetToEventWorks() {
onView(withId(R.id.categoryView)).perform(click())
onView(withText(R.string.select_category_dialog_title)).perform(click())
onView(withText(R.string.category_event)).perform(click())
assertThat(passStore.currentPass!!.type).isEqualTo(EVENT)
rule.screenShot("edit_set_event")
}
@Test
fun testSetToCouponWorks() {
onView(withId(R.id.categoryView)).perform(click())
onView(withText(R.string.select_category_dialog_title)).perform(click())
onView(withText(R.string.category_coupon)).perform(click())
assertThat(passStore.currentPass!!.type).isEqualTo(COUPON)
rule.screenShot("edit_set_coupon")
}
@Test
fun testSetDescriptionWorks() {
onView(withId(R.id.passTitle)).perform(clearText(), replaceText("test description"))
assertThat(passStore.currentPass!!.description).isEqualTo("test description")
rule.screenShot("edit_set_description")
}
@Test
fun testColorWheelIsThere() {
onView(withId(R.id.categoryView)).perform(click())
onView(withText(R.string.change_color_dialog_title)).perform(click())
onView(withId(R.id.colorPicker)).check(matches(isDisplayed()))
rule.screenShot("edit_set_color")
}
@Test
fun testAddAbortFooterImagePick() {
intending(hasAction(Intent.ACTION_CHOOSER)).respondWith(Instrumentation.ActivityResult(Activity.RESULT_CANCELED, null))
onView(withId(R.id.add_footer)).perform(scrollTo(), click())
intended(hasAction(Intent.ACTION_CHOOSER))
}
@Test
fun testAddAbortStripImagePick() {
intending(hasAction(Intent.ACTION_CHOOSER)).respondWith(Instrumentation.ActivityResult(Activity.RESULT_CANCELED, null))
onView(withId(R.id.add_strip)).perform(scrollTo(), click())
intended(hasAction(Intent.ACTION_CHOOSER))
}
@Test
fun testAddAbortLogoImagePick() {
intending(hasAction(Intent.ACTION_CHOOSER)).respondWith(Instrumentation.ActivityResult(Activity.RESULT_CANCELED, null))
onView(withId(R.id.add_logo)).perform(scrollTo(), click())
intended(hasAction(Intent.ACTION_CHOOSER))
}
}

View file

@ -0,0 +1,62 @@
package org.ligi.passandroid;
import android.annotation.TargetApi;
import android.support.test.filters.MediumTest;
import com.squareup.spoon.Spoon;
import org.ligi.passandroid.ui.PassListActivity;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.contrib.DrawerActions.open;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.CoreMatchers.not;
import static org.ligi.passandroid.steps.HelpSteps.checkThatHelpIsThere;
@TargetApi(14)
public class ThePassListActivity extends BaseIntegration<PassListActivity> {
public ThePassListActivity() {
super(PassListActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
App.setComponent(DaggerTestComponent.builder().build());
getActivity();
}
@MediumTest
public void testListIsThere() {
onView(withId(R.id.pass_recyclerview)).check(matches(isDisplayed()));
Spoon.screenshot(getActivity(), "list");
}
@MediumTest
public void testHelpMenuBringsUsToHelp() {
onView(withId(R.id.menu_help)).perform(click());
checkThatHelpIsThere();
}
@MediumTest
public void testNavigationDrawerIsUsuallyNotShown() {
onView(withId(R.id.navigationView)).check(matches(not(isDisplayed())));
}
@MediumTest
public void testThatNavigationDrawerOpens() {
onView(withId(R.id.drawer_layout)).perform(open());
onView(withId(R.id.navigationView)).check(matches(isDisplayed()));
Spoon.screenshot(getActivity(), "open_drawer");
}
}

View file

@ -1,66 +0,0 @@
package org.ligi.passandroid
import android.annotation.TargetApi
import android.os.Build
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.R.id.pass_recyclerview
import org.ligi.passandroid.functions.checkThatHelpIsThere
import org.ligi.passandroid.functions.expand
import org.ligi.passandroid.functions.isCollapsed
import org.ligi.passandroid.ui.PassListActivity
import org.ligi.trulesk.TruleskActivityRule
@TargetApi(14)
class ThePassListActivity {
@get:Rule
var rule = TruleskActivityRule(PassListActivity::class.java) {
TestApp.populatePassStoreWithSinglePass()
}
@Test
fun testListIsThere() {
onView(withId(pass_recyclerview)).check(matches(isDisplayed()))
rule.screenShot("list")
}
@Test
fun testHelpMenuBringsUsToHelp() {
onView(withId(R.id.menu_help)).perform(click())
checkThatHelpIsThere()
}
@Test
fun testCloseFabOnBackPressed() {
onView(withId(R.id.fam)).perform(expand())
pressBack()
onView(withId(R.id.fam))
.check(matches(isDisplayed()))
.check(matches(isCollapsed()))
}
@Test
fun testOpenVisibleOn19plus() {
onView(withId(R.id.fam)).perform(expand())
pressBack()
if (Build.VERSION.SDK_INT >= 19) {
onView(withId(R.id.fab_action_open_file)).check(matches(isDisplayed()))
} else {
onView(withId(R.id.fab_action_open_file)).check(matches(withEffectiveVisibility(Visibility.GONE)))
}
}
}

View file

@ -1,25 +1,34 @@
package org.ligi.passandroid
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.action.ViewActions.typeText
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.*
import android.support.test.rule.ActivityTestRule
import android.support.v7.widget.helper.ItemTouchHelper
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.helper.ScreenshotTaker
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.ui.PassListActivity
import org.ligi.passandroid.ui.PassListFragment
import org.ligi.trulesk.TruleskIntentRule
import javax.inject.Inject
const val CUSTOM_PROBE = "FOO_PROBE"
class ThePassListSwiping {
class ThePassListSwiping : BaseUnitTest() {
@get:Rule
val rule = TruleskIntentRule(PassListActivity::class.java) {
TestApp.populatePassStoreWithSinglePass()
val rule: ActivityTestRule<PassListActivity> = ActivityTestRule(PassListActivity::class.java, true, false)
@Inject
lateinit var passStore: PassStore
private val activity by lazy {
val build = DaggerTestComponent.create()
build.inject(this)
App.setComponent(build)
rule.launchActivity(null)
}
@Test
@ -28,7 +37,7 @@ class ThePassListSwiping {
onView(withText(R.string.topic_trash)).perform(click())
assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_trash))
assertThat(passStore.classifier.getTopics()).containsExactly(activity.getString(R.string.topic_trash))
}
@ -38,19 +47,20 @@ class ThePassListSwiping {
onView(withText(R.string.topic_archive)).perform(click())
assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_archive))
assertThat(passStore.classifier.getTopics()).containsExactly(activity.getString(R.string.topic_archive))
}
@Test
fun testWeCanMoveToCustom() {
val CUSTOM_PROBE = "FOO_PROBE"
fakeSwipeLeft()
onView(withId(R.id.new_topic_edit)).perform(replaceText(CUSTOM_PROBE))
onView(withId(R.id.new_topic_edit)).perform(typeText(CUSTOM_PROBE))
onView(withText(android.R.string.ok)).perform(click())
assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(CUSTOM_PROBE)
assertThat(passStore.classifier.getTopics()).containsExactly(CUSTOM_PROBE)
}
@ -65,7 +75,7 @@ class ThePassListSwiping {
fakeSwipeRight()
rule.screenShot("move_to_new_topic_dialog")
ScreenshotTaker.takeScreenshot(activity, "move_to_new_topic_dialog")
onView(withText(R.string.move_to_new_topic)).check(matches(isDisplayed()))
}
@ -76,13 +86,18 @@ class ThePassListSwiping {
http://stackoverflow.com/questions/35397439/swipe-tests-flaky-on-recyclerview
*/
private fun fakeSwipe(dir: Int) {
rule.activity.runOnUiThread {
val fragment = rule.activity.supportFragmentManager.fragments.firstOrNull { it is PassListFragment } as PassListFragment
activity.runOnUiThread {
val fragment = activity.supportFragmentManager.fragments.firstOrNull { it is PassListFragment } as PassListFragment
fragment.onSwiped(0, dir)
}
}
private fun fakeSwipeRight() = fakeSwipe(ItemTouchHelper.RIGHT)
private fun fakeSwipeLeft() = fakeSwipe(ItemTouchHelper.LEFT)
private fun fakeSwipeRight() {
fakeSwipe(ItemTouchHelper.RIGHT)
}
private fun fakeSwipeLeft() {
fakeSwipe(ItemTouchHelper.LEFT)
}
}

View file

@ -0,0 +1,133 @@
package org.ligi.passandroid;
import android.annotation.TargetApi;
import android.support.test.filters.MediumTest;
import java.util.ArrayList;
import javax.inject.Inject;
import org.ligi.passandroid.model.PassStore;
import org.ligi.passandroid.model.pass.BarCode;
import org.ligi.passandroid.model.pass.PassBarCodeFormat;
import org.ligi.passandroid.model.pass.PassImpl;
import org.ligi.passandroid.ui.PassViewActivity;
import org.threeten.bp.ZonedDateTime;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.IsNot.not;
@TargetApi(14)
public class ThePassViewActivity extends BaseIntegration<PassViewActivity> {
@Inject
PassStore passStore;
PassImpl act_pass;
public ThePassViewActivity() {
super(PassViewActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
final TestComponent newComponent = DaggerTestComponent.create();
newComponent.inject(this);
App.setComponent(newComponent);
act_pass = (PassImpl) passStore.getCurrentPass();
}
@MediumTest
public void testThatDescriptionIsThere() {
getActivity();
onView(withText(act_pass.getDescription())).check(matches(isDisplayed()));
}
@MediumTest
public void testDateIsGoneWhenPassbookHasNoDate() {
getActivity();
onView(withId(R.id.date)).check(matches(not(isDisplayed())));
}
@MediumTest
public void testDateIsThereWhenPassbookHasDate() {
act_pass.setCalendarTimespan(new PassImpl.TimeSpan(ZonedDateTime.now(), null, null));
getActivity();
onView(withId(R.id.date)).check(matches(isDisplayed()));
}
@MediumTest
public void testLinkToCalendarIsThereWhenPassbookHasDate() {
act_pass.setCalendarTimespan(new PassImpl.TimeSpan(ZonedDateTime.now(), null, null));
getActivity();
onView(withId(R.id.addCalendar)).check(matches(isDisplayed()));
}
@MediumTest
public void testClickOnCalendarWithExpirationDateGivesWarning() {
final ArrayList<PassImpl.TimeSpan> validTimespans = new ArrayList<>();
validTimespans.add(new PassImpl.TimeSpan(null, ZonedDateTime.now().minusHours(12), null));
act_pass.setValidTimespans(validTimespans);
act_pass.setCalendarTimespan(null);
getActivity();
onView(withId(R.id.addCalendar)).perform(click());
onView(withText(R.string.expiration_date_to_calendar_warning_message)).check(matches(isDisplayed()));
}
@MediumTest
public void testThatTheDialogCanBeDismissed() {
testClickOnCalendarWithExpirationDateGivesWarning();
onView(withText(android.R.string.cancel)).perform(click());
onView(withText(R.string.expiration_date_to_calendar_warning_message)).check(doesNotExist());
}
@MediumTest
public void testLinkToCalendarIsNotThereWhenPassbookHasNoDate() {
getActivity();
onView(withId(R.id.addCalendar)).check(matches(not(isDisplayed())));
}
@MediumTest
public void testClickOnBarcodeOpensFullscreenImage() {
getActivity();
onView(withId(R.id.barcode_img)).perform(click());
onView(withId(R.id.fullscreen_barcode)).check(matches(isDisplayed()));
}
@MediumTest
public void testZoomControlsAreThereWithBarcode() {
act_pass.setBarCode(new BarCode(PassBarCodeFormat.AZTEC, "foo"));
getActivity();
onView(withId(R.id.zoomIn)).check(matches(isDisplayed()));
onView(withId(R.id.zoomIn)).check(matches(isDisplayed()));
}
@MediumTest
public void testZoomControlsAreGoneWithoutBarcode() {
act_pass.setBarCode(null);
getActivity();
onView(withId(R.id.zoomIn)).check(matches(not(isDisplayed())));
onView(withId(R.id.zoomIn)).check(matches(not(isDisplayed())));
}
}

View file

@ -1,134 +0,0 @@
package org.ligi.passandroid
import android.annotation.TargetApi
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import org.hamcrest.core.IsNot.not
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.model.pass.BarCode
import org.ligi.passandroid.model.pass.PassBarCodeFormat
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.model.pass.PassLocation
import org.ligi.passandroid.ui.PassViewActivity
import org.ligi.trulesk.TruleskActivityRule
import org.threeten.bp.ZonedDateTime
import java.util.*
@TargetApi(14)
class ThePassViewActivity {
private fun getActPass() = TestApp.passStore.currentPass as PassImpl
@get:Rule
var rule = TruleskActivityRule(PassViewActivity::class.java, false)
@Before
fun before() {
TestApp.populatePassStoreWithSinglePass()
}
@Test
fun testThatDescriptionIsThere() {
rule.launchActivity(null)
onView(withText(getActPass().description)).check(matches(isDisplayed()))
}
@Test
fun testDateIsGoneWhenPassbookHasNoDate() {
getActPass().validTimespans = ArrayList()
rule.launchActivity(null)
onView(withId(R.id.date)).check(matches(not(isDisplayed())))
}
@Test
fun testEverythingWorksWhenWeHaveSomeLocation() {
val timeSpen = ArrayList<PassLocation>()
timeSpen.add(PassLocation())
getActPass().locations = timeSpen
rule.launchActivity(null)
onView(withId(R.id.date)).check(matches(not(isDisplayed())))
}
@Test
fun testDateIsThereWhenPassbookHasDate() {
getActPass().calendarTimespan = PassImpl.TimeSpan(ZonedDateTime.now(), null, null)
rule.launchActivity(null)
onView(withId(R.id.date)).check(matches(isDisplayed()))
}
@Test
fun testLinkToCalendarIsThereWhenPassbookHasDate() {
getActPass().calendarTimespan = PassImpl.TimeSpan(ZonedDateTime.now(), null, null)
rule.launchActivity(null)
onView(withText(R.string.pass_to_calendar)).check(matches(isDisplayed()))
}
@Test
fun testClickOnCalendarWithExpirationDateGivesWarning() {
val validTimespans = ArrayList<PassImpl.TimeSpan>()
validTimespans.add(PassImpl.TimeSpan(null, ZonedDateTime.now().minusHours(12), null))
getActPass().validTimespans = validTimespans
getActPass().calendarTimespan = null
rule.launchActivity(null)
onView(withText(R.string.pass_to_calendar)).perform(click())
onView(withText(R.string.expiration_date_to_calendar_warning_message)).check(matches(isDisplayed()))
}
@Test
fun testThatTheDialogCanBeDismissed() {
testClickOnCalendarWithExpirationDateGivesWarning()
onView(withText(android.R.string.cancel)).perform(click())
onView(withText(R.string.expiration_date_to_calendar_warning_message)).check(doesNotExist())
}
@Test
fun testLinkToCalendarIsNotThereWhenPassbookHasNoDate() {
getActPass().validTimespans = ArrayList()
rule.launchActivity(null)
onView(withText(R.string.pass_to_calendar)).check(matches(not(isDisplayed())))
}
@Test
fun testClickOnBarcodeOpensFullscreenImage() {
getActPass().barCode = BarCode(PassBarCodeFormat.QR_CODE, "foo")
rule.launchActivity(null)
onView(withId(R.id.barcode_img)).perform(click())
onView(withId(R.id.fullscreen_barcode)).check(matches(isDisplayed()))
}
@Test
fun testZoomControlsAreThereWithBarcode() {
getActPass().barCode = BarCode(PassBarCodeFormat.AZTEC, "foo")
rule.launchActivity(null)
onView(withId(R.id.zoomIn)).check(matches(isDisplayed()))
onView(withId(R.id.zoomIn)).check(matches(isDisplayed()))
}
@Test
fun testZoomControlsAreGoneWithoutBarcode() {
getActPass().barCode = null
rule.launchActivity(null)
onView(withId(R.id.zoomIn)).check(matches(not(isDisplayed())))
onView(withId(R.id.zoomIn)).check(matches(not(isDisplayed())))
}
}

View file

@ -1,76 +0,0 @@
package org.ligi.passandroid
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import org.hamcrest.CoreMatchers.not
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.model.pass.PassLocation
import org.ligi.passandroid.ui.PassListActivity
import org.ligi.trulesk.TruleskActivityRule
import org.threeten.bp.ZoneId
import org.threeten.bp.ZonedDateTime
class ThePassViewHolder {
private val currentPass by lazy {
TestApp.populatePassStoreWithSinglePass()
TestApp.passStore.currentPass as PassImpl
}
@get:Rule
var rule = TruleskActivityRule(PassListActivity::class.java, false)
@Test
fun locationButtonShouldBeVisibleIfWeHaveALocation() {
currentPass.locations = listOf(PassLocation())
rule.launchActivity()
onView(withId(R.id.locationButton)).check(ViewAssertions.matches(isDisplayed()))
rule.screenShot("with_location")
}
@Test
fun locationButtonShouldNotShowIfWeHaveNoLocation() {
currentPass.locations = listOf()
rule.launchActivity()
onView(withId(R.id.locationButton)).check(ViewAssertions.matches(not(isDisplayed())))
rule.screenShot("no_location")
}
@Test
fun dateButtonShouldBeVisibleIfWeHaveADate() {
currentPass.calendarTimespan = PassImpl.TimeSpan(ZonedDateTime.of(2016, 11, 23, 20, 42, 42, 5, ZoneId.systemDefault()))
rule.launchActivity()
onView(withId(R.id.timeButton)).check(ViewAssertions.matches(isDisplayed()))
rule.screenShot("with_date")
}
@Test
fun dateButtonShouldNotBeVisibleIfWeHaveNoDate() {
currentPass.calendarTimespan = null
rule.launchActivity()
onView(withId(R.id.timeButton)).check(ViewAssertions.matches(not(isDisplayed())))
rule.screenShot("no_date")
}
}

View file

@ -0,0 +1,60 @@
package org.ligi.passandroid;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.test.filters.SmallTest;
import javax.inject.Inject;
import org.ligi.passandroid.model.PastLocationsStore;
import static org.assertj.core.api.Assertions.assertThat;
public class ThePastLocationsStore extends BaseIntegration<Activity> {
@Inject
Tracker tracker;
private SharedPreferences sharedPreferences;
public ThePastLocationsStore() {
super(Activity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
App.setComponent(DaggerTestComponent.builder().testModule(new TestModule()).build());
sharedPreferences = getInstrumentation().getContext().getSharedPreferences("" + (System.currentTimeMillis() / 100000), Context.MODE_PRIVATE);
((TestComponent) App.component()).inject(this);
}
@Override
protected void tearDown() throws Exception {
sharedPreferences.edit().clear().apply();
super.tearDown();
}
@SmallTest
public void testPastLocationsStoreShouldNeverContainMoreThanMaxElements() {
PastLocationsStore tested = new PastLocationsStore(sharedPreferences, tracker);
for (int i = 0; i < PastLocationsStore.MAX_ELEMENTS * 2; i++) {
tested.putLocation("" + i);
}
assertThat(tested.getLocations().size()).isEqualTo(PastLocationsStore.MAX_ELEMENTS);
}
@SmallTest
public void testPastLocationsStoreShouldStoreOnlyOneOfAKind() {
PastLocationsStore tested = new PastLocationsStore(sharedPreferences, tracker);
for (int i = 0; i < 3; i++) {
tested.putLocation("foo");
}
assertThat(tested.getLocations()).containsOnly("foo");
}
}

View file

@ -1,58 +0,0 @@
package org.ligi.passandroid
import android.content.Context
import android.content.SharedPreferences
import androidx.test.platform.app.InstrumentationRegistry
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.model.PastLocationsStore
import org.ligi.passandroid.ui.PassViewActivity
import org.ligi.trulesk.TruleskActivityRule
import org.mockito.Mock
import org.mockito.MockitoAnnotations
class ThePastLocationsStore {
@get:Rule
var rule = TruleskActivityRule(PassViewActivity::class.java) {
TestApp.populatePassStoreWithSinglePass()
MockitoAnnotations.initMocks(this)
}
@Mock
lateinit var tracker: Tracker
private val prefs: SharedPreferences by lazy { InstrumentationRegistry.getInstrumentation().context.getSharedPreferences("" + System.currentTimeMillis() / 100000, Context.MODE_PRIVATE) }
@After
fun tearDown() {
prefs.edit().clear().apply()
}
@Test
fun testPastLocationsStoreShouldNeverContainMoreThanMaxElements() {
val tested = PastLocationsStore(prefs, tracker)
for (i in 0 until PastLocationsStore.MAX_ELEMENTS * 2) {
tested.putLocation("" + i)
}
assertThat(tested.locations.size).isEqualTo(PastLocationsStore.MAX_ELEMENTS)
}
@Test
fun testPastLocationsStoreShouldStoreOnlyOneOfAKind() {
val tested = PastLocationsStore(prefs, tracker)
for (i in 0..2) {
tested.putLocation("foo")
}
assertThat(tested.locations).containsOnly("foo")
}
}

View file

@ -1,102 +0,0 @@
package org.ligi.passandroid
import android.Manifest
import android.os.Build
import androidx.appcompat.app.AppCompatDelegate
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.linkedin.android.testbutler.TestButler
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.model.AndroidSettings
import org.ligi.passandroid.model.comparator.PassByTimeComparator
import org.ligi.passandroid.model.comparator.PassByTypeFirstAndTimeSecondComparator
import org.ligi.passandroid.model.comparator.PassTemporalDistanceComparator
import org.ligi.passandroid.ui.PreferenceActivity
import org.ligi.trulesk.TruleskActivityRule
class ThePreferenceActivity {
@get:Rule
val rule = TruleskActivityRule(PreferenceActivity::class.java) {
TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION)
}
private val androidSettings by lazy { AndroidSettings(rule.activity) }
@Test
fun autoLightToggles() {
rule.screenShot("preferences")
val automaticLightEnabled = androidSettings.isAutomaticLightEnabled()
onView(withText(R.string.preference_autolight_title)).perform(click())
assertThat(automaticLightEnabled).isEqualTo(!androidSettings.isAutomaticLightEnabled())
onView(withText(R.string.preference_autolight_title)).perform(click())
assertThat(automaticLightEnabled).isEqualTo(androidSettings.isAutomaticLightEnabled())
}
@Test
fun condensedToggles() {
val condensedModeEnabled = androidSettings.isCondensedModeEnabled()
onView(withText(R.string.preference_condensed_title)).perform(click())
assertThat(condensedModeEnabled).isEqualTo(!androidSettings.isCondensedModeEnabled())
onView(withText(R.string.preference_condensed_title)).perform(click())
assertThat(condensedModeEnabled).isEqualTo(androidSettings.isCondensedModeEnabled())
}
@Test
fun weCanSetAllSortOrders() {
val resources = rule.activity.resources
val sortOrders = resources.getStringArray(R.array.sort_orders)
sortOrders.forEach { sortOrder ->
onView(withText(R.string.preference_sort_title)).perform(click())
onView(withText(sortOrder)).perform(click())
assertThat(androidSettings.getSortOrder().toComparator()).isInstanceOf(when (sortOrder) {
resources.getString(R.string.sort_order_date_asc) -> PassByTimeComparator::class.java
resources.getString(R.string.sort_order_date_desc) -> PassByTimeComparator::class.java
resources.getString(R.string.sort_order_date_type) -> PassByTypeFirstAndTimeSecondComparator::class.java
resources.getString(R.string.sort_order_date_temporaldistance) -> PassTemporalDistanceComparator::class.java
else -> throw RuntimeException("unexpected sort order")
})
}
}
@Test
fun weCanSetAllNightModes() {
val resources = rule.activity.resources
val sortOrders = resources.getStringArray(R.array.night_modes)
sortOrders.filterNot { Build.VERSION.SDK_INT >= 21 && it == resources.getString(R.string.night_mode_auto) }.forEach { sortOrder ->
onView(withText(R.string.preference_daynight_title)).perform(click())
onView(withText(sortOrder)).perform(click())
assertThat(androidSettings.getNightMode()).isEqualTo(when (sortOrder) {
resources.getString(R.string.night_mode_day) -> AppCompatDelegate.MODE_NIGHT_NO
resources.getString(R.string.night_mode_night) -> AppCompatDelegate.MODE_NIGHT_YES
resources.getString(R.string.night_mode_auto) -> AppCompatDelegate.MODE_NIGHT_AUTO
else -> throw RuntimeException("unexpected night-mode")
})
}
}
}

View file

@ -1,15 +1,17 @@
package org.ligi.passandroid
import android.support.test.runner.AndroidJUnit4
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.ligi.passandroid.functions.loadPassFromAsset
import org.junit.runner.RunWith
class TheQuirkCorrector {
@RunWith(AndroidJUnit4::class)
class TheQuirkCorrector : TheAppleStyleBarcodeReaderBase() {
@Test
fun testWestbahnDescriptionIsFixed() {
loadPassFromAsset("passes/workarounds/westbahn/special.pkpass") {
assertThat(it!!.description).isEqualTo("Wien Westbahnhof->Amstetten")
assertThat(it.description).isEqualTo("Wien Westbahnhof->Amstetten")
}
}

View file

@ -1,6 +1,6 @@
package org.ligi.passandroid;
import androidx.test.platform.app.InstrumentationRegistry;
import android.support.test.InstrumentationRegistry;
import java.io.InputStream;
import org.junit.Before;
import org.junit.Test;
@ -27,7 +27,7 @@ public class TheUnzipPassController {
PassStore passStore;
@Before
public void setUp() {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@ -49,7 +49,7 @@ public class TheUnzipPassController {
verify(failCallback).fail(any(String.class));
} catch (Exception e) {
fail("should be able to load file " + e);
fail("should be able to load file");
}
}

View file

@ -1,22 +0,0 @@
package org.ligi.passandroid.functions
import android.graphics.Bitmap
import com.google.zxing.BinaryBitmap
import com.google.zxing.MultiFormatReader
import com.google.zxing.RGBLuminanceSource
import com.google.zxing.common.HybridBinarizer
fun Bitmap.decodeBarCode(): String {
val intArray = IntArray(this.width * this.height)
this.getPixels(intArray, 0, this.width, 0, 0, this.width, this.height)
val source = RGBLuminanceSource(this.width, this.height, intArray)
val bitmap = BinaryBitmap(HybridBinarizer(source))
val reader = MultiFormatReader()// use this otherwise ChecksumException
val result = reader.decode(bitmap)
return result.text
}

View file

@ -1,37 +0,0 @@
package org.ligi.passandroid.functions
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import android.view.View
import net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher
fun expand(): ViewAction? = ExpandFabAction()
class ExpandFabAction : ViewAction {
override fun getConstraints(): Matcher<View> = isAssignableFrom(FloatingActionsMenu::class.java)
override fun getDescription() = "expands the floating action menu"
override fun perform(uiController: UiController?, view: View?) {
val fam = view as FloatingActionsMenu
fam.expand()
}
}
class CollapsedCheck : TypeSafeMatcher<FloatingActionsMenu>(FloatingActionsMenu::class.java) {
override fun describeTo(description: Description?) {
description?.appendText("is in collapsed state")
}
override fun matchesSafely(fam: FloatingActionsMenu?): Boolean {
return !fam?.isExpanded!!
}
}

View file

@ -1,11 +0,0 @@
package org.ligi.passandroid.functions
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import org.ligi.passandroid.R
fun checkThatHelpIsThere() {
onView(withId(R.id.help_text)).check(matches(isDisplayed()))
}

View file

@ -1,6 +0,0 @@
package org.ligi.passandroid.functions
import android.view.View
import org.hamcrest.Matcher
fun isCollapsed(): Matcher<in View>? = CollapsedCheck() as Matcher<in View>

View file

@ -1,50 +0,0 @@
package org.ligi.passandroid.functions
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import org.assertj.core.api.Fail.fail
import org.ligi.passandroid.TestApp
import org.ligi.passandroid.model.InputStreamWithSource
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.reader.AppleStylePassReader
import org.ligi.passandroid.ui.UnzipPassController
import org.ligi.passandroid.ui.UnzipPassController.FailCallback
import org.ligi.passandroid.ui.UnzipPassController.InputStreamUnzipControllerSpec
import org.mockito.Mockito.*
import java.io.File
private fun getTestTargetPath(context: Context) = File(context.cacheDir, "test_passes")
fun loadPassFromAsset(asset: String, callback: (pass: Pass?) -> Unit) {
try {
val instrumentation = InstrumentationRegistry.getInstrumentation()
val inputStream = instrumentation.context.resources.assets.open(asset)
val inputStreamWithSource = InputStreamWithSource("none", inputStream)
val mock = mock(FailCallback::class.java)
val spec = InputStreamUnzipControllerSpec(inputStreamWithSource, instrumentation.targetContext, mock(
PassStore::class.java),
object : UnzipPassController.SuccessCallback {
override fun call(uuid: String) {
callback.invoke(AppleStylePassReader.read(File(getTestTargetPath(instrumentation.targetContext), uuid), "en",
instrumentation.targetContext,TestApp.tracker))
}
},
mock
)
spec.overwrite = true
spec.targetPath = getTestTargetPath(spec.context)
UnzipPassController.processInputStream(spec)
verify(mock, never()).fail(anyString())
} catch (e: Exception) {
fail("should be able to load file ", e)
}
}

View file

@ -0,0 +1,24 @@
package org.ligi.passandroid.helper
import android.graphics.Bitmap
import com.google.zxing.BinaryBitmap
import com.google.zxing.MultiFormatReader
import com.google.zxing.RGBLuminanceSource
import com.google.zxing.common.HybridBinarizer
object BarcodeDecoder {
fun decodeBitmap(bMap: Bitmap): String {
val intArray = IntArray(bMap.width * bMap.height)
bMap.getPixels(intArray, 0, bMap.width, 0, 0, bMap.width, bMap.height)
val source = RGBLuminanceSource(bMap.width, bMap.height, intArray)
val bitmap = BinaryBitmap(HybridBinarizer(source))
val reader = MultiFormatReader()// use this otherwise ChecksumException
val result = reader.decode(bitmap)
return result.text
}
}

View file

@ -0,0 +1,14 @@
package org.ligi.passandroid.helper
import android.app.Activity
import com.jraska.falcon.FalconSpoon
/*
Facade for screenshot's
*/
object ScreenshotTaker {
fun takeScreenshot(activity: Activity, message: String) {
FalconSpoon.screenshot(activity, message)
}
}

View file

@ -1,47 +1,28 @@
package org.ligi.passandroid.injections
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import org.ligi.passandroid.model.PassClassifier
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.PassStoreUpdateEvent
import org.ligi.passandroid.model.pass.Pass
import java.io.File
import java.util.*
class FixedPassListPassStore(private var passes: List<Pass>) : PassStore {
override lateinit var classifier: PassClassifier
init {
classifier = PassClassifier(HashMap(), this)
}
fun setList(newPasses: List<Pass>, newCurrentPass: Pass? = newPasses.firstOrNull()) {
currentPass = newCurrentPass
passes = newPasses
passMap.clear()
passMap.putAll(createHashMap())
classifier = PassClassifier(HashMap(), this)
}
class FixedPassListPassStore(private val passes: List<Pass>) : PassStore {
override var currentPass: Pass? = null
override val passMap: HashMap<String, Pass> by lazy {
return@lazy createHashMap()
}
private fun createHashMap(): HashMap<String, Pass> {
override val passMap: Map<String, Pass> by lazy {
val hashMap = HashMap<String, Pass>()
passes.forEach { hashMap[it.id] = it }
return hashMap
passes.forEach { hashMap.put(it.id, it) }
return@lazy hashMap
}
override fun getPassbookForId(id: String): Pass? {
return passMap[id]
}
override val classifier: PassClassifier by lazy {
PassClassifier(HashMap<String, String>(), this)
}
override fun deletePassWithId(id: String): Boolean {
return false
@ -51,8 +32,6 @@ class FixedPassListPassStore(private var passes: List<Pass>) : PassStore {
return File("")
}
override val updateChannel: BroadcastChannel<PassStoreUpdateEvent> = ConflatedBroadcastChannel()
override fun save(pass: Pass) {
// no effect in this impl
}

View file

@ -0,0 +1,43 @@
package org.ligi.passandroid.reporting
import android.app.Activity
import android.app.Instrumentation
import android.support.test.espresso.FailureHandler
import android.support.test.espresso.base.DefaultFailureHandler
import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
import android.support.test.runner.lifecycle.Stage
import android.view.View
import org.hamcrest.Matcher
import org.ligi.passandroid.helper.ScreenshotTaker
class SpooningFailureHandler(private val instrumentation: Instrumentation) : FailureHandler {
private val delegate by lazy { DefaultFailureHandler(instrumentation.targetContext) }
override fun handle(error: Throwable, viewMatcher: Matcher<View>) {
try {
ScreenshotTaker.takeScreenshot(currentActivity, "error_falcon")
} catch (throwable: Throwable) {
throwable.printStackTrace()
}
delegate.handle(error, viewMatcher)
}
private val currentActivity: Activity
@Throws(Throwable::class)
get() {
instrumentation.waitForIdleSync()
val activity = arrayOfNulls<Activity>(1)
instrumentation.runOnMainSync {
val activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED)
activity[0] = activities.iterator().next()
}
return activity[0]!!
}
}

View file

@ -0,0 +1,17 @@
package org.ligi.passandroid.steps;
import org.ligi.passandroid.R;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
public class HelpSteps {
public static void checkThatHelpIsThere() {
onView(withId(R.id.help_text)).check(matches(isDisplayed()));
}
}

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="market_url" translatable="false">amzn://apps/android?p=%s</string>
<string name="nav_market" translatable="false">Play</string>
</resources>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="market_url" translatable="false">https://f-droid.org/repository/browse/?fdid=%s</string>
<string name="market_url">https://f-droid.org/repository/browse/?fdid=%s</string>
</resources>

View file

@ -11,7 +11,6 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission-sdk-23 android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!--
@ -29,7 +28,6 @@
<receiver
android:name=".InstallListener"
android:permission="android.permission.INSTALL_PACKAGES"
android:exported="true">
<intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER"/>
@ -37,21 +35,21 @@
</receiver>
<provider
android:name="androidx.core.content.FileProvider"
android:name="android.support.v4.content.FileProvider"
android:authorities="@string/authority_fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths"/>
android:resource="@xml/filepaths" />
</provider>
<service android:name=".scan.SearchPassesIntentService"/>
<service android:name=".ui.SearchPassesIntentService"/>
<activity
android:name=".ui.PassListActivity"
android:label="@string/app_name"
android:theme="@style/AppThemeNoActionbar">
android:theme="@style/AppBaseThemeNoActionbar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@ -64,7 +62,7 @@
<activity
android:name=".ui.HelpActivity"
android:theme="@style/AppThemeNoActionbar"/>
android:theme="@style/AppBaseThemeNoActionbar"/>
<activity
android:name=".ui.PassEditActivity"/>
<activity
@ -123,35 +121,39 @@
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" android:mimeType="application/vnd.espass-espass"/>
<data android:scheme="https" android:mimeType="application/vnd.espass-espass"/>
<data android:scheme="content" android:mimeType="application/vnd.espass-espass"/>
<data android:scheme="file" android:mimeType="application/vnd.espass-espass"/>
<data android:mimeType="application/vnd.espass-espass"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="http" android:mimeType="application/vnd.espass-espass+zip"/>
<data android:scheme="https" android:mimeType="application/vnd.espass-espass+zip"/>
<data android:scheme="content" android:mimeType="application/vnd.espass-espass+zip"/>
<data android:scheme="file" android:mimeType="application/vnd.espass-espass+zip"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" android:mimeType="application/vnd.apple.pkpass"/>
<data android:scheme="https" android:mimeType="application/vnd.apple.pkpass"/>
<data android:scheme="content" android:mimeType="application/vnd.apple.pkpass"/>
<data android:scheme="file" android:mimeType="application/vnd.apple.pkpass"/>
<data android:mimeType="application/vnd.espass-espass+zip"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="http" android:mimeType="application/pkpass"/>
<data android:scheme="https" android:mimeType="application/pkpass"/>
<data android:scheme="content" android:mimeType="application/pkpass"/>
<data android:scheme="file" android:mimeType="application/pkpass"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" android:mimeType="application/vndapplepkpass"/>
<data android:scheme="https" android:mimeType="application/vndapplepkpass"/>
<data android:scheme="content" android:mimeType="application/vndapplepkpass"/>
<data android:scheme="file" android:mimeType="application/vndapplepkpass"/>
<data android:mimeType="application/vnd.apple.pkpass"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="http" android:mimeType="application/vnd-com.apple.pkpass"/>
<data android:scheme="https" android:mimeType="application/vnd-com.apple.pkpass"/>
<data android:scheme="content" android:mimeType="application/vnd-com.apple.pkpass"/>
<data android:scheme="file" android:mimeType="application/vnd-com.apple.pkpass"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/vndapplepkpass"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/vnd-com.apple.pkpass"/>
</intent-filter>
<intent-filter>
@ -553,460 +555,6 @@
android:scheme="content"/>
</intent-filter>
<!-- pdf -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="*"
android:pathPattern="/.*\\.pdf"
android:scheme="http"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="*"
android:pathPattern="/.*\\.pdf"
android:scheme="https"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="*"
android:pathPattern="/.*\\.pdf"
android:scheme="file"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="*"
android:pathPattern="/.*\\.pdf"
android:scheme="content"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="content" android:mimeType="application/x-pdf"/>
<data android:scheme="content" android:mimeType="application/pdf"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="*"
android:pathPattern="/.*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="*"
android:pathPattern="/.*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="*"
android:pathPattern="/.*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="*"
android:pathPattern="/.*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
</intent-filter>
<!-- pkpass -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
@ -1052,6 +600,22 @@
android:pathPattern="/.*\\.pkpass"
android:scheme="content"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/vnd.apple.pkpass"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/vnd-com.apple.pkpass"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
@ -1223,7 +787,8 @@
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pkpass"
android:scheme="https"/>
<data android:host="*"
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pkpass"
android:scheme="https"/>
<data
@ -1548,7 +1113,7 @@
<data
android:scheme="http"
android:host="pass-cloud.appspot.com"
/>
/>
</intent-filter>
<intent-filter>
@ -1569,28 +1134,6 @@
<data android:scheme="passandroid"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="mci.aircanada.com"
android:scheme="http"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="mci.aircanada.com"
android:scheme="https"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
@ -1602,6 +1145,7 @@
android:scheme="http"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
@ -1698,7 +1242,6 @@
</activity>
<activity android:name=".ui.quirk_fix.OpenIphoneWebView"/>
<activity android:name=".ui.TouchImageActivity"/>
<activity android:name=".ui.quirk_fix.USAirwaysLoadActivity">
<intent-filter>
@ -1792,9 +1335,8 @@
<activity
android:name=".ui.PassViewActivity"
android:theme="@style/AppThemeNoActionbar"
android:theme="@style/AppBaseThemeNoActionbar"
android:label="@string/app_name"
android:exported="true"
android:parentActivityName=".ui.PassListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@ -1804,7 +1346,6 @@
<activity
android:name=".ui.FullscreenBarcodeActivity"
android:label="@string/app_name"/>
<activity android:name=".scan.PassScanActivity" />
</application>

View file

@ -1,9 +1,9 @@
/*
*******************************************************************************
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
/**
*******************************************************************************
* Copyright (C) 2005-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
import java.io.IOException;
@ -35,7 +35,6 @@ import java.util.List;
* <p/>
* @stable ICU 3.4
*/
@SuppressWarnings("ALL")
public class CharsetDetector {
// Question: Should we have getters corresponding to the setters for input text
@ -183,7 +182,7 @@ public class CharsetDetector {
* @stable ICU 3.4
*/
public CharsetMatch[] detectAll() {
ArrayList<CharsetMatch> matches = new ArrayList<>();
ArrayList<CharsetMatch> matches = new ArrayList<CharsetMatch>();
MungeInput(); // Strip html markup, collect byte stats.
@ -345,7 +344,7 @@ public class CharsetDetector {
* it by removing what appears to be html markup.
*/
private void MungeInput() {
int srci;
int srci = 0;
int dsti = 0;
byte b;
boolean inMarkup = false;
@ -474,7 +473,7 @@ public class CharsetDetector {
private static final List<CSRecognizerInfo> ALL_CS_RECOGNIZERS;
static {
List<CSRecognizerInfo> list = new ArrayList<>();
List<CSRecognizerInfo> list = new ArrayList<CSRecognizerInfo>();
list.add(new CSRecognizerInfo(new CharsetRecog_UTF8(), true));
list.add(new CSRecognizerInfo(new CharsetRecog_Unicode.CharsetRecog_UTF_16_BE(), true));
@ -523,7 +522,7 @@ public class CharsetDetector {
*/
@Deprecated
public String[] getDetectableCharsets() {
List<String> csnames = new ArrayList<>(ALL_CS_RECOGNIZERS.size());
List<String> csnames = new ArrayList<String>(ALL_CS_RECOGNIZERS.size());
for (int i = 0; i < ALL_CS_RECOGNIZERS.size(); i++) {
CSRecognizerInfo rcinfo = ALL_CS_RECOGNIZERS.get(i);
boolean active = (fEnabledRecognizers == null) ? rcinfo.isDefaultEnabled : fEnabledRecognizers[i];
@ -531,7 +530,7 @@ public class CharsetDetector {
csnames.add(rcinfo.recognizer.getName());
}
}
return csnames.toArray(new String[0]);
return csnames.toArray(new String[csnames.size()]);
}
/**

View file

@ -1,9 +1,9 @@
/*
*******************************************************************************
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
/**
*******************************************************************************
* Copyright (C) 2005-2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
import java.io.ByteArrayInputStream;
@ -26,7 +26,6 @@ import java.io.Reader;
*
* @stable ICU 3.4
*/
@SuppressWarnings("ALL")
public class CharsetMatch implements Comparable<CharsetMatch> {
@ -86,13 +85,13 @@ public class CharsetMatch implements Comparable<CharsetMatch> {
* @stable ICU 3.4
*/
public String getString(int maxLength) throws java.io.IOException {
String result;
String result = null;
if (fInputStream != null) {
StringBuilder sb = new StringBuilder();
char[] buffer = new char[1024];
Reader reader = getReader();
int max = maxLength < 0? Integer.MAX_VALUE : maxLength;
int bytesRead;
int bytesRead = 0;
while ((bytesRead = reader.read(buffer, 0, Math.min(max, 1024))) >= 0) {
sb.append(buffer, 0, bytesRead);
@ -109,7 +108,6 @@ public class CharsetMatch implements Comparable<CharsetMatch> {
* be used to open a charset (e.g. IBM424_rtl). The ending '_rtl' or 'ltr'
* should be stripped off before creating the string.
*/
//noinspection IndexOfReplaceableByContains
int startSuffix = name.indexOf("_rtl") < 0 ? name.indexOf("_ltr") : name.indexOf("_rtl");
if (startSuffix > 0) {
name = name.substring(0, startSuffix);
@ -236,7 +234,7 @@ public class CharsetMatch implements Comparable<CharsetMatch> {
// If user gave us a byte array, this is it.
private int fRawLength; // Length of data in fRawInput array.
private InputStream fInputStream; // User's input stream, or null if the user
private InputStream fInputStream = null; // User's input stream, or null if the user
// gave us a byte array.
private String fCharsetName; // The name of the charset this CharsetMatch

View file

@ -1,9 +1,9 @@
/*
*******************************************************************************
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
*******************************************************************************
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
/**
@ -14,7 +14,6 @@ package com.ibm.icu.text;
*
* The separate classes are nested within this class.
*/
@SuppressWarnings("ALL")
abstract class CharsetRecog_2022 extends CharsetRecognizer {
@ -45,7 +44,7 @@ abstract class CharsetRecog_2022 extends CharsetRecognizer {
byte [] seq = escapeSequences[escN];
if ((textLen - i) < seq.length) {
continue;
continue checkEscapes;
}
for (j=1; j<seq.length; j++) {
@ -93,6 +92,9 @@ abstract class CharsetRecog_2022 extends CharsetRecognizer {
return quality;
}
static class CharsetRecog_2022JP extends CharsetRecog_2022 {
private byte [] [] escapeSequences = {
{0x1b, 0x24, 0x28, 0x43}, // KS X 1001:1992

View file

@ -1,15 +1,14 @@
/*
*******************************************************************************
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
/**
*******************************************************************************
* Copyright (C) 2005 - 2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
/**
* Charset recognizer for UTF-8
*/
@SuppressWarnings("ALL")
class CharsetRecog_UTF8 extends CharsetRecognizer {
String getName() {
@ -25,7 +24,7 @@ class CharsetRecog_UTF8 extends CharsetRecognizer {
int numInvalid = 0;
byte input[] = det.fRawInput;
int i;
int trailBytes;
int trailBytes = 0;
int confidence;
if (det.fRawLength >= 3 &&

View file

@ -12,7 +12,6 @@ package com.ibm.icu.text;
* This class matches UTF-16 and UTF-32, both big- and little-endian. The
* BOM will be used if it is present.
*/
@SuppressWarnings("ALL")
abstract class CharsetRecog_Unicode extends CharsetRecognizer {
/* (non-Javadoc)
@ -172,7 +171,7 @@ abstract class CharsetRecog_Unicode extends CharsetRecognizer {
{
int getChar(byte[] input, int index)
{
return (input[index] & 0xFF) << 24 | (input[index + 1] & 0xFF) << 16 |
return (input[index + 0] & 0xFF) << 24 | (input[index + 1] & 0xFF) << 16 |
(input[index + 2] & 0xFF) << 8 | (input[index + 3] & 0xFF);
}
@ -188,7 +187,7 @@ abstract class CharsetRecog_Unicode extends CharsetRecognizer {
int getChar(byte[] input, int index)
{
return (input[index + 3] & 0xFF) << 24 | (input[index + 2] & 0xFF) << 16 |
(input[index + 1] & 0xFF) << 8 | (input[index] & 0xFF);
(input[index + 1] & 0xFF) << 8 | (input[index + 0] & 0xFF);
}
String getName()

View file

@ -1,8 +1,9 @@
/*
*******************************************************************************
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
****************************************************************************
* Copyright (C) 2005-2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
****************************************************************************
*
*/
package com.ibm.icu.text;
@ -20,7 +21,6 @@ import java.util.Arrays;
* encodings to be checked. The specific encoding being recognized
* is determined by subclass.
*/
@SuppressWarnings("ALL")
abstract class CharsetRecog_mbcs extends CharsetRecognizer {
/**
@ -157,7 +157,8 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
done = true;
return -1;
}
return (int)det.fRawInput[nextIndex++] & 0x00ff;
int byteValue = (int)det.fRawInput[nextIndex++] & 0x00ff;
return byteValue;
}
}
@ -320,9 +321,9 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
boolean nextChar(iteratedChar it, CharsetDetector det) {
it.index = it.nextIndex;
it.error = false;
int firstByte;
int secondByte;
int thirdByte;
int firstByte = 0;
int secondByte = 0;
int thirdByte = 0;
//int fourthByte = 0;
buildChar: {
@ -371,7 +372,7 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
}
}
return (!it.done);
return (it.done == false);
}
/**
@ -460,10 +461,10 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
boolean nextChar(iteratedChar it, CharsetDetector det) {
it.index = it.nextIndex;
it.error = false;
int firstByte;
int secondByte;
int thirdByte;
int fourthByte;
int firstByte = 0;
int secondByte = 0;
int thirdByte = 0;
int fourthByte = 0;
buildChar: {
firstByte = it.charValue = it.nextByte(det);
@ -482,7 +483,7 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
secondByte = it.nextByte(det);
it.charValue = (it.charValue << 8) | secondByte;
if (firstByte <= 0xFE) {
if (firstByte >= 0x81 && firstByte <= 0xFE) {
// Two byte Char
if ((secondByte >= 0x40 && secondByte <= 0x7E) || (secondByte >=80 && secondByte <=0xFE)) {
break buildChar;
@ -503,10 +504,11 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
}
it.error = true;
break buildChar;
}
}
return !it.done;
return (it.done == false);
}
static int [] commonChars =

View file

@ -1,16 +1,17 @@
/*
*******************************************************************************
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
****************************************************************************
* Copyright (C) 2005-2013, International Business Machines Corporation and *
* others. All Rights Reserved. *
************************************************************************** *
*
*/
package com.ibm.icu.text;
/**
* This class recognizes single-byte encodings. Because the encoding scheme is so
* simple, language statistics are used to do the matching.
*/
@SuppressWarnings("ALL")
abstract class CharsetRecog_sbcs extends CharsetRecognizer {
/* (non-Javadoc)
@ -24,7 +25,7 @@ abstract class CharsetRecog_sbcs extends CharsetRecognizer {
private static final int N_GRAM_MASK = 0xFFFFFF;
protected int byteIndex = 0;
private int ngram;
private int ngram = 0;
private int[] ngramList;
protected byte[] byteMap;
@ -160,7 +161,7 @@ abstract class CharsetRecog_sbcs extends CharsetRecognizer {
return (int) (rawPercent * 300.0);
}
}
static class NGramParser_IBM420 extends NGramParser
{
private byte alef = 0x00;
@ -272,7 +273,7 @@ abstract class CharsetRecog_sbcs extends CharsetRecognizer {
return parser.parse(det, spaceChar);
}
int matchIBM420(CharsetDetector det, int[] ngrams, byte[] byteMap, @SuppressWarnings("SameParameterValue") byte spaceChar){
int matchIBM420(CharsetDetector det, int[] ngrams, byte[] byteMap, byte spaceChar){
NGramParser_IBM420 parser = new NGramParser_IBM420(ngrams, byteMap);
return parser.parse(det, spaceChar);
}

View file

@ -1,9 +1,9 @@
/*
*******************************************************************************
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
/**
*******************************************************************************
* Copyright (C) 2005-2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
/**
@ -20,7 +20,6 @@ package com.ibm.icu.text;
* The WILL be shared by multiple instances of CharsetDetector.
* They encapsulate const charset-specific information.
*/
@SuppressWarnings("ALL")
abstract class CharsetRecognizer {
/**
* Get the IANA name of this charset.

View file

@ -0,0 +1,45 @@
package org.ligi.passandroid;
import android.app.Application;
import android.support.annotation.VisibleForTesting;
import android.support.v7.app.AppCompatDelegate;
import com.jakewharton.threetenabp.AndroidThreeTen;
import com.squareup.leakcanary.LeakCanary;
import org.ligi.tracedroid.TraceDroid;
import org.ligi.tracedroid.logging.Log;
public class App extends Application {
private static AppComponent component;
@Override
public void onCreate() {
super.onCreate();
component = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.trackerModule(new TrackerModule(this))
.build();
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
LeakCanary.install(this);
AndroidThreeTen.init(this);
initTraceDroid();
AppCompatDelegate.setDefaultNightMode(component.settings().getNightMode());
}
private void initTraceDroid() {
TraceDroid.init(this);
Log.setTAG("PassAndroid");
}
public static AppComponent component() {
return component;
}
@VisibleForTesting
public static void setComponent(AppComponent newComponent) {
component = newComponent;
}
}

View file

@ -1,63 +0,0 @@
package org.ligi.passandroid
import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
import com.jakewharton.threetenabp.AndroidThreeTen
import com.squareup.moshi.Moshi
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
import org.koin.core.module.Module
import org.koin.dsl.module
import org.ligi.passandroid.json_adapter.ColorAdapter
import org.ligi.passandroid.json_adapter.ZonedTimeAdapter
import org.ligi.passandroid.model.AndroidFileSystemPassStore
import org.ligi.passandroid.model.AndroidSettings
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.Settings
import org.ligi.passandroid.scan.events.PassScanEventChannelProvider
import org.ligi.tracedroid.TraceDroid
import org.ligi.tracedroid.logging.Log
open class App : Application() {
private val moshi = Moshi.Builder()
.add(ZonedTimeAdapter())
.add(ColorAdapter())
.build()
private val settings by lazy { AndroidSettings(this) }
open fun createKoin(): Module {
return module {
single { AndroidFileSystemPassStore(this@App, get(), moshi) as PassStore }
single { settings as Settings }
single { createTracker(this@App) }
single { PassScanEventChannelProvider() }
}
}
override fun onCreate() {
super.onCreate()
startKoin {
if (BuildConfig.DEBUG) androidLogger()
androidContext(this@App)
modules(createKoin())
}
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
AndroidThreeTen.init(this)
initTraceDroid()
AppCompatDelegate.setDefaultNightMode(settings.getNightMode())
}
private fun initTraceDroid() {
TraceDroid.init(this)
Log.setTAG("PassAndroid")
}
}

View file

@ -0,0 +1,53 @@
package org.ligi.passandroid;
import dagger.Component;
import javax.inject.Singleton;
import org.ligi.passandroid.model.PassStore;
import org.ligi.passandroid.model.Settings;
import org.ligi.passandroid.ui.PassAdapter;
import org.ligi.passandroid.ui.PassAndroidActivity;
import org.ligi.passandroid.ui.PassEditActivity;
import org.ligi.passandroid.ui.PassImportActivity;
import org.ligi.passandroid.ui.PassListActivity;
import org.ligi.passandroid.ui.PassListFragment;
import org.ligi.passandroid.ui.PassMenuOptions;
import org.ligi.passandroid.ui.PassNavigationView;
import org.ligi.passandroid.ui.PassViewActivityBase;
import org.ligi.passandroid.ui.SearchPassesIntentService;
import org.ligi.passandroid.ui.edit.PassandroidFragment;
import org.ligi.passandroid.ui.quirk_fix.USAirwaysLoadActivity;
@Singleton
@Component(modules = {AppModule.class , TrackerModule.class})
public interface AppComponent {
void inject(PassViewActivityBase passViewActivityBase);
void inject(PassListActivity passListActivity);
void inject(PassEditActivity passEditActivity);
void inject(PassandroidFragment passandroidFragment);
void inject(PassAdapter passAdapter);
void inject(PassImportActivity passImportActivity);
void inject(PassMenuOptions passMenuOptions);
void inject(SearchPassesIntentService searchPassesIntentService);
void inject(USAirwaysLoadActivity usAirwaysLoadActivity);
void inject(PassAndroidActivity passAndroidActivity);
void inject(PassListFragment passListFragment);
void inject(PassNavigationView passNavigationView);
PassStore passStore();
Tracker tracker();
Settings settings();
}

View file

@ -0,0 +1,65 @@
package org.ligi.passandroid;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import com.squareup.moshi.Moshi;
import dagger.Module;
import dagger.Provides;
import javax.inject.Singleton;
import org.greenrobot.eventbus.EventBus;
import org.ligi.passandroid.json_adapter.ColorAdapter;
import org.ligi.passandroid.json_adapter.ZonedTimeAdapter;
import org.ligi.passandroid.model.AndroidFileSystemPassStore;
import org.ligi.passandroid.model.AndroidSettings;
import org.ligi.passandroid.model.PassStore;
import org.ligi.passandroid.model.Settings;
import org.ligi.passandroid.model.State;
@Module
public class AppModule {
private final App app;
public AppModule(App app) {
this.app = app;
}
@Singleton
@Provides
PassStore providePassStore(Settings settings, Moshi moshi,EventBus bus) {
return new AndroidFileSystemPassStore(app, settings, moshi,bus);
}
@Singleton
@Provides
Moshi provideMoshi() {
return new Moshi.Builder()
.add(new ZonedTimeAdapter())
.add(new ColorAdapter())
.build();
}
@Singleton
@Provides
Settings provideSettings() {
return new AndroidSettings(app);
}
@Singleton
@Provides
SharedPreferences provideSharedPreferences() {
return PreferenceManager.getDefaultSharedPreferences(app);
}
@Singleton
@Provides
EventBus provideBus() {
return EventBus.getDefault();
}
@Singleton
@Provides
State provideState() {
return new State();
}
}

View file

@ -1,7 +1,7 @@
package org.ligi.passandroid;
import androidx.annotation.Nullable;
import android.support.annotation.Nullable;
public interface Tracker {
void trackException(String s, Throwable e, boolean fatal);

View file

@ -0,0 +1,50 @@
package org.ligi.passandroid.actions;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.provider.CalendarContract;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import org.ligi.passandroid.R;
import org.ligi.passandroid.model.pass.Pass;
import org.threeten.bp.ZonedDateTime;
public class AddToCalendar {
public static void tryAddDateToCalendar(final Pass pass, final Activity activity, final ZonedDateTime date) {
if (pass.getCalendarTimespan() == null) {
new AlertDialog.Builder(activity).setMessage(R.string.expiration_date_to_calendar_warning_message)
.setTitle(R.string.expiration_date_to_calendar_warning_title)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
reallyAddToCalendar(pass, activity, date);
}
})
.show();
} else {
reallyAddToCalendar(pass, activity, date);
}
}
private static void reallyAddToCalendar(Pass pass, Activity activity, ZonedDateTime date) {
try {
final Intent intent = new Intent(Intent.ACTION_EDIT);
intent.setType("vnd.android.cursor.item/event");
intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, date.toEpochSecond() * 1000);
intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, date.plusHours(1).toEpochSecond() * 1000);
intent.putExtra("title", pass.getDescription());
activity.startActivity(intent);
} catch (ActivityNotFoundException exception) {
// TODO maybe action to install calendar app
Snackbar.make(activity.getWindow().getDecorView(), R.string.no_calendar_app_found, Snackbar.LENGTH_LONG).show();
}
}
}

View file

@ -0,0 +1,11 @@
package org.ligi.passandroid.events;
import org.ligi.passandroid.model.pass.Pass;
public class PassRefreshEvent {
public final Pass pass;
public PassRefreshEvent(Pass pass) {
this.pass = pass;
}
}

View file

@ -0,0 +1,3 @@
package org.ligi.passandroid.events
object PassStoreChangeEvent

View file

@ -0,0 +1,5 @@
package org.ligi.passandroid.events
import org.ligi.passandroid.model.pass.Pass
class ScanFinishedEvent(val foundPasses: List<Pass>)

View file

@ -0,0 +1,10 @@
package org.ligi.passandroid.events;
public class ScanProgressEvent {
public final String message;
public ScanProgressEvent(String message) {
this.message = message;
}
}

View file

@ -1,60 +0,0 @@
package org.ligi.passandroid.functions
import android.content.ActivityNotFoundException
import android.content.Intent
import android.provider.CalendarContract
import androidx.annotation.VisibleForTesting
import com.google.android.material.snackbar.Snackbar
import androidx.appcompat.app.AlertDialog
import android.view.View
import org.ligi.passandroid.R
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.model.pass.PassImpl
const val DEFAULT_EVENT_LENGTH_IN_HOURS = 8L
fun tryAddDateToCalendar(pass: Pass, contextView: View, timeSpan: PassImpl.TimeSpan) {
if (pass.calendarTimespan == null) {
AlertDialog.Builder(contextView.context).setMessage(R.string.expiration_date_to_calendar_warning_message)
.setTitle(R.string.expiration_date_to_calendar_warning_title)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok) { _, _ -> reallyAddToCalendar(pass, contextView, timeSpan) }
.show()
} else {
reallyAddToCalendar(pass, contextView, timeSpan)
}
}
private fun reallyAddToCalendar(pass: Pass, contextView: View, timeSpan: PassImpl.TimeSpan) = try {
val intent = createIntent(pass, timeSpan)
contextView.context.startActivity(intent)
} catch (exception: ActivityNotFoundException) {
// TODO maybe action to install calendar app
Snackbar.make(contextView, R.string.no_calendar_app_found, Snackbar.LENGTH_LONG).show()
}
@VisibleForTesting
fun createIntent(pass: Pass, timeSpan: PassImpl.TimeSpan) = Intent(Intent.ACTION_EDIT).apply {
if (timeSpan.from == null && timeSpan.to == null) {
throw IllegalArgumentException("span must have either a to or a from")
}
type = "vnd.android.cursor.item/event"
val from = timeSpan.from ?: timeSpan.to!!.minusHours(DEFAULT_EVENT_LENGTH_IN_HOURS)
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, from.toEpochSecond() * 1000)
val to = timeSpan.to ?: timeSpan.from!!.plusHours(DEFAULT_EVENT_LENGTH_IN_HOURS)
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, to.toEpochSecond() * 1000)
putExtra("title", pass.description)
pass.locations.firstOrNull()?.name?.let {
putExtra("eventLocation", it)
}
}

View file

@ -1,63 +0,0 @@
package org.ligi.passandroid.functions
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import com.google.zxing.MultiFormatWriter
import org.ligi.passandroid.model.pass.PassBarCodeFormat
import org.ligi.tracedroid.logging.Log
fun generateBitmapDrawable(resources: Resources, data: String, type: PassBarCodeFormat): BitmapDrawable? {
val bitmap = generateBarCodeBitmap(data, type) ?: return null
return BitmapDrawable(resources, bitmap).apply {
isFilterBitmap = false
setAntiAlias(false)
}
}
fun generateBarCodeBitmap(data: String, type: PassBarCodeFormat): Bitmap? {
if (data.isEmpty()) {
return null
}
try {
val matrix = getBitMatrix(data, type)
val is1D = matrix.height == 1
// generate an image from the byte matrix
val width = matrix.width
val height = if (is1D) width / 5 else matrix.height
// create buffered image to draw to
// NTFS Bitmap.Config.ALPHA_8 sounds like an awesome idea - been there - done that ..
val barcodeImage = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
// iterate through the matrix and draw the pixels to the image
for (y in 0 until height) {
for (x in 0 until width) {
barcodeImage.setPixel(x, y, if (matrix.get(x, if (is1D) 0 else y)) 0 else 0xFFFFFF)
}
}
return barcodeImage
} catch (e: com.google.zxing.WriterException) {
Log.w("could not write image: $e")
// TODO check if we should better return some rescue Image here
return null
} catch (e: IllegalArgumentException) {
Log.w("could not write image: $e")
return null
} catch (e: ArrayIndexOutOfBoundsException) {
// happens for ITF barcode on certain inputs
Log.w("could not write image: $e")
return null
}
}
fun getBitMatrix(data: String, type: PassBarCodeFormat)
= MultiFormatWriter().encode(data, type.zxingBarCodeFormat(), 0, 0)!!

View file

@ -1,46 +0,0 @@
package org.ligi.passandroid.functions
import android.graphics.Color
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import org.ligi.passandroid.R
import org.ligi.passandroid.model.pass.PassType
@StringRes
fun getHumanCategoryString(fromPass: PassType) = when (fromPass) {
PassType.BOARDING -> R.string.boarding_pass
PassType.EVENT -> R.string.category_event
PassType.COUPON -> R.string.category_coupon
PassType.LOYALTY -> R.string.category_storecard
PassType.GENERIC -> R.string.category_generic
PassType.VOUCHER -> R.string.categories_voucher
else -> R.string.category_none
}
@ColorInt
fun getCategoryDefaultBG(category: PassType) = when (category) {
PassType.BOARDING -> 0xFF3d73e9
PassType.EVENT -> 0xFF9f3dd0
PassType.COUPON -> 0xFF9ccb05
PassType.LOYALTY -> 0xFFf29b21
PassType.VOUCHER -> 0xFF2A2727
PassType.GENERIC -> 0xFFea3c48
else -> Color.WHITE.toLong()
}.toInt()
@DrawableRes
fun getCategoryTopImageRes(type: PassType) = when (type) {
PassType.BOARDING -> R.drawable.cat_bp
PassType.EVENT -> R.drawable.cat_et
PassType.COUPON -> R.drawable.cat_cp
PassType.LOYALTY -> R.drawable.cat_sc
PassType.VOUCHER -> R.drawable.cat_ps
PassType.GENERIC -> R.drawable.cat_none
else -> R.drawable.cat_none
}

View file

@ -1,72 +0,0 @@
package org.ligi.passandroid.functions
import android.content.Context
import android.net.Uri
import okhttp3.OkHttpClient
import okhttp3.Request
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.model.InputStreamWithSource
import java.io.BufferedInputStream
import java.net.URL
const val IPHONE_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53"
fun fromURI(context: Context, uri: Uri, tracker: Tracker): InputStreamWithSource? {
tracker.trackEvent("protocol", "to_inputstream", uri.scheme, null)
return when (uri.scheme) {
"content" -> fromContent(context, uri)
"http", "https" ->
// TODO check if SPDY should be here
return fromOKHttp(uri, tracker)
"file" -> getDefaultInputStreamForUri(uri)
else -> {
tracker.trackException("unknown scheme in ImportAsyncTask" + uri.scheme, false)
getDefaultInputStreamForUri(uri)
}
}
}
private fun fromOKHttp(uri: Uri, tracker: Tracker): InputStreamWithSource? {
val client = OkHttpClient()
val url = URL(uri.toString())
val requestBuilder = Request.Builder().url(url)
// fake to be an iPhone in some cases when the server decides to send no passbook
// to android phones - but only do it then - we are proud to be Android ;-)
val iPhoneFakeMap = mapOf(
"air_canada" to "//m.aircanada.ca/ebp/",
"air_canada2" to "//services.aircanada.com/ebp/",
"air_canada3" to "//mci.aircanada.com/mci/bp/",
"icelandair" to "//checkin.si.amadeus.net",
"mbk" to "//mbk.thy.com/",
"heathrow" to "//passbook.heathrow.com/",
"eventbrite" to "//www.eventbrite.com/passes/order"
)
for ((key, value) in iPhoneFakeMap) {
if (uri.toString().contains(value)) {
tracker.trackEvent("quirk_fix", "ua_fake", key, null)
requestBuilder.header("User-Agent", IPHONE_USER_AGENT)
}
}
val request = requestBuilder.build()
val response = client.newCall(request).execute()
val body = response.body()
if (body != null) {
return InputStreamWithSource(uri.toString(), body.byteStream())
}
return null
}
private fun fromContent(ctx: Context, uri: Uri) = ctx.contentResolver.openInputStream(uri)?.let {
InputStreamWithSource(uri.toString(), it)
}
private fun getDefaultInputStreamForUri(uri: Uri) = InputStreamWithSource(uri.toString(), BufferedInputStream(URL(uri.toString()).openStream(), 4096))

View file

@ -1,16 +0,0 @@
package org.ligi.passandroid.functions
import android.app.Activity
import com.google.android.material.snackbar.Snackbar
import org.ligi.passandroid.R
import org.ligi.passandroid.model.PassClassifier
import org.ligi.passandroid.model.pass.Pass
fun moveWithUndoSnackbar(passClassifier: PassClassifier, pass: Pass, topic: String, activity: Activity) {
val oldTopic = passClassifier.getTopic(pass, "")
Snackbar.make(activity.window.decorView.findViewById(R.id.fam), "Pass moved to $topic", Snackbar.LENGTH_LONG)
.setAction(R.string.undo) { passClassifier.moveToTopic(pass, oldTopic) }
.show()
passClassifier.moveToTopic(pass, topic)
}

View file

@ -1,68 +0,0 @@
package org.ligi.passandroid.functions
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import org.ligi.passandroid.R
import org.ligi.passandroid.model.PassBitmapDefinitions
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.model.pass.PassField
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.model.pass.PassType
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.util.*
const val APP = "passandroid"
fun createAndAddEmptyPass(passStore: PassStore, resources: Resources): Pass {
val pass = createBasePass()
pass.description = "custom Pass"
passStore.currentPass = pass
passStore.save(pass)
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_launcher)
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 90, FileOutputStream(File(passStore.getPathForID(pass.id), PassBitmapDefinitions.BITMAP_ICON + ".png")))
} catch (ignored: FileNotFoundException) {
}
return pass
}
fun createPassForImageImport(resources: Resources): Pass {
return createBasePass().apply {
description = resources.getString(R.string.image_import)
fields = mutableListOf(
PassField.create(R.string.field_source, R.string.field_source_image, resources),
PassField.create(R.string.field_advice_label, R.string.field_advice_text, resources),
PassField.create(R.string.field_note, R.string.field_note_image, resources, true)
)
}
}
fun createPassForPDFImport(resources: Resources): Pass {
return createBasePass().apply {
description = resources.getString(R.string.pdf_import)
fields = mutableListOf(
PassField.create(R.string.field_source, R.string.field_source_pdf, resources),
PassField.create(R.string.field_advice_label, R.string.field_advice_text, resources),
PassField.create(R.string.field_note, R.string.field_note_pdf, resources, true)
)
}
}
private fun createBasePass(): PassImpl {
val pass = PassImpl(UUID.randomUUID().toString())
pass.accentColor = 0xFF0000FF.toInt()
pass.app = APP
pass.type = PassType.EVENT
return pass
}

View file

@ -1,82 +0,0 @@
package org.ligi.passandroid.functions
import org.json.JSONException
import org.json.JSONObject
/**
* I got a really broken passes with invalid json from users.
* As it is not possible to change the problem in the generator side
* It has to be worked around here
*/
private val replacementMap = mapOf(
// first we try without fixing -> always positive and try to have minimal impact
"" to "",
// but here the horror starts ..
// Fix for Virgin Australia
// "value": "NTL",}
// a comma should never be before a closing curly brace like this ,}
// note the \t are greetings to Empire Theatres Tickets - without it their passes do not work
",[\n\r\t ]*\\}" to "}",
/*
Entrada cine Entradas.com
{
"key": "passSourceUpdate",
"label": "Actualiza tu entrada",
"value": "http://www.entradas.com/entradas/passbook.do?cutout
},
],
*/
",[\n\r\t ]*\\]" to "]",
/*
forgotten value aka ( also Entradas.com):
"locations": [
{
"latitude": ,
"longitude": ,
"relevantText": "Bienvenido a yelmo cines espacio coruña"
}
*/
":[ ]*,[\n\r\t ]*\"" to ":\"\",",
/*
from RENFE OPERADORA Billete de tren
],
"transitType": "PKTransitTypeTrain"
},
,
"relevantDate": "2013-08-10T19:15+02:00"
*/
",[\n\r\t ]*," to ","
)
@Throws(JSONException::class)
fun readJSONSafely(str: String?): JSONObject? {
if (str == null) {
return null
}
var allReplaced: String = str
// first try with single fixes
for ((key, value) in replacementMap) {
try {
allReplaced = allReplaced.replace(key.toRegex(), value)
return JSONObject(str.replace(key.toRegex(), value))
} catch (e: JSONException) {
// expected because of problems in JSON we are trying to fix here
}
}
// if that did not work do combination of all - if this is not working a JSONException is thrown
return JSONObject(allReplaced)
}

View file

@ -0,0 +1,70 @@
package org.ligi.passandroid.helper;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.Writer;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import org.ligi.passandroid.model.pass.PassBarCodeFormat;
import org.ligi.tracedroid.logging.Log;
public class BarcodeHelper {
@Nullable
public static BitmapDrawable generateBitmapDrawable(@NonNull Resources resources, @NonNull String data, @NonNull PassBarCodeFormat type) {
final Bitmap bitmap = generateBarCodeBitmap(data, type);
if (bitmap == null) {
return null;
}
final BitmapDrawable bitmapDrawable = new BitmapDrawable(resources, bitmap);
bitmapDrawable.setFilterBitmap(false);
bitmapDrawable.setAntiAlias(false);
return bitmapDrawable;
}
@Nullable
public static Bitmap generateBarCodeBitmap(@NonNull String data, @NonNull PassBarCodeFormat type) {
if (data.isEmpty()) {
return null;
}
try {
final BitMatrix matrix = getBitMatrix(data, type);
final boolean is1D = matrix.getHeight() == 1;
// generate an image from the byte matrix
final int width = matrix.getWidth();
final int height = is1D ? (width / 5) : matrix.getHeight();
// create buffered image to draw to
// NTFS Bitmap.Config.ALPHA_8 sounds like an awesome idea - been there - done that ..
final Bitmap barcode_image = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
// iterate through the matrix and draw the pixels to the image
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
barcode_image.setPixel(x, y, matrix.get(x, is1D ? 0 : y) ? 0 : 0xFFFFFF);
}
}
return barcode_image;
} catch (com.google.zxing.WriterException | IllegalArgumentException e) {
Log.w("could not write image " + e);
// TODO check if we should better return some rescue Image here
return null;
}
}
public static BitMatrix getBitMatrix(String data, PassBarCodeFormat type) throws WriterException {
final Writer writer = new MultiFormatWriter();
return writer.encode(data, type.zxingBarCodeFormat(), 0, 0);
}
}

View file

@ -0,0 +1,56 @@
package org.ligi.passandroid.helper
import android.graphics.Color
import android.support.annotation.ColorInt
import android.support.annotation.DrawableRes
import android.support.annotation.StringRes
import org.ligi.passandroid.R
import org.ligi.passandroid.model.pass.PassType
object CategoryHelper {
@StringRes
fun getHumanCategoryString(fromPass: PassType): Int {
when (fromPass) {
PassType.BOARDING -> return R.string.boarding_pass
PassType.EVENT -> return R.string.category_event
PassType.COUPON -> return R.string.category_coupon
PassType.LOYALTY -> return R.string.category_storecard
PassType.GENERIC -> return R.string.category_generic
PassType.VOUCHER -> return R.string.categories_voucher
else -> return R.string.category_none
}
}
@ColorInt
fun getCategoryDefaultBG(category: PassType): Int {
when (category) {
PassType.BOARDING -> return 0xFF3d73e9.toInt()
PassType.EVENT -> return 0xFF9f3dd0.toInt()
PassType.COUPON -> return 0xFF9ccb05.toInt()
PassType.LOYALTY -> return 0xFFf29b21.toInt()
PassType.VOUCHER -> return 0xFF2A2727.toInt()
PassType.GENERIC -> return 0xFFea3c48.toInt()
else -> return Color.WHITE
}
}
@DrawableRes
fun getCategoryTopImageRes(type: PassType): Int {
when (type) {
PassType.BOARDING -> return R.drawable.cat_bp
PassType.EVENT -> return R.drawable.cat_et
PassType.COUPON -> return R.drawable.cat_cp
PassType.LOYALTY -> return R.drawable.cat_sc
PassType.VOUCHER -> return R.drawable.cat_ps
PassType.GENERIC -> return R.drawable.cat_none
else -> return R.drawable.cat_none
}
}
}

View file

@ -0,0 +1,24 @@
package org.ligi.passandroid.helper;
import android.app.Activity;
import android.support.design.widget.Snackbar;
import android.view.View;
import org.ligi.passandroid.R;
import org.ligi.passandroid.model.PassClassifier;
import org.ligi.passandroid.model.pass.Pass;
public class MoveHelper {
public static void moveWithUndoSnackbar(final PassClassifier passClassifier, final Pass pass, String topic, final Activity activity) {
final String oldTopic = passClassifier.getTopic(pass, "");
Snackbar.make(activity.getWindow().getDecorView().findViewById(R.id.fam), "Pass moved to " + topic, Snackbar.LENGTH_LONG)
.setAction(R.string.undo, new View.OnClickListener() {
@Override
public void onClick(View v) {
passClassifier.moveToTopic(pass, oldTopic);
}
})
.show();
passClassifier.moveToTopic(pass, topic);
}
}

View file

@ -0,0 +1,58 @@
package org.ligi.passandroid.helper;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.ligi.passandroid.R;
import org.ligi.passandroid.model.PassBitmapDefinitions;
import org.ligi.passandroid.model.PassStore;
import org.ligi.passandroid.model.pass.Pass;
import org.ligi.passandroid.model.pass.PassField;
import org.ligi.passandroid.model.pass.PassImpl;
import org.ligi.passandroid.model.pass.PassType;
public class PassTemplates {
public static final String APP = "passandroid";
public static Pass createAndAddEmptyPass(final PassStore passStore, final Resources resources) {
final PassImpl pass = new PassImpl(UUID.randomUUID().toString());
pass.setAccentColor(0xFF0000ff);
pass.setDescription("custom Pass");
pass.setApp(APP);
pass.setType(PassType.EVENT);
passStore.setCurrentPass(pass);
passStore.save(pass);
final Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_launcher);
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 90, new FileOutputStream(new File(passStore.getPathForID(pass.getId()), PassBitmapDefinitions.BITMAP_ICON+".png")));
} catch (FileNotFoundException ignored) {
}
return pass;
}
public static Pass createPassForImageImport() {
final PassImpl pass = new PassImpl(UUID.randomUUID().toString());
pass.setAccentColor(0xFF0000ff);
pass.setDescription("image import");
List<PassField> list = new ArrayList<>();
list.add(new PassField(null, "source", "image", false));
list.add(new PassField(null, "note", "This is imported from image - not a real pass", false));
pass.setFields(list);
pass.setApp(APP);
pass.setType(PassType.EVENT);
return pass;
}
}

View file

@ -0,0 +1,87 @@
package org.ligi.passandroid.helper;
import android.support.annotation.Nullable;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* I got a really broken passes with invalid json from users.
* As it is not possible to change the problem in the generator side
* It has to be worked around here
*/
public class SafeJSONReader {
private static Map<String, String> replacementMap = new LinkedHashMap<String, String>() {{
// first we try without fixing -> always positive and try to have minimal impact
put("", "");
// but here the horror starts ..
// Fix for Virgin Australia
// "value": "NTL",}
// a comma should never be before a closing curly brace like this ,}
// note the \t are greetings to Empire Theatres Tickets - without it their passes do not work
put(",[\n\r\t ]*\\}", "}");
/*
Entrada cine Entradas.com
{
"key": "passSourceUpdate",
"label": "Actualiza tu entrada",
"value": "http://www.entradas.com/entradas/passbook.do?cutout
},
],
*/
put(",[\n\r\t ]*\\]", "]");
/*
forgotten value aka ( also Entradas.com):
"locations": [
{
"latitude": ,
"longitude": ,
"relevantText": "Bienvenido a yelmo cines espacio coruña"
}
*/
put(":[ ]*,[\n\r\t ]*\"", ":\"\",");
/*
from RENFE OPERADORA Billete de tren
],
"transitType": "PKTransitTypeTrain"
},
,
"relevantDate": "2013-08-10T19:15+02:00"
*/
put(",[\n\r\t ]*,", ",");
}};
public static JSONObject readJSONSafely(@Nullable String str) throws JSONException {
if (str == null) {
return null;
}
String allReplaced = str;
// first try with single fixes
for (Map.Entry<String, String> replacementPair : replacementMap.entrySet()) {
try {
allReplaced = allReplaced.replaceAll(replacementPair.getKey(), replacementPair.getValue());
return new JSONObject(str.replaceAll(replacementPair.getKey(), replacementPair.getValue()));
} catch (JSONException e) {
// expected because of problems in JSON we are trying to fix here
}
}
// if that did not work do combination of all - if this is not working a JSONException is thrown
return new JSONObject(allReplaced);
}
}

View file

@ -0,0 +1,9 @@
package org.ligi.passandroid.helper;
import android.support.annotation.Nullable;
public class Strings {
public static String nullToEmpty(@Nullable String in) {
return in != null ? in : "";
}
}

View file

@ -0,0 +1,19 @@
package org.ligi.passandroid.json_adapter;
import android.graphics.Color;
import com.squareup.moshi.FromJson;
import com.squareup.moshi.ToJson;
import org.ligi.passandroid.model.pass.PassImpl;
public class ColorAdapter {
@ToJson
String toJson(@PassImpl.HexColor int rgb) {
return String.format("#%06x", rgb);
}
@FromJson
@PassImpl.HexColor
int fromJson(String rgb) {
return Color.parseColor(rgb);
}
}

View file

@ -1,16 +0,0 @@
package org.ligi.passandroid.json_adapter
import android.graphics.Color
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import org.ligi.passandroid.model.pass.PassImpl
class ColorAdapter {
@ToJson
internal fun toJson(@PassImpl.HexColor rgb: Int) = String.format("#%06x", rgb)
@FromJson
@PassImpl.HexColor
internal fun fromJson(rgb: String) = Color.parseColor(rgb)
}

View file

@ -0,0 +1,18 @@
package org.ligi.passandroid.json_adapter;
import com.squareup.moshi.FromJson;
import com.squareup.moshi.ToJson;
import org.threeten.bp.ZonedDateTime;
import org.threeten.bp.format.DateTimeFormatter;
public class ZonedTimeAdapter {
@ToJson
String toJson(ZonedDateTime zonedDateTime) {
return zonedDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME );
}
@FromJson
ZonedDateTime fromJson(String zonedDateTime) {
return ZonedDateTime.parse(zonedDateTime);
}
}

View file

@ -1,16 +0,0 @@
package org.ligi.passandroid.json_adapter
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import org.threeten.bp.ZonedDateTime
import org.threeten.bp.format.DateTimeFormatter
class ZonedTimeAdapter {
@ToJson
internal fun toJson(zonedDateTime: ZonedDateTime) = zonedDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
@FromJson
internal fun fromJson(zonedDateTime: String) = ZonedDateTime.parse(zonedDateTime)
}

View file

@ -3,16 +3,12 @@ package org.ligi.passandroid.model
import android.content.Context
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlinx.coroutines.launch
import okio.buffer
import okio.sink
import okio.source
import org.koin.core.KoinComponent
import org.koin.core.inject
import okio.Okio
import org.greenrobot.eventbus.EventBus
import org.ligi.axt.AXT
import org.ligi.passandroid.App
import org.ligi.passandroid.BuildConfig
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.events.PassStoreChangeEvent
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.reader.AppleStylePassReader
@ -20,26 +16,15 @@ import org.ligi.passandroid.reader.PassReader
import java.io.File
import java.util.*
object PassStoreUpdateEvent
class AndroidFileSystemPassStore(
private val context: Context,
settings: Settings,
private val moshi: Moshi
) : PassStore, KoinComponent {
override val updateChannel = ConflatedBroadcastChannel<PassStoreUpdateEvent>()
private val path: File = settings.getPassesDir()
class AndroidFileSystemPassStore(private val context: Context, settings: Settings, private val moshi: Moshi, private val bus: EventBus) : PassStore {
private val path: File = settings.passesDir
override val passMap = HashMap<String, Pass>()
override var currentPass: Pass? = null
private val tracker: Tracker by inject()
override val classifier: PassClassifier by lazy {
val classificationFile = File(settings.getStateDir(), "classifier_state.json")
val classificationFile = File(settings.stateDir, "classifier_state.json")
FileBackedPassClassifier(classificationFile, this, moshi)
}
@ -52,11 +37,11 @@ class AndroidFileSystemPassStore(
pathForID.mkdirs()
}
val buffer = File(pathForID, "main.json").sink().buffer()
val buffer = Okio.buffer(Okio.sink(File(pathForID, "main.json")))
if (BuildConfig.DEBUG) {
val of = com.squareup.moshi.JsonWriter.of(buffer)
of.indent = " "
of.setIndent(" ")
jsonAdapter.toJson(of, pass as PassImpl)
buffer.close()
of.close()
@ -83,9 +68,9 @@ class AndroidFileSystemPassStore(
val jsonAdapter = moshi.adapter(PassImpl::class.java)
dirty = false
try {
result = jsonAdapter.fromJson(file.source().buffer())
result = jsonAdapter.fromJson(Okio.buffer(Okio.source(file)))
} catch (ignored: JsonDataException) {
tracker.trackException("invalid main.json", false)
App.component().tracker().trackException("invalid main.json", false)
}
}
@ -95,14 +80,14 @@ class AndroidFileSystemPassStore(
}
if (result == null && File(pathForID, "pass.json").exists()) {
result = AppleStylePassReader.read(pathForID, language, context, tracker)
result = AppleStylePassReader.read(pathForID, language, context)
}
if (result != null) {
if (dirty) {
save(result)
}
passMap[id] = result
passMap.put(id, result)
notifyChange()
}
@ -114,7 +99,7 @@ class AndroidFileSystemPassStore(
}
override fun deletePassWithId(id: String): Boolean {
val result = getPathForID(id).deleteRecursively()
val result = AXT.at(getPathForID(id)).deleteRecursive()
if (result) {
passMap.remove(id)
classifier.removePass(id)
@ -128,9 +113,7 @@ class AndroidFileSystemPassStore(
}
override fun notifyChange() {
GlobalScope.launch {
updateChannel.send(PassStoreUpdateEvent)
}
bus.post(PassStoreChangeEvent)
}
override fun syncPassStoreWithClassifier(defaultTopic: String) {

View file

@ -0,0 +1,78 @@
package org.ligi.passandroid.model;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatDelegate;
import java.io.File;
import org.ligi.passandroid.R;
import org.ligi.passandroid.model.comparator.PassSortOrder;
import static org.ligi.passandroid.R.string.preference_key_autolight;
import static org.ligi.passandroid.R.string.preference_key_condensed;
public class AndroidSettings implements Settings {
public final Context context;
final SharedPreferences sharedPreferences;
public AndroidSettings(Context context) {
this.context = context;
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
}
@Override
public PassSortOrder getSortOrder() {
final String key = context.getString(R.string.preference_key_sort);
final String stringValue = sharedPreferences.getString(key, "0");
final int id = Integer.valueOf(stringValue);
for (PassSortOrder order : PassSortOrder.values()) {
if (order.getInt() == id) {
return order;
}
}
return PassSortOrder.DATE_ASC;
}
@Override
public boolean doTraceDroidEmailSend() {
// will be overridden in test-module
return true;
}
@Override
public File getPassesDir() {
return new File(context.getFilesDir().getAbsolutePath(), "passes");
}
@Override
public File getStateDir() {
return new File(context.getFilesDir(), "state");
}
@Override
public boolean isCondensedModeEnabled() {
return sharedPreferences.getBoolean(context.getString(preference_key_condensed), false);
}
@Override
public boolean isAutomaticLightEnabled() {
return sharedPreferences.getBoolean(context.getString(preference_key_autolight), true);
}
@Override
public int getNightMode() {
final String key = sharedPreferences.getString(context.getString(R.string.preference_key_nightmode), "auto");
switch (key) {
case "day":
return AppCompatDelegate.MODE_NIGHT_NO;
case "night":
return AppCompatDelegate.MODE_NIGHT_YES;
case "auto":
return AppCompatDelegate.MODE_NIGHT_AUTO;
}
return AppCompatDelegate.MODE_NIGHT_AUTO;
}
}

View file

@ -1,44 +0,0 @@
package org.ligi.passandroid.model
import android.content.Context
import android.preference.PreferenceManager
import androidx.appcompat.app.AppCompatDelegate
import org.ligi.passandroid.R
import org.ligi.passandroid.R.string.preference_key_autolight
import org.ligi.passandroid.R.string.preference_key_condensed
import org.ligi.passandroid.model.comparator.PassSortOrder
import java.io.File
class AndroidSettings(val context: Context) : Settings {
private val sharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(context) }
override fun getSortOrder(): PassSortOrder {
val key = context.getString(R.string.preference_key_sort)
val stringValue = sharedPreferences.getString(key, "0")
val id = Integer.valueOf(stringValue!!)
return PassSortOrder.values().first { it.int == id }
}
override fun doTraceDroidEmailSend() = true
override fun getPassesDir() = File(context.filesDir.absolutePath, "passes")
override fun getStateDir() = File(context.filesDir, "state")
override fun isCondensedModeEnabled() = sharedPreferences.getBoolean(context.getString(preference_key_condensed), false)
override fun isAutomaticLightEnabled() = sharedPreferences.getBoolean(context.getString(preference_key_autolight), true)
override fun getNightMode(): Int {
val key = sharedPreferences.getString(context.getString(R.string.preference_key_nightmode), "auto")
return when (key) {
"day" -> AppCompatDelegate.MODE_NIGHT_NO
"night" -> AppCompatDelegate.MODE_NIGHT_YES
"auto" -> AppCompatDelegate.MODE_NIGHT_AUTO
else -> AppCompatDelegate.MODE_NIGHT_AUTO
}
}
}

View file

@ -1,6 +1,7 @@
package org.ligi.passandroid.model
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.helper.Strings
import org.ligi.passandroid.model.pass.PassField
import org.ligi.passandroid.model.pass.PassImpl
import org.threeten.bp.DateTimeException
@ -24,10 +25,10 @@ class ApplePassbookQuirkCorrector(val tracker: Tracker) {
tryToFindDate(pass)
}
private fun tryToFindDate(pass: PassImpl) {
fun tryToFindDate(pass: PassImpl) {
if (pass.calendarTimespan == null) {
val foundDate = pass.fields.filter { "date" == it.key }.map {
val foundDate = pass.fields.filter { it.key.equals("date") }.map {
try {
ZonedDateTime.parse(it.value)
} catch (e: DateTimeException) {
@ -44,11 +45,11 @@ class ApplePassbookQuirkCorrector(val tracker: Tracker) {
}
private fun getPassFieldForKey(pass: PassImpl, key: String): PassField? {
return pass.fields.firstOrNull { it.key != null && it.key == key }
return pass.fields.firstOrNull() { it.key != null && it.key == key }
}
private fun getPassFieldThatMatchesLabel(pass: PassImpl, matcher: String): PassField? {
return pass.fields.firstOrNull {
return pass.fields.firstOrNull() {
val label = it.label
label != null && label.matches(matcher.toRegex())
}
@ -151,7 +152,7 @@ class ApplePassbookQuirkCorrector(val tracker: Tracker) {
}
private fun careForWestbahn(pass: PassImpl) {
if (pass.calendarTimespan != null || (pass.creator ?: "") != "WESTbahn") {
if (pass.calendarTimespan != null || Strings.nullToEmpty(pass.creator) != "WESTbahn") {
return
}

View file

@ -1,16 +1,21 @@
package org.ligi.passandroid.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import com.ibm.icu.text.CharsetDetector;
import com.ibm.icu.text.CharsetMatch;
import org.ligi.passandroid.App;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
@VisibleForTesting
public class AppleStylePassTranslation extends HashMap<String, String> {
public String translate(String key) {
@ -58,13 +63,6 @@ public class AppleStylePassTranslation extends HashMap<String, String> {
dataInputStream.readFully(fileData);
dataInputStream.close();
if (fileData[0] == (byte) 0xEF && fileData[1] == (byte) 0xBB && fileData[2] == (byte) 0xBF) {
final byte[] crop = new byte[fileData.length - 3];
System.arraycopy(fileData, 3, crop, 0, crop.length);
//noinspection CharsetObjectCanBeUsed
return new String(crop, "utf-8");
}
final CharsetMatch match = new CharsetDetector().setText(fileData).detect();
if (match != null) try {
@ -73,6 +71,7 @@ public class AppleStylePassTranslation extends HashMap<String, String> {
}
return new String(fileData);
} catch (Throwable e) {
App.component().tracker().trackException("problem_reading_translation", e, false);
e.printStackTrace();
return null;
}

View file

@ -1,7 +1,6 @@
package org.ligi.passandroid.model;
public class PassBitmapDefinitions {
public final static String BITMAP_ICON = "icon";
public final static String BITMAP_THUMBNAIL = "thumbnail";
public final static String BITMAP_STRIP = "strip";

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