diff --git a/app/src/main/java/org/dslul/ticketreader/MainActivity.java b/app/src/main/java/org/dslul/ticketreader/MainActivity.java index 334a36d..e9a5056 100755 --- a/app/src/main/java/org/dslul/ticketreader/MainActivity.java +++ b/app/src/main/java/org/dslul/ticketreader/MainActivity.java @@ -1,360 +1,360 @@ package org.dslul.ticketreader; import android.annotation.SuppressLint; import android.nfc.tech.IsoDep; import android.os.Bundle; import android.os.CountDownTimer; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.CardView; import android.support.v7.widget.Toolbar; import android.view.View; import android.view.Menu; import android.view.MenuItem; import android.widget.ImageView; import android.widget.TableLayout; import android.widget.TextView; import java.util.Calendar; import java.util.concurrent.TimeUnit; import android.nfc.NfcAdapter; import android.nfc.tech.NfcA; import android.widget.Toast; import android.content.Intent; import android.content.IntentFilter; import android.app.PendingIntent; import android.os.Handler; import android.os.Message; import android.app.AlertDialog; import android.content.DialogInterface; import com.google.android.gms.ads.AdRequest; import com.google.android.gms.ads.AdView; import com.google.android.gms.ads.MobileAds; public class MainActivity extends AppCompatActivity { private NfcAdapter mNfcAdapter; private IntentFilter tech; private IntentFilter[] intentFiltersArray; private PendingIntent pendingIntent; private Intent intent; private AlertDialog alertDialog; private Toast currentToast; private byte[] pages = {(byte)0xFF}; private AdView adview; private ImageView imageNfc; private CardView ticketCard; private CardView statusCard; private ImageView statusImg; private TextView statoBiglietto; private TextView infoLabel; private TableLayout infoTable; private TextView tipologia; private TextView dataLabel; private TextView dataObliterazione; private TextView corseRimanenti; private CountDownTimer timer; private static final int ACTION_NONE = 0; private static final int ACTION_READ = 1; private int scanAction; // list of NFC technologies detected: private final String[][] techListsArray = new String[][] { new String[] { //MifareUltralight.class.getName(), NfcA.class.getName() }, new String[] { IsoDep.class.getName() } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); adview = (AdView) findViewById(R.id.adView); imageNfc = (ImageView) findViewById(R.id.imagenfcView); ticketCard = (CardView) findViewById(R.id.ticketCardView); statusCard = (CardView) findViewById(R.id.statusCardView); statusImg = (ImageView) findViewById(R.id.statusImg); statoBiglietto = (TextView) findViewById(R.id.stato_biglietto); infoLabel = (TextView) findViewById(R.id.infolabel); infoTable = (TableLayout) findViewById(R.id.info_table); tipologia = (TextView) findViewById(R.id.tipologia); dataLabel = (TextView) findViewById(R.id.validation_or_expire); dataObliterazione = (TextView) findViewById(R.id.data_obliterazione); corseRimanenti = (TextView) findViewById(R.id.corse_rimaste); MobileAds.initialize(this, "ca-app-pub-2102716674867426~1964394961"); AdRequest adRequest = new AdRequest.Builder().build(); adview.loadAd(adRequest); mNfcAdapter = NfcAdapter.getDefaultAdapter(this); if (mNfcAdapter == null) { Toast.makeText(this, R.string.nfc_not_supported, Toast.LENGTH_LONG).show(); finish(); return; } if (!mNfcAdapter.isEnabled()) { Toast.makeText(this, R.string.nfc_disabled, Toast.LENGTH_LONG).show(); startActivity(new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS)); } tech = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED); intentFiltersArray = new IntentFilter[] {tech}; intent = new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); //FLAG_ACTIVITY_REORDER_TO_FRONT FLAG_RECEIVER_REPLACE_PENDING pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); scanAction = ACTION_READ; onNewIntent(getIntent()); } @Override protected void onResume() { super.onResume(); mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, this.techListsArray); } @Override protected void onPause() { // disabling foreground dispatch: //NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); mNfcAdapter.disableForegroundDispatch(this); super.onPause(); } @Override protected void onNewIntent(Intent intent) { if (intent.getAction().equals(NfcAdapter.ACTION_TECH_DISCOVERED)) { String mTextBufferText = "aa"; NfcThread nfcThread = new NfcThread(getBaseContext(), intent, scanAction, mTextBufferText, mTextBufferHandler, mToastShortHandler, mToastLongHandler, mShowInfoDialogHandler); nfcThread.start(); scanAction = ACTION_READ; } } @SuppressLint("HandlerLeak") private Handler mTextBufferHandler = new Handler() { public void handleMessage(Message msg) { pages = (byte[])msg.obj; if(timer != null) timer.cancel(); //smartcard if(pages.length > 200) { /* ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText("content", pages); clipboard.setPrimaryClip(clip); */ SmartCard smartcard = new SmartCard(pages, getBaseContext()); if(smartcard.isSubscription()) { dataLabel.setText(R.string.expire_date); - tipologia.setText(smartcard.getName()); + tipologia.setText(smartcard.getSubscriptionName()); dataObliterazione.setText(smartcard.getDate()); if(smartcard.isExpired()) { corseRimanenti.setText("0"); statoBiglietto.setText(R.string.expired); statusImg.setImageResource(R.drawable.ic_error_grey_800_36dp); statusCard.setCardBackgroundColor(getResources().getColor(R.color.colorRed)); } else { corseRimanenti.setText(R.string.unlimited); statoBiglietto.setText(R.string.valid); statusImg.setImageResource(R.drawable.ic_check_circle_grey_800_36dp); statusCard.setCardBackgroundColor(getResources().getColor(R.color.colorGreen)); } statusCard.setVisibility(View.VISIBLE); ticketCard.setVisibility(View.VISIBLE); infoLabel.setText(R.string.read_another_ticket); imageNfc.setVisibility(View.GONE); } else { createTicketInterface(smartcard.getName(),smartcard.getDate(), smartcard.getRemainingRides(), 0); Toast.makeText(getBaseContext(), R.string.smartcard_tickets_not_supported_yet, Toast.LENGTH_LONG).show(); } } //chip on paper else if(pages.length > 48) { ChipOnPaper chipOnPaper = new ChipOnPaper(pages); createTicketInterface(chipOnPaper.getTypeName(),chipOnPaper.getDate(), chipOnPaper.getRemainingRides(), chipOnPaper.getRemainingMinutes()); } else { statusCard.setVisibility(View.GONE); ticketCard.setVisibility(View.GONE); infoLabel.setText(R.string.info_instructions); imageNfc.setVisibility(View.VISIBLE); } } }; private void createTicketInterface(String name, String date, int remainingRides, long remainingMinutes) { dataLabel.setText(R.string.data_obliterazione); tipologia.setText(name); dataObliterazione.setText(date); corseRimanenti.setText(Integer.toString(remainingRides)); if(remainingMinutes != 0) { statoBiglietto.setText(R.string.in_corso); statusImg.setImageResource(R.drawable.ic_restore_grey_800_36dp); statusCard.setCardBackgroundColor(getResources().getColor(R.color.colorBlue)); Calendar calendar = Calendar.getInstance(); int sec = calendar.get(Calendar.SECOND); timer = new CountDownTimer((remainingMinutes*60 - sec)*1000, 1000) { public void onTick(long millis) { statoBiglietto.setText(String.format(getResources().getString(R.string.in_corso), TimeUnit.MILLISECONDS.toMinutes(millis), TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)))); } public void onFinish() { statoBiglietto.setText(R.string.corse_esaurite); statusImg.setImageResource(R.drawable.ic_error_grey_800_36dp); statusCard.setCardBackgroundColor(getResources().getColor(R.color.colorRed)); if(timer != null) timer.cancel(); } }.start(); } else if(remainingRides == 0 && remainingMinutes == 0) { statoBiglietto.setText(R.string.corse_esaurite); statusImg.setImageResource(R.drawable.ic_error_grey_800_36dp); statusCard.setCardBackgroundColor(getResources().getColor(R.color.colorRed)); } else if(remainingRides != 0 && remainingMinutes == 0) { statoBiglietto.setText(String.format(getResources().getString(R.string.corse_disponibili), remainingRides)); statusImg.setImageResource(R.drawable.ic_check_circle_grey_800_36dp); statusCard.setCardBackgroundColor(getResources().getColor(R.color.colorGreen)); } statusCard.setVisibility(View.VISIBLE); ticketCard.setVisibility(View.VISIBLE); infoLabel.setText(R.string.read_another_ticket); imageNfc.setVisibility(View.GONE); } private Handler mToastShortHandler = new Handler() { public void handleMessage(Message msg) { String text = (String)msg.obj; if(currentToast != null) currentToast.cancel(); currentToast = Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT); currentToast.show(); } }; private Handler mToastLongHandler = new Handler() { public void handleMessage(Message msg) { String text = (String)msg.obj; if(currentToast != null) currentToast.cancel(); currentToast = Toast.makeText(MainActivity.this, text, Toast.LENGTH_LONG); currentToast.show(); } }; private Handler mShowInfoDialogHandler = new Handler() { public void handleMessage(Message msg) { String text = (String)msg.obj; //infoDialog = showInfoDialog(text); //infoDialog.show(); } }; @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_info) { alertDialog = showAlertDialog(getString(R.string.info_message)); alertDialog.show(); return true; } return super.onOptionsItemSelected(item); } private AlertDialog showAlertDialog(String message) { DialogInterface.OnClickListener dialogInterfaceListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { alertDialog.cancel(); scanAction = ACTION_READ; } }; alertDialog = new AlertDialog.Builder(this) .setTitle(R.string.information) .setIcon(android.R.drawable.ic_dialog_info) .setMessage(message) .setPositiveButton(R.string.close_dialog, null) .create(); alertDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { public void onCancel(DialogInterface dialog) { scanAction = ACTION_READ; } }); return alertDialog; } } diff --git a/app/src/main/java/org/dslul/ticketreader/NfcThread.java b/app/src/main/java/org/dslul/ticketreader/NfcThread.java index 66468ae..5b8759f 100755 --- a/app/src/main/java/org/dslul/ticketreader/NfcThread.java +++ b/app/src/main/java/org/dslul/ticketreader/NfcThread.java @@ -1,341 +1,344 @@ package org.dslul.ticketreader; import java.io.ByteArrayOutputStream; import java.io.IOException; import android.content.ClipData; import android.content.Context; import android.content.Intent; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.tech.IsoDep; import android.nfc.tech.NfcA; import android.os.Handler; import android.os.Message; import android.content.ClipboardManager; import android.util.Log; //import android.util.Log; //code from http://www.emutag.com/soft.php public class NfcThread extends Thread { private static final int ACTION_NONE = 0; private static final int ACTION_READ = 1; private static final int ACTION_WRITE = 2; private Context context; private Intent intent; private int scanAction; private String mTextBufferText; private Handler mTextBufferHandler, mToastShortHandler, mToastLongHandler, mShowInfoDialogHandler; private byte[] readBuffer = new byte[1024]; // maximum theoretical capacity of MIFARE Ultralight private byte[] toWriteBuffer = new byte[1024]; NfcThread( Context context, Intent intent, int scanAction, String mTextBufferText, Handler mTextBufferHandler, Handler mToastShortHandler, Handler mToastLongHandler, Handler mShowInfoDialogHandler ) { this.context = context; this.intent = intent; this.scanAction = scanAction; this.mTextBufferText = mTextBufferText; this.mTextBufferHandler = mTextBufferHandler; this.mToastShortHandler = mToastShortHandler; this.mToastLongHandler = mToastLongHandler; this.mShowInfoDialogHandler = mShowInfoDialogHandler; } public void run() { final Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); if(tagFromIntent.getTechList()[0].equals(IsoDep.class.getName())) { handleIsoDep(tagFromIntent); } else { handleNfcA(tagFromIntent); } } private void handleIsoDep(Tag tagFromIntent) { IsoDep isoDep = IsoDep.get(tagFromIntent); if (isoDep != null) { try { isoDep.connect(); byte[] getinfo = {0x00, (byte)0xa4, 0x00, 0x00, 0x02, 0x20, 0x01}; isoDep.transceive(getinfo); byte[] read = {0x00, (byte)0xb2, 0x01, 0x05}; byte[] info = isoDep.transceive(read); byte[] gettickets = {0x00, (byte)0xa4, 0x00, 0x00, 0x02, 0x20, 0x20}; isoDep.transceive(gettickets); byte[] tickets = isoDep.transceive(read); ByteArrayOutputStream outputStream = new ByteArrayOutputStream( ); outputStream.write(info); outputStream.write(tickets); byte content[] = outputStream.toByteArray(); byte err[] = {(byte)0xFF}; + //override + //content = hexStringToByteArray(""); + //TODO: throw exception if(info.length < 30 || info[3] == 0) { showToastLong(context.getString(R.string.invalid_smartcard)); setTextBuffer(err); } else { setTextBuffer(content); showToastLong(context.getString(R.string.smartcard_read_correctly)); } isoDep.close(); } catch (IOException e) { e.printStackTrace(); } } } private void handleNfcA(Tag tagFromIntent) { if (scanAction == ACTION_NONE) { showToastLong("Please select READ or WRITE before scanning tag"); return; } final NfcA mfu = NfcA.get(tagFromIntent); if (mfu == null) { showToastLong(context.getString(R.string.ticket_not_supported)); return; } byte[] ATQA = mfu.getAtqa(); if (mfu.getSak() != 0x00 || ATQA.length != 2 || ATQA[0] != 0x44 || ATQA[1] != 0x00) { showToastLong(context.getString(R.string.ticket_not_supported)); return; } int pagesRead; try { //Log.i("position", "read data"); if (scanAction == ACTION_READ) { mfu.connect(); pagesRead = rdNumPages(mfu, 0); // 0 for no limit (until error) mfu.close(); byte[] content = new byte[pagesRead*4]; System.arraycopy(readBuffer, 0, content, 0, pagesRead*4); /* String content = ""; byte[] mfuPage = new byte[4]; for (int i = 0; i < pagesRead*4; i += 4) { System.arraycopy(readBuffer, i, mfuPage, 0, 4); content = content + ByteArrayToHexString(mfuPage) + System.getProperty("line.separator"); }*/ if(pagesRead >= 16 && content.length >= 16*4) { showToastShort(context.getString(R.string.ticket_correctly_read)); setTextBuffer(content); } else { showToastShort(context.getString(R.string.read_failure)); //TODO: throw error instead byte[] error = {(byte)0xFF}; setTextBuffer(error); } } } catch (Exception e) { showToastLong(context.getString(R.string.communication_error)); } } private void setTextBuffer(byte[] content) { Message msg = new Message(); msg.obj = content; mTextBufferHandler.sendMessage(msg); } private void showToastShort(String text) { Message msg = new Message(); msg.obj = text; mToastShortHandler.sendMessage(msg); } private void showToastLong(String text) { Message msg = new Message(); msg.obj = text; mToastLongHandler.sendMessage(msg); } private void showInfoDialog(String text) { Message msg = new Message(); msg.obj = text; mShowInfoDialogHandler.sendMessage(msg); } /* private int rdAllPages(NfcA mfu) { int pagesRead = 0; while (rdPages(mfu, pagesRead) == 0) { pagesRead += 4; if (pagesRead == 256) break; } return pagesRead; } */ private int rdNumPages(NfcA mfu, int num) { int pagesRead = 0; while (rdPages(mfu, pagesRead) == 0) { pagesRead++; if (pagesRead == num || pagesRead == 256) break; } return pagesRead; } /* private int rdNumPages(NfcA mfu, int num) { int pagesRead = 0; // align number of pages to a multiple of 4 num += 3; num >>>= 2; // unsigned shift num <<= 2; while (rdPages(mfu, pagesRead) == 0) { pagesRead += 4; if (pagesRead == num || pagesRead == 256) break; } return pagesRead; } */ // first failure (NAK) causes response 0x00 (or possibly other 1-byte values) // second failure (NAK) causes transceive() to throw IOException private byte rdPages(NfcA tag, int pageOffset) { byte[] cmd = {0x30, (byte)pageOffset}; byte[] response = new byte[16]; try { response = tag.transceive(cmd); } catch (IOException e) { return 1; } if (response.length != 16) return 1; //System.arraycopy(response, 0, readBuffer, pageOffset * 4, 16); System.arraycopy(response, 0, readBuffer, pageOffset * 4, 4); return 0; } // first failure (NAK) causes transceive() to throw IOException /* private byte wrPage(NfcA tag, int pageOffset) { byte[] cmd = {(byte)0xA2, (byte)pageOffset, 0x00, 0x00, 0x00, 0x00}; System.arraycopy(toWriteBuffer, pageOffset * 4, cmd, 2, 4); try { Log.i("TRANS START", Integer.toString(pageOffset)); tag.transceive(cmd); Log.i("TRANS END", Integer.toString(pageOffset)); } catch (IOException e) { return 1; } return 0; } */ private byte wrPage(NfcA mfu, int pageOffset) { byte[] cmd = {(byte)0xA2, (byte)pageOffset, 0x00, 0x00, 0x00, 0x00}; System.arraycopy(toWriteBuffer, pageOffset * 4, cmd, 2, 4); //byte[] data = {0x00, 0x00, 0x00, 0x00}; //System.arraycopy(toWriteBuffer, pageOffset * 4, data, 0, 4); //byte errors = 0; //final MifareUltralight mfu = MifareUltralight.get(tag); try { //mfu.connect(); //Log.i("TRANS START", Integer.toString(pageOffset)); //mfu.writePage(pageOffset, data); mfu.transceive(cmd); //Log.i("TRANS END", Integer.toString(pageOffset)); //mfu.close(); } catch (final IOException e) { return 1; } /* finally { try { mfu.close(); } catch (final Exception e) {} } */ return 0; } // first failure (NAK) causes transceive() to throw IOException private byte wrPageCompat(NfcA tag, int pageOffset) { byte[] cmd1 = {(byte)0xA0, (byte)pageOffset}; byte[] cmd2 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; System.arraycopy(toWriteBuffer, pageOffset * 4, cmd2, 0, 4); try { tag.transceive(cmd1); tag.transceive(cmd2); } catch (IOException e) { return 1; } return 0; } private String ByteArrayToHexString(byte[] inarray) { int i, j, in; String [] hex = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"}; String out= ""; for(j = 0 ; j < inarray.length ; ++j) { in = (int) inarray[j] & 0xff; i = (in >> 4) & 0x0f; out += hex[i]; i = in & 0x0f; out += hex[i]; } return out; } private int HexStringToByteArray(String instring, byte[] outarray, int outoffset) { int errors = 0; byte[] nibbles = new byte[2]; for (int i = 0; i < instring.length(); i += 2) { nibbles[0] = (byte)instring.charAt(i+0); nibbles[1] = (byte)instring.charAt(i+1); if (notHex(nibbles[0])) errors = 1; if (notHex(nibbles[1])) errors = 1; outarray[outoffset] = (byte)((hex2bin(nibbles[0]) << 4) | hex2bin(nibbles[1])); outoffset++; } return errors; } private byte[] hexStringToByteArray(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); } return data; } private boolean notHex(byte inchar) { if (inchar >= '0' && inchar <= '9') return false; if (inchar >= 'a' && inchar <= 'f') return false; if (inchar >= 'A' && inchar <= 'F') return false; return true; } private byte hex2bin(byte inchar) { if (inchar > 'Z') inchar -= ' '; if (inchar > '9') inchar -= 7; inchar &= 0x0f; return inchar; } } diff --git a/app/src/main/java/org/dslul/ticketreader/SmartCard.java b/app/src/main/java/org/dslul/ticketreader/SmartCard.java index f16ab7c..13cb5e6 100644 --- a/app/src/main/java/org/dslul/ticketreader/SmartCard.java +++ b/app/src/main/java/org/dslul/ticketreader/SmartCard.java @@ -1,254 +1,271 @@ package org.dslul.ticketreader; import android.content.Context; import android.util.Log; import org.dslul.ticketreader.util.GttDate; import java.text.DateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; public class SmartCard { public enum Type { BIP, PYOU, EDISU } static final Map subscriptionCodes = new HashMap() {{ put(68, "Mensile UNDER 26"); put(72, "Mensile Studenti Rete Urbana"); put(101, "Settimanale Formula 1"); put(102, "Settimanale Formula 2"); put(103, "Settimanale Formula 3"); put(104, "Settimanale Formula 4"); put(105, "Settimanale Formula 5"); put(106, "Settimanale Formula 6"); put(107, "Settimanale Formula 7"); put(108, "Settimanale Intera Area Formula"); put(109, "Settimanale Personale Rete Urbana"); put(199, "Mensile Personale Rete Urbana (Formula U)"); put(201, "Mensile Formula 1"); put(202, "Mensile Formula 2"); put(203, "Mensile Formula 3"); put(204, "Mensile Formula 4"); put(205, "Mensile Formula 5"); put(206, "Mensile Formula 6"); put(207, "Mensile Formula 7"); put(208, "Mensile Intera Area Formula"); put(261, "Mensile Studenti Urbano+Suburbano"); put(290, "Mensile 65+ Urbano Orario Ridotto"); put(291, "Mensile 65+ Urbano"); put(307, "Annuale Ivrea Rete Urbana e Dintorni"); put(308, "Annuale Extraurbano O/D"); put(310, "Plurimensile Studenti Extraurbano O/D"); put(721, "Annuale UNDER 26"); put(722, "Annuale UNDER 26 Fascia A"); put(723, "Annuale UNDER 26 Fascia B"); put(724, "Annuale UNDER 26 Fascia C"); put(730, "Mensile urbano Over 65"); put(761, "Annuale Over A"); put(731, "Annuale Over B"); put(732, "Annuale Over C"); put(733, "Annuale Over D"); put(911, "10 Mesi Studenti"); put(912, "Annuale Studenti"); put(990, "Junior"); put(993, "Annuale Formula U"); put(4003, "Annuale Formula U a Zone"); }}; static final Map ticketCodes = new HashMap() {{ put(712, "Ordinario Urbano"); put(714, "City 100"); put(715, "Daily"); put(716, "Multidaily"); }}; Context context; private class Item { private int code; private boolean isValid; private boolean isTicket; private boolean isSubscription; private Date startDate; private Date endDate; public Item(byte[] data) { //get item type code = ((data[6] & 0xff) << 8) | data[7] & 0xff; if(code == 0) { isValid = false; } else { isValid = true; } if(ticketCodes.containsKey(code)) { isTicket = true; isSubscription = false; } else if(subscriptionCodes.containsKey(code)) { isTicket = false; isSubscription = true; } else { isTicket = false; isSubscription = false; } long minutes = ~(data[11] << 16 & 0xff0000 | data[12] << 8 & 0xff00 | data[13] & 0xff) & 0xffffff; startDate = GttDate.decode(minutes); minutes = ~(data[14] << 16 & 0xff0000 | data[15] << 8 & 0xff00 | data[16] & 0xff) & 0xffffff; endDate = GttDate.decode(minutes); } public int getCode() { return code; } public Date getStartDate() { return startDate; } public Date getEndDate() { return endDate; } public boolean isValid() { return isValid; } public boolean isSubscription() { if(isSubscription) return true; else return false; } public boolean isTicket() { if(isTicket) return true; else return false; } public String getTypeName() { if(isTicket) return ticketCodes.get(code); else if(isSubscription) return subscriptionCodes.get(code); else return "Sconosciuto"; } } private byte[] cardinfo = new byte[31]; private byte[] itemsdata = new byte[31*7]; private Date creationDate; private Type type; private List items = new ArrayList<>(); private boolean isSubscription = false; private Item lastItem; + + private String subscriptionName; private int ridesLeft = 0; public SmartCard(byte[] content, Context context) { this.context = context; System.arraycopy(content, 0, cardinfo, 0, 31); System.arraycopy(content, 31*2+2, itemsdata, 0, 31*7); byte[] minutes = new byte[3]; System.arraycopy(cardinfo, 11, minutes, 0, 3); creationDate = GttDate.decode(minutes); if(cardinfo[30] == (byte)0xC0) type = Type.BIP; else if(cardinfo[30] == (byte)0xC1) type = Type.PYOU; else if(cardinfo[30] == (byte)0xC2) type = Type.EDISU; Date lastExpireDate = GttDate.getGttEpoch(); for (int i = 0; i < 7; i++) { byte[] itemdata = new byte[31]; System.arraycopy(itemsdata, 31*i, itemdata, 0, 31); Item item = new Item(itemdata); if(item.isValid()) { if(lastExpireDate.before(item.getEndDate())) { lastExpireDate = item.getEndDate(); - if(item.isSubscription()) - isSubscription = true; lastItem = item; + if(item.isSubscription()) { + isSubscription = true; + subscriptionName = item.getTypeName(); + if(!isExpired(item.getEndDate())) + break; + } if(item.isTicket()) { ridesLeft += 1; + if(isExpired(lastItem.getEndDate())) + isSubscription = false; //TODO: count tickets in daily 7 carnets } } //isSubscription = item.isSubscription(); items.add(item); } } } public String getName() { return type + " - " + lastItem.getTypeName(); } + public String getSubscriptionName() { + return type + " - " + subscriptionName; + } + public String getDate() { return DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT) .format(lastItem.getEndDate()); } public boolean isExpired() { Calendar c = Calendar.getInstance(); return c.getTime().after(lastItem.getEndDate()); } + private boolean isExpired(Date date) { + Calendar c = Calendar.getInstance(); + return c.getTime().after(date); + } + public int getRemainingRides() { return ridesLeft; } public boolean isSubscription() { return isSubscription; } }