diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 884d74b2..cd98672f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ + diff --git a/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/MQTT/MQTTService.java b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/MQTT/MQTTService.java index d4270189..5cf18801 100644 --- a/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/MQTT/MQTTService.java +++ b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/MQTT/MQTTService.java @@ -9,6 +9,7 @@ package de.fraunhofer.fokus.OpenMobileNetworkToolkit.MQTT; import android.app.Notification; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; @@ -20,6 +21,7 @@ import android.os.Handler; import android.os.IBinder; import android.util.Log; +import android.widget.Toast; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; @@ -29,19 +31,27 @@ import androidx.work.multiprocess.RemoteWorkManager; +import com.hivemq.client.mqtt.MqttClientState; import com.hivemq.client.mqtt.datatypes.MqttQos; +import com.hivemq.client.mqtt.lifecycle.MqttClientConnectedContext; +import com.hivemq.client.mqtt.lifecycle.MqttClientDisconnectedContext; import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient; import com.hivemq.client.mqtt.mqtt5.Mqtt5Client; import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAck; import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5PayloadFormatIndicator; +import org.jetbrains.annotations.NotNull; + import java.net.InetSocketAddress; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.EnumSet; import java.util.HashMap; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import de.fraunhofer.fokus.OpenMobileNetworkToolkit.CustomEventListener; import de.fraunhofer.fokus.OpenMobileNetworkToolkit.MQTT.Handler.Iperf3Handler; @@ -77,31 +87,104 @@ private void setupSharedPreferences(){ mqttSP = spg.getSharedPreference(SPType.MQTT); mqttSP.registerOnSharedPreferenceChangeListener((sharedPreferences, key) -> { if(key == null) return; - if (key.equals("mqtt_host")) { - Log.d(TAG, "MQTT Host update: " + sharedPreferences.getString("mqtt_host", "")); - client.disconnect(); - createClient(); - createNotification(); + isEnabled = sharedPreferences.getBoolean("enable_mqtt", false); + switch (key){ + case "mqtt_host": + if(!isEnabled) return; + Log.d(TAG, "mqtt_host: " + sharedPreferences.getString("mqtt_host", "")); + disconnectClient(); + createClient(); + createNotification(); + break; + case "enable_mqtt": + Log.d(TAG, "enable_mqtt: " + isEnabled); + if(!isEnabled && client != null){ + this.onDestroy(); + } + break; } + }); } - - public void createClient(){ - String addressString = mqttSP.getString("mqtt_host", "localhost:1883"); - String host = null; - int port = -1; + private boolean isValidUrl(String addressString) { try { - host = addressString.split(":")[0]; - port = Integer.parseInt(addressString.split(":")[1]); + new java.net.URL(addressString); + return true; } catch (Exception e) { - Log.e(TAG, "createClient: Invalid address string: " + addressString); + return false; + } + } + + private boolean isProtocolIpPort(String addressString) { + // Example: mqtt://192.168.1.1:1883 + String regex = "^[\\d.]+:\\d+$"; + return addressString.matches(regex); + } + + + public String mQTTClientStateToString(MqttClientState state) { + switch (state) { + case CONNECTED: + return "Connected"; + case CONNECTING: + return "Connecting"; + case DISCONNECTED: + return "Disconnected"; + case DISCONNECTED_RECONNECT: + return "Disconnected_Reconnecting"; + case CONNECTING_RECONNECT: + return "Connecting_Reconnecting"; + default: + return "Unknown"; + } + } + + + public void createClient() { + String addressString = mqttSP.getString("mqtt_host", ""); + Log.d(TAG, "createClient: creating client..."); + if (addressString.isBlank()) { + Log.e(TAG, "createClient: MQTT Host is empty"); + spg.getSharedPreference(SPType.MQTT).edit().putBoolean("enable_mqtt", false).apply(); + client = null; return; } - if(host == null || port == -1){ - Log.e(TAG, "createClient: Invalid address string: " + addressString); + + if (!isValidUrl(addressString) && !isProtocolIpPort(addressString)) { + Log.e(TAG, "createClient: MQTT Host is not a valid URL or IP:Port"); + Toast.makeText(context, "MQTT Host is not a valid URL or IP:Port", Toast.LENGTH_SHORT).show(); + spg.getSharedPreference(SPType.MQTT).edit().putBoolean("enable_mqtt", false).apply(); + client = null; + return; + } + + String host; + int port; + + try { + if (isProtocolIpPort(addressString)) { + // Case: raw host:port + String[] hostPort = addressString.split(":"); + host = hostPort[0]; + port = Integer.parseInt(hostPort[1]); + } else { + // Case: URL with scheme + URI uri = new URI(addressString); + host = uri.getHost(); + port = uri.getPort() == -1 ? 1883 : uri.getPort(); // default MQTT port + } + } catch (Exception e) { + Log.e(TAG, "createClient: Invalid MQTT address", e); + spg.getSharedPreference(SPType.MQTT).edit().putBoolean("enable_mqtt", false).apply(); + client = null; return; } - InetSocketAddress address = new InetSocketAddress(host, port); + + InetSocketAddress address = InetSocketAddress.createUnresolved(host, port); + if(client != null){ + disconnectClient(); + client = null; + } client = Mqtt5Client.builder() .identifier(deviceName) .serverAddress(address) @@ -109,30 +192,35 @@ public void createClient(){ .initialDelay(5, TimeUnit.SECONDS) .maxDelay(30, TimeUnit.SECONDS) .applyAutomaticReconnect() - .addConnectedListener(context -> { - Log.i(TAG, "createClient: Connected to MQTT server"); - createNotification(); + .addConnectedListener(ctx -> { + Log.i(TAG, "addConnectedListener: Connected to MQTT server"); + createNotification(null, ctx); publishToTopic(String.format("device/%s/status", deviceName), "1", false); + Log.d(TAG, "addConnectedListener: "+mQTTClientStateToString(client.getState())); }) - .addDisconnectedListener(context -> { - Log.i(TAG, "createClient: Disconnected from MQTT server"); - createNotification(); + .addDisconnectedListener(ctx -> { + Log.i(TAG, "addDisconnectedListener: Disconnected from MQTT server"); + createNotification(ctx, null); + }) .willPublish() - .topic(String.format("device/%s/status", deviceName)) - .qos(MqttQos.EXACTLY_ONCE) - .payload("0".getBytes()) - .retain(true) - .payloadFormatIndicator(Mqtt5PayloadFormatIndicator.UTF_8) - .contentType("text/plain") - .noMessageExpiry() - .applyWillPublish() + .topic(String.format("device/%s/status", deviceName)) + .qos(MqttQos.EXACTLY_ONCE) + .payload("0".getBytes()) + .retain(true) + .payloadFormatIndicator(Mqtt5PayloadFormatIndicator.UTF_8) + .contentType("text/plain") + .noMessageExpiry() + .applyWillPublish() .buildAsync(); - - Log.i(TAG, "createClient: Client created with address: " + addressString); + Log.i(TAG, "createClient: Client created with address: " + host + ":" + port); } - private void createNotification(){ + private void createNotification() { + createNotification(null, null); + } + private void createNotification(MqttClientDisconnectedContext mqttClientDisconnectedContext, + MqttClientConnectedContext mqttClientConnectedContext) { StringBuilder s = new StringBuilder(); String address = spg.getSharedPreference(SPType.MQTT).getString("mqtt_host", "None"); if(address.equals("None")){ @@ -140,6 +228,11 @@ private void createNotification(){ } else { s.append("Host: ").append(address).append("\n"); s.append("State: ").append(client.getState().toString()).append("\n"); + if(mqttClientDisconnectedContext != null){ + if(mqttClientDisconnectedContext.getCause() != null){ + s.append("Cause: ").append(mqttClientDisconnectedContext.getCause().getMessage()).append("\n"); + } + } } builder.setStyle(new NotificationCompat.BigTextStyle() .bigText(s)); @@ -149,9 +242,21 @@ private void createNotification(){ @Override public void onCreate() { super.onCreate(); + Log.d(TAG, "onCreate: Creating MQTTService"); nm = getSystemService(NotificationManager.class); Intent notificationIntent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel( + "OMNT_notification_channel", + "OMNT MQTT Service", + NotificationManager.IMPORTANCE_MAX + ); + nm.createNotificationChannel(channel); + } + setupSharedPreferences(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // create notification builder = new NotificationCompat.Builder(this, "OMNT_notification_channel") @@ -184,19 +289,33 @@ public void publishToTopic(String topic, String message, boolean retain){ .retain(retain) .send(); } - + private boolean isConnected(){ + if(client == null){ + Log.e(TAG, "isConnected: Client is null"); + return false; + } + return client.getState().isConnected(); + } public void disconnectClient(){ - CompletableFuture disconnect = client.disconnect(); - disconnect.whenComplete((aVoid, throwable) -> { - if(throwable != null){ - Log.e(TAG, "disconnectClient: Error disconnecting from MQTT server: " + throwable.getMessage()); - } else { - Log.i(TAG, "disconnectClient: Disconnected from MQTT server"); - } - }); + Log.d(TAG, "disconnectClient: starting to disconnect client...."); + if(isConnected()){ + + CompletableFuture disconnect = client.disconnect(); + disconnect.whenComplete((aVoid, throwable) -> { + if(throwable != null){ + Log.e(TAG, "disconnectClient: Error disconnecting from MQTT server: " + throwable.getMessage()); + } else { + Log.i(TAG, "disconnectClient: Disconnected from MQTT server"); + } + + }); + } + client = null; + nm.cancel(3); } public void connectClient(){ + Log.d(TAG, "connectClient: Connecting to MQTT server..."); CompletableFuture connAck = client.connectWith() .keepAlive(1) @@ -435,16 +554,21 @@ private void subscribeToAllTopics(){ subsribetoTopic(String.format("device/%s/#", deviceName)); } + public void onDestroy(){ + disconnectClient(); + client = null; + Log.d(TAG, "onDestroy: Destroying MQTTService"); + super.onDestroy(); + + } @Override public int onStartCommand(Intent intent, int flags, int startId) { - Log.d(TAG, "onStartCommand: Start MQTT service"); + Log.d(TAG, "onStartCommand: Start MQTTservice"); context = getApplicationContext(); - mqttSP = SharedPreferencesGrouper.getInstance(context).getSharedPreference(SPType.MQTT); deviceName = SharedPreferencesGrouper.getInstance(context).getSharedPreference(SPType.MAIN).getString("device_name", "null").strip(); startForeground(3, builder.build()); - setupSharedPreferences(); createClient(); if(client == null){ Log.e(TAG, "onStartCommand: Client is null"); diff --git a/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/SettingPreferences/MQTTSettingsFragment.java b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/SettingPreferences/MQTTSettingsFragment.java index d9258029..86f468d5 100644 --- a/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/SettingPreferences/MQTTSettingsFragment.java +++ b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/SettingPreferences/MQTTSettingsFragment.java @@ -43,6 +43,7 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, @Null if (key.equals("enable_mqtt")) { boolean logger = sharedPreferences.getBoolean("enable_mqtt", false); Log.d(TAG, "Logger update: " + logger); + _switch.setChecked(logger); } diff --git a/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/SettingPreferences/SettingsFragment.java b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/SettingPreferences/SettingsFragment.java index d3785f8a..0e49edc8 100644 --- a/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/SettingPreferences/SettingsFragment.java +++ b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/SettingPreferences/SettingsFragment.java @@ -12,6 +12,7 @@ import android.os.Build; import android.os.Bundle; import android.telephony.SubscriptionInfo; +import android.util.Log; import android.widget.Toast; import androidx.activity.OnBackPressedCallback; @@ -33,6 +34,8 @@ public class SettingsFragment extends PreferenceFragmentCompat { + private static final String TAG = "SettingsFragment"; + @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { SharedPreferencesGrouper spg = SharedPreferencesGrouper.getInstance(requireContext()); @@ -64,6 +67,15 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { } } + for (String key : pfm.getSharedPreferences().getAll().keySet()) { + Preference pref = pfm.findPreference(key); + if (pref != null) { + pref.setOnPreferenceChangeListener((preference, newValue) -> { + Log.d(TAG, "Preference changed: " + preference.getKey() + " -> " + newValue); + return true; + }); + } + } Preference button = pfm.findPreference("reset_modem"); if (button != null) { if (GlobalVars.getInstance().isCarrier_permissions()) { diff --git a/app/src/main/res/raw/config.json b/app/src/main/res/raw/config.json index f00711d3..a3118585 100644 --- a/app/src/main/res/raw/config.json +++ b/app/src/main/res/raw/config.json @@ -513,7 +513,7 @@ "metadata": { "version": "0.7", "code": 7, - "gitHash": "9d3ed7b" + "gitHash": "52f0533" } } ] \ 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 fb6f3ffc..51d8c75b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -121,7 +121,7 @@ MQTT Client Password MQTT-Broker Address, including Port MQTT-Broker Address - tcp://192.168.213.89:1883 + 192.168.213.89:1883 MQTT Service diff --git a/docs/config.json b/docs/config.json index f00711d3..a3118585 100644 --- a/docs/config.json +++ b/docs/config.json @@ -513,7 +513,7 @@ "metadata": { "version": "0.7", "code": 7, - "gitHash": "9d3ed7b" + "gitHash": "52f0533" } } ] \ No newline at end of file diff --git a/docs/preferences.md b/docs/preferences.md index 0600fe49..2b83a785 100644 --- a/docs/preferences.md +++ b/docs/preferences.md @@ -92,7 +92,7 @@ _Section to set Credentials for MQTT._ | Key | Title | Summary | Default Value | | --- | ----- | ------- | ------------- | -| **mqtt_host** | MQTT-Broker Address | MQTT Broker Address | `tcp://192.168.213.89:1883` | +| **mqtt_host** | MQTT-Broker Address | MQTT Broker Address | `192.168.213.89:1883` | | **mqtt_client_username** | MQTT Client Username | MQTT Username | `USERNAME` | | **mqtt_client_password** | MQTT Client Password | MQTT Client Password. | `PASSWORD` |