diff --git a/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/MessageContext.java b/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/MessageContext.java index c7a771d..21fc45e 100644 --- a/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/MessageContext.java +++ b/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/MessageContext.java @@ -17,6 +17,7 @@ public class MessageContext { private final long estimatedNextTimestamp; private final String deviceName; @Nullable private final String currentSsid; + @Nullable private final String currentBssid; MessageContext( long nextAlarmclockTimestamp, @@ -26,7 +27,8 @@ public class MessageContext { long currentTimestamp, long estimatedNextTimestamp, String deviceName, - @Nullable String currentSsid) { + @Nullable String currentSsid, + @Nullable String currentBssid) { this.nextAlarmclockTimestamp = nextAlarmclockTimestamp; this.batteryStatus = batteryStatus; this.conditionContents = conditionContents; @@ -35,6 +37,7 @@ public class MessageContext { this.estimatedNextTimestamp = estimatedNextTimestamp; this.deviceName = deviceName; this.currentSsid = currentSsid; + this.currentBssid = currentBssid; } public long getNextAlarmclockTimestamp() { @@ -69,4 +72,9 @@ public String getDeviceName() { public String getCurrentSsid() { return currentSsid; } + + @Nullable + public String getCurrentBssid() { + return currentBssid; + } } diff --git a/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/MessageContextProvider.java b/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/MessageContextProvider.java index 855cc56..dec49fa 100644 --- a/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/MessageContextProvider.java +++ b/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/MessageContextProvider.java @@ -36,6 +36,7 @@ public MessageContextProvider(Context applicationContext) { public MessageContext getContext() { long currentTimestamp = System.currentTimeMillis(); String currentSsid = networkService.getCurrentSsid(); + String currentBssid = networkService.getCurrentBssid(); return new MessageContext( alarmclockTimestampProvider.getNextAlarmclockTimestamp(), batteryStatusProvider.getCurrentBatteryStatus(), @@ -44,6 +45,7 @@ public MessageContext getContext() { currentTimestamp, preferences.getLong(NEXT_SCHEDULE, 0L), deviceNameProvider.getDeviceName(), - currentSsid); + currentSsid, + currentBssid); } } diff --git a/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/NetworkService.java b/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/NetworkService.java index fd1a87d..5e6eefe 100644 --- a/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/NetworkService.java +++ b/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/NetworkService.java @@ -61,10 +61,25 @@ public String getCurrentSsid() { return wifiEventConsumer.getCurrentSsid(); } + @Nullable + public String getCurrentBssid() { + // since the detection of wifi networks in Android < 12 with a network callback is flaky + // due to the SSID not being present in the event data, we re-check the current wifi + // whenever we need it + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && wifiCallbackApi23 != null) { + wifiCallbackApi23.checkWifiConnection(); + } + return wifiEventConsumer.getCurrentBssid(); + } + public static Optional getSsid(Optional wifiInfo) { return wifiInfo.map(WifiInfo::getSSID).map(NetworkService::normalizeSsid); } + public static Optional getBssid(Optional wifiInfo) { + return wifiInfo.map(WifiInfo::getBSSID); + } + private static String normalizeSsid(String ssid) { if (ssid == null) { return null; diff --git a/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/WifiCallback.java b/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/WifiCallback.java index e9e3b97..b138c5e 100644 --- a/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/WifiCallback.java +++ b/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/WifiCallback.java @@ -20,6 +20,7 @@ public class WifiCallback extends ConnectivityManager.NetworkCallback { private static final String TAG = "WifiCallback"; private final Map> ssidByNetworkHandle = new ConcurrentHashMap<>(); + private final Map> bssidByNetworkHandle = new ConcurrentHashMap<>(); private final WifiEventConsumer consumer; public WifiCallback(WifiEventConsumer consumer) { @@ -32,13 +33,15 @@ public void onCapabilitiesChanged( @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) { long networkHandle = network.getNetworkHandle(); Optional current = NetworkService.getSsid(getWifiInfo(networkCapabilities)); + Optional currentB = NetworkService.getBssid(getWifiInfo(networkCapabilities)); Optional previous = ssidByNetworkHandle.putIfAbsent(networkHandle, current); + Optional previousB = bssidByNetworkHandle.putIfAbsent(networkHandle, currentB); // absent means not a Wi-Fi network, null means (previously) unknown //noinspection OptionalAssignedToNull - if (previous == null) { - if (current.isPresent()) { + if (previous == null || previousB == null) { + if (current.isPresent() && currentB.isPresent()) { DatabaseLogger.d(TAG, "Connected to Wifi network " + networkHandle); - consumer.wifiConnected(current.get()); + consumer.wifiConnected(current.get(), currentB.get()); } else { DatabaseLogger.d(TAG, "Connected to non-Wifi network " + networkHandle); } @@ -49,10 +52,11 @@ public void onCapabilitiesChanged( public void onLost(@NonNull Network network) { long networkHandle = network.getNetworkHandle(); Optional removed = ssidByNetworkHandle.remove(networkHandle); - if (removed != null) { - if (removed.isPresent()) { + Optional removedB = bssidByNetworkHandle.remove(networkHandle); + if (removed != null && removedB != null) { + if (removed.isPresent() && removedB.isPresent()) { DatabaseLogger.d(TAG, "Lost Wifi network " + networkHandle); - consumer.wifiDisconnected(removed.get()); + consumer.wifiDisconnected(removed.get(), removedB.get()); } else { DatabaseLogger.d(TAG, "Lost non-Wifi network " + networkHandle); } diff --git a/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/WifiCallbackApi23.java b/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/WifiCallbackApi23.java index 782fd10..c808f90 100644 --- a/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/WifiCallbackApi23.java +++ b/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/WifiCallbackApi23.java @@ -40,10 +40,11 @@ public void onLost(@NonNull Network network) { // on every publishing schedule void checkWifiConnection() { Optional ssid = getConnectedSsid(); - if (ssid.isPresent()) { - consumer.wifiConnected(ssid.get()); + Optional bssid = getConnectedBssid(); + if (ssid.isPresent() && bssid.isPresent()) { + consumer.wifiConnected(ssid.get(), bssid.get()); } else { - consumer.wifiDisconnected(null); + consumer.wifiDisconnected(null, null); } } @@ -52,4 +53,10 @@ private Optional getConnectedSsid() { Optional.ofNullable(wifiManager.getConnectionInfo()) .filter(w -> w.getSupplicantState() == SupplicantState.COMPLETED)); } + + private Optional getConnectedBssid() { + return NetworkService.getBssid( + Optional.ofNullable(wifiManager.getConnectionInfo()) + .filter(w -> w.getSupplicantState() == SupplicantState.COMPLETED)); + } } diff --git a/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/WifiEventConsumer.java b/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/WifiEventConsumer.java index 18de8b7..34671c4 100644 --- a/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/WifiEventConsumer.java +++ b/app/src/main/java/org/ostrya/presencepublisher/mqtt/context/condition/network/WifiEventConsumer.java @@ -23,6 +23,7 @@ public class WifiEventConsumer { // will be reset so that we do trigger an event after restart even if the current network is the // same as before the app restart private static final AtomicReference CURRENT_SSID = new AtomicReference<>(); + private static final AtomicReference CURRENT_BSSID = new AtomicReference<>(); private final SharedPreferences preference; private final Scheduler scheduler; @@ -31,14 +32,14 @@ public WifiEventConsumer(Context applicationContext) { scheduler = new Scheduler(applicationContext); } - public void wifiDisconnected(@Nullable String ssid) { - if (CURRENT_SSID.getAndSet(null) != null) { + public void wifiDisconnected(@Nullable String ssid, @Nullable String bssid) { + if (CURRENT_SSID.getAndSet(null) != null || CURRENT_BSSID.getAndSet(null) != null) { if (ssid != null) { - DatabaseLogger.logDetection("Wi-Fi disconnected: " + ssid); + DatabaseLogger.logDetection("Wi-Fi disconnected: " + ssid + " " + bssid); } else { DatabaseLogger.logDetection("Wi-Fi disconnected"); } - DatabaseLogger.i(TAG, "Wi-Fi disconnected: " + ssid); + DatabaseLogger.i(TAG, "Wi-Fi disconnected: " + ssid + " " + bssid); if (preference.getBoolean(SEND_VIA_MOBILE_NETWORK, false)) { DatabaseLogger.i(TAG, "Triggering scheduler after disconnect"); // since we don't want to continuously send "offline" / "online" ping-pong, let's @@ -48,17 +49,17 @@ public void wifiDisconnected(@Nullable String ssid) { scheduler.runIn(15, TimeUnit.SECONDS); } } else { - DatabaseLogger.d(TAG, "Ignoring disconnect for already disconnected network " + ssid); + DatabaseLogger.d(TAG, "Ignoring disconnect for already disconnected network " + ssid + " " + bssid); } } - public void wifiConnected(@NonNull String ssid) { - if (!ssid.equals(CURRENT_SSID.getAndSet(ssid))) { - DatabaseLogger.logDetection("Wi-Fi connected: " + ssid); - DatabaseLogger.i(TAG, "Triggering scheduler after connecting to Wi-Fi " + ssid); + public void wifiConnected(@NonNull String ssid, @NonNull String bssid) { + if (!ssid.equals(CURRENT_SSID.getAndSet(ssid)) && !bssid.equals(CURRENT_BSSID.getAndSet(bssid))) { + DatabaseLogger.logDetection("Wi-Fi connected/changed: " + ssid + " " + bssid); + DatabaseLogger.i(TAG, "Triggering scheduler after connecting to Wi-Fi " + ssid + " " + bssid); scheduler.runNow(); } else { - DatabaseLogger.d(TAG, "Ignoring connect for already connected network " + ssid); + DatabaseLogger.d(TAG, "Ignoring connect for already connected network " + ssid + " " + bssid); } } @@ -66,4 +67,9 @@ public void wifiConnected(@NonNull String ssid) { public String getCurrentSsid() { return CURRENT_SSID.get(); } + + @Nullable + public String getCurrentBssid() { + return CURRENT_BSSID.get(); + } } diff --git a/app/src/main/java/org/ostrya/presencepublisher/mqtt/message/MessageItem.java b/app/src/main/java/org/ostrya/presencepublisher/mqtt/message/MessageItem.java index 2154943..34b738f 100644 --- a/app/src/main/java/org/ostrya/presencepublisher/mqtt/message/MessageItem.java +++ b/app/src/main/java/org/ostrya/presencepublisher/mqtt/message/MessageItem.java @@ -53,6 +53,17 @@ public boolean apply(MessageContext messageContext, Message.MessageBuilder build return true; } }, + CONNECTED_WIFI_BSSID { + @Override + public boolean apply(MessageContext messageContext, Message.MessageBuilder builder) { + builder.withEntry( + this, + messageContext.getCurrentBssid() == null + ? UNKNOWN + : messageContext.getCurrentBssid()); + return true; + } + }, GEO_LOCATION { @Override public boolean apply(MessageContext messageContext, Message.MessageBuilder builder) { @@ -133,6 +144,7 @@ public static MessageItem[] settingValues() { CHARGING_STATE, PLUG_STATE, CONNECTED_WIFI, + CONNECTED_WIFI_BSSID, GEO_LOCATION, CURRENT_TIMESTAMP, NEXT_SCHEDULED_TIMESTAMP, diff --git a/app/src/main/java/org/ostrya/presencepublisher/receiver/NetworkReceiver.java b/app/src/main/java/org/ostrya/presencepublisher/receiver/NetworkReceiver.java index ec69376..a53a91b 100644 --- a/app/src/main/java/org/ostrya/presencepublisher/receiver/NetworkReceiver.java +++ b/app/src/main/java/org/ostrya/presencepublisher/receiver/NetworkReceiver.java @@ -39,11 +39,18 @@ public void onReceive(Context context, Intent intent) { w -> w.getSupplicantState() == SupplicantState.COMPLETED)); + Optional bssid = + NetworkService.getBssid( + Optional.ofNullable(wifiManager.getConnectionInfo()) + .filter( + w -> + w.getSupplicantState() + == SupplicantState.COMPLETED)); WifiEventConsumer consumer = new WifiEventConsumer(applicationContext); if (ssid.isPresent()) { - consumer.wifiConnected(ssid.get()); + consumer.wifiConnected(ssid.get(), bssid.get()); } else { - consumer.wifiDisconnected(null); + consumer.wifiDisconnected(null,null); } } } diff --git a/app/src/main/res/values-de/arrays.xml b/app/src/main/res/values-de/arrays.xml index 3b994ae..d09b6c9 100644 --- a/app/src/main/res/values-de/arrays.xml +++ b/app/src/main/res/values-de/arrays.xml @@ -17,6 +17,7 @@ chargingState: Ladezustand, mögliche Werte: CHARGING, DISCHARGING, FULL, NOT_CHARGING, N/A plugState: Ladeanschluss, mögliche Werte: AC, USB, WIRELESS, N/A connectedWifi: Die SSID des aktuell verbundenen WLANs. Kann \"N/A\" sein, wenn das Gerät nicht verbunden ist oder die SSID nicht bestimmt werden kann. + connectedWifiBssid: Die BSSID des aktuell verbundenen WLANs. Kann \"N/A\" sein, wenn das Gerät nicht verbunden ist oder die BSSID nicht bestimmt werden kann. geoLocation: Der letzte bekannte Standort deines Gerätes, nach RFC 5870 encodiert. Er enthält einen zusätzlichen Parameter \"timestamp\", welcher das Alter des Standorts seit dem 1. 1. 1970 angibt. currentTimestamp: Der Zeitstempel in Sekunden seit dem 1. 1. 1970, zu dem diese Nachricht generiert worden ist. nextScheduledTimestamp: Der Zeitstempel in Sekunden seit dem 1. 1. 1970, für den die nächste Nachricht geplant ist. Oder 0, wenn nicht geplant (z.B. wenn keine Bedingung erfüllt ist) diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 7215f83..58e4b11 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -22,6 +22,7 @@ chargingState: Possible values: CHARGING, DISCHARGING, FULL, NOT_CHARGING, N/A plugState: Possible values: AC, USB, WIRELESS, N/A connectedWifi: The SSID of the network you are currently connected to. May be \"N/A\" if your device is not connected or the app is unable to determine the SSID. + connectedWifiBssid: The BSSID of the network you are currently connected to. May be \"N/A\" if your device is not connected or the app is unable to determine the BSSID. geoLocation: The last known geo location of your device. It is encoded according to RFC 5870 with custom parameter \"timestamp\" which contains the age of the location in seconds since 1970–01–01. currentTimestamp: The timestamp when this message was generated in seconds since 1970–01–01. nextScheduledTimestamp: The timestamp when the next message is scheduled in seconds since 1970–01–01, or 0 if not scheduled (e.g. if currently no condition matches).