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.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application <application
android:allowBackup="false" android:allowBackup="false"
@ -57,6 +58,17 @@
android:name="android.support.UI_OPTIONS" android:name="android.support.UI_OPTIONS"
android:value="splitActionBarWhenNarrow" /> android:value="splitActionBarWhenNarrow" />
</activity> </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> </application>
</manifest> </manifest>

View File

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

View File

@ -111,8 +111,8 @@ public class LocalNodeInfoFragment extends Fragment
} }
@Override @Override
public void onApiChange(boolean isAvailable) { public void onApiChange(SyncthingService.State currentState) {
if (!isAvailable) if (currentState != SyncthingService.State.ACTIVE)
return; return;
updateGui(); 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.Tab;
import android.support.v7.app.ActionBar.TabListener; import android.support.v7.app.ActionBar.TabListener;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -66,8 +67,15 @@ public class MainActivity extends ActionBarActivity
*/ */
@Override @Override
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
public void onApiChange(boolean isAvailable) { public void onApiChange(SyncthingService.State currentState) {
if (!isAvailable) { 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 = final SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(MainActivity.this); PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
@ -96,7 +104,7 @@ public class MainActivity extends ActionBarActivity
}) })
.show(); .show();
} }
}
return; return;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

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 @Override
public void onWebGuiAvailable() { public void onWebGuiAvailable() {
mAvailableCount.set(0);
new GetTask() { new GetTask() {
@Override @Override
protected void onPostExecute(String version) { 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, * Increments mAvailableCount by one, and, if it reached TOTAL_STARTUP_CALLS,
* calls {@link SyncthingService#onApiChange(boolean)}. * calls {@link SyncthingService#onApiChange()}.
*/ */
private void tryIsAvailable() { private void tryIsAvailable() {
int value = mAvailableCount.incrementAndGet(); int value = mAvailableCount.incrementAndGet();
assert(value <= TOTAL_STARTUP_CALLS);
if (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. * Returns a list of all existing nodes.
*/ */
public List<Node> getNodes() { public List<Node> getNodes() {
if (mConfig == null)
return new ArrayList<Node>();
try { try {
return getNodes(mConfig.getJSONArray("Nodes")); return getNodes(mConfig.getJSONArray("Nodes"));
} }
catch (JSONException e) { catch (JSONException e) {
Log.w(TAG, "Failed to read nodes", 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; package com.nutomic.syncthingandroid.syncthing;
import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Notification; import android.app.Notification;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
@ -16,6 +20,7 @@ import android.view.WindowManager;
import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.gui.MainActivity; import com.nutomic.syncthingandroid.gui.MainActivity;
import com.nutomic.syncthingandroid.gui.SettingsActivity;
import com.nutomic.syncthingandroid.util.ConfigXml; import com.nutomic.syncthingandroid.util.ConfigXml;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
@ -98,20 +103,47 @@ public class SyncthingService extends Service {
private final HashSet<OnWebGuiAvailableListener> mOnWebGuiAvailableListeners = private final HashSet<OnWebGuiAvailableListener> mOnWebGuiAvailableListeners =
new HashSet<OnWebGuiAvailableListener>(); new HashSet<OnWebGuiAvailableListener>();
private boolean mIsWebGuiAvailable = false;
public interface OnApiChangeListener { 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>>(); 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 @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null && ACTION_RESTART.equals(intent.getAction())) { // Just catch the empty intent and return.
mIsWebGuiAvailable = false; if (intent == null) {
onApiChange(false); }
else if (ACTION_RESTART.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
new PostTask() { new PostTask() {
@Override @Override
protected void onPostExecute(Void aVoid) { protected void onPostExecute(Void aVoid) {
@ -123,9 +155,55 @@ public class SyncthingService extends Service {
} }
}.execute(mApi.getUrl(), PostTask.URI_RESTART, mApi.getApiKey()); }.execute(mApi.getUrl(), PostTask.URI_RESTART, mApi.getApiKey());
} }
else if (mCurrentState != State.INIT) {
mDeviceStateHolder.update(intent);
updateState();
}
return START_STICKY; 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; private Handler mMainThreadHandler;
/** /**
@ -254,8 +332,15 @@ public class SyncthingService extends Service {
@Override @Override
protected void onPostExecute(Void aVoid) { 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()); Log.i(TAG, "Web GUI has come online at " + mApi.getUrl());
mIsWebGuiAvailable = true; mCurrentState = State.ACTIVE;
for (OnWebGuiAvailableListener listener : mOnWebGuiAvailableListeners) { for (OnWebGuiAvailableListener listener : mOnWebGuiAvailableListeners) {
listener.onWebGuiAvailable(); listener.onWebGuiAvailable();
} }
@ -316,6 +401,9 @@ public class SyncthingService extends Service {
n.flags |= Notification.FLAG_ONGOING_EVENT; n.flags |= Notification.FLAG_ONGOING_EVENT;
startForeground(NOTIFICATION_RUNNING, n); startForeground(NOTIFICATION_RUNNING, n);
mMainThreadHandler = new Handler();
mDeviceStateHolder = new DeviceStateHolder(SyncthingService.this);
registerReceiver(mDeviceStateHolder, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
new StartupTask().execute(); new StartupTask().execute();
} }
@ -327,7 +415,6 @@ public class SyncthingService extends Service {
private class StartupTask extends AsyncTask<Void, Void, Pair<String, String>> { private class StartupTask extends AsyncTask<Void, Void, Pair<String, String>> {
@Override @Override
protected Pair<String, String> doInBackground(Void... voids) { protected Pair<String, String> doInBackground(Void... voids) {
//Looper.prepare();
if (isFirstStart()) { if (isFirstStart()) {
Log.i(TAG, "App started for the first time. " + Log.i(TAG, "App started for the first time. " +
"Copying default config, keys will be generated automatically"); "Copying default config, keys will be generated automatically");
@ -347,14 +434,12 @@ public class SyncthingService extends Service {
protected void onPostExecute(Pair<String, String> urlAndKey) { protected void onPostExecute(Pair<String, String> urlAndKey) {
mApi = new RestApi(SyncthingService.this, urlAndKey.first, urlAndKey.second); mApi = new RestApi(SyncthingService.this, urlAndKey.first, urlAndKey.second);
Log.i(TAG, "Web GUI will be available at " + mApi.getUrl()); Log.i(TAG, "Web GUI will be available at " + mApi.getUrl());
// HACK: Make sure there is no syncthing binary left running from an improper // HACK: Make sure there is no syncthing binary left running from an improper
// shutdown (eg Play Store update). // shutdown (eg Play Store update).
// NOTE: This will log an exception if syncthing is not actually running. // NOTE: This will log an exception if syncthing is not actually running.
new PostTask().execute(mApi.getUrl(), PostTask.URI_SHUTDOWN, urlAndKey.second, ""); mApi.shutdown();
registerOnWebGuiAvailableListener(mApi); updateState();
new PollWebGuiAvailableTask().execute();
mMainThreadHandler = new Handler();
new Thread(new SyncthingRunnable()).start();
} }
} }
@ -380,7 +465,7 @@ public class SyncthingService extends Service {
* Listeners are unregistered automatically after being called. * Listeners are unregistered automatically after being called.
*/ */
public void registerOnWebGuiAvailableListener(OnWebGuiAvailableListener listener) { public void registerOnWebGuiAvailableListener(OnWebGuiAvailableListener listener) {
if (mIsWebGuiAvailable) { if (mCurrentState == State.ACTIVE) {
listener.onWebGuiAvailable(); listener.onWebGuiAvailable();
} }
else { else {
@ -442,20 +527,23 @@ public class SyncthingService extends Service {
* changes. * changes.
*/ */
public void registerOnApiChangeListener(OnApiChangeListener listener) { public void registerOnApiChangeListener(OnApiChangeListener listener) {
listener.onApiChange((mApi != null) ? mApi.isApiAvailable() : false); // Make sure we don't send an invalid state or syncthing might shwow a "disabled" message
mOnApiAvailableListeners.add(new WeakReference<OnApiChangeListener>(listener)); // when it's just starting up.
listener.onApiChange(mCurrentState);
mOnApiChangeListeners.add(new WeakReference<OnApiChangeListener>(listener));
} }
/** /**
* Called when the state of the API changes. * Called to notifiy listeners of an API change.
* *
* Must only be called from SyncthingService or {@link RestApi}. * Must only be called from SyncthingService or {@link RestApi}.
*/ */
public void onApiChange(boolean isAvailable) { public void onApiChange() {
for (Iterator<WeakReference<OnApiChangeListener>> i = mOnApiAvailableListeners.iterator(); i.hasNext();) { for (Iterator<WeakReference<OnApiChangeListener>> i = mOnApiChangeListeners.iterator();
i.hasNext(); ) {
WeakReference<OnApiChangeListener> listener = i.next(); WeakReference<OnApiChangeListener> listener = i.next();
if (listener.get() != null) { if (listener.get() != null) {
listener.get().onApiChange(isAvailable); listener.get().onApiChange(mCurrentState);
} }
else { else {
i.remove(); 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 --> <!-- RepositoriesFragment -->
<string name="repositories_fragment_title">Repositories</string> <string name="repositories_fragment_title">Repositories</string>
<!-- Format string for repository progress. First parameter is status string, second is sync percentage --> <!-- Format string for repository progress. First parameter is status string, second is sync percentage -->
@ -33,6 +34,7 @@
<!-- NodesFragment --> <!-- NodesFragment -->
<string name="nodes_fragment_title">Nodes</string> <string name="nodes_fragment_title">Nodes</string>
<!-- Shown if no nodes exist --> <!-- Shown if no nodes exist -->
@ -192,6 +194,10 @@ Please report any problems you encounter.</string>
<!-- Activity title --> <!-- Activity title -->
<string name="settings_title">Settings</string> <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 --> <!-- Settings item that opens issue tracker -->
<string name="report_issue_title">Report Issue</string> <string name="report_issue_title">Report Issue</string>
@ -221,7 +227,6 @@ Please report any problems you encounter.</string>
<!-- SyncthingService --> <!-- SyncthingService -->
<!-- Title of the dialog shown when the syncthing binary returns an error --> <!-- Title of the dialog shown when the syncthing binary returns an error -->
<string name="binary_crashed_title">Syncthing Binary Crashed</string> <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 <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> 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 --> <!-- RestApi -->

View File

@ -1,6 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <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 <PreferenceScreen
android:key="syncthing_options" android:key="syncthing_options"
android:title="Syncthing Options" android:title="Syncthing Options"