Add options to stop sync when not charging or not on wifi (fixes #15).

This commit is contained in:
Felix Ableitner 2014-08-01 23:09:00 +02:00
parent 733940cbdf
commit 9c4a85b85d
16 changed files with 383 additions and 91 deletions

View File

@ -13,6 +13,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:allowBackup="false"
@ -57,6 +58,17 @@
android:name="android.support.UI_OPTIONS"
android:value="splitActionBarWhenNarrow" />
</activity>
<receiver android:name=".syncthing.NetworkReceiver" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
<receiver android:name=".syncthing.BatteryReceiver" >
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -203,9 +203,10 @@ public class FolderPickerActivity extends ActionBarActivity
}
@Override
public void onApiChange(boolean isAvailable) {
if (!isAvailable) {
public void onApiChange(SyncthingService.State currentState) {
if (currentState != SyncthingService.State.ACTIVE) {
setResult(Activity.RESULT_CANCELED);
SyncthingService.showDisabledDialog(this);
finish();
}
}

View File

@ -111,8 +111,8 @@ public class LocalNodeInfoFragment extends Fragment
}
@Override
public void onApiChange(boolean isAvailable) {
if (!isAvailable)
public void onApiChange(SyncthingService.State currentState) {
if (currentState != SyncthingService.State.ACTIVE)
return;
updateGui();

View File

@ -23,6 +23,7 @@ import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBar.Tab;
import android.support.v7.app.ActionBar.TabListener;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@ -66,37 +67,44 @@ public class MainActivity extends ActionBarActivity
*/
@Override
@SuppressLint("InflateParams")
public void onApiChange(boolean isAvailable) {
if (!isAvailable) {
final SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
LayoutInflater inflater = getLayoutInflater();
View dialogLayout = inflater.inflate(R.layout.loading_dialog, null);
TextView loadingText = (TextView) dialogLayout.findViewById(R.id.loading_text);
loadingText.setText((mSyncthingService.isFirstStart())
? R.string.web_gui_creating_key
: R.string.api_loading);
mLoadingDialog = new AlertDialog.Builder(this)
.setCancelable(false)
.setView(dialogLayout)
.show();
// Make sure the first start dialog is shown on top.
if (prefs.getBoolean("first_start", true)) {
new AlertDialog.Builder(this)
.setTitle(R.string.welcome_title)
.setMessage(R.string.welcome_text)
.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
prefs.edit().putBoolean("first_start", false).commit();
}
})
.show();
public void onApiChange(SyncthingService.State currentState) {
if (currentState != SyncthingService.State.ACTIVE && !isFinishing()) {
if (currentState == SyncthingService.State.DISABLED) {
if (mLoadingDialog != null) {
mLoadingDialog.dismiss();
}
SyncthingService.showDisabledDialog(this);
}
else if (mLoadingDialog == null) {
final SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
LayoutInflater inflater = getLayoutInflater();
View dialogLayout = inflater.inflate(R.layout.loading_dialog, null);
TextView loadingText = (TextView) dialogLayout.findViewById(R.id.loading_text);
loadingText.setText((mSyncthingService.isFirstStart())
? R.string.web_gui_creating_key
: R.string.api_loading);
mLoadingDialog = new AlertDialog.Builder(this)
.setCancelable(false)
.setView(dialogLayout)
.show();
// Make sure the first start dialog is shown on top.
if (prefs.getBoolean("first_start", true)) {
new AlertDialog.Builder(this)
.setTitle(R.string.welcome_title)
.setMessage(R.string.welcome_text)
.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
prefs.edit().putBoolean("first_start", false).commit();
}
})
.show();
}
}
return;
}

View File

@ -75,6 +75,7 @@ public class NodeSettingsActivity extends PreferenceActivity implements
@Override
@SuppressLint("AppCompatMethod")
@TargetApi(11)
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -109,8 +110,9 @@ public class NodeSettingsActivity extends PreferenceActivity implements
}
@Override
public void onApiChange(boolean isAvailable) {
if (!isAvailable) {
public void onApiChange(SyncthingService.State currentState) {
if (currentState != SyncthingService.State.ACTIVE) {
SyncthingService.showDisabledDialog(this);
finish();
return;
}

View File

@ -31,8 +31,8 @@ public class NodesFragment extends ListFragment implements SyncthingService.OnAp
}
@Override
public void onApiChange(boolean isAvailable) {
if (!isAvailable)
public void onApiChange(SyncthingService.State currentState) {
if (currentState != SyncthingService.State.ACTIVE)
return;
initAdapter();

View File

@ -118,8 +118,9 @@ public class RepoSettingsActivity extends PreferenceActivity
}
@Override
public void onApiChange(boolean isAvailable) {
if (!isAvailable) {
public void onApiChange(SyncthingService.State currentState) {
if (currentState != SyncthingService.State.ACTIVE) {
SyncthingService.showDisabledDialog(this);
finish();
return;
}

View File

@ -30,8 +30,8 @@ public class ReposFragment extends ListFragment implements SyncthingService.OnAp
}
@Override
public void onApiChange(boolean isAvailable) {
if (!isAvailable)
public void onApiChange(SyncthingService.State currentState) {
if (currentState != SyncthingService.State.ACTIVE)
return;
initAdapter();

View File

@ -32,6 +32,10 @@ public class SettingsActivity extends PreferenceActivity
private static final String SYNCTHING_VERSION_KEY = "syncthing_version";
private CheckBoxPreference mStopNotCharging;
private CheckBoxPreference mStopMobileData;
private Preference mVersion;
private PreferenceScreen mOptionsScreen;
@ -57,30 +61,29 @@ public class SettingsActivity extends PreferenceActivity
};
@Override
public void onApiChange(boolean isAvailable) {
if (!isAvailable) {
finish();
return;
}
public void onApiChange(SyncthingService.State currentState) {
mOptionsScreen.setEnabled(currentState == SyncthingService.State.ACTIVE);
mGuiScreen.setEnabled(currentState == SyncthingService.State.ACTIVE);
mVersion.setSummary(mSyncthingService.getApi().getVersion());
for (int i = 0; i < mOptionsScreen.getPreferenceCount(); i++) {
Preference pref = mOptionsScreen.getPreference(i);
pref.setOnPreferenceChangeListener(SettingsActivity.this);
String value = mSyncthingService.getApi()
.getValue(RestApi.TYPE_OPTIONS, pref.getKey());
applyPreference(pref, value);
}
if (currentState == SyncthingService.State.ACTIVE) {
for (int i = 0; i < mOptionsScreen.getPreferenceCount(); i++) {
Preference pref = mOptionsScreen.getPreference(i);
pref.setOnPreferenceChangeListener(SettingsActivity.this);
String value = mSyncthingService.getApi()
.getValue(RestApi.TYPE_OPTIONS, pref.getKey());
applyPreference(pref, value);
}
for (int i = 0; i < mGuiScreen.getPreferenceCount(); i++) {
Preference pref = mGuiScreen.getPreference(i);
pref.setOnPreferenceChangeListener(SettingsActivity.this);
String value = mSyncthingService.getApi()
.getValue(RestApi.TYPE_GUI, pref.getKey());
applyPreference(pref, value);
for (int i = 0; i < mGuiScreen.getPreferenceCount(); i++) {
Preference pref = mGuiScreen.getPreference(i);
pref.setOnPreferenceChangeListener(SettingsActivity.this);
String value = mSyncthingService.getApi()
.getValue(RestApi.TYPE_GUI, pref.getKey());
applyPreference(pref, value);
}
}
}
/**
@ -119,6 +122,10 @@ public class SettingsActivity extends PreferenceActivity
addPreferencesFromResource(R.xml.app_settings);
PreferenceScreen screen = getPreferenceScreen();
mStopNotCharging = (CheckBoxPreference) findPreference("stop_sync_on_mobile_data");
mStopNotCharging.setOnPreferenceChangeListener(this);
mStopMobileData = (CheckBoxPreference) findPreference("stop_sync_while_not_charging");
mStopMobileData.setOnPreferenceChangeListener(this);
mVersion = screen.findPreference(SYNCTHING_VERSION_KEY);
mOptionsScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_OPTIONS_KEY);
mGuiScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_GUI_KEY);
@ -159,16 +166,18 @@ public class SettingsActivity extends PreferenceActivity
}
}
if (mOptionsScreen.findPreference(preference.getKey()) != null) {
if (preference.equals(mStopNotCharging) || preference.equals(mStopMobileData)) {
mSyncthingService.updateState();
}
else if (mOptionsScreen.findPreference(preference.getKey()) != null) {
mSyncthingService.getApi().setValue(RestApi.TYPE_OPTIONS, preference.getKey(), o,
preference.getKey().equals("ListenAddress"), this);
return true;
}
else if (mGuiScreen.findPreference(preference.getKey()) != null) {
mSyncthingService.getApi().setValue(
RestApi.TYPE_GUI, preference.getKey(), o, false, this);
return true;
}
return false;
return true;
}
}

View File

@ -0,0 +1,20 @@
package com.nutomic.syncthingandroid.syncthing;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* Receives battery plug/unplug intents and sends the charging state to {@link SyncthingService}.
*/
public class BatteryReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
boolean isCharging = Intent.ACTION_POWER_CONNECTED.equals(intent.getAction());
Intent i = new Intent(context, SyncthingService.class);
i.putExtra(DeviceStateHolder.EXTRA_IS_CHARGING, isCharging);
context.startService(i);
}
}

View File

@ -0,0 +1,65 @@
package com.nutomic.syncthingandroid.syncthing;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.os.BatteryManager;
import android.util.Log;
/**
* Holds information about the current wifi and charging state of the device.
*
* This information is actively read on construction, and then updated from intents that are passed
* to {@link #update(android.content.Intent)}.
*/
public class DeviceStateHolder extends BroadcastReceiver {
/**
* Intent extra containing a boolean saying whether wifi is connected or not.
*/
public static final String EXTRA_HAS_WIFI = "has_wifi";
/**
* Intent extra containging a boolean saying whether the device is
* charging or not (any power source).
*/
public static final String EXTRA_IS_CHARGING = "is_charging";
private boolean mIsInitialized = false;
private boolean mIsWifiConnected = false;
private boolean mIsCharging = false;
private SyncthingService mService;
public DeviceStateHolder(SyncthingService service) {
mService = service;
ConnectivityManager cm = (ConnectivityManager)
mService.getSystemService(Context.CONNECTIVITY_SERVICE);
mIsWifiConnected = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected();
}
@Override
public void onReceive(Context context, Intent intent) {
context.unregisterReceiver(this);
int status = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
mIsCharging = status != 0;
mIsInitialized = true;
}
public boolean isCharging() {
return mIsCharging;
}
public boolean isWifiConnected() {
return mIsWifiConnected;
}
public void update(Intent intent) {
mIsWifiConnected = intent.getBooleanExtra(EXTRA_HAS_WIFI, mIsWifiConnected);
mIsCharging = intent.getBooleanExtra(EXTRA_IS_CHARGING, mIsCharging);
}
}

View File

@ -0,0 +1,31 @@
package com.nutomic.syncthingandroid.syncthing;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.BatteryManager;
import android.preference.PreferenceManager;
import android.util.Log;
/**
* Receives network connection change intents and sends the wifi state to {@link SyncthingService}.
*/
public class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo wifiInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
boolean isWifiConnected = (wifiInfo != null && wifiInfo.isConnected()) ||
activeNetworkInfo == null;
Intent i = new Intent(context, SyncthingService.class);
i.putExtra(DeviceStateHolder.EXTRA_HAS_WIFI, isWifiConnected);
context.startService(i);
}
}

View File

@ -195,6 +195,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
*/
@Override
public void onWebGuiAvailable() {
mAvailableCount.set(0);
new GetTask() {
@Override
protected void onPostExecute(String version) {
@ -226,12 +227,13 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
/**
* Increments mAvailableCount by one, and, if it reached TOTAL_STARTUP_CALLS,
* calls {@link SyncthingService#onApiChange(boolean)}.
* calls {@link SyncthingService#onApiChange()}.
*/
private void tryIsAvailable() {
int value = mAvailableCount.incrementAndGet();
assert(value <= TOTAL_STARTUP_CALLS);
if (value == TOTAL_STARTUP_CALLS) {
mSyncthingService.onApiChange(true);
mSyncthingService.onApiChange();
}
}
@ -370,12 +372,15 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
* Returns a list of all existing nodes.
*/
public List<Node> getNodes() {
if (mConfig == null)
return new ArrayList<Node>();
try {
return getNodes(mConfig.getJSONArray("Nodes"));
}
catch (JSONException e) {
Log.w(TAG, "Failed to read nodes", e);
return null;
return new ArrayList<Node>();
}
}

View File

@ -1,14 +1,18 @@
package com.nutomic.syncthingandroid.syncthing;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.util.Pair;
@ -16,6 +20,7 @@ import android.view.WindowManager;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.gui.MainActivity;
import com.nutomic.syncthingandroid.gui.SettingsActivity;
import com.nutomic.syncthingandroid.util.ConfigXml;
import org.apache.http.HttpResponse;
@ -98,20 +103,47 @@ public class SyncthingService extends Service {
private final HashSet<OnWebGuiAvailableListener> mOnWebGuiAvailableListeners =
new HashSet<OnWebGuiAvailableListener>();
private boolean mIsWebGuiAvailable = false;
public interface OnApiChangeListener {
public void onApiChange(boolean isAvailable);
public void onApiChange(State currentState);
}
private final HashSet<WeakReference<OnApiChangeListener>> mOnApiAvailableListeners =
private final HashSet<WeakReference<OnApiChangeListener>> mOnApiChangeListeners =
new HashSet<WeakReference<OnApiChangeListener>>();
/**
* INIT: Service is starting up and initializing.
* STARTING: Syncthing binary is starting (but the API is not yet ready).
* ACTIVE: Syncthing binary is up and running.
* DISABLED: Syncthing binary is stopped according to user preferences.
*/
public enum State {
INIT,
STARTING,
ACTIVE,
DISABLED
}
private State mCurrentState = State.INIT;
/**
* True if a stop was requested while syncthing is starting, in that case, perform stop in
* {@link PollWebGuiAvailableTask}.
*/
private boolean mStopScheduled = false;
private DeviceStateHolder mDeviceStateHolder;
/**
* Handles intents, either {@link #ACTION_RESTART}, or intents having
* {@link DeviceStateHolder.EXTRA_HAS_WIFI} or {@link DeviceStateHolder.EXTRA_IS_CHARGING}
* (which are handled by {@link DeviceStateHolder}.
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null && ACTION_RESTART.equals(intent.getAction())) {
mIsWebGuiAvailable = false;
onApiChange(false);
// Just catch the empty intent and return.
if (intent == null) {
}
else if (ACTION_RESTART.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
new PostTask() {
@Override
protected void onPostExecute(Void aVoid) {
@ -123,9 +155,55 @@ public class SyncthingService extends Service {
}
}.execute(mApi.getUrl(), PostTask.URI_RESTART, mApi.getApiKey());
}
else if (mCurrentState != State.INIT) {
mDeviceStateHolder.update(intent);
updateState();
}
return START_STICKY;
}
/**
* Checks according to preferences and charging/wifi state, whether syncthing should be enabled
* or not.
*
* Depending on the result, syncthing is started or stopped, and {@link #onApiChange()} is
* called.
*/
public void updateState() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean prefStopMobileData = prefs.getBoolean("stop_sync_on_mobile_data", true);
boolean prefStopNotCharging = prefs.getBoolean("stop_sync_while_not_charging", true);
// Start syncthing.
if ((mDeviceStateHolder.isCharging() || !prefStopNotCharging) &&
(mDeviceStateHolder.isWifiConnected() || !prefStopMobileData)) {
if (mCurrentState == State.ACTIVE || mCurrentState == State.STARTING) {
mStopScheduled = false;
return;
}
mCurrentState = State.STARTING;
registerOnWebGuiAvailableListener(mApi);
new PollWebGuiAvailableTask().execute();
new Thread(new SyncthingRunnable()).start();
}
// Stop syncthing.
else {
if (mCurrentState == State.DISABLED)
return;
mCurrentState = State.DISABLED;
// Syncthing is currently started, perform the stop later.
if (mCurrentState == State.STARTING) {
mStopScheduled = true;
} else if (mApi != null) {
mApi.shutdown();
}
}
onApiChange();
}
private Handler mMainThreadHandler;
/**
@ -254,8 +332,15 @@ public class SyncthingService extends Service {
@Override
protected void onPostExecute(Void aVoid) {
if (mStopScheduled) {
mCurrentState = State.DISABLED;
onApiChange();
mApi.shutdown();
mStopScheduled = false;
return;
}
Log.i(TAG, "Web GUI has come online at " + mApi.getUrl());
mIsWebGuiAvailable = true;
mCurrentState = State.ACTIVE;
for (OnWebGuiAvailableListener listener : mOnWebGuiAvailableListeners) {
listener.onWebGuiAvailable();
}
@ -316,6 +401,9 @@ public class SyncthingService extends Service {
n.flags |= Notification.FLAG_ONGOING_EVENT;
startForeground(NOTIFICATION_RUNNING, n);
mMainThreadHandler = new Handler();
mDeviceStateHolder = new DeviceStateHolder(SyncthingService.this);
registerReceiver(mDeviceStateHolder, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
new StartupTask().execute();
}
@ -327,7 +415,6 @@ public class SyncthingService extends Service {
private class StartupTask extends AsyncTask<Void, Void, Pair<String, String>> {
@Override
protected Pair<String, String> doInBackground(Void... voids) {
//Looper.prepare();
if (isFirstStart()) {
Log.i(TAG, "App started for the first time. " +
"Copying default config, keys will be generated automatically");
@ -347,14 +434,12 @@ public class SyncthingService extends Service {
protected void onPostExecute(Pair<String, String> urlAndKey) {
mApi = new RestApi(SyncthingService.this, urlAndKey.first, urlAndKey.second);
Log.i(TAG, "Web GUI will be available at " + mApi.getUrl());
// HACK: Make sure there is no syncthing binary left running from an improper
// shutdown (eg Play Store update).
// NOTE: This will log an exception if syncthing is not actually running.
new PostTask().execute(mApi.getUrl(), PostTask.URI_SHUTDOWN, urlAndKey.second, "");
registerOnWebGuiAvailableListener(mApi);
new PollWebGuiAvailableTask().execute();
mMainThreadHandler = new Handler();
new Thread(new SyncthingRunnable()).start();
mApi.shutdown();
updateState();
}
}
@ -380,7 +465,7 @@ public class SyncthingService extends Service {
* Listeners are unregistered automatically after being called.
*/
public void registerOnWebGuiAvailableListener(OnWebGuiAvailableListener listener) {
if (mIsWebGuiAvailable) {
if (mCurrentState == State.ACTIVE) {
listener.onWebGuiAvailable();
}
else {
@ -442,20 +527,23 @@ public class SyncthingService extends Service {
* changes.
*/
public void registerOnApiChangeListener(OnApiChangeListener listener) {
listener.onApiChange((mApi != null) ? mApi.isApiAvailable() : false);
mOnApiAvailableListeners.add(new WeakReference<OnApiChangeListener>(listener));
// Make sure we don't send an invalid state or syncthing might shwow a "disabled" message
// when it's just starting up.
listener.onApiChange(mCurrentState);
mOnApiChangeListeners.add(new WeakReference<OnApiChangeListener>(listener));
}
/**
* Called when the state of the API changes.
*
* Must only be called from SyncthingService or {@link RestApi}.
* Called to notifiy listeners of an API change.
*
* Must only be called from SyncthingService or {@link RestApi}.
*/
public void onApiChange(boolean isAvailable) {
for (Iterator<WeakReference<OnApiChangeListener>> i = mOnApiAvailableListeners.iterator(); i.hasNext();) {
public void onApiChange() {
for (Iterator<WeakReference<OnApiChangeListener>> i = mOnApiChangeListeners.iterator();
i.hasNext(); ) {
WeakReference<OnApiChangeListener> listener = i.next();
if (listener.get() != null) {
listener.get().onApiChange(isAvailable);
listener.get().onApiChange(mCurrentState);
}
else {
i.remove();
@ -463,4 +551,31 @@ public class SyncthingService extends Service {
}
}
/**
* Dialog to be shown when attempting to start syncthing while it is disabled according
* to settings (because the device is not charging or wifi is disconnected).
*/
public static void showDisabledDialog(final Activity activity) {
new AlertDialog.Builder(activity)
.setTitle(R.string.syncthing_disabled_title)
.setMessage(R.string.syncthing_disabled_message)
.setPositiveButton(R.string.syncthing_disabled_change_settings,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
activity.finish();
activity.startActivity(new Intent(activity, SettingsActivity.class));
}
})
.setNegativeButton(R.string.exit,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
activity.finish();
}
})
.show()
.setCancelable(false);
}
}

View File

@ -20,6 +20,7 @@
<!-- RepositoriesFragment -->
<string name="repositories_fragment_title">Repositories</string>
<!-- Format string for repository progress. First parameter is status string, second is sync percentage -->
@ -33,6 +34,7 @@
<!-- NodesFragment -->
<string name="nodes_fragment_title">Nodes</string>
<!-- Shown if no nodes exist -->
@ -192,6 +194,10 @@ Please report any problems you encounter.</string>
<!-- Activity title -->
<string name="settings_title">Settings</string>
<string name="stop_sync_while_not_charging">Stop sync when not charging</string>
<string name="stop_sync_on_mobile_data">Stop sync on mobile data</string>
<!-- Settings item that opens issue tracker -->
<string name="report_issue_title">Report Issue</string>
@ -221,7 +227,6 @@ Please report any problems you encounter.</string>
<!-- SyncthingService -->
<!-- Title of the dialog shown when the syncthing binary returns an error -->
<string name="binary_crashed_title">Syncthing Binary Crashed</string>
@ -230,6 +235,14 @@ Please report any problems you encounter.</string>
<string name="binary_crashed_message">The syncthing binary has exited with error code %1$d.\n\n
If this error persists, try reinstalling the app and restarting your device.\n\n</string>
<!-- Title of the "syncthing disabled" dialog -->
<string name="syncthing_disabled_title">Syncthing is disabled</string>
<!-- Message of the "syncthing disabled" dialog -->
<string name="syncthing_disabled_message">Do you want to change your preferences?</string>
<string name="syncthing_disabled_change_settings">Change Settings</string>
<!-- RestApi -->

View File

@ -1,6 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:key="stop_sync_while_not_charging"
android:title="@string/stop_sync_while_not_charging"
android:defaultValue="true" />
<CheckBoxPreference
android:key="stop_sync_on_mobile_data"
android:title="@string/stop_sync_on_mobile_data"
android:defaultValue="true" />
<PreferenceScreen
android:key="syncthing_options"
android:title="Syncthing Options"