Android Open Source - smartcard-reader Reader Activity






From Project

Back to project page smartcard-reader.

License

The source code is released under:

GNU General Public License

If you think the Android project smartcard-reader listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright 2014 Ryan Jones/*from  ww w .ja v a2s. c  o  m*/
 *
 * This file is part of smartcard-reader, package org.docrj.smartcard.reader.
 *
 * smartcard-reader 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.
 *
 * smartcard-reader 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 smartcard-reader. If not, see <http://www.gnu.org/licenses/>.
 */

package org.docrj.smartcard.reader;

import java.util.ArrayList;

import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.PorterDuff;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.ReaderCallback;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
import android.text.Html;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.ShareActionProvider;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import android.widget.Toast;

import org.docrj.smartcard.reader.R;

public class ReaderActivity extends Activity implements ReaderXcvr.UiCallbacks,
    MessageAdapter.OnDialog, ReaderCallback {

    protected static final String TAG = "smartcard-reader";

    private final static String SOURCE_LINK =
        "<a href=\"https://github.com/doc-rj/smartcard-reader\">source & licensing</a>";

    // HCE demo smartcard app
    private final static String DEMO_NAME = "Demo";
    // proprietary unregistered AID starts with 0xFx, length 5 - 16 bytes
    private final static String DEMO_AID = "F0646F632D726A";

    // update all five items below when adding/removing default apps!
    private final static int NUM_RO_APPS = 11;
    private final static int DEFAULT_APP_POS = 0; // Demo
    private final static String APP_NAMES = DEMO_NAME
            + "|Amex|Amex 7-Byte|Amex 8-Byte" + "|MC|MC 8-Byte"
            + "|Visa|Visa 8-Byte" + "|Discover Zip" + "|Test Pay|Test Other";
    private final static String APP_AIDS = DEMO_AID
            + "|A00000002501|A0000000250109|A000000025010988"
            + "|A0000000041010|A000000004101088"
            + "|A0000000031010|A000000003101088"
            + "|A0000003241010|F07465737420414944|F07465737420414944";
    private final static String APP_TYPES = "1|0|0|0|0|0|0|0|0|0|1";

    private final static int DIALOG_NEW_APP = 1;
    private final static int DIALOG_COPY_LIST = 2;
    private final static int DIALOG_COPY_APP = 3;
    private final static int DIALOG_EDIT_APP = 4;
    private final static int DIALOG_EDIT_ALL_APPS = 5;
    private final static int DIALOG_ENABLE_NFC = 6;
    private final static int DIALOG_PARSED_MSG = 7;
    private final static int DIALOG_ABOUT = 8;

    public final static int TEST_MODE_AID_ROUTE = 0;
    public final static int TEST_MODE_EMV_READ = 1;

    private int mTestMode = TEST_MODE_AID_ROUTE;
    private Handler mHandler;
    private Editor mEditor;
    private MenuItem mEditMenuItem;
    private MenuItem mManualMenuItem;
    private ImageButton mManualButton;
    private NfcAdapter mNfcAdapter;
    private ListView mMsgListView;
    private ListView mEditAllListView;
    private AppAdapter mAppAdapter;
    private AppAdapter mEditAllAdapter;
    private MessageAdapter mMsgAdapter;
    private Button mSelectButton;
    private View mSelectSplitter;

    private int mMsgPos;
    private boolean mSkipNextClear;
    private boolean mManual;   

    private ShareActionProvider mShareProvider;
    private int mSelectedAppPos = DEFAULT_APP_POS;
    private ArrayList<SmartcardApp> mApps;
    private boolean mSelectOnCreate;
    private TextView mIntro;
    private View mSplitter;
    private Spinner mAidSpinner;
    private ActionBar mActionBar;
    private AlertDialog mNewDialog;
    private AlertDialog mCopyListDialog;
    private AlertDialog mCopyDialog;
    private AlertDialog mEditDialog;
    private AlertDialog mEditAllDialog;
    private AlertDialog mEnableNfcDialog;
    private AlertDialog mParsedMsgDialog;
    private AlertDialog mAboutDialog;
    private String mParsedMsgName = "";
    private String mParsedMsgText = "";
    private int mCopyPos;
    private int mEditPos;
    private String mVersionName = "?";
    private String mLogPath = "?";
    private Spanned mSourceLink = Html.fromHtml(SOURCE_LINK);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
        mActionBar = getActionBar();
        //View titleView = getLayoutInflater().inflate(R.layout.app_title, null);
        //mActionBar.setCustomView(titleView);
        mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM
                | ActionBar.DISPLAY_SHOW_HOME);

        SpinnerAdapter sAdapter = ArrayAdapter.createFromResource(this,
                R.array.test_modes, R.layout.spinner_dropdown_item_2);
        mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
        mActionBar.setListNavigationCallbacks(sAdapter, new ActionBar.OnNavigationListener() {
            String[] strings = getResources().getStringArray(R.array.test_modes);

            @Override
            public boolean onNavigationItemSelected(int position, long itemId) {
                int testMode = strings[position].equals(getString(R.string.aid_route)) ?
                        TEST_MODE_AID_ROUTE : TEST_MODE_EMV_READ;
                if (mTestMode != testMode) {
                    prepareViewForTestMode(testMode);
                    clearMessages();
                    mTestMode = testMode;
                }
                return true;
            }
        });

        mActionBar.show();

        setContentView(R.layout.activity_reader);
        mIntro = (TextView) findViewById(R.id.intro);
        mSplitter = findViewById(R.id.splitter);
        mSelectButton = (Button) findViewById(R.id.manualSelectButton);
        mSelectSplitter = findViewById(R.id.splitter2);
        mSelectButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onError(getString(R.string.manual_disconnected), false);
            }
        });

        mMsgListView = (ListView) findViewById(R.id.msgListView);
        mMsgAdapter = new MessageAdapter(getLayoutInflater(),
                savedInstanceState, this);
        mMsgListView.setAdapter(mMsgAdapter);
        if (savedInstanceState != null) {
            mManual = savedInstanceState.getBoolean("manual");
            mMsgPos = savedInstanceState.getInt("msg_pos");
            mParsedMsgName = savedInstanceState.getString("parsed_msg_name");
            mParsedMsgText = savedInstanceState.getString("parsed_msg_text");
        }

        mHandler = new Handler();
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);

        ApduParser.init(this);

        SharedPreferences ss = getSharedPreferences("prefs",
                Context.MODE_PRIVATE);
        mEditor = ss.edit();
        String appNames = ss.getString("app_names", null);

        // if shared prefs is empty, synchronously write defaults
        if (appNames == null) {
            writePrefs();
            appNames = ss.getString("app_names", null);
        }

        String appAids = ss.getString("app_aids", null);
        String[] names = appNames.split("\\|");
        String[] aids = appAids.split("\\|");

        String appTypes = ss.getString("app_types", null);
        String[] typeStrs = appTypes.split("\\|");

        int[] types = new int[typeStrs.length];
        for (int i = 0; i < typeStrs.length; i++) {
            types[i] = Integer.valueOf(typeStrs[i]);
        }

        mApps = new ArrayList<SmartcardApp>();
        for (int i = 0; i < names.length; i++) {
            mApps.add(new SmartcardApp(names[i], aids[i], types[i]));
        }

        mAidSpinner = (Spinner) findViewById(R.id.aid);
        mAppAdapter = new AppAdapter(this, mApps, savedInstanceState, false);
        mAidSpinner.setAdapter(mAppAdapter);
        mAidSpinner
                .setOnItemSelectedListener(new Spinner.OnItemSelectedListener() {
                    @Override
                    public void onItemSelected(AdapterView<?> parent,
                            View view, int pos, long id) {
                        if (!mSelectOnCreate && !mManual) {
                            clearMessages();
                        }
                        mSelectOnCreate = false;
                        mSelectedAppPos = pos;
                        Log.d(TAG, "App: " + mApps.get(pos).getName()
                                + ", AID: " + mApps.get(pos).getAid());
                    }

                    @Override
                    public void onNothingSelected(AdapterView<?> parent) {
                    }
                });

        mSelectOnCreate = true;
        // mSelectedAppPos saved in onPause(), restored in onResume()

        try {
            PackageManager manager = getPackageManager();
            PackageInfo info = manager.getPackageInfo(getPackageName(), 0);
            mVersionName = info.versionName;
        } catch (Exception e) {
            Log.e(TAG, "error getting version");
        }
        
        String path = getExternalFilesDir(null).getPath();
        int idx = path.lastIndexOf("/Android");
        mLogPath = (idx == -1) ? path : "<storage>" + path.substring(idx);
    }

    private void prepareViewForTestMode(int testMode) {
        if (testMode == TEST_MODE_AID_ROUTE) {
            mIntro.setText(mManual ? R.string.intro_aid_route_manual : R.string.intro_aid_route);
            mAidSpinner.setVisibility(View.VISIBLE);
            mSplitter.setVisibility(View.VISIBLE);
        } else {
            mIntro.setText(R.string.intro_emv_read);
            mAidSpinner.setVisibility(View.GONE);
            mSplitter.setVisibility(View.GONE);
        }
    }

    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @SuppressWarnings("deprecation")
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action == null)
                return;
            if (action.equals(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)) {
                int state = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE,
                        NfcAdapter.STATE_ON);
                if (state == NfcAdapter.STATE_ON
                        || state == NfcAdapter.STATE_TURNING_ON) {
                    Log.d(TAG, "state: " + state + " , dialog: "
                            + mEnableNfcDialog);
                    if (mEnableNfcDialog != null) {
                        mEnableNfcDialog.dismiss();
                    }
                    if (state == NfcAdapter.STATE_ON) {
                        //Bundle extras = new Bundle();
                        //extras.putBoolean("bit_transparent_mode", true);
                        mNfcAdapter
                                .enableReaderMode(
                                        ReaderActivity.this,
                                        ReaderActivity.this,
                                        NfcAdapter.FLAG_READER_NFC_A
                                                //NfcAdapter.FLAG_READER_NFC_B
                                                //| NfcAdapter.FLAG_READER_NFC_F
                                                //| NfcAdapter.FLAG_READER_NFC_V
                                                //| NfcAdapter.FLAG_READER_NFC_BARCODE
                                                //| NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS
                                                | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
                                        null);
                                        //extras);
                    }
                } else {
                    if (mEnableNfcDialog == null
                            || !mEnableNfcDialog.isShowing()) {
                        showDialog(DIALOG_ENABLE_NFC);
                    }
                }
            }
        }
    };

    @SuppressWarnings("deprecation")
    @Override
    public void onResume() {
        super.onResume();
        // restore test mode and selected pos from prefs
        SharedPreferences ss = getSharedPreferences("prefs",
                Context.MODE_PRIVATE);
        mTestMode = ss.getInt("test_mode", mTestMode);
        mActionBar.setSelectedNavigationItem(mTestMode);
        prepareViewForTestMode(mTestMode);

        mSelectedAppPos = ss.getInt("selected_aid_pos", mSelectedAppPos);
        mAidSpinner.setSelection(mSelectedAppPos);

        // this delay is a bit hacky; would be better to extend ListView
        // and override onLayout()
        mHandler.postDelayed(new Runnable() {
            public void run() {
                mMsgListView.smoothScrollToPosition(mMsgPos);
            }
        }, 50L);

        // register broadcast receiver
        IntentFilter filter = new IntentFilter(
                NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
        registerReceiver(mBroadcastReceiver, filter);

        // prompt to enable NFC if disabled
        if (!mNfcAdapter.isEnabled()) {
            showDialog(DIALOG_ENABLE_NFC);
        }

        // listen for type A tags/smartcards, skipping ndef check
        mNfcAdapter.enableReaderMode(this, this, NfcAdapter.FLAG_READER_NFC_A
                | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null);
    }

    @Override
    public void onPause() {
        super.onPause();
        // save selected pos to prefs
        writePrefs();
        // unregister broadcast receiver
        unregisterReceiver(mBroadcastReceiver);
        // disable reader mode
        mNfcAdapter.disableReaderMode(this);
    }

    @Override
    public void onStop() {
        super.onStop();
        if (mNewDialog != null) {
            mNewDialog.dismiss();
        }
        if (mCopyDialog != null) {
            mCopyDialog.dismiss();
        }
        if (mCopyListDialog != null) {
            mCopyListDialog.dismiss();
        }
        if (mEditDialog != null) {
            mEditDialog.dismiss();
        }
        if (mEditAllDialog != null) {
            mEditAllDialog.dismiss();
        }
        if (mEnableNfcDialog != null) {
            mEnableNfcDialog.dismiss();
        }
        if (mParsedMsgDialog != null) {
            mParsedMsgDialog.dismiss();
        }
        if (mAboutDialog != null) {
            mAboutDialog.dismiss();
        }
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        clearShareMsgsIntent();        
    }

    @Override
    protected void onSaveInstanceState(Bundle outstate) {
        Log.d(TAG, "saving instance state!");
        outstate.putBoolean("manual", mManual);
        // console message i/o list
        outstate.putInt("msg_pos", mMsgListView.getLastVisiblePosition());
        outstate.putString("parsed_msg_name", mParsedMsgName);
        outstate.putString("parsed_msg_text", mParsedMsgText);
        if (mMsgAdapter != null) {
            mMsgAdapter.onSaveInstanceState(outstate);
        }
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        AlertDialog.Builder builder = new AlertDialog.Builder(
                ReaderActivity.this, R.style.dialog);
        final LayoutInflater li = getLayoutInflater();
        Dialog dialog = null;
        switch (id) {
        case DIALOG_NEW_APP: {
            final View view = li.inflate(R.layout.dialog_new_app, null);
            builder.setView(view)
                    .setCancelable(false)
                    .setIcon(R.drawable.ic_action_new)
                    .setTitle(R.string.smartcard_app)
                    .setPositiveButton(R.string.dialog_ok, null)
                    .setNegativeButton(R.string.dialog_cancel,
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog,
                                        int id) {
                                    dialog.cancel();
                                }
                            });

            mNewDialog = builder.create();
            dialog = mNewDialog;
            dialog.setOnShowListener(new DialogInterface.OnShowListener() {
                @Override
                public void onShow(DialogInterface di) {
                    Button b = mNewDialog
                            .getButton(AlertDialog.BUTTON_POSITIVE);
                    b.setOnClickListener(new View.OnClickListener() {
                        public void onClick(View v) {
                            EditText appName = (EditText) view
                                    .findViewById(R.id.app_name);
                            EditText appAid = (EditText) view
                                    .findViewById(R.id.app_aid);

                            // validate name and aid
                            String name = appName.getText().toString();
                            String aid = appAid.getText().toString();
                            if (name.isEmpty()) {
                                showToast(getString(R.string.empty_name));
                                return;
                            }
                            if (aid.length() < 10 || aid.length() > 32
                                    || aid.length() % 2 != 0) {
                                showToast(getString(R.string.invalid_aid));
                                return;
                            }
                            // ensure name is unique (aid can be dup)
                            for (SmartcardApp app : mApps) {
                                if (app.getName().equals(name)) {
                                    showToast(getString(R.string.name_exists,
                                            name));
                                    return;
                                }
                            }
                            // app type radio group
                            RadioGroup appTypeGrp = (RadioGroup) view
                                    .findViewById(R.id.radio_grp_type);
                            int selectedId = appTypeGrp
                                    .getCheckedRadioButtonId();
                            RadioButton radioBtn = (RadioButton) view
                                    .findViewById(selectedId);
                            int type = radioBtn.getText().toString()
                                    .equals(getString(R.string.radio_payment)) ? SmartcardApp.TYPE_PAYMENT
                                    : SmartcardApp.TYPE_OTHER;

                            // current app checkbox
                            CheckBox cbCurrent = (CheckBox) view
                                    .findViewById(R.id.make_current);
                            if (cbCurrent.isChecked()) {
                                mSelectedAppPos = mApps.size();
                            }

                            // update apps list
                            SmartcardApp newApp = new SmartcardApp(appName
                                    .getText().toString(), appAid.getText()
                                    .toString(), type);
                            Log.d(TAG, "newApp: " + newApp);
                            synchronized (mApps) {
                                mApps.add(newApp);
                                if (mApps.size() == NUM_RO_APPS + 1) {
                                    // enable edit menu item
                                    prepareOptionsMenu();
                                }
                            }

                            mAidSpinner.setAdapter(mAppAdapter);
                            mAidSpinner.setSelection(mSelectedAppPos);
                            mAppAdapter.notifyDataSetChanged();

                            // write apps to shared prefs
                            new writePrefsTask().execute();
                            mNewDialog.dismiss();
                        }
                    });
                }
            });
            break;
        } // case
        case DIALOG_COPY_LIST: {
            final View view = li.inflate(R.layout.dialog_edit_apps, null);
            final ListView listView = (ListView) view
                    .findViewById(R.id.listView);
            listView.setOnItemClickListener(new ListView.OnItemClickListener() {
                @SuppressWarnings("deprecation")
                @Override
                public void onItemClick(AdapterView<?> parent, View view,
                        int pos, long id) {
                    mCopyPos = pos;
                    showDialog(DIALOG_COPY_APP);
                    mCopyListDialog.dismiss();
                }
            });
            listView.setOnItemLongClickListener(new ListView.OnItemLongClickListener() {
                @Override
                public boolean onItemLongClick(AdapterView<?> parent,
                        View view, int pos, long id) {
                    return true;
                }
            });

            builder.setView(view).setCancelable(false)
                    .setIcon(R.drawable.ic_action_copy)
                    .setTitle(R.string.smartcard_app)
                    .setPositiveButton(R.string.dialog_cancel, null);

            mCopyListDialog = builder.create();
            dialog = mCopyListDialog;
            break;
        } // case
        case DIALOG_COPY_APP: {
            final View view = li.inflate(R.layout.dialog_new_app, null);
            builder.setView(view)
                    .setCancelable(false)
                    .setIcon(R.drawable.ic_action_new)
                    .setTitle(R.string.smartcard_app)
                    .setPositiveButton(R.string.dialog_ok, null)
                    .setNegativeButton(R.string.dialog_cancel,
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog,
                                        int id) {
                                    dialog.cancel();
                                }
                            });

            mCopyDialog = builder.create();
            dialog = mCopyDialog;
            dialog.setOnShowListener(new DialogInterface.OnShowListener() {
                @Override
                public void onShow(DialogInterface di) {
                    Button b = mCopyDialog
                            .getButton(AlertDialog.BUTTON_POSITIVE);
                    b.setOnClickListener(new View.OnClickListener() {
                        public void onClick(View v) {
                            EditText appName = (EditText) view
                                    .findViewById(R.id.app_name);
                            EditText appAid = (EditText) view
                                    .findViewById(R.id.app_aid);

                            // validate name and aid
                            String name = appName.getText().toString();
                            String aid = appAid.getText().toString();
                            if (name.isEmpty()) {
                                showToast(getString(R.string.empty_name));
                                return;
                            }
                            if (aid.length() < 10 || aid.length() > 32
                                    || aid.length() % 2 != 0) {
                                showToast(getString(R.string.invalid_aid));
                                return;
                            }
                            // ensure name is unique (aid can be dup)
                            for (SmartcardApp app : mApps) {
                                if (app.getName().equals(name)) {
                                    showToast(getString(R.string.name_exists,
                                            name));
                                    return;
                                }
                            }
                            // app type radio group
                            RadioGroup appTypeGrp = (RadioGroup) view
                                    .findViewById(R.id.radio_grp_type);
                            int selectedId = appTypeGrp
                                    .getCheckedRadioButtonId();
                            RadioButton radioBtn = (RadioButton) view
                                    .findViewById(selectedId);
                            int type = radioBtn.getText().toString()
                                    .equals(getString(R.string.radio_payment)) ? SmartcardApp.TYPE_PAYMENT
                                    : SmartcardApp.TYPE_OTHER;

                            // current app checkbox
                            CheckBox cbCurrent = (CheckBox) view
                                    .findViewById(R.id.make_current);
                            if (cbCurrent.isChecked()) {
                                mSelectedAppPos = mApps.size();
                            }

                            // update apps list
                            SmartcardApp newApp = new SmartcardApp(appName
                                    .getText().toString(), appAid.getText()
                                    .toString(), type);
                            Log.d(TAG, "newApp: " + newApp);
                            synchronized (mApps) {
                                mApps.add(newApp);
                                if (mApps.size() == NUM_RO_APPS + 1) {
                                    // enable edit menu item
                                    prepareOptionsMenu();
                                }
                            }
 
                            mAidSpinner.setAdapter(mAppAdapter);
                            mAidSpinner.setSelection(mSelectedAppPos);
                            mAppAdapter.notifyDataSetChanged();

                            // write apps to shared prefs
                            new writePrefsTask().execute();
                            mCopyDialog.dismiss();
                        }
                    });
                }
            });
            break;
        } // case
        case DIALOG_EDIT_APP: {
            final View view = li.inflate(R.layout.dialog_new_app, null);
            builder.setView(view)
                    .setCancelable(false)
                    .setIcon(R.drawable.ic_action_edit)
                    .setTitle(R.string.smartcard_app)
                    .setPositiveButton(R.string.dialog_ok, null)
                    .setNegativeButton(R.string.dialog_cancel,
                            new DialogInterface.OnClickListener() {
                                @SuppressWarnings("deprecation")
                                public void onClick(DialogInterface dialog,
                                        int id) {
                                    dismissKeyboard(mEditDialog
                                            .getCurrentFocus());
                                    showDialog(DIALOG_EDIT_ALL_APPS);
                                    dialog.cancel();
                                }
                            });

            mEditDialog = builder.create();
            dialog = mEditDialog;

            dialog.setOnShowListener(new DialogInterface.OnShowListener() {
                @Override
                public void onShow(DialogInterface di) {
                    Button b = mEditDialog
                            .getButton(AlertDialog.BUTTON_POSITIVE);
                    b.setOnClickListener(new View.OnClickListener() {
                        @SuppressWarnings("deprecation")
                        public void onClick(View v) {
                            EditText appName = (EditText) view
                                    .findViewById(R.id.app_name);
                            EditText appAid = (EditText) view
                                    .findViewById(R.id.app_aid);

                            // validate name and aid
                            String name = appName.getText().toString();
                            String aid = appAid.getText().toString();
                            if (name.isEmpty()) {
                                showToast(getString(R.string.empty_name));
                                return;
                            }
                            if (aid.length() < 10 || aid.length() > 32
                                    || aid.length() % 2 != 0) {
                                showToast(getString(R.string.invalid_aid));
                                return;
                            }
                            // ensure name is unique
                            for (int i = 0; i < mApps.size(); i++) {
                                // skip the app being edited
                                if (i == mEditPos)
                                    continue;
                                SmartcardApp app = mApps.get(i);
                                if (app.getName().equals(name)) {
                                    showToast(getString(R.string.name_exists,
                                            name));
                                    return;
                                }
                            }
                            // app type radio group
                            RadioGroup appTypeGrp = (RadioGroup) view
                                    .findViewById(R.id.radio_grp_type);
                            int selectedId = appTypeGrp
                                    .getCheckedRadioButtonId();
                            RadioButton radioBtn = (RadioButton) view
                                    .findViewById(selectedId);
                            int type = radioBtn.getText().toString()
                                    .equals(getString(R.string.radio_payment)) ? SmartcardApp.TYPE_PAYMENT
                                    : SmartcardApp.TYPE_OTHER;

                            // current app checkbox
                            CheckBox cbCurrent = (CheckBox) view
                                    .findViewById(R.id.make_current);
                            if (cbCurrent.isChecked()) {
                                mSelectedAppPos = mEditPos;
                            }

                            // update apps list
                            SmartcardApp app;
                            synchronized (mApps) {
                                app = mApps.get(mEditPos);
                                app.setName(name);
                                app.setAid(aid);
                                app.setType(type);
                            }
                            Log.d(TAG, "app: " + app);

                            mAidSpinner.setSelection(mSelectedAppPos);
                            mAppAdapter.notifyDataSetChanged();

                            SmartcardApp subApp = mEditAllAdapter
                                    .getItem(mEditPos - NUM_RO_APPS);
                            subApp.copy(app);
                            mEditAllAdapter.notifyDataSetChanged();
                            mEditAllListView.setAdapter(mEditAllAdapter);

                            // write shared prefs in another thread
                            new writePrefsTask().execute();
                            dismissKeyboard(mEditDialog.getCurrentFocus());
                            showDialog(DIALOG_EDIT_ALL_APPS);
                            mEditDialog.dismiss();
                        }
                    });
                }
            });
            break;
        } // case
        case DIALOG_EDIT_ALL_APPS: {
            final View view = li.inflate(R.layout.dialog_edit_apps, null);
            final ListView listView = (ListView) view
                    .findViewById(R.id.listView);
            mEditAllListView = listView;
            listView.setOnItemClickListener(new ListView.OnItemClickListener() {
                @SuppressWarnings("deprecation")
                @Override
                public void onItemClick(AdapterView<?> parent, View view,
                        int pos, long id) {
                    mEditPos = NUM_RO_APPS + pos;
                    showDialog(DIALOG_EDIT_APP);
                    mEditAllDialog.dismiss();
                }
            });
            listView.setOnItemLongClickListener(new ListView.OnItemLongClickListener() {
                @Override
                public boolean onItemLongClick(AdapterView<?> parent,
                        View view, int pos, long id) {
                    // TODO: confirmation dialog or discard icon?
                    mApps.remove(NUM_RO_APPS + pos);
                    if (mSelectedAppPos == NUM_RO_APPS + pos) {
                        mSelectedAppPos = 0;
                    } else if (mSelectedAppPos > NUM_RO_APPS + pos) {
                        mSelectedAppPos--;
                    }

                    mAidSpinner.setAdapter(mAppAdapter);
                    mAidSpinner.setSelection(mSelectedAppPos);
                    mAppAdapter.notifyDataSetChanged();

                    SmartcardApp app = mEditAllAdapter.getItem(pos);
                    mEditAllAdapter.remove(app);
                    mEditAllAdapter.notifyDataSetChanged();
                    listView.setAdapter(mEditAllAdapter);

                    new writePrefsTask().execute();
                    if (mApps.size() == NUM_RO_APPS) {
                        parent.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
                        mEditAllDialog.dismiss();
                        // disable edit menu item
                        prepareOptionsMenu();
                    }
                    return true;
                }
            });

            builder.setView(view).setCancelable(false)
                    .setIcon(R.drawable.ic_action_edit)
                    .setTitle(R.string.smartcard_app)
                    .setPositiveButton(R.string.dialog_done, null);

            mEditAllDialog = builder.create();
            dialog = mEditAllDialog;
            break;
        } // case
        case DIALOG_ENABLE_NFC: {
            final View view = li.inflate(R.layout.dialog_enable_nfc, null);
            builder.setView(view)
                    .setCancelable(false)
                    .setIcon(R.drawable.ic_enable_nfc)
                    .setTitle(R.string.nfc_disabled)
                    .setPositiveButton(R.string.dialog_ok,
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog,
                                        int id) {
                                    // take user to wireless settings
                                    startActivity(new Intent(
                                            Settings.ACTION_WIRELESS_SETTINGS));
                                }
                            })
                    .setNegativeButton(R.string.dialog_quit,
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog,
                                        int id) {
                                    dialog.cancel();
                                    finish();
                                }
                            });

            mEnableNfcDialog = builder.create();
            dialog = mEnableNfcDialog;
            break;
        } // case
        case DIALOG_PARSED_MSG: {
            // TODO: better icon?
            final View view = li.inflate(R.layout.dialog_parsed_msg, null);
            builder.setView(view)
                    .setCancelable(false)
                    .setIcon(R.drawable.ic_action_search)
                    .setTitle(R.string.parsed_msg)
                    .setPositiveButton(R.string.dialog_dismiss, null);

            mParsedMsgDialog = builder.create();
            dialog = mParsedMsgDialog;
            break;
        }
        case DIALOG_ABOUT: {
            final View view = li.inflate(R.layout.dialog_about, null);
            TextView version = (TextView) view.findViewById(R.id.dialog_version);
            TextView source = (TextView) view.findViewById(R.id.dialog_source);
            TextView path = (TextView) view.findViewById(R.id.dialog_log_path);

            version.setText(getString(R.string.about_version, mVersionName));
            source.setText(mSourceLink);
            source.setMovementMethod(LinkMovementMethod.getInstance());
            path.setText(mLogPath);

            builder.setView(view)
                    .setCancelable(false)
                    .setIcon(R.drawable.ic_action_about)
                    .setTitle(getString(R.string.about_title, getString(R.string.app_name)))
                    .setPositiveButton(R.string.dialog_dismiss, null);

            mAboutDialog = builder.create();
            dialog = mAboutDialog;
            break;
        }
        } // switch
        return dialog;
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
        switch (id) {
        case DIALOG_NEW_APP: {
            EditText name = (EditText) dialog.findViewById(R.id.app_name);
            EditText aid = (EditText) dialog.findViewById(R.id.app_aid);
            RadioGroup type = (RadioGroup) dialog
                    .findViewById(R.id.radio_grp_type);
            CheckBox current = (CheckBox) dialog
                    .findViewById(R.id.make_current);

            name.setText("");
            name.requestFocus();
            aid.setText("");
            type.check(R.id.radio_payment);
            current.setChecked(false);
            break;
        }
        case DIALOG_COPY_LIST: {
            ListView listView = (ListView) dialog.findViewById(R.id.listView);
            TextView textView = (TextView) dialog.findViewById(R.id.text1);

            textView.setText(R.string.tap_to_copy);
            AppAdapter copyListAdapter = new AppAdapter(this, mApps, null, true);
            listView.setAdapter(copyListAdapter);
            break;
        }
        case DIALOG_COPY_APP: {
            EditText name = (EditText) dialog.findViewById(R.id.app_name);
            EditText aid = (EditText) dialog.findViewById(R.id.app_aid);
            RadioGroup type = (RadioGroup) dialog
                    .findViewById(R.id.radio_grp_type);
            CheckBox current = (CheckBox) dialog
                    .findViewById(R.id.make_current);

            SmartcardApp app = mApps.get(mCopyPos);
            name.setText(app.getName());
            name.requestFocus();
            aid.setText(app.getAid());
            type.check((app.getType() == SmartcardApp.TYPE_OTHER) ? R.id.radio_other
                    : R.id.radio_payment);
            current.setChecked(false);
            break;
        }
        case DIALOG_EDIT_APP: {
            EditText name = (EditText) dialog.findViewById(R.id.app_name);
            EditText aid = (EditText) dialog.findViewById(R.id.app_aid);
            RadioGroup type = (RadioGroup) dialog
                    .findViewById(R.id.radio_grp_type);
            CheckBox current = (CheckBox) dialog
                    .findViewById(R.id.make_current);

            SmartcardApp app = mApps.get(mEditPos);
            name.setText(app.getName());
            name.requestFocus();
            aid.setText(app.getAid());
            type.check((app.getType() == SmartcardApp.TYPE_OTHER) ? R.id.radio_other
                    : R.id.radio_payment);
            current.setChecked(mEditPos == mSelectedAppPos);
            current.setEnabled(mEditPos != mSelectedAppPos);
            break;
        }
        case DIALOG_EDIT_ALL_APPS: {
            ListView listView = (ListView) dialog.findViewById(R.id.listView);
            ArrayList<SmartcardApp> sl = new ArrayList<SmartcardApp>(
                    mApps.subList(NUM_RO_APPS, mApps.size()));
            mEditAllAdapter = new AppAdapter(this, sl, null, true);
            listView.setAdapter(mEditAllAdapter);
            break;
        }
        case DIALOG_ENABLE_NFC: {
            break;
        }
        case DIALOG_PARSED_MSG: {
            TextView tv = (TextView)dialog.findViewById(R.id.dialog_text);
            tv.setText(mParsedMsgText);
            dialog.setTitle(mParsedMsgName);
            break;
        }
        }
    }

    private void dismissKeyboard(View focus) {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(focus.getWindowToken(), 0);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        mEditMenuItem = menu.findItem(R.id.menu_edit_all_apps);
        mManualMenuItem = menu.findItem(R.id.menu_manual);
        LinearLayout layout = (LinearLayout) mManualMenuItem.getActionView();
        mManualButton = (ImageButton) layout.findViewById(R.id.menu_btn);
        mManualButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mManual = !mManual;
                // TODO: look into deprecation
                mManualButton.setBackgroundDrawable(mManual ?
                    getResources().getDrawable(R.drawable.button_bg_selected_states) :
                    getResources().getDrawable(R.drawable.button_bg_unselected_states));
                mSelectSplitter.setVisibility(mManual ? View.VISIBLE : View.GONE);
                mSelectButton.setVisibility(mManual ? View.VISIBLE : View.GONE);
                mIntro.setText(getString(mManual ? R.string.intro_aid_route_manual :
                                                   R.string.intro_aid_route));
                clearMessages();               
            }
        });

        prepareOptionsMenu();

        MenuItem item = menu.findItem(R.id.menu_share_msgs);
        mShareProvider = (ShareActionProvider) item.getActionProvider();
        return true;
    }

    private void prepareOptionsMenu() {
        boolean editEnabled = mApps.size() > NUM_RO_APPS;
        Drawable editIcon = getResources().getDrawable(
                R.drawable.ic_action_edit);
        if (!editEnabled) {
            editIcon.mutate().setColorFilter(Color.LTGRAY,
                    PorterDuff.Mode.SRC_IN);
        }
        mEditMenuItem.setIcon(editIcon);
        mEditMenuItem.setEnabled(editEnabled);
        // look into deprecation
        mManualButton.setBackgroundDrawable(mManual ?
            getResources().getDrawable(R.drawable.button_bg_selected_states) :
            getResources().getDrawable(R.drawable.button_bg_unselected_states));
        mSelectSplitter.setVisibility(mManual ? View.VISIBLE : View.GONE);
        mSelectButton.setVisibility(mManual ? View.VISIBLE : View.GONE);
        mIntro.setText(getString(mManual ? R.string.intro_aid_route_manual :
                                           R.string.intro_aid_route));
    }

    @SuppressWarnings("deprecation")
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_clear_msgs:
            clearMessages();
            return true;

        case R.id.menu_new_app:
            showDialog(DIALOG_NEW_APP);
            return true;

        case R.id.menu_copy_app:
            showDialog(DIALOG_COPY_LIST);
            return true;

        case R.id.menu_edit_all_apps:
            showDialog(DIALOG_EDIT_ALL_APPS);
            return true;

        case R.id.menu_about:
            showDialog(DIALOG_ABOUT);
            return true;
            
        case R.id.menu_manual:
            mManual = !mManual;
            mManualMenuItem.
                setTitle(getString(mManual ? R.string.auto_select :
                                             R.string.manual_select));
            mManualButton.setPressed(mManual);
            mSelectSplitter.setVisibility(mManual ? View.VISIBLE : View.GONE);
            mSelectButton.setVisibility(mManual ? View.VISIBLE : View.GONE);
            mIntro.setText(getString(mManual ? R.string.intro_aid_route_manual :
                                               R.string.intro_aid_route));
            clearMessages();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void showToast(String text) {
        Toast toast = Toast.makeText(ReaderActivity.this, text,
                Toast.LENGTH_SHORT);
        toast.setGravity(Gravity.CENTER_VERTICAL, 0, -100);
        toast.show();
    }

    @Override
    public void onTagDiscovered(Tag tag) {
        // first clear messages
        if (mSkipNextClear) {
            mSkipNextClear = false;
        } else {
            clearMessages();
        }
        // get IsoDep handle and run xcvr thread
        IsoDep isoDep = IsoDep.get(tag);
        if (isoDep == null) {
            onError(getString(R.string.wrong_tag_err), true);
        } else {
            ReaderXcvr xcvr;
            String name = mApps.get(mSelectedAppPos).getName();
            String aid = mApps.get(mSelectedAppPos).getAid();
            if (mTestMode == TEST_MODE_AID_ROUTE) {
                if (DEMO_NAME.equals(name) && DEMO_AID.equals(aid)) {
                    xcvr = new DemoReaderXcvr(isoDep, aid, this);
                } else if (mManual) {
                    // manual select mode; for multiple selects per tap/connect
                    // does not select ppse for payment apps unless specifically configured
                    xcvr = new ManualReaderXcvr(isoDep, aid, this);
                } else if (mApps.get(mSelectedAppPos).getType() == SmartcardApp.TYPE_PAYMENT) {
                    // payment, ie. always selects ppse first
                    xcvr = new PaymentReaderXcvr(isoDep, aid, this, mTestMode);
                } else {
                    // other/non-payment; auto select on each tap/connect
                    xcvr = new OtherReaderXcvr(isoDep, aid, this);
                }
            } else {
                // test mode is emv read
                xcvr = new PaymentReaderXcvr(isoDep, aid, this, mTestMode);
            }
            new Thread(xcvr).start();
        }
    }

    @Override
    public void onMessageSend(final String raw, final String name) {
        onMessage(raw, MessageAdapter.MSG_SEND, name, null);
    }

    @Override
    public void onMessageRcv(final String raw, final String name, final String parsed) {
        onMessage(raw, MessageAdapter.MSG_RCV, name, parsed);
    }

    @Override
    public void onOkay(final String message) {
        onMessage(message, MessageAdapter.MSG_OKAY, null, null);
    }

    @Override
    public void onError(final String message, boolean skipNextClear) {
        onMessage(message, MessageAdapter.MSG_ERROR, null, null);
        mSkipNextClear = skipNextClear;
    }

    private void onMessage(final String text, final int type, final String name, final String parsed) {
        if (mMsgAdapter != null) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mMsgAdapter.addMessage(text, type, name, parsed);
                    setShareMsgsIntent();
                }
            });
        }
    }

    @Override
    public void setUserSelectListener(final ReaderXcvr.UiListener callback) {
        mSelectButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                callback.onUserSelect(mApps.get(mSelectedAppPos).getAid());
            }
        });
    }

    @Override
    public void clearMessages() {
        if (mMsgAdapter != null) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mMsgAdapter.clearMessages();
                    clearShareMsgsIntent();
                }
            });
        }
    }

    private void setShareMsgsIntent() {
        if (mMsgAdapter != null && mShareProvider != null) {
            Intent sendIntent = null;
            sendIntent = new Intent();
            sendIntent.setAction(Intent.ACTION_SEND);
            //Log.d(TAG, mMsgAdapter.getShareMsgsHtml());
            sendIntent.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(mMsgAdapter.getShareMsgsHtml()));
            // subject for emails
            String subject;
            if (mTestMode == TEST_MODE_AID_ROUTE) {
                subject = getString(R.string.app_name) + ": " +
                    getString(R.string.aid_route) + ": " +
                    mApps.get(mSelectedAppPos).getName();
            } else {
                subject = getString(R.string.app_name) + ": " +
                    getString(R.string.emv_read);   
            }
            sendIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
            sendIntent.setType("text/html");
            mShareProvider.setShareIntent(sendIntent);
        }
    }

    private void clearShareMsgsIntent() {
        if (mShareProvider != null) {
            mShareProvider.setShareIntent(null);
        }
    }    

    private void writePrefs() {
        StringBuffer names = new StringBuffer(APP_NAMES);
        StringBuffer aids = new StringBuffer(APP_AIDS);
        StringBuffer types = new StringBuffer(APP_TYPES);

        if (mApps != null) {
            synchronized (mApps) {
                for (int i = NUM_RO_APPS; i < mApps.size(); i++) {
                    SmartcardApp app = mApps.get(i);
                    names.append("|" + app.getName());
                    aids.append("|" + app.getAid());
                    types.append("|" + app.getType());
                }
            }
        }

        mEditor.putString("app_names", names.toString());
        mEditor.putString("app_aids", aids.toString());
        mEditor.putString("app_types", types.toString());
        mEditor.putInt("selected_aid_pos", mSelectedAppPos);
        mEditor.putInt("test_mode", mTestMode);
        mEditor.commit();
    }

    private class writePrefsTask extends AsyncTask<Void, Void, Void> {
        @Override
        protected Void doInBackground(Void... v) {
            writePrefs();
            return null;
        }
    }
    
    @SuppressWarnings("deprecation")
    public void onDialogParsedMsg(String name, String text) {
        mParsedMsgName = name;
        mParsedMsgText = text;
        showDialog(DIALOG_PARSED_MSG);    
    }
}




Java Source Code List

org.docrj.smartcard.emv.AppElementaryFile.java
org.docrj.smartcard.emv.AppFileLocator.java
org.docrj.smartcard.emv.AppInterchangeProfile.java
org.docrj.smartcard.emv.AppPriorityIndicator.java
org.docrj.smartcard.emv.DDF.java
org.docrj.smartcard.emv.DOL.java
org.docrj.smartcard.emv.EMVApp.java
org.docrj.smartcard.emv.EMVTags.java
org.docrj.smartcard.emv.EMVTerminal.java
org.docrj.smartcard.emv.GpoApdu.java
org.docrj.smartcard.emv.IssuerIdNumber.java
org.docrj.smartcard.emv.LanguagePref.java
org.docrj.smartcard.emv.LogEntry.java
org.docrj.smartcard.emv.MCTags.java
org.docrj.smartcard.emv.Record.java
org.docrj.smartcard.emv.TagProvider.java
org.docrj.smartcard.emv.TerminalTranQualifiers.java
org.docrj.smartcard.emv.TerminalVerifResults.java
org.docrj.smartcard.emv.VISATags.java
org.docrj.smartcard.iso7816.BERTLV.java
org.docrj.smartcard.iso7816.CommandApdu.java
org.docrj.smartcard.iso7816.ReadRecordApdu.java
org.docrj.smartcard.iso7816.ResponseApdu.java
org.docrj.smartcard.iso7816.SelectApdu.java
org.docrj.smartcard.iso7816.SmartcardException.java
org.docrj.smartcard.iso7816.TLVException.java
org.docrj.smartcard.iso7816.TLVUtil.java
org.docrj.smartcard.iso7816.TagAndLength.java
org.docrj.smartcard.iso7816.TagImpl.java
org.docrj.smartcard.iso7816.TagType.java
org.docrj.smartcard.iso7816.TagValueType.java
org.docrj.smartcard.iso7816.Tag.java
org.docrj.smartcard.reader.ApduParser.java
org.docrj.smartcard.reader.AppAdapter.java
org.docrj.smartcard.reader.DemoReaderXcvr.java
org.docrj.smartcard.reader.FileShareActivity.java
org.docrj.smartcard.reader.ManualReaderXcvr.java
org.docrj.smartcard.reader.MessageAdapter.java
org.docrj.smartcard.reader.OtherReaderXcvr.java
org.docrj.smartcard.reader.PaymentReaderXcvr.java
org.docrj.smartcard.reader.ReaderActivity.java
org.docrj.smartcard.reader.ReaderXcvr.java
org.docrj.smartcard.reader.SmartcardApp.java
org.docrj.smartcard.util.ByteArrayWrapper.java
org.docrj.smartcard.util.ISO3166_1.java
org.docrj.smartcard.util.ISO4217_Numeric.java
org.docrj.smartcard.util.Util.java