diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser
index d0460fb..cf0869b 100755
Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ
diff --git a/.idea/dictionaries/daniele.xml b/.idea/dictionaries/daniele.xml
new file mode 100644
index 0000000..bcb10f1
--- /dev/null
+++ b/.idea/dictionaries/daniele.xml
@@ -0,0 +1,7 @@
+
+
+
+ dumplist
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 99202cc..c0f68ed 100755
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,34 +1,34 @@
-
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 99f0fde..7936434 100755
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,32 +1,34 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 26
+ compileSdkVersion 27
defaultConfig {
applicationId "org.dslul.ticketreader"
minSdkVersion 15
- targetSdkVersion 26
- versionCode 12
- versionName "1.9.1"
+ targetSdkVersion 27
+ versionCode 14
+ versionName "2.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation 'com.android.support:appcompat-v7:26.1.0'
- implementation 'com.android.support.constraint:constraint-layout:1.0.2'
- implementation 'com.android.support:design:26.1.0'
+ implementation 'com.android.support:appcompat-v7:27.1.1'
+ implementation 'com.android.support:customtabs:27.1.1'
+ implementation 'com.android.support.constraint:constraint-layout:1.1.2'
+ implementation 'com.android.support:design:27.1.1'
testImplementation 'junit:junit:4.12'
- androidTestImplementation 'com.android.support.test:runner:1.0.1'
- androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
- implementation 'com.google.android.gms:play-services-ads:12.0.1'
- implementation 'com.android.support:cardview-v7:26.1.0'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ implementation 'com.google.android.gms:play-services-ads:15.0.1'
+ implementation 'com.android.support:cardview-v7:27.1.1'
+ implementation 'com.yarolegovich:lovely-dialog:1.1.0'
}
diff --git a/app/release/output.json b/app/release/output.json
index 8bfabed..e305b20 100755
--- a/app/release/output.json
+++ b/app/release/output.json
@@ -1 +1 @@
-[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":12,"versionName":"1.9.1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
\ No newline at end of file
+[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":13,"versionName":"1.9.2","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 54f14d3..0c8e555 100755
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,32 +1,38 @@
-
+
+
+
+
+
+
+ android:label="@string/app_name"
+ android:screenOrientation="portrait"
+ android:theme="@style/AppTheme.NoActionBar">
+
-
+
+
\ No newline at end of file
diff --git a/app/src/main/java/org/dslul/ticketreader/ChipOnPaper.java b/app/src/main/java/org/dslul/ticketreader/ChipOnPaper.java
index d81d51d..78ddeb7 100755
--- a/app/src/main/java/org/dslul/ticketreader/ChipOnPaper.java
+++ b/app/src/main/java/org/dslul/ticketreader/ChipOnPaper.java
@@ -1,153 +1,121 @@
package org.dslul.ticketreader;
import android.util.Log;
import org.dslul.ticketreader.util.GttDate;
+import org.dslul.ticketreader.util.HelperFunctions;
+import java.nio.ByteBuffer;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
+import java.util.List;
import java.util.concurrent.TimeUnit;
+import static org.dslul.ticketreader.util.GttDate.addMinutesToDate;
+import static org.dslul.ticketreader.util.HelperFunctions.getBytesFromPage;
+
public class ChipOnPaper {
- private String pages;
- private String date;
+ private Date date;
private int type;
private long remainingMins;
+ private int remainingRides;
+
+ ChipOnPaper(List dumplist) {
+
+ dumplist = dumplist;
+
+ type = (int)getBytesFromPage(dumplist.get(5), 2, 2);
+
+ long minutes = getBytesFromPage(dumplist.get(10), 0, 3);
+
+ if(type == 9521)
+ minutes = getBytesFromPage(dumplist.get(12), 0, 3);
+
+ date = addMinutesToDate(minutes, GttDate.getGttEpoch());
+
+ //calcola minuti rimanenti
+ Calendar c = Calendar.getInstance();
+ long diff = (c.getTime().getTime() - date.getTime()) / 60000;
+ long maxtime = 90;
+ //city 100
+ if(type == 302 || type == 304) {
+ maxtime = 100;
+ }
+ //daily
+ if(type == 303 || type == 305) {
+ maxtime = GttDate.getMinutesUntilMidnight();
+ }
+ //Tour TODO: make a distinction between the two types
+ if(type == 704) {
+ maxtime = 2*24*60;
+ }
+ if(diff >= maxtime) {
+ remainingMins = 0;
+ } else {
+ remainingMins = maxtime - diff;
+ }
- public ChipOnPaper(byte[] content) {
- //if(data == null)
- //TODO: convert this class to use bytes internally instead of strings
- String contentstr = "";
- byte[] page = new byte[4];
- for (int i = 0; i < content.length; i += 4) {
- System.arraycopy(content, i, page, 0, 4);
- contentstr = contentstr + ByteArrayToHexString(page) + System.getProperty("line.separator");
+ //calcola le corse rimanenti
+ //TODO: corse in metropolitana (forse bit più significativo pag. 3)
+ int tickets;
+ if(type == 300) { //extraurbano
+ tickets = (int) (~getBytesFromPage(dumplist.get(3), 0, 4));
+ } else {
+ tickets = (int)(~getBytesFromPage(dumplist.get(3), 2, 2))
+ & 0xFFFF;
}
- this.pages = contentstr;
- this.date = this.pages.substring(90, 96);
- //this.type = (int)getBytesFromPage(5, 0, 1);
- this.type = (int)getBytesFromPage(5, 2, 2);
+ remainingRides = Integer.bitCount(tickets);
+
}
public String getTypeName() {
//http://www.gtt.to.it/cms/biglietti-abbonamenti/biglietti/biglietti-carnet
switch (type) {
case 302:
case 304:
return "City 100";
case 303:
case 305:
return "Daily";
case 704:
return "Tour";
case 301:
return "Multicorsa extraurbano";
case 702:
case 706:
return "Carnet 5 corse";
case 701:
case 705:
return "Carnet 15 corse";
case 300:
return "Extraurbano";
+ case 9521:
+ return "Sadem Aeroporto Torino";
default:
return "Non riconosciuto";
}
}
public String getDate() {
- Date finalDate = addMinutesToDate(Long.parseLong(this.date, 16), GttDate.getGttEpoch());
-
- //calcola minuti rimanenti
- Calendar c = Calendar.getInstance();
- long diff = (c.getTime().getTime() - finalDate.getTime()) / 60000;
- long maxtime = 90;
- //city 100
- if(type == 302 || type == 304) {
- maxtime = 100;
- }
- //daily
- if(type == 303 || type == 305) {
- maxtime = GttDate.getMinutesUntilMidnight();
- }
- //Tour TODO: make a distinction between the two types
- if(type == 704) {
- maxtime = 2*24*60;
- }
- if(diff >= maxtime) {
- remainingMins = 0;
- } else {
- remainingMins = maxtime - diff;
- }
-
-
return DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT)
- .format(finalDate);
+ .format(date);
}
- //TODO: corse in metropolitana (forse bit più significativo pag. 3)
public int getRemainingRides() {
- int tickets;
- if(type == 300) { //extraurbano
- tickets = (int) (~getBytesFromPage(3, 0, 4));
- } else {
- tickets = (int)(~getBytesFromPage(3, 2, 2))
- & 0xFFFF;
- }
- return Integer.bitCount(tickets);
+ return remainingRides;
}
public long getRemainingMinutes() {
return remainingMins;
}
- private long getBytesFromPage(int page, int offset, int bytesnum) {
- return Long.parseLong(
- pages.substring(9 * page + offset * 2, 9 * page + offset * 2 + bytesnum * 2), 16);
- }
-
-
-
- private static Date addMinutesToDate(long minutes, Date beforeTime){
- final long ONE_MINUTE_IN_MILLIS = 60000;
-
- long curTimeInMs = beforeTime.getTime();
- Date afterAddingMins = new Date(curTimeInMs + (minutes * ONE_MINUTE_IN_MILLIS));
- return afterAddingMins;
- }
-
-
- private static 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 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;
- }
-
}
diff --git a/app/src/main/java/org/dslul/ticketreader/MainActivity.java b/app/src/main/java/org/dslul/ticketreader/MainActivity.java
index e9a5056..120f24b 100755
--- a/app/src/main/java/org/dslul/ticketreader/MainActivity.java
+++ b/app/src/main/java/org/dslul/ticketreader/MainActivity.java
@@ -1,360 +1,394 @@
package org.dslul.ticketreader;
import android.annotation.SuppressLint;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
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.text.Html;
+import android.text.method.LinkMovementMethod;
+import android.util.Log;
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.List;
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;
+import com.yarolegovich.lovelydialog.LovelyCustomDialog;
+import com.yarolegovich.lovelydialog.LovelyInfoDialog;
+import com.yarolegovich.lovelydialog.LovelyStandardDialog;
+
+import org.dslul.ticketreader.util.HelperFunctions;
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;
+ private List dump;
// 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 nfcThread = new NfcThread(getBaseContext(), intent, mContentHandler, mToastShortHandler, mToastLongHandler, mShowInfoDialogHandler);
nfcThread.start();
-
- scanAction = ACTION_READ;
}
}
@SuppressLint("HandlerLeak")
- private Handler mTextBufferHandler = new Handler() {
+ private Handler mContentHandler = new Handler() {
public void handleMessage(Message msg) {
- pages = (byte[])msg.obj;
+ List dumplist = (List)msg.obj;
+ dump = dumplist;
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.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();
-
- }
-
-
+ try {
+ //smartcard
+ if(dumplist.size() == 15) {
+ SmartCard smartcard = new SmartCard(dumplist, getBaseContext());
+ if(smartcard.isSubscription()) {
+ dataLabel.setText(R.string.expire_date);
+ 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.getValidationDate(),
+ smartcard.getRemainingRides(), smartcard.getRemainingMinutes());
+ //Toast.makeText(getBaseContext(), R.string.smartcard_tickets_not_supported_yet, Toast.LENGTH_LONG).show();
+
+ }
+
+
+ }
+ //chip on paper
+ else if(dumplist.size() > 15) {
+
+ ChipOnPaper chipOnPaper = new ChipOnPaper(dumplist);
+ 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);
+ }
+
+ } catch (Exception ex) {
+ Toast.makeText(getBaseContext(), R.string.unknown_error, Toast.LENGTH_LONG).show();
+ Log.d("card", ex.getMessage());
}
- //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;
+ TextView view = new TextView(getBaseContext());
+ view.setText(Html.fromHtml(getString(R.string.html_info)));
+ view.setMovementMethod(LinkMovementMethod.getInstance());
+ view.setPadding( 40, 40, 40, 40 );
+ new LovelyCustomDialog(this)
+ .setTopColorRes(R.color.darkBlueGrey)
+ .setIcon(R.drawable.ic_info_outline_white_36dp)
+ .setTitle("Info")
+ .setView(view)
+ .show();
+ return true;
+ }
+
+ if (id == R.id.action_copy_content) {
+ try {
+ ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+ String content = "";
+ if (dump != null && dump.size() >= 15) {
+ for (byte[] page : dump) {
+ content = content.concat(HelperFunctions.byteArrayToHexString(page));
+ }
+ ClipData clip = ClipData.newPlainText("content", content);
+ if (clipboard != null) {
+ clipboard.setPrimaryClip(clip);
+ }
+ Toast.makeText(getBaseContext(), R.string.content_copied, Toast.LENGTH_LONG).show();
+ } else {
+ Toast.makeText(getBaseContext(), R.string.no_content, Toast.LENGTH_LONG).show();
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+
+ 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 5b8759f..ab19343 100755
--- a/app/src/main/java/org/dslul/ticketreader/NfcThread.java
+++ b/app/src/main/java/org/dslul/ticketreader/NfcThread.java
@@ -1,344 +1,212 @@
package org.dslul.ticketreader;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
-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;
+import static org.dslul.ticketreader.util.HelperFunctions.hexStringToByteArray;
-//code from http://www.emutag.com/soft.php
+
+//parts of 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 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);
+ List dumplist = new ArrayList<>();
+
+ //selectApplication
+ dumplist.add(isoDep.transceive(hexStringToByteArray("00A404000E315449432E494341D38012009101")));
+ //efEnvironment
+ dumplist.add(isoDep.transceive(hexStringToByteArray("00B2013C1D")));
+ //efContractList
+ dumplist.add(isoDep.transceive(hexStringToByteArray("00B201F41D")));
+ //efContract1
+ dumplist.add(isoDep.transceive(hexStringToByteArray("00B2014C1D")));
+ //efContract2
+ dumplist.add(isoDep.transceive(hexStringToByteArray("00B2024C1D")));
+ //efContract3
+ dumplist.add(isoDep.transceive(hexStringToByteArray("00B2034C1D")));
+ //efContract4
+ dumplist.add(isoDep.transceive(hexStringToByteArray("00B2044C1D")));
+ //efContract5
+ dumplist.add(isoDep.transceive(hexStringToByteArray("00B2054C1D")));
+ //efContract6
+ dumplist.add(isoDep.transceive(hexStringToByteArray("00B2064C1D")));
+ //efContract7
+ dumplist.add(isoDep.transceive(hexStringToByteArray("00B2074C1D")));
+ //efContract8
+ dumplist.add(isoDep.transceive(hexStringToByteArray("00B2084C1D")));
+ //efEventLogs1
+ dumplist.add(isoDep.transceive(hexStringToByteArray("00B201441D")));
+ //efEventLogs2
+ dumplist.add(isoDep.transceive(hexStringToByteArray("00B202441D")));
+ //efEventLogs3
+ dumplist.add(isoDep.transceive(hexStringToByteArray("00B203441D")));
+ //efCredit
+ dumplist.add(isoDep.transceive(hexStringToByteArray("007C000721")));
+
+ if(dumplist.size() == 15 && dumplist.get(1)[0] != 0) {
+ if(dumplist.get(2)[1] == 0) {
+ showToastLong(context.getString(R.string.smartcard_empty));
+ return;
+ }
+ setContentBuffer(dumplist);
showToastLong(context.getString(R.string.smartcard_read_correctly));
+ } else {
+ showToastLong(context.getString(R.string.invalid_smartcard));
}
isoDep.close();
} catch (IOException e) {
- e.printStackTrace();
+ showToastLong(context.getString(R.string.read_failure));
}
}
}
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;
+ List dumplist = new ArrayList<>();
try {
- //Log.i("position", "read data");
-
- if (scanAction == ACTION_READ) {
- mfu.connect();
- pagesRead = rdNumPages(mfu, 0); // 0 for no limit (until error)
- mfu.close();
+ mfu.connect();
+ pagesRead = rdNumPages(mfu, 16); // 0 for no limit (until error)
+ mfu.close();
- byte[] content = new byte[pagesRead*4];
- System.arraycopy(readBuffer, 0, content, 0, pagesRead*4);
- /*
- String content = "";
+ for (int i = 0; i < pagesRead*4; i += 4) {
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);
- }
+ System.arraycopy(readBuffer, i, mfuPage, 0, 4);
+ dumplist.add(mfuPage);
+ }
+
+ if(pagesRead >= 16) {
+ showToastShort(context.getString(R.string.ticket_correctly_read));
+ setContentBuffer(dumplist);
+ } else {
+ throw new RuntimeException(context.getString(R.string.read_failure));
}
+
+ }
+ catch (RuntimeException e) {
+ showToastLong(context.getString(R.string.read_failure));
}
catch (Exception e) {
showToastLong(context.getString(R.string.communication_error));
}
}
- private void setTextBuffer(byte[] content) {
+ private void setContentBuffer(List 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;
- }
- */
+
+
+ private int rdNumPages(NfcA mfu, int num) {
+ int pagesRead = 0;
+
+ while (rdPages(mfu, pagesRead) == 0) {
+ pagesRead++;
+ 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();
+ response = tag.transceive(cmd);
}
- catch (final IOException e) { return 1; }
- /*
- finally {
- try { mfu.close(); }
- catch (final Exception e) {}
+ catch (IOException e) {
+ return 1;
}
- */
-
- 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; }
+ if (response.length != 16)
+ return 1;
+
+ System.arraycopy(response, 0, readBuffer, pageOffset * 4, 4);
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 13cb5e6..e014d7f 100644
--- a/app/src/main/java/org/dslul/ticketreader/SmartCard.java
+++ b/app/src/main/java/org/dslul/ticketreader/SmartCard.java
@@ -1,271 +1,312 @@
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;
+import static java.lang.Math.abs;
+import static org.dslul.ticketreader.util.HelperFunctions.getBytesFromPage;
+
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 class Contract {
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;
+ public Contract(byte[] data) {
+ //get contract type
+ code = ((data[4] & 0xff) << 8) | data[5] & 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;
+ long minutes = ~(data[9] << 16 & 0xff0000 | data[10] << 8 & 0xff00 |
+ data[11] & 0xff) & 0xffffff;
startDate = GttDate.decode(minutes);
- minutes = ~(data[14] << 16 & 0xff0000 | data[15] << 8 & 0xff00 |
- data[16] & 0xff) & 0xffffff;
+ minutes = ~(data[12] << 16 & 0xff0000 | data[13] << 8 & 0xff00 |
+ data[14] & 0xff) & 0xffffff;
endDate = GttDate.decode(minutes);
}
public int getCode() {
return code;
}
public Date getStartDate() {
return startDate;
}
public Date getEndDate() {
return endDate;
}
- public boolean isValid() {
+ public boolean isContract() {
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 byte[] efEnvironment;
+ private Date validationDate;
private Date creationDate;
private Type type;
- private List- items = new ArrayList<>();
+ private List contracts = new ArrayList<>();
private boolean isSubscription = false;
- private Item lastItem;
+ private Contract latestContract;
private String subscriptionName;
private int ridesLeft = 0;
+ private long remainingMins;
- public SmartCard(byte[] content, Context context) {
+ SmartCard(List dumplist, Context context) {
this.context = context;
- System.arraycopy(content, 0, cardinfo, 0, 31);
- System.arraycopy(content, 31*2+2, itemsdata, 0, 31*7);
+ efEnvironment = dumplist.get(1);
+ byte[] efContractList = dumplist.get(2);
byte[] minutes = new byte[3];
- System.arraycopy(cardinfo, 11, minutes, 0, 3);
+ System.arraycopy(efEnvironment, 9, minutes, 0, 3);
creationDate = GttDate.decode(minutes);
- if(cardinfo[30] == (byte)0xC0)
+ if(efEnvironment[28] == (byte)0xC0)
type = Type.BIP;
- else if(cardinfo[30] == (byte)0xC1)
+ else if(efEnvironment[28] == (byte)0xC1)
type = Type.PYOU;
- else if(cardinfo[30] == (byte)0xC2)
+ else if(efEnvironment[28] == (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();
- 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
- }
+ Date latestExpireDate = GttDate.getGttEpoch();
+ for (int i = 0; i < 8; i++) {
+ Contract contract = new Contract(dumplist.get(i+3));
+ if(contract.isContract()) {
+ if(latestExpireDate.before(contract.getEndDate())) {
+ latestExpireDate = contract.getEndDate();
+ latestContract = contract;
+ }
+ if(contract.isSubscription()) {
+ isSubscription = true;
+ subscriptionName = contract.getTypeName();
+ if(!isExpired(contract.getEndDate()))
+ break;
+ }
+ if(contract.isTicket()) {
+ //ridesLeft += 1;
+ if(isExpired(latestContract.getEndDate()))
+ isSubscription = false;
+ //TODO: count tickets in daily 7 carnets
}
- //isSubscription = item.isSubscription();
- items.add(item);
+ //isSubscription = contract.isSubscription();
+
+ contracts.add(contract);
}
}
+ //actual tickets count
+ for (int i = 2; i < 29; i+=3) {
+ int num = abs(efContractList[i+1]) >> 4;
+ int used = efContractList[i+2];
+ if(num != 0 && num <= 8 && used == 0)
+ ridesLeft += 1;
+ }
+
+
+ //get last validation time
+ long mins = getBytesFromPage(dumplist.get(11), 20, 3);
+ validationDate = GttDate.addMinutesToDate(mins, GttDate.getGttEpoch());
+ Calendar c = Calendar.getInstance();
+ long diff = (c.getTime().getTime() - validationDate.getTime()) / 60000;
+
+ int num = (int)(getBytesFromPage(dumplist.get(11), 25, 1) >> 4);
+ int tickettype = (int)getBytesFromPage(dumplist.get(num+2), 4, 2);
+
+ long maxtime = 90;
+ //city 100
+ if(tickettype == 714) {
+ maxtime = 100;
+ }
+ //daily
+ if(tickettype == 715 || tickettype == 716) {
+ maxtime = GttDate.getMinutesUntilMidnight();
+ }
+ if(diff >= maxtime) {
+ remainingMins = 0;
+ } else {
+ remainingMins = maxtime - diff;
+ }
}
public String getName() {
- return type + " - " + lastItem.getTypeName();
+ return type + " - " + latestContract.getTypeName();
}
public String getSubscriptionName() {
return type + " - " + subscriptionName;
}
public String getDate() {
return DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT)
- .format(lastItem.getEndDate());
+ .format(latestContract.getEndDate());
+ }
+
+ public String getValidationDate() {
+ return DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT)
+ .format(validationDate);
}
public boolean isExpired() {
Calendar c = Calendar.getInstance();
- return c.getTime().after(lastItem.getEndDate());
+ return c.getTime().after(latestContract.getEndDate());
}
private boolean isExpired(Date date) {
Calendar c = Calendar.getInstance();
return c.getTime().after(date);
}
public int getRemainingRides() {
return ridesLeft;
}
+ public long getRemainingMinutes() {
+ return remainingMins;
+ }
public boolean isSubscription() {
return isSubscription;
}
}
diff --git a/app/src/main/java/org/dslul/ticketreader/util/HelperFunctions.java b/app/src/main/java/org/dslul/ticketreader/util/HelperFunctions.java
index c9e4972..5ac0743 100644
--- a/app/src/main/java/org/dslul/ticketreader/util/HelperFunctions.java
+++ b/app/src/main/java/org/dslul/ticketreader/util/HelperFunctions.java
@@ -1,4 +1,70 @@
package org.dslul.ticketreader.util;
-public class HelperFunctions {
+import android.content.Context;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class HelperFunctions {
+
+ public static 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;
+ }
+
+ public static 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;
+ }
+
+ public static long getBytesFromPage(byte[] page, int offset, int bytesnum) {
+ byte[] bytes = new byte[bytesnum];
+ System.arraycopy(page, offset, bytes, 0, bytesnum);
+
+ long value = 0;
+ for (byte tmp : bytes)
+ value = (value << 8) + (tmp & 0xff);
+
+ return value;
+
+ }
+
+ public static List loadFromFile(String filename, Context context) {
+ List array = new ArrayList<>();
+
+ BufferedReader reader;
+
+ try{
+ InputStream file = context.getAssets().open(filename);
+ reader = new BufferedReader(new InputStreamReader(file));
+ String line = reader.readLine();
+ while(line != null){
+ line = reader.readLine();
+ array.add(hexStringToByteArray(line));
+ }
+ } catch(IOException ioe){
+ ioe.printStackTrace();
+ }
+
+ return array;
+ }
+
}
diff --git a/app/src/main/res/drawable-hdpi/ic_info_outline_white_36dp.png b/app/src/main/res/drawable-hdpi/ic_info_outline_white_36dp.png
new file mode 100644
index 0000000..2dd40bd
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_info_outline_white_36dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_star_border_white_36dp.png b/app/src/main/res/drawable-hdpi/ic_star_border_white_36dp.png
new file mode 100644
index 0000000..c9ab328
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_star_border_white_36dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_info_outline_white_36dp.png b/app/src/main/res/drawable-mdpi/ic_info_outline_white_36dp.png
new file mode 100644
index 0000000..c7b1113
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_info_outline_white_36dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_star_border_white_36dp.png b/app/src/main/res/drawable-mdpi/ic_star_border_white_36dp.png
new file mode 100644
index 0000000..e302ef6
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_star_border_white_36dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_info_outline_white_36dp.png b/app/src/main/res/drawable-xhdpi/ic_info_outline_white_36dp.png
new file mode 100644
index 0000000..c41a5fc
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_info_outline_white_36dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_star_border_white_36dp.png b/app/src/main/res/drawable-xhdpi/ic_star_border_white_36dp.png
new file mode 100644
index 0000000..7e41906
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_star_border_white_36dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_info_outline_white_36dp.png b/app/src/main/res/drawable-xxhdpi/ic_info_outline_white_36dp.png
new file mode 100644
index 0000000..2e500d8
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_info_outline_white_36dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_star_border_white_36dp.png b/app/src/main/res/drawable-xxhdpi/ic_star_border_white_36dp.png
new file mode 100644
index 0000000..25ef834
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_star_border_white_36dp.png differ
diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml
index 5885961..674007b 100755
--- a/app/src/main/res/menu/menu_main.xml
+++ b/app/src/main/res/menu/menu_main.xml
@@ -1,10 +1,16 @@
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index b8180f9..a8d8017 100755
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -1,31 +1,37 @@
Lettore Biglietti GTT
Info
Data obliterazione:
Minuti rimanenti:
Corse rimaste:
Avvicina un biglietto alla parte posteriore del dispositivo per effettuare la scansione
Immagine biglietto nfc
Per leggere un altro biglietto, avvicinalo nuovamente al sensore NFC
%d corse disponibili
In corso %d:%d
Corse esaurite
Dettagli biglietto
Questo dispositivo non supporta la tecnologia NFC.
NFC disabilitato. Attiva l\'NFC e premi il tasto indietro.
Informazioni
Chiudi
- Semplice applicazione opensource per visualizzare le corse rimanenti nei biglietti GTT.\n\nhttps://github.com/dslul/ticketreader\n\nIcone: Card Paypass by Viktor Vorobyev from the Noun Project; samsung galaxy by Setyo Ari Wibowo from the Noun Project
+ Semplice applicazione opensource per visualizzare le corse rimanenti nei biglietti GTT.
Se riscontri problemi durante l\'utilizzo dell\'app, scrivimi a questo indirizzo email, incollando una copia del contenuto della carta (effettuata dal menu).
Biglietto non valido!
Biglietto letto correttamente.
Lettura fallita, riprovare
Errore di comunicazione. Riprova
Data di scadenza:
Tessera non valida
Tessera letta correttamente.
Scaduto
Illimitato
Valido
- Tessere con biglietti non ancora supportate, stay tuned...
+ Tessere con biglietti non ancora supportate, stay tuned…
+ Segnala errori
+ Errore sconosciuto, contatta lo sviluppatore per risolvere questo problema.
+ Copia contenuto
+ Contenuto copiato negli appunti
+ Scansiona un biglietto prima di effettuare la copia del contenuto
+ Tessera vuota!
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 041db00..30ae204 100755
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,9 +1,18 @@
#00529f
#00529f
#FF4081
#FFA5D6A7
#FF90CAF9
#FFEF9A9A
+
+ #3F51B5
+ #009688
+ #E64A19
+ #388E3C
+ #455A64
+ #D32F2F
+ #FFFFFF
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 59a0b0c..617af6a 100755
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,3 +1,6 @@
16dp
+
+ 16dp
+ 16dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c62659d..eeb5858 100755
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,30 +1,36 @@
GTT Ticket Reader
Info
Time of validation:
Minutes remaining:
Trips remaining:
Place a ticket near the back of the device to scan
NFC ticket image
To read another ticket, move it close to the NFC sensor again
%d trips available
In progress %d:%d
Trips exhausted
Ticket info
This device does not support NFC technology.
NFC disabled. Activate NFC and press the back button.
Information
Close
- Simple opensource application to view the remaining trips in GTT tickets.\n\nhttps://github.com/dslul/ticketreader\n\nIcons: Card Paypass by Viktor Vorobyev from the Noun Project; samsung galaxy by Setyo Ari Wibowo from the Noun Project
+ Simple opensource application to view the remaining trips in GTT tickets.
If you encounter any problem with this app, feel free to contact me at this email address describing your problem. Do not forget to paste the content of your ticket (this can be done by the menu).
Project URL
Credits:
Icons: Card Paypass by Viktor Vorobyev from the Noun Project; samsung galaxy by Setyo Ari Wibowo from the Noun Project
]]>
Invalid ticket!
Ticket read correctly.
Reading failed, try again
Communication error. Try again
Expire Date:
Invalid smartcard
Smartcard read correctly.
Expired
Unlimited
Valid
Smartcards with tickets still not supported, stay tuned…
+ Send feedback
+ Unknown error, please contact the developer to solve this problem.
+ Copy content
+ Content successfully copied to clipboard
+ No content to copy. Scan a ticket first
+ This smartcard is still empty!
diff --git a/app/src/test/java/org/dslul/ticketreader/ChipOnPaperUnitTest.java b/app/src/test/java/org/dslul/ticketreader/ChipOnPaperUnitTest.java
index 87205d7..d1c1e13 100755
--- a/app/src/test/java/org/dslul/ticketreader/ChipOnPaperUnitTest.java
+++ b/app/src/test/java/org/dslul/ticketreader/ChipOnPaperUnitTest.java
@@ -1,17 +1,50 @@
package org.dslul.ticketreader;
+import android.util.Log;
+
+import org.dslul.ticketreader.util.HelperFunctions;
import org.junit.Test;
+import java.util.ArrayList;
+import java.util.List;
+
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see Testing documentation
*/
-public class ExampleUnitTest {
+public class ChipOnPaperUnitTest {
+
+ List ticket2 = new ArrayList<>();
+ List ticket3 = new ArrayList<>();
+ List ticket4 = new ArrayList<>();
+ List ticket5 = new ArrayList<>();
+ List ticket6 = new ArrayList<>();
+
@Test
- public void addition_isCorrect() throws Exception {
- assertEquals(4, 2 + 2);
+ public void ChipOnPaper_isCorrect() throws Exception {
+ List ticket1 = new ArrayList<>();
+ ticket1.add(HelperFunctions.hexStringToByteArray("057D6292"));
+ ticket1.add(HelperFunctions.hexStringToByteArray("AD2954E9"));
+ ticket1.add(HelperFunctions.hexStringToByteArray("3915F203"));
+ ticket1.add(HelperFunctions.hexStringToByteArray("07FFFFF0"));
+ ticket1.add(HelperFunctions.hexStringToByteArray("01040000"));
+ ticket1.add(HelperFunctions.hexStringToByteArray("020102BE"));
+ ticket1.add(HelperFunctions.hexStringToByteArray("68970000"));
+ ticket1.add(HelperFunctions.hexStringToByteArray("00AE10A7"));
+ ticket1.add(HelperFunctions.hexStringToByteArray("0200645C"));
+ ticket1.add(HelperFunctions.hexStringToByteArray("397D91B4"));
+ ticket1.add(HelperFunctions.hexStringToByteArray("68A4F900"));
+ ticket1.add(HelperFunctions.hexStringToByteArray("04F80000"));
+ ticket1.add(HelperFunctions.hexStringToByteArray("68A4F900"));
+ ticket1.add(HelperFunctions.hexStringToByteArray("00050004"));
+ ticket1.add(HelperFunctions.hexStringToByteArray("F8AE1079"));
+ ticket1.add(HelperFunctions.hexStringToByteArray("9E1291E4"));
+ ChipOnPaper chip = new ChipOnPaper(ticket1);
+ assertEquals(4, chip.getRemainingRides());
+ assertEquals(0, chip.getRemainingMinutes());
+
}
}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 75385e9..654620b 100755
--- a/build.gradle
+++ b/build.gradle
@@ -1,30 +1,30 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.1.3'
+ classpath 'com.android.tools.build:gradle:3.1.4'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven {
url "https://maven.google.com"
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}