diff --git a/app/build.gradle b/app/build.gradle --- a/app/build.gradle +++ b/app/build.gradle @@ -102,7 +102,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.1' + implementation 'com.github.hannesa2:paho.mqtt.android:4.2' + //implementation 'com.github.fabmazz:paho.mqtt.android:v0.0.1' + // ViewModel 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 @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.INTERNET"/> @@ -10,6 +10,9 @@ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> + <!-- this is to REMOVE the permission from MQTT service --> + <uses-permission tools:node="remove" + android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/> <queries> <intent> 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 @@ -12,6 +12,8 @@ public static final String DEFAULT_CHANNEL_ID ="Default"; public static final String DB_UPDATE_CHANNELS_ID ="Database Update"; + //match this value to the one used by the MQTTAndroidClient MANUALLY + public static void createDefaultNotificationChannel(Context context) { // Create the NotificationChannel, but only on API 26+ because // the NotificationChannel class is new and not in the support library @@ -51,7 +53,7 @@ public static Notification makeMatoDownloadNotification(Context context,String title){ return new NotificationCompat.Builder(context, Notifications.DB_UPDATE_CHANNELS_ID) //.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), Constants.PENDING_INTENT_FLAG_IMMUTABLE)) - .setSmallIcon(R.drawable.bus) + .setSmallIcon(R.drawable.ic_bus_stilized_transparent) .setOngoing(true) .setAutoCancel(true) .setOnlyAlertOnce(true) @@ -66,6 +68,15 @@ 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)); + } + + public static void cancelNotification(Context context, int notificationID){ + NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + manager.cancel(notificationID); + } + public static void createDBNotificationChannel(Context context){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( 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 @@ -1,23 +1,25 @@ package it.reyboz.bustorino.backend.mato +import android.app.Notification +import android.app.NotificationManager import android.content.Context +import android.os.Build import android.util.Log import androidx.lifecycle.LifecycleOwner import info.mqtt.android.service.Ack -import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate -import org.eclipse.paho.client.mqttv3.* import info.mqtt.android.service.MqttAndroidClient import info.mqtt.android.service.QoS - +import it.reyboz.bustorino.backend.Notifications +import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate +import org.eclipse.paho.client.mqttv3.* import org.json.JSONArray import org.json.JSONException import java.lang.ref.WeakReference -import java.util.ArrayList -import java.util.Properties +import java.util.* typealias PositionsMap = HashMap<String, HashMap<String, LivePositionUpdate> > -class MQTTMatoClient private constructor(): MqttCallbackExtended{ +class MQTTMatoClient(): MqttCallbackExtended{ private var isStarted = false private var subscribedToAll = false @@ -30,14 +32,29 @@ private val currentPositions = PositionsMap() private lateinit var lifecycle: LifecycleOwner + //TODO: remove class reference to context (always require context in all methods) private var context: Context?= null private var connectionTrials = 0 + private var notification: Notification? = null + + //private lateinit var notification: Notification private fun connect(context: Context, iMqttActionListener: IMqttActionListener?){ val clientID = "mqtt-explorer-${getRandomString(8)}"//"mqttjs_${getRandomString(8)}" + //notification = Notifications.makeMQTTServiceNotification(context) + 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){ + //we need a notification + val notific = Notifications.makeMQTTServiceNotification(context) + client.setForegroundService(notific) + notification=notific + }*/ + val options = MqttConnectOptions() //options.sslProperties = @@ -121,7 +138,7 @@ return true } - fun desubscribe(responder: MQTTMatoListener){ + fun stopMatoRequests(responder: MQTTMatoListener){ var removed = false for ((line,v)in respondersMap.entries){ var done = false @@ -143,7 +160,8 @@ } removed = done || removed } - // remove lines that have no responders + // check responders map, remove lines that have no responders + for(line in respondersMap.keys){ if(respondersMap[line]?.isEmpty() == true){ respondersMap.remove(line) @@ -155,6 +173,15 @@ return currentPositions } + /** + * Cancel the notification + */ + fun removeNotification(context: Context){ + val notifManager = context.applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + notifManager.cancel(MQTT_NOTIFICATION_ID) + } + private fun sendUpdateToResponders(responders: ArrayList<WeakReference<MQTTMatoListener>>): Int{ //var sent = false var count = 0 @@ -174,9 +201,20 @@ } override fun connectionLost(cause: Throwable?) { + var doReconnect = false + for ((line,elms) in respondersMap.entries){ + if(!elms.isEmpty()){ + doReconnect = true + break + } + } + if (!doReconnect){ + Log.d(DEBUG_TAG, "Disconnected, but no responders to give the positions, avoid reconnecting") + //finish here + return + } Log.w(DEBUG_TAG, "Lost connection in MQTT Mato Client") - synchronized(this){ // isStarted = false //var i = 0 @@ -227,9 +265,7 @@ try { val jsonList = JSONArray(messString) - //val full = if(jsonList.length()>7) { - // if (jsonList.get(7).equals(null)) null else jsonList.getInt(7) - //}else null + /*val posUpdate = MQTTPositionUpdate(lineId+"U", vehicleId, jsonList.getDouble(0), jsonList.getDouble(1), @@ -240,7 +276,6 @@ if(jsonList.get(6).equals(null)) null else jsonList.getInt(6), //full ) - */ if(jsonList.get(4)==null){ Log.d(DEBUG_TAG, "We have null tripId: line $lineId veh $vehicleId: $jsonList") @@ -309,18 +344,27 @@ //NOT USED (we're not sending any messages) } + /*/** + * Stop the service forever. Client has not to be used again!! + */ + fun closeClientForever(){ + client.disconnect() + client.close() + }*/ + + fun disconnect(){ + client.disconnect() + } + companion object{ const val SERVER_ADDR="wss://mapi.5t.torino.it:443/scre" const val LINES_ALL="ALL" private const val DEBUG_TAG="BusTO-MatoMQTT" - @Volatile - private var instance: MQTTMatoClient? = null + //this has to match the value in MQTT library (MQTTAndroidClient) + const val MQTT_NOTIFICATION_ID: Int = 77 - fun getInstance() = instance?: synchronized(this){ - instance?: MQTTMatoClient().also { instance= it } - } @JvmStatic fun mapTopic(lineId: String): String{ 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 @@ -91,7 +91,7 @@ .setContentTitle(con.getString(R.string.database_update_msg_notif)) .setProgress(0,0,true) .setPriority(NotificationCompat.PRIORITY_LOW); - builder.setSmallIcon(R.drawable.ic_bus_orange); + builder.setSmallIcon(R.drawable.ic_bus_stilized); notificationManager.notify(notification_ID,builder.build()); 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 @@ -42,7 +42,7 @@ private val netVolleyManager = NetworkVolleyManager.getInstance(application) - private var mqttClient = MQTTMatoClient.getInstance() + private var mqttClient = MQTTMatoClient() private var lineListening = "" private var lastTimeReceived: Long = 0 @@ -155,7 +155,7 @@ fun stopMatoUpdates(){ viewModelScope.launch { val tt = System.currentTimeMillis() - mqttClient.desubscribe(matoPositionListener) + mqttClient.stopMatoRequests(matoPositionListener) val time = System.currentTimeMillis() -tt Log.d(DEBUG_TI, "Took $time ms to unsubscribe") } @@ -212,7 +212,14 @@ } } + override fun onCleared() { + //stop the MQTT Service + Log.d(DEBUG_TI, "Clearing the live positions view model, stopping the mqttClient") + mqttClient.disconnect() + super.onCleared() + } + companion object{ private const val DEBUG_TI = "BusTO-LivePosViewModel" } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_bus_stilized.xml b/app/src/main/res/drawable/ic_bus_stilized.xml new file mode 100644 --- /dev/null +++ b/app/src/main/res/drawable/ic_bus_stilized.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M12.45,42.5q-0.75,0 -1.35,-0.475 -0.6,-0.475 -0.6,-1.175v-4.1q-1.45,-0.85 -2.125,-2.375T7.7,31.15L7.7,11.2q0,-3.85 3.9,-5.65 3.9,-1.8 12.45,-1.8 8.45,0 12.35,1.775Q40.3,7.3 40.3,11.2v19.95q0,1.7 -0.675,3.225Q38.95,35.9 37.5,36.75v4.1q0,0.7 -0.6,1.175 -0.6,0.475 -1.35,0.475h-0.65q-0.85,0 -1.425,-0.475 -0.575,-0.475 -0.575,-1.125v-2.5L15.1,38.4v2.5q0,0.65 -0.575,1.125T13.1,42.5ZM24.05,10L37,10 11,10h13.05ZM32.8,24.65L11.1,24.65h25.8,-4.1ZM11.1,21.65h25.8L36.9,13L11.1,13ZM16.35,32.55q1.15,0 1.95,-0.8t0.8,-1.95q0,-1.15 -0.8,-1.95t-1.95,-0.8q-1.15,0 -1.95,0.8t-0.8,1.95q0,1.15 0.8,1.95t1.95,0.8ZM31.65,32.55q1.15,0 1.95,-0.8t0.8,-1.95q0,-1.15 -0.8,-1.95t-1.95,-0.8q-1.15,0 -1.95,0.8t-0.8,1.95q0,1.15 0.8,1.95t1.95,0.8ZM11,10h26q-1.2,-1.3 -4.6,-2.05 -3.4,-0.75 -8.35,-0.75 -5.9,0 -9.05,0.675 -3.15,0.675 -4,2.125ZM15.2,35h17.6q1.75,0 2.925,-1.375Q36.9,32.25 36.9,30.55v-5.9L11.1,24.65v5.9q0,1.7 1.175,3.075Q13.45,35 15.2,35Z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_bus_stilized_transparent.xml b/app/src/main/res/drawable/ic_bus_stilized_transparent.xml new file mode 100644 --- /dev/null +++ b/app/src/main/res/drawable/ic_bus_stilized_transparent.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + <path + android:fillColor="#CCFFFFFF" + android:pathData="M12.45,42.5q-0.75,0 -1.35,-0.475 -0.6,-0.475 -0.6,-1.175v-4.1q-1.45,-0.85 -2.125,-2.375T7.7,31.15L7.7,11.2q0,-3.85 3.9,-5.65 3.9,-1.8 12.45,-1.8 8.45,0 12.35,1.775Q40.3,7.3 40.3,11.2v19.95q0,1.7 -0.675,3.225Q38.95,35.9 37.5,36.75v4.1q0,0.7 -0.6,1.175 -0.6,0.475 -1.35,0.475h-0.65q-0.85,0 -1.425,-0.475 -0.575,-0.475 -0.575,-1.125v-2.5L15.1,38.4v2.5q0,0.65 -0.575,1.125T13.1,42.5ZM24.05,10L37,10 11,10h13.05ZM32.8,24.65L11.1,24.65h25.8,-4.1ZM11.1,21.65h25.8L36.9,13L11.1,13ZM16.35,32.55q1.15,0 1.95,-0.8t0.8,-1.95q0,-1.15 -0.8,-1.95t-1.95,-0.8q-1.15,0 -1.95,0.8t-0.8,1.95q0,1.15 0.8,1.95t1.95,0.8ZM31.65,32.55q1.15,0 1.95,-0.8t0.8,-1.95q0,-1.15 -0.8,-1.95t-1.95,-0.8q-1.15,0 -1.95,0.8t-0.8,1.95q0,1.15 0.8,1.95t1.95,0.8ZM11,10h26q-1.2,-1.3 -4.6,-2.05 -3.4,-0.75 -8.35,-0.75 -5.9,0 -9.05,0.675 -3.15,0.675 -4,2.125ZM15.2,35h17.6q1.75,0 2.925,-1.375Q36.9,32.25 36.9,30.55v-5.9L11.1,24.65v5.9q0,1.7 1.175,3.075Q13.45,35 15.2,35Z"/> +</vector> 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 @@ -131,6 +131,8 @@ <string name="enable_position_message_map">Consenti l\'accesso alla posizione per mostrarla sulla mappa</string> + <string name="enable_position_message_nearby">Consenti l\'accesso alla posizione per mostrare le fermate vicine</string> + <string name="enableGpsText">Abilitare il GPS</string> <string name="bus_arriving_at">arriva alle</string> <string name="arrivals_card_at_the_stop">alla fermata</string> @@ -175,6 +177,8 @@ <string name="default_notification_channel_description">Canale default delle notifiche</string> <string name="database_notification_channel">Operazioni sul database</string> <string name="database_notification_channel_desc">Informazioni sul database (aggiornamento)</string> + <string name="mqtt_notification_text">Servizio posizioni in tempo reale attivo</string> + <string name="db_trips_download_message">Downloading trips from MaTO server</string> 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,6 +203,7 @@ <string name="default_notification_channel_description">Default channel for notifications</string> <string name="database_notification_channel">Database operations</string> <string name="database_notification_channel_desc">Updates of the app database</string> + <string name="mqtt_notification_text">Bus live positions service is running</string> <string name="db_trips_download_message">Downloading trips from MaTO server</string> diff --git a/build.gradle b/build.gradle --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ activity_version = "1.7.2" appcompat_version = "1.6.1" preference_version = "1.2.1" - work_version = "2.8.1" + work_version = "2.9.0" acra_version = "5.7.0" lifecycle_version = "2.4.1"