diff --git a/.gitignore b/.gitignore
index 2fa2de7..a6a4480 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,13 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
+/dump
/captures
.externalNativeBuild
/app/build
/app/libs
/app/release
diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser
index 0ac0485..2fc209c 100644
Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ
diff --git a/.idea/caches/gradle_models.ser b/.idea/caches/gradle_models.ser
new file mode 100644
index 0000000..1e58ca5
Binary files /dev/null and b/.idea/caches/gradle_models.ser differ
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 30aa626..ae78c11 100755
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,29 +1,113 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 7ac24c7..f43d428 100755
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -1,18 +1,21 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index c0f68ed..d04819c 100755
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,34 +1,48 @@
-
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index c39ecab..a089a46 100755
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,37 +1,39 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 28
+ compileSdkVersion 29
defaultConfig {
applicationId "org.dslul.ticketreader"
minSdkVersion 15
- targetSdkVersion 28
- versionCode 33
- versionName "2.3"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ targetSdkVersion 29
+ versionCode 37
+ versionName "2.6"
+ testInstrumentationRunner "androidx.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:28.0.0'
- implementation 'com.android.support:animated-vector-drawable:28.0.0'
- implementation 'com.android.support:support-media-compat:28.0.0'
- implementation 'com.android.support:support-v4:28.0.0'
- implementation 'com.android.support:customtabs:28.0.0'
- implementation 'com.android.support.constraint:constraint-layout:1.1.3'
- implementation 'com.android.support:design:28.0.0'
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.preference:preference:1.1.0'
+ implementation 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
+ implementation 'androidx.media:media:1.1.0'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ implementation 'androidx.browser:browser:1.0.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'com.google.android.material:material:1.0.0'
+ implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
testImplementation 'junit:junit:4.12'
- 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:17.1.1'
- implementation 'com.android.support:cardview-v7:28.0.0'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+ implementation 'com.google.android.gms:play-services-ads:18.3.0'
+ implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.yarolegovich:lovely-dialog:1.1.0'
}
diff --git a/app/release/output.json b/app/release/output.json
index d4ba1fa..4344130 100755
--- a/app/release/output.json
+++ b/app/release/output.json
@@ -1 +1 @@
-[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":33,"versionName":"2.3","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
\ No newline at end of file
+[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":37,"versionName":"2.6","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/androidTest/java/org/dslul/ticketreader/ExampleInstrumentedTest.java b/app/src/androidTest/java/org/dslul/ticketreader/ExampleInstrumentedTest.java
index 951f201..26b0f37 100755
--- a/app/src/androidTest/java/org/dslul/ticketreader/ExampleInstrumentedTest.java
+++ b/app/src/androidTest/java/org/dslul/ticketreader/ExampleInstrumentedTest.java
@@ -1,26 +1,26 @@
package org.dslul.ticketreader;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see Testing documentation
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("org.dslul.ticketreader", appContext.getPackageName());
}
}
diff --git a/app/src/main/java/org/dslul/ticketreader/ChipOnPaper.java b/app/src/main/java/org/dslul/ticketreader/ChipOnPaper.java
index ed2d90e..d932c43 100755
--- a/app/src/main/java/org/dslul/ticketreader/ChipOnPaper.java
+++ b/app/src/main/java/org/dslul/ticketreader/ChipOnPaper.java
@@ -1,125 +1,129 @@
package org.dslul.ticketreader;
import android.util.Log;
import org.dslul.ticketreader.util.GttDate;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import static org.dslul.ticketreader.util.GttDate.addMinutesToDate;
import static org.dslul.ticketreader.util.HelperFunctions.getBytesFromPage;
public class ChipOnPaper {
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 || type == 650 || type == 651) {
maxtime = 100;
}
- //Tour TODO: make a distinction between the two types
- if(type == 704) {
+ //Tour 2 giorni
+ if(type == 288) {
maxtime = 2*24*60;
}
+ //Tour 3 giorni
+ if(type == 289) {
+ maxtime = 3*24*60;
+ }
//daily
if(type == 303 || type == 305) {
remainingMins = GttDate.getMinutesUntilEndOfService(date);
}
else if(diff >= maxtime) {
remainingMins = 0;
} else {
remainingMins = maxtime - diff;
}
//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;
}
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 650:
case 651:
return "MultiCity";
- case 704:
- return "Tour";
+ case 288:
+ return "Tour 2 giorni";
+ case 289:
+ return "Tour 3 giorni";
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() {
return DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT)
.format(date);
}
public int getRemainingRides() {
return remainingRides;
}
public long getRemainingMinutes() {
if(remainingMins < 0)
return 0;
else
return remainingMins;
}
}
diff --git a/app/src/main/java/org/dslul/ticketreader/MainActivity.java b/app/src/main/java/org/dslul/ticketreader/MainActivity.java
index feb4d52..26d322c 100755
--- a/app/src/main/java/org/dslul/ticketreader/MainActivity.java
+++ b/app/src/main/java/org/dslul/ticketreader/MainActivity.java
@@ -1,417 +1,419 @@
package org.dslul.ticketreader;
import android.annotation.SuppressLint;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
-import android.content.ComponentName;
-import android.content.SharedPreferences;
import android.nfc.tech.IsoDep;
import android.os.Bundle;
import android.os.CountDownTimer;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.preference.PreferenceManager;
-import android.support.v7.widget.CardView;
-import android.support.v7.widget.Toolbar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.preference.PreferenceManager;
+import androidx.cardview.widget.CardView;
+import androidx.appcompat.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.content.pm.PackageManager;
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.google.android.gms.ads.initialization.InitializationStatus;
+import com.google.android.gms.ads.initialization.OnInitializationCompleteListener;
import com.yarolegovich.lovelydialog.LovelyCustomDialog;
-import com.yarolegovich.lovelydialog.LovelyInfoDialog;
-import com.yarolegovich.lovelydialog.LovelyStandardDialog;
import org.dslul.ticketreader.util.HelperFunctions;
+import static org.dslul.ticketreader.util.HelperFunctions.millisToString;
+
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 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 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);
+ //initialize NFC first
+ 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);
+
+ onNewIntent(getIntent());
+
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
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);
-
- onNewIntent(getIntent());
+ MobileAds.initialize(this, new OnInitializationCompleteListener() {
+ @Override
+ public void onInitializationComplete(InitializationStatus initializationStatus) {
+ }
+ });
+ AdRequest adRequest = new AdRequest.Builder().build();
+ adview.loadAd(adRequest);
/*
new LovelyInfoDialog(this)
.setTopColorRes(R.color.darkBlueGrey)
.setIcon(R.drawable.ic_info_outline_white_36dp)
//This will add Don't show again checkbox to the dialog. You can pass any ID as argument
.setNotShowAgainOptionEnabled(0)
.setNotShowAgainOptionChecked(false)
.setTitle("BETA")
.setMessage("Questa versione è una beta e potrebbe restituire risultati sbagliati. Segnalare per favore ogni incongruenza (insieme ad una copia del contenuto della carta) all'indirizzo email specificato nelle informazioni. Grazie.")
.show();
*/
}
@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)) {
+ super.onNewIntent(intent);
+ if (intent.getAction().equals(NfcAdapter.ACTION_TECH_DISCOVERED)) {
- NfcThread nfcThread = new NfcThread(getBaseContext(), intent, mContentHandler, mToastShortHandler, mToastLongHandler, mShowInfoDialogHandler);
- nfcThread.start();
- }
+ NfcThread nfcThread = new NfcThread(getBaseContext(), intent, mContentHandler, mToastShortHandler, mToastLongHandler, mShowInfoDialogHandler);
+ nfcThread.start();
+ }
}
@SuppressLint("HandlerLeak")
private Handler mContentHandler = new Handler() {
public void handleMessage(Message msg) {
List dumplist = (List)msg.obj;
dump = dumplist;
if(timer != null)
timer.cancel();
try {
//smartcard
if(dumplist.size() == 15) {
SmartCard smartcard = new SmartCard(dumplist);
if(smartcard.hasSubscriptions() && !smartcard.hasTickets()) {
dataLabel.setText(R.string.expire_date);
tipologia.setText(smartcard.getSubscriptionName());
dataObliterazione.setText(smartcard.getExpireDate());
if(smartcard.isSubscriptionExpired()) {
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.getTicketName(),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());
ex.printStackTrace();
}
}
};
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))));
+ statoBiglietto.setText(String.format("%s %s",
+ getResources().getString(R.string.in_corso), millisToString(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) {
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_settings) {
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
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();
}
};
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) {
}
});
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 5692a9c..21415db 100755
--- a/app/src/main/java/org/dslul/ticketreader/NfcThread.java
+++ b/app/src/main/java/org/dslul/ticketreader/NfcThread.java
@@ -1,214 +1,220 @@
package org.dslul.ticketreader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
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.util.Log;
import static org.dslul.ticketreader.util.HelperFunctions.byteArrayToHexString;
import static org.dslul.ticketreader.util.HelperFunctions.hexStringToByteArray;
//parts of code from http://www.emutag.com/soft.php
public class NfcThread extends Thread {
private Context context;
private Intent intent;
private Handler mTextBufferHandler, mToastShortHandler, mToastLongHandler, mShowInfoDialogHandler;
private byte[] readBuffer = new byte[1024]; // maximum theoretical capacity of MIFARE Ultralight
NfcThread(
Context context,
Intent intent,
Handler mTextBufferHandler, Handler mToastShortHandler, Handler mToastLongHandler, Handler mShowInfoDialogHandler
) {
this.context = context;
this.intent = intent;
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();
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")));
//efValidation
dumplist.add(isoDep.transceive(hexStringToByteArray("00B201CC1D")));
+ setContentBuffer(dumplist);
+
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) {
+ setContentBuffer(new ArrayList());
showToastLong(context.getString(R.string.read_failure));
}
}
}
private void handleNfcA(Tag tagFromIntent) {
final NfcA mfu = NfcA.get(tagFromIntent);
if (mfu == null) {
+ setContentBuffer(new ArrayList());
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) {
+ setContentBuffer(new ArrayList());
showToastLong(context.getString(R.string.ticket_not_supported));
return;
}
int pagesRead;
List dumplist = new ArrayList<>();
try {
mfu.connect();
pagesRead = rdNumPages(mfu, 16); // 0 for no limit (until error)
mfu.close();
for (int i = 0; i < pagesRead*4; i += 4) {
byte[] mfuPage = new byte[4];
System.arraycopy(readBuffer, i, mfuPage, 0, 4);
dumplist.add(mfuPage);
}
+ setContentBuffer(dumplist);
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) {
+ setContentBuffer(new ArrayList());
showToastLong(context.getString(R.string.read_failure));
}
catch (Exception e) {
+ setContentBuffer(new ArrayList());
showToastLong(context.getString(R.string.communication_error));
}
}
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 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, 4);
return 0;
}
}
diff --git a/app/src/main/java/org/dslul/ticketreader/SettingsActivity.java b/app/src/main/java/org/dslul/ticketreader/SettingsActivity.java
index 28dfd7d..30b32c2 100644
--- a/app/src/main/java/org/dslul/ticketreader/SettingsActivity.java
+++ b/app/src/main/java/org/dslul/ticketreader/SettingsActivity.java
@@ -1,45 +1,44 @@
package org.dslul.ticketreader;
import android.content.ComponentName;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
-import android.support.v7.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
-import android.support.v7.preference.PreferenceManager;
-import android.support.v7.preference.SwitchPreferenceCompat;
-import android.util.Log;
+import androidx.preference.PreferenceManager;
+import androidx.preference.SwitchPreferenceCompat;
public class SettingsActivity extends AppCompatActivity {
public static final String AUTOSTART_SWITCH = "switch_autostart";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportFragmentManager().beginTransaction()
.replace(android.R.id.content, new SettingsFragment())
.commit();
SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(AUTOSTART_SWITCH)) {
boolean switchAutostart = sharedPreferences.getBoolean
(SettingsActivity.AUTOSTART_SWITCH, false);
PackageManager pm = getApplicationContext().getPackageManager();
ComponentName compName =
new ComponentName(getPackageName(), getPackageName() + ".AliasAutoStartMainActivity");
pm.setComponentEnabledSetting(
compName,
switchAutostart ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
}
};
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
sharedPref.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/dslul/ticketreader/SettingsFragment.java b/app/src/main/java/org/dslul/ticketreader/SettingsFragment.java
index f631160..961ef55 100644
--- a/app/src/main/java/org/dslul/ticketreader/SettingsFragment.java
+++ b/app/src/main/java/org/dslul/ticketreader/SettingsFragment.java
@@ -1,21 +1,21 @@
package org.dslul.ticketreader;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v7.preference.PreferenceFragmentCompat;
+import androidx.fragment.app.Fragment;
+import androidx.preference.PreferenceFragmentCompat;
/**
* A simple {@link Fragment} subclass.
*/
public class SettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState,
String rootKey) {
setPreferencesFromResource(R.xml.preferences, rootKey);
}
}
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 5ac0743..85bc0b6 100644
--- a/app/src/main/java/org/dslul/ticketreader/util/HelperFunctions.java
+++ b/app/src/main/java/org/dslul/ticketreader/util/HelperFunctions.java
@@ -1,70 +1,92 @@
package org.dslul.ticketreader.util;
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 String millisToString(long ms) {
+ long totalSecs = ms/1000;
+ long hours = (totalSecs / 3600);
+ long mins = (totalSecs / 60) % 60;
+ long secs = totalSecs % 60;
+ String minsString = (mins == 0)
+ ? "00"
+ : ((mins < 10)
+ ? "0" + mins
+ : "" + mins);
+ String secsString = (secs == 0)
+ ? "00"
+ : ((secs < 10)
+ ? "0" + secs
+ : "" + secs);
+ if (hours > 0)
+ return hours + ":" + minsString + ":" + secsString;
+ else if (mins > 0)
+ return mins + ":" + secsString;
+ else return "00:" + secsString;
+ }
+
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/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index d512d40..14e2a0f 100755
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,25 +1,25 @@
-
-
-
-
+
-
+
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
index 465051a..183a4d0 100755
--- a/app/src/main/res/layout/content_main.xml
+++ b/app/src/main/res/layout/content_main.xml
@@ -1,223 +1,224 @@
-
-
+
-
-
+
-
-
+
-
-
+ ads:adUnitId="ca-app-pub-2102716674867426/2375937430">
+
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 645a45d..4cd5695 100755
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -1,40 +1,40 @@
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
+ In corso
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. 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…
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!
Impostazioni
Disattiva se non vuoi che Android apra automaticamente l\'app quando viene avvicinato un biglietto
Apertura automatica
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7f78a7c..0e31855 100755
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,41 +1,41 @@
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
+ In progress
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. 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!
Settings
Turn off if you don\'t want Android to start the app when a tag is scanned
Automatically open
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 60e8097..3eef579 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -1,9 +1,9 @@
-
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index eaa42a0..ddd15c8 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.2.1'
+ classpath 'com.android.tools.build:gradle:3.5.2'
// 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
}
diff --git a/gradle.properties b/gradle.properties
index aac7c9b..9e6fce1 100755
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,17 +1,19 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
+android.enableJetifier=true
+android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index e9cd780..e597517 100755
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sat Dec 01 10:58:45 CET 2018
+#Mon Aug 26 13:12:33 CEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip