diff --git a/app/build.gradle b/app/build.gradle
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,8 +12,8 @@
minSdkVersion 21
targetSdkVersion 34
buildToolsVersion = '34.0.0'
- versionCode 57
- versionName "2.2.0"
+ versionCode 58
+ versionName "2.2.1"
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
javaCompileOptions {
@@ -70,6 +70,9 @@
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+
+ // Guava implementation for DBUpdateWorker
+ implementation 'com.google.guava:guava:29.0-android'
implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation "androidx.activity:activity:$activity_version"
@@ -83,7 +86,7 @@
implementation "androidx.work:work-runtime-ktx:$work_version"
- implementation "com.google.android.material:material:1.9.0"
+ implementation "com.google.android.material:material:1.11.0"
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
@@ -102,11 +105,9 @@
implementation 'com.google.protobuf:protobuf-java:3.19.6'
// mqtt library
implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
- implementation 'com.github.hannesa2:paho.mqtt.android:4.2'
+ implementation 'com.github.hannesa2:paho.mqtt.android:4.2.4'
//implementation 'com.github.fabmazz:paho.mqtt.android:v0.0.1'
-
-
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,9 +12,10 @@
-
+ -->
@@ -133,6 +134,8 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="it.reyboz.bustorino.ActivityPrincipal"/>
+
+
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/BustoApp.java b/app/src/main/java/it/reyboz/bustorino/BustoApp.java
--- a/app/src/main/java/it/reyboz/bustorino/BustoApp.java
+++ b/app/src/main/java/it/reyboz/bustorino/BustoApp.java
@@ -28,29 +28,44 @@
import org.acra.config.MailSenderConfigurationBuilder;
import org.acra.data.StringFormat;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.List;
+
import static org.acra.ReportField.*;
public class BustoApp extends MultiDexApplication {
- private static final ReportField[] REPORT_FIELDS = {REPORT_ID, APP_VERSION_CODE, APP_VERSION_NAME,
+ private static final List REPORT_FIELDS = List.of(REPORT_ID, APP_VERSION_CODE, APP_VERSION_NAME,
PACKAGE_NAME, PHONE_MODEL, BRAND, PRODUCT, ANDROID_VERSION, BUILD_CONFIG, CUSTOM_DATA,
IS_SILENT, STACK_TRACE, INITIAL_CONFIGURATION, CRASH_CONFIGURATION, DISPLAY, USER_COMMENT,
- USER_APP_START_DATE, USER_CRASH_DATE, LOGCAT, SHARED_PREFERENCES};
+ USER_APP_START_DATE, USER_CRASH_DATE, LOGCAT, SHARED_PREFERENCES);
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
- CoreConfigurationBuilder builder = new CoreConfigurationBuilder(this);
- builder.setBuildConfigClass(BuildConfig.class).setReportFormat(StringFormat.JSON)
- .setDeleteUnapprovedReportsOnApplicationStart(true);
- builder.getPluginConfigurationBuilder(MailSenderConfigurationBuilder.class).setMailTo("gtt@succhia.cz")
- .setReportFileName(it.reyboz.bustorino.BuildConfig.VERSION_NAME +"_report.json")
- .setResBody(R.string.acra_email_message)
- .setEnabled(true);
- builder.getPluginConfigurationBuilder(DialogConfigurationBuilder.class).setResText(R.string.message_crash)
- .setResTheme(R.style.AppTheme)
+ CoreConfigurationBuilder builder = new CoreConfigurationBuilder();
+ // mail stuff
+ MailSenderConfigurationBuilder mailConfig = new MailSenderConfigurationBuilder();
+ mailConfig.withMailTo("gtt@succhia.cz")
+ .withReportFileName(it.reyboz.bustorino.BuildConfig.VERSION_NAME +"_report.json")
+ .withBody(getString(R.string.acra_email_message))
.setEnabled(true);
+ //dialog stuff
+ DialogConfigurationBuilder dialogBuild = new DialogConfigurationBuilder();
+ dialogBuild.withText(getString(R.string.message_crash))
+ .withResTheme(R.style.AppTheme).setEnabled(true);
+ //Set options
+ builder.withBuildConfigClass(BuildConfig.class)
+ .withReportFormat(StringFormat.JSON)
+ .withDeleteUnapprovedReportsOnApplicationStart(true);
+ //Add plugins
+ builder.withPluginConfigurations(
+ mailConfig.build(), dialogBuild.build()
+ );
+
+
builder.setReportContent(REPORT_FIELDS);
if (!it.reyboz.bustorino.BuildConfig.DEBUG)
ACRA.init(this, builder);
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/Notifications.java b/app/src/main/java/it/reyboz/bustorino/backend/Notifications.java
--- a/app/src/main/java/it/reyboz/bustorino/backend/Notifications.java
+++ b/app/src/main/java/it/reyboz/bustorino/backend/Notifications.java
@@ -11,6 +11,7 @@
public class Notifications {
public static final String DEFAULT_CHANNEL_ID ="Default";
public static final String DB_UPDATE_CHANNELS_ID ="Database Update";
+ public static final String MATO_LIVE_POSITIONS_CHANNEL="Live Positions";
//match this value to the one used by the MQTTAndroidClient MANUALLY
@@ -64,12 +65,27 @@
.setContentText(title)
.build();
}
+
+ public static Notification makeLivePositionsNotification(Context context,String title){
+ return new NotificationCompat.Builder(context, Notifications.MATO_LIVE_POSITIONS_CHANNEL)
+ //.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), Constants.PENDING_INTENT_FLAG_IMMUTABLE))
+ .setSmallIcon(R.drawable.ic_bus_stilized_transparent)
+ .setOngoing(true)
+ .setAutoCancel(true)
+ .setOnlyAlertOnce(true)
+ .setPriority(NotificationCompat.PRIORITY_MIN)
+ .setContentTitle(context.getString(R.string.app_name))
+ .setLocalOnly(true)
+ .setVisibility(NotificationCompat.VISIBILITY_SECRET)
+ .setContentText(title)
+ .build();
+ }
public static Notification makeMatoDownloadNotification(Context context){
return makeMatoDownloadNotification(context, context.getString(R.string.downloading_data_mato));
}
public static Notification makeMQTTServiceNotification(Context context){
- return makeMatoDownloadNotification(context, context.getString(R.string.mqtt_notification_text));
+ return makeLivePositionsNotification(context, context.getString(R.string.mqtt_notification_text));
}
public static void cancelNotification(Context context, int notificationID){
@@ -88,4 +104,18 @@
notificationManager.createNotificationChannel(channel);
}
}
+
+ public static void createLivePositionsChannel(Context context){
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(
+ Notifications.MATO_LIVE_POSITIONS_CHANNEL,
+ context.getString(R.string.live_positions_notification_channel),
+ NotificationManager.IMPORTANCE_MIN
+ );
+ channel.setDescription(context.getString(R.string.live_positions_notification_channel_desc));
+
+ NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(channel);
+ }
+ }
}
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt b/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt
--- a/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt
+++ b/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt
@@ -48,12 +48,15 @@
client = MqttAndroidClient(context,SERVER_ADDR,clientID,Ack.AUTO_ACK)
// WE DO NOT WANT A FOREGROUND SERVICE -> it's only more mayhem
// (and the positions need to be downloaded only when the app is shown)
- /*if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
+ // update, 2024-04: Google Play doesn't understand our needs, so we put back the notification
+ // and add a video of it working as Google wants
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
//we need a notification
+ Notifications.createLivePositionsChannel(context)
val notific = Notifications.makeMQTTServiceNotification(context)
- client.setForegroundService(notific)
+ client!!.setForegroundService(notific)
notification=notific
- }*/
+ }
val options = MqttConnectOptions()
diff --git a/app/src/main/java/it/reyboz/bustorino/data/DBUpdateWorker.java b/app/src/main/java/it/reyboz/bustorino/data/DBUpdateWorker.java
--- a/app/src/main/java/it/reyboz/bustorino/data/DBUpdateWorker.java
+++ b/app/src/main/java/it/reyboz/bustorino/data/DBUpdateWorker.java
@@ -34,6 +34,7 @@
import static android.content.Context.MODE_PRIVATE;
+//TODO: Move to code to Kotlin
public class DBUpdateWorker extends Worker{
diff --git a/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt b/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt
--- a/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt
+++ b/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt
@@ -82,15 +82,20 @@
}
requestCountDown.await()
val tripsIDsCompleted = downloadedMatoTrips.map { trip-> trip.tripID }
- val doInsert = (queriedMatoTrips subtract failedMatoTripsDownload).containsAll(tripsIDsCompleted)
- Log.i(DEBUG_TAG, "Inserting missing GtfsTrips in the database, should insert $doInsert")
- if(doInsert){
+ if (tripsIDsCompleted.isEmpty()){
+ Log.d(DEBUG_TAG, "No trips have been downloaded, set work to fail")
+ return Result.failure()
+ } else {
+ val doInsert = (queriedMatoTrips subtract failedMatoTripsDownload).containsAll(tripsIDsCompleted)
+ Log.i(DEBUG_TAG, "Inserting missing GtfsTrips in the database, should insert $doInsert")
+ if (doInsert) {
- gtfsRepository.gtfsDao.insertTrips(downloadedMatoTrips)
+ gtfsRepository.gtfsDao.insertTrips(downloadedMatoTrips)
- }
+ }
- return Result.success()
+ return Result.success()
+ }
}
override suspend fun getForegroundInfo(): ForegroundInfo {
val notificationManager =
@@ -109,8 +114,8 @@
const val TAG_TRIPS ="gtfsTripsDownload"
- fun downloadTripsFromMato(trips: List, context: Context, debugTag: String): Boolean{
- if (trips.isEmpty()) return false
+ fun requestMatoTripsDownload(trips: List, context: Context, debugTag: String): OneTimeWorkRequest? {
+ if (trips.isEmpty()) return null
val workManager = WorkManager.getInstance(context)
val info = workManager.getWorkInfosForUniqueWork(TAG_TRIPS).get()
@@ -128,8 +133,8 @@
.addTag(TAG_TRIPS)
.build()
workManager.enqueueUniqueWork(TAG_TRIPS, ExistingWorkPolicy.KEEP, requ)
- }
- return true
+ return requ
+ } else return null;
}
}
}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
@@ -366,7 +366,7 @@
//download missing tripIDs
liveBusViewModel.tripsGtfsIDsToQuery.observe(viewLifecycleOwner){
//gtfsPosViewModel.downloadTripsFromMato(dat);
- MatoTripsDownloadWorker.downloadTripsFromMato(
+ MatoTripsDownloadWorker.requestMatoTripsDownload(
it, requireContext().applicationContext,
"BusTO-MatoTripDownload"
)
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java
@@ -46,7 +46,6 @@
import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate;
import it.reyboz.bustorino.backend.mato.MQTTMatoClient;
import it.reyboz.bustorino.backend.utils;
-import it.reyboz.bustorino.data.MatoTripsDownloadWorker;
import it.reyboz.bustorino.data.gtfs.MatoPattern;
import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops;
import it.reyboz.bustorino.map.*;
@@ -374,12 +373,16 @@
else
livePositionsViewModel.requestGTFSUpdates();
//mapViewModel.testCascade();
+ livePositionsViewModel.isLastWorkResultGood().observe(this, d ->
+ Log.d(DEBUG_TAG, "Last trip download result is "+d));
livePositionsViewModel.getTripsGtfsIDsToQuery().observe(this, dat -> {
Log.i(DEBUG_TAG, "Have these trips IDs missing from the DB, to be queried: "+dat);
- //gtfsPosViewModel.downloadTripsFromMato(dat);
- MatoTripsDownloadWorker.Companion.downloadTripsFromMato(dat,
+ livePositionsViewModel.downloadTripsFromMato(dat);
+ /*MatoTripsDownloadWorker.Companion.requestMatoTripsDownload(dat,
requireContext().getApplicationContext(),
"BusTO-MatoTripDownload");
+
+ */
});
} /*else if(gtfsPosViewModel!=null){
gtfsPosViewModel.requestUpdates();
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/GtfsPositionsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/GtfsPositionsViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/GtfsPositionsViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/GtfsPositionsViewModel.kt
@@ -20,6 +20,7 @@
import android.app.Application
import android.util.Log
import androidx.lifecycle.*
+import androidx.work.OneTimeWorkRequest
import com.android.volley.Response
import it.reyboz.bustorino.backend.NetworkVolleyManager
import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
@@ -166,8 +167,8 @@
Keep in mind that trips DO CHANGE often, and so do the Patterns
*/
- fun downloadTripsFromMato(trips: List): Boolean{
- return MatoTripsDownloadWorker.downloadTripsFromMato(trips,getApplication(), DEBUG_TI)
+ fun downloadTripsFromMato(trips: List): OneTimeWorkRequest?{
+ return MatoTripsDownloadWorker.requestMatoTripsDownload(trips,getApplication(), "BusTO-MatoTripsDown")
}
private fun downloadMissingPatterns(routeIds: List): Boolean{
return MatoPatternsDownloadWorker.downloadPatternsForRoutes(routeIds, getApplication())
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt
@@ -20,6 +20,8 @@
import android.app.Application
import android.util.Log
import androidx.lifecycle.*
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
import com.android.volley.DefaultRetryPolicy
import com.android.volley.Response
import it.reyboz.bustorino.backend.NetworkVolleyManager
@@ -28,9 +30,14 @@
import it.reyboz.bustorino.backend.mato.MQTTMatoClient
import it.reyboz.bustorino.data.GtfsRepository
import it.reyboz.bustorino.data.MatoPatternsDownloadWorker
+import it.reyboz.bustorino.data.MatoTripsDownloadWorker
import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import java.util.*
+import kotlin.collections.ArrayList
+import kotlin.collections.HashMap
+import kotlin.collections.HashSet
class LivePositionsViewModel(application: Application): AndroidViewModel(application) {
@@ -49,6 +56,27 @@
private val gtfsRtRequestRunning = MutableLiveData(false)
+ private val lastFailedTripsRequest = HashMap()
+ private val workManager = WorkManager.getInstance(application)
+
+ private var lastRequestedDownloadTrips = MutableLiveData>()
+
+ var isLastWorkResultGood = workManager
+ .getWorkInfosForUniqueWorkLiveData(MatoTripsDownloadWorker.TAG_TRIPS).map { it ->
+ if (it.isEmpty()) return@map false
+ var res = true
+ if(it[0].state == WorkInfo.State.FAILED){
+ val currDate = Date()
+ res = false
+ lastRequestedDownloadTrips.value?.let { trips->
+ for(tr in trips){
+ lastFailedTripsRequest[tr] = currDate
+ }
+ }
+
+ }
+ return@map res
+ }
/**
* Responder to the MQTT Client
*/
@@ -218,8 +246,42 @@
mqttClient.disconnect()
super.onCleared()
}
+ //Request trips download
+ fun downloadTripsFromMato(trips: List): Boolean{
+ if(trips.isEmpty())
+ return false
+ var shouldContinue = false
+ val currentDateTime = Date().time
+
+ for (tr in trips){
+ if (!lastFailedTripsRequest.containsKey(tr)){
+ shouldContinue = true
+ break
+ } else{
+ //Log.i(DEBUG_TI, "Last time the trip has failed is ${lastFailedTripsRequest[tr]}")
+ if ((lastFailedTripsRequest[tr]!!.time - currentDateTime) > MAX_TIME_RETRY){
+ shouldContinue =true
+ break
+ }
+ }
+ }
+ if (shouldContinue) {
+ //if one trip
+ val workRequ =MatoTripsDownloadWorker.requestMatoTripsDownload(trips, getApplication(), "BusTO-MatoTripsDown")
+ workRequ?.let { req ->
+ Log.d(DEBUG_TI, "Enqueueing new work, saving work info")
+ lastRequestedDownloadTrips.postValue(trips)
+ //isLastWorkResultGood =
+ }
+ } else{
+ Log.w(DEBUG_TI, "Requested to fetch data for ${trips.size} trips but they all have failed before in the last $MAX_MINUTES_RETRY mins")
+ }
+ return shouldContinue
+ }
companion object{
private const val DEBUG_TI = "BusTO-LivePosViewModel"
+ private const val MAX_MINUTES_RETRY = 3
+ private const val MAX_TIME_RETRY = MAX_MINUTES_RETRY*60*1000 //3 minutes (in milliseconds)
}
}
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -177,7 +177,12 @@
Canale default delle notifiche
Operazioni sul database
Informazioni sul database (aggiornamento)
- Servizio posizioni in tempo reale attivo
+ BusTO - posizioni in tempo reale
+
+ Posizioni in tempo reale
+ Attività del servizio delle posizioni in tempo reale
+
+ Servizio posizioni MaTO in tempo reale attivo
Downloading trips from MaTO server
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -203,7 +203,10 @@
Default channel for notifications
Database operations
Updates of the app database
- Bus live positions service is running
+ BusTO - live position service
+ Live positions
+ Showing activity related to the live positions service
+ MaTO live bus positions service is running
Downloading trips from MaTO server
diff --git a/build.gradle b/build.gradle
--- a/build.gradle
+++ b/build.gradle
@@ -11,7 +11,7 @@
}
//kotlin
ext.kotlin_version = '1.9.0'
- ext.coroutines_version = "1.7.3"
+ ext.coroutines_version = "1.8.0"
dependencies {
classpath 'com.android.tools.build:gradle:8.1.4'
@@ -31,8 +31,8 @@
preference_version = "1.2.1"
work_version = "2.9.0"
- acra_version = "5.7.0"
- lifecycle_version = "2.4.1"
+ acra_version = "5.11.3"
+ lifecycle_version = "2.7.0"
arch_version = "2.1.0"
room_version = "2.5.2"