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
+ * 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