99package de .fraunhofer .fokus .OpenMobileNetworkToolkit .MQTT ;
1010
1111import android .app .Notification ;
12+ import android .app .NotificationChannel ;
1213import android .app .NotificationManager ;
1314import android .app .PendingIntent ;
1415import android .app .Service ;
2021import android .os .Handler ;
2122import android .os .IBinder ;
2223import android .util .Log ;
24+ import android .widget .Toast ;
2325
2426import androidx .annotation .Nullable ;
2527import androidx .core .app .NotificationCompat ;
2931import androidx .work .multiprocess .RemoteWorkManager ;
3032
3133
34+ import com .hivemq .client .mqtt .MqttClientState ;
3235import com .hivemq .client .mqtt .datatypes .MqttQos ;
36+ import com .hivemq .client .mqtt .lifecycle .MqttClientConnectedContext ;
37+ import com .hivemq .client .mqtt .lifecycle .MqttClientDisconnectedContext ;
3338import com .hivemq .client .mqtt .mqtt5 .Mqtt5AsyncClient ;
3439import com .hivemq .client .mqtt .mqtt5 .Mqtt5Client ;
3540import com .hivemq .client .mqtt .mqtt5 .message .connect .connack .Mqtt5ConnAck ;
3641import com .hivemq .client .mqtt .mqtt5 .message .publish .Mqtt5PayloadFormatIndicator ;
3742
43+ import org .jetbrains .annotations .NotNull ;
44+
3845import java .net .InetSocketAddress ;
46+ import java .net .URI ;
3947import java .nio .charset .StandardCharsets ;
4048import java .util .ArrayList ;
49+ import java .util .EnumSet ;
4150import java .util .HashMap ;
4251import java .util .UUID ;
4352import java .util .concurrent .CompletableFuture ;
4453import java .util .concurrent .TimeUnit ;
54+ import java .util .function .Consumer ;
4555
4656import de .fraunhofer .fokus .OpenMobileNetworkToolkit .CustomEventListener ;
4757import de .fraunhofer .fokus .OpenMobileNetworkToolkit .MQTT .Handler .Iperf3Handler ;
@@ -77,69 +87,152 @@ private void setupSharedPreferences(){
7787 mqttSP = spg .getSharedPreference (SPType .MQTT );
7888 mqttSP .registerOnSharedPreferenceChangeListener ((sharedPreferences , key ) -> {
7989 if (key == null ) return ;
80- if (key .equals ("mqtt_host" )) {
81- Log .d (TAG , "MQTT Host update: " + sharedPreferences .getString ("mqtt_host" , "" ));
82- client .disconnect ();
83- createClient ();
84- createNotification ();
90+ isEnabled = sharedPreferences .getBoolean ("enable_mqtt" , false );
91+ switch (key ){
92+ case "mqtt_host" :
93+ if (!isEnabled ) return ;
94+ Log .d (TAG , "mqtt_host: " + sharedPreferences .getString ("mqtt_host" , "" ));
95+ disconnectClient ();
96+ createClient ();
97+ createNotification ();
98+ break ;
99+ case "enable_mqtt" :
100+ Log .d (TAG , "enable_mqtt: " + isEnabled );
101+ if (!isEnabled && client != null ){
102+ this .onDestroy ();
103+ }
104+ break ;
85105 }
106+
86107 });
87108 }
88-
89- public void createClient (){
90- String addressString = mqttSP .getString ("mqtt_host" , "localhost:1883" );
91- String host = null ;
92- int port = -1 ;
109+ private boolean isValidUrl (String addressString ) {
93110 try {
94- host = addressString . split ( ":" )[ 0 ] ;
95- port = Integer . parseInt ( addressString . split ( ":" )[ 1 ]) ;
111+ new java . net . URL ( addressString ) ;
112+ return true ;
96113 } catch (Exception e ) {
97- Log .e (TAG , "createClient: Invalid address string: " + addressString );
114+ return false ;
115+ }
116+ }
117+
118+ private boolean isProtocolIpPort (String addressString ) {
119+ // Example: mqtt://192.168.1.1:1883
120+ String regex = "^[\\ d.]+:\\ d+$" ;
121+ return addressString .matches (regex );
122+ }
123+
124+
125+ public String mQTTClientStateToString (MqttClientState state ) {
126+ switch (state ) {
127+ case CONNECTED :
128+ return "Connected" ;
129+ case CONNECTING :
130+ return "Connecting" ;
131+ case DISCONNECTED :
132+ return "Disconnected" ;
133+ case DISCONNECTED_RECONNECT :
134+ return "Disconnected_Reconnecting" ;
135+ case CONNECTING_RECONNECT :
136+ return "Connecting_Reconnecting" ;
137+ default :
138+ return "Unknown" ;
139+ }
140+ }
141+
142+
143+ public void createClient () {
144+ String addressString = mqttSP .getString ("mqtt_host" , "" );
145+ Log .d (TAG , "createClient: creating client..." );
146+ if (addressString .isBlank ()) {
147+ Log .e (TAG , "createClient: MQTT Host is empty" );
148+ spg .getSharedPreference (SPType .MQTT ).edit ().putBoolean ("enable_mqtt" , false ).apply ();
149+ client = null ;
98150 return ;
99151 }
100- if (host == null || port == -1 ){
101- Log .e (TAG , "createClient: Invalid address string: " + addressString );
152+
153+ if (!isValidUrl (addressString ) && !isProtocolIpPort (addressString )) {
154+ Log .e (TAG , "createClient: MQTT Host is not a valid URL or IP:Port" );
155+ Toast .makeText (context , "MQTT Host is not a valid URL or IP:Port" , Toast .LENGTH_SHORT ).show ();
156+ spg .getSharedPreference (SPType .MQTT ).edit ().putBoolean ("enable_mqtt" , false ).apply ();
157+ client = null ;
158+ return ;
159+ }
160+
161+ String host ;
162+ int port ;
163+
164+ try {
165+ if (isProtocolIpPort (addressString )) {
166+ // Case: raw host:port
167+ String [] hostPort = addressString .split (":" );
168+ host = hostPort [0 ];
169+ port = Integer .parseInt (hostPort [1 ]);
170+ } else {
171+ // Case: URL with scheme
172+ URI uri = new URI (addressString );
173+ host = uri .getHost ();
174+ port = uri .getPort () == -1 ? 1883 : uri .getPort (); // default MQTT port
175+ }
176+ } catch (Exception e ) {
177+ Log .e (TAG , "createClient: Invalid MQTT address" , e );
178+ spg .getSharedPreference (SPType .MQTT ).edit ().putBoolean ("enable_mqtt" , false ).apply ();
179+ client = null ;
102180 return ;
103181 }
104- InetSocketAddress address = new InetSocketAddress (host , port );
182+
183+ InetSocketAddress address = InetSocketAddress .createUnresolved (host , port );
184+ if (client != null ){
185+ disconnectClient ();
186+ client = null ;
187+ }
105188 client = Mqtt5Client .builder ()
106189 .identifier (deviceName )
107190 .serverAddress (address )
108191 .automaticReconnect ()
109192 .initialDelay (5 , TimeUnit .SECONDS )
110193 .maxDelay (30 , TimeUnit .SECONDS )
111194 .applyAutomaticReconnect ()
112- .addConnectedListener (context -> {
113- Log .i (TAG , "createClient : Connected to MQTT server" );
114- createNotification ();
195+ .addConnectedListener (ctx -> {
196+ Log .i (TAG , "addConnectedListener : Connected to MQTT server" );
197+ createNotification (null , ctx );
115198 publishToTopic (String .format ("device/%s/status" , deviceName ), "1" , false );
199+ Log .d (TAG , "addConnectedListener: " +mQTTClientStateToString (client .getState ()));
116200 })
117- .addDisconnectedListener (context -> {
118- Log .i (TAG , "createClient: Disconnected from MQTT server" );
119- createNotification ();
201+ .addDisconnectedListener (ctx -> {
202+ Log .i (TAG , "addDisconnectedListener: Disconnected from MQTT server" );
203+ createNotification (ctx , null );
204+
120205 })
121206 .willPublish ()
122- .topic (String .format ("device/%s/status" , deviceName ))
123- .qos (MqttQos .EXACTLY_ONCE )
124- .payload ("0" .getBytes ())
125- .retain (true )
126- .payloadFormatIndicator (Mqtt5PayloadFormatIndicator .UTF_8 )
127- .contentType ("text/plain" )
128- .noMessageExpiry ()
129- .applyWillPublish ()
207+ .topic (String .format ("device/%s/status" , deviceName ))
208+ .qos (MqttQos .EXACTLY_ONCE )
209+ .payload ("0" .getBytes ())
210+ .retain (true )
211+ .payloadFormatIndicator (Mqtt5PayloadFormatIndicator .UTF_8 )
212+ .contentType ("text/plain" )
213+ .noMessageExpiry ()
214+ .applyWillPublish ()
130215 .buildAsync ();
131-
132- Log .i (TAG , "createClient: Client created with address: " + addressString );
216+ Log .i (TAG , "createClient: Client created with address: " + host + ":" + port );
133217 }
134218
135- private void createNotification (){
219+ private void createNotification () {
220+ createNotification (null , null );
221+ }
222+ private void createNotification (MqttClientDisconnectedContext mqttClientDisconnectedContext ,
223+ MqttClientConnectedContext mqttClientConnectedContext ) {
136224 StringBuilder s = new StringBuilder ();
137225 String address = spg .getSharedPreference (SPType .MQTT ).getString ("mqtt_host" , "None" );
138226 if (address .equals ("None" )){
139227 s .append ("MQTT Host: None\n " );
140228 } else {
141229 s .append ("Host: " ).append (address ).append ("\n " );
142230 s .append ("State: " ).append (client .getState ().toString ()).append ("\n " );
231+ if (mqttClientDisconnectedContext != null ){
232+ if (mqttClientDisconnectedContext .getCause () != null ){
233+ s .append ("Cause: " ).append (mqttClientDisconnectedContext .getCause ().getMessage ()).append ("\n " );
234+ }
235+ }
143236 }
144237 builder .setStyle (new NotificationCompat .BigTextStyle ()
145238 .bigText (s ));
@@ -149,9 +242,21 @@ private void createNotification(){
149242 @ Override
150243 public void onCreate () {
151244 super .onCreate ();
245+ Log .d (TAG , "onCreate: Creating MQTTService" );
152246 nm = getSystemService (NotificationManager .class );
153247 Intent notificationIntent = new Intent (this , MainActivity .class );
154248 PendingIntent pendingIntent = PendingIntent .getActivity (this , 0 , notificationIntent , PendingIntent .FLAG_IMMUTABLE );
249+
250+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
251+ NotificationChannel channel = new NotificationChannel (
252+ "OMNT_notification_channel" ,
253+ "OMNT MQTT Service" ,
254+ NotificationManager .IMPORTANCE_MAX
255+ );
256+ nm .createNotificationChannel (channel );
257+ }
258+ setupSharedPreferences ();
259+
155260 if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .S ) {
156261 // create notification
157262 builder = new NotificationCompat .Builder (this , "OMNT_notification_channel" )
@@ -184,19 +289,33 @@ public void publishToTopic(String topic, String message, boolean retain){
184289 .retain (retain )
185290 .send ();
186291 }
187-
292+ private boolean isConnected (){
293+ if (client == null ){
294+ Log .e (TAG , "isConnected: Client is null" );
295+ return false ;
296+ }
297+ return client .getState ().isConnected ();
298+ }
188299 public void disconnectClient (){
189- CompletableFuture <Void > disconnect = client .disconnect ();
190- disconnect .whenComplete ((aVoid , throwable ) -> {
191- if (throwable != null ){
192- Log .e (TAG , "disconnectClient: Error disconnecting from MQTT server: " + throwable .getMessage ());
193- } else {
194- Log .i (TAG , "disconnectClient: Disconnected from MQTT server" );
195- }
196- });
300+ Log .d (TAG , "disconnectClient: starting to disconnect client...." );
301+ if (isConnected ()){
302+
303+ CompletableFuture <Void > disconnect = client .disconnect ();
304+ disconnect .whenComplete ((aVoid , throwable ) -> {
305+ if (throwable != null ){
306+ Log .e (TAG , "disconnectClient: Error disconnecting from MQTT server: " + throwable .getMessage ());
307+ } else {
308+ Log .i (TAG , "disconnectClient: Disconnected from MQTT server" );
309+ }
310+
311+ });
312+ }
313+ client = null ;
314+ nm .cancel (3 );
197315 }
198316
199317 public void connectClient (){
318+ Log .d (TAG , "connectClient: Connecting to MQTT server..." );
200319
201320 CompletableFuture <Mqtt5ConnAck > connAck = client .connectWith ()
202321 .keepAlive (1 )
@@ -435,16 +554,21 @@ private void subscribeToAllTopics(){
435554 subsribetoTopic (String .format ("device/%s/#" , deviceName ));
436555 }
437556
557+ public void onDestroy (){
558+ disconnectClient ();
559+ client = null ;
560+ Log .d (TAG , "onDestroy: Destroying MQTTService" );
561+ super .onDestroy ();
562+
563+ }
438564
439565
440566 @ Override
441567 public int onStartCommand (Intent intent , int flags , int startId ) {
442- Log .d (TAG , "onStartCommand: Start MQTT service " );
568+ Log .d (TAG , "onStartCommand: Start MQTTservice " );
443569 context = getApplicationContext ();
444- mqttSP = SharedPreferencesGrouper .getInstance (context ).getSharedPreference (SPType .MQTT );
445570 deviceName = SharedPreferencesGrouper .getInstance (context ).getSharedPreference (SPType .MAIN ).getString ("device_name" , "null" ).strip ();
446571 startForeground (3 , builder .build ());
447- setupSharedPreferences ();
448572 createClient ();
449573 if (client == null ){
450574 Log .e (TAG , "onStartCommand: Client is null" );
0 commit comments