From e22e9dcadef693f9a08fac3c4e8ce47284e941d0 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 19 May 2021 11:33:50 -0300 Subject: [PATCH] Make hotspot SSID and passphrase persistent --- .../bramble/api/settings/SettingsManager.java | 7 ++ .../bramble/settings/SettingsManagerImpl.java | 6 ++ .../briar/android/hotspot/HotspotManager.java | 78 +++++++++---------- .../android/hotspot/HotspotViewModel.java | 47 ++++++++++- 4 files changed, 97 insertions(+), 41 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/settings/SettingsManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/settings/SettingsManager.java index 56ec0c3f0..731664018 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/settings/SettingsManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/settings/SettingsManager.java @@ -22,4 +22,11 @@ public interface SettingsManager { * namespace. */ void mergeSettings(Settings s, String namespace) throws DbException; + + /** + * Merges the given settings with any existing settings in the given + * namespace. + */ + void mergeSettings(Transaction txn, Settings s, String namespace) + throws DbException; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/settings/SettingsManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/settings/SettingsManagerImpl.java index fa049a070..e4e03989c 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/settings/SettingsManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/settings/SettingsManagerImpl.java @@ -37,4 +37,10 @@ class SettingsManagerImpl implements SettingsManager { public void mergeSettings(Settings s, String namespace) throws DbException { db.transaction(false, txn -> db.mergeSettings(txn, s, namespace)); } + + @Override + public void mergeSettings(Transaction txn, Settings s, String namespace) + throws DbException { + db.mergeSettings(txn, s, namespace); + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotManager.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotManager.java index 6c5977280..caa84140a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotManager.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotManager.java @@ -21,8 +21,8 @@ import java.util.concurrent.Executor; import java.util.logging.Logger; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.annotation.UiThread; +import androidx.lifecycle.LiveData; import static android.content.Context.WIFI_P2P_SERVICE; import static android.content.Context.WIFI_SERVICE; @@ -34,8 +34,10 @@ import static android.net.wifi.p2p.WifiP2pManager.ERROR; import static android.net.wifi.p2p.WifiP2pManager.NO_SERVICE_REQUESTS; import static android.net.wifi.p2p.WifiP2pManager.P2P_UNSUPPORTED; import static android.os.Build.VERSION.SDK_INT; +import static java.util.Objects.requireNonNull; import static java.util.logging.Level.INFO; import static java.util.logging.Logger.getLogger; +import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce; class HotspotManager implements ActionListener { @@ -61,25 +63,22 @@ class HotspotManager implements ActionListener { private final Context ctx; @IoExecutor private final Executor ioExecutor; - private final SecureRandom random; + private final LiveData savedNetworkConfig; private final HotspotListener listener; private final WifiManager wifiManager; private final WifiP2pManager wifiP2pManager; private final Handler handler; private final String lockTag; - @Nullable - // on API < 29 this is null because we cannot request a custom network name - private String networkName = null; - private WifiManager.WifiLock wifiLock; private WifiP2pManager.Channel channel; HotspotManager(Context ctx, @IoExecutor Executor ioExecutor, - SecureRandom random, HotspotListener listener) { + LiveData savedNetworkConfig, + HotspotListener listener) { this.ctx = ctx; this.ioExecutor = ioExecutor; - this.random = random; + this.savedNetworkConfig = savedNetworkConfig; this.listener = listener; wifiManager = (WifiManager) ctx.getApplicationContext() .getSystemService(WIFI_SERVICE); @@ -106,14 +105,14 @@ class HotspotManager implements ActionListener { acquireLock(); try { if (SDK_INT >= 29) { - networkName = getNetworkName(); - String passphrase = getPassphrase(); - WifiP2pConfig config = new WifiP2pConfig.Builder() - .setGroupOperatingBand(GROUP_OWNER_BAND_2GHZ) - .setNetworkName(networkName) - .setPassphrase(passphrase) - .build(); - wifiP2pManager.createGroup(channel, config, this); + observeForeverOnce(savedNetworkConfig, c -> { + WifiP2pConfig config = new WifiP2pConfig.Builder() + .setGroupOperatingBand(GROUP_OWNER_BAND_2GHZ) + .setNetworkName(c.ssid) + .setPassphrase(c.password) + .build(); + wifiP2pManager.createGroup(channel, config, this); + }); } else { wifiP2pManager.createGroup(channel, this); } @@ -154,16 +153,6 @@ class HotspotManager implements ActionListener { } } - @RequiresApi(29) - private String getNetworkName() { - return "DIRECT-" + getRandomString(2) + "-" + - getRandomString(10); - } - - private String getPassphrase() { - return getRandomString(8); - } - void stopWifiP2pHotspot() { if (channel == null) return; wifiP2pManager.removeGroup(channel, new ActionListener() { @@ -251,13 +240,17 @@ class HotspotManager implements ActionListener { group.getNetworkName()); } return false; - } else if (networkName != null && - !networkName.equals(group.getNetworkName())) { - if (LOG.isLoggable(INFO)) { - LOG.info("expected networkName: " + networkName); - LOG.info("received networkName: " + group.getNetworkName()); + } else if (SDK_INT >= 29) { + // if we get here, the savedNetworkConfig must have a value + String networkName = + requireNonNull(savedNetworkConfig.getValue()).ssid; + if (!networkName.equals(group.getNetworkName())) { + if (LOG.isLoggable(INFO)) { + LOG.info("expected networkName: " + networkName); + LOG.info("received networkName: " + group.getNetworkName()); + } + return false; } - return false; } return true; } @@ -304,26 +297,31 @@ class HotspotManager implements ActionListener { return "WIFI:S:" + ssid + ";T:WPA;P:" + password + ";;"; } + static String getSsid(SecureRandom random) { + return "DIRECT-" + getRandomString(random, 2) + "-" + + getRandomString(random, 10); + } + + static String getPassword(SecureRandom random) { + return getRandomString(random, 8); + } + private static final String digits = "123456789"; // avoid 0 private static final String letters = "abcdefghijkmnopqrstuvwxyz"; // no l private static final String LETTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ"; // no I, O - private String getRandomString(int length) { + private static String getRandomString(SecureRandom random, int length) { char[] c = new char[length]; for (int i = 0; i < length; i++) { if (random.nextBoolean()) { - c[i] = random(digits); + c[i] = digits.charAt(random.nextInt(digits.length())); } else if (random.nextBoolean()) { - c[i] = random(letters); + c[i] = letters.charAt(random.nextInt(letters.length())); } else { - c[i] = random(LETTERS); + c[i] = LETTERS.charAt(random.nextInt(LETTERS.length())); } } return new String(c); } - private char random(String universe) { - return universe.charAt(random.nextInt(universe.length())); - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotViewModel.java index e27f73ebc..85a90915b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotViewModel.java @@ -7,6 +7,8 @@ import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.settings.Settings; +import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.briar.R; import org.briarproject.briar.android.hotspot.HotspotManager.HotspotListener; @@ -28,10 +30,12 @@ import java.util.logging.Logger; import javax.inject.Inject; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.annotation.UiThread; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import static android.os.Build.VERSION.SDK_INT; import static java.util.logging.Logger.getLogger; @NotNullByDefault @@ -40,9 +44,14 @@ class HotspotViewModel extends DbViewModel private static final Logger LOG = getLogger(HotspotViewModel.class.getName()); + private static final String HOTSPOT_NAMESPACE = "hotspot"; + private static final String HOTSPOT_KEY_SSID = "ssid"; + private static final String HOTSPOT_KEY_PASS = "pass"; @IoExecutor private final Executor ioExecutor; + private final SettingsManager settingsManager; + private final SecureRandom random; private final AndroidNotificationManager notificationManager; private final HotspotManager hotspotManager; private final WebServerManager webServerManager; @@ -51,6 +60,8 @@ class HotspotViewModel extends DbViewModel new MutableLiveData<>(); private final MutableLiveEvent peerConnected = new MutableLiveEvent<>(); + private final MutableLiveData savedNetworkConfig = + new MutableLiveData<>(); @Nullable // Field to temporarily store the network config received via onHotspotStarted() @@ -64,14 +75,48 @@ class HotspotViewModel extends DbViewModel TransactionManager db, AndroidExecutor androidExecutor, @IoExecutor Executor ioExecutor, + SettingsManager settingsManager, SecureRandom secureRandom, AndroidNotificationManager notificationManager) { super(app, dbExecutor, lifecycleManager, db, androidExecutor); this.ioExecutor = ioExecutor; + this.settingsManager = settingsManager; + this.random = secureRandom; this.notificationManager = notificationManager; hotspotManager = - new HotspotManager(app, ioExecutor, secureRandom, this); + new HotspotManager(app, ioExecutor, savedNetworkConfig, this); webServerManager = new WebServerManager(app, this); + // get or set persistent SSID and password + if (SDK_INT >= 29) getOrSetNetworkConfig(); + } + + /** + * Store persistent Wi-Fi SSID and passphrase in Settings to improve UX + * so that users don't have to change them when attempting to connect. + * Works only on API 29 and above. + */ + @RequiresApi(29) + private void getOrSetNetworkConfig() { + runOnDbThread(false, txn -> { + Settings settings = + settingsManager.getSettings(txn, HOTSPOT_NAMESPACE); + String ssid = settings.get(HOTSPOT_KEY_SSID); + String pass = settings.get(HOTSPOT_KEY_PASS); + if (ssid == null || pass == null) { + ssid = HotspotManager.getSsid(random); + pass = HotspotManager.getPassword(random); + settings.put(HOTSPOT_KEY_SSID, ssid); + settings.put(HOTSPOT_KEY_PASS, pass); + settingsManager.mergeSettings(txn, settings, HOTSPOT_NAMESPACE); + } + savedNetworkConfig.postValue(new NetworkConfig(ssid, pass, null)); + }, error -> { + handleException(error); + // probably never happens, but if lets use non-persistent data + String ssid = HotspotManager.getSsid(random); + String pass = HotspotManager.getPassword(random); + savedNetworkConfig.postValue(new NetworkConfig(ssid, pass, null)); + }); } @UiThread