Java tutorial
/** * CredentialListActivity.java * * This program 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. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. * * Copyright (C) Wouter Lueks, Radboud University Nijmegen, Februari 2013. */ package org.irmacard.androidmanagement; import java.io.IOException; import java.util.ArrayList; import net.sourceforge.scuba.smartcards.CardServiceException; import net.sourceforge.scuba.smartcards.IsoDepCardService; import org.irmacard.android.util.credentials.CredentialPackage; import org.irmacard.androidmanagement.dialogs.AlertDialogFragment; import org.irmacard.androidmanagement.dialogs.CardMissingDialogFragment; import org.irmacard.androidmanagement.dialogs.ChangePinDialogFragment; import org.irmacard.androidmanagement.dialogs.ConfirmDeleteDialogFragment; import org.irmacard.androidmanagement.dialogs.ConfirmDeleteDialogFragment.ConfirmDeleteDialogListener; import org.irmacard.androidmanagement.util.TransmitResult; import org.irmacard.credentials.idemix.IdemixCredentials; import org.irmacard.credentials.info.CredentialDescription; import org.irmacard.credentials.util.CardVersion; import org.irmacard.credentials.util.log.LogEntry; import org.irmacard.idemix.IdemixService; import android.app.DialogFragment; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.tech.IsoDep; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; /** * An activity representing a list of Credentials. This activity has different * presentations for handset and tablet-size devices. On handsets, the activity * presents a list of items, which when touched, lead to a * {@link CredentialDetailActivity} representing item details. On tablets, the * activity presents the list of items and item details side-by-side using two * vertical panes. * <p> * The activity makes heavy use of fragments. The list of items combined with * log and settings buttons is a {@link MenuFragment} and the item details (if * present) is a {@link CredentialDetailFragment}. * <p> * This activity also implements the required {@link MenuFragment.Callbacks} * interface to listen for item selections and button callbacks. */ public class CredentialListActivity extends FragmentActivity implements MenuFragment.Callbacks, ConfirmDeleteDialogListener, CardMissingDialogFragment.CardMissingDialogListener, ChangePinDialogFragment.ChangePinDialogListener, AlertDialogFragment.AlertDialogListener, CredentialDetailFragment.Callbacks, SettingsFragment.Callbacks { /** * Whether or not the activity is in two-pane mode, i.e. running on a tablet * device. */ private boolean mTwoPane; private ArrayList<CredentialPackage> credentials; private ArrayList<LogEntry> logs; private Tag tag; private NfcAdapter nfcA; private PendingIntent mPendingIntent; private IntentFilter[] mFilters; private String[][] mTechLists; private String TAG = "CredentialListActivity"; private interface CardProgram { public TransmitResult run(IdemixService is) throws CardServiceException; } private enum Action { NONE, DELETE_CREDENTIAL, CHANGE_CARD_PIN, CHANGE_CREDENTIAL_PIN } private enum State { NORMAL, TEST_CARD_PRESENCE, WAITING_FOR_CARD, CONFIRM_ACTION, PERFORM_ACTION, ACTION_PERFORMED, ACTION_FAILED } private Action currentAction = Action.NONE; private State currentState = State.NORMAL; private DialogFragment cardMissingDialog; // Needed to delete credentials private CredentialDescription toBeDeleted; // Needed to change PINs private String old_pin; private String new_pin; private int tries; // Deal properly with failures private Exception exception; private String cardPin; private CardVersion cardVersion; // Requests private int SETTINGS_REQUEST = 11; private int DETAIL_REQUEST = 12; // PIN lengths private int CRED_PIN_LENGTH = 4; private int CARD_PIN_LENGTH = 6; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Pass the list of CredentialPackages on to the ListFragement Intent intent = getIntent(); @SuppressWarnings("unchecked") ArrayList<CredentialPackage> credentials = (ArrayList<CredentialPackage>) intent .getSerializableExtra(WaitingForCardActivity.EXTRA_CREDENTIAL_PACKAGES); setCredentials(credentials); @SuppressWarnings("unchecked") ArrayList<LogEntry> logs = (ArrayList<LogEntry>) intent .getSerializableExtra(WaitingForCardActivity.EXTRA_LOG_ENTRIES); setLogs(logs); Tag tag = (Tag) intent.getParcelableExtra(WaitingForCardActivity.EXTRA_TAG); setTag(tag); cardPin = (String) intent.getSerializableExtra(WaitingForCardActivity.EXTRA_CARD_PIN); cardVersion = (CardVersion) intent.getSerializableExtra(WaitingForCardActivity.EXTRA_CARD_VERSION); setContentView(R.layout.activity_credential_list); if (findViewById(R.id.credential_detail_container) != null) { // The detail container view will be present only in the // large-screen layouts (res/values-large and // res/values-sw600dp). If this view is present, then the // activity should be in two-pane mode. mTwoPane = true; // In two-pane mode, list items should be given the // 'activated' state when touched. ((MenuFragment) getSupportFragmentManager().findFragmentById(R.id.credential_menu_fragment)) .setTwoPaneMode(true); InitFragment initFragment = new InitFragment(); getSupportFragmentManager().beginTransaction().replace(R.id.credential_detail_container, initFragment) .commit(); Log.i("blaat", "Simulating initial click!!"); ((MenuFragment) getSupportFragmentManager().findFragmentById(R.id.credential_menu_fragment)) .simulateListClick(0); // Do not show action bar in two-pane mode getActionBar().hide(); } // NFC stuff nfcA = NfcAdapter.getDefaultAdapter(getApplicationContext()); mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); // Setup an intent filter for all TECH based dispatches IntentFilter tech = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED); mFilters = new IntentFilter[] { tech }; // Setup a tech list for all IsoDep cards mTechLists = new String[][] { new String[] { IsoDep.class.getName() } }; } @Override public void onResume() { super.onResume(); if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(getIntent().getAction())) { processIntent(getIntent()); } if (nfcA != null) { nfcA.enableForegroundDispatch(this, mPendingIntent, mFilters, mTechLists); } } @Override public void onPause() { super.onPause(); if (nfcA != null) { nfcA.disableForegroundDispatch(this); } } @Override public void onNewIntent(Intent intent) { Log.i(TAG, "Discovered tag with intent: " + intent); setIntent(intent); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Menu is only inflated in single pane mode MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu, menu); return !mTwoPane; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_history: onLogSelected(); return true; case R.id.menu_settings: onSettingsSelected(); return true; default: return super.onOptionsItemSelected(item); } } public void processIntent(Intent intent) { tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); IsoDep isotag = IsoDep.get(tag); if (isotag != null) { // We are waiting for the card, notify dialog if (currentState == State.WAITING_FOR_CARD) { cardMissingDialog.dismiss(); gotoState(State.TEST_CARD_PRESENCE); } } } private void setCredentials(ArrayList<CredentialPackage> credentials) { this.credentials = credentials; } private void setLogs(ArrayList<LogEntry> logs) { this.logs = logs; } private void setTag(Tag tag) { this.tag = tag; } /** * Callback method from {@link MenuFragment.Callbacks} indicating * that the item with the given ID was selected. */ @Override public void onItemSelected(short id) { CredentialPackage credential = null; for (CredentialPackage cp : credentials) { if (cp.getCredentialDescription().getId() == id) { credential = cp; } } if (mTwoPane) { // In two-pane mode, show the detail view in this activity by // adding or replacing the detail fragment using a // fragment transaction. Bundle arguments = new Bundle(); arguments.putSerializable(CredentialDetailFragment.ARG_ITEM, credential); CredentialDetailFragment fragment = new CredentialDetailFragment(); fragment.setArguments(arguments); getSupportFragmentManager().beginTransaction().replace(R.id.credential_detail_container, fragment) .commit(); } else { // In single-pane mode, simply start the detail activity // for the selected item ID. Log.i(TAG, "Starting detail activity"); Intent detailIntent = new Intent(this, CredentialDetailActivity.class); detailIntent.putExtra(CredentialDetailFragment.ARG_ITEM, credential); startActivityForResult(detailIntent, DETAIL_REQUEST); } } /** * Callback method from {@link MenuFragment.Callbacks} indicating * that the log was selected. */ public void onLogSelected() { Log.i("cla", "log selected"); if (mTwoPane) { // In two-pane mode, show the detail view in this activity by // adding or replacing the detail fragment using a // fragment transaction. Bundle arguments = new Bundle(); arguments.putSerializable(LogFragment.ARG_LOG, logs); LogFragment fragment = new LogFragment(); fragment.setArguments(arguments); getSupportFragmentManager().beginTransaction().replace(R.id.credential_detail_container, fragment) .commit(); } else { // In single-pane mode, simply start the detail activity // for the the log Intent logIntent = new Intent(this, LogActivity.class); logIntent.putExtra(LogFragment.ARG_LOG, logs); startActivity(logIntent); } } /** * Callback method from {@link MenuFragment.Callbacks} indicating * that the settings were selected. */ public void onSettingsSelected() { Log.i("cla", "settings selected"); if (mTwoPane) { // In two-pane mode, show the detail view in this activity by // adding or replacing the detail fragment using a // fragment transaction. Bundle arguments = new Bundle(); arguments.putSerializable(SettingsFragment.ARG_CARD_VERSION, cardVersion); SettingsFragment fragment = new SettingsFragment(); fragment.setArguments(arguments); getSupportFragmentManager().beginTransaction().replace(R.id.credential_detail_container, fragment) .commit(); } else { // In single-pane mode, simply start the detail activity // for the selected item ID. Intent settingsIntent = new Intent(this, SettingsActivity.class); settingsIntent.putExtra(SettingsFragment.ARG_CARD_VERSION, cardVersion); startActivityForResult(settingsIntent, SETTINGS_REQUEST); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == SETTINGS_REQUEST) { if (resultCode == SettingsActivity.RESULT_CHANGE_CARD_PIN) onChangeCardPIN(); else if (resultCode == SettingsActivity.RESULT_CHANGE_CRED_PIN) onChangeCredPIN(); } else if (requestCode == DETAIL_REQUEST) { if (resultCode == CredentialDetailActivity.RESULT_DELETE) { CredentialDescription cd = (CredentialDescription) data .getSerializableExtra(CredentialDetailActivity.ARG_RESULT_DELETE); onDeleteCredential(cd); } } } public void onCardMissingCancel() { // If cancelled we cannot continue with the action gotoState(State.NORMAL); } public void onCardMissingRetry() { // We should retry gotoState(State.TEST_CARD_PRESENCE); } @Override public void onPINChange(String old_pin, String new_pin) { this.old_pin = old_pin; this.new_pin = new_pin; gotoState(State.PERFORM_ACTION); } @Override public void onPINChangeCancel() { // If cancelled we cannot continue, returning to normal gotoState(State.NORMAL); } protected ArrayList<CredentialPackage> getCredentials() { return credentials; } public ArrayList<LogEntry> getLogs() { return logs; } public CardVersion getCardVersion() { return cardVersion; } private void gotoState(State state) { String TAG = "CLA:State"; //State previousState = currentState; currentState = state; switch (state) { case NORMAL: Log.i(TAG, "Returning to default state"); break; case TEST_CARD_PRESENCE: Log.i(TAG, "Checking card presence"); // This is the start of a new action so we should reset some state tries = -1; new CheckCardPresentTask(this).execute(tag); break; case WAITING_FOR_CARD: Log.i(TAG, "Going to wait for card"); cardMissingDialog = new CardMissingDialogFragment(); cardMissingDialog.show(getFragmentManager(), "cardmissing"); break; case CONFIRM_ACTION: Log.i(TAG, "Confirming action"); ChangePinDialogFragment pinDialog; switch (currentAction) { case DELETE_CREDENTIAL: ConfirmDeleteDialogFragment confirmDialog = ConfirmDeleteDialogFragment.getInstance(toBeDeleted); confirmDialog.show(getFragmentManager(), "confirm_delete"); break; case CHANGE_CARD_PIN: pinDialog = ChangePinDialogFragment.getInstance("card", tries, new_pin, CARD_PIN_LENGTH); pinDialog.show(getFragmentManager(), "change_card_pin"); break; case CHANGE_CREDENTIAL_PIN: pinDialog = ChangePinDialogFragment.getInstance("credential", tries, new_pin, CRED_PIN_LENGTH); pinDialog.show(getFragmentManager(), "change_cred_pin"); break; case NONE: Log.i(TAG, "Illegal state, returning to normal"); currentState = State.NORMAL; break; } break; case ACTION_FAILED: Log.i(TAG, "Action failed"); AlertDialogFragment f = AlertDialogFragment.getInstance("Action Failed", exception.getMessage()); f.show(getFragmentManager(), "alert"); break; case ACTION_PERFORMED: Log.i(TAG, "Action succeeded"); completeAction(); gotoState(State.NORMAL); break; case PERFORM_ACTION: Log.i(TAG, "Performing action"); runAction(); break; } } private class CheckCardPresentTask extends AsyncTask<Tag, Void, TransmitResult> { private final String TAG = "CheckCardPresentTask"; protected CheckCardPresentTask(Context context) { } @Override protected TransmitResult doInBackground(Tag... arg0) { IsoDep tag = IsoDep.get(arg0[0]); // Make sure time-out is long enough (10 seconds) tag.setTimeout(10000); IdemixService is = new IdemixService(new IsoDepCardService(tag)); TransmitResult result = null; try { is.open(); is.close(); result = new TransmitResult(TransmitResult.Result.SUCCESS); } catch (CardServiceException e) { Log.e(TAG, "Unable to select idemix applet"); e.printStackTrace(); return new TransmitResult(e); } finally { try { tag.close(); } catch (IOException e) { Log.e(TAG, "Failed to close tag connection"); e.printStackTrace(); } } return result; } @Override protected void onPostExecute(TransmitResult tresult) { switch (tresult.getResult()) { case FAILURE: gotoState(State.WAITING_FOR_CARD); Log.i(TAG, "Cannot connect to card, proceeding to waiting for card"); break; case SUCCESS: gotoState(State.CONFIRM_ACTION); break; default: // Nothing to do? break; } } } private class TransmitAPDUsTask extends AsyncTask<Tag, Void, TransmitResult> { private final String TAG = "TransmitAPDUsTask"; private CardProgram cardProgram; protected TransmitAPDUsTask(Context context, CardProgram cardProgram) { this.cardProgram = cardProgram; } @Override protected TransmitResult doInBackground(Tag... arg0) { IsoDep tag = IsoDep.get(arg0[0]); // Make sure time-out is long enough (10 seconds) tag.setTimeout(10000); IdemixService is = new IdemixService(new IsoDepCardService(tag)); TransmitResult result = null; try { is.open(); Log.i(TAG, "Performing requested actions now"); result = cardProgram.run(is); is.close(); Log.i(TAG, "Performed action succesfully!"); } catch (Exception e) { Log.e(TAG, "Reading verification caused exception"); e.printStackTrace(); return new TransmitResult(e); } finally { try { tag.close(); } catch (IOException e) { Log.e(TAG, "Failed to close tag connection"); e.printStackTrace(); } } return result; } @Override protected void onPostExecute(TransmitResult tresult) { switch (tresult.getResult()) { case SUCCESS: Log.i(TAG, "Complete succesfully, finishing task"); gotoState(State.ACTION_PERFORMED); break; case FAILURE: Log.i(TAG, "Action failed, notifying user"); exception = tresult.getException(); gotoState(State.ACTION_FAILED); break; case INCORRECT_PIN: Log.i(TAG, "Pincode incorrect, notifying user"); tries = tresult.getTries(); if (tries > 0) { gotoState(State.CONFIRM_ACTION); } else { exception = new Exception("You have no more pin tries left, the card is now blocked"); gotoState(State.ACTION_FAILED); } } } } @Override public void onConfirmDeleteOK() { gotoState(State.PERFORM_ACTION); } @Override public void onConfirmDeleteCancel() { gotoState(State.NORMAL); // If in twoPane mode return to detailed view upon cancel if (!mTwoPane) { onItemSelected(toBeDeleted.getId()); } } public void onChangeCardPIN() { Log.i(TAG, "Change card PIN called"); currentAction = Action.CHANGE_CARD_PIN; gotoState(State.TEST_CARD_PRESENCE); } public void onChangeCredPIN() { Log.i(TAG, "Change cred PIN called"); currentAction = Action.CHANGE_CREDENTIAL_PIN; gotoState(State.TEST_CARD_PRESENCE); } public void runAction() { CardProgram program = null; switch (currentAction) { case DELETE_CREDENTIAL: program = new CardProgram() { @Override public TransmitResult run(IdemixService is) throws CardServiceException { is.sendCardPin(cardPin.getBytes()); IdemixCredentials ic = new IdemixCredentials(is); ic.removeCredential(toBeDeleted); return new TransmitResult(TransmitResult.Result.SUCCESS); } }; break; case CHANGE_CARD_PIN: program = new CardProgram() { @Override public TransmitResult run(IdemixService is) throws CardServiceException { int tries; tries = is.sendCardPin(old_pin.getBytes()); // Only continu if Card Pin was correct initially if (tries == -1) { tries = is.updateCardPin(old_pin.getBytes(), new_pin.getBytes()); } if (tries == -1) { return new TransmitResult(TransmitResult.Result.SUCCESS); } else { return new TransmitResult(tries); } } }; break; case CHANGE_CREDENTIAL_PIN: program = new CardProgram() { @Override public TransmitResult run(IdemixService is) throws CardServiceException { is.sendCardPin(cardPin.getBytes()); int tries = is.updateCredentialPin(new_pin.getBytes()); if (tries == -1) { return new TransmitResult(TransmitResult.Result.SUCCESS); } else { return new TransmitResult(tries); } } }; break; default: // Nothing to do? break; } new TransmitAPDUsTask(this, program).execute(tag); } public void completeAction() { // This is run after action completes succesfully switch (currentAction) { case DELETE_CREDENTIAL: Log.i("CLA:completeAction", "Removing item from list"); // Find deleted package int deletedIdx = -1; for (int i = 0; i < credentials.size(); i++) { CredentialPackage cp = credentials.get(i); System.out.println("Examining credential: " + cp.toString()); if (cp.getCredentialDescription().getId() == toBeDeleted.getId()) { deletedIdx = i; } } if (deletedIdx == -1) { Log.i("CLA:completeAction", "Failed to locate credential"); } else { credentials.remove(deletedIdx); } if (mTwoPane) { if (credentials.size() > 0) { int new_idx = deletedIdx > 0 ? deletedIdx - 1 : 0; ((MenuFragment) getSupportFragmentManager().findFragmentById(R.id.credential_menu_fragment)) .simulateListClick(new_idx); } else { InitFragment initFragment = new InitFragment(); getSupportFragmentManager().beginTransaction() .replace(R.id.credential_detail_container, initFragment).commit(); } } else { ((MenuFragment) getSupportFragmentManager().findFragmentById(R.id.credential_menu_fragment)) .updateList(); } break; case CHANGE_CARD_PIN: // We need to cache the new PIN now cardPin = new_pin; // Return to settings in single-pane mode if (!mTwoPane) onSettingsSelected(); break; case CHANGE_CREDENTIAL_PIN: if (!mTwoPane) onSettingsSelected(); break; default: // Nothing to do? } } public void onDeleteCredential(CredentialDescription cd) { Log.i("blaat", "Delete credential called"); toBeDeleted = cd; currentAction = Action.DELETE_CREDENTIAL; Log.i("blaat", "Will delete: " + cd.toString()); gotoState(State.TEST_CARD_PRESENCE); } @Override public void onAlertDismiss() { // Returning to normal state gotoState(State.NORMAL); } }