WIP: Reactivex wifi state listener
(This is clearly not a tiny change, so I've tried to be comprehensive with my description of it. It should realistically not change much other than swap code, and the largest number of added lines of code are explained below (i.e. "Dumb Wrappers" and "Tests"). Please ask if you have any questions.)
Overview
Previous Behaviour
Previously, F-Droid would eagerly try to identify the IP address of the device it was running on when started. This was for the reason that when a swap session was started - it would already know:
- Whether it was connected to a network
- The IP of the device
- The SSID/BSSID of the network it was connected to
And it could use this information to snappily display to the user without them having to wait.
Once the information was figured out, it would store it in static variables in FDroidApp
.
It was smart enough to know that if the Android WifiManager
was unable to proactively tell us the network information, then it may be able to be ascertained by procedurally querying the network interface related classes provided by Android.
Problems
The code to ascertain the network information was a little bit difficult to reason about:
- It could have either been invoked in response to a
WifiManager
broadcast from the Android system, or explicitly by F-Droid - The way in which it decided which mode it was in was not transparent
- There Was a background thread which would usually terminate nicely, but sometimes would continue running (see #522 (closed))
- Code that wanted to be notified of network state changes would need to:
- Firstly, listen for broadcasts from the
WifiStateChangeService
. - Secondly, know to query the
FDroidApp.ipAddressString
static variable and its friends to get the relevant info.
Solution
By and large, this entire change is about making the process of figuring out the network state easier to reason about and more functional. The end result is a reduction in the amount of state in F-Droid, and also code that is testable (with tests!).
The WifiStateChangeService
has been rewritten into three classes:
-
WifiState
is the class you can ask for information about the network state -
WifiNetworkState
is a dumb class that contains information about the current network state. It is what is sent to code which askedWifiState
for the current network setup. -
WifiStateCalculator
figures out what the currentWifiNetworkState
is. It first tries to do this by querying the AndroidWifiManager
if it tells us it isWIFI_STATE_CONNECTED
. If that is not successful, then it will defer to procedurally querying the network interface API, as was done before. This is the class that is covered by the test suite.
There is no potential for endless loops in the new code, which should prevent #522 (closed)
The static FDroidApp.*
variables about the network state are now completely removed.
Code accessing the network state no longer needs to understand the relationship between this global state (and how it does or doesn't get set).
When code requires access to the network state, it has two options:
- If it requires the network state only once, then it can call
WifiState.getState(...)
- If it wants to be notified of network state changes, then it can call
WifiState.subscribe(...)
Note that getting the state via WifiState.getState(...)
is a little bit more verbose than the previous FDroidApp.ipAddress
+ FDroidApp.ssid
approach.
However, it should be more clear that you will always get a sensible response from it, even if F-Droid is starting and has not yet obtained its network state.
WifiState.getState(new Action1<WifiNetworkState>() {
@Override
public void call(WifiNetworkState state) {
doStuffWithState(state);
}
});
The verbosity is in part due to the lack of support for lambda expressions in the current version of Java supported by Android, or else this would be rewritten as:
WifiState.getState((state) -> doStuffWithState(state))
Dumb wrapper classes
A handful of classes have been added to the ord.fdroid.fdroid.net.wifi.wrappers
package.
These are dumb wrappers around existing functionality in the Android API.
The only reason they exist, is because it was difficult to mock them in order to write unit tests due to:
- Package-private constructors
-
final
classes - Residing in a
java.*
package (this was new to me, but you can't put classes, even test classes, in this package)
You will note that each interface in the org.fdroid.fdroid.net.wifi.wrappers
package has a corresponding *Impl
in both the F-Droid source code, and a mock implementation for the tests.
git diff --stat master -- F-Droid/src/org/fdroid/fdroid/net/wifi/wrappers/
F-Droid/src/org/fdroid/fdroid/net/wifi/wrappers/InterfaceAddressWrapper.java | 12 ++++++++++++
F-Droid/src/org/fdroid/fdroid/net/wifi/wrappers/InterfaceAddressWrapperImpl.java | 22 ++++++++++++++++++++++
F-Droid/src/org/fdroid/fdroid/net/wifi/wrappers/NetworkInterfaceManager.java | 8 ++++++++
F-Droid/src/org/fdroid/fdroid/net/wifi/wrappers/NetworkInterfaceManagerImpl.java | 20 ++++++++++++++++++++
F-Droid/src/org/fdroid/fdroid/net/wifi/wrappers/NetworkInterfaceWrapper.java | 23 +++++++++++++++++++++++
F-Droid/src/org/fdroid/fdroid/net/wifi/wrappers/NetworkInterfaceWrapperImpl.java | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
F-Droid/src/org/fdroid/fdroid/net/wifi/wrappers/WifiInfoWrapper.java | 18 ++++++++++++++++++
F-Droid/src/org/fdroid/fdroid/net/wifi/wrappers/WifiInfoWrapperImpl.java | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
F-Droid/src/org/fdroid/fdroid/net/wifi/wrappers/WifiManagerWrapper.java | 17 +++++++++++++++++
F-Droid/src/org/fdroid/fdroid/net/wifi/wrappers/WifiManagerWrapperImpl.java | 35 +++++++++++++++++++++++++++++++++++
10 files changed, 264 insertions(+)
WifiStateCalculator
class
Tests for new These are by no means comprehensive, but they should test the following features of the WifiStateCalculator
class:
-
WIFI_STATE_ENABLED
should return relevant network info. -
WIFI_STATE_ENABLING
should return not connected because we expect aWIFI_STATE_ENABLED
in the near future. - Any other of the
WIFI_STATE_*
states fromWifiManager
should defer to the network interface devices:
- Any NIC other than 'eth0', 'ap0', or 'wlan0' should be not connected
- 'eth0', 'ap0', or 'wlan0' are tested both with valid IP + SSID info, and also without.
git diff --stat master -- F-Droid/test
F-Droid/test/src/android/net/MockDhcpInfo.java | 4 ++
F-Droid/test/src/android/net/wifi/MockWifiInfo.java | 36 ++++++++++++++++++
F-Droid/test/src/org/fdroid/fdroid/mock/MockInterfaceAddress.java | 24 ++++++++++++
F-Droid/test/src/org/fdroid/fdroid/net/wifi/MockInterfaceAddressWrapper.java | 49 +++++++++++++++++++++++++
F-Droid/test/src/org/fdroid/fdroid/net/wifi/MockNetworkInterfaceManager.java | 22 +++++++++++
F-Droid/test/src/org/fdroid/fdroid/net/wifi/MockNetworkInterfaceWrapper.java | 60 ++++++++++++++++++++++++++++++
F-Droid/test/src/org/fdroid/fdroid/net/wifi/MockNetworkUtils.java | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
F-Droid/test/src/org/fdroid/fdroid/net/wifi/MockWifiManagerWrapper.java | 34 +++++++++++++++++
F-Droid/test/src/org/fdroid/fdroid/net/wifi/TestWifiState.java | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 517 insertions(+)
Other Changes
While removing the dependency on FDroidApp.ssid
+ FDroidApp.bssid
global state, I noticed that the NFC sharing of repo URLs via RepoDetailsActivity
was busted. It was sharing all repos (not just swap repos) with the following format:
fdroidrepo://server/path/to/fdroid/repo?fingerprint=...&isSwap=1&bssid=...&ssid=...
The new code now shares with the following format:
fdroidrepo://server/path/to/fdroid/repo?fingerprint=...
And in the process, I also renamed Utils.getSharingUri(...)
to Utils.getSwapUri(...)
.