diff --git a/.classpath b/.classpath
deleted file mode 100644
index 6c635c0..0000000
--- a/.classpath
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/.gitignore b/.gitignore
index ff2422f..37c3137 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,114 @@
-/assets
-/bin
-/gen
+
+# Created by https://www.gitignore.io/api/androidstudio
+
+### AndroidStudio ###
+# Covers files to be ignored for android development using Android Studio.
+
+# Built application files
+*.apk
+*.ap_
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+
+# Gradle files
+.gradle
+.gradle/
+build/
+
+# Signing files
+.signing/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio
+/*/build/
+/*/local.properties
+/*/out
+/*/*/build
+/*/*/production
+captures/
+.navigation/
+*.ipr
+*~
+*.swp
+
+# Android Patch
+gen-external-apklibs
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+
+# NDK
+obj/
+
+# IntelliJ IDEA
+*.iml
+*.iws
+/out/
+
+# https://stackoverflow.com/questions/26336709/android-studio-should-the-entire-idea-directory-be-in-git-ignore
+.idea
+
+# Keystore files
+*.jks
+
+# OS-specific files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+# Legacy Eclipse project files
+.classpath
+.project
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
+hs_err_pid*
+
+## Plugin-specific files:
+
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+### AndroidStudio Patch ###
+# Google Services plugin
+google-services.json
+
+!/gradle/wrapper/gradle-wrapper.jar
+
+# End of https://www.gitignore.io/api/androidstudio
diff --git a/.project b/.project
deleted file mode 100644
index e39595e..0000000
--- a/.project
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
- HelloSmartcard
-
-
-
-
-
- com.android.ide.eclipse.adt.ResourceManagerBuilder
-
-
-
-
- com.android.ide.eclipse.adt.PreCompilerBuilder
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- com.android.ide.eclipse.adt.ApkBuilder
-
-
-
-
-
- com.android.ide.eclipse.adt.AndroidNature
- org.eclipse.jdt.core.javanature
-
-
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..5b39783
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,21 @@
+apply plugin: 'com.android.application'
+android {
+ compileSdkVersion 'Giesecke Devrient GmbH:Open Mobile API:21'
+ buildToolsVersion '27.0.3'
+
+ defaultConfig {
+ applicationId "com.gieseckedevrient.android.hellosmartcard"
+ minSdkVersion 4
+ targetSdkVersion 15
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+}
+
+dependencies {
+}
\ No newline at end of file
diff --git a/app/release/output.json b/app/release/output.json
new file mode 100644
index 0000000..b651919
--- /dev/null
+++ b/app/release/output.json
@@ -0,0 +1 @@
+[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0.1-SNAPSHOT","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
\ No newline at end of file
diff --git a/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
similarity index 100%
rename from AndroidManifest.xml
rename to app/src/main/AndroidManifest.xml
diff --git a/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/Eduroam.java b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/Eduroam.java
new file mode 100644
index 0000000..485de38
--- /dev/null
+++ b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/Eduroam.java
@@ -0,0 +1,55 @@
+package com.gieseckedevrient.android.hellosmartcard;
+
+import android.util.Log;
+
+import nl.mansoft.openmobileapi.util.CommandApdu;
+import nl.mansoft.openmobileapi.util.ResponseApdu;
+
+import java.io.IOException;
+
+public class Eduroam {
+ private final String TAG = Eduroam.class.getSimpleName();
+ public final static int OFFSET_USER = 0x00;
+ public final static int OFFSET_PASSWORD = 0x20;
+ private SmartcardIO mSmartcardIO;
+
+ public Eduroam(SmartcardIO smartcardIO) {
+ mSmartcardIO = smartcardIO;
+ }
+
+ public ResponseApdu selectEduroam() throws IOException {
+ CommandApdu c = new CommandApdu((byte)0x00, (byte)0xA4, (byte)0x00, (byte)0x00, new byte[] { 0x10, 0x00 });
+ return mSmartcardIO.runAPDU(c);
+ }
+
+ public ResponseApdu readEduroam() throws IOException {
+ ResponseApdu result = selectEduroam();
+ if (result.isSuccess()) {
+ Log.d(TAG, "reading eduroam");
+ CommandApdu c = new CommandApdu((byte)0x00, (byte)0xB0, (byte)0x00, (byte)0x00);
+ result = mSmartcardIO.runAPDU(c);
+ }
+ return result;
+ }
+
+ public boolean updateEduroam(byte[] data) throws IOException {
+ boolean result = false;
+ if (selectEduroam() != null) {
+ Log.d(TAG, "updating eduroam");
+ CommandApdu c = new CommandApdu((byte)0x00, (byte)0xD6, (byte)0x00, (byte)0x00, data);
+ result = mSmartcardIO.runAPDU(c) != null;
+ }
+ return result;
+ }
+
+ public static String readStringFromByteArray(byte[] barr, int offset) {
+ String result = "";
+ byte b;
+ int i = offset;
+ while ((b = barr[i++]) != (byte) 0xFF) {
+ result += (char) b;
+ }
+ return result;
+ }
+
+}
diff --git a/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/MainActivity.java b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/MainActivity.java
new file mode 100644
index 0000000..23b7ae5
--- /dev/null
+++ b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/MainActivity.java
@@ -0,0 +1,241 @@
+package com.gieseckedevrient.android.hellosmartcard;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import org.simalliance.openmobileapi.SEService;
+import nl.mansoft.openmobileapi.util.CommandApdu;
+import nl.mansoft.openmobileapi.util.ResponseApdu;
+
+import java.io.IOException;
+
+
+public class MainActivity extends Activity implements SEService.CallBack {
+ final String TAG = MainActivity.class.getSimpleName();
+ public final static byte[] AID_HELLOAPPLET = {
+ (byte) 0xD2, 0x76, 0x00, 0x01, 0x18, 0x00, 0x02,
+ (byte) 0xFF, 0x49, 0x50, 0x25, (byte) 0x89,
+ (byte) 0xC0, 0x01, (byte) 0x9B, 0x01
+ };
+ public final static byte[] AID_JOOSTAPPLET = { (byte) 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x01 };
+ public final static byte[] AID_3GPP = { (byte) 0xA0, 0x00, 0x00, 0x00, (byte) 0x87 };
+ public final static byte[] AID_ISOAPPLET = { (byte) 0xF2, (byte) 0x76, (byte) 0xA2, (byte) 0x88, (byte) 0xBC, (byte) 0xFB, (byte) 0xA6, (byte) 0x9D, (byte) 0x34, (byte) 0xF3, (byte) 0x10, (byte) 0x01 };
+
+ private Button hello;
+ private Button joost;
+ private Button eduroam;
+ private Button telecom;
+
+ private SmartcardIO mSmartcardIO;
+
+ void loge(String message) {
+ Log.e(TAG, message);
+ Toast.makeText(this, message, Toast.LENGTH_LONG).show();
+ }
+
+ private void logd(String message) {
+ Log.d(TAG, message);
+ Toast.makeText(this, message, Toast.LENGTH_LONG).show();
+ }
+
+ interface ResponseCallback {
+ void responseCallback(byte response[]);
+ }
+
+ class ShowText implements ResponseCallback {
+ @Override
+ public void responseCallback(byte response[]) {
+ String hello = new String(response);
+ logd(hello);
+ }
+ }
+
+ private class MyOnClickListener implements OnClickListener {
+ final String LOG_TAG = MyOnClickListener.class.getSimpleName();
+ private byte[] mAid;
+ private CommandApdu mCommandApdu[];
+ private SmartcardIO mSmartcardIO;
+ private ResponseCallback mResponseCallback;
+
+
+ public MyOnClickListener(SmartcardIO smartcardIO, byte[] aid, CommandApdu commandApdu[], ResponseCallback responseCallback) throws IOException {
+ mSmartcardIO = smartcardIO;
+ mAid = aid;
+ mCommandApdu = commandApdu;
+ mResponseCallback = responseCallback;
+ }
+
+ @Override
+ public void onClick(View view) {
+ try {
+ mSmartcardIO.openChannel(mAid);
+ int length = mCommandApdu.length;
+ for (int i = 0; i < length; i++) {
+ CommandApdu commandApdu = mCommandApdu[i];
+ ResponseApdu response = mSmartcardIO.runAPDU(commandApdu);
+ if (response.isSuccess()) {
+ byte data[] = response.getData();
+ Log.d(LOG_TAG, "response: " + SmartcardIO.hex(data));
+ if (mResponseCallback != null) {
+ mResponseCallback.responseCallback(data);
+ }
+ } else {
+ Log.d(LOG_TAG, "response: null");
+ }
+ }
+ } catch (Exception e) {
+ loge(e.getMessage());
+ }
+ }
+ }
+
+ private class EduroamOnClickListener implements OnClickListener {
+ final String TAG = EduroamOnClickListener.class.getSimpleName();
+ private SmartcardIO mSmartcardIO;
+
+ public EduroamOnClickListener(SmartcardIO smartcardIO) throws IOException {
+ mSmartcardIO = smartcardIO;
+ }
+
+ public void showEduroam() throws Exception {
+ mSmartcardIO.openChannel(AID_ISOAPPLET);
+ Eduroam eduroam = new Eduroam(mSmartcardIO);
+ ResponseApdu responseApdu = eduroam.readEduroam();
+ if (responseApdu.isSuccess()) {
+ byte data[] = responseApdu.getData();
+ logd("user: " + Eduroam.readStringFromByteArray(data, Eduroam.OFFSET_USER));
+ logd("password: " + Eduroam.readStringFromByteArray(data, Eduroam.OFFSET_PASSWORD));
+ } else {
+ loge("No credentials found on SIM card");
+ }
+ mSmartcardIO.closeChannel();
+ }
+
+ @Override
+ public void onClick(View view) {
+ try {
+ showEduroam();
+ } catch (Exception e) {
+ loge(e.getMessage());
+ }
+ }
+ }
+
+ private class TelecomOnClickListener implements OnClickListener {
+ final String TAG = EduroamOnClickListener.class.getSimpleName();
+ private SmartcardIO mSmartcardIO;
+
+ public TelecomOnClickListener(SmartcardIO smartcardIO) throws IOException {
+ mSmartcardIO = smartcardIO;
+ }
+
+ public void showTelecom() throws Exception {
+ mSmartcardIO.openChannel(AID_3GPP);
+ // select EXT1
+ Telecom telecom = new Telecom(mSmartcardIO);
+ logd("user: " + telecom.readData(Telecom.EF_EXT1, Telecom.RECORD_USER));
+ logd("password: " + telecom.readData(Telecom.EF_EXT1, Telecom.RECORD_PASSWORD));
+ mSmartcardIO.closeChannel();
+ }
+
+ @Override
+ public void onClick(View view) {
+ try {
+ showTelecom();
+ } catch (Exception e) {
+ loge(e.getMessage());
+ }
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+
+ try {
+ super.onCreate(savedInstanceState);
+
+ mSmartcardIO = new SmartcardIO();
+ mSmartcardIO.setup(this, this);
+ LinearLayout layout = new LinearLayout(this);
+ layout.setLayoutParams(new LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+
+ hello = new Button(this);
+ hello.setLayoutParams(new LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+
+ hello.setEnabled(false);
+ hello.setText("Click Me");
+ hello.setOnClickListener(new MyOnClickListener(
+ mSmartcardIO,
+ AID_HELLOAPPLET,
+ new CommandApdu[] { new CommandApdu((byte) 0x90, (byte) 0x10, (byte) 0x00, (byte) 0x00,(byte) 0x00 ) },
+ new ShowText()
+ ));
+ layout.addView(hello);
+ joost = new Button(this);
+ joost.setLayoutParams(new LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+ joost.setEnabled(false);
+ joost.setText("Joost");
+ joost.setOnClickListener(new MyOnClickListener(
+ mSmartcardIO,
+ AID_JOOSTAPPLET,
+ new CommandApdu[] { new CommandApdu((byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00,(byte) 0x04 ) },
+ new ShowText()
+ ));
+ layout.addView(joost);
+ eduroam = new Button(this);
+ eduroam.setLayoutParams(new LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+
+ eduroam.setEnabled(false);
+ eduroam.setText("eduroam");
+ eduroam.setOnClickListener(new EduroamOnClickListener(mSmartcardIO));
+ layout.addView(eduroam);
+ telecom = new Button(this);
+ telecom.setLayoutParams(new LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+
+ telecom.setEnabled(false);
+ telecom.setText("telecom");
+ telecom.setOnClickListener(new TelecomOnClickListener(mSmartcardIO));
+ layout.addView(telecom);
+ setContentView(layout);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ mSmartcardIO.teardown();
+ super.onDestroy();
+ }
+
+ @Override
+ public void serviceConnected(SEService seService) {
+ try {
+ mSmartcardIO.setSession();
+ hello.setEnabled(true);
+ joost.setEnabled(true);
+ eduroam.setEnabled(true);
+ telecom.setEnabled(true);
+ } catch (IOException e) {
+ loge(e.getMessage());
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/SmartcardIO.java b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/SmartcardIO.java
new file mode 100644
index 0000000..d8775c9
--- /dev/null
+++ b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/SmartcardIO.java
@@ -0,0 +1,99 @@
+package com.gieseckedevrient.android.hellosmartcard;
+
+import android.content.Context;
+import android.util.Log;
+import android.widget.Toast;
+
+import org.simalliance.openmobileapi.Channel;
+import org.simalliance.openmobileapi.Reader;
+import org.simalliance.openmobileapi.SEService;
+import org.simalliance.openmobileapi.Session;
+import nl.mansoft.openmobileapi.util.CommandApdu;
+import nl.mansoft.openmobileapi.util.ResponseApdu;
+
+import java.io.IOException;
+
+public class SmartcardIO {
+ private final String TAG = SmartcardIO.class.getSimpleName();
+ private Session session;
+ private Channel cardChannel;
+ private SEService mSeService;
+ private Context mContext;
+
+ private void loge(String message) {
+ Log.e(TAG, message);
+ Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
+ }
+
+ private void logd(String message) {
+ Log.d(TAG, message);
+ Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
+ }
+
+ public ResponseApdu runAPDU(CommandApdu commandApdu) throws IOException {
+ byte cmdApdu[] = commandApdu.toByteArray();
+ ResponseApdu responseApdu = new ResponseApdu(cardChannel.transmit(cmdApdu));
+
+ if (!responseApdu.isSuccess()) {
+ Log.e(TAG,"ERROR: status: " + String.format("%04X", responseApdu.getSwValue()));
+ }
+ return responseApdu;
+ }
+
+ public void setup(Context context, SEService.CallBack callBack) throws IOException {
+ mContext = context;
+ mSeService = new SEService(context, callBack);
+ }
+
+ public void teardown() {
+ Reader[] readers = mSeService.getReaders();
+ closeChannel();
+ if (readers.length < 1) {
+ Log.e(TAG, "No readers found");
+ } else {
+ readers[0].closeSessions();
+ }
+ if (mSeService != null && mSeService.isConnected()) {
+ mSeService.shutdown();
+ }
+ }
+
+ public void closeChannel() {
+ if (cardChannel != null && !cardChannel.isClosed()) {
+ cardChannel.close();
+ }
+ }
+ public void openChannel(byte aid[]) throws Exception {
+ closeChannel();
+ cardChannel = session.openLogicalChannel(aid);
+ }
+
+ public static String hex2(int hex) {
+ return String.format("%02X", hex & 0xff);
+ }
+
+ public static String hex(byte[] barr) {
+ String result;
+ if (barr == null) {
+ result = "null";
+ } else {
+ result = "";
+ for (byte b : barr) {
+ result += " " + hex2(b);
+ }
+ }
+ return result;
+ }
+
+ public void setSession() throws IOException {
+ Log.d(TAG, "serviceConnected()");
+ Log.d(TAG, "Retrieve available readers...");
+ Reader[] readers = mSeService.getReaders();
+ if (readers.length < 1) {
+ loge("No readers found");
+ } else {
+ Log.d(TAG, "Create Session from the first reader...");
+ session = readers[0].openSession();
+ }
+ }
+}
diff --git a/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/Telecom.java b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/Telecom.java
new file mode 100644
index 0000000..b64bf2a
--- /dev/null
+++ b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/Telecom.java
@@ -0,0 +1,93 @@
+package com.gieseckedevrient.android.hellosmartcard;
+
+import android.util.Log;
+
+import nl.mansoft.openmobileapi.util.CommandApdu;
+import nl.mansoft.openmobileapi.util.ResponseApdu;
+
+import java.io.IOException;
+
+public class Telecom {
+ private final String TAG = Telecom.class.getSimpleName();
+ private SmartcardIO mSmartcardIO;
+ public final static int EXT_RECORD_SIZE = 13;
+ public final static int EF_EXT1 = 0x6F4A;
+ public final static int EF_EXT2 = 0x6F4B;
+ public final static int RECORD_USER = 1;
+ public final static int RECORD_PASSWORD = 3;
+
+ public Telecom(SmartcardIO smartcardIO) {
+ mSmartcardIO = smartcardIO;
+ }
+
+ public ResponseApdu readRecord(int record) throws IOException {
+ CommandApdu c = new CommandApdu((byte)0x00, (byte)0xB2, (byte)record, (byte)0x04);
+ return mSmartcardIO.runAPDU(c);
+ }
+
+ public void readRecords() throws IOException {
+ int record = 1;
+ ResponseApdu responseApdu;
+ while ((responseApdu = readRecord(record++)).isSuccess()) {
+ Log.d(TAG, SmartcardIO.hex(responseApdu.getData()));
+ }
+ }
+ public static byte hi(int x) {
+ return (byte) (x >> 8);
+ }
+
+ public static byte lo(int x) {
+ return (byte) (x & 0xff);
+ }
+
+ public ResponseApdu selectTelecom(int fid) throws IOException {
+ CommandApdu c = new CommandApdu((byte)0x00, (byte)0xA4, (byte)0x08, (byte)0x04, new byte[] { 0x7f, 0x10, hi(fid), lo(fid) });
+ return mSmartcardIO.runAPDU(c);
+ }
+
+ public byte[] readTelecomRecord(int fid, int record) throws IOException {
+ byte result[] = null;
+ if (selectTelecom(fid) != null) {
+ Log.d(TAG, String.format("reading telecom %04X", fid));
+ ResponseApdu responseApdu = readRecord(record);
+ if (responseApdu.isSuccess()) {
+ result = responseApdu.getData();
+ }
+ }
+ return result;
+ }
+
+ public void readTelecomRecords(int fid) throws IOException {
+ if (selectTelecom(fid) != null) {
+ Log.d(TAG, String.format("reading telecom %04X", fid));
+ readRecords();
+ }
+ }
+ public void updateRecord(int record, byte[] data) throws IOException {
+ CommandApdu c = new CommandApdu((byte)0x00, (byte)0xdc, (byte)record, (byte)0x04, data);
+ mSmartcardIO.runAPDU(c);
+ }
+
+ public void writeTelecom(int fid, int record, byte[] data) throws IOException {
+ if (selectTelecom(fid) != null) {
+ Log.d(TAG, String.format("write telecom %04X, record %d", fid, record));
+ updateRecord(record, data);
+ }
+ }
+
+ public String readData(int ext, int recordnr) throws IOException {
+ String result = "";
+ byte record[];
+ while ((record = readTelecomRecord(ext, recordnr++)) != null) {
+ for (int i = 1; i < EXT_RECORD_SIZE; i++) {
+ int val = (record[i] & 0xFF);
+ if (val == 0xFF) {
+ return result;
+ }
+ result += String.valueOf((char) val);
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/app/src/main/java/nl/mansoft/openmobileapi/util/CommandApdu.java b/app/src/main/java/nl/mansoft/openmobileapi/util/CommandApdu.java
new file mode 100644
index 0000000..b735900
--- /dev/null
+++ b/app/src/main/java/nl/mansoft/openmobileapi/util/CommandApdu.java
@@ -0,0 +1,476 @@
+/*
+ * Copyright 2013 Giesecke & Devrient GmbH.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package nl.mansoft.openmobileapi.util;
+
+/**
+ * This object represents a command APDU as specified by ISO/IEC 7816.
+ *
+ * @author Giesecke & Devrient
+ *
+ */
+public class CommandApdu {
+
+ /**
+ * CLA field of command APDU.
+ */
+ private byte mCla;
+
+ /**
+ * INS field of command APDU.
+ */
+ private byte mIns;
+
+ /**
+ * P1 field of command APDU.
+ */
+ private byte mP1;
+
+ /**
+ * P2 field of command APDU.
+ */
+ private byte mP2;
+
+ /**
+ * DATA field of command APDU.
+ */
+ private byte[] mData;
+
+ /**
+ * Le field of command APDU.
+ */
+ private Integer mLe;
+
+ /**
+ * Override default constructor.
+ */
+ private CommandApdu() {
+ }
+
+ /**
+ * Creates a case 1 Command APDU.
+ *
+ * @param cla Value of CLA field.
+ * @param ins Value of INS field.
+ * @param p1 Value of P1 field.
+ * @param p2 Value of P2 field.
+ *
+ * @throws IllegalArgumentException if CLA or INS are invalid.
+ */
+ public CommandApdu(byte cla, byte ins, byte p1, byte p2)
+ throws IllegalArgumentException {
+ setCla(cla);
+ setIns(ins);
+ setP1(p1);
+ setP2(p2);
+ }
+
+ /**
+ * Creates a case 2 Command APDU.
+ *
+ * @param cla Value of CLA field.
+ * @param ins Value of INS field.
+ * @param p1 Value of P1 field.
+ * @param p2 Value of P2 field.
+ * @param le Value of Le field.
+ *
+ * @throws IllegalArgumentException if CLA, INS or Le are invalid.
+ */
+ public CommandApdu(byte cla, byte ins, byte p1, byte p2, int le)
+ throws IllegalArgumentException {
+ setCla(cla);
+ setIns(ins);
+ setP1(p1);
+ setP2(p2);
+ setLe(le);
+ }
+
+ /**
+ * Creates case 3 Command APDU.
+ *
+ * @param cla Value of CLA field.
+ * @param ins Value of INS field.
+ * @param p1 Value of P1 field.
+ * @param p2 Value of P2 field.
+ * @param data Value of DATA field.
+ *
+ * @throws IllegalArgumentException if CLA, INS or Data are invalid.
+ */
+ public CommandApdu(byte cla, byte ins, byte p1, byte p2, byte[] data)
+ throws IllegalArgumentException {
+ setCla(cla);
+ setIns(ins);
+ setP1(p1);
+ setP2(p2);
+ setData(data);
+ }
+
+ /**
+ * Creates a case 4 Command APDU.
+ *
+ * @param cla Value of CLA field.
+ * @param ins Value of INS field.
+ * @param p1 Value of P1 field.
+ * @param p2 Value of P2 field.
+ * @param data Value of DATA field.
+ * @param le Value of Le field.
+ *
+ * @throws IllegalArgumentException if CLA, INS, Data or Le are invalid.
+ */
+ public CommandApdu(
+ byte cla, byte ins, byte p1, byte p2, byte[] data, int le)
+ throws IllegalArgumentException {
+ setCla(cla);
+ setIns(ins);
+ setP1(p1);
+ setP2(p2);
+ setData(data);
+ setLe(le);
+ }
+
+ /**
+ * Creates a command APDU object from a byte Array.
+ *
+ * @param cmdApduAsByteArray Command APDU as byte array.
+ *
+ * @throws IllegalArgumentException If cmdApduAsByteArray does not
+ * contain a valid APDU.
+ */
+ public CommandApdu(byte[] cmdApduAsByteArray)
+ throws IllegalArgumentException {
+ if (cmdApduAsByteArray.length < ISO7816.CMD_APDU_LENGTH_CASE1) {
+ throw new IllegalArgumentException("Invalid length for command ("
+ + cmdApduAsByteArray.length + ").");
+ }
+
+ setCla(cmdApduAsByteArray[ISO7816.OFFSET_CLA]);
+ setIns(cmdApduAsByteArray[ISO7816.OFFSET_INS]);
+ setP1(cmdApduAsByteArray[ISO7816.OFFSET_P1]);
+ setP2(cmdApduAsByteArray[ISO7816.OFFSET_P2]);
+
+ if (cmdApduAsByteArray.length == ISO7816.CMD_APDU_LENGTH_CASE1) {
+ // Case 1 APDU -- Nothing left to be done
+ } else if (cmdApduAsByteArray.length == ISO7816.CMD_APDU_LENGTH_CASE2) {
+ // Case 2 APDU
+ setLe((int) 0x0FF & cmdApduAsByteArray[ISO7816.OFFSET_P3]);
+ } else if (cmdApduAsByteArray[ISO7816.OFFSET_P3] != (byte) 0x00) {
+ // Case 3 or Case 4 APDU
+
+ // Get Lc and check that it's not 0
+ int lc = ((int) 0x0FF & cmdApduAsByteArray[ISO7816.OFFSET_P3]);
+ if (lc == 0) {
+ throw new IllegalArgumentException(
+ "Lc can't be 0");
+ }
+
+ if (cmdApduAsByteArray.length
+ == ISO7816.CMD_APDU_LENGTH_CASE3_WITHOUT_DATA + lc) {
+ // Case 3 APDU -- nothing to be done here
+ } else if (cmdApduAsByteArray.length
+ == ISO7816.CMD_APDU_LENGTH_CASE4_WITHOUT_DATA + lc) {
+ // Case 4 APDU -- get Le:
+ setLe((int) 0x0FF
+ & cmdApduAsByteArray[cmdApduAsByteArray.length - 1]);
+ } else {
+ // Lc has a wrong value!
+ throw new IllegalArgumentException(
+ "Unexpected value of Lc (" + lc + ")");
+ }
+
+ // Store the data
+ mData = new byte[lc];
+ System.arraycopy(
+ cmdApduAsByteArray,
+ ISO7816.OFFSET_DATA,
+ mData,
+ 0,
+ lc);
+ } else if (cmdApduAsByteArray.length
+ == ISO7816.CMD_APDU_LENGTH_CASE2_EXTENDED) {
+ // Case 2 extended APDU
+ setLe((((int) 0x0FF
+ & cmdApduAsByteArray[ISO7816.OFFSET_DATA]) << 8)
+ + ((int) 0x0FF
+ & cmdApduAsByteArray[ISO7816.OFFSET_DATA + 1]));
+ } else {
+ // Case 3 or Case 4 APDU
+
+ if (cmdApduAsByteArray.length <= ISO7816.OFFSET_DATA_EXTENDED) {
+ throw new IllegalArgumentException(
+ "Unexpected value of Lc or Le" + cmdApduAsByteArray.length);
+ }
+ // Get Lc and check that it's not 0
+ int lc = (((int) 0x0FF
+ & cmdApduAsByteArray[ISO7816.OFFSET_DATA]) << 8)
+ + ((int) 0x0FF
+ & cmdApduAsByteArray[ISO7816.OFFSET_DATA + 1]);
+ if (lc == 0) {
+ throw new IllegalArgumentException(
+ "Lc can't be 0");
+ }
+
+ if (cmdApduAsByteArray.length
+ == ISO7816.CMD_APDU_LENGTH_CASE3_WITHOUT_DATA_EXTENDED
+ + lc) {
+ // Case 3 APDU -- nothing to be done here
+ } else if (cmdApduAsByteArray.length
+ == ISO7816.CMD_APDU_LENGTH_CASE4_WITHOUT_DATA_EXTENDED
+ + lc) {
+ // Case 4 APDU -- get Le:
+ setLe((((int) 0x0FF
+ & cmdApduAsByteArray[cmdApduAsByteArray.length - 2])
+ << 8)
+ + ((int) 0x0FF
+ & cmdApduAsByteArray[cmdApduAsByteArray.length - 1]));
+ } else {
+ // Lc has a wrong value!
+ throw new IllegalArgumentException(
+ "Unexpected value of Lc (" + lc + ")--- 9 -" + cmdApduAsByteArray.length);
+ }
+
+ // Store the data
+ mData = new byte[lc];
+ System.arraycopy(
+ cmdApduAsByteArray,
+ ISO7816.OFFSET_DATA_EXTENDED,
+ mData,
+ 0,
+ lc);
+ }
+ }
+
+ /**
+ * Returns this Command APDU as a byte array.
+ *
+ * @return Command APDU as byte array.
+ */
+ public byte[] toByteArray() {
+ byte[] array;
+ if (!isExtendedLength()) {
+ if (mData == null && mLe == null) {
+ // APDU Case 1
+ array = new byte[ISO7816.CMD_APDU_LENGTH_CASE1];
+ array[ISO7816.OFFSET_CLA] = mCla;
+ array[ISO7816.OFFSET_INS] = mIns;
+ array[ISO7816.OFFSET_P1] = mP1;
+ array[ISO7816.OFFSET_P2] = mP2;
+ } else if (mData == null && mLe != null) {
+ // APDU Case 2
+ array = new byte[ISO7816.CMD_APDU_LENGTH_CASE2];
+ array[ISO7816.OFFSET_CLA] = mCla;
+ array[ISO7816.OFFSET_INS] = mIns;
+ array[ISO7816.OFFSET_P1] = mP1;
+ array[ISO7816.OFFSET_P2] = mP2;
+ array[ISO7816.OFFSET_P3] = (byte) (mLe & 0x0FF);
+ } else if (mData != null && mLe == null) {
+ // APDU Case 3
+ array = new byte[ISO7816.CMD_APDU_LENGTH_CASE3_WITHOUT_DATA
+ + mData.length];
+ array[ISO7816.OFFSET_CLA] = mCla;
+ array[ISO7816.OFFSET_INS] = mIns;
+ array[ISO7816.OFFSET_P1] = mP1;
+ array[ISO7816.OFFSET_P2] = mP2;
+ array[ISO7816.OFFSET_P3] = (byte) (mData.length & 0x0FF);
+ System.arraycopy(
+ mData, 0, array, ISO7816.OFFSET_DATA, mData.length);
+ } else {
+ // APDU Case 4
+ array = new byte[ISO7816.CMD_APDU_LENGTH_CASE4_WITHOUT_DATA
+ + mData.length];
+ array[ISO7816.OFFSET_CLA] = mCla;
+ array[ISO7816.OFFSET_INS] = mIns;
+ array[ISO7816.OFFSET_P1] = mP1;
+ array[ISO7816.OFFSET_P2] = mP2;
+ array[ISO7816.OFFSET_P3] = (byte) (mData.length & 0x0FF);
+ System.arraycopy(
+ mData, 0, array, ISO7816.OFFSET_DATA, mData.length);
+ array[array.length - 1] = (byte) (mLe & 0x0FF);
+ }
+
+ } else {
+ if (mData == null && mLe != null) {
+ // APDU Case 2
+ array = new byte[ISO7816.CMD_APDU_LENGTH_CASE2_EXTENDED];
+ array[ISO7816.OFFSET_CLA] = mCla;
+ array[ISO7816.OFFSET_INS] = mIns;
+ array[ISO7816.OFFSET_P1] = mP1;
+ array[ISO7816.OFFSET_P2] = mP2;
+ array[ISO7816.OFFSET_P3] = (byte) 0x00;
+ array[ISO7816.OFFSET_DATA] = (byte) ((mLe >> 8) & 0x0FF);
+ array[ISO7816.OFFSET_DATA + 1] = (byte) (mLe & 0x0FF);
+ } else if (mData != null && mLe == null) {
+ // APDU Case 3
+ array = new byte[ISO7816.
+ CMD_APDU_LENGTH_CASE3_WITHOUT_DATA_EXTENDED
+ + mData.length];
+ array[ISO7816.OFFSET_CLA] = mCla;
+ array[ISO7816.OFFSET_INS] = mIns;
+ array[ISO7816.OFFSET_P1] = mP1;
+ array[ISO7816.OFFSET_P2] = mP2;
+ array[ISO7816.OFFSET_P3] = (byte) 0x00;
+ array[ISO7816.OFFSET_DATA] = (byte) ((mData.length >> 8)
+ & 0x0FF);
+ array[ISO7816.OFFSET_DATA + 1] = (byte) (mData.length & 0x0FF);
+ System.arraycopy(
+ mData,
+ 0,
+ array,
+ ISO7816.OFFSET_DATA_EXTENDED,
+ mData.length);
+ } else {
+ // APDU Case 4
+ array = new byte[ISO7816.
+ CMD_APDU_LENGTH_CASE4_WITHOUT_DATA_EXTENDED
+ + mData.length];
+ array[ISO7816.OFFSET_CLA] = mCla;
+ array[ISO7816.OFFSET_INS] = mIns;
+ array[ISO7816.OFFSET_P1] = mP1;
+ array[ISO7816.OFFSET_P2] = mP2;
+ array[ISO7816.OFFSET_P3] = (byte) 0x00;
+ array[ISO7816.OFFSET_DATA] = (byte) ((mData.length >> 8)
+ & 0x0FF);
+ array[ISO7816.OFFSET_DATA + 1] = (byte) (mData.length & 0x0FF);
+ System.arraycopy(
+ mData,
+ 0,
+ array,
+ ISO7816.OFFSET_DATA_EXTENDED,
+ mData.length);
+ array[array.length - 2] = (byte) ((mLe >> 8) & 0x0FF);
+ array[array.length - 1] = (byte) (mLe & 0x0FF);
+ }
+ }
+ return array;
+ }
+
+ public CommandApdu cloneWithLe(int le) {
+ if (mData == null) {
+ // Original APDU was Case 1 or Case 2
+ return new CommandApdu(mCla, mIns, mP1, mP2, (byte) le);
+ } else {
+ // Original APDU was case 3 or 4
+ return new CommandApdu(mCla, mIns, mP1, mP2, mData, (byte) le);
+ }
+ }
+
+ /**
+ * Private method - Set CLA byte and check if it is a valid value or not.
+ *
+ * @param cla Value of CLA field.
+ *
+ * @throws IllegalArgumentException if CLA is invalid.
+ */
+ private void setCla(byte cla) throws IllegalArgumentException {
+ if (cla == (byte) 0xFF) {
+ // cla has a wrong value!
+ throw new IllegalArgumentException(
+ "Invalid value of CLA (" + Integer.toHexString(cla) + ")");
+ }
+ mCla = cla;
+ }
+
+ /**
+ * Private method - Set INS byte and check if it is a valid value or not.
+ *
+ * @param ins Value of INS field.
+ *
+ * @throws IllegalArgumentException if INS is invalid.
+ */
+ private void setIns(byte ins) throws IllegalArgumentException {
+ if ((ins & 0x0F0) == 0x60 || ((ins & 0x0F0) == 0x90)) {
+ // ins has a wrong value!
+ throw new IllegalArgumentException(
+ "Invalid value of INS (" + Integer.toHexString(ins) + "). "
+ + "0x6X and 0x9X are not valid values");
+ }
+ mIns = ins;
+ }
+
+ /**
+ * Private method - Set p1 byte.
+ *
+ * @param p1 Value of P1 field.
+ */
+ private void setP1(byte p1) {
+ mP1 = p1;
+ }
+
+ /**
+ * Private method - Set p2 byte.
+ *
+ * @param p2 Value of P2 field.
+ */
+ private void setP2(byte p2) {
+ mP2 = p2;
+ }
+
+ /**
+ * Private method Set Data.
+ *
+ * @param data Value of APDU data.
+ * @throws IllegalArgumentException if Data is null, 0 or is too long.
+ */
+ private void setData(byte[] data) throws IllegalArgumentException {
+ if (data == null) {
+ throw new IllegalArgumentException(
+ "Data must not be null.");
+ }
+
+ if (data.length > ISO7816.MAX_COMMAND_DATA_LENGTH) {
+ throw new IllegalArgumentException(
+ "Data too long.");
+ }
+
+ if (data.length == 0) {
+ throw new IllegalArgumentException(
+ "Data must not be empty.");
+ }
+
+ mData = new byte[data.length];
+ System.arraycopy(data, 0, mData, 0, data.length);
+ }
+
+ /**
+ * Private method - Set LE byte.
+ *
+ * @param le Value of LE field.
+ *
+ * @throws IllegalArgumentException if Le is invalid.
+ */
+ private void setLe(int le) throws IllegalArgumentException {
+
+ if (le < 0 || le > ISO7816.MAX_RESPONSE_DATA_LENGTH) {
+ throw new IllegalArgumentException(
+ "Invalid value for le parameter (" + le + ").");
+ }
+ mLe = le;
+ }
+
+ /**
+ * Check if the Apdu is extended.
+ *
+ * @return true if Apdu is extended or false otherwise.
+ */
+ public boolean isExtendedLength() {
+ if ((mLe != null && mLe > ISO7816.MAX_RESPONSE_DATA_LENGTH_NO_EXTENDED)
+ || (mData != null
+ && mData.length
+ > ISO7816.MAX_COMMAND_DATA_LENGTH_NO_EXTENDED)) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/app/src/main/java/nl/mansoft/openmobileapi/util/ISO7816.java b/app/src/main/java/nl/mansoft/openmobileapi/util/ISO7816.java
new file mode 100644
index 0000000..6fe2f0e
--- /dev/null
+++ b/app/src/main/java/nl/mansoft/openmobileapi/util/ISO7816.java
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+
+package nl.mansoft.openmobileapi.util;
+
+/**
+ * This object represents ISO/IEC 7816.
+ *
+ * @author Giesecke & Devrient
+ *
+ */
+public class ISO7816 {
+ /**
+ * Maximum command APDU data length.
+ */
+ public static final int MAX_COMMAND_DATA_LENGTH = 65535;
+
+ /**
+ * Maximum response APDU data length.
+ */
+ public static final int MAX_RESPONSE_DATA_LENGTH = 65536;
+
+ /**
+ * Maximum command APDU data length.
+ */
+ public static final int MAX_COMMAND_DATA_LENGTH_NO_EXTENDED = 255;
+
+ /**
+ * Maximum response APDU data length.
+ */
+ public static final int MAX_RESPONSE_DATA_LENGTH_NO_EXTENDED = 256;
+
+ /**
+ * Length of an APDU Case 1.
+ */
+ public static final int CMD_APDU_LENGTH_CASE1 = 4;
+
+ /**
+ * Length of an APDU Case 2.
+ */
+ public static final int CMD_APDU_LENGTH_CASE2 = 5;
+
+ /**
+ * Length of an APDU Case 2 extended case.
+ */
+ public static final int CMD_APDU_LENGTH_CASE2_EXTENDED = 7;
+
+ /**
+ * Length of an APDU Case 3 without taking data into account.
+ */
+ public static final int CMD_APDU_LENGTH_CASE3_WITHOUT_DATA = 5;
+
+ /**
+ * Length of an APDU Case 3 extended without taking data into account.
+ */
+ public static final int CMD_APDU_LENGTH_CASE3_WITHOUT_DATA_EXTENDED = 7;
+
+ /**
+ * Length of an APDU Case 4 without taking data into account.
+ */
+ public static final int CMD_APDU_LENGTH_CASE4_WITHOUT_DATA = 6;
+
+ /**
+ * Length of an APDU Case 4 extended without taking data into account.
+ */
+ public static final int CMD_APDU_LENGTH_CASE4_WITHOUT_DATA_EXTENDED = 9;
+
+ /**
+ * Length of an status word.
+ */
+ public static final int RESP_APDU_LENGTH_SW = 2;
+
+ // Offsets in Command APDU.
+
+ /**
+ * CLA offset in Command APDU.
+ */
+ public static final int OFFSET_CLA = 0;
+ /**
+ * INS offset in Command APDU.
+ */
+ public static final int OFFSET_INS = 1;
+ /**
+ * P1 offset in Command APDU.
+ */
+ public static final int OFFSET_P1 = 2;
+ /**
+ * P2 offset in Command APDU.
+ */
+ public static final int OFFSET_P2 = 3;
+ /**
+ * P3 offset in Command APDU.
+ */
+ public static final int OFFSET_P3 = 4;
+ /**
+ * Data offset in Command APDU.
+ */
+ public static final int OFFSET_DATA = 5;
+
+ /**
+ * Data offset in Command APDU.
+ */
+ public static final int OFFSET_DATA_EXTENDED = 7;
+
+ // CLA values.
+
+ /**
+ * Interindustry class value.
+ */
+ public static final byte CLA_INTERINDUSTRY = (byte) 0x00;
+ /**
+ * Proprietary class value.
+ */
+ public static final byte CLA_PROPRIETARY = (byte) 0x80;
+
+ // Ins values.
+
+ /**
+ * ISO EXTERNAL AUTHENTICATE DATA instruction value.
+ */
+ public static final byte INS_EXTERNAL_AUTHENTICATE = (byte) 0x82;
+ /**
+ * ISO APPEND RECORD instruction value.
+ */
+ public static final byte INS_APPEND_RECORD = (byte) 0xE2;
+ /**
+ * ISO CHANGE REFERENCE DATA instruction value.
+ */
+ public static final byte INS_CHANGE_REF_DATA = (byte) 0x24;
+ /**
+ * ISO ENABLE VERIFICAITION REQUIREMENT instruction value.
+ */
+ public static final byte INS_ENABLE_VERIF_REQ = (byte) 0x28;
+ /**
+ * ISO ENVELOPE instruction value (0xC3).
+ */
+ public static final byte INS_ENVELOPE_C3 = (byte) 0xC3;
+ /**
+ * ISO ENVELOPE instruction value (0xC2).
+ */
+ public static final byte INS_ENVELOPE_C2 = (byte) 0xC2;
+ /**
+ * ISO ERASE BINARY instruction value (0x0E).
+ */
+ public static final byte INS_ERASE_BINARY_0E = (byte) 0x0E;
+ /**
+ * ISO ERASE BINARY instruction value (0x0F).
+ */
+ public static final byte INS_ERASE_BINARY_0F = (byte) 0x0F;
+ /**
+ * ISO ERASE RECORD instruction value.
+ */
+ public static final byte INS_ERASE_RECORD = (byte) 0x0C;
+ /**
+ * ISO GENERAL AUTHENTICATE instruction value (0x86).
+ */
+ public static final byte INS_GENERAL_AUTHENTICATE_86 = (byte) 0x86;
+ /**
+ * ISO GENERAL AUTHENTICATE instruction value (0x87).
+ */
+ public static final byte INS_GENERAL_AUTHENTICATE_87 = (byte) 0x87;
+ /**
+ * ISO GENERATE ASYMMETRIC KEY PAIR instruction value.
+ */
+ public static final byte INS_GENERATE_ASYMMETRIC_KEY_PAIR = (byte) 0x46;
+ /**
+ * ISO GET CHALLENGE instruction value.
+ */
+ public static final byte INS_GET_CHALLENGE = (byte) 0x84;
+ /**
+ * ISO GET DATA instruction value (0xCA).
+ */
+ public static final byte INS_GET_DATA_CA = (byte) 0xCA;
+ /**
+ * ISO GET DATA instruction value (0xCB).
+ */
+ public static final byte INS_GET_DATA_CB = (byte) 0xCB;
+ /**
+ * ISO GET RESPONSE instruction value.
+ */
+ public static final byte INS_GET_RESPONSE = (byte) 0xC0;
+ /**
+ * ISO INTERNAL AUTHENTICATE instruction value.
+ */
+ public static final byte INS_INTERNAL_AUTHENTICATE = (byte) 0x88;
+ /**
+ * ISO MANAGE CHANNEL instruction value.
+ */
+ public static final byte INS_MANAGE_CHANNEL = (byte) 0x70;
+ /**
+ * ISO MANAGE SECURITY ENVIRONMENT instruction value.
+ */
+ public static final byte INS_MANAGE_SECURITY_ENVIRONMENT = (byte) 0x22;
+ /**
+ * ISO PUT DATA instruction value (0xDA).
+ */
+ public static final byte INS_PUT_DATA_DA = (byte) 0xDA;
+ /**
+ * ISO PUT DATA instruction value (0xDB).
+ */
+ public static final byte INS_PUT_DATA_DB = (byte) 0xDB;
+ /**
+ * ISO DISABLE VERIFICATION REQUIREMENT instruction value.
+ */
+ public static final byte INS_DISABLE_VERIF_REQ = (byte) 0x26;
+ /**
+ * ISO READ BINARY instruction value (0xB0).
+ */
+ public static final byte INS_READ_BINARY_B0 = (byte) 0xB0;
+ /**
+ * ISO READ BINARY instruction value (0xB1).
+ */
+ public static final byte INS_READ_BINARY_B1 = (byte) 0xB1;
+ /**
+ * ISO READ RECORD instruction value (0xB2).
+ */
+ public static final byte INS_READ_RECORD_B2 = (byte) 0xB2;
+ /**
+ * ISO READ RECORD instruction value (0xB3).
+ */
+ public static final byte INS_READ_RECORD_B3 = (byte) 0xB3;
+ /**
+ * ISO RESET RETRY COUNTER instruction value.
+ */
+ public static final byte INS_RESET_RETRY_CTR = (byte) 0x2C;
+ /**
+ * ISO SEARCH BINARY instruction value (0xA0).
+ */
+ public static final byte INS_SEARCH_BINARY_A0 = (byte) 0xA0;
+ /**
+ * ISO SEARCH BINARY instruction value (0xA1).
+ */
+ public static final byte INS_SEARCH_BINARY_A1 = (byte) 0xA1;
+ /**
+ * ISO SEARCH RECORD instruction value.
+ */
+ public static final byte INS_SEARCH_RECORD = (byte) 0xA2;
+ /**
+ * ISO SELECT instruction value.
+ */
+ public static final byte INS_SELECT = (byte) 0xA4;
+ /**
+ * ISO UPDATE BINARY instruction value (0xD6).
+ */
+ public static final byte INS_UPDATE_BINARY_D6 = (byte) 0xD6;
+ /**
+ * ISO UPDATE BINARY instruction value (0xD7).
+ */
+ public static final byte INS_UPDATE_BINARY_D7 = (byte) 0xD7;
+ /**
+ * ISO UPDATE RECORD instruction value (0xDC).
+ */
+ public static final byte INS_UPDATE_RECORD_DC = (byte) 0xDC;
+ /**
+ * ISO UPDATE RECORD instruction value (0xDD).
+ */
+ public static final byte INS_UPDATE_RECORD_DD = (byte) 0xDD;
+ /**
+ * ISO VERIFY instruction value (0x20).
+ */
+ public static final byte INS_VERIFY_20 = (byte) 0x20;
+ /**
+ * ISO VERIFY instruction value (0x21).
+ */
+ public static final byte INS_VERIFY_21 = (byte) 0x21;
+ /**
+ * ISO WRITE BINARY instruction value (0xD0).
+ */
+ public static final byte INS_WRITE_BINARY_D0 = (byte) 0xD0;
+ /**
+ * ISO WRITE BINARY instruction value (0xD1).
+ */
+ public static final byte INS_WRITE_BINARY_D1 = (byte) 0xD1;
+ /**
+ * ISO WRITE RECORD instruction value.
+ */
+ public static final byte INS_WRITE_RECORD = (byte) 0xD2;
+
+ // SW Values
+
+ // 9000 - Normal processing - No further qualification.
+ /**
+ * No further qualification.
+ */
+ public static final int SW_NO_FURTHER_QUALIFICATION = 0x9000;
+
+ // 61XX - Normal processing - SW2 encodes the number of data bytes still
+ // available.
+ /**
+ * SW2 encodes the number of data bytes still available (minimum value).
+ */
+ public static final int SW_NORMAL_PROCESSING_MIN = 0x6100;
+ /**
+ * SW2 encodes the number of data bytes still available (maximum value).
+ */
+ public static final int SW_NORMAL_PROCESSING_MAX = 0x61FF;
+
+ // 62XX - Warning processing - State of non-volatile memory is unchanged
+ // (further qualification in SW2).
+ /**
+ * Warning given.
+ */
+ public static final byte SW1_62 = (byte) 0x62;
+ /**
+ * No information given.
+ */
+ public static final int SW_62_NO_INFO = 0x6200;
+ /**
+ * Triggering by the card (minimum value).
+ */
+ public static final int SW_62_TRIGGERING_CARD_MIN = 0x6202;
+ /**
+ * Triggering by the card (maximum value).
+ */
+ public static final int SW_62_TRIGGERING_CARD_MAX = 0x6280;
+ /**
+ * Part of returned data may be corrupted.
+ */
+ public static final int SW_DATA_CORRUPTED = 0x6281;
+ /**
+ * End of file or record reached before reading Ne bytes.
+ */
+ public static final int SW_UNEXPECTED_EOF = 0x6282;
+ /**
+ * Selected file deactivated.
+ */
+ public static final int SW_FILE_DEACTIVATED = 0x6283;
+ /**
+ * File control information not formatted according to 5.3.3.
+ */
+ public static final int SW_WRONG_FILE_CONTROL_FORMAT = 0x6284;
+ /**
+ * Selected file in termination state.
+ */
+ public static final int SW_FILE_STATE_TERMINATION = 0x6285;
+ /**
+ * No input data available from a sensor on the card.
+ */
+ public static final int SW_NO_INPUT_DATA_AVAILABLE = 0x6286;
+
+ // 63XX - Warning processing - State of non-volatile memory has
+ // changed (further qualification in SW2).
+ /**
+ * Warning given.
+ */
+ public static final byte SW1_63 = (byte) 0x63;
+ /**
+ * No information given.
+ */
+ public static final int SW_63_NO_INFO = 0x6300;
+ /**
+ * File filled up by the last write.
+ */
+ public static final int SW_FILE_FILLED_UP = 0x6381;
+ /**
+ * Counter from 0 to 15 encoded by X (exact meaning depending on the
+ * command). Minimum value.
+ */
+ public static final int SW_CTR_MIN = 0x63C0;
+ /**
+ * Counter from 0 to 15 encoded by X (exact meaning depending on the
+ * command). Maximum value.
+ */
+ public static final int SW_CTR_MAX = 0x63CF;
+
+ // 64XX - Execution error - State of non-volatile memory is
+ // unchanged (further qualification in SW2)
+ /**
+ * Execution error.
+ */
+ public static final int SW_EXEC_ERROR = 0x6400;
+ /**
+ * Immediate response required by the card.
+ */
+ public static final int SW_IMMEDIATE_RESPONSE_REQUIRED = 0x6401;
+ /**
+ * Triggering by the card (minimum value).
+ */
+ public static final int SW_64_TRIGGERING_CARD_MIN = 0x6402;
+ /**
+ * Triggering by the card (maximum value).
+ */
+ public static final int SW_64_TRIGGERING_CARD_MAX = 0x6480;
+
+ // 65XX - Execution error - State of non-volatile memory has changed
+ // (further qualification in SW2)
+ /**
+ * No information given.
+ */
+ public static final int SW_65_NO_INFO = 0x6500;
+ /**
+ * Memory failure.
+ */
+ public static final int SW_MEMORY_FAILURE = 0x6581;
+
+ // 66XX - Execution error - Security-related issues
+ /**
+ * Security issue (minimum value).
+ */
+ public static final int SW_SECURITY_ISSUE_MIN = 0x6600;
+ /**
+ * Security issue (maximum value).
+ */
+ public static final int SW_SECURITY_ISSUE_MAX = 0x66FF;
+
+ // 6700 - Checking error - Wrong length, no further indication
+ /**
+ * Wrong length, no further indication.
+ */
+ public static final int SW_WRONG_LENGTH = 0x6700;
+
+ // 68XX - Checking error - Functions in CLA not supported
+ // (further qualification in SW2)
+ /**
+ * No information given.
+ */
+ public static final int SW_68_NO_INFO = 0x6800;
+ /**
+ * Logical channel not supported.
+ */
+ public static final int SW_LOGICAL_CHANNEL_NOT_SUPPORTED = 0x6881;
+ /**
+ * Secure messaging not supported.
+ */
+ public static final int SW_SECURE_MESSAGING_NOT_SUPPORTED = 0x6882;
+ /**
+ * Last command of the chain expected.
+ */
+ public static final int SW_LAST_COMMAND_EXPECTED = 0x6883;
+ /**
+ * Command chaining not supported.
+ */
+ public static final int SW_COMMAND_CHAINING_NOT_SUPPORTED = 0x6884;
+
+ // 69XX - Checking error - Command not allowed (further
+ // qualification in SW2).
+ /**
+ * No information given.
+ */
+ public static final int SW_69_NO_INFO = 0x6900;
+ /**
+ * Command incompatible with file structure.
+ */
+ public static final int SW_COMMAND_INCOMPATIBLE = 0x6981;
+ /**
+ * Security status not satisfied.
+ */
+ public static final int SW_SECURITY_STATUS_NOT_SATISFIED = 0x6982;
+ /**
+ * Authentication method blocked.
+ */
+ public static final int SW_AUTH_METHOD_BLOCKED = 0x6983;
+ /**
+ * Reference data not usable.
+ */
+ public static final int SW_REF_DATA_NOT_USABLE = 0x6984;
+ /**
+ * Conditions of use not satisfied.
+ */
+ public static final int SW_CONDITIONS_NOT_SATISFIED = 0x6985;
+ /**
+ * Command not allowed (no current EF).
+ */
+ public static final int SW_COMMAND_NOT_ALLOWED = 0x6986;
+ /**
+ * Expected secure messaging data objects missing.
+ */
+ public static final int SW_SM_OBJECT_MISSING = 0x6987;
+ /**
+ * Incorrect secure messaging data objects.
+ */
+ public static final int SW_SM_INCORRECT_OBJECT = 0x6988;
+
+ // 6AXX - Checking error - Wrong parameters P1-P2 (further
+ // qualification in SW2)
+ /**
+ * No information given.
+ */
+ public static final int SW_6A_NO_INFO = 0x6A00;
+ /**
+ * Incorrect parameters in the command data field.
+ */
+ public static final int SW_WRONG_DATA = 0x6A80;
+ /**
+ * Function not supported.
+ */
+ public static final int SW_FUNC_NOT_SUPPORTED = 0x6A81;
+ /**
+ * File or application not found.
+ */
+ public static final int SW_FILE_OR_APP_NOT_FOUND = 0x6A82;
+ /**
+ * Record not found.
+ */
+ public static final int SW_RECORD_NOT_FOUND = 0x6A83;
+ /**
+ * Not enough memory space in the file.
+ */
+ public static final int SW_NOT_ENOUGH_MEMORY = 0x6A84;
+ /**
+ * Nc inconsistent with TLV structure.
+ */
+ public static final int SW_WRONG_NC_TLV = 0x6A85;
+ /**
+ * Incorrect parameters P1-P2.
+ */
+ public static final int SW_INCORRECT_P1P2 = 0x6A86;
+ /**
+ * Nc inconsistent with parameters P1-P2.
+ */
+ public static final int SW_WRONG_NC_P1P2 = 0x6A87;
+ /**
+ * Referenced data or reference data not found (exact meaning depending on
+ * the command).
+ */
+ public static final int SW_REF_NOT_FOUND = 0x6A88;
+ /**
+ * File already exists.
+ */
+ public static final int SW_FILE_ALREADY_EXISTS = 0x6A89;
+ /**
+ * DF name already exists.
+ */
+ public static final int SW_DF_NAME_ALREADY_EXISTS = 0x6A8A;
+
+ // 6B00 - Checking error - Wrong parameters P1-P2
+ /**
+ * Wrong parameters P1-P2.
+ */
+ public static final int SW_WRONG_PARAMETERS_P1P2 = 0x6B00;
+
+ // 6CXX - Checking error - Wrong Le field; SW2 encodes the exact
+ // number of available data bytes.
+ /**
+ * Wrong Le field; SW2 encodes the exact number of available data bytes
+ * (minimum value).
+ */
+ public static final int SW_WRONG_LE_MIN = 0x6C00;
+ /**
+ * Wrong Le field; SW2 encodes the exact number of available data bytes
+ * (maximum value).
+ */
+ public static final int SW_WRONG_LE_MAX = 0x6CFF;
+
+ // 6D00 - Checking error - Instruction code not supported or
+ // invalid.
+ /**
+ * Instruction code not supported or invalid.
+ */
+ public static final int SW_INS_NOT_SUPPORTED = 0x6D00;
+
+ // 6E00 - Checking error - Class not supported.
+ /**
+ * Class not supported.
+ */
+ public static final int SW_CLA_NOT_SUPPORTED = 0x6E00;
+
+ // 6F00 - Checking error - No precise diagnostic
+ /**
+ * No precise diagnostic.
+ */
+ public static final int SW_NO_PRECISE_DIAGNOSTIC = 0x6F00;
+
+ /**
+ * This class cannot be instantiated.
+ */
+ private ISO7816() {
+ }
+}
diff --git a/app/src/main/java/nl/mansoft/openmobileapi/util/ResponseApdu.java b/app/src/main/java/nl/mansoft/openmobileapi/util/ResponseApdu.java
new file mode 100644
index 0000000..e1bab86
--- /dev/null
+++ b/app/src/main/java/nl/mansoft/openmobileapi/util/ResponseApdu.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2013 Giesecke & Devrient GmbH.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package nl.mansoft.openmobileapi.util;
+
+/**
+ * This object represents a response APDU as specified by ISO/IEC 7816.
+ *
+ * @author Giesecke & Devrient
+ *
+ */
+public class ResponseApdu {
+
+ /**
+ * DATA field of response APDU.
+ */
+ private byte[] mData;
+
+ /**
+ * STATUS WORD field of response APDU.
+ */
+ private byte[] mSw;
+
+ /**
+ * Creates a response APDU.
+ *
+ * @param response The response APDU as a byte array.
+ */
+ public ResponseApdu(byte[] response) {
+ if (response == null) {
+ throw new IllegalArgumentException("Response must not be null.");
+ }
+ if (response.length < ISO7816.RESP_APDU_LENGTH_SW
+ || response.length > ISO7816.MAX_RESPONSE_DATA_LENGTH
+ + ISO7816.RESP_APDU_LENGTH_SW) {
+ throw new IllegalArgumentException(
+ "Invalid response length (" + response.length + ").");
+ }
+ if (response.length > ISO7816.RESP_APDU_LENGTH_SW) {
+ mData = new byte[response.length - ISO7816.RESP_APDU_LENGTH_SW];
+ System.arraycopy(response, 0, mData, 0, mData.length);
+ }
+ mSw = new byte[ISO7816.RESP_APDU_LENGTH_SW];
+ System.arraycopy(response, response.length
+ - ISO7816.RESP_APDU_LENGTH_SW, mSw, 0,
+ ISO7816.RESP_APDU_LENGTH_SW);
+ }
+
+ /**
+ * Returns the DATA field of the response if present, null otherwise.
+ *
+ * @return The DATA field of the response if present, null otherwise.
+ */
+ public byte[] getData() {
+ return mData;
+ }
+
+ /**
+ * Returns the STATUS WORD field of the response.
+ *
+ * @return The STATUS WORD field of the response.
+ */
+ public byte[] getSw() {
+ return mSw;
+ }
+
+ /**
+ * Returns the STATUS WORD field as int value.
+ *
+ * @return The STATUS WORD field as int value.
+ */
+ public int getSwValue() {
+ return ((mSw[0] & 0x0FF) << 8) + (mSw[1] & 0x0FF);
+ }
+
+ /**
+ * Returns true if the SW = 90 00, false otherwise.
+ *
+ * @return true if the SW = 90 00, false otherwise..
+ */
+ public boolean isSuccess() {
+ return getSwValue() == ISO7816.SW_NO_FURTHER_QUALIFICATION;
+ }
+
+ /**
+ * Returns true if the SW = 62 XX or SW = 63 XX, false otherwise.
+ *
+ * @return true if the SW = 62 XX or SW = 63 XX, false otherwise.
+ */
+ public boolean isWarning() {
+ return mSw[0] == ISO7816.SW1_62 || mSw[0] == ISO7816.SW1_63;
+ }
+
+ /**
+ * Returns the first byte of STATUS WORD field as int value.
+ *
+ * @return The first byte of STATUS WORD field as int value.
+ */
+ public int getSw1Value() {
+ return (int) 0x0FF & mSw[0];
+ }
+
+ /**
+ * Returns the second byte of STATUS WORD field as int value.
+ *
+ * @return The second byte of STATUS WORD field as int value.
+ */
+ public int getSw2Value() {
+ return (int) 0x0FF & mSw[1];
+ }
+}
+
diff --git a/res/drawable-hdpi/icon.png b/app/src/main/res/drawable-hdpi/icon.png
similarity index 100%
rename from res/drawable-hdpi/icon.png
rename to app/src/main/res/drawable-hdpi/icon.png
diff --git a/res/drawable-ldpi/icon.png b/app/src/main/res/drawable-ldpi/icon.png
similarity index 100%
rename from res/drawable-ldpi/icon.png
rename to app/src/main/res/drawable-ldpi/icon.png
diff --git a/res/drawable-mdpi/icon.png b/app/src/main/res/drawable-mdpi/icon.png
similarity index 100%
rename from res/drawable-mdpi/icon.png
rename to app/src/main/res/drawable-mdpi/icon.png
diff --git a/res/layout/main.xml b/app/src/main/res/layout/main.xml
similarity index 100%
rename from res/layout/main.xml
rename to app/src/main/res/layout/main.xml
diff --git a/res/values/strings.xml b/app/src/main/res/values/strings.xml
similarity index 100%
rename from res/values/strings.xml
rename to app/src/main/res/values/strings.xml
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..6c5cbc3
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,17 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ jcenter()
+ google()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.1.3'
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ google()
+ }
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..d08c250
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Apr 10 19:04:30 CEST 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/project.properties b/project.properties
deleted file mode 100644
index 7320fca..0000000
--- a/project.properties
+++ /dev/null
@@ -1,16 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system edit
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-#
-# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
-#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
-
-# Indicates whether an apk should be generated for each density.
-split.density=false
-# Project target.
-target=Giesecke & Devrient GmbH:Open Mobile API:15
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/src/com/gieseckedevrient/android/hellosmartcard/MainActivity.java b/src/com/gieseckedevrient/android/hellosmartcard/MainActivity.java
deleted file mode 100644
index 3a3f210..0000000
--- a/src/com/gieseckedevrient/android/hellosmartcard/MainActivity.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package com.gieseckedevrient.android.hellosmartcard;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.Toast;
-import org.simalliance.openmobileapi.*;
-
-
-public class MainActivity extends Activity implements SEService.CallBack {
-
- final String LOG_TAG = "HelloSmartcard";
-
- private SEService seService;
-
- private Button button;
-
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
-
- super.onCreate(savedInstanceState);
-
- LinearLayout layout = new LinearLayout(this);
- layout.setLayoutParams(new LayoutParams(
- LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT));
-
- button = new Button(this);
- button.setLayoutParams(new LayoutParams(
- LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT));
-
- button.setText("Click Me");
- button.setEnabled(false);
- button.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- try {
- Log.i(LOG_TAG, "Retrieve available readers...");
- Reader[] readers = seService.getReaders();
- if (readers.length < 1)
- return;
-
- Log.i(LOG_TAG, "Create Session from the first reader...");
- Session session = readers[0].openSession();
-
- Log.i(LOG_TAG, "Create logical channel within the session...");
- Channel channel = session.openLogicalChannel(new byte[] {
- (byte) 0xD2, 0x76, 0x00, 0x01, 0x18, 0x00, 0x02,
- (byte) 0xFF, 0x49, 0x50, 0x25, (byte) 0x89,
- (byte) 0xC0, 0x01, (byte) 0x9B, 0x01 });
-
- Log.i(LOG_TAG, "Send HelloWorld APDU command");
- byte[] respApdu = channel.transmit(new byte[] {
- (byte) 0x90, 0x10, 0x00, 0x00, 0x00 });
-
- channel.close();
-
- // Parse response APDU and show text but remove SW1 SW2 first
- byte[] helloStr = new byte[respApdu.length - 2];
- System.arraycopy(respApdu, 0, helloStr, 0, respApdu.length - 2);
- Toast.makeText(MainActivity.this, new String(helloStr), Toast.LENGTH_LONG).show();
- } catch (Exception e) {
- Log.e(LOG_TAG, "Error occured:", e);
- return;
- }
- }
- });
-
- layout.addView(button);
- setContentView(layout);
-
-
- try {
- Log.i(LOG_TAG, "creating SEService object");
- seService = new SEService(this, this);
- } catch (SecurityException e) {
- Log.e(LOG_TAG, "Binding not allowed, uses-permission org.simalliance.openmobileapi.SMARTCARD?");
- } catch (Exception e) {
- Log.e(LOG_TAG, "Exception: " + e.getMessage());
- }
- }
-
- @Override
- protected void onDestroy() {
- if (seService != null && seService.isConnected()) {
- seService.shutdown();
- }
- super.onDestroy();
- }
-
- public void serviceConnected(SEService service) {
- Log.i(LOG_TAG, "seviceConnected()");
- button.setEnabled(true);
- }
-}