mirror of
https://github.com/syncthing/syncthing-android.git
synced 2024-11-26 22:31:16 +00:00
Merge pull request #501 from capi/whitelisted-wifi-only
Add restriction for allowed WiFi SSIDs when WiFi-only sync
This commit is contained in:
commit
0582c67008
7 changed files with 231 additions and 3 deletions
|
@ -8,6 +8,7 @@
|
|||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<application
|
||||
|
|
|
@ -15,16 +15,20 @@ import android.preference.PreferenceManager;
|
|||
import android.preference.PreferenceScreen;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.nutomic.syncthingandroid.R;
|
||||
import com.nutomic.syncthingandroid.activities.SyncthingActivity;
|
||||
import com.nutomic.syncthingandroid.preferences.WifiSsidPreference;
|
||||
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
||||
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import eu.chainfire.libsuperuser.Shell;
|
||||
|
||||
|
@ -52,6 +56,7 @@ public class SettingsFragment extends PreferenceFragment
|
|||
private CheckBoxPreference mAlwaysRunInBackground;
|
||||
private CheckBoxPreference mSyncOnlyCharging;
|
||||
private CheckBoxPreference mSyncOnlyWifi;
|
||||
private WifiSsidPreference mSyncOnlyOnSSIDs;
|
||||
private CheckBoxPreference mUseRoot;
|
||||
private PreferenceScreen mOptionsScreen;
|
||||
private PreferenceScreen mGuiScreen;
|
||||
|
@ -126,6 +131,8 @@ public class SettingsFragment extends PreferenceFragment
|
|||
mSyncOnlyCharging = (CheckBoxPreference)
|
||||
findPreference(SyncthingService.PREF_SYNC_ONLY_CHARGING);
|
||||
mSyncOnlyWifi = (CheckBoxPreference) findPreference(SyncthingService.PREF_SYNC_ONLY_WIFI);
|
||||
mSyncOnlyOnSSIDs = (WifiSsidPreference) findPreference(SyncthingService.PREF_SYNC_ONLY_WIFI_SSIDS);
|
||||
mSyncOnlyOnSSIDs.setDefaultValue(new TreeSet<String>()); // default to empty list
|
||||
mUseRoot = (CheckBoxPreference) findPreference(SyncthingService.PREF_USE_ROOT);
|
||||
Preference appVersion = screen.findPreference(APP_VERSION_KEY);
|
||||
mOptionsScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_OPTIONS_KEY);
|
||||
|
@ -144,6 +151,7 @@ public class SettingsFragment extends PreferenceFragment
|
|||
mAlwaysRunInBackground.setOnPreferenceChangeListener(this);
|
||||
mSyncOnlyCharging.setOnPreferenceChangeListener(this);
|
||||
mSyncOnlyWifi.setOnPreferenceChangeListener(this);
|
||||
mSyncOnlyOnSSIDs.setOnPreferenceChangeListener(this);
|
||||
mUseRoot.setOnPreferenceClickListener(this);
|
||||
screen.findPreference(EXPORT_CONFIG).setOnPreferenceClickListener(this);
|
||||
screen.findPreference(IMPORT_CONFIG).setOnPreferenceClickListener(this);
|
||||
|
@ -153,8 +161,9 @@ public class SettingsFragment extends PreferenceFragment
|
|||
sttrace.setOnPreferenceChangeListener(this);
|
||||
|
||||
// Force summary update and wifi/charging preferences enable/disable.
|
||||
onPreferenceChange(mAlwaysRunInBackground, mAlwaysRunInBackground.isChecked());
|
||||
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
onPreferenceChange(mAlwaysRunInBackground, mAlwaysRunInBackground.isChecked());
|
||||
onPreferenceChange(mSyncOnlyOnSSIDs, sp.getStringSet("sync_only_wifi_ssids_set", new TreeSet<String>()));
|
||||
user.setSummary(sp.getString("gui_user", ""));
|
||||
sttrace.setSummary(sp.getString("sttrace", ""));
|
||||
}
|
||||
|
@ -236,11 +245,19 @@ public class SettingsFragment extends PreferenceFragment
|
|||
: R.string.always_run_in_background_disabled);
|
||||
mSyncOnlyCharging.setEnabled(value);
|
||||
mSyncOnlyWifi.setEnabled(value);
|
||||
mSyncOnlyOnSSIDs.setEnabled(mSyncOnlyWifi.isChecked());
|
||||
// Uncheck items when disabled, so it is clear they have no effect.
|
||||
if (!value) {
|
||||
mSyncOnlyCharging.setChecked(false);
|
||||
mSyncOnlyWifi.setChecked(false);
|
||||
}
|
||||
} else if (preference.equals(mSyncOnlyWifi)) {
|
||||
mSyncOnlyOnSSIDs.setEnabled((Boolean) o);
|
||||
} else if (preference.equals(mSyncOnlyOnSSIDs)) {
|
||||
String ssids = formatWifiNameList((Set<String>) o);
|
||||
mSyncOnlyOnSSIDs.setSummary(ssids.isEmpty()
|
||||
? getString(R.string.sync_only_wifi_ssids_all)
|
||||
: getString(R.string.sync_only_wifi_ssids_values, ssids));
|
||||
} else if (preference.getKey().equals(DEVICE_NAME_KEY)) {
|
||||
RestApi.Device old = mSyncthingService.getApi().getLocalDevice();
|
||||
RestApi.Device updated = new RestApi.Device();
|
||||
|
@ -297,6 +314,14 @@ public class SettingsFragment extends PreferenceFragment
|
|||
return true;
|
||||
}
|
||||
|
||||
private String formatWifiNameList(Set<String> ssids) {
|
||||
Set<String> formatted = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
|
||||
for (String ssid : ssids) {
|
||||
formatted.add(ssid.replaceFirst("^\"", "").replaceFirst("\"$", ""));
|
||||
}
|
||||
return TextUtils.join(", ", formatted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the owner of syncthing files so they can be accessed without root.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
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.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.nutomic.syncthingandroid.R;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
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<String>());
|
||||
// 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++) {
|
||||
// WiFi SSIDs can either be UTF-8 (encapsulated in '"') or hex-strings
|
||||
if (stripQuotes)
|
||||
result[i] = configs[i].SSID.replaceFirst("^\"", "").replaceFirst("\"$", "");
|
||||
else
|
||||
result[i] = configs[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, new Comparator<WifiConfiguration>() {
|
||||
@Override
|
||||
public int compare(WifiConfiguration lhs, WifiConfiguration rhs) {
|
||||
return lhs.SSID.compareToIgnoreCase(rhs.SSID);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
// WiFi is turned off or device doesn't have WiFi
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -5,6 +5,8 @@ import android.content.BroadcastReceiver;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.BatteryManager;
|
||||
|
||||
/**
|
||||
|
@ -26,17 +28,25 @@ public class DeviceStateHolder extends BroadcastReceiver {
|
|||
*/
|
||||
public static final String EXTRA_IS_CHARGING = "is_charging";
|
||||
|
||||
private Context mContext;
|
||||
|
||||
private boolean mIsWifiConnected = false;
|
||||
|
||||
private String mWifiSsid;
|
||||
|
||||
private boolean mIsCharging = false;
|
||||
|
||||
@TargetApi(16)
|
||||
public DeviceStateHolder(Context context) {
|
||||
mContext = context;
|
||||
ConnectivityManager cm = (ConnectivityManager)
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
mIsWifiConnected = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected();
|
||||
if (android.os.Build.VERSION.SDK_INT >= 16 && cm.isActiveNetworkMetered())
|
||||
mIsWifiConnected = false;
|
||||
if (mIsWifiConnected) {
|
||||
updateWifiSsid();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,5 +71,25 @@ public class DeviceStateHolder extends BroadcastReceiver {
|
|||
public void update(Intent intent) {
|
||||
mIsWifiConnected = intent.getBooleanExtra(EXTRA_HAS_WIFI, mIsWifiConnected);
|
||||
mIsCharging = intent.getBooleanExtra(EXTRA_IS_CHARGING, mIsCharging);
|
||||
|
||||
if (mIsWifiConnected) {
|
||||
updateWifiSsid();
|
||||
} else {
|
||||
mWifiSsid = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateWifiSsid() {
|
||||
mWifiSsid = null;
|
||||
WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
|
||||
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
|
||||
// may be null, if WiFi has been turned off in meantime
|
||||
if (wifiInfo != null) {
|
||||
mWifiSsid = wifiInfo.getSSID();
|
||||
}
|
||||
}
|
||||
|
||||
public String getWifiSsid() {
|
||||
return mWifiSsid;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ import java.security.SecureRandom;
|
|||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Holds the native syncthing instance and provides an API to access it.
|
||||
|
@ -92,6 +93,7 @@ public class SyncthingService extends Service implements
|
|||
|
||||
public static final String PREF_ALWAYS_RUN_IN_BACKGROUND = "always_run_in_background";
|
||||
public static final String PREF_SYNC_ONLY_WIFI = "sync_only_wifi";
|
||||
public static final String PREF_SYNC_ONLY_WIFI_SSIDS = "sync_only_wifi_ssids_set";
|
||||
public static final String PREF_SYNC_ONLY_CHARGING = "sync_only_charging";
|
||||
public static final String PREF_USE_ROOT = "use_root";
|
||||
private static final String PREF_NOTIFICATION_TYPE = "notification_type";
|
||||
|
@ -213,7 +215,7 @@ public class SyncthingService extends Service implements
|
|||
boolean prefStopNotCharging = prefs.getBoolean(PREF_SYNC_ONLY_CHARGING, false);
|
||||
|
||||
shouldRun = (mDeviceStateHolder.isCharging() || !prefStopNotCharging) &&
|
||||
(mDeviceStateHolder.isWifiConnected() || !prefStopMobileData);
|
||||
(!prefStopMobileData || isAllowedWifiConnected());
|
||||
}
|
||||
|
||||
// Start syncthing.
|
||||
|
@ -261,6 +263,34 @@ public class SyncthingService extends Service implements
|
|||
onApiChange();
|
||||
}
|
||||
|
||||
private boolean isAllowedWifiConnected() {
|
||||
boolean wifiConnected = mDeviceStateHolder.isWifiConnected();
|
||||
if (wifiConnected) {
|
||||
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
Set<String> ssids = sp.getStringSet(PREF_SYNC_ONLY_WIFI_SSIDS, new HashSet<String>());
|
||||
if (ssids.isEmpty()) {
|
||||
Log.d(TAG, "All SSIDs allowed for syncing");
|
||||
return true;
|
||||
} else {
|
||||
String ssid = mDeviceStateHolder.getWifiSsid();
|
||||
if (ssid != null) {
|
||||
if (ssids.contains(ssid)) {
|
||||
Log.d(TAG, "SSID " + ssid + " found in whitelist");
|
||||
return true;
|
||||
}
|
||||
Log.i(TAG, "SSID " + ssid + " not whitelisted");
|
||||
return false;
|
||||
} else {
|
||||
// Don't know the SSID (yet) (should not happen?!), so not allowing
|
||||
Log.w(TAG, "SSID unknown (yet), cannot check SSID whitelist. Disallowing sync.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "Wifi not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows or hides the persistent notification based on running state and
|
||||
* {@link #PREF_NOTIFICATION_TYPE}.
|
||||
|
@ -290,7 +320,8 @@ public class SyncthingService extends Service implements
|
|||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (key.equals(PREF_NOTIFICATION_TYPE))
|
||||
updateNotification();
|
||||
else if (key.equals(PREF_SYNC_ONLY_CHARGING) || key.equals(PREF_SYNC_ONLY_WIFI))
|
||||
else if (key.equals(PREF_SYNC_ONLY_CHARGING) || key.equals(PREF_SYNC_ONLY_WIFI)
|
||||
|| key.equals(PREF_SYNC_ONLY_WIFI_SSIDS))
|
||||
updateState();
|
||||
}
|
||||
|
||||
|
|
|
@ -238,6 +238,14 @@ Please report any problems you encounter via Github.</string>
|
|||
|
||||
<string name="sync_only_wifi">Sync only on wifi</string>
|
||||
|
||||
<string name="sync_only_wifi_ssids">Restrict to certain wifi networks</string>
|
||||
|
||||
<string name="sync_only_wifi_ssids_all">Sync on all wifi networks</string>
|
||||
|
||||
<string name="sync_only_wifi_ssids_values">Sync only while connected to: %1$s</string>
|
||||
|
||||
<string name="sync_only_wifi_ssids_wifi_turn_on_wifi">Please turn on WiFi to select networks.</string>
|
||||
|
||||
<string name="advanced_folder_picker">Use advanced Folder Picker</string>
|
||||
|
||||
<string name="advanced_folder_picker_summary">Select any folder on the device for syncing</string>
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
android:title="@string/sync_only_wifi"
|
||||
android:defaultValue="false" />
|
||||
|
||||
<com.nutomic.syncthingandroid.preferences.WifiSsidPreference
|
||||
android:key="sync_only_wifi_ssids_set"
|
||||
android:title="@string/sync_only_wifi_ssids" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="advanced_folder_picker"
|
||||
android:title="@string/advanced_folder_picker"
|
||||
|
|
Loading…
Reference in a new issue