From 1d3e5fe4fd945d130d466bad17762e8b3e33b60d Mon Sep 17 00:00:00 2001 From: Frank Morgner Date: Fri, 1 Apr 2016 02:27:37 +0200 Subject: [PATCH] Rewrite of Remote Smart Card Reader - use material design - use AsyncTask instead of Runnable for VPCDWorker to integrate with the App's lifecycle - use a dedicated Preference Activity - integrated zxing's QR code scanner - use Snackbar instead of Toast - capture debug messages in log instead of explicitly passing messages - use reader mode only, breaks support of Android before kitkat --- remote-reader/.gitignore | 8 + remote-reader/app/app.iml | 34 +- remote-reader/app/build.gradle | 27 +- ...{proguard-rules.txt => proguard-rules.pro} | 8 +- .../app/ApplicationTest.java | 13 + .../app/src/main/AndroidManifest.xml | 35 +- .../example/android/common/logger/Log.java | 236 ++++++++++ .../android/common/logger/LogFragment.java | 109 +++++ .../android/common/logger/LogNode.java | 39 ++ .../android/common/logger/LogView.java | 145 +++++++ .../android/common/logger/LogWrapper.java | 75 ++++ .../common/logger/MessageOnlyLogFilter.java | 60 +++ .../app/AppCompatPreferenceActivity.java | 109 +++++ .../app/MainActivity.java | 410 ++++++------------ .../app/MessageSender.java | 81 ---- .../app/MyLogFragment.java | 80 ++++ .../remotesmartcardreader/app/Settings.java | 67 --- .../app/SettingsActivity.java | 247 +++++++++++ .../remotesmartcardreader/app/VPCDWorker.java | 92 ++-- .../app/screaders/NFCReader.java | 55 ++- .../main/res/drawable-hdpi/ic_action_copy.png | Bin 375 -> 0 bytes .../res/drawable-hdpi/ic_action_delete.png | Bin 3053 -> 0 bytes .../main/res/drawable-mdpi/ic_action_copy.png | Bin 284 -> 0 bytes .../res/drawable-mdpi/ic_action_delete.png | Bin 3011 -> 0 bytes .../res/drawable-xhdpi/ic_action_copy.png | Bin 351 -> 0 bytes .../res/drawable-xhdpi/ic_action_delete.png | Bin 3128 -> 0 bytes .../res/drawable-xxhdpi/ic_action_copy.png | Bin 464 -> 0 bytes .../main/res/layout-w480dp/activity_main.xml | 80 ---- .../app/src/main/res/layout/activity_main.xml | 110 ++--- .../app/src/main/res/layout/content_main.xml | 19 + .../main/res/menu/{main.xml => menu_log.xml} | 9 +- .../app/src/main/res/menu/menu_main.xml | 11 + .../src/main/res/mipmap-hdpi/ic_connect.png | Bin 0 -> 1125 bytes .../src/main/res/mipmap-mdpi/ic_connect.png | Bin 0 -> 703 bytes .../src/main/res/mipmap-xhdpi/ic_connect.png | Bin 0 -> 1489 bytes .../src/main/res/mipmap-xxhdpi/ic_connect.png | Bin 0 -> 2375 bytes .../main/res/mipmap-xxxhdpi/ic_connect.png | Bin 0 -> 3356 bytes .../app/src/main/res/values-v21/styles.xml | 9 + .../app/src/main/res/values/colors.xml | 6 + .../app/src/main/res/values/dimens.xml | 9 +- .../app/src/main/res/values/strings.xml | 45 +- .../app/src/main/res/values/styles.xml | 12 + .../app/src/main/res/xml/settings.xml | 55 +++ .../ExampleUnitTest.java | 15 + remote-reader/build.gradle | 6 +- remote-reader/doc/README.txt | 5 +- remote-reader/gradle.properties | 4 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 49896 -> 53637 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- remote-reader/gradlew | 10 +- remote-reader/remote-reader.iml | 2 +- 51 files changed, 1624 insertions(+), 717 deletions(-) create mode 100644 remote-reader/.gitignore rename remote-reader/app/{proguard-rules.txt => proguard-rules.pro} (71%) create mode 100644 remote-reader/app/src/androidTest/java/com/vsmartcard/remotesmartcardreader/app/ApplicationTest.java create mode 100644 remote-reader/app/src/main/java/com/example/android/common/logger/Log.java create mode 100644 remote-reader/app/src/main/java/com/example/android/common/logger/LogFragment.java create mode 100644 remote-reader/app/src/main/java/com/example/android/common/logger/LogNode.java create mode 100644 remote-reader/app/src/main/java/com/example/android/common/logger/LogView.java create mode 100644 remote-reader/app/src/main/java/com/example/android/common/logger/LogWrapper.java create mode 100644 remote-reader/app/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java create mode 100644 remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/AppCompatPreferenceActivity.java delete mode 100644 remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MessageSender.java create mode 100644 remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MyLogFragment.java delete mode 100644 remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/Settings.java create mode 100644 remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/SettingsActivity.java delete mode 100644 remote-reader/app/src/main/res/drawable-hdpi/ic_action_copy.png delete mode 100755 remote-reader/app/src/main/res/drawable-hdpi/ic_action_delete.png delete mode 100644 remote-reader/app/src/main/res/drawable-mdpi/ic_action_copy.png delete mode 100755 remote-reader/app/src/main/res/drawable-mdpi/ic_action_delete.png delete mode 100644 remote-reader/app/src/main/res/drawable-xhdpi/ic_action_copy.png delete mode 100755 remote-reader/app/src/main/res/drawable-xhdpi/ic_action_delete.png delete mode 100644 remote-reader/app/src/main/res/drawable-xxhdpi/ic_action_copy.png delete mode 100644 remote-reader/app/src/main/res/layout-w480dp/activity_main.xml create mode 100644 remote-reader/app/src/main/res/layout/content_main.xml rename remote-reader/app/src/main/res/menu/{main.xml => menu_log.xml} (69%) create mode 100644 remote-reader/app/src/main/res/menu/menu_main.xml create mode 100644 remote-reader/app/src/main/res/mipmap-hdpi/ic_connect.png create mode 100644 remote-reader/app/src/main/res/mipmap-mdpi/ic_connect.png create mode 100644 remote-reader/app/src/main/res/mipmap-xhdpi/ic_connect.png create mode 100644 remote-reader/app/src/main/res/mipmap-xxhdpi/ic_connect.png create mode 100644 remote-reader/app/src/main/res/mipmap-xxxhdpi/ic_connect.png create mode 100644 remote-reader/app/src/main/res/values-v21/styles.xml create mode 100644 remote-reader/app/src/main/res/values/colors.xml create mode 100644 remote-reader/app/src/main/res/xml/settings.xml create mode 100644 remote-reader/app/src/test/java/vsmartcard/remotesmartcardreader/ExampleUnitTest.java mode change 100644 => 100755 remote-reader/gradlew diff --git a/remote-reader/.gitignore b/remote-reader/.gitignore new file mode 100644 index 0000000..c6cbe56 --- /dev/null +++ b/remote-reader/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/remote-reader/app/app.iml b/remote-reader/app/app.iml index fe4f5bf..f76412b 100644 --- a/remote-reader/app/app.iml +++ b/remote-reader/app/app.iml @@ -65,33 +65,37 @@ - + - - - - - + + + + + + + - - - - + - - + - - - + + + + + + + + + \ No newline at end of file diff --git a/remote-reader/app/build.gradle b/remote-reader/app/build.gradle index e667cbf..8e77211 100644 --- a/remote-reader/app/build.gradle +++ b/remote-reader/app/build.gradle @@ -5,22 +5,33 @@ android { buildToolsVersion "23.0.1" defaultConfig { - applicationId "com.vsmartcard.remotesmartcardreader" - minSdkVersion 10 + applicationId "com.vsmartcard.remotesmartcardreader.app" + // NFC reader mode was added in KitKat + minSdkVersion 19 targetSdkVersion 23 - versionCode 3 - versionName "1.2" + versionCode 4 + versionName "2.0" } + buildTypes { release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } +repositories { + mavenCentral() +} + dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:23.0.1' - compile 'com.android.support:support-v4:23.0.1' + testCompile 'junit:junit:4.12' + compile 'com.android.support:appcompat-v7:23.2.1' + compile 'com.android.support:design:23.2.1' + compile 'com.android.support:support-v4:23.2.1' + + compile 'com.journeyapps:zxing-android-embedded:3.2.0@aar' + compile 'com.google.zxing:core:3.2.1' } diff --git a/remote-reader/app/proguard-rules.txt b/remote-reader/app/proguard-rules.pro similarity index 71% rename from remote-reader/app/proguard-rules.txt rename to remote-reader/app/proguard-rules.pro index 7514c34..c1c098d 100644 --- a/remote-reader/app/proguard-rules.txt +++ b/remote-reader/app/proguard-rules.pro @@ -1,8 +1,8 @@ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified -# in /home/fm/.local/opt/android-studio/sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. +# in /home/fm/.local/opt/Android/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html @@ -14,4 +14,4 @@ # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; -#} \ No newline at end of file +#} diff --git a/remote-reader/app/src/androidTest/java/com/vsmartcard/remotesmartcardreader/app/ApplicationTest.java b/remote-reader/app/src/androidTest/java/com/vsmartcard/remotesmartcardreader/app/ApplicationTest.java new file mode 100644 index 0000000..71f5eb4 --- /dev/null +++ b/remote-reader/app/src/androidTest/java/com/vsmartcard/remotesmartcardreader/app/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.vsmartcard.remotesmartcardreader.app; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/remote-reader/app/src/main/AndroidManifest.xml b/remote-reader/app/src/main/AndroidManifest.xml index 30b443d..d6362d8 100644 --- a/remote-reader/app/src/main/AndroidManifest.xml +++ b/remote-reader/app/src/main/AndroidManifest.xml @@ -5,22 +5,23 @@ - + - + android:theme="@style/AppTheme.NoActionBar" > - @@ -30,30 +31,18 @@ + + - + - - - - - - - - - - - - - diff --git a/remote-reader/app/src/main/java/com/example/android/common/logger/Log.java b/remote-reader/app/src/main/java/com/example/android/common/logger/Log.java new file mode 100644 index 0000000..17503c5 --- /dev/null +++ b/remote-reader/app/src/main/java/com/example/android/common/logger/Log.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2013 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. + */ +package com.example.android.common.logger; + +/** + * Helper class for a list (or tree) of LoggerNodes. + * + *

When this is set as the head of the list, + * an instance of it can function as a drop-in replacement for {@link android.util.Log}. + * Most of the methods in this class server only to map a method call in Log to its equivalent + * in LogNode.

+ */ +public class Log { + // Grabbing the native values from Android's native logging facilities, + // to make for easy migration and interop. + public static final int NONE = -1; + public static final int VERBOSE = android.util.Log.VERBOSE; + public static final int DEBUG = android.util.Log.DEBUG; + public static final int INFO = android.util.Log.INFO; + public static final int WARN = android.util.Log.WARN; + public static final int ERROR = android.util.Log.ERROR; + public static final int ASSERT = android.util.Log.ASSERT; + + // Stores the beginning of the LogNode topology. + private static LogNode mLogNode; + + /** + * Returns the next LogNode in the linked list. + */ + public static LogNode getLogNode() { + return mLogNode; + } + + /** + * Sets the LogNode data will be sent to. + */ + public static void setLogNode(LogNode node) { + mLogNode = node; + } + + /** + * Instructs the LogNode to print the log data provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void println(int priority, String tag, String msg, Throwable tr) { + if (mLogNode != null) { + mLogNode.println(priority, tag, msg, tr); + } + } + + /** + * Instructs the LogNode to print the log data provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + */ + public static void println(int priority, String tag, String msg) { + println(priority, tag, msg, null); + } + + /** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void v(String tag, String msg, Throwable tr) { + println(VERBOSE, tag, msg, tr); + } + + /** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void v(String tag, String msg) { + v(tag, msg, null); + } + + + /** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void d(String tag, String msg, Throwable tr) { + println(DEBUG, tag, msg, tr); + } + + /** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void d(String tag, String msg) { + d(tag, msg, null); + } + + /** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void i(String tag, String msg, Throwable tr) { + println(INFO, tag, msg, tr); + } + + /** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void i(String tag, String msg) { + i(tag, msg, null); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void w(String tag, String msg, Throwable tr) { + println(WARN, tag, msg, tr); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void w(String tag, String msg) { + w(tag, msg, null); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void w(String tag, Throwable tr) { + w(tag, null, tr); + } + + /** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void e(String tag, String msg, Throwable tr) { + println(ERROR, tag, msg, tr); + } + + /** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void e(String tag, String msg) { + e(tag, msg, null); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void wtf(String tag, String msg, Throwable tr) { + println(ASSERT, tag, msg, tr); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void wtf(String tag, String msg) { + wtf(tag, msg, null); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void wtf(String tag, Throwable tr) { + wtf(tag, null, tr); + } +} diff --git a/remote-reader/app/src/main/java/com/example/android/common/logger/LogFragment.java b/remote-reader/app/src/main/java/com/example/android/common/logger/LogFragment.java new file mode 100644 index 0000000..b302acd --- /dev/null +++ b/remote-reader/app/src/main/java/com/example/android/common/logger/LogFragment.java @@ -0,0 +1,109 @@ +/* +* Copyright 2013 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. +*/ +/* + * Copyright 2013 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. + */ + +package com.example.android.common.logger; + +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ScrollView; + +/** + * Simple fraggment which contains a LogView and uses is to output log data it receives + * through the LogNode interface. + */ +public class LogFragment extends Fragment { + + private LogView mLogView; + private ScrollView mScrollView; + + public LogFragment() {} + + public View inflateViews() { + mScrollView = new ScrollView(getActivity()); + ViewGroup.LayoutParams scrollParams = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + mScrollView.setLayoutParams(scrollParams); + + mLogView = new LogView(getActivity()); + ViewGroup.LayoutParams logParams = new ViewGroup.LayoutParams(scrollParams); + logParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; + mLogView.setLayoutParams(logParams); + mLogView.setClickable(true); + mLogView.setFocusable(true); + mLogView.setTypeface(Typeface.MONOSPACE); + + // Want to set padding as 16 dips, setPadding takes pixels. Hooray math! + int paddingDips = 16; + double scale = getResources().getDisplayMetrics().density; + int paddingPixels = (int) ((paddingDips * (scale)) + .5); + mLogView.setPadding(paddingPixels, paddingPixels, paddingPixels, paddingPixels); + mLogView.setCompoundDrawablePadding(paddingPixels); + + mLogView.setGravity(Gravity.BOTTOM); + mLogView.setTextAppearance(getActivity(), android.R.style.TextAppearance_Holo_Medium); + + mScrollView.addView(mLogView); + return mScrollView; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View result = inflateViews(); + + mLogView.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + mScrollView.fullScroll(ScrollView.FOCUS_DOWN); + } + }); + return result; + } + + public LogView getLogView() { + return mLogView; + } +} \ No newline at end of file diff --git a/remote-reader/app/src/main/java/com/example/android/common/logger/LogNode.java b/remote-reader/app/src/main/java/com/example/android/common/logger/LogNode.java new file mode 100644 index 0000000..b8b59a4 --- /dev/null +++ b/remote-reader/app/src/main/java/com/example/android/common/logger/LogNode.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012 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. + */ +package com.example.android.common.logger; + +/** + * Basic interface for a logging system that can output to one or more targets. + * Note that in addition to classes that will output these logs in some format, + * one can also implement this interface over a filter and insert that in the chain, + * such that no targets further down see certain data, or see manipulated forms of the data. + * You could, for instance, write a "ToHtmlLoggerNode" that just converted all the log data + * it received to HTML and sent it along to the next node in the chain, without printing it + * anywhere. + */ +public interface LogNode { + + /** + * Instructs first LogNode in the list to print the log data provided. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + void println(int priority, String tag, String msg, Throwable tr); + +} diff --git a/remote-reader/app/src/main/java/com/example/android/common/logger/LogView.java b/remote-reader/app/src/main/java/com/example/android/common/logger/LogView.java new file mode 100644 index 0000000..c01542b --- /dev/null +++ b/remote-reader/app/src/main/java/com/example/android/common/logger/LogView.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2013 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. + */ +package com.example.android.common.logger; + +import android.app.Activity; +import android.content.Context; +import android.util.*; +import android.widget.TextView; + +/** Simple TextView which is used to output log data received through the LogNode interface. +*/ +public class LogView extends TextView implements LogNode { + + public LogView(Context context) { + super(context); + } + + public LogView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public LogView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Formats the log data and prints it out to the LogView. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + + + String priorityStr = null; + + // For the purposes of this View, we want to print the priority as readable text. + switch(priority) { + case android.util.Log.VERBOSE: + priorityStr = "VERBOSE"; + break; + case android.util.Log.DEBUG: + priorityStr = "DEBUG"; + break; + case android.util.Log.INFO: + priorityStr = "INFO"; + break; + case android.util.Log.WARN: + priorityStr = "WARN"; + break; + case android.util.Log.ERROR: + priorityStr = "ERROR"; + break; + case android.util.Log.ASSERT: + priorityStr = "ASSERT"; + break; + default: + break; + } + + // Handily, the Log class has a facility for converting a stack trace into a usable string. + String exceptionStr = null; + if (tr != null) { + exceptionStr = android.util.Log.getStackTraceString(tr); + } + + // Take the priority, tag, message, and exception, and concatenate as necessary + // into one usable line of text. + final StringBuilder outputBuilder = new StringBuilder(); + + String delimiter = "\t"; + appendIfNotNull(outputBuilder, priorityStr, delimiter); + appendIfNotNull(outputBuilder, tag, delimiter); + appendIfNotNull(outputBuilder, msg, delimiter); + appendIfNotNull(outputBuilder, exceptionStr, delimiter); + + // In case this was originally called from an AsyncTask or some other off-UI thread, + // make sure the update occurs within the UI thread. + ((Activity) getContext()).runOnUiThread( (new Thread(new Runnable() { + @Override + public void run() { + // Display the text we just generated within the LogView. + appendToLog(outputBuilder.toString()); + } + }))); + + if (mNext != null) { + mNext.println(priority, tag, msg, tr); + } + } + + public LogNode getNext() { + return mNext; + } + + public void setNext(LogNode node) { + mNext = node; + } + + /** Takes a string and adds to it, with a separator, if the bit to be added isn't null. Since + * the logger takes so many arguments that might be null, this method helps cut out some of the + * agonizing tedium of writing the same 3 lines over and over. + * @param source StringBuilder containing the text to append to. + * @param addStr The String to append + * @param delimiter The String to separate the source and appended strings. A tab or comma, + * for instance. + * @return The fully concatenated String as a StringBuilder + */ + private StringBuilder appendIfNotNull(StringBuilder source, String addStr, String delimiter) { + if (addStr != null) { + if (addStr.length() == 0) { + delimiter = ""; + } + + return source.append(addStr).append(delimiter); + } + return source; + } + + // The next LogNode in the chain. + LogNode mNext; + + /** Outputs the string as a new line of log data in the LogView. */ + public void appendToLog(String s) { + append("\n" + s); + } + + +} diff --git a/remote-reader/app/src/main/java/com/example/android/common/logger/LogWrapper.java b/remote-reader/app/src/main/java/com/example/android/common/logger/LogWrapper.java new file mode 100644 index 0000000..16a9e7b --- /dev/null +++ b/remote-reader/app/src/main/java/com/example/android/common/logger/LogWrapper.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012 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. + */ +package com.example.android.common.logger; + +import android.util.Log; + +/** + * Helper class which wraps Android's native Log utility in the Logger interface. This way + * normal DDMS output can be one of the many targets receiving and outputting logs simultaneously. + */ +public class LogWrapper implements LogNode { + + // For piping: The next node to receive Log data after this one has done its work. + private LogNode mNext; + + /** + * Returns the next LogNode in the linked list. + */ + public LogNode getNext() { + return mNext; + } + + /** + * Sets the LogNode data will be sent to.. + */ + public void setNext(LogNode node) { + mNext = node; + } + + /** + * Prints data out to the console using Android's native log mechanism. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + // There actually are log methods that don't take a msg parameter. For now, + // if that's the case, just convert null to the empty string and move on. + String useMsg = msg; + if (useMsg == null) { + useMsg = ""; + } + + // If an exeption was provided, convert that exception to a usable string and attach + // it to the end of the msg method. + if (tr != null) { + msg += "\n" + Log.getStackTraceString(tr); + } + + // This is functionally identical to Log.x(tag, useMsg); + // For instance, if priority were Log.VERBOSE, this would be the same as Log.v(tag, useMsg) + Log.println(priority, tag, useMsg); + + // If this isn't the last node in the chain, move things along. + if (mNext != null) { + mNext.println(priority, tag, msg, tr); + } + } +} diff --git a/remote-reader/app/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java b/remote-reader/app/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java new file mode 100644 index 0000000..19967dc --- /dev/null +++ b/remote-reader/app/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 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. + */ +package com.example.android.common.logger; + +/** + * Simple {@link LogNode} filter, removes everything except the message. + * Useful for situations like on-screen log output where you don't want a lot of metadata displayed, + * just easy-to-read message updates as they're happening. + */ +public class MessageOnlyLogFilter implements LogNode { + + LogNode mNext; + + /** + * Takes the "next" LogNode as a parameter, to simplify chaining. + * + * @param next The next LogNode in the pipeline. + */ + public MessageOnlyLogFilter(LogNode next) { + mNext = next; + } + + public MessageOnlyLogFilter() { + } + + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + if (mNext != null) { + getNext().println(Log.NONE, null, msg, null); + } + } + + /** + * Returns the next LogNode in the chain. + */ + public LogNode getNext() { + return mNext; + } + + /** + * Sets the LogNode data will be sent to.. + */ + public void setNext(LogNode node) { + mNext = node; + } + +} diff --git a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/AppCompatPreferenceActivity.java b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/AppCompatPreferenceActivity.java new file mode 100644 index 0000000..870efc0 --- /dev/null +++ b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/AppCompatPreferenceActivity.java @@ -0,0 +1,109 @@ +package com.vsmartcard.remotesmartcardreader.app; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.support.annotation.LayoutRes; +import android.support.annotation.Nullable; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatDelegate; +import android.support.v7.widget.Toolbar; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls + * to be used with AppCompat. + */ +public abstract class AppCompatPreferenceActivity extends PreferenceActivity { + + private AppCompatDelegate mDelegate; + + @Override + protected void onCreate(Bundle savedInstanceState) { + getDelegate().installViewFactory(); + getDelegate().onCreate(savedInstanceState); + super.onCreate(savedInstanceState); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + getDelegate().onPostCreate(savedInstanceState); + } + + public ActionBar getSupportActionBar() { + return getDelegate().getSupportActionBar(); + } + + public void setSupportActionBar(@Nullable Toolbar toolbar) { + getDelegate().setSupportActionBar(toolbar); + } + + @Override + public MenuInflater getMenuInflater() { + return getDelegate().getMenuInflater(); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + getDelegate().setContentView(layoutResID); + } + + @Override + public void setContentView(View view) { + getDelegate().setContentView(view); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().setContentView(view, params); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().addContentView(view, params); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override + protected void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getDelegate().onDestroy(); + } + + public void invalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + private AppCompatDelegate getDelegate() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, null); + } + return mDelegate; + } +} diff --git a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MainActivity.java b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MainActivity.java index 8043c2b..efff709 100644 --- a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MainActivity.java +++ b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MainActivity.java @@ -20,339 +20,210 @@ package com.vsmartcard.remotesmartcardreader.app; import android.annotation.TargetApi; -import android.app.Activity; import android.app.PendingIntent; -import android.content.ClipData; -import android.content.ClipboardManager; +import android.content.DialogInterface; import android.content.Intent; -import android.net.Uri; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.os.Build; import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.support.annotation.NonNull; -import android.text.Editable; -import android.text.Layout; -import android.text.method.ScrollingMovementMethod; -import android.view.KeyEvent; +import android.preference.PreferenceManager; +import android.provider.Settings; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.Snackbar; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; -import com.vsmartcard.remotesmartcardreader.app.screaders.DummyReader; -import com.vsmartcard.remotesmartcardreader.app.screaders.NFCReader; -import com.vsmartcard.remotesmartcardreader.app.screaders.SCReader; +import com.example.android.common.logger.Log; +import com.example.android.common.logger.LogFragment; +import com.example.android.common.logger.LogWrapper; +import com.example.android.common.logger.MessageOnlyLogFilter; + +import com.vsmartcard.remotesmartcardreader.app.screaders.*; @TargetApi(Build.VERSION_CODES.KITKAT) -public class MainActivity extends Activity implements NfcAdapter.ReaderCallback { +public class MainActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback { - class VPCDHandler extends Handler { - VPCDHandler(Looper looper) { - super(looper); - } - @Override - public void handleMessage(Message message) { - switch (message.what) { - case MessageSender.MESSAGE_CONNECTED: - textViewVPCDStatus.append(getResources().getString(R.string.status_connected_to)+" "+message.obj+"\n"); - spinner.setVisibility(View.INVISIBLE); - updateLabels(); - break; - case MessageSender.MESSAGE_DISCONNECTED: - textViewVPCDStatus.append(getResources().getString(R.string.status_disconnected)+"\n"); - testing = false; - updateLabels(); - break; - case MessageSender.MESSAGE_ATR: - textViewVPCDStatus.append(getResources().getString(R.string.status_atr)+": "+message.obj+"\n"); - break; - case MessageSender.MESSAGE_ON: - textViewVPCDStatus.append(getResources().getString(R.string.status_on)+"\n"); - break; - case MessageSender.MESSAGE_OFF: - textViewVPCDStatus.append(getResources().getString(R.string.status_off)+"\n"); - break; - case MessageSender.MESSAGE_RESET: - textViewVPCDStatus.append(getResources().getString(R.string.status_reset)+"\n"); - break; - case MessageSender.MESSAGE_CAPDU: - textViewVPCDStatus.append(getResources().getString(R.string.status_capdu)+": "+message.obj+"\n"); - break; - case MessageSender.MESSAGE_RAPDU: - textViewVPCDStatus.append(getResources().getString(R.string.status_rapdu)+": "+message.obj+"\n"); - break; - case MessageSender.MESSAGE_ERROR: - if (testing) { - textViewVPCDStatus.append(getResources().getString(R.string.status_error)+": "+message.obj+"\n"); - testing = false; - vpcdDisconnect(); - updateLabels(); - } - break; - default: - textViewVPCDStatus.append(getResources().getString(R.string.status_unknown)+" "+Integer.toString(message.what)+"\n"); - break; - } - Layout layout = textViewVPCDStatus.getLayout(); - if (layout != null) { - int scrollAmount = layout.getLineTop(textViewVPCDStatus.getLineCount()) - textViewVPCDStatus.getHeight(); - if (scrollAmount > 0) - textViewVPCDStatus.scrollTo(0, scrollAmount); - else - textViewVPCDStatus.scrollTo(0, 0); - } - } - } - - private EditText editTextVPCDHost; - private EditText editTextVPCDPort; - private TextView textViewVPCDStatus; - private Button button; - private ProgressBar spinner; - private VPCDHandler handler; private VPCDWorker vpcdTest; - private boolean testing; - private String hostname; - private String port; + private AlertDialog dialog; + private int oldOrientation; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); - editTextVPCDHost = (EditText) findViewById(R.id.editTextHostname); - editTextVPCDPort = (EditText) findViewById(R.id.editTextPort); - textViewVPCDStatus = (TextView) findViewById(R.id.textViewLog); - textViewVPCDStatus.setMovementMethod(new ScrollingMovementMethod()); - spinner = (ProgressBar) findViewById(R.id.progressBar); - button = (Button) findViewById(R.id.buttonDisConnect); - - EditText.OnEditorActionListener vpcdWatcher = new EditText.OnEditorActionListener() { + FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); + assert fab != null; + fab.setOnClickListener(new View.OnClickListener() { @Override - public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { - if (i == EditorInfo.IME_ACTION_DONE) { - editTextOnEditorDone(); - return true; + public void onClick(View view) { + if (vpcdTest != null && !vpcdTest.isCancelled()) { + Snackbar.make(view, "Disconnecting from VPCD...", Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + vpcdDisconnect(); + } else { + Snackbar.make(view, "Testing with dummy card...", Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + vpcdConnect(new DummyReader()); } - return false; } - }; - - editTextVPCDHost.setOnEditorActionListener(vpcdWatcher); - editTextVPCDPort.setOnEditorActionListener(vpcdWatcher); - - handler = new VPCDHandler(Looper.getMainLooper()); - + }); PendingIntent.getActivity(this, 0, new Intent(this, this.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); - - loadSettings(); - updateLabels(); } @Override public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.main, menu); - - return super.onCreateOptionsMenu(menu); + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { - // Handle presses on the action bar items - switch (item.getItemId()) { - case R.id.action_copy: - // Code to Copy the content of Text View to the Clip board. - ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("simple text", textViewVPCDStatus.getText()); - clipboard.setPrimaryClip(clip); - Toast.makeText(getApplicationContext(), "Log copied to clipboard.", - Toast.LENGTH_LONG).show(); - return true; - case R.id.action_delete: - textViewVPCDStatus.setText(""); - return true; - default: - return super.onOptionsItemSelected(item); + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + startActivity(new Intent(this, SettingsActivity.class)); + return true; } + + return super.onOptionsItemSelected(item); + } + + @Override + protected void onStart() { + super.onStart(); + initializeLogging(); + } + + /** Create a chain of targets that will receive log data */ + private void initializeLogging() { + // Wraps Android's native log framework. + LogWrapper logWrapper = new LogWrapper(); + // Using Log, front-end to the logging chain, emulates android.util.log method signatures. + Log.setLogNode(logWrapper); + + // Filter strips out everything except the message text. + MessageOnlyLogFilter msgFilter = new MessageOnlyLogFilter(); + logWrapper.setNext(msgFilter); + + // On screen logging via a fragment with a TextView. + LogFragment logFragment = (LogFragment) getSupportFragmentManager() + .findFragmentById(R.id.log_fragment); + msgFilter.setNext(logFragment.getLogView()); + } + + @Override + public void onPause() { + super.onPause(); + vpcdDisconnect(); + disableReaderMode(); } private void enableReaderMode() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - Bundle bundle = new Bundle(); - bundle.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, NFCReader.TIMEOUT*10); - NfcAdapter.getDefaultAdapter(this).enableReaderMode(this, this, - NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_NFC_B | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, - bundle); - } else { - //NfcAdapter.enableForegroundDispatch(this, mPendingIntent, mFilters, mTechLists); + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this); + if (!adapter.isEnabled()) { + if (dialog == null) { + dialog = new AlertDialog.Builder(this) + .setMessage("NFC is required to communicate with a contactless smart card. Do you want to enable NFC now?") + .setTitle("Enable NFC") + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + startActivity(new Intent(Settings.ACTION_NFC_SETTINGS)); + } + }) + .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + } + }).create(); + } + dialog.show(); + } + + // avoid re-starting the App and loosing the tag by rotating screen + oldOrientation = getRequestedOrientation(); + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); + + SharedPreferences SP = PreferenceManager.getDefaultSharedPreferences(this); + int timeout = Integer.parseInt(SP.getString("delay", Integer.toString(NFCReader.TIMEOUT))); + Bundle bundle = new Bundle(); + bundle.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, timeout * 10); + adapter.enableReaderMode(this, this, + NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_NFC_B | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, + bundle); + } + + private void disableReaderMode() { + if (dialog != null) { + dialog.dismiss(); + } + + setRequestedOrientation(oldOrientation); + + NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this); + if (nfc != null) { + nfc.disableReaderMode(this); } } @Override public void onTagDiscovered(Tag tag) { - NFCReader nfcReader = NFCReader.get(tag); + vpcdDisconnect(); + String[] techList = tag.getTechList(); + for (String aTechList : techList) { + if (aTechList.equals("android.nfc.tech.NfcA")) { + Log.i(getClass().getName(), "Discovered ISO/IEC 14443-A tag"); + } else if (aTechList.equals("android.nfc.tech.NfcB")) { + Log.i(getClass().getName(), "Discovered ISO/IEC 14443-B tag"); + } + } + NFCReader nfcReader = NFCReader.get(tag, this); if (nfcReader != null) { - /* avoid updating UI components since this may end up on a non ui thread */ - vpcdDisconnect(); - testing = true; vpcdConnect(nfcReader); } } - private void disableReaderMode() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this); - if (nfc != null) { - nfc.disableReaderMode(this); - } - } - } - - private static final String saved_status_key = "textViewVPCDStatus"; - @Override - public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - textViewVPCDStatus.setText(savedInstanceState.getCharSequence(saved_status_key)); - } - @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - savedInstanceState.putCharSequence(saved_status_key, textViewVPCDStatus.getText()); - super.onSaveInstanceState(savedInstanceState); - } - - @Override - public void onPause() { - super.onPause(); - - testing = false; - vpcdDisconnect(); - disableReaderMode(); - } - - public void buttonOnClickDisConnect(View view) { - if (testing) { - testing = false; - vpcdDisconnect(); - } else { - saveSettings(); - testing = true; - spinner.setVisibility(View.VISIBLE); - textViewVPCDStatus.append(getResources().getString(R.string.status_dummy_start)+"\n"); - vpcdConnect(new DummyReader()); - } - updateLabels(); - } - - private void editTextOnEditorDone() { - textViewVPCDStatus.append(getResources().getString(R.string.status_dummy_start)+"\n"); - forceConnect(new DummyReader()); - } - private void vpcdConnect(SCReader scReader) { - int p = VPCDWorker.DEFAULT_PORT; - try { - p = Integer.parseInt(port); - } catch (NumberFormatException e) { - textViewVPCDStatus.append(getResources().getString(R.string.status_default_port)+"\n"); - } - vpcdTest = new VPCDWorker(hostname, p, scReader, handler); - new Thread(vpcdTest).start(); + SharedPreferences SP = PreferenceManager.getDefaultSharedPreferences(this); + int port = Integer.parseInt(SP.getString("port", Integer.toString(VPCDWorker.DEFAULT_PORT))); + String hostname = SP.getString("hostname", VPCDWorker.DEFAULT_HOSTNAME); + vpcdTest = new VPCDWorker(); + vpcdTest.execute(new VPCDWorker.VPCDWorkerParams(hostname, port, scReader)); } private void vpcdDisconnect() { if (vpcdTest != null) { - vpcdTest.close(); + vpcdTest.cancel(true); vpcdTest = null; } } - private void updateLabels() { - if (testing) { - button.setText(getResources().getString(R.string.button_stop_testing)); - } else { - button.setText(getResources().getString(R.string.button_test_configuration)); - spinner.setVisibility(View.INVISIBLE); - } - } - - private void loadSettings() { - Settings settings = new Settings(this); - hostname = settings.getVPCDHost(); - port = settings.getVPCDPort(); - editTextVPCDHost.setText(hostname); - editTextVPCDPort.setText(port); - } - - private void saveSettings() { - Editable textHostname = editTextVPCDHost.getText(); - Editable textPort = editTextVPCDPort.getText(); - if (textHostname != null) { - hostname = textHostname.toString(); - } - if (textPort != null) { - port = textPort.toString(); - } - Settings settings = new Settings(this); - settings.setVPCDSettings(hostname, port); - } - - private void forceConnect(SCReader scReader) { - if (testing) { - testing = false; - vpcdDisconnect(); - } - saveSettings(); - testing = true; - spinner.setVisibility(View.VISIBLE); - vpcdConnect(scReader); - updateLabels(); - } - @Override public void onResume() { super.onResume(); Intent intent = getIntent(); // Check to see that the Activity started due to a discovered tag if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())) { - NFCReader nfcReader = NFCReader.get(intent); + vpcdDisconnect(); + NFCReader nfcReader = NFCReader.get(intent, this); if (nfcReader != null) { - forceConnect(nfcReader); + vpcdConnect(nfcReader); } else { super.onNewIntent(intent); } - } else { - // Check to see that the Activity started due to a configuration URI - if (Intent.ACTION_VIEW.equals(intent.getAction())) { - testing = false; - vpcdDisconnect(); - Uri uri = intent.getData(); - String h = ""; - String p = ""; - if (uri.getScheme().equals(getResources().getString(R.string.scheme_vpcd))) { - h = uri.getHost(); - int _p = uri.getPort(); - if (_p > 0) { - p = Integer.toString(_p); - } - } else { - h = uri.getQueryParameter("host"); - p = uri.getQueryParameter("port"); - } - editTextVPCDHost.setText(h); - editTextVPCDPort.setText(p); - super.onNewIntent(intent); - } } enableReaderMode(); } @@ -362,4 +233,5 @@ public class MainActivity extends Activity implements NfcAdapter.ReaderCallback // onResume gets called after this to handle the intent setIntent(intent); } -} \ No newline at end of file + +} diff --git a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MessageSender.java b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MessageSender.java deleted file mode 100644 index 0ee4b16..0000000 --- a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MessageSender.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2014 Frank Morgner - * - * This file is part of RemoteSmartCardReader. - * - * RemoteSmartCardReader is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * RemoteSmartCardReader is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * RemoteSmartCardReader. If not, see . - */ - -package com.vsmartcard.remotesmartcardreader.app; - -import android.os.Handler; -import android.os.Message; - -class MessageSender { - public static final int MESSAGE_CONNECTED = 0; - public static final int MESSAGE_DISCONNECTED = 1; - public static final int MESSAGE_ERROR = 2; - public static final int MESSAGE_ATR = 3; - public static final int MESSAGE_ON = 4; - public static final int MESSAGE_OFF = 5; - public static final int MESSAGE_RESET = 6; - public static final int MESSAGE_CAPDU = 7; - public static final int MESSAGE_RAPDU = 8; - private final Handler handler; - - public MessageSender(Handler handler) { - this.handler = handler; - } - - private void sendMessage(int messageCode, Object object) { - Message message = handler.obtainMessage(messageCode, object); - handler.sendMessage(message); - } - - public void sendConnected(String details) { - sendMessage(MESSAGE_CONNECTED, details); - } - - public void sendATR(String details) { - sendMessage(MESSAGE_ATR, details); - } - - public void sendCAPDU(String details) { - sendMessage(MESSAGE_CAPDU, details); - } - - public void sendDisconnected(String details) { - sendMessage(MESSAGE_DISCONNECTED, ""); - } - - public void sendError(String details) { - sendMessage(MESSAGE_ERROR, details); - } - - public void sendOff() { - sendMessage(MESSAGE_OFF, ""); - } - - public void sendOn() { - sendMessage(MESSAGE_ON, ""); - } - - public void sendRAPDU(String details) { - sendMessage(MESSAGE_RAPDU, details); - } - - public void sendReset() { - sendMessage(MESSAGE_RESET, ""); - } -} diff --git a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MyLogFragment.java b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MyLogFragment.java new file mode 100644 index 0000000..43a5222 --- /dev/null +++ b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MyLogFragment.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2014 Frank Morgner + * + * This file is part of RemoteSmartCardReader. + * + * RemoteSmartCardReader is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * RemoteSmartCardReader is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * RemoteSmartCardReader. If not, see . + */ + +package com.vsmartcard.remotesmartcardreader.app; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.Bundle; +import android.support.design.widget.Snackbar; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import com.example.android.common.logger.LogFragment; + +public class MyLogFragment extends LogFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + // TODO Add your menu entries here + getActivity().getMenuInflater().inflate(R.menu.menu_log, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle presses on the action bar items + switch (item.getItemId()) { + case R.id.action_copy: + // Code to Copy the content of Text View to the Clip board. + ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("simple text", getLogView().getText()); + clipboard.setPrimaryClip(clip); + Snackbar.make(getView(), "Log copied to clipboard.", Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + return true; + case R.id.action_delete: + getLogView().setText(""); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (savedInstanceState != null) { + getLogView().setText(savedInstanceState.getCharSequence("logView")); + } + } + + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + savedInstanceState.putCharSequence("logView", getLogView().getText()); + super.onSaveInstanceState(savedInstanceState); + } +} diff --git a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/Settings.java b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/Settings.java deleted file mode 100644 index 6dc1147..0000000 --- a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/Settings.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2014 Frank Morgner - * - * This file is part of RemoteSmartCardReader. - * - * RemoteSmartCardReader is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * RemoteSmartCardReader is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * RemoteSmartCardReader. If not, see . - */ - -package com.vsmartcard.remotesmartcardreader.app; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.preference.PreferenceManager; - -class Settings { - - private static final String KEY_VPCD_HOST = "hostname"; - private static final String KEY_VPCD_PORT = "port"; - - private static final String DEFAULT_VPCD_HOST = "10.0.2.2"; - private static final String DEFAULT_VPCD_PORT = "35963"; - - private final SharedPreferences settings; - - public Settings (Context activity) { - settings = PreferenceManager.getDefaultSharedPreferences(activity); - } - - private void setString(String key, String value) { - Editor editor = settings.edit(); - editor.putString(key, value); - editor.apply(); - } - -// private void setBoolean(String key, boolean value) { -// Editor editor = settings.edit(); -// editor.putBoolean(key, value); -// editor.apply(); -// } - - public void setVPCDSettings(String hostname, String port) { - Editor editor = settings.edit(); - editor.putString(KEY_VPCD_HOST, hostname); - editor.putString(KEY_VPCD_PORT, port); - editor.apply(); - } - - public String getVPCDHost() { - return settings.getString(KEY_VPCD_HOST, DEFAULT_VPCD_HOST); - } - - public String getVPCDPort() { - return settings.getString(KEY_VPCD_PORT, DEFAULT_VPCD_PORT); - } -} diff --git a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/SettingsActivity.java b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/SettingsActivity.java new file mode 100644 index 0000000..f2eba45 --- /dev/null +++ b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/SettingsActivity.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2014 Frank Morgner + * + * This file is part of RemoteSmartCardReader. + * + * RemoteSmartCardReader is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * RemoteSmartCardReader is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * RemoteSmartCardReader. If not, see . + */ + +package com.vsmartcard.remotesmartcardreader.app; + + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.provider.Settings; +import android.support.design.widget.Snackbar; +import android.support.v7.app.ActionBar; +import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; +import android.view.MenuItem; +import com.google.zxing.integration.android.IntentIntegrator; +import com.google.zxing.integration.android.IntentResult; + +/** + * A {@link PreferenceActivity} that presents a set of application settings. On + * handset devices, settings are presented as a single list. On tablets, + * settings are split by category, with category headers shown to the left of + * the list of settings. + *

+ * See + * Android Design: Settings for design guidelines and the Settings + * API Guide for more information on developing a Settings UI. + */ +public class SettingsActivity extends AppCompatPreferenceActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setupActionBar(); + + // Display the fragment as the main content. + getFragmentManager().beginTransaction().replace(android.R.id.content, + new VPCDPreferenceFragment()).commit(); + } + + /** + * Set up the {@link android.app.ActionBar}, if the API is available. + */ + private void setupActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + // Show the Up button in the action bar. + actionBar.setDisplayHomeAsUpEnabled(true); + } + } + + + /** {@inheritDoc} */ + @Override + public boolean onIsMultiPane() { + return isXLargeTablet(this); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + /** + * Helper method to determine if the device has an extra-large screen. For + * example, 10" tablets are extra-large. + */ + private static boolean isXLargeTablet(Context context) { + return (context.getResources().getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE; + } + + /** + * A settings value change listener that updates the settings's summary + * to reflect its new value. + */ + private static final Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object value) { + String stringValue = value.toString(); + + if (preference instanceof ListPreference) { + // For list preferences, look up the correct display value in + // the settings's 'entries' list. + ListPreference listPreference = (ListPreference) preference; + int index = listPreference.findIndexOfValue(stringValue); + + // Set the summary to reflect the new value. + preference.setSummary( + index >= 0 + ? listPreference.getEntries()[index] + : null); + } else { + // For all other preferences, set the summary to the value's + // simple string representation. + preference.setSummary(stringValue); + } + return true; + } + }; + + /** + * Binds a settings's summary to its value. More specifically, when the + * settings's value is changed, its summary (line of text below the + * settings title) is updated to reflect the value. The summary is also + * immediately updated upon calling this method. The exact display format is + * dependent on the type of settings. + * + * @see #sBindPreferenceSummaryToValueListener + */ + private static void bindPreferenceSummaryToValue(Preference preference) { + // Set the listener to watch for value changes. + preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener); + + // Trigger the listener immediately with the settings's + // current value. + sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, + PreferenceManager + .getDefaultSharedPreferences(preference.getContext()) + .getString(preference.getKey(), "")); + } + + /** + * This fragment shows data and sync preferences only. It is used when the + * activity is showing a two-pane settings UI. + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class VPCDPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings); + setHasOptionsMenu(true); + + // Bind the summaries of EditText/List/Dialog/Ringtone preferences + // to their values. When their values change, their summaries are + // updated to reflect the new value, per the Android Design + // guidelines. + bindPreferenceSummaryToValue(findPreference("hostname")); + bindPreferenceSummaryToValue(findPreference("port")); + bindPreferenceSummaryToValue(findPreference("delay")); + + Preference nfcSettings = findPreference("nfcSettings"); + nfcSettings.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference preference) { + Intent viewIntent = new Intent(Settings.ACTION_NFC_SETTINGS); + startActivity(viewIntent); + return true; + } + }); + + Preference scan = findPreference("scan"); + scan.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference preference) { + new IntentIntegrator(getActivity()).initiateScan(); + return true; + } + }); + } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + startActivity(new Intent(getActivity(), SettingsActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } + + } + + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); + switch(requestCode) { + case IntentIntegrator.REQUEST_CODE: + if (resultCode != RESULT_CANCELED) { + handleScannedURI(Uri.parse(scanResult.getContents())); + } + break; + } + } + + private void handleScannedURI(Uri uri) { + try { + String h, p; + h = uri.getHost(); + int _p = uri.getPort(); + if (_p < 0) { + _p = VPCDWorker.DEFAULT_PORT; + } + p = Integer.toString(_p); + SharedPreferences SP = PreferenceManager.getDefaultSharedPreferences(this); + SP.edit().putString("hostname", h).apply(); + SP.edit().putString("port", p).apply(); + } catch (Exception e) { + Snackbar.make(this.getCurrentFocus(), "Could not import configuration", Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + } + } + + @Override + public void onResume() { + super.onResume(); + Intent intent = getIntent(); + // Check to see that the Activity started due to a configuration URI + if (Intent.ACTION_VIEW.equals(intent.getAction())) { + Uri uri = intent.getData(); + handleScannedURI(uri); + super.onNewIntent(intent); + } + } + + @Override + public void onNewIntent(Intent intent) { + // onResume gets called after this to handle the intent + setIntent(intent); + } +} diff --git a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/VPCDWorker.java b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/VPCDWorker.java index 5d1759c..63580f5 100644 --- a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/VPCDWorker.java +++ b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/VPCDWorker.java @@ -19,39 +19,46 @@ package com.vsmartcard.remotesmartcardreader.app; -import android.os.Handler; +import android.os.AsyncTask; +import android.support.annotation.Nullable; +import com.example.android.common.logger.Log; import com.vsmartcard.remotesmartcardreader.app.screaders.SCReader; -import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; -class VPCDWorker implements Runnable, Closeable { +class VPCDWorker extends AsyncTask { + + public static class VPCDWorkerParams { + final String hostname; + final int port; + final SCReader reader; + VPCDWorkerParams(String hostname, int port, SCReader reader) { + this.hostname = hostname; + this.port = port; + this.reader = reader; + } + } + public static final int DEFAULT_PORT = 35963; - private final int port; - private final String hostname; - private final SCReader reader; + // default URI when used in emulator + public static final String DEFAULT_HOSTNAME = "10.0.2.2"; + + private SCReader reader; private Socket socket; private InputStream inputStream; private OutputStream outputStream; - private boolean doRun; - private final MessageSender messageSender; - public VPCDWorker(String hostname, int port, SCReader reader, Handler handler) { - this.hostname = hostname; - this.port = port; - this.reader = reader; - this.messageSender = new MessageSender(handler); - } - - public void close() { - doRun = false; + @Override + protected void onCancelled () { try { - vpcdDisconnect(); + if (socket != null) + // interrupt all blocking socket communication + socket.close(); } catch (IOException e) { e.printStackTrace(); } @@ -64,36 +71,32 @@ class VPCDWorker implements Runnable, Closeable { private static final byte VPCD_CTRL_ATR = 4; @Override - public void run() { - android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND); - doRun = true; - + public Void doInBackground(VPCDWorkerParams... params) { try { - vpcdConnect(); - messageSender.sendConnected(hostname+":"+Integer.toString(port)); + reader = params[0].reader; + Log.i(this.getClass().getName(), "Connecting to " + params[0].hostname + ":" + Integer.toString(params[0].port) + "..."); + vpcdConnect(params[0].hostname, params[0].port); + Log.i(this.getClass().getName(), "Connected to VPCD"); - while (doRun) { - byte[] in = receiveFromVPCD(); - if (in == null) { - doRun = false; - break; - } + while (!isCancelled()) { byte[] out = null; + byte[] in = receiveFromVPCD(); + if (in == null) + break; if (in.length == VPCD_CTRL_LEN) { switch (in[0]) { case VPCD_CTRL_OFF: reader.powerOff(); - messageSender.sendOff(); + Log.i(this.getClass().getName(), "Powered down the card (cold reset)"); break; case VPCD_CTRL_ON: reader.powerOn(); - messageSender.sendOn(); - messageSender.sendATR(Hex.getHexString(reader.getATR())); + Log.i(this.getClass().getName(), "Powered up the card with ATR " + Hex.getHexString(reader.getATR())); break; case VPCD_CTRL_RESET: reader.reset(); - messageSender.sendReset(); + Log.i(this.getClass().getName(), "Resetted the card (warm reset)"); break; case VPCD_CTRL_ATR: out = reader.getATR(); @@ -102,22 +105,29 @@ class VPCDWorker implements Runnable, Closeable { throw new IOException("Unhandled command from VPCD."); } } else { - messageSender.sendCAPDU(Hex.getHexString(in)); + Log.i(this.getClass().getName(), "C-APDU: " + Hex.getHexString(in)); out = reader.transmit(in); - messageSender.sendRAPDU(Hex.getHexString(out)); + Log.i(this.getClass().getName(), "R-APDU: " + Hex.getHexString(out)); } if (out != null) { sendToVPCD(out); } } } catch (Exception e) { - if (doRun) { + if (!isCancelled()) { e.printStackTrace(); - messageSender.sendError(e.getMessage()); + Log.i(this.getClass().getName(), "ERROR: " + e.getMessage()); } } + try { + vpcdDisconnect(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; } + @Nullable private byte[] receiveFromVPCD() throws IOException { /* convert length from network byte order. Note that Java always uses network byte order internally. */ @@ -158,7 +168,7 @@ class VPCDWorker implements Runnable, Closeable { outputStream.flush(); } - private void vpcdConnect() throws IOException { + private void vpcdConnect(String hostname, int port) throws IOException { socket = new Socket(InetAddress.getByName(hostname), port); outputStream = socket.getOutputStream(); inputStream = socket.getInputStream(); @@ -170,7 +180,7 @@ class VPCDWorker implements Runnable, Closeable { } if (socket != null) { socket.close(); - messageSender.sendDisconnected(""); + Log.i(this.getClass().getName(), "Disconnected from VPCD"); } } -} +} \ No newline at end of file diff --git a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/screaders/NFCReader.java b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/screaders/NFCReader.java index 40310a9..de1a021 100644 --- a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/screaders/NFCReader.java +++ b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/screaders/NFCReader.java @@ -19,12 +19,16 @@ package com.vsmartcard.remotesmartcardreader.app.screaders; +import android.app.Activity; import android.content.Intent; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.tech.IsoDep; import android.os.Build; +import android.os.Handler; +import android.os.Looper; import android.util.Log; +import android.view.WindowManager; import com.vsmartcard.remotesmartcardreader.app.Hex; @@ -33,19 +37,51 @@ import java.io.IOException; public class NFCReader implements SCReader { private final IsoDep card; + private Activity activity; - private NFCReader(IsoDep sc) throws IOException { + private NFCReader(IsoDep sc, Activity activity) throws IOException { this.card = sc; sc.connect(); card.setTimeout(TIMEOUT); + this.activity = activity; + avoidScreenTimeout(); + } + + private void avoidScreenTimeout() { + if (activity != null) { + final Runnable run = new Runnable() { + public void run() { + // avoid tag loss due to screen timeout + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + Log.i(this.getClass().getName(), "Disabled screen timeout"); + } + }; + final Handler handler = new Handler(Looper.getMainLooper()); + handler.post(run); + } + } + + private void resetScreenTimeout() { + if (activity != null) { + final Runnable run = new Runnable() { + public void run() { + // reset screen properties + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + Log.i(this.getClass().getName(), "Activated screen timeout"); + } + }; + final Handler handler = new Handler(Looper.getMainLooper()); + handler.post(run); + } } @Override public void eject() throws IOException { card.close(); + resetScreenTimeout(); } - public static final int TIMEOUT = 2500; + public static final int TIMEOUT = 500; @Override public void powerOn() { /* should already be connected... */ @@ -72,7 +108,12 @@ public class NFCReader implements SCReader { /* calculation based on https://code.google.com/p/ifdnfc/source/browse/src/atr.c */ @Override public byte[] getATR() { + // get historical bytes for 14443-A byte[] historicalBytes = card.getHistoricalBytes(); + if (historicalBytes == null) { + // get historical bytes for 14443-B + historicalBytes = card.getHiLayerResponse(); + } if (historicalBytes == null) { historicalBytes = new byte[0]; } @@ -100,7 +141,7 @@ public class NFCReader implements SCReader { return card.transceive(apdu); } - public static NFCReader get(Intent intent) { + public static NFCReader get(Intent intent, Activity activity) { NFCReader nfcReader = null; if (intent.getExtras() != null) { @@ -114,7 +155,7 @@ public class NFCReader implements SCReader { e.printStackTrace(); } } else { - nfcReader = NFCReader.get(tag); + nfcReader = NFCReader.get(tag, activity); } intent.removeExtra(NfcAdapter.EXTRA_TAG); } @@ -123,13 +164,13 @@ public class NFCReader implements SCReader { return nfcReader; } - public static NFCReader get(Tag tag) { + public static NFCReader get(Tag tag, Activity activity) { NFCReader nfcReader = null; try { - nfcReader = new NFCReader(IsoDep.get(tag)); + nfcReader = new NFCReader(IsoDep.get(tag), activity); } catch (IOException e) { e.printStackTrace(); } return nfcReader; } -} +} \ No newline at end of file diff --git a/remote-reader/app/src/main/res/drawable-hdpi/ic_action_copy.png b/remote-reader/app/src/main/res/drawable-hdpi/ic_action_copy.png deleted file mode 100644 index f97df1d51e42e97e1c289984f0c20f76da0f9cfc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 375 zcmV--0f_#IP)+7(pa7{!8lV7Efd1n#!w17agph4ZAX;2(hr+_;o34R_ilq@F-JJ86h6K0eI3^<0I3M--iR6P#{Fe3 z?zAKVBHRfsq|Sw;xf3J=0!U)d;3RC;hr4CloQo#F_*fPnBR&E=!>`^xXHyayL(ar2 z4HB}ofrMg8$eSCjB_Sg=0$Z057BWa^f`pFzxXwA;GwCuKf%e2ySY)#2a-PwpNgS^g zLrJJ=Zxe7H@D+if8EwLzsUXnajFkhqAc6pnls$Js1pbZyEs%`ppML-VD8W~N0RU|A VUEvzg=U@N;002ovPDHLkV1m9pl$8Jg diff --git a/remote-reader/app/src/main/res/drawable-hdpi/ic_action_delete.png b/remote-reader/app/src/main/res/drawable-hdpi/ic_action_delete.png deleted file mode 100755 index 572df98ef958f1bce85b9d94a172dd025025d0df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3053 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 YKq%ZI3Qld3r>g){Lo0(Sm}|uU0Qe=hzyJUM diff --git a/remote-reader/app/src/main/res/drawable-xhdpi/ic_action_copy.png b/remote-reader/app/src/main/res/drawable-xhdpi/ic_action_copy.png deleted file mode 100644 index a6e1aa290b94f5516dc596ec2d0186450ea99a5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 351 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7#TfX978H@y_tQRx7mQl)$mmQ^%pE}jxd}#$Z&V1&H*V| z1&bgyg`X?8Zqf=eMX)acNxk{8F`mzK4;DPP^Y*cg=y3GmuFfmlVeuT7GA@-BF}FDZxTc7 zf_EbOt<(dUBpMijXv@LbOLVn+PMz+&X6fwE&GKUI-N$LIJPOSJu1k3~F&Hs0-8$d! zp(ei-#Js`nz#z%M_=a`OcSbdqmgk>#-hVimF@=FmB$(-_V$EvCkJH7aRT~(2oIHz} tSQ3EZ+J@`CKl>NZ2=s#K8s`Q^hWlmikq`bf<^w~F!PC{xWt~$(696_Th8F+; diff --git a/remote-reader/app/src/main/res/drawable-xhdpi/ic_action_delete.png b/remote-reader/app/src/main/res/drawable-xhdpi/ic_action_delete.png deleted file mode 100755 index ead332270d6cbb5f44fd00a833236dddcd77c8eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3128 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 - - - - - - - -