Compare commits
164 Commits
Author | SHA1 | Date |
---|---|---|
|
28520bee74 | |
|
0c6a486dd9 | |
|
ec15c79b63 | |
|
4592d1984f | |
|
aae74cf9db | |
|
e19b0db39d | |
|
23fd52b0c5 | |
|
62a62049c3 | |
|
849698779b | |
|
a42a92a3c5 | |
|
be30e2f968 | |
|
c629b2aec2 | |
|
99ba87c3f6 | |
|
953b4825a9 | |
|
2807843d25 | |
|
638c321798 | |
|
42d938b0bc | |
|
9d7504bbaf | |
|
8ecfe8f120 | |
|
ff0c4ad1a7 | |
|
ea0caea91e | |
|
1f660bdd37 | |
|
be43d87b1a | |
|
dd5c7b3fb8 | |
|
fb3dab6dc3 | |
|
bcfdb83d14 | |
|
8c55366ec0 | |
|
6f68fbb73c | |
|
297dd77e79 | |
|
6a70f26807 | |
|
f6b00a8d81 | |
|
69ce9fef5a | |
|
2d753851c0 | |
|
6c0624c793 | |
|
7779296345 | |
|
3e2ef0521e | |
|
bdfeb46faf | |
|
18ca435764 | |
|
948f330ebe | |
|
72e9efb9e7 | |
|
bea1b47396 | |
|
34a68ff75d | |
|
1ba3f1fa87 | |
|
48544aef2f | |
|
bc09e33147 | |
|
6ec9b9f5e2 | |
|
a19173b499 | |
|
85dc3181fd | |
|
e46c234b6c | |
|
faa07966da | |
|
ccc0f0f2bc | |
|
e15bf95b85 | |
|
01677c04ef | |
|
6e9c63d3d4 | |
|
c343735b57 | |
|
9c5274dc06 | |
|
b186a2e96e | |
|
2cb4b72369 | |
|
2d497d1a96 | |
|
9d2de3fcb3 | |
|
bed3f5d978 | |
|
8f5a4dd1b3 | |
|
9907d083e9 | |
|
d4860b2a32 | |
|
5ec2b53bce | |
|
701a351e6e | |
|
5cad924b26 | |
|
605bf6248d | |
|
d5adc4df51 | |
|
c23454f081 | |
|
b240beacc9 | |
|
aa69d2242f | |
|
6e6c9f71a0 | |
|
889c673c5d | |
|
522f31e077 | |
|
b9ca18044c | |
|
bd49e482e2 | |
|
2f5b2a6579 | |
|
4ce37b3dcf | |
|
7550fcfd22 | |
|
a23f7c9212 | |
|
75168c6688 | |
|
be916a74ab | |
|
3e909ab68f | |
|
5f2b3aa496 | |
|
6cc1671a52 | |
|
b4ed1ca927 | |
|
e74c307566 | |
|
733b675ffa | |
|
3a0a31f781 | |
|
f52151fbf1 | |
|
e8cae7e807 | |
|
c6ac19bfae | |
|
a7abd48726 | |
|
9a22d9b737 | |
|
2cc2ecf952 | |
|
ea70aedbd0 | |
|
98b3adbf3b | |
|
a055f59cc8 | |
|
01db6bb451 | |
|
4838c9406c | |
|
23195f94e0 | |
|
5e766ec126 | |
|
9c1f95ca25 | |
|
e99127a63a | |
|
f7fa96b1ae | |
|
ac37bce145 | |
|
6fd82254e0 | |
|
0dd8ba9475 | |
|
770e9a255d | |
|
4589badbfc | |
|
74f75bfbde | |
|
a00c651bfd | |
|
77757ad9ca | |
|
fe111ac56b | |
|
4971d0b1b8 | |
|
2bd86ff6bb | |
|
dbaf496a79 | |
|
77326a8ed6 | |
|
9fc897e194 | |
|
f9ac219ec6 | |
|
a87e57c80e | |
|
ff1d353cae | |
|
6e5cf29eaa | |
|
ee388bf5a5 | |
|
8aaf8e3647 | |
|
d645d75bbf | |
|
f97491addd | |
|
e08790aaa4 | |
|
750a808fbe | |
|
e51a80b78d | |
|
a4eaea2918 | |
|
cd3136715f | |
|
15f1386b6e | |
|
3bace6c155 | |
|
61716f8eb4 | |
|
a1410f7b80 | |
|
953185425b | |
|
a6d14044c2 | |
|
f3b7ff066d | |
|
040eb26dcf | |
|
1a5d2b6561 | |
|
92b60d660c | |
|
a4eceeddc9 | |
|
3e3a80442e | |
|
5a1a07cd42 | |
|
dbfdaffe99 | |
|
58eb217ab7 | |
|
24f920c05f | |
|
6301308d76 | |
|
8e205fa889 | |
|
e9bdcee443 | |
|
b214cfccb2 | |
|
404ddd58b8 | |
|
137ff7df0c | |
|
b4071d7456 | |
|
5e6e6cfde6 | |
|
ffeb09a37f | |
|
75a457312d | |
|
87bf614d28 | |
|
e69354af96 | |
|
ec74a8e4f8 | |
|
b49d16b1a1 | |
|
70059b4b0c |
|
@ -0,0 +1,9 @@
|
|||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: assembleRelease
|
||||
image: nextcloudci/android10:android-56
|
||||
commands:
|
||||
- ./gradlew assembleRelease
|
||||
|
29
README.md
29
README.md
|
@ -1,12 +1,27 @@
|
|||
# ProjectLaogai "hso App"
|
||||
Some info about the app ...
|
||||
[](https://drone.mosad.xyz/Seil0/ProjectLaogai)
|
||||

|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
|
||||
# Project Laogai
|
||||
Project Laogai is an app to access the timetable, grades (qispos) and the canteen menu of Hochschule Offenburg. Laogai uses the TCoR-API fot timetables and the canteen menu, wich makes acessing them super fast. To get the grades from Qispos, Laogai will ask for your login data, wich are stored encrypted on your device.
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="75">](https://f-droid.org/packages/org.mosad.seil0.projectlaogai/)
|
||||
[<img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" height="75">](https://play.google.com/store/apps/details?id=org.mosad.seil0.projectlaogai)
|
||||
|
||||
## Features
|
||||
* look up what you can eat in the mensa
|
||||
* check your timetable
|
||||
* probably many many bugs
|
||||
* have your grades displayed directly in the app
|
||||
* show the timetable of your course
|
||||
* take a look at the canteen menu for the current and next week
|
||||
* check the current balance of your mensa card
|
||||
* open moodle directly in the app
|
||||
|
||||
Please report bugs and issues to support@mosad.xyz
|
||||
|
||||
## Screenshots
|
||||
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_HomeScreen.png)
|
||||
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa.png)
|
||||
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Timetable.png)
|
||||
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Settings.png)
|
||||
[<img src="https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa_dark.png" width=180>](https://www.mosad.xyz/images/Project_Laogai/ProjectLaogai_Mensa_dark.png)
|
||||
|
||||
|
||||
ProjectLaogai © 2018 mosad www.mosad.xyz, Project by [@Seil0](https://git.mosad.xyz/Seil0)
|
||||
ProjectLaogai © 2019-2020 [@Seil0](https://git.mosad.xyz/Seil0), a [mosad.xyz](http://www.mosad.xyz) Project
|
||||
|
|
|
@ -1,43 +1,75 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
android {
|
||||
signingConfigs {
|
||||
}
|
||||
compileSdkVersion 28
|
||||
compileSdkVersion 29
|
||||
defaultConfig {
|
||||
applicationId "org.mosad.seil0.projectlaogai"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 9
|
||||
versionName "0.3.1"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 29
|
||||
versionCode 6000 // 0006000
|
||||
versionName "0.6.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resValue "string", "build_time", buildTime()
|
||||
setProperty("archivesBaseName", "projectlaogai-$versionName")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
res.srcDirs =
|
||||
[
|
||||
'src/main/res/layouts/activities',
|
||||
'src/main/res/layouts/dialogs',
|
||||
'src/main/res/layouts/fragments',
|
||||
'src/main/res/layouts',
|
||||
'src/main/res'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'
|
||||
implementation 'androidx.core:core:1.3.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
|
||||
implementation 'org.jsoup:jsoup:1.11.3'
|
||||
implementation 'org.jetbrains.anko:anko-commons:0.10.8'
|
||||
implementation 'com.afollestad.material-dialogs:core:2.0.0-rc1'
|
||||
implementation 'com.afollestad.material-dialogs:color:2.0.0-rc1'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'androidx.security:security-crypto:1.1.0-alpha02'
|
||||
implementation 'com.google.android.material:material:1.2.0'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'com.afollestad:aesthetic:1.0.0-beta05'
|
||||
implementation 'com.afollestad.material-dialogs:core:3.3.0'
|
||||
implementation 'com.afollestad.material-dialogs:color:3.3.0'
|
||||
implementation 'com.afollestad.material-dialogs:bottomsheets:3.3.0'
|
||||
implementation 'de.psdev.licensesdialog:licensesdialog:2.1.0'
|
||||
|
||||
implementation 'org.apache.commons:commons-lang3:3.11'
|
||||
implementation 'org.jsoup:jsoup:1.13.1'
|
||||
|
||||
testImplementation 'junit:junit:4.13'
|
||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
}
|
||||
|
||||
static def buildTime() {
|
||||
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
|
||||
}
|
|
@ -15,7 +15,16 @@
|
|||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
-dontobfuscate
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
-keep class org.mosad.seil0.projectlaogai.util.** { <fields>; }
|
||||
|
||||
#Gson
|
||||
-keepattributes Signature
|
||||
-dontwarn sun.misc.**
|
||||
|
||||
#misc
|
||||
-dontwarn java.lang.instrument.ClassFileTransformer
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.mosad.seil0.projectlaogai
|
||||
|
||||
import androidx.test.InstrumentationRegistry
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
@ -18,7 +18,7 @@ class ExampleInstrumentedTest {
|
|||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getTargetContext()
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().context
|
||||
assertEquals("org.mosad.seil0.projectlaogai", appContext.packageName)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,31 +2,55 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.mosad.seil0.projectlaogai">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup_descriptor"
|
||||
android:icon="@mipmap/ic_laogai_icon"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_laogai_icon"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
android:theme="@style/AppTheme.Light">
|
||||
|
||||
<activity
|
||||
android:name=".SplashActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/SplashTheme"
|
||||
android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".OnboardingActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.Light"
|
||||
android:screenOrientation="portrait"
|
||||
android:launchMode="singleTop">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:screenOrientation="portrait">
|
||||
android:theme="@style/AppTheme.Light"
|
||||
android:screenOrientation="portrait"
|
||||
android:launchMode="singleTop">
|
||||
|
||||
<!-- nfc stuff -->
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.TECH_DISCOVERED"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
|
||||
android:resource="@xml/nfc_tech_filter" />
|
||||
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
Before Width: | Height: | Size: 39 KiB |
|
@ -0,0 +1,227 @@
|
|||
/**
|
||||
* Utils.kt
|
||||
*
|
||||
* Copyright (C) 2011 Eric Butler
|
||||
* Copyright (C) 2019 <seil0@mosad.xyz>
|
||||
*
|
||||
* Authors:
|
||||
* Eric Butler <eric@codebutler.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.codebutler.farebot
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import com.codebutler.farebot.card.desfire.DesfireException
|
||||
import com.codebutler.farebot.card.desfire.DesfireFileSettings
|
||||
import com.codebutler.farebot.card.desfire.DesfireProtocol
|
||||
import org.w3c.dom.Node
|
||||
import java.io.StringWriter
|
||||
import javax.xml.transform.OutputKeys
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
import kotlin.experimental.and
|
||||
|
||||
class Utils {
|
||||
|
||||
companion object {
|
||||
private val TAG = Utils::class.java.name
|
||||
|
||||
@Suppress("unused")
|
||||
fun showError(activity: Activity, ex: Exception) {
|
||||
Log.e(activity.javaClass.name, ex.message, ex)
|
||||
AlertDialog.Builder(activity)
|
||||
.setMessage(getErrorMessage(ex))
|
||||
.show()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun showErrorAndFinish(activity: Activity, ex: Exception) {
|
||||
try {
|
||||
Log.e(activity.javaClass.name, getErrorMessage(ex))
|
||||
ex.printStackTrace()
|
||||
|
||||
AlertDialog.Builder(activity)
|
||||
.setMessage(getErrorMessage(ex))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> activity.finish() }
|
||||
.show()
|
||||
} catch (unused: WindowManager.BadTokenException) {
|
||||
/* Ignore... happens if the activity was destroyed */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun getHexString(b: ByteArray): String {
|
||||
var result = ""
|
||||
for (i in b.indices) {
|
||||
result += ((b[i] and 0xff.toByte()) + 0x100).toString(16).substring(1)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun getHexString(b: ByteArray, defaultResult: String): String {
|
||||
return try {
|
||||
getHexString(b)
|
||||
} catch (ex: Exception) {
|
||||
defaultResult
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun hexStringToByteArray(s: String): ByteArray {
|
||||
if (s.length % 2 != 0) {
|
||||
throw IllegalArgumentException("Bad input string: $s")
|
||||
}
|
||||
|
||||
val len = s.length
|
||||
val data = ByteArray(len / 2)
|
||||
var i = 0
|
||||
while (i < len) {
|
||||
data[i / 2] = ((Character.digit(s[i], 16) shl 4) + Character.digit(s[i + 1], 16)).toByte()
|
||||
i += 2
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun byteArrayToInt(b: ByteArray, offset: Int = 0, length: Int = b.size): Int {
|
||||
return byteArrayToLong(b, offset, length).toInt()
|
||||
}
|
||||
|
||||
fun byteArrayToLong(b: ByteArray, offset: Int, length: Int): Long {
|
||||
if (b.size < length)
|
||||
throw IllegalArgumentException("length must be less than or equal to b.length")
|
||||
|
||||
var value: Long = 0
|
||||
for (i in 0 until length) {
|
||||
val shift = (length - 1 - i) * 8
|
||||
value += ((b[i + offset].toInt() and 0x000000FF).toLong() shl shift)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun byteArraySlice(b: ByteArray, offset: Int, length: Int): ByteArray {
|
||||
val ret = ByteArray(length)
|
||||
for (i in 0 until length)
|
||||
ret[i] = b[offset + i]
|
||||
return ret
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@Throws(Exception::class)
|
||||
fun xmlNodeToString(node: Node): String {
|
||||
// The amount of code required to do simple things in Java is incredible.
|
||||
val source = DOMSource(node)
|
||||
val stringWriter = StringWriter()
|
||||
val result = StreamResult(stringWriter)
|
||||
val factory = TransformerFactory.newInstance()
|
||||
val transformer = factory.newTransformer()
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
|
||||
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
|
||||
transformer.uriResolver = null
|
||||
transformer.transform(source, result)
|
||||
return stringWriter.buffer.toString()
|
||||
}
|
||||
|
||||
fun getErrorMessage(ex: Throwable): String {
|
||||
var errorMessage: String? = ex.localizedMessage
|
||||
if (errorMessage == null)
|
||||
errorMessage = ex.message
|
||||
if (errorMessage == null)
|
||||
errorMessage = ex.toString()
|
||||
|
||||
if (ex.cause != null) {
|
||||
var causeMessage: String? = ex.cause!!.localizedMessage
|
||||
if (causeMessage == null)
|
||||
causeMessage = ex.cause!!.message
|
||||
if (causeMessage == null)
|
||||
causeMessage = ex.cause.toString()
|
||||
|
||||
errorMessage += ": $causeMessage"
|
||||
}
|
||||
|
||||
return errorMessage
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun <T> findInList(list: List<T>, matcher: Matcher<T>): T? {
|
||||
for (item in list) {
|
||||
if (matcher.matches(item)) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
interface Matcher<T> {
|
||||
fun matches(t: T): Boolean
|
||||
}
|
||||
|
||||
fun selectAppFile(tag: DesfireProtocol, appID: Int, fileID: Int): DesfireFileSettings? {
|
||||
try {
|
||||
tag.selectApp(appID)
|
||||
} catch (e: DesfireException) {
|
||||
Log.w(TAG, "App not found")
|
||||
return null
|
||||
}
|
||||
|
||||
return try {
|
||||
tag.getFileSettings(fileID)
|
||||
} catch (e: DesfireException) {
|
||||
Log.w(TAG, "File not found")
|
||||
null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun arrayContains(arr: IntArray, item: Int): Boolean {
|
||||
for (i in arr)
|
||||
if (i == item)
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun containsAppFile(tag: DesfireProtocol, appID: Int, fileID: Int): Boolean {
|
||||
try {
|
||||
tag.selectApp(appID)
|
||||
} catch (e: DesfireException) {
|
||||
Log.w(TAG, "App not found")
|
||||
Log.w(TAG, e)
|
||||
return false
|
||||
}
|
||||
|
||||
return try {
|
||||
arrayContains(tag.fileList, fileID)
|
||||
} catch (e: DesfireException) {
|
||||
Log.w(TAG, "File not found")
|
||||
Log.w(TAG, e)
|
||||
false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.codebutler.farebot.card.desfire
|
||||
|
||||
/**
|
||||
* Created by Jakob Wenzel on 16.11.13.
|
||||
*/
|
||||
class DesfireException : Exception {
|
||||
constructor(message: String) : super(message)
|
||||
constructor(cause: Throwable) : super(cause)
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* DesfireFile.kt
|
||||
*
|
||||
* Copyright (C) 2011 Eric Butler
|
||||
* Copyright (C) 2019 <seil0@mosad.xyz>
|
||||
*
|
||||
* Authors:
|
||||
* Eric Butler <eric@codebutler.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.codebutler.farebot.card.desfire
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.codebutler.farebot.card.desfire.DesfireFileSettings.RecordDesfireFileSettings
|
||||
import org.apache.commons.lang3.ArrayUtils
|
||||
|
||||
|
||||
open class DesfireFile private constructor(val id: Int, private val fileSettings: DesfireFileSettings?, val data: ByteArray) :
|
||||
Parcelable {
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeInt(id)
|
||||
if (this is InvalidDesfireFile) {
|
||||
parcel.writeInt(1)
|
||||
parcel.writeString(this.errorMessage)
|
||||
} else {
|
||||
parcel.writeInt(0)
|
||||
parcel.writeParcelable(fileSettings, 0)
|
||||
parcel.writeInt(data.size)
|
||||
parcel.writeByteArray(data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
class RecordDesfireFile(fileId: Int, fileSettings: DesfireFileSettings, fileData: ByteArray) :
|
||||
DesfireFile(fileId, fileSettings, fileData) {
|
||||
private val records: Array<DesfireRecord?>
|
||||
|
||||
init {
|
||||
|
||||
val settings = fileSettings as RecordDesfireFileSettings
|
||||
|
||||
val records = arrayOfNulls<DesfireRecord>(settings.curRecords)
|
||||
for (i in 0 until settings.curRecords) {
|
||||
val offset = settings.recordSize * i
|
||||
records[i] = DesfireRecord(ArrayUtils.subarray(data, offset, offset + settings.recordSize))
|
||||
}
|
||||
this.records = records
|
||||
}
|
||||
}
|
||||
|
||||
class InvalidDesfireFile(fileId: Int, val errorMessage: String?) : DesfireFile(fileId, null, ByteArray(0))
|
||||
|
||||
companion object {
|
||||
|
||||
fun create(fileId: Int, fileSettings: DesfireFileSettings, fileData: ByteArray): DesfireFile {
|
||||
return (fileSettings as? RecordDesfireFileSettings)?.let { RecordDesfireFile(fileId, it, fileData) }
|
||||
?: DesfireFile(fileId, fileSettings, fileData)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<DesfireFile> = object : Parcelable.Creator<DesfireFile> {
|
||||
override fun createFromParcel(source: Parcel): DesfireFile {
|
||||
val fileId = source.readInt()
|
||||
|
||||
val isError = source.readInt() == 1
|
||||
|
||||
return if (!isError) {
|
||||
val fileSettings =
|
||||
source.readParcelable<Parcelable>(DesfireFileSettings::class.java.classLoader) as DesfireFileSettings
|
||||
val dataLength = source.readInt()
|
||||
val fileData = ByteArray(dataLength)
|
||||
source.readByteArray(fileData)
|
||||
|
||||
create(fileId, fileSettings, fileData)
|
||||
} else {
|
||||
InvalidDesfireFile(fileId, source.readString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<DesfireFile?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
/**
|
||||
* DesfireFileSettings.kt
|
||||
*
|
||||
* Copyright (C) 2011 Eric Butler
|
||||
* Copyright (C) 2019 <seil0@mosad.xyz>
|
||||
*
|
||||
* Authors:
|
||||
* Eric Butler <eric@codebutler.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.codebutler.farebot.card.desfire
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.codebutler.farebot.Utils
|
||||
import org.apache.commons.lang3.ArrayUtils
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
abstract class DesfireFileSettings : Parcelable {
|
||||
private val fileType: Byte
|
||||
private val commSetting: Byte
|
||||
private val accessRights: ByteArray
|
||||
|
||||
@Suppress("unused")
|
||||
val fileTypeName: String
|
||||
get() {
|
||||
return when (fileType) {
|
||||
STANDARD_DATA_FILE -> "Standard"
|
||||
BACKUP_DATA_FILE -> "Backup"
|
||||
VALUE_FILE -> "Value"
|
||||
LINEAR_RECORD_FILE -> "Linear Record"
|
||||
CYCLIC_RECORD_FILE -> "Cyclic Record"
|
||||
else -> "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
private constructor(stream: ByteArrayInputStream) {
|
||||
fileType = stream.read().toByte()
|
||||
commSetting = stream.read().toByte()
|
||||
|
||||
accessRights = ByteArray(2)
|
||||
stream.read(accessRights, 0, accessRights.size)
|
||||
}
|
||||
|
||||
private constructor(fileType: Byte, commSetting: Byte, accessRights: ByteArray) {
|
||||
this.fileType = fileType
|
||||
this.commSetting = commSetting
|
||||
this.accessRights = accessRights
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeByte(fileType)
|
||||
parcel.writeByte(commSetting)
|
||||
parcel.writeInt(accessRights.size)
|
||||
parcel.writeByteArray(accessRights)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
class StandardDesfireFileSettings : DesfireFileSettings {
|
||||
private val fileSize: Int
|
||||
|
||||
internal constructor(stream: ByteArrayInputStream) : super(stream) {
|
||||
val buf = ByteArray(3)
|
||||
stream.read(buf, 0, buf.size)
|
||||
ArrayUtils.reverse(buf)
|
||||
fileSize = Utils.byteArrayToInt(buf)
|
||||
}
|
||||
|
||||
internal constructor(
|
||||
fileType: Byte,
|
||||
commSetting: Byte,
|
||||
accessRights: ByteArray,
|
||||
fileSize: Int
|
||||
) : super(fileType, commSetting, accessRights) {
|
||||
this.fileSize = fileSize
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
super.writeToParcel(parcel, flags)
|
||||
parcel.writeInt(fileSize)
|
||||
}
|
||||
}
|
||||
|
||||
class RecordDesfireFileSettings : DesfireFileSettings {
|
||||
private val maxRecords: Int
|
||||
val recordSize: Int
|
||||
val curRecords: Int
|
||||
|
||||
constructor(stream: ByteArrayInputStream) : super(stream) {
|
||||
|
||||
var buf = ByteArray(3)
|
||||
stream.read(buf, 0, buf.size)
|
||||
ArrayUtils.reverse(buf)
|
||||
recordSize = Utils.byteArrayToInt(buf)
|
||||
|
||||
buf = ByteArray(3)
|
||||
stream.read(buf, 0, buf.size)
|
||||
ArrayUtils.reverse(buf)
|
||||
maxRecords = Utils.byteArrayToInt(buf)
|
||||
|
||||
buf = ByteArray(3)
|
||||
stream.read(buf, 0, buf.size)
|
||||
ArrayUtils.reverse(buf)
|
||||
curRecords = Utils.byteArrayToInt(buf)
|
||||
}
|
||||
|
||||
internal constructor(
|
||||
fileType: Byte,
|
||||
commSetting: Byte,
|
||||
accessRights: ByteArray,
|
||||
recordSize: Int,
|
||||
maxRecords: Int,
|
||||
curRecords: Int
|
||||
) : super(fileType, commSetting, accessRights) {
|
||||
this.recordSize = recordSize
|
||||
this.maxRecords = maxRecords
|
||||
this.curRecords = curRecords
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
super.writeToParcel(parcel, flags)
|
||||
parcel.writeInt(recordSize)
|
||||
parcel.writeInt(maxRecords)
|
||||
parcel.writeInt(curRecords)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ValueDesfireFileSettings(stream: ByteArrayInputStream) : DesfireFileSettings(stream) {
|
||||
private val lowerLimit: Int
|
||||
private val upperLimit: Int
|
||||
val value: Int
|
||||
private val limitedCreditEnabled: Byte
|
||||
|
||||
init {
|
||||
|
||||
var buf = ByteArray(4)
|
||||
stream.read(buf, 0, buf.size)
|
||||
ArrayUtils.reverse(buf)
|
||||
lowerLimit = Utils.byteArrayToInt(buf)
|
||||
|
||||
buf = ByteArray(4)
|
||||
stream.read(buf, 0, buf.size)
|
||||
ArrayUtils.reverse(buf)
|
||||
upperLimit = Utils.byteArrayToInt(buf)
|
||||
|
||||
buf = ByteArray(4)
|
||||
stream.read(buf, 0, buf.size)
|
||||
ArrayUtils.reverse(buf)
|
||||
value = Utils.byteArrayToInt(buf)
|
||||
|
||||
|
||||
buf = ByteArray(1)
|
||||
stream.read(buf, 0, buf.size)
|
||||
limitedCreditEnabled = buf[0]
|
||||
|
||||
//http://www.skyetek.com/docs/m2/desfire.pdf
|
||||
//http://neteril.org/files/M075031_desfire.pdf
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
super.writeToParcel(parcel, flags)
|
||||
parcel.writeInt(lowerLimit)
|
||||
parcel.writeInt(upperLimit)
|
||||
parcel.writeInt(value)
|
||||
parcel.writeByte(limitedCreditEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
class UnsupportedDesfireFileSettings(fileType: Byte) :
|
||||
DesfireFileSettings(fileType, java.lang.Byte.MIN_VALUE, ByteArray(0))
|
||||
|
||||
companion object {
|
||||
|
||||
/* DesfireFile Types */
|
||||
internal const val STANDARD_DATA_FILE = 0x00.toByte()
|
||||
internal const val BACKUP_DATA_FILE = 0x01.toByte()
|
||||
internal const val VALUE_FILE = 0x02.toByte()
|
||||
internal const val LINEAR_RECORD_FILE = 0x03.toByte()
|
||||
internal const val CYCLIC_RECORD_FILE = 0x04.toByte()
|
||||
|
||||
@Throws(DesfireException::class)
|
||||
fun create(data: ByteArray): DesfireFileSettings {
|
||||
val fileType = data[0]
|
||||
|
||||
val stream = ByteArrayInputStream(data)
|
||||
|
||||
return if (fileType == STANDARD_DATA_FILE || fileType == BACKUP_DATA_FILE)
|
||||
StandardDesfireFileSettings(stream)
|
||||
else if (fileType == LINEAR_RECORD_FILE || fileType == CYCLIC_RECORD_FILE)
|
||||
RecordDesfireFileSettings(stream)
|
||||
else if (fileType == VALUE_FILE)
|
||||
ValueDesfireFileSettings(stream)
|
||||