├── .gitignore ├── .idea ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── uiDesigner.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── lucida_console.ttf │ └── networks │ │ ├── 250 │ │ ├── 149 │ │ │ ├── cardstruct.xml │ │ │ ├── fares.xml │ │ │ ├── profiles.xml │ │ │ └── topology.xml │ │ └── 901 │ │ │ └── cardstruct.xml │ │ └── networks.xml │ ├── java │ └── fr │ │ └── mikado │ │ └── calypsoinspectorandroid │ │ ├── AndroidCalypsoEnvironment.java │ │ ├── AndroidCalypsoRawDump.java │ │ ├── CalypsoDump.java │ │ ├── CalypsoDumpListAdapter.java │ │ ├── MainActivity.java │ │ ├── OnboardIsoDepImpl.java │ │ ├── ReaderProgress.java │ │ ├── ReaderTask.java │ │ └── XMLIOImpl.java │ └── res │ ├── layout │ ├── about.xml │ ├── activity_main.xml │ ├── list_group.xml │ ├── list_item.xml │ └── niy.xml │ ├── menu │ └── actionbar.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ ├── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── nfc_tech_list.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Calypso Inspector (Android) 2 | 3 | This project is the Android port of [Calypso Inspector](http://github.com/ABeaujet/CalypsoInspector). 4 | 5 | This is my first time publishing an Android app onto GitHub so the import process may be a bit rough. 6 | 7 | This version is still in a very alpha state. A nicer UI, update manager and contribution activity are to come. 8 | 9 | For more details, check out the main repo. -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /libs 3 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | signingConfigs { 5 | config { 6 | keyAlias 'MikaDo' 7 | keyPassword '' 8 | storeFile file('/home/alexis/intellij-apk-keystore.jks') 9 | storePassword '' 10 | } 11 | } 12 | compileSdkVersion 23 13 | buildToolsVersion "23.0.3" 14 | defaultConfig { 15 | applicationId "fr.mikado.calypsoinspectorandroid" 16 | minSdkVersion 17 17 | targetSdkVersion 23 18 | versionCode 1 19 | versionName "1.0" 20 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 21 | } 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 26 | signingConfig signingConfigs.config 27 | } 28 | } 29 | } 30 | 31 | dependencies { 32 | compile fileTree(include: ['*.jar'], dir: 'libs') 33 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.0', { 34 | exclude group: 'com.android.support', module: 'support-annotations' 35 | }) 36 | compile 'com.android.support:appcompat-v7:23.+' 37 | testCompile 'junit:junit:4.12' 38 | } 39 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/alexis/downloads/android-sdk-linux/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/assets/lucida_console.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABeaujet/CalypsoInspectorAndroid/f1ede5599adda536ed63726c26e7b56909e15fb7/app/src/main/assets/lucida_console.ttf -------------------------------------------------------------------------------- /app/src/main/assets/networks/250/149/cardstruct.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /app/src/main/assets/networks/250/149/fares.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /app/src/main/assets/networks/250/149/profiles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/assets/networks/250/901/cardstruct.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /app/src/main/assets/networks/networks.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/java/fr/mikado/calypsoinspectorandroid/AndroidCalypsoEnvironment.java: -------------------------------------------------------------------------------- 1 | package fr.mikado.calypsoinspectorandroid; 2 | 3 | import android.content.Context; 4 | import fr.mikado.calypso.CalypsoEnvironment; 5 | import org.jdom2.Document; 6 | 7 | import java.io.IOException; 8 | 9 | public class AndroidCalypsoEnvironment extends CalypsoEnvironment { 10 | 11 | private final Context context; 12 | 13 | public AndroidCalypsoEnvironment(Context context){ 14 | super(); 15 | this.context = context; 16 | this.buildEnvironmentList(); 17 | } 18 | 19 | @Override 20 | public Document loadDocument(String filename) { 21 | try { 22 | return new XMLIOImpl(context, XMLIOImpl.XMLIOType.AssetFile).loadDocument(filename); 23 | } catch (IOException e) { 24 | return null; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/fr/mikado/calypsoinspectorandroid/AndroidCalypsoRawDump.java: -------------------------------------------------------------------------------- 1 | package fr.mikado.calypsoinspectorandroid; 2 | 3 | import android.content.Context; 4 | import android.widget.Toast; 5 | import fr.mikado.calypso.BitArray; 6 | import fr.mikado.calypso.CalypsoEnvironment; 7 | import fr.mikado.calypso.CalypsoFile; 8 | import fr.mikado.calypso.CalypsoRawDump; 9 | import org.jdom2.Document; 10 | import org.jdom2.Element; 11 | import org.jdom2.JDOMException; 12 | import org.jdom2.input.SAXBuilder; 13 | import org.jdom2.output.Format; 14 | import org.jdom2.output.XMLOutputter; 15 | 16 | import java.io.*; 17 | import java.util.ArrayList; 18 | 19 | import static fr.mikado.calypso.CalypsoFile.CalypsoFileType.EF; 20 | 21 | /* 22 | 23 | fr.mikado.calypso.CalypsoRawDump est maintenant portable avec XMLIOInterface ! 24 | 25 | public class AndroidCalypsoRawDump extends CalypsoRawDump{ 26 | 27 | private Context context; 28 | 29 | public AndroidCalypsoRawDump(Context context, CalypsoEnvironment env) { 30 | super(env); 31 | } 32 | 33 | public void writeXML(String filename){ 34 | Document doc = this.getXMLDump(); 35 | XMLIOImpl xmlio = new XMLIOImpl(context, XMLIOImpl.XMLIOType.InternalStorage); 36 | 37 | if(xmlio.writeDocument(doc, filename)) 38 | Toast.makeText(context, "Saved as " + filename, Toast.LENGTH_SHORT).show(); 39 | else 40 | Toast.makeText(context, "Could not save XML ! IO error.", Toast.LENGTH_SHORT).show(); 41 | } 42 | 43 | public void loadXML(String filename) throws IOException { 44 | Document dump = new XMLIOImpl(context, XMLIOImpl.XMLIOType.InternalStorage).loadDocument(filename); 45 | if(dump == null){ 46 | Toast.makeText(context, "Could not load the dump !", Toast.LENGTH_SHORT).show(); 47 | return; 48 | } 49 | Element root = dump.getRootElement(); 50 | 51 | this.dump = new ArrayList<>(); 52 | for(Element f : root.getChildren("file")) 53 | this.dump.add(new CalypsoFileDump(f)); 54 | } 55 | 56 | } 57 | */ 58 | -------------------------------------------------------------------------------- /app/src/main/java/fr/mikado/calypsoinspectorandroid/CalypsoDump.java: -------------------------------------------------------------------------------- 1 | package fr.mikado.calypsoinspectorandroid; 2 | 3 | import android.content.Context; 4 | import fr.mikado.calypso.CalypsoEnvironment; 5 | import fr.mikado.calypso.CalypsoFile; 6 | import fr.mikado.calypso.CalypsoRecord; 7 | import fr.mikado.calypso.CalypsoRecordField; 8 | import org.jdom2.Document; 9 | import org.jdom2.Element; 10 | 11 | import java.io.Serializable; 12 | import java.text.DateFormat; 13 | import java.text.ParseException; 14 | import java.text.SimpleDateFormat; 15 | import java.util.ArrayList; 16 | import java.util.Date; 17 | import java.util.HashMap; 18 | import java.util.Locale; 19 | 20 | import static fr.mikado.calypso.CalypsoFile.CalypsoFileType.EF; 21 | 22 | /** 23 | * Created by alexis on 06/04/17. 24 | */ 25 | public class CalypsoDump { 26 | 27 | private CalypsoEnvironment env; 28 | private ArrayList dumpTreeHeaders; 29 | private HashMap> dumpTreeChildren; 30 | 31 | public CalypsoDump(CalypsoEnvironment env){ 32 | this.env = env; 33 | this.dumpTreeChildren = new HashMap<>(); 34 | this.dumpTreeHeaders = new ArrayList<>(); 35 | } 36 | 37 | public void dumpTrips(String headerName){ 38 | this.dumpTreeChildren.put(headerName, dumpTrips(this.env)); 39 | this.dumpTreeHeaders.add(headerName); 40 | } 41 | 42 | public void dumpProfiles(String headerName){ 43 | this.dumpTreeChildren.put(headerName, dumpProfiles(this.env)); 44 | this.dumpTreeHeaders.add(headerName); 45 | } 46 | 47 | public void dumpContracts(String headerName){ 48 | this.dumpTreeChildren.put(headerName, dumpContracts(this.env)); 49 | this.dumpTreeHeaders.add(headerName); 50 | } 51 | 52 | public void dumpHolder(String headerName){ 53 | ArrayList holderArray = new ArrayList<>(); 54 | holderArray.add(dumpHolder(this.env)); 55 | this.dumpTreeChildren.put(headerName, holderArray); 56 | this.dumpTreeHeaders.add(headerName); 57 | } 58 | 59 | public CalypsoDumpListAdapter getListAdapter(Context context){ 60 | return new CalypsoDumpListAdapter(context, this.dumpTreeHeaders, this.dumpTreeChildren); 61 | } 62 | 63 | public static ArrayList dumpTrips(CalypsoEnvironment env) { 64 | ArrayList res = new ArrayList<>(); 65 | CalypsoFile events = env.getFile("Events"); 66 | if(events == null){ 67 | res.add("Events file not loaded"); 68 | return res; 69 | } 70 | CalypsoFile contracts = env.getFile("Contracts"); 71 | 72 | for(CalypsoRecord rec : events.getRecords()){ 73 | // basic event details 74 | String date = rec.getRecordField("Event Date").getConvertedValue(); 75 | String time = rec.getRecordField("Event Time").getConvertedValue(); 76 | CalypsoRecordField event = rec.getRecordField("Event"); 77 | String code = event.getSubfield("EventCode").getConvertedValue(); 78 | String stop = event.getSubfield("EventLocationId").getConvertedValue(); 79 | String route = event.getSubfield("EventRouteNumber").getConvertedValue(); 80 | String direction = event.getSubfield("EventData").getSubfield("EventDataRouteDirection").getConvertedValue(); 81 | 82 | String fare = ""; 83 | if(contracts != null) { 84 | // which contract for this event ? 85 | fare = "Error while decoding contract pointer. - Dog, probably."; 86 | int farePointer = event.getSubfield("EventContractPointer").getBits().getInt(); 87 | int contractIndex = env.getContractIndex(farePointer); 88 | if (contractIndex >= 0){ 89 | if(contracts.getRecords().size() > contractIndex) 90 | fare = "Interrupted read"; 91 | else{ 92 | CalypsoRecord contract = contracts.getRecords().get(contractIndex); 93 | CalypsoRecordField contractBitmap = contract.getRecordField("PublicTransportContractBitmap"); 94 | CalypsoRecordField contractType = contractBitmap.getSubfield("ContractType"); 95 | fare = contractType.getConvertedValue(); 96 | } 97 | } 98 | } 99 | 100 | StringBuilder sb = new StringBuilder(); 101 | sb.append(" Date : " + date + " " + time + "\n"); 102 | sb.append(" Arrêt : " + stop + "\n"); 103 | sb.append(" Ligne : " + route + "\n"); 104 | sb.append(" Sens : " + direction + "\n"); 105 | sb.append(" Code : " + code + "\n"); 106 | sb.append(" Contrat : " + fare); 107 | 108 | res.add(sb.toString()); 109 | } 110 | return res; 111 | } 112 | 113 | public static ArrayList dumpProfiles(CalypsoEnvironment env){ 114 | ArrayList res = new ArrayList<>(); 115 | CalypsoFile envHolder= env.getFile("Environment, Holder"); 116 | if(envHolder == null) { 117 | res.add("File not loaded"); 118 | return res; 119 | } 120 | CalypsoRecord envHolderRec = envHolder.getRecords().get(0); 121 | if(envHolderRec == null) { 122 | res.add("Record #0 not found"); 123 | return res; 124 | } 125 | CalypsoRecordField holderProfiles = envHolderRec.getRecordField("Holder Bitmap").getSubfield("Holder Profiles(0..4)"); 126 | 127 | for(CalypsoRecordField f : holderProfiles.getSubfields()) { 128 | if(!f.isFilled()) { 129 | res.add("No profiles."); 130 | break; 131 | } 132 | StringBuilder sb = new StringBuilder(); 133 | sb.append(" Label : " + f.getSubfield("Profile Number").getConvertedValue() + "\n"); 134 | sb.append(" Profile date : " + f.getSubfield("Profile Date").getConvertedValue()); 135 | res.add(sb.toString()); 136 | } 137 | return res; 138 | } 139 | 140 | public static ArrayList dumpContracts(CalypsoEnvironment env){ 141 | ArrayList res = new ArrayList<>(); 142 | DateFormat format = new SimpleDateFormat("dd/MM/yyyy", Locale.FRANCE); 143 | Date now = new Date(); 144 | 145 | CalypsoFile contracts = env.getFile("Contracts"); 146 | if(contracts != null) { 147 | // which contract for this event ? 148 | for(CalypsoRecord contract : contracts.getRecords()) { 149 | CalypsoRecordField contractBitmap = contract.getRecordField("PublicTransportContractBitmap"); 150 | CalypsoRecordField contractType = contractBitmap.getSubfield("ContractType"); 151 | CalypsoRecordField contractStatus= contractBitmap.getSubfield("ContractStatus"); 152 | CalypsoRecordField contractValidity = contractBitmap.getSubfield("ContractValidityInfo"); 153 | CalypsoRecordField contractStart = contractValidity.getSubfield("ContractValidityStartDate"); 154 | CalypsoRecordField contractEnd = contractValidity.getSubfield("ContractValidityEndDate"); 155 | String contractEndStr = contractEnd.getConvertedValue(); 156 | 157 | StringBuilder sb = new StringBuilder(); 158 | sb.append(" Label : " + contractType.getConvertedValue() + "\n"); 159 | sb.append(" Start date : " + contractStart.getConvertedValue() + "\n"); 160 | if(contractEndStr.length() > 0) { 161 | String exp = " End date : " + contractEndStr; 162 | try { 163 | if (now.after(format.parse(contractEndStr))) 164 | exp += " (EXPIRED)\n"; 165 | else 166 | exp += "\n"; 167 | } catch (ParseException e) { 168 | } 169 | sb.append(exp); 170 | } 171 | sb.append(" Status : " + contractStatus.getConvertedValue()); 172 | res.add(sb.toString()); 173 | } 174 | }else 175 | res.add("Contracts file not loaded"); 176 | return res; 177 | } 178 | 179 | public static String dumpHolder(CalypsoEnvironment env){ 180 | CalypsoFile envHolderFile = env.getFile("Environment, Holder"); 181 | if(envHolderFile == null) 182 | return "EnvHolder file not loaded"; 183 | CalypsoRecord envHolder = envHolderFile.getRecords().get(0); 184 | if(envHolder == null) 185 | return "Record #0 not found"; 186 | CalypsoRecordField holder = envHolder.getRecordField("Holder Bitmap"); 187 | if(holder == null) 188 | return "Holder bitmap not found"; 189 | 190 | CalypsoRecordField holderName = holder.getSubfield("Holder Name"); 191 | String holderSurname = holderName.getSubfield("Holder Surname").getConvertedValue(); 192 | String holderForename = holderName.getSubfield("Holder Forename").getConvertedValue(); 193 | 194 | CalypsoRecordField holderBirth = holder.getSubfield("Holder Birth"); 195 | String holderBirthDate = holderBirth.getSubfield("Birth Date").getConvertedValue(); 196 | String holderBirthPlace = holderBirth.getSubfield("Birth Place").getConvertedValue(); 197 | 198 | String holderIdNumber = holder.getSubfield("Holder Id Number").getConvertedValue(); 199 | String holderCompany = holder.getSubfield("Holder Company").getConvertedValue(); 200 | 201 | CalypsoRecordField holderData = holder.getSubfield("Holder Data"); 202 | String holderCardStatus = holderData.getSubfield("HolderDataCardStatus").getConvertedValue(); 203 | String holderStudyPlace = holderData.getSubfield("HolderDataStudyPlace").getConvertedValue(); 204 | 205 | String[] holderProfileStartDates = new String[4]; 206 | for(int i = 1;i<5;i++) 207 | holderProfileStartDates[i-1] = holderData.getSubfield("HolderDataProfileStartDate"+i).getConvertedValue(); 208 | 209 | StringBuilder sb = new StringBuilder(); 210 | if(holderForename.length() > 0) 211 | sb.append("Forename : " + holderForename + "\n"); 212 | if(holderSurname.length() > 0) 213 | sb.append("Surname : " + holderSurname + "\n"); 214 | if(holderBirthDate.length() > 0) 215 | sb.append("Birth date : " + holderBirthDate + "\n"); 216 | if(holderBirthPlace.length() > 0) 217 | sb.append("Birth place : " + holderBirthPlace + "\n"); 218 | if(holderIdNumber.length() > 0) 219 | sb.append("Id number : " + holderIdNumber + "\n"); 220 | if(holderCompany.length() > 0) 221 | sb.append("Company : " + holderCompany + "\n"); 222 | if(holderStudyPlace.length() > 0) 223 | sb.append("Study place : " + holderStudyPlace + "\n"); 224 | if(holderCardStatus.length() > 0) 225 | sb.append("CardStatus : " + holderCardStatus + "\n"); 226 | 227 | return sb.toString(); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /app/src/main/java/fr/mikado/calypsoinspectorandroid/CalypsoDumpListAdapter.java: -------------------------------------------------------------------------------- 1 | package fr.mikado.calypsoinspectorandroid; 2 | 3 | import android.content.Context; 4 | import android.graphics.Typeface; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.BaseExpandableListAdapter; 9 | import android.widget.TextView; 10 | 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | 14 | /** 15 | * Created by alexis on 06/04/17. 16 | */ 17 | public class CalypsoDumpListAdapter extends BaseExpandableListAdapter { 18 | 19 | private Context context; 20 | private ArrayList headers; 21 | private HashMap> children; 22 | private static Typeface lucidaConsole; 23 | 24 | public CalypsoDumpListAdapter(Context context, ArrayList headers, HashMap> children){ 25 | this.context = context; 26 | this.headers = headers; 27 | this.children = children; 28 | 29 | if(lucidaConsole == null){ 30 | lucidaConsole = Typeface.createFromAsset(context.getAssets(), "lucida_console.ttf"); 31 | } 32 | } 33 | 34 | public void addHeader(String headerName){ 35 | this.headers.add(headerName); 36 | this.children.put(headerName, new ArrayList()); 37 | } 38 | 39 | public void addChildren(String headerName, ArrayList children){ 40 | ArrayList cat = this.children.get(headerName); 41 | if(cat == null) { 42 | this.addHeader(headerName); 43 | cat = this.children.get(headerName); 44 | } 45 | cat.addAll(children); 46 | } 47 | 48 | public void clear(){ 49 | this.headers = new ArrayList<>(); 50 | this.children = new HashMap<>(); 51 | } 52 | 53 | @Override 54 | public int getGroupCount() { 55 | return this.headers.size(); 56 | } 57 | 58 | @Override 59 | public int getChildrenCount(int groupPosition) { 60 | return this.children.get(this.headers.get(groupPosition)).size(); 61 | } 62 | 63 | @Override 64 | public Object getGroup(int groupPosition) { 65 | return this.headers.get(groupPosition); 66 | } 67 | 68 | @Override 69 | public Object getChild(int groupPosition, int childPosition) { 70 | return this.children.get(this.headers.get(groupPosition)).get(childPosition); 71 | } 72 | 73 | @Override 74 | public long getGroupId(int groupPosition) { 75 | return groupPosition; 76 | } 77 | 78 | @Override 79 | public long getChildId(int groupPosition, int childPosition) { 80 | return childPosition; 81 | } 82 | 83 | @Override 84 | public boolean hasStableIds() { 85 | return false; 86 | } 87 | 88 | @Override 89 | public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { 90 | String headerTitle = (String)this.getGroup(groupPosition); 91 | if(convertView == null){ 92 | LayoutInflater infalInflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 93 | convertView = infalInflater.inflate(R.layout.list_group, null); 94 | } 95 | TextView labelListHeader = (TextView)convertView.findViewById(R.id.labelListHeader); 96 | labelListHeader.setText(headerTitle); 97 | return convertView; 98 | } 99 | 100 | @Override 101 | public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { 102 | String childText = (String)this.getChild(groupPosition, childPosition); 103 | if(convertView == null){ 104 | LayoutInflater infalInflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 105 | convertView = infalInflater.inflate(R.layout.list_item, null); 106 | } 107 | TextView labelListItem = (TextView)convertView.findViewById(R.id.labelListItem); 108 | labelListItem.setText(childText); 109 | labelListItem.setTypeface(lucidaConsole); 110 | return convertView; 111 | } 112 | 113 | @Override 114 | public boolean isChildSelectable(int groupPosition, int childPosition) { 115 | return false; 116 | } 117 | } 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /app/src/main/java/fr/mikado/calypsoinspectorandroid/MainActivity.java: -------------------------------------------------------------------------------- 1 | package fr.mikado.calypsoinspectorandroid; 2 | 3 | import android.app.AlertDialog; 4 | import android.app.PendingIntent; 5 | import android.content.*; 6 | import android.hardware.usb.UsbDevice; 7 | import android.hardware.usb.UsbManager; 8 | import android.nfc.NfcAdapter; 9 | import android.nfc.Tag; 10 | import android.nfc.tech.IsoDep; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.os.Bundle; 13 | import android.view.Menu; 14 | import android.view.MenuInflater; 15 | import android.view.MenuItem; 16 | import android.view.View; 17 | import android.widget.*; 18 | import com.acs.smartcard.Reader; 19 | import fr.mikado.calypso.*; 20 | import fr.mikado.isodep.CardException; 21 | import fr.mikado.isodep.CommandAPDU; 22 | import fr.mikado.isodep.IsoDepInterface; 23 | 24 | import java.io.File; 25 | import java.io.FilenameFilter; 26 | import java.io.IOException; 27 | import java.text.DateFormat; 28 | import java.text.SimpleDateFormat; 29 | import java.util.ArrayList; 30 | import java.util.Date; 31 | import java.util.HashMap; 32 | 33 | public class MainActivity extends AppCompatActivity { 34 | 35 | private NfcAdapter nfc; 36 | private AndroidCalypsoEnvironment calypsoEnv; 37 | 38 | private Context context; 39 | private TextView statusLabel; 40 | private Spinner networksSpinner; 41 | private ExpandableListView treeView; 42 | private Button saveButton; 43 | private Button loadButton; 44 | private DateFormat dumpDateFormat; 45 | 46 | // ACS SHIT 47 | private Reader acsReader; 48 | private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"; 49 | private UsbManager usbManager; 50 | private static final String[] stateStrings = {"Unknown", "Absent", "Present", "Swallowed", "Powered", "Negotiable", "Specific"}; 51 | private PendingIntent usbPersmissionIntent; 52 | private BroadcastReceiver acsReceiver; 53 | 54 | @Override 55 | public boolean onCreateOptionsMenu(Menu menu){ 56 | MenuInflater inflater = getMenuInflater(); 57 | inflater.inflate(R.menu.actionbar, menu); 58 | return true; 59 | } 60 | 61 | @Override 62 | public boolean onOptionsItemSelected(MenuItem item){ 63 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 64 | switch(item.getItemId()){ 65 | case R.id.moreNetworksMenuItem: 66 | builder.setIcon(R.mipmap.ic_launcher); 67 | builder.setTitle(R.string.app_name); 68 | builder.setView(getLayoutInflater().inflate(R.layout.niy, null, false)); 69 | builder.create(); 70 | builder.show(); 71 | break; 72 | case R.id.aboutMenuItem: 73 | builder.setIcon(R.mipmap.ic_launcher); 74 | builder.setTitle(R.string.app_name); 75 | builder.setView(getLayoutInflater().inflate(R.layout.about, null, false)); 76 | builder.create(); 77 | builder.show(); 78 | break; 79 | case R.id.clearMenuItem: 80 | this.treeView.setAdapter(new CalypsoDumpListAdapter(this, new ArrayList(), new HashMap>())); 81 | break; 82 | } 83 | return true; 84 | } 85 | 86 | @Override 87 | protected void onCreate(Bundle savedInstanceState) { 88 | super.onCreate(savedInstanceState); 89 | setContentView(R.layout.activity_main); 90 | this.dumpDateFormat = new SimpleDateFormat("dd-MM-yyy_hh-mm-ss"); 91 | this.context = this.getApplicationContext(); 92 | this.calypsoEnv = new AndroidCalypsoEnvironment(this.context); 93 | 94 | this.treeView = (ExpandableListView)findViewById(R.id.tree_view); 95 | 96 | this.networksSpinner = (Spinner)findViewById(R.id.networksSpinner); 97 | ArrayAdapter networksSpinnerAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, this.calypsoEnv.getAvailableNetworks()); 98 | networksSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 99 | this.networksSpinner.setAdapter(networksSpinnerAdapter); 100 | this.networksSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 101 | @Override 102 | public void onItemSelected(AdapterView parent, View view, int position, long id) { 103 | statusLabel.setText("Loading network config..."); 104 | ArrayList networks = calypsoEnv.getAvailableNetworks(); 105 | if(position >= networks.size()) 106 | return; 107 | try { 108 | System.out.println("Loading network : " + networks.get(position)); 109 | calypsoEnv.clear(); 110 | calypsoEnv.loadEnvironment(networks.get(position)); 111 | statusLabel.setText(R.string.net_loaded_waiting_card); 112 | Toast.makeText(getApplicationContext(), "Network loaded", Toast.LENGTH_SHORT).show(); 113 | } catch (Exception e) { 114 | Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show(); 115 | } 116 | } 117 | 118 | @Override 119 | public void onNothingSelected(AdapterView parent) { 120 | } 121 | }); 122 | this.networksSpinner.setSelection(1); 123 | 124 | this.saveButton = (Button)findViewById(R.id.saveButton); 125 | this.saveButton.setOnClickListener(new View.OnClickListener() { 126 | @Override 127 | public void onClick(View v) { 128 | ListAdapter adapter = treeView.getAdapter(); 129 | if(adapter == null || adapter.isEmpty()){ 130 | Toast.makeText(v.getContext(), "Nothing to save !", Toast.LENGTH_SHORT).show(); 131 | return; 132 | } 133 | String filename = dumpDateFormat.format(new Date()) + ".dump.xml"; 134 | try { 135 | XMLIOImpl xmlio = new XMLIOImpl(MainActivity.this, XMLIOImpl.XMLIOType.InternalStorage); 136 | new CalypsoRawDump(calypsoEnv).writeXML(xmlio, filename); 137 | } catch (IOException e) { 138 | Toast.makeText(v.getContext(), "IO: Could not save the dump !", Toast.LENGTH_SHORT).show(); 139 | } 140 | Toast.makeText(v.getContext(), "Dump saved as " + filename, Toast.LENGTH_SHORT).show(); 141 | } 142 | }); 143 | 144 | this.loadButton = (Button)findViewById(R.id.loadButton); 145 | 146 | this.loadButton.setOnClickListener(new View.OnClickListener() { 147 | @Override 148 | public void onClick(View v) { 149 | File dir = getFilesDir(); 150 | final File[] dumps = dir.listFiles(new FilenameFilter() { 151 | @Override 152 | public boolean accept(File dir, String filename) { 153 | return filename.toLowerCase().endsWith(".dump.xml"); 154 | } 155 | }); 156 | if(dumps.length == 0){ 157 | Toast.makeText(v.getContext(), "Cannot find any saved dumps.", Toast.LENGTH_SHORT).show(); 158 | return; 159 | } 160 | final CharSequence[] dumpList = new CharSequence[dumps.length]; 161 | for(int i = 0;i(), new HashMap>())); 171 | CalypsoRawDump dumpXML = new CalypsoRawDump(calypsoEnv); 172 | dumpXML.loadXML(new XMLIOImpl(MainActivity.this, XMLIOImpl.XMLIOType.InternalStorage), dumps[which].getName()); 173 | 174 | calypsoEnv.loadDump(dumpXML); 175 | CalypsoDump dump = new CalypsoDump(calypsoEnv); 176 | dump.dumpHolder("Holder"); 177 | dump.dumpProfiles("Profiles"); 178 | dump.dumpContracts("Contracts"); 179 | dump.dumpTrips("Trips"); 180 | treeView.setAdapter(dump.getListAdapter(MainActivity.this)); 181 | } catch (Exception e) { 182 | e.printStackTrace(); 183 | } 184 | } 185 | }); 186 | saveLoader.setNegativeButton("Clear", new DialogInterface.OnClickListener() { 187 | @Override 188 | public void onClick(DialogInterface dialog, int which) { 189 | AlertDialog.Builder confirm = new AlertDialog.Builder(MainActivity.this); 190 | confirm.setTitle("Delete all saved dumps ?"); 191 | confirm.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { 192 | @Override 193 | public void onClick(DialogInterface dialog, int which) { 194 | dialog.dismiss(); 195 | } 196 | }); 197 | confirm.setPositiveButton("Confirm", new DialogInterface.OnClickListener() { 198 | @Override 199 | public void onClick(DialogInterface confirmDial, int which) { 200 | File[] dumps = getFilesDir().listFiles(new FilenameFilter() { 201 | @Override 202 | public boolean accept(File dir, String filename) { 203 | return filename.toLowerCase().endsWith(".dump.xml"); 204 | } 205 | }); 206 | for(File f : dumps) 207 | f.delete(); 208 | confirmDial.dismiss(); 209 | } 210 | }); 211 | confirm.show(); 212 | } 213 | }); 214 | saveLoader.show(); 215 | } 216 | }); 217 | 218 | if(this.calypsoEnv == null){ 219 | Toast.makeText(this, "Environment not loaded !", Toast.LENGTH_SHORT).show(); 220 | finish(); 221 | } 222 | 223 | Switch externalSwitch = (Switch) findViewById(R.id.externalSwitch); 224 | externalSwitch.setEnabled(false); 225 | TextView activeReaderLabel = (TextView) findViewById(R.id.activeReaderLabel); 226 | activeReaderLabel.setText("None"); 227 | this.statusLabel = (TextView) findViewById(R.id.statusLabel); 228 | this.statusLabel.setText("Initializing..."); 229 | 230 | // ONBOARD NFC 231 | this.nfc = NfcAdapter.getDefaultAdapter(this); 232 | if(this.nfc == null || !this.nfc.isEnabled()){ 233 | Toast.makeText(this, "NFC is not enabled !", Toast.LENGTH_SHORT).show(); 234 | finish(); 235 | } 236 | 237 | // ACS READERS 238 | this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); 239 | this.acsReader = new Reader(this.usbManager); 240 | this.acsReader.setOnStateChangeListener(new Reader.OnStateChangeListener() { 241 | @Override 242 | public void onStateChange(int slotNum, int prevState, int currState) { 243 | if(prevState < Reader.CARD_UNKNOWN || prevState > Reader.CARD_SPECIFIC) 244 | prevState = Reader.CARD_UNKNOWN; 245 | if(prevState < Reader.CARD_UNKNOWN || prevState > Reader.CARD_SPECIFIC) 246 | prevState = Reader.CARD_UNKNOWN; 247 | 248 | final String logString = "Slot "+slotNum+" : "+stateStrings[prevState]+" > " + stateStrings[currState]; 249 | runOnUiThread(new Runnable() { 250 | @Override 251 | public void run() { 252 | logMsg(logString); 253 | } 254 | }); 255 | } 256 | }); 257 | 258 | this.acsReceiver = new BroadcastReceiver(){ 259 | @Override 260 | public void onReceive(Context context, Intent intent) { 261 | if(intent == null) 262 | return; 263 | String action = intent.getAction(); 264 | if(ACTION_USB_PERMISSION.equals(action)){ 265 | synchronized (this){ 266 | UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); 267 | if(intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { 268 | if (device != null) { 269 | //Open reader 270 | logMsg("Opening reader " + device.getDeviceName() + "..."); 271 | } 272 | }else 273 | logMsg("Permission denied for device " + device.getDeviceName()+ "."); 274 | } 275 | } 276 | } 277 | }; 278 | 279 | this.usbPersmissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); 280 | IntentFilter filter = new IntentFilter(); 281 | filter.addAction(ACTION_USB_PERMISSION); 282 | filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); 283 | registerReceiver(acsReceiver, filter); 284 | 285 | externalSwitch.setEnabled(true); 286 | externalSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 287 | @Override 288 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 289 | if(isChecked){ 290 | UsbManager usbMgr = (UsbManager)getSystemService("usb"); 291 | UsbDevice readerDev = null; 292 | for(UsbDevice dev : usbMgr.getDeviceList().values()) 293 | if(dev.getVendorId() == 0x072f){ 294 | readerDev = dev; 295 | break; 296 | } 297 | Toast t = Toast.makeText(getApplicationContext(), "lel", Toast.LENGTH_SHORT); 298 | t.setDuration(Toast.LENGTH_SHORT); 299 | if(readerDev == null){ 300 | t.setText("Reader unplugged"); 301 | buttonView.setChecked(false); 302 | }else{ 303 | t.setText("Reader found !"); 304 | MainActivity.this.statusLabel.setText("External NFC reader"); 305 | buttonView.setChecked(true); 306 | } 307 | t.show(); 308 | }else{ 309 | Toast.makeText(getApplicationContext(), "Reader deselected", Toast.LENGTH_SHORT).show(); 310 | MainActivity.this.statusLabel.setText("Onboard NFC reader"); 311 | } 312 | } 313 | }); 314 | 315 | activeReaderLabel.setText("Onbard NFC reader"); 316 | } 317 | 318 | private void logMsg(String message){ 319 | DateFormat fmt = new SimpleDateFormat("dd-MM-yyy HH:mm:ss : "); 320 | System.out.println(fmt.format(new Date()) + message); 321 | } 322 | 323 | @Override 324 | protected void onResume(){ 325 | super.onResume(); 326 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, this.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); 327 | this.nfc.enableForegroundDispatch(this, pendingIntent, null, null); 328 | } 329 | 330 | @Override 331 | protected void onDestroy(){ 332 | super.onDestroy(); 333 | unregisterReceiver(this.acsReceiver); 334 | } 335 | 336 | 337 | private void handleIsoDep(IsoDepInterface iso){ 338 | this.calypsoEnv.purgeFilesContents(); 339 | try { 340 | CalypsoCard c = new CalypsoCard(iso, this.calypsoEnv); 341 | c.read(); 342 | c.dump(false); 343 | } catch (CardException e) { 344 | System.out.println("Exception while reading : " + e.getMessage()); 345 | Toast.makeText(this, "Don't move while I read the card !", Toast.LENGTH_SHORT).show(); 346 | return; 347 | } 348 | 349 | CalypsoDump dump = new CalypsoDump(this.calypsoEnv); 350 | dump.dumpHolder("Holder"); 351 | dump.dumpProfiles("Profiles"); 352 | dump.dumpContracts("Contracts"); 353 | dump.dumpTrips("Trips"); 354 | this.treeView.setAdapter(dump.getListAdapter(this)); 355 | } 356 | 357 | @Override 358 | protected void onNewIntent(Intent intent){ 359 | super.onNewIntent(intent); 360 | if(intent == null) 361 | return; 362 | 363 | if(NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { 364 | Toast.makeText(this, "NFC tag discovered", Toast.LENGTH_SHORT).show(); 365 | Tag card = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); 366 | TextView message = (TextView) findViewById(R.id.statusLabel); 367 | message.setText(getString(R.string.card_found)); 368 | 369 | IsoDep isodep = IsoDep.get(card); 370 | if(isodep == null){ 371 | Toast.makeText(this, "This is not an NFC Calypso card !", Toast.LENGTH_SHORT).show(); 372 | message.setText(R.string.net_loaded_waiting_card); 373 | return; 374 | } 375 | try { 376 | isodep.connect(); 377 | } catch (IOException e) { 378 | e.printStackTrace(); 379 | return; 380 | } 381 | if(isodep.isConnected()) { 382 | OnboardIsoDepImpl iso = new OnboardIsoDepImpl(isodep); 383 | // check if calypso card 384 | /* This may be exclusive to PassPass cards. No documentation on that so let's remove it. 385 | try { 386 | byte[] rdata = iso.transmit(new CommandAPDU(new byte[]{0x00, 0x10, 0x00, 0x00, 0x00})).getData(); 387 | // rdata[45] : Celego 2D 388 | // rdata[52] : Celego 34 389 | // rdata[53] : Celego 35 390 | // rdata[54] : Celego Chip Type 391 | // rdata[55] : Celego ROM Version 392 | if(rdata[54] != 0x3C || rdata[53] != 0x00){ 393 | Toast.makeText(this, "This is not an NFC Calypso card !", Toast.LENGTH_SHORT).show(); 394 | message.setText(getString(R.string.net_loaded_waiting_card)); 395 | return; 396 | } 397 | } catch (CardException e) { 398 | Toast.makeText(this, "Error while checking if Calypso card", Toast.LENGTH_SHORT).show(); 399 | }*/ 400 | 401 | handleIsoDep(iso); 402 | message.setText(getString(R.string.card_read_waiting)); 403 | }else 404 | message.setText(getString(R.string.cannot_connect_isodep)); 405 | } 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /app/src/main/java/fr/mikado/calypsoinspectorandroid/OnboardIsoDepImpl.java: -------------------------------------------------------------------------------- 1 | package fr.mikado.calypsoinspectorandroid; 2 | 3 | import android.nfc.tech.IsoDep; 4 | import fr.mikado.isodep.CardException; 5 | import fr.mikado.isodep.CommandAPDU; 6 | import fr.mikado.isodep.IsoDepInterface; 7 | import fr.mikado.isodep.ResponseAPDU; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * Created by alexis on 05/04/17. 13 | */ 14 | public class OnboardIsoDepImpl implements IsoDepInterface { 15 | 16 | private IsoDep iso; 17 | 18 | public OnboardIsoDepImpl(IsoDep iso){ 19 | this.iso = iso; 20 | } 21 | 22 | @Override 23 | public ResponseAPDU transmit(CommandAPDU commandAPDU) throws CardException { 24 | byte[] r; 25 | try { 26 | r = iso.transceive(commandAPDU.getBytes()); 27 | } catch (IOException e) { 28 | throw new CardException(e.getMessage()); 29 | } 30 | return new ResponseAPDU(r); 31 | } 32 | 33 | @Override 34 | public byte[] getATR() { 35 | return iso.getHistoricalBytes(); 36 | } 37 | 38 | @Override 39 | public void disconnect() throws CardException { 40 | try { 41 | iso.close(); 42 | } catch (IOException e) { 43 | throw new CardException(e.getMessage()); 44 | } 45 | } 46 | } 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /app/src/main/java/fr/mikado/calypsoinspectorandroid/ReaderProgress.java: -------------------------------------------------------------------------------- 1 | package fr.mikado.calypsoinspectorandroid; 2 | 3 | import android.widget.ArrayAdapter; 4 | 5 | /** 6 | * Created by alexis on 06/04/17. 7 | */ 8 | public class ReaderProgress { 9 | 10 | private String[] lines; 11 | private ArrayAdapter output; 12 | 13 | public ReaderProgress(ArrayAdapter output, String[] lines){ 14 | this.output = output; 15 | this.lines = lines; 16 | } 17 | 18 | public ArrayAdapter getOutput() { 19 | return output; 20 | } 21 | 22 | public void setOutput(ArrayAdapter output) { 23 | this.output = output; 24 | } 25 | 26 | public String[] getLines() { 27 | return lines; 28 | } 29 | 30 | public void setLines(String[] lines) { 31 | this.lines = lines; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/fr/mikado/calypsoinspectorandroid/ReaderTask.java: -------------------------------------------------------------------------------- 1 | package fr.mikado.calypsoinspectorandroid; 2 | 3 | import android.os.AsyncTask; 4 | import android.widget.ArrayAdapter; 5 | import android.widget.ListView; 6 | import fr.mikado.calypso.CalypsoEnvironment; 7 | import fr.mikado.isodep.IsoDepInterface; 8 | 9 | /** 10 | * Created by alexis on 06/04/17. 11 | */ 12 | public class ReaderTask extends AsyncTask { 13 | 14 | private CalypsoEnvironment env; 15 | 16 | public ReaderTask(CalypsoEnvironment env){ 17 | this.env = env; 18 | } 19 | 20 | @Override 21 | protected Void doInBackground(IsoDepInterface... isoDep) { 22 | 23 | // On lit la carte. 24 | 25 | 26 | return null; 27 | } 28 | 29 | protected void onProgressUpdate(ReaderProgress progress){ 30 | ArrayAdapter output = progress.getOutput(); 31 | output.addAll(progress.getLines()); 32 | for(String s : progress.getLines()) 33 | System.out.println(s); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/fr/mikado/calypsoinspectorandroid/XMLIOImpl.java: -------------------------------------------------------------------------------- 1 | package fr.mikado.calypsoinspectorandroid; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetManager; 5 | import android.widget.Toast; 6 | import fr.mikado.xmlio.XMLIOInterface; 7 | import org.jdom2.Document; 8 | import org.jdom2.JDOMException; 9 | import org.jdom2.input.SAXBuilder; 10 | import org.jdom2.output.Format; 11 | import org.jdom2.output.XMLOutputter; 12 | 13 | import java.io.FileNotFoundException; 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | 18 | public class XMLIOImpl implements XMLIOInterface { 19 | 20 | public enum XMLIOType { 21 | InternalStorage, AssetFile 22 | } 23 | 24 | private XMLIOType type; 25 | private Context context; 26 | 27 | public XMLIOImpl(Context context, XMLIOType type){ 28 | this.type = type; 29 | this.context = context; 30 | } 31 | 32 | @Override 33 | public Document loadDocument(String filename) throws IOException { 34 | InputStream is; 35 | 36 | switch (this.type){ 37 | case AssetFile: 38 | is = this.context.getAssets().open(filename); 39 | break; 40 | case InternalStorage: 41 | default: 42 | is = this.context.openFileInput(filename); 43 | } 44 | 45 | Document doc; 46 | try { 47 | doc = this.loadDocument(is); 48 | } catch (JDOMException e) { 49 | System.out.println("Error while parsing file"); 50 | e.printStackTrace(); 51 | return null; 52 | } catch (IOException e) { 53 | System.out.println("Cannot read file \""+filename+"\""); 54 | System.out.println(e.getMessage()); 55 | return null; 56 | } 57 | return doc; 58 | } 59 | 60 | public Document loadDocument(InputStream is) throws JDOMException, IOException { 61 | SAXBuilder sax = new SAXBuilder(); 62 | Document doc; 63 | doc = sax.build(is); 64 | return doc; 65 | } 66 | 67 | @Override 68 | public boolean writeDocument(Document doc, String filename) { 69 | FileOutputStream fos; 70 | try { 71 | fos = context.openFileOutput(filename, Context.MODE_PRIVATE); 72 | } catch (FileNotFoundException e) { 73 | Toast.makeText(context, "Could not save XML ! FileNotFoundException.", Toast.LENGTH_SHORT).show(); 74 | return false; 75 | } 76 | return writeDocument(doc, fos); 77 | } 78 | 79 | public boolean writeDocument(Document doc, FileOutputStream fos){ 80 | XMLOutputter outputter = new XMLOutputter(); 81 | outputter.setFormat(Format.getPrettyFormat()); 82 | try { 83 | outputter.output(doc, fos); 84 | } catch (IOException e) { 85 | e.printStackTrace(); 86 | Toast.makeText(context, "Could not save XML ! IO error.", Toast.LENGTH_SHORT).show(); 87 | return false; 88 | } 89 | return true; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/res/layout/about.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 25 | 26 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 18 | 19 | 25 | 26 | 31 | 32 | 33 | 37 | 38 | 44 | 45 | 51 | 52 | 53 | 54 | 58 | 59 | 65 | 66 | 74 | 75 | 76 | 77 | 82 | 83 | 88 | 89 | 93 | 94 | 95 | 96 | 100 | 101 |