Extract library (#1)
* Use gradle 3.2.1 * Use gradle 4.10.2 * Cleanup - this is not needed * Cleanup * Cleanup * Leverage SystemClock * Cleanup * Separate the Application specific code * Extract library * Do not bleed out BouncyCastle into library API Also change the logging as we do not have the byte[]->hex method from spongycastle anymore
|
@ -1,26 +0,0 @@
|
|||
package im.status.hardwallet_lite_android;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
assertEquals("im.status.hardwallet_lite_android", appContext.getPackageName());
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package im.status.hardwallet_lite_android.app;
|
||||
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.os.Bundle;
|
||||
import im.status.hardwallet_lite_android.R;
|
||||
import im.status.hardwallet_lite_android.io.CardManager;
|
||||
|
||||
import java.security.Security;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
static {
|
||||
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
|
||||
}
|
||||
|
||||
private NfcAdapter nfcAdapter;
|
||||
private CardManager cardManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||
this.cardManager = new CardManager();
|
||||
this.cardManager.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (nfcAdapter != null) {
|
||||
nfcAdapter.enableReaderMode(this, this.cardManager, NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (nfcAdapter != null) {
|
||||
nfcAdapter.disableReaderMode(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
package im.status.hardwallet_lite_android.io;
|
||||
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.Tag;
|
||||
import android.nfc.tech.IsoDep;
|
||||
import android.util.Log;
|
||||
import im.status.hardwallet_lite_android.wallet.WalletAppletCommandSet;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class CardManager extends Thread implements NfcAdapter.ReaderCallback {
|
||||
private static final String TAG = "CardManager";
|
||||
|
||||
private IsoDep isoDep;
|
||||
private boolean isRunning;
|
||||
|
||||
public boolean isConnected() {
|
||||
return this.isoDep != null && this.isoDep.isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTagDiscovered(Tag tag) {
|
||||
this.isoDep = IsoDep.get(tag);
|
||||
|
||||
try {
|
||||
this.isoDep = IsoDep.get(tag);
|
||||
this.isoDep.connect();
|
||||
this.isoDep.setTimeout(120000);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "error connecting to tag");
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
boolean connected = this.isConnected();
|
||||
|
||||
while(true) {
|
||||
boolean newConnected = this.isConnected();
|
||||
if (newConnected != connected) {
|
||||
connected = newConnected;
|
||||
Log.i(TAG, "tag " + (connected ? "connected" : "disconnected"));
|
||||
|
||||
if (connected && !isRunning) {
|
||||
this.onCardConnected();
|
||||
} else {
|
||||
this.onCardDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "error in TagManager thread: " + e.getMessage());
|
||||
this.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onCardConnected() {
|
||||
this.isRunning = true;
|
||||
|
||||
try {
|
||||
CardChannel cardChannel = new CardChannel(this.isoDep);
|
||||
// Applet-specific code
|
||||
WalletAppletCommandSet cmdSet = new WalletAppletCommandSet(cardChannel);
|
||||
|
||||
// First thing to do is selecting the applet on the card.
|
||||
cmdSet.select().checkOK();
|
||||
|
||||
// In real projects, the pairing key should be saved and used for all new sessions.
|
||||
cmdSet.autoPair("WalletAppletTest");
|
||||
|
||||
// Opening a Secure Channel is needed for all other applet commands
|
||||
cmdSet.autoOpenSecureChannel();
|
||||
|
||||
// We send a GET STATUS command, which does not require PIN authentication
|
||||
APDUResponse resp = cmdSet.getStatus(WalletAppletCommandSet.GET_STATUS_P1_APPLICATION).checkOK();
|
||||
|
||||
// PIN authentication allows execution of privileged commands
|
||||
cmdSet.verifyPIN("000000").checkOK();
|
||||
|
||||
// Cleanup, in a real application you would not unpair and instead keep the pairing key for successive interactions.
|
||||
// We also remove all other pairings so that we do not fill all slots with failing runs. Again in real application
|
||||
// this would be a very bad idea to do.
|
||||
cmdSet.unpairOthers();
|
||||
cmdSet.autoUnpair();
|
||||
|
||||
Log.i(TAG, "GET STATUS response: " + Hex.toHexString(resp.getData()));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
}
|
||||
|
||||
this.isRunning = false;
|
||||
}
|
||||
|
||||
private void onCardDisconnected() {
|
||||
this.isRunning = false;
|
||||
this.isoDep = null;
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package im.status.hardwallet_lite_android;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ buildscript {
|
|||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.0'
|
||||
classpath 'com.android.tools.build:gradle:3.2.1'
|
||||
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
|
|
@ -3,7 +3,7 @@ apply plugin: 'com.android.application'
|
|||
android {
|
||||
compileSdkVersion 28
|
||||
defaultConfig {
|
||||
applicationId "im.status.hardwallet_lite_android"
|
||||
applicationId "im.status.hardwallet_lite_android.demo"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
|
@ -19,11 +19,11 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'com.android.support:appcompat-v7:28.0.0'
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
implementation 'com.madgag.spongycastle:core:1.58.0.0'
|
||||
implementation 'com.madgag.spongycastle:prov:1.58.0.0'
|
||||
|
||||
implementation project(':lib')
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
|
@ -1,9 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="im.status.hardwallet_lite_android">
|
||||
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-feature android:name="android.hardware.nfc.hce" android:required="true" />
|
||||
package="im.status.hardwallet_lite_android.demo">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
|
@ -0,0 +1,95 @@
|
|||
package im.status.hardwallet_lite_android.app;
|
||||
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import im.status.hardwallet_lite_android.demo.R;
|
||||
import im.status.hardwallet_lite_android.io.APDUResponse;
|
||||
import im.status.hardwallet_lite_android.io.CardChannel;
|
||||
import im.status.hardwallet_lite_android.io.CardManager;
|
||||
import im.status.hardwallet_lite_android.io.OnCardConnectedListener;
|
||||
import im.status.hardwallet_lite_android.wallet.WalletAppletCommandSet;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = "MainActivity";
|
||||
|
||||
private NfcAdapter nfcAdapter;
|
||||
private CardManager cardManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||
cardManager = new CardManager();
|
||||
|
||||
cardManager.setOnCardConnectedListener(new OnCardConnectedListener() {
|
||||
@Override
|
||||
public void onConnected(CardChannel cardChannel) {
|
||||
try {
|
||||
|
||||
Log.i(TAG, "onCardConnected()");
|
||||
|
||||
// Applet-specific code
|
||||
WalletAppletCommandSet cmdSet = new WalletAppletCommandSet(cardChannel);
|
||||
|
||||
// First thing to do is selecting the applet on the card.
|
||||
cmdSet.select().checkOK();
|
||||
|
||||
Log.i(TAG, "Applet is installed on the connected card.");
|
||||
|
||||
// In real projects, the pairing key should be saved and used for all new sessions.
|
||||
cmdSet.autoPair("WalletAppletTest");
|
||||
|
||||
Log.i(TAG, "Pairing with card is done.");
|
||||
|
||||
// Opening a Secure Channel is needed for all other applet commands
|
||||
cmdSet.autoOpenSecureChannel();
|
||||
|
||||
Log.i(TAG, "Secure channel opened.");
|
||||
|
||||
// We send a GET STATUS command, which does not require PIN authentication
|
||||
APDUResponse resp = cmdSet.getStatus(WalletAppletCommandSet.GET_STATUS_P1_APPLICATION).checkOK();
|
||||
|
||||
Log.i(TAG, "Got status (response length=" + resp.getData().length + ")." );
|
||||
|
||||
// PIN authentication allows execution of privileged commands
|
||||
cmdSet.verifyPIN("000000").checkOK();
|
||||
|
||||
Log.i(TAG, "Pin Verified.");
|
||||
|
||||
// Cleanup, in a real application you would not unpair and instead keep the pairing key for successive interactions.
|
||||
// We also remove all other pairings so that we do not fill all slots with failing runs. Again in real application
|
||||
// this would be a very bad idea to do.
|
||||
cmdSet.unpairOthers();
|
||||
cmdSet.autoUnpair();
|
||||
|
||||
Log.i(TAG, "Unpaired.");
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
cardManager.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (nfcAdapter != null) {
|
||||
nfcAdapter.enableReaderMode(this, this.cardManager, NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (nfcAdapter != null) {
|
||||
nfcAdapter.disableReaderMode(this);
|
||||
}
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
@ -1,6 +1,5 @@
|
|||
#Tue Oct 23 10:14:17 CEST 2018
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,26 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
defaultConfig {
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
task androidSourcesJar(type: Jar) {
|
||||
from android.sourceSets.main.java.source
|
||||
classifier = 'sources'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives androidSourcesJar
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.madgag.spongycastle:core:1.58.0.0'
|
||||
implementation 'com.madgag.spongycastle:prov:1.58.0.0'
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="im.status.hardwallet_lite_android">
|
||||
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-feature android:name="android.hardware.nfc.hce" android:required="true" />
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,76 @@
|
|||
package im.status.hardwallet_lite_android.io;
|
||||
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.Tag;
|
||||
import android.nfc.tech.IsoDep;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import java.io.IOException;
|
||||
import java.security.Security;
|
||||
|
||||
public class CardManager extends Thread implements NfcAdapter.ReaderCallback {
|
||||
|
||||
public CardManager() {
|
||||
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
|
||||
}
|
||||
|
||||
private static final String TAG = "CardManager";
|
||||
|
||||
private IsoDep isoDep;
|
||||
private boolean isRunning;
|
||||
private OnCardConnectedListener onCardConnectedListener;
|
||||
|
||||
public boolean isConnected() {
|
||||
return isoDep != null && isoDep.isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTagDiscovered(Tag tag) {
|
||||
isoDep = IsoDep.get(tag);
|
||||
|
||||
try {
|
||||
isoDep = IsoDep.get(tag);
|
||||
isoDep.connect();
|
||||
isoDep.setTimeout(120000);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "error connecting to tag");
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
boolean connected = isConnected();
|
||||
|
||||
while (true) {
|
||||
boolean newConnected = isConnected();
|
||||
if (newConnected != connected) {
|
||||
connected = newConnected;
|
||||
Log.i(TAG, "tag " + (connected ? "connected" : "disconnected"));
|
||||
|
||||
if (connected && !isRunning) {
|
||||
onCardConnected();
|
||||
} else {
|
||||
onCardDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
SystemClock.sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
private void onCardConnected() {
|
||||
isRunning = true;
|
||||
|
||||
onCardConnectedListener.onConnected(new CardChannel(isoDep));
|
||||
|
||||
isRunning = false;
|
||||
}
|
||||
|
||||
private void onCardDisconnected() {
|
||||
isRunning = false;
|
||||
isoDep = null;
|
||||
}
|
||||
|
||||
public void setOnCardConnectedListener(OnCardConnectedListener onConnectedListener) {
|
||||
onCardConnectedListener = onConnectedListener;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package im.status.hardwallet_lite_android.io;
|
||||
|
||||
public interface OnCardConnectedListener {
|
||||
void onConnected(CardChannel channel);
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
include ':app'
|
||||
include ':lib'
|
||||
include ':demo'
|
||||
|
|