diff --git a/app/build.gradle b/app/build.gradle index 80832e3b..a7ab817b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -35,8 +35,8 @@ android { applicationId "com.github.catfriend1.syncthingandroid" minSdkVersion 16 targetSdkVersion 26 - versionCode 4169 - versionName "0.14.51.6" + versionCode 4170 + versionName "0.14.51.7" testApplicationId 'com.github.catfriend1.syncthingandroid.test' testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' playAccountConfig = playAccountConfigs.defaultAccountConfig diff --git a/app/src/main/java/com/nutomic/syncthingandroid/DaggerComponent.java b/app/src/main/java/com/nutomic/syncthingandroid/DaggerComponent.java index a0fad95d..bb224522 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/DaggerComponent.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/DaggerComponent.java @@ -1,5 +1,6 @@ package com.nutomic.syncthingandroid; +import com.nutomic.syncthingandroid.activities.DeviceActivity; import com.nutomic.syncthingandroid.activities.FirstStartActivity; import com.nutomic.syncthingandroid.activities.FolderActivity; import com.nutomic.syncthingandroid.activities.FolderPickerActivity; @@ -26,6 +27,7 @@ public interface DaggerComponent { void inject(SyncthingApp app); void inject(MainActivity activity); void inject(FirstStartActivity activity); + void inject(DeviceActivity activity); void inject(FolderActivity activity); void inject(FolderPickerActivity activity); void inject(SyncConditionsActivity activity); diff --git a/app/src/main/java/com/nutomic/syncthingandroid/activities/DeviceActivity.java b/app/src/main/java/com/nutomic/syncthingandroid/activities/DeviceActivity.java index b7971ae5..ba497076 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/activities/DeviceActivity.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/activities/DeviceActivity.java @@ -4,6 +4,7 @@ import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.v4.content.ContextCompat; @@ -24,10 +25,14 @@ import android.widget.Toast; import com.google.gson.Gson; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; + import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.model.Connections; import com.nutomic.syncthingandroid.model.Device; +import com.nutomic.syncthingandroid.service.Constants; +import com.nutomic.syncthingandroid.service.RestApi; import com.nutomic.syncthingandroid.service.SyncthingService; +import com.nutomic.syncthingandroid.SyncthingApp; import com.nutomic.syncthingandroid.util.Compression; import com.nutomic.syncthingandroid.util.TextWatcherAdapter; import com.nutomic.syncthingandroid.util.Util; @@ -36,6 +41,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import javax.inject.Inject; + import static android.text.TextUtils.isEmpty; import static android.view.View.GONE; import static android.view.View.VISIBLE; @@ -46,7 +53,11 @@ import static com.nutomic.syncthingandroid.util.Compression.METADATA; /** * Shows device details and allows changing them. */ -public class DeviceActivity extends SyncthingActivity implements View.OnClickListener { +public class DeviceActivity extends SyncthingActivity + implements + View.OnClickListener, + SyncthingActivity.OnServiceConnectedListener, + SyncthingService.OnServiceStateChangeListener { public static final String EXTRA_NOTIFICATION_ID = "com.nutomic.syncthingandroid.activities.DeviceActivity.NOTIFICATION_ID"; @@ -84,10 +95,19 @@ public class DeviceActivity extends SyncthingActivity implements View.OnClickLis private SwitchCompat mDevicePaused; + private SwitchCompat mCustomSyncConditionsSwitch; + + private TextView mCustomSyncConditionsDescription; + + private TextView mCustomSyncConditionsDialog; + private TextView mSyncthingVersionView; private View mCompressionContainer; + @Inject + SharedPreferences mPreferences; + private boolean mIsCreateMode; private boolean mDeviceNeedsToUpdate; @@ -154,6 +174,12 @@ public class DeviceActivity extends SyncthingActivity implements View.OnClickLis mDevice.paused = isChecked; mDeviceNeedsToUpdate = true; break; + case R.id.customSyncConditionsSwitch: + mCustomSyncConditionsDescription.setEnabled(isChecked); + mCustomSyncConditionsDialog.setEnabled(isChecked); + // This is needed to display the "discard changes dialog". + mDeviceNeedsToUpdate = true; + break; } } }; @@ -161,11 +187,12 @@ public class DeviceActivity extends SyncthingActivity implements View.OnClickLis @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + ((SyncthingApp) getApplication()).component().inject(this); setContentView(R.layout.fragment_device); mIsCreateMode = getIntent().getBooleanExtra(EXTRA_IS_CREATE, false); - registerOnServiceConnectedListener(this::onServiceConnected); setTitle(mIsCreateMode ? R.string.add_device : R.string.edit_device); + registerOnServiceConnectedListener(this); mIdContainer = findViewById(R.id.idContainer); mIdView = findViewById(R.id.id); @@ -177,9 +204,13 @@ public class DeviceActivity extends SyncthingActivity implements View.OnClickLis mCompressionValueView = findViewById(R.id.compressionValue); mIntroducerView = findViewById(R.id.introducer); mDevicePaused = findViewById(R.id.devicePause); + mCustomSyncConditionsSwitch = findViewById(R.id.customSyncConditionsSwitch); + mCustomSyncConditionsDescription = findViewById(R.id.customSyncConditionsDescription); + mCustomSyncConditionsDialog = findViewById(R.id.customSyncConditionsDialog); mSyncthingVersionView = findViewById(R.id.syncthingVersion); mQrButton.setOnClickListener(this); + mCustomSyncConditionsDialog.setOnClickListener(view -> onCustomSyncConditionsDialogClick()); mCompressionContainer.setOnClickListener(this); if (savedInstanceState != null){ @@ -198,6 +229,19 @@ public class DeviceActivity extends SyncthingActivity implements View.OnClickLis } } + /** + * Invoked after user clicked on the {@link mCustomSyncConditionsDialog} label. + */ + private void onCustomSyncConditionsDialogClick() { + startActivityForResult( + SyncConditionsActivity.createIntent( + this, Constants.PREF_OBJECT_PREFIX_DEVICE + mDevice.deviceID, mDevice.name + ), + 0 + ); + return; + } + private void restoreDialogStates(Bundle savedInstanceState) { if (savedInstanceState.getBoolean(IS_SHOWING_COMPRESSION_DIALOG)){ showCompressionDialog(); @@ -257,20 +301,32 @@ public class DeviceActivity extends SyncthingActivity implements View.OnClickLis Util.dismissDialogSafe(mDeleteDialog, this); } - private void onServiceConnected() { + /** + * Register for service state change events. + */ + @Override + public void onServiceConnected() { Log.v(TAG, "onServiceConnected"); SyncthingService syncthingService = (SyncthingService) getService(); syncthingService.getNotificationHandler().cancelConsentNotification(getIntent().getIntExtra(EXTRA_NOTIFICATION_ID, 0)); - syncthingService.registerOnServiceStateChangeListener(this::onServiceStateChange); + syncthingService.registerOnServiceStateChangeListener(this); } /** * Sets version and current address of the device. - *

* NOTE: This is only called once on startup, should be called more often to properly display * version/address changes. */ private void onReceiveConnections(Connections connections) { + if (connections == null || connections.connections == null) { + Log.e(TAG, "onReceiveConnections: connections == null || connections.connections == null"); + return; + } + if (mDevice == null) { + Log.e(TAG, "onReceiveConnections: mDevice == null"); + return; + } + boolean viewsExist = mSyncthingVersionView != null && mCurrentAddressView != null; if (viewsExist && connections.connections.containsKey(mDevice.deviceID)) { mCurrentAddressView.setVisibility(VISIBLE); @@ -280,18 +336,21 @@ public class DeviceActivity extends SyncthingActivity implements View.OnClickLis } } - private void onServiceStateChange(SyncthingService.State currentState) { + @Override + public void onServiceStateChange(SyncthingService.State currentState) { if (currentState != ACTIVE) { finish(); return; } if (!mIsCreateMode) { - List devices = getApi().getDevices(false); + RestApi restApi = getApi(); // restApi != null because of State.ACTIVE + List devices = restApi.getDevices(false); + String passedId = getIntent().getStringExtra(EXTRA_DEVICE_ID); mDevice = null; - for (Device device : devices) { - if (device.deviceID.equals(getIntent().getStringExtra(EXTRA_DEVICE_ID))) { - mDevice = device; + for (Device currentDevice : devices) { + if (currentDevice.deviceID.equals(passedId)) { + mDevice = currentDevice; break; } } @@ -300,10 +359,10 @@ public class DeviceActivity extends SyncthingActivity implements View.OnClickLis finish(); return; } + if (restApi != null) { + restApi.getConnections(this::onReceiveConnections); + } } - - getApi().getConnections(this::onReceiveConnections); - updateViewsAndSetListeners(); } @@ -313,6 +372,7 @@ public class DeviceActivity extends SyncthingActivity implements View.OnClickLis mAddressesView.removeTextChangedListener(mAddressesTextWatcher); mIntroducerView.setOnCheckedChangeListener(null); mDevicePaused.setOnCheckedChangeListener(null); + mCustomSyncConditionsSwitch.setOnCheckedChangeListener(null); // Update views mIdView.setText(mDevice.deviceID); @@ -322,12 +382,26 @@ public class DeviceActivity extends SyncthingActivity implements View.OnClickLis mIntroducerView.setChecked(mDevice.introducer); mDevicePaused.setChecked(mDevice.paused); + // Update views - custom sync conditions. + mCustomSyncConditionsSwitch.setChecked(false); + if (mIsCreateMode) { + findViewById(R.id.customSyncConditionsContainer).setVisibility(View.GONE); + } else { + mCustomSyncConditionsSwitch.setChecked(mPreferences.getBoolean( + Constants.DYN_PREF_OBJECT_CUSTOM_SYNC_CONDITIONS(Constants.PREF_OBJECT_PREFIX_DEVICE + mDevice.deviceID), false + )); + } + mCustomSyncConditionsSwitch.setEnabled(!mIsCreateMode); + mCustomSyncConditionsDescription.setEnabled(mCustomSyncConditionsSwitch.isChecked()); + mCustomSyncConditionsDialog.setEnabled(mCustomSyncConditionsSwitch.isChecked()); + // Keep state updated mIdView.addTextChangedListener(mIdTextWatcher); mNameView.addTextChangedListener(mNameTextWatcher); mAddressesView.addTextChangedListener(mAddressesTextWatcher); mIntroducerView.setOnCheckedChangeListener(mCheckedListener); mDevicePaused.setOnCheckedChangeListener(mCheckedListener); + mCustomSyncConditionsSwitch.setOnCheckedChangeListener(mCheckedListener); } @Override @@ -423,11 +497,34 @@ public class DeviceActivity extends SyncthingActivity implements View.OnClickLis /** * Sends the updated device info if in edit mode. + * Preconditions: mDeviceNeedsToUpdate == true */ private void updateDevice() { - if (!mIsCreateMode && mDeviceNeedsToUpdate && mDevice != null) { - getApi().editDevice(mDevice); + if (mIsCreateMode) { + // If we are about to create this folder, we cannot update via restApi. + return; } + if (mDevice == null) { + Log.e(TAG, "updateDevice: mDevice == null"); + return; + } + + // Save device specific preferences. + Log.v(TAG, "updateDevice: mDevice.deviceID = \'" + mDevice.deviceID + "\'"); + SharedPreferences.Editor editor = mPreferences.edit(); + editor.putBoolean( + Constants.DYN_PREF_OBJECT_CUSTOM_SYNC_CONDITIONS(Constants.PREF_OBJECT_PREFIX_DEVICE + mDevice.deviceID), + mCustomSyncConditionsSwitch.isChecked() + ); + editor.apply(); + + // Update device via restApi and send the config to REST endpoint. + RestApi restApi = getApi(); + if (restApi == null) { + Log.e(TAG, "updateDevice: restApi == null"); + return; + } + restApi.updateDevice(mDevice); } private List persistableAddresses(CharSequence userInput) { diff --git a/app/src/main/java/com/nutomic/syncthingandroid/activities/FolderActivity.java b/app/src/main/java/com/nutomic/syncthingandroid/activities/FolderActivity.java index e0738b9b..c101b613 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/activities/FolderActivity.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/activities/FolderActivity.java @@ -263,7 +263,6 @@ public class FolderActivity extends SyncthingActivity /** * Invoked after user clicked on the {@link mCustomSyncConditionsDialog} label. */ - @SuppressLint("InlinedAPI") private void onCustomSyncConditionsDialogClick() { startActivityForResult( SyncConditionsActivity.createIntent( @@ -367,7 +366,7 @@ public class FolderActivity extends SyncthingActivity } /** - * Save current settings in case we are in create mode and they aren't yet stored in the config. + * Register for service state change events. */ @Override public void onServiceConnected() { @@ -701,11 +700,19 @@ public class FolderActivity extends SyncthingActivity deviceView.setOnCheckedChangeListener(mCheckedListener); } + /** + * Sends the updated folder info if in edit mode. + * Preconditions: mFolderNeedsToUpdate == true + */ private void updateFolder() { if (mIsCreateMode) { // If we are about to create this folder, we cannot update via restApi. return; } + if (mFolder == null) { + Log.e(TAG, "updateFolder: mFolder == null"); + return; + } // Save folder specific preferences. Log.v(TAG, "updateFolder: mFolder.id = \'" + mFolder.id + "\'"); @@ -716,18 +723,13 @@ public class FolderActivity extends SyncthingActivity ); editor.apply(); - // Update folder via restApi. + // Update folder via restApi and send the config to REST endpoint. RestApi restApi = getApi(); - /** - * RestApi is guaranteed not to be null as {@link onServiceStateChange} - * immediately finishes this activity if SyncthingService shuts down. - */ - /* if (restApi == null) { Log.e(TAG, "updateFolder: restApi == null"); return; } - */ + // Update ignore list. String[] ignore = mEditIgnoreListContent.getText().toString().split("\n"); restApi.postFolderIgnoreList(mFolder.id, ignore); diff --git a/app/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java b/app/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java index e9ca27bd..9a9e76f0 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java @@ -105,6 +105,7 @@ public class SettingsActivity extends SyncthingActivity { private CheckBoxPreference mRunOnMobileData; private CheckBoxPreference mRunOnWifi; private CheckBoxPreference mRunOnMeteredWifi; + private CheckBoxPreference mUseWifiWhitelist; private WifiSsidPreference mWifiSsidWhitelist; private CheckBoxPreference mRunInFlightMode; @@ -173,6 +174,8 @@ public class SettingsActivity extends SyncthingActivity { (CheckBoxPreference) findPreference(Constants.PREF_RUN_ON_WIFI); mRunOnMeteredWifi = (CheckBoxPreference) findPreference(Constants.PREF_RUN_ON_METERED_WIFI); + mUseWifiWhitelist = + (CheckBoxPreference) findPreference(Constants.PREF_USE_WIFI_SSID_WHITELIST); mWifiSsidWhitelist = (WifiSsidPreference) findPreference(Constants.PREF_WIFI_SSID_WHITELIST); mRunInFlightMode = @@ -225,7 +228,8 @@ public class SettingsActivity extends SyncthingActivity { Preference appVersion = screen.findPreference("app_version"); mRunOnMeteredWifi.setEnabled(mRunOnWifi.isChecked()); - mWifiSsidWhitelist.setEnabled(mRunOnWifi.isChecked()); + mUseWifiWhitelist.setEnabled(mRunOnWifi.isChecked()); + mWifiSsidWhitelist.setEnabled(mRunOnWifi.isChecked() && mUseWifiWhitelist.isChecked()); /* Experimental options */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { /* Wakelocks are only valid on Android 5 or lower. */ @@ -262,7 +266,7 @@ public class SettingsActivity extends SyncthingActivity { screen.findPreference(Constants.PREF_POWER_SOURCE).setSummary(mPowerSource.getEntry()); String wifiSsidSummary = TextUtils.join(", ", mPreferences.getStringSet(Constants.PREF_WIFI_SSID_WHITELIST, new HashSet<>())); screen.findPreference(Constants.PREF_WIFI_SSID_WHITELIST).setSummary(TextUtils.isEmpty(wifiSsidSummary) ? - getString(R.string.run_on_all_wifi_networks) : + getString(R.string.wifi_ssid_whitelist_empty) : getString(R.string.run_on_whitelisted_wifi_networks, wifiSsidSummary) ); handleSocksProxyPreferenceChange(screen.findPreference(Constants.PREF_SOCKS_PROXY_ADDRESS), mPreferences.getString(Constants.PREF_SOCKS_PROXY_ADDRESS, "")); @@ -366,12 +370,16 @@ public class SettingsActivity extends SyncthingActivity { break; case Constants.PREF_RUN_ON_WIFI: mRunOnMeteredWifi.setEnabled((Boolean) o); + mUseWifiWhitelist.setEnabled((Boolean) o); + mWifiSsidWhitelist.setEnabled((Boolean) o && mUseWifiWhitelist.isChecked()); + break; + case Constants.PREF_USE_WIFI_SSID_WHITELIST: mWifiSsidWhitelist.setEnabled((Boolean) o); break; case Constants.PREF_WIFI_SSID_WHITELIST: String wifiSsidSummary = TextUtils.join(", ", (Set) o); preference.setSummary(TextUtils.isEmpty(wifiSsidSummary) ? - getString(R.string.run_on_all_wifi_networks) : + getString(R.string.wifi_ssid_whitelist_empty) : getString(R.string.run_on_whitelisted_wifi_networks, wifiSsidSummary) ); break; @@ -386,7 +394,7 @@ public class SettingsActivity extends SyncthingActivity { case "deviceName": Device localDevice = mRestApi.getLocalDevice(); localDevice.name = (String) o; - mRestApi.editDevice(localDevice); + mRestApi.updateDevice(localDevice); break; case "listenAddresses": mOptions.listenAddresses = Iterables.toArray(splitter.split((String) o), String.class); diff --git a/app/src/main/java/com/nutomic/syncthingandroid/activities/SyncConditionsActivity.java b/app/src/main/java/com/nutomic/syncthingandroid/activities/SyncConditionsActivity.java index 8c11f9b5..cd5c8d9b 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/activities/SyncConditionsActivity.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/activities/SyncConditionsActivity.java @@ -60,7 +60,8 @@ public class SyncConditionsActivity extends SyncthingActivity private SwitchCompat mSyncOnMobileData; /** - * Shared preferences names for custom per-folder settings. + * Shared preferences names for custom object settings. + * Object can e.g. be a folder or device. */ private String mObjectPrefixAndId; private String mPrefSyncOnWifi; @@ -108,7 +109,7 @@ public class SyncConditionsActivity extends SyncthingActivity mObjectPrefixAndId = intent.getStringExtra(EXTRA_OBJECT_PREFIX_AND_ID); Log.v(TAG, "Prefix is \'" + mObjectPrefixAndId + "\' (" + mObjectReadableName + ")"); mPrefSyncOnWifi = Constants.DYN_PREF_OBJECT_SYNC_ON_WIFI(mObjectPrefixAndId); - mPrefSyncOnWhitelistedWifi = Constants.DYN_PREF_OBJECT_SYNC_ON_WHITELISTED_WIFI(mObjectPrefixAndId); + mPrefSyncOnWhitelistedWifi = Constants.DYN_PREF_OBJECT_USE_WIFI_SSID_WHITELIST(mObjectPrefixAndId); mPrefSelectedWhitelistSsid = Constants.DYN_PREF_OBJECT_SELECTED_WHITELIST_SSID(mObjectPrefixAndId); mPrefSyncOnMeteredWifi = Constants.DYN_PREF_OBJECT_SYNC_ON_METERED_WIFI(mObjectPrefixAndId); mPrefSyncOnMobileData = Constants.DYN_PREF_OBJECT_SYNC_ON_MOBILE_DATA(mObjectPrefixAndId); @@ -117,14 +118,13 @@ public class SyncConditionsActivity extends SyncthingActivity * Load global run conditions. */ Boolean globalRunOnWifiEnabled = mPreferences.getBoolean(Constants.PREF_RUN_ON_WIFI, true); - Boolean globalWhitelistEnabled = !mPreferences.getStringSet(Constants.PREF_WIFI_SSID_WHITELIST, new HashSet<>()) - .isEmpty(); Set globalWhitelistedSsid = mPreferences.getStringSet(Constants.PREF_WIFI_SSID_WHITELIST, new HashSet<>()); + Boolean globalWhitelistEnabled = mPreferences.getBoolean(Constants.PREF_USE_WIFI_SSID_WHITELIST, false); Boolean globalRunOnMeteredWifiEnabled = mPreferences.getBoolean(Constants.PREF_RUN_ON_METERED_WIFI, false); Boolean globalRunOnMobileDataEnabled = mPreferences.getBoolean(Constants.PREF_RUN_ON_MOBILE_DATA, false); /** - * Load custom folder preferences. If unset, use global setting as default. + * Load custom object preferences. If unset, use global setting as default. */ mSyncOnWifi.setChecked(globalRunOnWifiEnabled && mPreferences.getBoolean(mPrefSyncOnWifi, globalRunOnWifiEnabled)); mSyncOnWifi.setEnabled(globalRunOnWifiEnabled); @@ -143,7 +143,7 @@ public class SyncConditionsActivity extends SyncthingActivity mSyncOnMobileData.setOnCheckedChangeListener(mCheckedListener); // Read selected WiFi Ssid whitelist items. - Set selectedWhitelistedSsid = mPreferences.getStringSet(mPrefSelectedWhitelistSsid, new HashSet<>()); + Set selectedWhitelistedSsid = mPreferences.getStringSet(mPrefSelectedWhitelistSsid, globalWhitelistedSsid); // Removes any network that is no longer part of the global WiFi Ssid whitelist. selectedWhitelistedSsid.retainAll(globalWhitelistedSsid); @@ -162,7 +162,7 @@ public class SyncConditionsActivity extends SyncthingActivity setMarginEnd(params, contentInset); TextView emptyView = new TextView(mWifiSsidContainer.getContext()); emptyView.setGravity(CENTER_VERTICAL); - emptyView.setText(R.string.wifi_ssid_whitelist_empty); + emptyView.setText(R.string.custom_wifi_ssid_whitelist_empty); mWifiSsidContainer.addView(emptyView, params); mWifiSsidContainer.setEnabled(false); } else { @@ -224,7 +224,7 @@ public class SyncConditionsActivity extends SyncthingActivity if (mUnsavedChanges) { Log.v(TAG, "onPause: mUnsavedChanges == true. Saving prefs ..."); /** - * Save custom folder preferences. + * Save custom object preferences. */ SharedPreferences.Editor editor = mPreferences.edit(); editor.putBoolean(mPrefSyncOnWifi, mSyncOnWifi.isChecked()); diff --git a/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java b/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java index 10d2a86e..d2a64d80 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java @@ -15,6 +15,7 @@ public class Constants { public static final String PREF_RUN_ON_MOBILE_DATA = "run_on_mobile_data"; public static final String PREF_RUN_ON_WIFI = "run_on_wifi"; public static final String PREF_RUN_ON_METERED_WIFI = "run_on_metered_wifi"; + public static final String PREF_USE_WIFI_SSID_WHITELIST = "use_wifi_whitelist"; public static final String PREF_WIFI_SSID_WHITELIST = "wifi_ssid_whitelist"; public static final String PREF_POWER_SOURCE = "power_source"; public static final String PREF_RESPECT_BATTERY_SAVING = "respect_battery_saving"; @@ -43,8 +44,8 @@ public class Constants { return objectPrefixAndId + "_" + PREF_RUN_ON_WIFI; } - public static String DYN_PREF_OBJECT_SYNC_ON_WHITELISTED_WIFI(String objectPrefixAndId) { - return objectPrefixAndId + "_" + "use_wifi_whitelist"; + public static String DYN_PREF_OBJECT_USE_WIFI_SSID_WHITELIST(String objectPrefixAndId) { + return objectPrefixAndId + "_" + PREF_USE_WIFI_SSID_WHITELIST; } public static String DYN_PREF_OBJECT_SELECTED_WHITELIST_SSID(String objectPrefixAndId) { diff --git a/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java b/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java index afab4c44..74fdecfc 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java @@ -518,7 +518,7 @@ public class RestApi { }, errorListener); } - public void editDevice(Device newDevice) { + public void updateDevice(Device newDevice) { synchronized (mConfigLock) { removeDeviceInternal(newDevice.deviceID); mConfig.devices.add(newDevice); @@ -782,26 +782,58 @@ public class RestApi { Log.v(TAG, "onSyncPreconditionChanged: Event fired."); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); synchronized (mConfigLock) { - if (mConfig == null || mConfig.folders == null) { + Boolean configChanged = false; + + // Check if the config has been loaded. + if (mConfig == null) { + Log.d(TAG, "onSyncPreconditionChanged: mConfig is not ready yet."); + return; + } + + // Check if the folders are available from config. + if (mConfig.folders != null) { + for (Folder folder : mConfig.folders) { + Boolean folderCustomSyncConditionsEnabled = sharedPreferences.getBoolean( + Constants.DYN_PREF_OBJECT_CUSTOM_SYNC_CONDITIONS(Constants.PREF_OBJECT_PREFIX_FOLDER + folder.id), false + ); + if (folderCustomSyncConditionsEnabled) { + Boolean syncConditionsMet = runConditionMonitor.checkObjectSyncConditions( + Constants.PREF_OBJECT_PREFIX_FOLDER + folder.id + ); + Log.v(TAG, "onSyncPreconditionChanged: syncFolder(" + folder.id + ")=" + (syncConditionsMet ? "1" : "0")); + if (folder.paused != !syncConditionsMet) { + folder.paused = !syncConditionsMet; + configChanged = true; + } + } + } + } else { Log.d(TAG, "onSyncPreconditionChanged: mConfig.folders is not ready yet."); return; } - Boolean configChanged = false; - for (Folder folder : mConfig.folders) { - Boolean folderCustomSyncConditionsEnabled = sharedPreferences.getBoolean( - Constants.DYN_PREF_OBJECT_CUSTOM_SYNC_CONDITIONS(Constants.PREF_OBJECT_PREFIX_FOLDER + folder.id), false - ); - if (folderCustomSyncConditionsEnabled) { - Boolean syncConditionsMet = runConditionMonitor.checkObjectSyncConditions( - Constants.PREF_OBJECT_PREFIX_FOLDER + folder.id + + // Check if the devices are available from config. + if (mConfig.devices != null) { + for (Device device : mConfig.devices) { + Boolean deviceCustomSyncConditionsEnabled = sharedPreferences.getBoolean( + Constants.DYN_PREF_OBJECT_CUSTOM_SYNC_CONDITIONS(Constants.PREF_OBJECT_PREFIX_DEVICE + device.deviceID), false ); - Log.v(TAG, "onSyncPreconditionChanged: syncFolder(" + folder.id + ")=" + (syncConditionsMet ? "1" : "0")); - if (folder.paused != !syncConditionsMet) { - folder.paused = !syncConditionsMet; - configChanged = true; + if (deviceCustomSyncConditionsEnabled) { + Boolean syncConditionsMet = runConditionMonitor.checkObjectSyncConditions( + Constants.PREF_OBJECT_PREFIX_DEVICE + device.deviceID + ); + Log.v(TAG, "onSyncPreconditionChanged: syncDevice(" + device.deviceID + ")=" + (syncConditionsMet ? "1" : "0")); + if (device.paused != !syncConditionsMet) { + device.paused = !syncConditionsMet; + configChanged = true; + } } } + } else { + Log.d(TAG, "onSyncPreconditionChanged: mConfig.devices is not ready yet."); + return; } + if (configChanged) { Log.v(TAG, "onSyncPreconditionChanged: Sending changed config ..."); sendConfig(); diff --git a/app/src/main/java/com/nutomic/syncthingandroid/service/RunConditionMonitor.java b/app/src/main/java/com/nutomic/syncthingandroid/service/RunConditionMonitor.java index 5677e5cf..ac08b6fc 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/service/RunConditionMonitor.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/service/RunConditionMonitor.java @@ -228,11 +228,13 @@ public class RunConditionMonitor { /** * Constants.PREF_WIFI_SSID_WHITELIST */ - private SyncConditionResult checkConditionSyncOnWhitelistedWifi(String prefNameSyncOnWhitelistedWifi) { - Set whitelistedWifiSsids = mPreferences.getStringSet(prefNameSyncOnWhitelistedWifi, new HashSet<>()); - boolean prefWifiWhitelistEnabled = !whitelistedWifiSsids.isEmpty(); + private SyncConditionResult checkConditionSyncOnWhitelistedWifi( + String prefNameUseWifiWhitelist, + String prefNameSelectedWhitelistSsid) { + boolean wifiWhitelistEnabled = mPreferences.getBoolean(prefNameUseWifiWhitelist, false); + Set whitelistedWifiSsids = mPreferences.getStringSet(prefNameSelectedWhitelistSsid, new HashSet<>()); try { - if (wifiWhitelistConditionMet(prefWifiWhitelistEnabled, whitelistedWifiSsids)) { + if (wifiWhitelistConditionMet(wifiWhitelistEnabled, whitelistedWifiSsids)) { return new SyncConditionResult(true, "\n" + res.getString(R.string.reason_on_whitelisted_wifi)); } return new SyncConditionResult(false, "\n" + res.getString(R.string.reason_not_on_whitelisted_wifi)); @@ -348,7 +350,7 @@ public class RunConditionMonitor { // Wifi type is allowed. Log.v(TAG, "decideShouldRun: checkConditionSyncOnWifi && checkConditionSyncOnMeteredWifi"); - scr = checkConditionSyncOnWhitelistedWifi(Constants.PREF_WIFI_SSID_WHITELIST); + scr = checkConditionSyncOnWhitelistedWifi(Constants.PREF_USE_WIFI_SSID_WHITELIST, Constants.PREF_WIFI_SSID_WHITELIST); mRunDecisionExplanation += scr.explanation; if (scr.conditionMet) { // Wifi is whitelisted. @@ -395,7 +397,10 @@ public class RunConditionMonitor { // Wifi type is allowed. Log.v(TAG, "checkObjectSyncConditions: checkConditionSyncOnWifi && checkConditionSyncOnMeteredWifi"); - scr = checkConditionSyncOnWhitelistedWifi(Constants.DYN_PREF_OBJECT_SELECTED_WHITELIST_SSID(objectPrefixAndId)); + scr = checkConditionSyncOnWhitelistedWifi( + Constants.DYN_PREF_OBJECT_USE_WIFI_SSID_WHITELIST(objectPrefixAndId), + Constants.DYN_PREF_OBJECT_SELECTED_WHITELIST_SSID(objectPrefixAndId) + ); if (scr.conditionMet) { // Wifi is whitelisted. Log.v(TAG, "checkObjectSyncConditions: checkConditionSyncOnWifi && checkConditionSyncOnMeteredWifi && checkConditionSyncOnWhitelistedWifi"); diff --git a/app/src/main/play/en-GB/whatsnew b/app/src/main/play/en-GB/whatsnew index 24661d4a..51fb0ded 100644 --- a/app/src/main/play/en-GB/whatsnew +++ b/app/src/main/play/en-GB/whatsnew @@ -1,5 +1,5 @@ Enhancements -* Specify sync conditions differently for each folder [NEW] +* Specify sync conditions differently for each folder, device [NEW] * UI explains why syncthing is running (or not) * Support in-app editing of folder's ignore list items Fixes diff --git a/app/src/main/res/layout/fragment_device.xml b/app/src/main/res/layout/fragment_device.xml index 28574c79..17da098b 100644 --- a/app/src/main/res/layout/fragment_device.xml +++ b/app/src/main/res/layout/fragment_device.xml @@ -122,6 +122,59 @@ android:drawableStart="@drawable/ic_pause_circle_outline_black_24dp" android:text="@string/pause_device" /> + + + + + + + + + + + + + Run when device is connected to a metered Wi-Fi network e.g. a hotspot or tethered network. Attention: This can consume large portion of your data plan if you sync a lot of data. Run on specified Wi-Fi networks - Run only on selected Wi-Fi networks: %1$s + Select Wi-Fi networks + Selected Wi-Fi networks: %1$s Run on all Wi-Fi networks. + No Wi-Fi networks specified. Click to specify networks. Please turn on Wi-Fi to select networks. @@ -627,7 +629,7 @@ Please report any problems you encounter via Github. - No WiFi SSID\'s whitelisted. Please specify some in the settings. + No WiFi SSID\'s whitelisted. Please specify some in the settings. diff --git a/app/src/main/res/xml/app_settings.xml b/app/src/main/res/xml/app_settings.xml index 0ce25afa..979472d1 100644 --- a/app/src/main/res/xml/app_settings.xml +++ b/app/src/main/res/xml/app_settings.xml @@ -12,23 +12,34 @@ android:title="@string/run_conditions_title" android:summary="@string/run_conditions_summary"/> + + + + + + +