package com.nutomic.syncthingandroid.preferences;
import android.content.Context;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.preference.MultiSelectListPreference;
import android.util.AttributeSet;
import android.widget.Toast;
import com.nutomic.syncthingandroid.R;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* MultiSelectListPreference which allows the user to select on which WiFi networks (based on SSID)
* syncing should be allowed.
*
* Setting can be "All networks" (none selected), or selecting individual networks.
*
* Due to restrictions in Android, it is possible/likely, that the list of saved WiFi networks
* cannot be retrieved if the WiFi is turned off. In this case, an explanation is shown.
*
* The preference is stored as Set<String> where an empty set represents
* "all networks allowed".
*
* SSIDs are formatted according to the naming convention of WifiManager, i.e. they have the
* surrounding double-quotes (") for UTF-8 names, or they are hex strings (if not quoted).
*/
public class WifiSsidPreference extends MultiSelectListPreference {
public WifiSsidPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public WifiSsidPreference(Context context) {
super(context);
}
/**
* Show the dialog if WiFi is available and configured networks can be loaded.
* Otherwise will display a Toast requesting to turn on WiFi.
*
* On opening of the dialog, will also remove any SSIDs from the set that have been removed
* by the user in the WiFi manager. This change will be persisted only if the user changes
* any other setting
*/
@Override
protected void showDialog(Bundle state) {
WifiConfiguration[] networks = loadConfiguredNetworksSorted();
if (networks != null) {
Set<String> selected = getSharedPreferences().getStringSet(getKey(), new HashSet<>());
// from JavaDoc: Note that you must not modify the set instance returned by this call.
// therefore required to make a defensive copy of the elements
selected = new HashSet<>(selected);
CharSequence[] all = extractSsid(networks, false);
filterRemovedNetworks(selected, all);
setEntries(extractSsid(networks, true)); // display without surrounding quotes
setEntryValues(all); // the value of the entry is the SSID "as is"
setValues(selected); // the currently selected values (without meanwhile deleted networks)
super.showDialog(state);
} else {
Toast.makeText(getContext(), R.string.sync_only_wifi_ssids_wifi_turn_on_wifi, Toast.LENGTH_LONG).show();
}
}
/**
* Removes any network that is no longer saved on the device. Otherwise it will never be
* removed from the allowed set by MultiSelectListPreference.
*/
private void filterRemovedNetworks(Set<String> selected, CharSequence[] all) {
HashSet<CharSequence> availableNetworks = new HashSet<>(Arrays.asList(all));
selected.retainAll(availableNetworks);
}
/**
* Converts WiFi configuration to it's string representation, using the SSID.
*
* It can also remove surrounding quotes which indicate that the SSID is an UTF-8
* string and not a Hex-String, if the strings are intended to be displayed to the
* user, who will not expect the quotes.
*
* @param configs the objects to convert
* @param stripQuotes if to remove surrounding quotes
* @return the formatted SSID of the wifi configurations
*/
private CharSequence[] extractSsid(WifiConfiguration[] configs, boolean stripQuotes) {
CharSequence[] result = new CharSequence[configs.length];
for (int i = 0; i < configs.length; i++) {
// See #620: there may be null-SSIDs
String ssid = configs[i].SSID != null ? configs[i].SSID : "";
// WiFi SSIDs can either be UTF-8 (encapsulated in '"') or hex-strings
if (stripQuotes)
result[i] = ssid.replaceFirst("^\"", "").replaceFirst("\"$", "");
else
result[i] = ssid;
}
return result;
}
/**
* Load the configured WiFi networks, sort them by SSID.
*
* @return a sorted array of WifiConfiguration, or null, if data cannot be retrieved
*/
private WifiConfiguration[] loadConfiguredNetworksSorted() {
WifiManager wifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
if (wifiManager != null) {
List<WifiConfiguration> configuredNetworks = wifiManager.getConfiguredNetworks();
// if WiFi is turned off, getConfiguredNetworks returns null on many devices
if (configuredNetworks != null) {
WifiConfiguration[] result = configuredNetworks.toArray(new WifiConfiguration[configuredNetworks.size()]);
Arrays.sort(result, (lhs, rhs) -> {
// See #620: There may be null-SSIDs
String l = lhs.SSID != null ? lhs.SSID : "";
String r = rhs.SSID != null ? rhs.SSID : "";
return l.compareToIgnoreCase(r);
});
return result;
}
}
// WiFi is turned off or device doesn't have WiFi
return null;
}
}