From 224c0831756e9ffeb09bfaabfbdc5452302fcb66 Mon Sep 17 00:00:00 2001 From: Johannes Zweng Date: Fri, 19 Dec 2014 23:14:09 +0100 Subject: [PATCH] =?UTF-8?q?Version=202.0.3=20(as=20published=20in=20Google?= =?UTF-8?q?=20Play)=20=E2=80=A2=20Austrian=20e-purse=20"Quick"=20logs=20(o?= =?UTF-8?q?nly=20for=20Austria)=20=E2=80=A2=20experimental=20support=20for?= =?UTF-8?q?=20VISA=20logs=20=E2=80=A2=20added=20more=20currency=20codes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AndroidManifest.xml | 4 +- doc/google_play_store/changelog_de.txt | 5 + doc/google_play_store/changelog_en.txt | 5 + .../list_item_quick_transaction_expanded.xml | 185 +++++++++++++++ res/layout/list_item_transaction_expanded.xml | 8 +- res/raw-de/changelog.txt | 7 + res/raw/changelog.txt | 6 + res/values-de/strings.xml | 22 +- res/values/strings.xml | 20 +- .../bankomatinfos/iso7816emv/EmvUtils.java | 46 +++- .../iso7816emv/Iso4217CurrencyCodes.java | 58 +++-- .../iso7816emv/NfcBankomatCardReader.java | 222 ++++++++++++++++-- .../model/AbstractTransactionLogEntry.java | 105 +++++++++ .../zweng/bankomatinfos/model/CardInfo.java | 32 ++- ...Entry.java => EmvTransactionLogEntry.java} | 110 +-------- .../model/QuickTransactionLogEntry.java | 105 +++++++++ ...s.java => ListAdapterEmvTransactions.java} | 45 ++-- .../ui/ListAdapterQuickTransactions.java | 158 +++++++++++++ .../bankomatinfos/ui/ResultActivity.java | 77 +++--- ...ment.java => ResultEmvTxListFragment.java} | 6 +- .../ui/ResultQuickTxListFragment.java | 37 +++ src/at/zweng/bankomatinfos/util/Utils.java | 20 ++ 22 files changed, 1053 insertions(+), 230 deletions(-) create mode 100644 res/layout/list_item_quick_transaction_expanded.xml create mode 100644 src/at/zweng/bankomatinfos/model/AbstractTransactionLogEntry.java rename src/at/zweng/bankomatinfos/model/{TransactionLogEntry.java => EmvTransactionLogEntry.java} (50%) create mode 100644 src/at/zweng/bankomatinfos/model/QuickTransactionLogEntry.java rename src/at/zweng/bankomatinfos/ui/{ListAdapterTransactions.java => ListAdapterEmvTransactions.java} (75%) create mode 100644 src/at/zweng/bankomatinfos/ui/ListAdapterQuickTransactions.java rename src/at/zweng/bankomatinfos/ui/{ResultTxListFragment.java => ResultEmvTxListFragment.java} (88%) create mode 100644 src/at/zweng/bankomatinfos/ui/ResultQuickTxListFragment.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 45333b9..8a12e13 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="16" + android:versionName="2.0.3" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/list_item_transaction_expanded.xml b/res/layout/list_item_transaction_expanded.xml index 46141bd..5ae5685 100644 --- a/res/layout/list_item_transaction_expanded.xml +++ b/res/layout/list_item_transaction_expanded.xml @@ -118,7 +118,7 @@ +$ 2.0.3 + % Version 2.0.3 + _ 2014-12-19 + * Support für Logs der elektronischen Geldböres "Quick" (gibt's nur in Österreich) + * Allerdings liefern nicht alle Quick Karten sinnvolle Logs (manchmal nur Nullen) + * experimenteller Support für das Parsing von VISA Log Einträgen + * einige zusätzliche Währungscodes $ 2.0.2 % Version 2.0.2 _ 2014-12-17 diff --git a/res/raw/changelog.txt b/res/raw/changelog.txt index 0b00854..83dfc6a 100644 --- a/res/raw/changelog.txt +++ b/res/raw/changelog.txt @@ -19,6 +19,12 @@ +$ 2.0.3 + % Version 2.0.3 + _ 2014-12-19 + * support for Austrian e-purse "Quick" log entries (but not all Quick cards seem to contain meaningful logs) + * experimental support for reading logs from VISA + * added more currency codes $ 2.0.2 % Version 2.0.2 _ 2014-12-17 diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 8c1bf4b..76589e4 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -29,10 +29,11 @@ Ergebnisse - Infos - Transaktionen - Log - + Infos + Transaktionen + Quick Logs + Log + @@ -61,21 +62,22 @@ - Keine Transaktionen gefunden + Keine Transaktionslogs gefunden Diese Karte scheint keine Transaktionslogs zu beinhalten. - - Hinweis - Sorry.. Diese Karte beinhaltet offensichtlich keine Transaktions-Logs.\n\nDetails: Der EMV TAG \""9F 4D\" (\"Log Entry\") ist auf dieser Karte gar nicht vorhanden. - + Einklapp Symbol Ausklapp Symbol Cryptogram Information Data: - ATC (application tx counter): + ATC (Transaktionszähler): Customer Exclusive Data: Application Default Action (ADA)??: Unknown??: + + Verbleibendes Guthaben: + Terminal Infos??: + Bankomatkarten Infos - Ergebnisse diff --git a/res/values/strings.xml b/res/values/strings.xml index c7c1e0b..7199d19 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -29,9 +29,10 @@ Results - Infos - Transactions - Log + Infos + Transactions + Quick Logs + Log @@ -63,15 +64,11 @@ Lower Consecutive Offline Transaction Amount: Upper Consecutive Offline Transaction Amount: - - - No transactions found + No transaction logs found This card seems not to contain any transactions logs. - - Note - Sorry.. This card seems not contain transaction logs at all. \n\nDetails: the EMV TAG \"9F 4D\" (\"Log Entry\") is not present on this card. + Icon Collapse @@ -82,6 +79,11 @@ Application Default Action (ADA)??: Unknown??: + + Remaining Balance: + Terminal Infos??: + + Bankomat Card Info - Results diff --git a/src/at/zweng/bankomatinfos/iso7816emv/EmvUtils.java b/src/at/zweng/bankomatinfos/iso7816emv/EmvUtils.java index 02ea6c8..6976b68 100644 --- a/src/at/zweng/bankomatinfos/iso7816emv/EmvUtils.java +++ b/src/at/zweng/bankomatinfos/iso7816emv/EmvUtils.java @@ -714,7 +714,7 @@ public static Date getTimeStampFromBcdBytes(byte[] date, byte[] time) * --> which represents 31. December 2013 * * @param date - * @return + * @return date or null if all 3 bytes are 0 * @throws ParseException */ public static Date getDateFromBcdBytes(byte[] date) throws ParseException { @@ -722,8 +722,50 @@ public static Date getDateFromBcdBytes(byte[] date) throws ParseException { throw new IllegalArgumentException( "getDateFromBcdBytes: date must be exactly 3 bytes long"); } + String s = prettyPrintString(bytesToHex(date), 2); + if ("00 00 00".equals(s)) { + return null; + } DateFormat df = new SimpleDateFormat("yy MM dd", Locale.US); - return df.parse(prettyPrintString(bytesToHex(date), 2)); + return df.parse(s); + } + + /** + * Parse timestamp from quick log entry + * + * @param date + * @param time + * @return date or null if days are 0000000 + * @throws ParseException + */ + public static Date getTimeStampFromQuickLog(int days, byte[] time) { + if (days == 0) { + return null; + } + if (time == null || time.length != 3) { + throw new IllegalArgumentException( + "getTimeStampFromQuickLog: time must be exactly 3 bytes long"); + } + Calendar logDate = getDayFromQuickLogEntry(days); + logDate.set(Calendar.HOUR_OF_DAY, Integer.parseInt(byte2Hex(time[0]))); + logDate.set(Calendar.MINUTE, Integer.parseInt(byte2Hex(time[1]))); + logDate.set(Calendar.SECOND, Integer.parseInt(byte2Hex(time[2]))); + return logDate.getTime(); + } + + /** + * @param days + * number of days after September 02, 1975 (what happened on this + * day?) + * @return + */ + public static Calendar getDayFromQuickLogEntry(int days) { + Calendar logDay = GregorianCalendar.getInstance(); + logDay.set(Calendar.DAY_OF_MONTH, 2); + logDay.set(Calendar.MONTH, Calendar.SEPTEMBER); + logDay.set(Calendar.YEAR, 1975); + logDay.add(Calendar.DAY_OF_YEAR, days); + return logDay; } /** diff --git a/src/at/zweng/bankomatinfos/iso7816emv/Iso4217CurrencyCodes.java b/src/at/zweng/bankomatinfos/iso7816emv/Iso4217CurrencyCodes.java index 8988fd5..26679ae 100644 --- a/src/at/zweng/bankomatinfos/iso7816emv/Iso4217CurrencyCodes.java +++ b/src/at/zweng/bankomatinfos/iso7816emv/Iso4217CurrencyCodes.java @@ -17,57 +17,67 @@ public class Iso4217CurrencyCodes { */ public static String getCurrencyAsString(byte[] currencyCode) { String byteString = bytesToHex(currencyCode); - if ("0978".equals(byteString)) { - return "€"; - } if ("0040".equals(byteString)) { return "ATS"; } - if ("0840".equals(byteString)) { - return "USD"; - } - if ("0826".equals(byteString)) { - return "GBP"; + if ("0124".equals(byteString)) { + return "CAD"; } - if ("0946".equals(byteString)) { - return "RON"; + if ("0156".equals(byteString)) { + return "CNY"; } - if ("0977".equals(byteString)) { - return "BAM"; + if ("0348".equals(byteString)) { + return "HUF"; } - if ("0975".equals(byteString)) { - return "BGN"; + if ("0643".equals(byteString)) { + return "RUB"; } - if ("0124".equals(byteString)) { - return "CAD"; + if ("0752".equals(byteString)) { + return "SEK"; } if ("0756".equals(byteString)) { return "CHF"; } - if ("0348".equals(byteString)) { - return "HUF"; + if ("0784".equals(byteString)) { + return "AED"; } - if ("0985".equals(byteString)) { - return "PLN"; + if ("0826".equals(byteString)) { + return "GBP"; + } + if ("0840".equals(byteString)) { + return "USD"; } if ("0941".equals(byteString)) { return "RSD"; } - if ("0643".equals(byteString)) { - return "RUB"; + if ("0946".equals(byteString)) { + return "RON"; } - if ("0752".equals(byteString)) { - return "SEK"; + if ("0975".equals(byteString)) { + return "BGN"; + } + if ("0977".equals(byteString)) { + return "BAM"; + } + if ("0978".equals(byteString)) { + return "€"; } if ("0980".equals(byteString)) { return "UAH"; } + if ("0985".equals(byteString)) { + return "PLN"; + } // special code for "not set" or "undefined" if ("0999".equals(byteString)) { // TODO localization return ""; } + if ("0000".equals(byteString)) { + return "?"; + } + return "ISO 4217 Currency Code " + byteString; } diff --git a/src/at/zweng/bankomatinfos/iso7816emv/NfcBankomatCardReader.java b/src/at/zweng/bankomatinfos/iso7816emv/NfcBankomatCardReader.java index 38f6ae8..a79e8f9 100644 --- a/src/at/zweng/bankomatinfos/iso7816emv/NfcBankomatCardReader.java +++ b/src/at/zweng/bankomatinfos/iso7816emv/NfcBankomatCardReader.java @@ -30,6 +30,7 @@ import static at.zweng.bankomatinfos.iso7816emv.EmvUtils.getNextTLV; import static at.zweng.bankomatinfos.iso7816emv.EmvUtils.getTagsFromBerTlvAPDUResponse; import static at.zweng.bankomatinfos.iso7816emv.EmvUtils.getTimeStampFromBcdBytes; +import static at.zweng.bankomatinfos.iso7816emv.EmvUtils.getTimeStampFromQuickLog; import static at.zweng.bankomatinfos.iso7816emv.EmvUtils.isStatusSuccess; import static at.zweng.bankomatinfos.iso7816emv.EmvUtils.prettyPrintBerTlvAPDUResponse; import static at.zweng.bankomatinfos.iso7816emv.EmvUtils.statusToString; @@ -37,9 +38,12 @@ import static at.zweng.bankomatinfos.util.Utils.byteArrayToInt; import static at.zweng.bankomatinfos.util.Utils.bytesToHex; import static at.zweng.bankomatinfos.util.Utils.cutoffLast2Bytes; +import static at.zweng.bankomatinfos.util.Utils.fromHexString; import static at.zweng.bankomatinfos.util.Utils.getByteArrayPart; import static at.zweng.bankomatinfos.util.Utils.getLast2Bytes; import static at.zweng.bankomatinfos.util.Utils.prettyPrintString; +import static at.zweng.bankomatinfos.util.Utils.readBcdIntegerFromBytes; +import static at.zweng.bankomatinfos.util.Utils.readLongFromBytes; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -56,8 +60,9 @@ import at.zweng.bankomatinfos.exceptions.NoSmartCardException; import at.zweng.bankomatinfos.exceptions.TlvParsingException; import at.zweng.bankomatinfos.model.CardInfo; +import at.zweng.bankomatinfos.model.EmvTransactionLogEntry; import at.zweng.bankomatinfos.model.InfoKeyValuePair; -import at.zweng.bankomatinfos.model.TransactionLogEntry; +import at.zweng.bankomatinfos.model.QuickTransactionLogEntry; import at.zweng.bankomatinfos2.R; /** @@ -72,6 +77,19 @@ public class NfcBankomatCardReader { private List _tagList; private Context _ctx; + // 9F 4F - 18 bytes: Log Format + // 9F 36 (02 bytes) -> Application Transaction Counter (ATC) + // 9F 02 (06 bytes) -> Amount, Authorised (Numeric) + // 9F 03 (06 bytes) -> Amount, Other (Numeric) + // 9F 1A (02 bytes) -> Terminal Country Code + // 95 (05 bytes) -> Terminal Verification Results (TVR) + // 5F 2A (02 bytes) -> Transaction Currency Code + // 9A (03 bytes) -> Transaction Date + // 9C (01 bytes) -> Transaction Type + // 9F 80 04 (04 bytes) -> [UNHANDLED TAG] + private static final String LOG_FORMAT_VISA = "9F4F189F36029F02069F03069F1A0295055F2A029A039C019F80049000"; + private static final int LOG_LENGTH_VISA = 33; + // 9F 4F - 11 bytes: Log Format // 9F 27 (01 bytes) -> Cryptogram Information Data // 9F 02 (06 bytes) -> Amount, Authorised (Numeric) @@ -97,6 +115,8 @@ public class NfcBankomatCardReader { private static final String LOG_FORMAT_BANKOMAT_AUSTRIA = "9F4F1A9F27019F02065F2A029A039F36029F5206DF3E019F21039F7C149000"; private static final int LOG_LENGTH_BANKOMAT_AUSTRIA = 46; + private static final int LOG_LENGTH_QUICK = 35; + // until now on all cards I've seen which head a tx log, they were stored on // EF11 // we also cannot rely on cards Log Entry tag, as some cards don't contain @@ -253,6 +273,7 @@ private CardInfo readQuickInfos(CardInfo result) throws IOException { result.setQuickBalance(getQuickCardBalance()); result.setQuickCurrency(Iso4217CurrencyCodes .getCurrencyAsString(getQuickCardCurrencyBytes())); + result = tryReadingQuickLogEntries(result); } catch (TlvParsingException pe) { _ctl.log("ERROR: Catched Exception while reading QUICK infos:\n" + pe + "\n" + pe.getMessage()); @@ -589,6 +610,46 @@ private void tryToVerifyPlaintextPin(String pin) throws IOException { logBerTlvResponse(resultPdu); } + private byte[] transceiveAndLog(byte[] data) throws IOException { + _ctl.log("sending: " + bytesToHex(data)); + return _localIsoDep.transceive(data); + } + + /** + * tests with Paylife "quick" cards (that's an Austrian thing).. + * + * @throws IOException + */ + private CardInfo tryReadingQuickLogEntries(CardInfo result) + throws IOException { + List quickLogs = new ArrayList(); + + byte[] resultPdu; + + _ctl.log("-----------------------------------"); + _ctl.log("-- reading Paylife QUICK log entries: "); + // selecting DF containing logs: + resultPdu = transceiveAndLog(fromHexString("00 a4 00 00 02 01 04")); + logResultPdu(resultPdu); + int currRecord = 1; + while (true) { + // read currently selected file + resultPdu = readRecord(0, currRecord, true); + if (isStatusSuccess(getLast2Bytes(resultPdu))) { + QuickTransactionLogEntry log = parseQuickTxLogEntryFromByteArray(resultPdu); + _ctl.log("-----------------------------------"); + _ctl.log(log.toString()); + quickLogs.add(log); + currRecord++; + } else { + break; + } + } + result.setQuickLog(quickLogs); + _ctl.log("-----------------------------------"); + return result; + } + /** * some tests.. that's my "playground" for experimenting with new commands.. * :-) @@ -712,7 +773,7 @@ private CardInfo searchForFiles(CardInfo result, boolean fullFileScan, // just iterate over everything. // if we find something looking like a TX log, add it to TX list - List txList = new ArrayList(); + List txList = new ArrayList(); int consecutiveErrorRecords = 0; @@ -752,7 +813,7 @@ private CardInfo searchForFiles(CardInfo result, boolean fullFileScan, if (tryToParse) { if (shortEfFileIdentifier == LOG_RECORD_EF && lengthLooksLikeTxLog(responsePdu)) { - TransactionLogEntry txLogEntry = tryToParseLogEntry(responsePdu); + EmvTransactionLogEntry txLogEntry = tryToParseLogEntry(responsePdu); if (txLogEntry != null) { txList.add(txLogEntry); _ctl.log(txLogEntry.toString()); @@ -795,6 +856,8 @@ private boolean lengthLooksLikeTxLog(byte[] rawRecord) { return (rawRecord.length == LOG_LENGTH_BANKOMAT_AUSTRIA); } else if (LOG_FORMAT_MASTERCARD.equals(_logFormatResponse)) { return (rawRecord.length == LOG_LENGTH_MASTERCARD); + } else if (LOG_FORMAT_VISA.equals(_logFormatResponse)) { + return (rawRecord.length == LOG_LENGTH_VISA); } return false; } @@ -803,11 +866,13 @@ private boolean lengthLooksLikeTxLog(byte[] rawRecord) { * @param rawRecord * @return */ - private TransactionLogEntry tryToParseLogEntry(byte[] rawRecord) { + private EmvTransactionLogEntry tryToParseLogEntry(byte[] rawRecord) { if (LOG_FORMAT_BANKOMAT_AUSTRIA.equals(_logFormatResponse)) { return parseBankomatTxLogEntryFromByteArray(rawRecord); } else if (LOG_FORMAT_MASTERCARD.equals(_logFormatResponse)) { return parseMastercardTxLogEntryFromByteArray(rawRecord); + } else if (LOG_FORMAT_VISA.equals(_logFormatResponse)) { + return parseVisaTxLogEntryFromByteArray(rawRecord); } return null; } @@ -820,16 +885,79 @@ private TransactionLogEntry tryToParseLogEntry(byte[] rawRecord) { * @return the parsed record or null if something could not be * parsed */ - private TransactionLogEntry parseMastercardTxLogEntryFromByteArray( + private QuickTransactionLogEntry parseQuickTxLogEntryFromByteArray( byte[] rawRecord) { - // TODO: change this method to parse tx log entries dynamically based on - // format as specified by card - // UPDATE: according users' responses it seems that all Austrian - // Cards use the same logging structure (of course, all the cards come - // from the same issuer). So I keep this for now.. + // ATC amount amount curr Term# term stat? tagBCD? zeit rest + // (conv to dez) + + // 0 1 2 3 6 7 10 1112 13 16 17 20 21 22 25 26 28 + // 27 001D 000001A4 000001A4 0978 000984AB 00001AC4 90 00014339 194500 + + // 29 32 3334 + // 0000014D 9000 + + // logformat of quick (not documented, as guessed by me) + // + // 01 bytes -> unknown byte 1, always seems to be 0x27 ?? + // 02 bytes -> Application Transaction Counter (ATC) + // 04 bytes -> Amount (no BCD, stored as normal integer) + // 04 bytes -> Amount2 (no BCD, stored as normal integer) seems always + // to be the same as the first amount?? + // 02 bytes -> Transaction Currency Code + // 04 bytes -> Terminal Info 1 + // 04 bytes -> Terminal Info 2 + // 01 bytes -> unknown byte 2, always seems to be 0x90 ?? + // 04 bytes -> day as integer + // 03 bytes -> time same coding as in EMV + // 04 bytes -> Remaining balance afterwards (no BCD, stored as normal + // integer) ?? + + // 9A (03 bytes) -> Transaction Date + // 9F 52 (06 bytes) -> Application Default Action (ADA) + + if (rawRecord.length < LOG_LENGTH_QUICK) { + Log.w(TAG, + "parseTxLogEntryFromByteArray: byte array is not long enough for quick log entry:\n" + + prettyPrintString(bytesToHex(rawRecord), 2)); + return null; + } + QuickTransactionLogEntry tx = new QuickTransactionLogEntry(); + tx.setUnknownByte1(rawRecord[0]); + tx.setAtc(byteArrayToInt(getByteArrayPart(rawRecord, 1, 2))); + tx.setAmount(getAmountFromBytes(getByteArrayPart(rawRecord, 3, 6))); + tx.setAmount2(getAmountFromBytes(getByteArrayPart(rawRecord, 7, 10))); + tx.setCurrency(Iso4217CurrencyCodes + .getCurrencyAsString(getByteArrayPart(rawRecord, 11, 12))); + tx.setTerminalInfos1(readLongFromBytes( + getByteArrayPart(rawRecord, 13, 16), 0, 4)); + tx.setTerminalInfos2(readLongFromBytes( + getByteArrayPart(rawRecord, 17, 20), 0, 4)); + tx.setUnknownByte2(rawRecord[21]); + tx.setTransactionTimestamp( + getTimeStampFromQuickLog( + readBcdIntegerFromBytes(getByteArrayPart(rawRecord, 22, + 25)), getByteArrayPart(rawRecord, 26, 28)), + true); + tx.setRemainingBalance(getAmountFromBytes(getByteArrayPart(rawRecord, + 29, 32))); + // and raw entry + tx.setRawEntry(rawRecord); + return tx; + } + + /** + * Try to parse the raw byte array into an object + * + * @param rawRecord + * (without status word + * @return the parsed record or null if something could not be + * parsed + */ + private EmvTransactionLogEntry parseMastercardTxLogEntryFromByteArray( + byte[] rawRecord) { - // TODO: currently hardcoded to log format of Austrian cards + // hardcoded to log format of Mastercard (2014) // 9F 4F - 1A bytes: Log Format // -------------------------------------- @@ -848,7 +976,7 @@ private TransactionLogEntry parseMastercardTxLogEntryFromByteArray( return null; } - TransactionLogEntry tx = new TransactionLogEntry(); + EmvTransactionLogEntry tx = new EmvTransactionLogEntry(); try { tx.setCryptogramInformationData(rawRecord[0]); tx.setAmount(getAmountFromBcdBytes(getByteArrayPart(rawRecord, 1, 6))); @@ -879,16 +1007,70 @@ private TransactionLogEntry parseMastercardTxLogEntryFromByteArray( * @return the parsed record or null if something could not be * parsed */ - private TransactionLogEntry parseBankomatTxLogEntryFromByteArray( + private EmvTransactionLogEntry parseVisaTxLogEntryFromByteArray( byte[] rawRecord) { - // TODO: change this method to parse tx log entries dynamically based on - // format as specified by card - // UPDATE: according users' responses it seems that all Austrian - // Cards use the same logging structure (of course, all the cards come - // from the same issuer). So I keep this for now.. + // hardcoded to log format of Mastercard (2014) + + // 9F 4F - 1A bytes: Log Format + // -------------------------------------- + // 9F 36 (02 bytes) -> Application Transaction Counter (ATC) + // 9F 02 (06 bytes) -> Amount, Authorised (Numeric) + // 9F 03 (06 bytes) -> Amount, Other (Numeric) + // 9F 1A (02 bytes) -> Terminal Country Code + // 95 (05 bytes) -> Terminal Verification Results (TVR) + // 5F 2A (02 bytes) -> Transaction Currency Code + // 9A (03 bytes) -> Transaction Date + // 9C (01 bytes) -> Transaction Type + // 9F 80 (04 bytes) -> [UNHANDLED TAG] + + if (rawRecord.length < LOG_LENGTH_VISA) { + Log.w(TAG, + "parseTxLogEntryFromByteArray: byte array is not long enough for VISA log entry:\n" + + prettyPrintString(bytesToHex(rawRecord), 2)); + return null; + } + + EmvTransactionLogEntry tx = new EmvTransactionLogEntry(); + try { + tx.setAtc(byteArrayToInt(getByteArrayPart(rawRecord, 0, 1))); + tx.setAmount(getAmountFromBcdBytes(getByteArrayPart(rawRecord, 2, 7))); + // TODO: currently we ignore "Amount, Other" for VISA logs + // 8-13 + // TODO: include terminal country code (14-15) + // TODO: include TVR for VISA (16-20) + tx.setCurrency(Iso4217CurrencyCodes + .getCurrencyAsString(getByteArrayPart(rawRecord, 21, 22))); + tx.setTransactionTimestamp( + getDateFromBcdBytes(getByteArrayPart(rawRecord, 23, 25)), + false); + // TODO: include Transaction Type (26) + // TODO: include unknown tag 9f 80 (27-30) + + tx.setRawEntry(rawRecord); + } catch (Exception e) { + String msg = "Exception while trying to parse transaction entry: " + + e + "\n" + e.getMessage() + "\nraw byte array:\n" + + prettyPrintString(bytesToHex(rawRecord), 2); + Log.w(TAG, msg, e); + _ctl.log(msg); + return null; + } + return tx; + } + + /** + * Try to parse the raw byte array into an object + * + * @param rawRecord + * (without status word + * @return the parsed record or null if something could not be + * parsed + */ + private EmvTransactionLogEntry parseBankomatTxLogEntryFromByteArray( + byte[] rawRecord) { - // TODO: currently hardcoded to log format of Austrian cards + // hardcoded to log format of Austrian cards // 9F 4F - 1A bytes: Log Format // -------------------------------------- @@ -910,7 +1092,7 @@ private TransactionLogEntry parseBankomatTxLogEntryFromByteArray( return null; } - TransactionLogEntry tx = new TransactionLogEntry(); + EmvTransactionLogEntry tx = new EmvTransactionLogEntry(); try { tx.setCryptogramInformationData(rawRecord[0]); tx.setAmount(getAmountFromBcdBytes(getByteArrayPart(rawRecord, 1, 6))); diff --git a/src/at/zweng/bankomatinfos/model/AbstractTransactionLogEntry.java b/src/at/zweng/bankomatinfos/model/AbstractTransactionLogEntry.java new file mode 100644 index 0000000..c55a207 --- /dev/null +++ b/src/at/zweng/bankomatinfos/model/AbstractTransactionLogEntry.java @@ -0,0 +1,105 @@ +package at.zweng.bankomatinfos.model; + +import java.util.Date; + +/** + * base class for transaction log entries + * + * @author john + */ +public abstract class AbstractTransactionLogEntry { + + protected Date _transactionTimestamp; + protected long _amount; + protected int _atc; + protected String _currency; + private byte[] _rawEntry; + protected boolean _hasTime; + + /** + * @return the _transactionTimestamp + */ + public Date getTransactionTimestamp() { + return _transactionTimestamp; + } + + /** + * @param transactionTimestamp + * the _transactionTimestamp to set + * @param true if timestamp also contains time (not just date) + */ + public void setTransactionTimestamp(Date transactionTimestamp, + boolean includesTime) { + this._transactionTimestamp = transactionTimestamp; + this._hasTime = includesTime; + } + + /** + * @return the _amount + */ + public long getAmount() { + return _amount; + } + + /** + * @param _amount + * the _amount to set + */ + public void setAmount(long amount) { + this._amount = amount; + } + + /** + * @return the _currency + */ + public String getCurrency() { + return _currency; + } + + /** + * @param _currency + * the _currency to set + */ + public void setCurrency(String currency) { + this._currency = currency; + } + + /** + * @return the _atc (application transaction counter) + */ + public int getAtc() { + return _atc; + } + + /** + * @param atc + * the _atc (application transaction counter) to set + */ + public void setAtc(int atc) { + this._atc = atc; + } + + /** + * @return the _rawEntry + */ + public byte[] getRawEntry() { + return _rawEntry; + } + + /** + * @param _rawEntry + * the _rawEntry to set + */ + public void setRawEntry(byte[] rawEntry) { + this._rawEntry = rawEntry; + } + + /** + * @return true if timestamp contains date + time, + * false otherwise + */ + public boolean hasTime() { + return _hasTime; + } + +} \ No newline at end of file diff --git a/src/at/zweng/bankomatinfos/model/CardInfo.java b/src/at/zweng/bankomatinfos/model/CardInfo.java index c98534b..e2ab14f 100644 --- a/src/at/zweng/bankomatinfos/model/CardInfo.java +++ b/src/at/zweng/bankomatinfos/model/CardInfo.java @@ -28,7 +28,8 @@ public class CardInfo { private String _quickCurrency; private Context _ctx; - private List _transactionLog; + private List _quickLog; + private List _transactionLog; private List _infoKeyValuePairs; /** @@ -36,7 +37,8 @@ public class CardInfo { */ public CardInfo(Context ctx) { // create empty list - this._transactionLog = new ArrayList(); + this._transactionLog = new ArrayList(); + this._quickLog = new ArrayList(); this._infoKeyValuePairs = new ArrayList(); this._pinRetryCounter = -1; this._quickCurrency = ""; @@ -61,10 +63,25 @@ public void setNfcTagId(byte[] nfcTagId) { + bytesToHex(nfcTagId))); } + /** + * @return the _quickLog + */ + public List getQuickLog() { + return _quickLog; + } + + /** + * @param _quickLog + * the _quickLog to set + */ + public void setQuickLog(List quickLog) { + this._quickLog = quickLog; + } + /** * @return the _transactionLog */ - public List getTransactionLog() { + public List getTransactionLog() { return _transactionLog; } @@ -72,7 +89,7 @@ public List getTransactionLog() { * @param _transactionLog * the _transactionLog to set */ - public void setTransactionLog(List transactionLog) { + public void setTransactionLog(List transactionLog) { this._transactionLog = transactionLog; } @@ -157,6 +174,13 @@ public boolean isSupportedCard() { return _quickCard || _maestroCard || _masterCard || _visaCard; } + /** + * @return true if is (one of the supported) EMV cards (not quick) + */ + public boolean isEmvCard() { + return _maestroCard || _masterCard || _visaCard; + } + /** * @return true card contains TX logs */ diff --git a/src/at/zweng/bankomatinfos/model/TransactionLogEntry.java b/src/at/zweng/bankomatinfos/model/EmvTransactionLogEntry.java similarity index 50% rename from src/at/zweng/bankomatinfos/model/TransactionLogEntry.java rename to src/at/zweng/bankomatinfos/model/EmvTransactionLogEntry.java index 077aead..5cb9cf0 100644 --- a/src/at/zweng/bankomatinfos/model/TransactionLogEntry.java +++ b/src/at/zweng/bankomatinfos/model/EmvTransactionLogEntry.java @@ -1,95 +1,24 @@ package at.zweng.bankomatinfos.model; import static at.zweng.bankomatinfos.util.Utils.*; -import java.util.Date; /** * Represents a single entry in the cards transaction log * * @author Johannes Zweng */ -public class TransactionLogEntry { +public class EmvTransactionLogEntry extends AbstractTransactionLogEntry { - private Date _transactionTimestamp; - private long _amount; - private int _atc; - private String _currency; - private byte _cryptogramInformation; + private Byte _cryptogramInformation; private byte[] _applicationDefaultAction; private byte[] _customerExclusiveData; // TAG "DF 3E" private Byte _unknownByte; - private byte[] _rawEntry; - - private boolean _hasTime; - - /** - * @return the _transactionTimestamp - */ - public Date getTransactionTimestamp() { - return _transactionTimestamp; - } - - /** - * @param transactionTimestamp - * the _transactionTimestamp to set - * @param true if timestamp also contains time (not just date) - */ - public void setTransactionTimestamp(Date transactionTimestamp, - boolean includesTime) { - this._transactionTimestamp = transactionTimestamp; - this._hasTime = includesTime; - } - - /** - * @return the _amount - */ - public long getAmount() { - return _amount; - } - - /** - * @param _amount - * the _amount to set - */ - public void setAmount(long amount) { - this._amount = amount; - } - - /** - * @return the _currency - */ - public String getCurrency() { - return _currency; - } - - /** - * @param _currency - * the _currency to set - */ - public void setCurrency(String currency) { - this._currency = currency; - } - - /** - * @return the _atc (application transaction counter) - */ - public int getAtc() { - return _atc; - } - - /** - * @param atc - * the _atc (application transaction counter) to set - */ - public void setAtc(int atc) { - this._atc = atc; - } /** * @return the _cryptogramInformation */ - public byte getCryptogramInformationData() { + public Byte getCryptogramInformationData() { return _cryptogramInformation; } @@ -146,29 +75,6 @@ public void setUnknownByte(byte unknownByte) { this._unknownByte = unknownByte; } - /** - * @return the _rawEntry - */ - public byte[] getRawEntry() { - return _rawEntry; - } - - /** - * @param _rawEntry - * the _rawEntry to set - */ - public void setRawEntry(byte[] rawEntry) { - this._rawEntry = rawEntry; - } - - /** - * @return true if timestamp contains date + time, - * false otherwise - */ - public boolean hasTime() { - return _hasTime; - } - /* * (non-Javadoc) * @@ -177,7 +83,7 @@ public boolean hasTime() { @Override public String toString() { StringBuilder sb = new StringBuilder( - "TransactionLogEntry [\n - transactionTimestamp: "); + "EmvTransactionLogEntry [\n - transactionTimestamp: "); sb.append(formatDateWithTime(_transactionTimestamp)); sb.append("\n - includes time: " + _hasTime); @@ -185,9 +91,11 @@ public String toString() { sb.append(formatBalance(_amount) + "\n - atc: " + _atc); sb.append("\n - currency: " + _currency); sb.append("\n - cryptogramInformationData: "); - sb.append(byte2Hex(_cryptogramInformation)); - sb.append("\n - applicationDefaultAction: "); - sb.append(bytesToHexNullAllowed(_applicationDefaultAction)); + if (_cryptogramInformation != null) { + sb.append(byte2Hex(_cryptogramInformation)); + sb.append("\n - applicationDefaultAction: "); + sb.append(bytesToHexNullAllowed(_applicationDefaultAction)); + } if (_customerExclusiveData != null) { sb.append("\n - customerExclusiveData: "); sb.append(bytesToHex(_customerExclusiveData)); diff --git a/src/at/zweng/bankomatinfos/model/QuickTransactionLogEntry.java b/src/at/zweng/bankomatinfos/model/QuickTransactionLogEntry.java new file mode 100644 index 0000000..9ad6bcf --- /dev/null +++ b/src/at/zweng/bankomatinfos/model/QuickTransactionLogEntry.java @@ -0,0 +1,105 @@ +package at.zweng.bankomatinfos.model; + +import static at.zweng.bankomatinfos.util.Utils.byte2Hex; +import static at.zweng.bankomatinfos.util.Utils.formatBalance; +import static at.zweng.bankomatinfos.util.Utils.formatDateWithTime; + +/** + * represents transaction logs for Quick (Austrian e-purse system) + * + * @author john + */ +public class QuickTransactionLogEntry extends AbstractTransactionLogEntry { + + // in superclass: + // protected Date _transactionTimestamp; + // protected long _amount; + // protected int _atc; + // protected String _currency; + // private byte[] _rawEntry; + // protected boolean _hasTime; + + private long _amount2; + private long _remainingBalance; + private long _terminalInfos1; + private long _terminalInfos2; + private Byte _unknownByte1; + private Byte _unknownByte2; + + public long getAmount2() { + return _amount2; + } + + public void setAmount2(long _amount2) { + this._amount2 = _amount2; + } + + public Byte getUnknownByte1() { + return _unknownByte1; + } + + public void setUnknownByte1(Byte _unknownByte1) { + this._unknownByte1 = _unknownByte1; + } + + public Byte getUnknownByte2() { + return _unknownByte2; + } + + public void setUnknownByte2(Byte _unknownByte2) { + this._unknownByte2 = _unknownByte2; + } + + public long getRemainingBalance() { + return _remainingBalance; + } + + public void setRemainingBalance(long _remainingBalance) { + this._remainingBalance = _remainingBalance; + } + + public long getTerminalInfos1() { + return _terminalInfos1; + } + + public void setTerminalInfos1(long _terminalInfos1) { + this._terminalInfos1 = _terminalInfos1; + } + + public long getTerminalInfos2() { + return _terminalInfos2; + } + + public void setTerminalInfos2(long _terminalInfos2s) { + this._terminalInfos2 = _terminalInfos2s; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder( + "QuickTransactionLogEntry [\n - transactionTimestamp: "); + sb.append(formatDateWithTime(_transactionTimestamp)); + sb.append("\n - includes time: " + _hasTime); + sb.append("\n - amount: "); + sb.append(formatBalance(_amount)); + sb.append("\n - amount2: "); + sb.append(formatBalance(_amount2)); + sb.append("\n - remaining balance: "); + sb.append(formatBalance(_remainingBalance)); + sb.append("\n - atc: " + _atc); + sb.append("\n - currency: " + _currency); + if (_unknownByte1 != null) { + sb.append("\n - unknown byte 1: "); + sb.append(byte2Hex(_unknownByte1)); + } + if (_unknownByte2 != null) { + sb.append("\n - unknown byte 2: "); + sb.append(byte2Hex(_unknownByte2)); + } + sb.append("\n - terminal info 1: " + _terminalInfos1); + sb.append("\n - terminal info 2: " + _terminalInfos2); + sb.append("\n"); + return sb.toString(); + } + +} diff --git a/src/at/zweng/bankomatinfos/ui/ListAdapterTransactions.java b/src/at/zweng/bankomatinfos/ui/ListAdapterEmvTransactions.java similarity index 75% rename from src/at/zweng/bankomatinfos/ui/ListAdapterTransactions.java rename to src/at/zweng/bankomatinfos/ui/ListAdapterEmvTransactions.java index c2cc471..296c000 100644 --- a/src/at/zweng/bankomatinfos/ui/ListAdapterTransactions.java +++ b/src/at/zweng/bankomatinfos/ui/ListAdapterEmvTransactions.java @@ -24,7 +24,7 @@ import android.widget.BaseAdapter; import android.widget.TextView; import at.zweng.bankomatinfos.AppController; -import at.zweng.bankomatinfos.model.TransactionLogEntry; +import at.zweng.bankomatinfos.model.EmvTransactionLogEntry; import at.zweng.bankomatinfos2.R; /** @@ -32,17 +32,17 @@ * * @author Johannes Zweng */ -public class ListAdapterTransactions extends BaseAdapter { +public class ListAdapterEmvTransactions extends BaseAdapter { private Context _context; - private List _txList; + private List _txList; private SparseBooleanArray itemExpandedStateMap; private int expandedElementId = -1; /** * Constructor */ - public ListAdapterTransactions(Context ctx) { + public ListAdapterEmvTransactions(Context ctx) { this._context = ctx; this._txList = AppController.getInstance().getCardInfoNullSafe(ctx) .getTransactionLog(); @@ -67,7 +67,7 @@ public long getItemId(int position) { @Override public View getView(int position, View v, ViewGroup parent) { - TransactionLogEntry tx; + EmvTransactionLogEntry tx; tx = _txList.get(position); // read setting value boolean showFullTxData = (itemExpandedStateMap.get(position, false)); @@ -98,14 +98,18 @@ public View getView(int position, View v, ViewGroup parent) { // only if the stated is expanded full tx data if (showFullTxData) { + TextView cryptogramInformationLabel = (TextView) v + .findViewById(R.id.txListItemCryptogramInformationDataLabel); TextView cryptogramInformation = (TextView) v .findViewById(R.id.txListItemCryptogramInformationData); TextView cryptogramInformationExplained = (TextView) v .findViewById(R.id.txListItemCryptogramInformationDataExplained); TextView atc = (TextView) v.findViewById(R.id.txListItemATC); + TextView appDefaultActionLabel = (TextView) v + .findViewById(R.id.txListItemDefaultActionLabel); TextView appDefaultAction = (TextView) v - .findViewById(R.id.txListItemApplicationDefaultAction); + .findViewById(R.id.txListItemDefaultAction); TextView unknownByte = (TextView) v .findViewById(R.id.txListItemUnknownByte); TextView unknownByteLabel = (TextView) v @@ -116,16 +120,27 @@ public View getView(int position, View v, ViewGroup parent) { .findViewById(R.id.txListItemCustomerExclusiveDataLabel); TextView rawData = (TextView) v.findViewById(R.id.txListRawData); - cryptogramInformation.setText("0x" - + byte2Hex(tx.getCryptogramInformationData())); - cryptogramInformationExplained - .setText(explainCryptogramInformationByte( - tx.getCryptogramInformationData(), _context)); + if (tx.getCryptogramInformationData() != null) { + cryptogramInformation.setText("0x" + + byte2Hex(tx.getCryptogramInformationData())); + cryptogramInformationExplained + .setText(explainCryptogramInformationByte( + tx.getCryptogramInformationData(), _context)); + } else { + cryptogramInformationLabel.setVisibility(View.GONE); + cryptogramInformation.setVisibility(View.GONE); + cryptogramInformationExplained.setVisibility(View.GONE); + } atc.setText(Integer.toString(tx.getAtc())); - appDefaultAction.setText(prettyPrintString( - bytesToHex(tx.getApplicationDefaultAction()), 2)); - if (tx.getUnknownByte()!=null) { - unknownByte.setText(byte2Hex(tx.getUnknownByte())); + if (tx.getApplicationDefaultAction() != null) { + appDefaultAction.setText(prettyPrintString( + bytesToHex(tx.getApplicationDefaultAction()), 2)); + } else { + appDefaultAction.setVisibility(View.GONE); + appDefaultActionLabel.setVisibility(View.GONE); + } + if (tx.getUnknownByte() != null) { + unknownByte.setText(byte2Hex(tx.getUnknownByte())); } else { unknownByte.setVisibility(View.GONE); unknownByteLabel.setVisibility(View.GONE); diff --git a/src/at/zweng/bankomatinfos/ui/ListAdapterQuickTransactions.java b/src/at/zweng/bankomatinfos/ui/ListAdapterQuickTransactions.java new file mode 100644 index 0000000..33b2d5b --- /dev/null +++ b/src/at/zweng/bankomatinfos/ui/ListAdapterQuickTransactions.java @@ -0,0 +1,158 @@ +/** + * + */ +package at.zweng.bankomatinfos.ui; + +import static at.zweng.bankomatinfos.util.Utils.byte2Hex; +import static at.zweng.bankomatinfos.util.Utils.bytesToHex; +import static at.zweng.bankomatinfos.util.Utils.formatBalance; +import static at.zweng.bankomatinfos.util.Utils.formatDateOnly; +import static at.zweng.bankomatinfos.util.Utils.formatDateWithTime; +import static at.zweng.bankomatinfos.util.Utils.prettyPrintString; + +import java.util.List; + +import android.app.Activity; +import android.content.Context; +import android.util.SparseBooleanArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.widget.BaseAdapter; +import android.widget.TextView; +import at.zweng.bankomatinfos.AppController; +import at.zweng.bankomatinfos.model.QuickTransactionLogEntry; +import at.zweng.bankomatinfos2.R; + +/** + * list adapter for the Quick transaction list + * + * @author Johannes Zweng + */ +public class ListAdapterQuickTransactions extends BaseAdapter { + + private Context _context; + private List _txList; + private SparseBooleanArray itemExpandedStateMap; + private int expandedElementId = -1; + + /** + * Constructor + */ + public ListAdapterQuickTransactions(Context ctx) { + this._context = ctx; + this._txList = AppController.getInstance().getCardInfoNullSafe(ctx) + .getQuickLog(); + itemExpandedStateMap = new SparseBooleanArray(); + } + + @Override + public int getCount() { + return _txList.size(); + } + + @Override + public Object getItem(int position) { + return _txList.get(position); + } + + @Override + public long getItemId(int position) { + // we simply use position in list as ID for events + return position; + } + + @Override + public View getView(int position, View v, ViewGroup parent) { + QuickTransactionLogEntry tx; + tx = _txList.get(position); + // read setting value + boolean showFullTxData = (itemExpandedStateMap.get(position, false)); + + // if (v == null) { + LayoutInflater mInflater = (LayoutInflater) _context + .getSystemService(Activity.LAYOUT_INFLATER_SERVICE); + if (showFullTxData) { + v = mInflater.inflate( + R.layout.list_item_quick_transaction_expanded, null); + } else { + v = mInflater.inflate(R.layout.list_item_transaction_collapsed, + null); + } + // } + + TextView timeStamp = (TextView) v + .findViewById(R.id.txListItemTimestamp); + TextView amount = (TextView) v.findViewById(R.id.txListItemAmount); + + if (tx.hasTime()) { + timeStamp.setText(formatDateWithTime(tx.getTransactionTimestamp())); + } else { + timeStamp.setText(formatDateOnly(tx.getTransactionTimestamp())); + } + + // TODO: we have 2 amounts, currently we simply display the larger one + long txAmount = Math.max(tx.getAmount(), tx.getAmount2()); + amount.setText("-" + formatBalance(txAmount) + " " + tx.getCurrency()); + + // only if the stated is expanded full tx data + if (showFullTxData) { + TextView atc = (TextView) v.findViewById(R.id.txListItemATC); + TextView remainingBalance = (TextView) v + .findViewById(R.id.txListItemRemainingBalance); + TextView termInfo = (TextView) v + .findViewById(R.id.txListItemTermInfo); + TextView unknownByte = (TextView) v + .findViewById(R.id.txListItemUnknownByte); + TextView rawData = (TextView) v.findViewById(R.id.txListRawData); + + atc.setText(Integer.toString(tx.getAtc())); + remainingBalance.setText(formatBalance(tx.getRemainingBalance()) + + " " + tx.getCurrency()); + termInfo.setText(Long.toString(tx.getTerminalInfos1()) + " / " + + Long.toString(tx.getTerminalInfos2())); + unknownByte.setText("0x"+byte2Hex(tx.getUnknownByte1()) + " / " + + "0x"+byte2Hex(tx.getUnknownByte2())); + rawData.setText(prettyPrintString(bytesToHex(tx.getRawEntry()), 2)); + } + + Animation animation; + if (position == expandedElementId) { + expandedElementId = -1; + animation = new AlphaAnimation(0, 1); + animation.setDuration(90); + v.startAnimation(animation); + animation = null; + } + return v; + } + + /** + * @param position + */ + public void toggleItemExpandedState(int position) { + + // EXPAND: + if (itemExpandedStateMap.get(position, false) == false) { + expandedElementId = position; + // and if we expanded an element collapse all other elements (so + // that only 1 elements is expanded) + for (int i = 0; i < itemExpandedStateMap.size(); i++) { + if (i != position) { + itemExpandedStateMap.put(i, false); + } + } + itemExpandedStateMap.put(position, true); + } + + // COLLAPSE: + else { + expandedElementId = -1; + itemExpandedStateMap.put(position, false); + } + + super.notifyDataSetChanged(); + } +} diff --git a/src/at/zweng/bankomatinfos/ui/ResultActivity.java b/src/at/zweng/bankomatinfos/ui/ResultActivity.java index 2d442f0..b5bde2d 100644 --- a/src/at/zweng/bankomatinfos/ui/ResultActivity.java +++ b/src/at/zweng/bankomatinfos/ui/ResultActivity.java @@ -19,7 +19,6 @@ import android.view.MenuItem; import android.widget.ShareActionProvider; import at.zweng.bankomatinfos.AppController; -import at.zweng.bankomatinfos.util.Utils; import at.zweng.bankomatinfos2.R; // TODO: maybe also add share action for general and transations fragment @@ -34,10 +33,12 @@ public class ResultActivity extends FragmentActivity implements private static AppController _controller = AppController.getInstance(); private Fragment _fragmentResultInfos; - private Fragment _fragmentResultTxList; + private Fragment _fragmentResultEmxTxList; + private Fragment _fragmentResultQuickTxList; private Fragment _fragmentResultLog; - - private boolean _alertShown = false; + private boolean _showQuickLog; + private boolean _showEmvLog; + private int _numLogTabs; /** * The {@link android.support.v4.view.PagerAdapter} that will provide @@ -58,9 +59,21 @@ public class ResultActivity extends FragmentActivity implements protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_result); - + _showQuickLog = (_controller.getCardInfoNullSafe(this).getQuickLog() + .size() > 0); + _showEmvLog = _controller.getCardInfoNullSafe(this).isEmvCard(); + if (_showEmvLog && _showQuickLog) { + _numLogTabs = 2; + } else { + _numLogTabs = 1; + } _fragmentResultInfos = new ResultInfosListFragment(); - _fragmentResultTxList = new ResultTxListFragment(); + if (_showEmvLog) { + _fragmentResultEmxTxList = new ResultEmvTxListFragment(); + } + if (_showQuickLog) { + _fragmentResultQuickTxList = new ResultQuickTxListFragment(); + } _fragmentResultLog = new ResultLogFragment(); // Set up the action bar. @@ -128,7 +141,9 @@ public boolean onCreateOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) { // show share action only on Tab 2 (Log) // (tab index starts with 0) - if (_viewPager.getCurrentItem() == 2) { + if (_viewPager.getCurrentItem() == 2 && _numLogTabs == 1) { + menu.findItem(R.id.action_share).setVisible(true); + } else if (_viewPager.getCurrentItem() == 3 && _numLogTabs == 2) { menu.findItem(R.id.action_share).setVisible(true); } else { menu.findItem(R.id.action_share).setVisible(false); @@ -161,23 +176,7 @@ public void onTabSelected(ActionBar.Tab tab, // When the given tab is selected, switch to the corresponding page in // the ViewPager. _viewPager.setCurrentItem(tab.getPosition()); - - // show alert dialog for one time if card does not support tx logs - if (!_alertShown - && tab.getPosition() == 1 - && !_controller.getCardInfoNullSafe(this).containsTxLogs() - && _controller.getCardInfoNullSafe(this).getTransactionLog() - .size() == 0) { - _alertShown = true; - Utils.displaySimpleAlertDialog( - this, - getResources().getString(R.string.tx_log_alertdialog_title), - this.getResources().getString( - R.string.tx_log_alertdialog_text)); - } - - invalidateOptionsMenu(); // creates call to - // onPrepareOptionsMenu() + invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() } @Override @@ -204,8 +203,12 @@ public SectionsPagerAdapter(FragmentManager fm) { public Fragment getItem(int position) { if (position == 0) { return _fragmentResultInfos; - } else if (position == 1) { - return _fragmentResultTxList; + } else if (position == 1 && _showEmvLog) { + return _fragmentResultEmxTxList; + } else if (position == 1 && !_showEmvLog && _showQuickLog) { + return _fragmentResultQuickTxList; + } else if (position == 2 && _showEmvLog && _showQuickLog) { + return _fragmentResultQuickTxList; } else { return _fragmentResultLog; } @@ -213,22 +216,24 @@ public Fragment getItem(int position) { @Override public int getCount() { - // Show 3 total pages. - return 3; + return 2 + _numLogTabs; } @Override public CharSequence getPageTitle(int position) { Locale locale = Locale.getDefault(); - switch (position) { - case 0: - return getString(R.string.title_section1).toUpperCase(locale); - case 1: - return getString(R.string.title_section2).toUpperCase(locale); - case 2: - return getString(R.string.title_section3).toUpperCase(locale); + + if (position == 0) { + return getString(R.string.title_section_infos).toUpperCase(locale); + } else if (position == 1 && _showEmvLog) { + return getString(R.string.title_section_emv_logs).toUpperCase(locale); + } else if (position == 1 && !_showEmvLog && _showQuickLog) { + return getString(R.string.title_section_quick_logs).toUpperCase(locale); + } else if (position == 2 && _showEmvLog && _showQuickLog) { + return getString(R.string.title_section_quick_logs).toUpperCase(locale); + } else { + return getString(R.string.title_section_debug_log).toUpperCase(locale); } - return null; } } diff --git a/src/at/zweng/bankomatinfos/ui/ResultTxListFragment.java b/src/at/zweng/bankomatinfos/ui/ResultEmvTxListFragment.java similarity index 88% rename from src/at/zweng/bankomatinfos/ui/ResultTxListFragment.java rename to src/at/zweng/bankomatinfos/ui/ResultEmvTxListFragment.java index 8f60703..75904c5 100644 --- a/src/at/zweng/bankomatinfos/ui/ResultTxListFragment.java +++ b/src/at/zweng/bankomatinfos/ui/ResultEmvTxListFragment.java @@ -14,11 +14,11 @@ /** * A simple Fragment subclass, showing the transaction list. */ -public class ResultTxListFragment extends Fragment { +public class ResultEmvTxListFragment extends Fragment { private ListView _listView; private TextView _noEntriesText; - private ListAdapterTransactions _listAdapter; + private ListAdapterEmvTransactions _listAdapter; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -27,7 +27,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, false); _listView = (ListView) v.findViewById(R.id.listviewTxList); _noEntriesText = (TextView) v.findViewById(R.id.lblNoEntriesAvailable); - _listAdapter = new ListAdapterTransactions(getActivity()); + _listAdapter = new ListAdapterEmvTransactions(getActivity()); _listView.setAdapter(_listAdapter); _listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override diff --git a/src/at/zweng/bankomatinfos/ui/ResultQuickTxListFragment.java b/src/at/zweng/bankomatinfos/ui/ResultQuickTxListFragment.java new file mode 100644 index 0000000..4e193bd --- /dev/null +++ b/src/at/zweng/bankomatinfos/ui/ResultQuickTxListFragment.java @@ -0,0 +1,37 @@ +package at.zweng.bankomatinfos.ui; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ListView; +import at.zweng.bankomatinfos2.R; + +/** + * A simple Fragment subclass, showing the quick tx list. + */ +public class ResultQuickTxListFragment extends Fragment { + + private ListView _listView; + private ListAdapterQuickTransactions _listAdapter; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.fragment_result_tx_list, container, + false); + _listView = (ListView) v.findViewById(R.id.listviewTxList); + _listAdapter = new ListAdapterQuickTransactions(getActivity()); + _listView.setAdapter(_listAdapter); + _listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, + int position, long id) { + _listAdapter.toggleItemExpandedState(position); + } + }); + return v; + } +} diff --git a/src/at/zweng/bankomatinfos/util/Utils.java b/src/at/zweng/bankomatinfos/util/Utils.java index 92cf3ad..2519184 100644 --- a/src/at/zweng/bankomatinfos/util/Utils.java +++ b/src/at/zweng/bankomatinfos/util/Utils.java @@ -216,6 +216,20 @@ public static long readLongFromBytes(byte[] rawData, int offset, int lenght) return result; } + /** + * Parses a BCD encoded integer from the given byte array + * + * @param data + * @return + */ + public static int readBcdIntegerFromBytes(byte[] data) { + if (data.length > 4) { + throw new InvalidParameterException( + "cannot parse more than 4 bytes into int type"); + } + return Integer.parseInt(bytesToHex(data)); + } + /** * Display a alert dialog * @@ -245,6 +259,9 @@ public void onClick(DialogInterface dialog, int id) { * @return */ public static String formatDateWithTime(Date d) { + if (d == null) { + return "00.00.0000 00:00:00"; + } return fullTimeWithDateFormat.format(d); } @@ -255,6 +272,9 @@ public static String formatDateWithTime(Date d) { * @return */ public static String formatDateOnly(Date d) { + if (d == null) { + return "00.00.0000"; + } return dateOnlyDateFormat.format(d); }