1
0
Fork 0
mirror of https://github.com/syncthing/syncthing-android.git synced 2024-12-23 11:21:29 +00:00

Changed code style to use spaces instead of tabs.

It is impossible to add a custom code style in Android Studio that is
stored in the project repository. So change style to something that's
easy to use in practice now rather than later.
This commit is contained in:
Felix Ableitner 2014-08-18 11:30:03 +02:00
parent c3b027e8b5
commit b1749ce7cb
49 changed files with 3611 additions and 3668 deletions

View file

@ -41,6 +41,6 @@ screenshots: *link to file* (only for UI problems)
Always welcome. Always welcome.
Code should follow the [Android Code Style Guidelines](https://source.android.com/source/code-style.html#java-language-rules), with the exception that we use tabs, not spaces for indentation. Code should follow the [Android Code Style Guidelines](https://source.android.com/source/code-style.html#java-language-rules). This can be done automatically in Android Studio.
Lint warnings should be fixed. If that's not possible, they should be ignored as specifically as possible. Lint warnings should be fixed. If that's not possible, they should be ignored as specifically as possible.

View file

@ -1,12 +1,12 @@
buildscript { buildscript {
repositories { repositories {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:0.12.+' classpath 'com.android.tools.build:gradle:0.12.+'
classpath 'com.alexvasilkov:android_sign:0.2+' classpath 'com.alexvasilkov:android_sign:0.2+'
} }
} }
import java.util.regex.Pattern import java.util.regex.Pattern
@ -15,126 +15,126 @@ apply plugin: 'com.android.application'
apply plugin: 'android_sign' apply plugin: 'android_sign'
repositories { repositories {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
compile 'com.android.support:appcompat-v7:19.1.+' compile 'com.android.support:appcompat-v7:19.1.+'
compile project(':android-support-v4-preferencefragment') compile project(':android-support-v4-preferencefragment')
} }
preBuild { preBuild {
dependsOn 'buildNative' dependsOn 'buildNative'
} }
android { android {
compileSdkVersion 19 compileSdkVersion 19
buildToolsVersion "19.1.0" buildToolsVersion "19.1.0"
defaultConfig { defaultConfig {
versionCode getVersionCodeFromManifest() versionCode getVersionCodeFromManifest()
} }
sourceSets { sourceSets {
main { main {
jniLibs.srcDir file("libs/") jniLibs.srcDir file("libs/")
} }
} }
signingConfigs { signingConfigs {
release { release {
// Android Studio does not pass environment variables. // Android Studio does not pass environment variables.
// This means you have to use the command line for release builds. // This means you have to use the command line for release builds.
def ks = System.getenv("KEYSTORE") def ks = System.getenv("KEYSTORE")
def ka = System.getenv("KEY_ALIAS") def ka = System.getenv("KEY_ALIAS")
if (ks != null && ka != null) { if (ks != null && ka != null) {
storeFile file(ks) storeFile file(ks)
keyAlias ka keyAlias ka
} }
} }
} }
buildTypes { buildTypes {
debug { debug {
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
debuggable true debuggable true
} }
release { release {
signingConfig signingConfigs.release signingConfig signingConfigs.release
} }
} }
productFlavors { productFlavors {
x86 { x86 {
versionCode Integer.parseInt("4" + defaultConfig.versionCode) versionCode Integer.parseInt("4" + defaultConfig.versionCode)
ndk { ndk {
abiFilter "x86" abiFilter "x86"
} }
} }
armeabi_v7a { armeabi_v7a {
versionCode Integer.parseInt("3" + defaultConfig.versionCode) versionCode Integer.parseInt("3" + defaultConfig.versionCode)
ndk { ndk {
abiFilter "armeabi-v7a" abiFilter "armeabi-v7a"
} }
} }
armeabi { armeabi {
versionCode Integer.parseInt("2" + defaultConfig.versionCode) versionCode Integer.parseInt("2" + defaultConfig.versionCode)
ndk { ndk {
abiFilter "armeabi" abiFilter "armeabi"
} }
} }
mips { mips {
versionCode Integer.parseInt("1" + defaultConfig.versionCode) versionCode Integer.parseInt("1" + defaultConfig.versionCode)
ndk { ndk {
abiFilter "mips" abiFilter "mips"
} }
} }
fat { fat {
versionCode Integer.parseInt("0" + defaultConfig.versionCode) versionCode Integer.parseInt("0" + defaultConfig.versionCode)
} }
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7 sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7
} }
} }
def getVersionCodeFromManifest() { def getVersionCodeFromManifest() {
def manifestFile = file(android.sourceSets.main.manifest.srcFile) def manifestFile = file(android.sourceSets.main.manifest.srcFile)
def pattern = Pattern.compile("versionCode=\"(\\d+)\"") def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
def matcher = pattern.matcher(manifestFile.getText()) def matcher = pattern.matcher(manifestFile.getText())
matcher.find() matcher.find()
return Integer.parseInt(matcher.group(1)) return Integer.parseInt(matcher.group(1))
} }
task buildNative(type: Exec) { task buildNative(type: Exec) {
outputs.upToDateWhen { false } outputs.upToDateWhen { false }
executable = './build-syncthing.sh' executable = './build-syncthing.sh'
} }
task copyNative(type: Copy) { task copyNative(type: Copy) {
def lib_dir = "libs/" def lib_dir = "libs/"
new File(lib_dir).mkdirs() new File(lib_dir).mkdirs()
def st_dir = "bin/"; def st_dir = "bin/";
from st_dir + 'syncthing-x86', st_dir + 'syncthing-armeabi', st_dir + 'syncthing-armeabi-v7a'; from st_dir + 'syncthing-x86', st_dir + 'syncthing-armeabi', st_dir + 'syncthing-armeabi-v7a';
into lib_dir into lib_dir
rename('syncthing-x86', 'x86/libsyncthing.so') rename('syncthing-x86', 'x86/libsyncthing.so')
rename('syncthing-armeabi-v7a', 'armeabi-v7a/libsyncthing.so') rename('syncthing-armeabi-v7a', 'armeabi-v7a/libsyncthing.so')
rename('syncthing-armeabi', 'armeabi/libsyncthing.so') rename('syncthing-armeabi', 'armeabi/libsyncthing.so')
} }
buildNative.finalizedBy copyNative buildNative.finalizedBy copyNative
task cleanBin(type: Delete) { task cleanBin(type: Delete) {
delete 'bin/' delete 'bin/'
} }
copyNative.finalizedBy cleanBin copyNative.finalizedBy cleanBin
task cleanNative(type: Delete) { task cleanNative(type: Delete) {
delete 'bin/' delete 'bin/'
delete 'build/' delete 'build/'
delete 'libs/' delete 'libs/'
delete 'ext/syncthing/bin/' delete 'ext/syncthing/bin/'
delete 'ext/syncthing/pkg/' delete 'ext/syncthing/pkg/'
} }
clean.dependsOn cleanNative clean.dependsOn cleanNative

View file

@ -1,75 +1,75 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.nutomic.syncthingandroid" package="com.nutomic.syncthingandroid"
android:versionCode="33" android:versionCode="33"
android:versionName="0.4.19" android:versionName="0.4.19"
tools:ignore="GradleOverrides" > tools:ignore="GradleOverrides" >
<uses-sdk <uses-sdk
android:minSdkVersion="8" android:minSdkVersion="8"
android:targetSdkVersion="19" /> android:targetSdkVersion="19" />
<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"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application <application
android:allowBackup="false" android:allowBackup="false"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme" > android:theme="@style/AppTheme">
<activity <activity
android:name=".activities.MainActivity" android:name=".activities.MainActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop" > android:launchMode="singleTop">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".activities.WebGuiActivity" android:name=".activities.WebGuiActivity"
android:label="@string/web_gui_title" > android:label="@string/web_gui_title">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".gui.MainActivity" /> android:value=".gui.MainActivity" />
</activity> </activity>
<activity <activity android:name=".activities.SettingsActivity">
android:name=".activities.SettingsActivity" > <meta-data
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:name="android.support.PARENT_ACTIVITY" android:value=".gui.MainActivity" />
android:value=".gui.MainActivity" /> </activity>
</activity>
<service android:name=".syncthing.SyncthingService" /> <service android:name=".syncthing.SyncthingService" />
<activity <activity
android:name=".activities.FolderPickerActivity" android:name=".activities.FolderPickerActivity"
android:label="@string/folder_picker_title" > android:label="@string/folder_picker_title">
<meta-data <meta-data
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> <receiver android:name=".syncthing.NetworkReceiver">
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> <intent-filter>
</intent-filter> <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</receiver> </intent-filter>
<receiver android:name=".syncthing.BatteryReceiver" > </receiver>
<intent-filter> <receiver android:name=".syncthing.BatteryReceiver">
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/> <intent-filter>
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/> <action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
</intent-filter> <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
</receiver> </intent-filter>
</receiver>
<receiver android:name=".syncthing.BootReceiver"> <receiver android:name=".syncthing.BootReceiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter> </intent-filter>
</receiver> </receiver>
</application> </application>
</manifest> </manifest>

View file

@ -32,42 +32,42 @@ import java.util.Arrays;
* Activity that allows selecting a directory in the local file system. * Activity that allows selecting a directory in the local file system.
*/ */
public class FolderPickerActivity extends SyncthingActivity public class FolderPickerActivity extends SyncthingActivity
implements AdapterView.OnItemClickListener, SyncthingService.OnApiChangeListener { implements AdapterView.OnItemClickListener, SyncthingService.OnApiChangeListener {
private static final String TAG = "FolderPickerActivity"; private static final String TAG = "FolderPickerActivity";
public static final String EXTRA_INITIAL_DIRECTORY = "initial_directory"; public static final String EXTRA_INITIAL_DIRECTORY = "initial_directory";
public static final String EXTRA_RESULT_DIRECTORY = "result_directory"; public static final String EXTRA_RESULT_DIRECTORY = "result_directory";
private ListView mListView; private ListView mListView;
private FileAdapter mAdapter; private FileAdapter mAdapter;
private File mLocation; private File mLocation;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.folder_picker_activity); setContentView(R.layout.folder_picker_activity);
mListView = (ListView) findViewById(android.R.id.list); mListView = (ListView) findViewById(android.R.id.list);
mListView.setOnItemClickListener(this); mListView.setOnItemClickListener(this);
mListView.setEmptyView(findViewById(android.R.id.empty)); mListView.setEmptyView(findViewById(android.R.id.empty));
mAdapter = new FileAdapter(this); mAdapter = new FileAdapter(this);
mListView.setAdapter(mAdapter); mListView.setAdapter(mAdapter);
mLocation = new File(getIntent().getStringExtra(EXTRA_INITIAL_DIRECTORY)); mLocation = new File(getIntent().getStringExtra(EXTRA_INITIAL_DIRECTORY));
refresh(); refresh();
} }
@Override @Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) { public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
super.onServiceConnected(componentName, iBinder); super.onServiceConnected(componentName, iBinder);
getService().registerOnApiChangeListener(this); getService().registerOnApiChangeListener(this);
} }
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
@ -78,116 +78,116 @@ public class FolderPickerActivity extends SyncthingActivity
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.create_folder: case R.id.create_folder:
final EditText et = new EditText(this); final EditText et = new EditText(this);
AlertDialog dialog = new AlertDialog.Builder(this) AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle(R.string.create_folder) .setTitle(R.string.create_folder)
.setView(et) .setView(et)
.setPositiveButton(android.R.string.ok, .setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialogInterface, int i) { public void onClick(DialogInterface dialogInterface, int i) {
createFolder(et.getText().toString()); createFolder(et.getText().toString());
} }
}) }
.setNegativeButton(android.R.string.cancel, null) )
.create(); .setNegativeButton(android.R.string.cancel, null)
dialog.setOnShowListener(new DialogInterface.OnShowListener() { .create();
@Override dialog.setOnShowListener(new DialogInterface.OnShowListener() {
public void onShow(DialogInterface dialogInterface) { @Override
((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)) public void onShow(DialogInterface dialogInterface) {
.showSoftInput(et, InputMethodManager.SHOW_IMPLICIT); ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE))
} .showSoftInput(et, InputMethodManager.SHOW_IMPLICIT);
}); }
dialog.show(); });
return true; dialog.show();
case R.id.select: return true;
Intent intent = new Intent() case R.id.select:
.putExtra(EXTRA_RESULT_DIRECTORY, mLocation.getAbsolutePath()); Intent intent = new Intent()
setResult(Activity.RESULT_OK, intent); .putExtra(EXTRA_RESULT_DIRECTORY, mLocation.getAbsolutePath());
finish(); setResult(Activity.RESULT_OK, intent);
return true; finish();
case android.R.id.home: return true;
finish(); case android.R.id.home:
return true; finish();
default: return true;
return super.onOptionsItemSelected(item); default:
} return super.onOptionsItemSelected(item);
}
} }
/** /**
* Creates a new folder with the given name and enters it. * Creates a new folder with the given name and enters it.
*/ */
private void createFolder(String name) { private void createFolder(String name) {
File newFolder = new File(mLocation, name); File newFolder = new File(mLocation, name);
newFolder.mkdir(); newFolder.mkdir();
mLocation = newFolder; mLocation = newFolder;
refresh(); refresh();
} }
/** /**
* Refreshes the ListView to show the contents of the folder in {@code }mLocation.peek()}. * Refreshes the ListView to show the contents of the folder in {@code }mLocation.peek()}.
*/ */
private void refresh() { private void refresh() {
mAdapter.clear(); mAdapter.clear();
File[] contents = mLocation.listFiles(new FileFilter() { File[] contents = mLocation.listFiles(new FileFilter() {
@Override @Override
public boolean accept(File file) { public boolean accept(File file) {
return file.isDirectory(); return file.isDirectory();
} }
}); });
Arrays.sort(contents); Arrays.sort(contents);
for (File f : contents) { for (File f : contents) {
mAdapter.add(f); mAdapter.add(f);
} }
} }
@Override @Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
mLocation = mAdapter.getItem(i); mLocation = mAdapter.getItem(i);
refresh(); refresh();
} }
private class FileAdapter extends ArrayAdapter<File> { private class FileAdapter extends ArrayAdapter<File> {
public FileAdapter(Context context) { public FileAdapter(Context context) {
super(context, android.R.layout.simple_list_item_1); super(context, android.R.layout.simple_list_item_1);
} }
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) { if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) getContext() LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE); .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false); convertView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
} }
TextView title = (TextView) convertView.findViewById(android.R.id.text1); TextView title = (TextView) convertView.findViewById(android.R.id.text1);
title.setText(getItem(position).getName()); title.setText(getItem(position).getName());
return convertView; return convertView;
} }
} }
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (!mLocation.equals(Environment.getExternalStorageDirectory())) { if (!mLocation.equals(Environment.getExternalStorageDirectory())) {
mLocation = mLocation.getParentFile(); mLocation = mLocation.getParentFile();
refresh(); refresh();
} } else {
else { setResult(Activity.RESULT_CANCELED);
setResult(Activity.RESULT_CANCELED); finish();
finish(); }
} }
}
@Override @Override
public void onApiChange(SyncthingService.State currentState) { public void onApiChange(SyncthingService.State currentState) {
if (currentState != SyncthingService.State.ACTIVE) { if (currentState != SyncthingService.State.ACTIVE) {
setResult(Activity.RESULT_CANCELED); setResult(Activity.RESULT_CANCELED);
SyncthingService.showDisabledDialog(this); SyncthingService.showDisabledDialog(this);
finish(); finish();
} }
} }
} }

View file

@ -37,253 +37,254 @@ import com.nutomic.syncthingandroid.syncthing.SyncthingService;
* {@link com.nutomic.syncthingandroid.fragments.LocalNodeInfoFragment} in the navigation drawer. * {@link com.nutomic.syncthingandroid.fragments.LocalNodeInfoFragment} in the navigation drawer.
*/ */
public class MainActivity extends SyncthingActivity public class MainActivity extends SyncthingActivity
implements SyncthingService.OnApiChangeListener { implements SyncthingService.OnApiChangeListener {
private AlertDialog mLoadingDialog; private AlertDialog mLoadingDialog;
/** /**
* Causes population of repo and node lists, unlocks info drawer. * Causes population of repo and node lists, unlocks info drawer.
*/ */
@Override @Override
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
public void onApiChange(SyncthingService.State currentState) { public void onApiChange(SyncthingService.State currentState) {
if (currentState != SyncthingService.State.ACTIVE && !isFinishing()) { if (currentState != SyncthingService.State.ACTIVE && !isFinishing()) {
if (currentState == SyncthingService.State.DISABLED) { if (currentState == SyncthingService.State.DISABLED) {
if (mLoadingDialog != null) { if (mLoadingDialog != null) {
mLoadingDialog.dismiss(); mLoadingDialog.dismiss();
} }
SyncthingService.showDisabledDialog(this); SyncthingService.showDisabledDialog(this);
} } else if (mLoadingDialog == null) {
else if (mLoadingDialog == null) { final SharedPreferences prefs =
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
LayoutInflater inflater = getLayoutInflater(); LayoutInflater inflater = getLayoutInflater();
View dialogLayout = inflater.inflate(R.layout.loading_dialog, null); View dialogLayout = inflater.inflate(R.layout.loading_dialog, null);
TextView loadingText = (TextView) dialogLayout.findViewById(R.id.loading_text); TextView loadingText = (TextView) dialogLayout.findViewById(R.id.loading_text);
loadingText.setText((getService().isFirstStart()) loadingText.setText((getService().isFirstStart())
? R.string.web_gui_creating_key ? R.string.web_gui_creating_key
: R.string.api_loading); : R.string.api_loading);
mLoadingDialog = new AlertDialog.Builder(this) mLoadingDialog = new AlertDialog.Builder(this)
.setCancelable(false) .setCancelable(false)
.setView(dialogLayout) .setView(dialogLayout)
.show(); .show();
// Make sure the first start dialog is shown on top. // Make sure the first start dialog is shown on top.
if (prefs.getBoolean("first_start", true)) { if (prefs.getBoolean("first_start", true)) {
new AlertDialog.Builder(this) new AlertDialog.Builder(this)
.setTitle(R.string.welcome_title) .setTitle(R.string.welcome_title)
.setMessage(R.string.welcome_text) .setMessage(R.string.welcome_text)
.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() { .setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialogInterface, int i) { public void onClick(DialogInterface dialogInterface, int i) {
prefs.edit().putBoolean("first_start", false).commit(); prefs.edit().putBoolean("first_start", false).commit();
} }
}) })
.show(); .show();
} }
} }
return; return;
} }
if (mLoadingDialog != null) { if (mLoadingDialog != null) {
mLoadingDialog.dismiss(); mLoadingDialog.dismiss();
} }
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
mDrawerLayout.setDrawerListener(mDrawerToggle); mDrawerLayout.setDrawerListener(mDrawerToggle);
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true); getSupportActionBar().setHomeButtonEnabled(true);
} }
private final FragmentPagerAdapter mSectionsPagerAdapter = private final FragmentPagerAdapter mSectionsPagerAdapter =
new FragmentPagerAdapter(getSupportFragmentManager()) { new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override @Override
public Fragment getItem(int position) { public Fragment getItem(int position) {
switch (position) { switch (position) {
case 0: return mRepositoriesFragment; case 0:
case 1: return mNodesFragment; return mRepositoriesFragment;
default: return null; case 1:
} return mNodesFragment;
} default:
return null;
}
}
@Override @Override
public int getCount() { public int getCount() {
return 2; return 2;
} }
}; };
private ReposFragment mRepositoriesFragment; private ReposFragment mRepositoriesFragment;
private NodesFragment mNodesFragment; private NodesFragment mNodesFragment;
private LocalNodeInfoFragment mLocalNodeInfoFragment; private LocalNodeInfoFragment mLocalNodeInfoFragment;
private ViewPager mViewPager; private ViewPager mViewPager;
private ActionBarDrawerToggle mDrawerToggle; private ActionBarDrawerToggle mDrawerToggle;
private DrawerLayout mDrawerLayout; private DrawerLayout mDrawerLayout;
/** /**
* Initializes tab navigation. * Initializes tab navigation.
*/ */
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
final ActionBar actionBar = getSupportActionBar(); final ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
setContentView(R.layout.main_activity); setContentView(R.layout.main_activity);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mViewPager = (ViewPager) findViewById(R.id.pager); mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter); mViewPager.setAdapter(mSectionsPagerAdapter);
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override @Override
public void onPageSelected(int position) { public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position); actionBar.setSelectedNavigationItem(position);
} }
}); });
TabListener tabListener = new TabListener() { TabListener tabListener = new TabListener() {
public void onTabSelected(Tab tab, FragmentTransaction ft) { public void onTabSelected(Tab tab, FragmentTransaction ft) {
mViewPager.setCurrentItem(tab.getPosition()); mViewPager.setCurrentItem(tab.getPosition());
} }
@Override @Override
public void onTabReselected(Tab tab, FragmentTransaction ft) { public void onTabReselected(Tab tab, FragmentTransaction ft) {
} }
@Override @Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) { public void onTabUnselected(Tab tab, FragmentTransaction ft) {
} }
}; };
actionBar.addTab(actionBar.newTab() actionBar.addTab(actionBar.newTab()
.setText(R.string.repositories_fragment_title) .setText(R.string.repositories_fragment_title)
.setTabListener(tabListener)); .setTabListener(tabListener));
actionBar.addTab(actionBar.newTab() actionBar.addTab(actionBar.newTab()
.setText(R.string.nodes_fragment_title) .setText(R.string.nodes_fragment_title)
.setTabListener(tabListener)); .setTabListener(tabListener));
if (savedInstanceState != null) { if (savedInstanceState != null) {
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
mRepositoriesFragment = (ReposFragment) fm.getFragment( mRepositoriesFragment = (ReposFragment) fm.getFragment(
savedInstanceState, ReposFragment.class.getName()); savedInstanceState, ReposFragment.class.getName());
mNodesFragment = (NodesFragment) fm.getFragment( mNodesFragment = (NodesFragment) fm.getFragment(
savedInstanceState, NodesFragment.class.getName()); savedInstanceState, NodesFragment.class.getName());
mLocalNodeInfoFragment = (LocalNodeInfoFragment) fm.getFragment( mLocalNodeInfoFragment = (LocalNodeInfoFragment) fm.getFragment(
savedInstanceState, LocalNodeInfoFragment.class.getName()); savedInstanceState, LocalNodeInfoFragment.class.getName());
mViewPager.setCurrentItem(savedInstanceState.getInt("currentTab")); mViewPager.setCurrentItem(savedInstanceState.getInt("currentTab"));
} } else {
else { mRepositoriesFragment = new ReposFragment();
mRepositoriesFragment = new ReposFragment(); mNodesFragment = new NodesFragment();
mNodesFragment = new NodesFragment(); mLocalNodeInfoFragment = new LocalNodeInfoFragment();
mLocalNodeInfoFragment = new LocalNodeInfoFragment(); }
}
getSupportFragmentManager() getSupportFragmentManager()
.beginTransaction() .beginTransaction()
.replace(R.id.drawer, mLocalNodeInfoFragment) .replace(R.id.drawer, mLocalNodeInfoFragment)
.commit(); .commit();
mDrawerToggle = mLocalNodeInfoFragment.new Toggle(this, mDrawerLayout, mDrawerToggle = mLocalNodeInfoFragment.new Toggle(this, mDrawerLayout,
R.drawable.ic_drawer); R.drawable.ic_drawer);
mDrawerLayout.setDrawerListener(mDrawerToggle); mDrawerLayout.setDrawerListener(mDrawerToggle);
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if (mLoadingDialog != null) { if (mLoadingDialog != null) {
mLoadingDialog.dismiss(); mLoadingDialog.dismiss();
} }
} }
@Override @Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) { public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
super.onServiceConnected(componentName, iBinder); super.onServiceConnected(componentName, iBinder);
getService().registerOnApiChangeListener(mRepositoriesFragment); getService().registerOnApiChangeListener(mRepositoriesFragment);
getService().registerOnApiChangeListener(mNodesFragment); getService().registerOnApiChangeListener(mNodesFragment);
} }
/** /**
* Saves fragment states. * Saves fragment states.
*/ */
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
// Avoid crash if called during startup. // Avoid crash if called during startup.
if (mRepositoriesFragment != null && mNodesFragment != null && if (mRepositoriesFragment != null && mNodesFragment != null &&
mLocalNodeInfoFragment != null) { mLocalNodeInfoFragment != null) {
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
fm.putFragment(outState, ReposFragment.class.getName(), mRepositoriesFragment); fm.putFragment(outState, ReposFragment.class.getName(), mRepositoriesFragment);
fm.putFragment(outState, NodesFragment.class.getName(), mNodesFragment); fm.putFragment(outState, NodesFragment.class.getName(), mNodesFragment);
fm.putFragment(outState, LocalNodeInfoFragment.class.getName(), mLocalNodeInfoFragment); fm.putFragment(outState, LocalNodeInfoFragment.class.getName(), mLocalNodeInfoFragment);
outState.putInt("currentTab", mViewPager.getCurrentItem()); outState.putInt("currentTab", mViewPager.getCurrentItem());
} }
} }
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu); getMenuInflater().inflate(R.menu.main, menu);
return true; return true;
} }
/** /**
* Shows menu only once syncthing service is running, and shows "share" option only when * Shows menu only once syncthing service is running, and shows "share" option only when
* drawer is open. * drawer is open.
*/ */
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
boolean drawerOpen = mDrawerLayout.isDrawerOpen(findViewById(R.id.drawer)); boolean drawerOpen = mDrawerLayout.isDrawerOpen(findViewById(R.id.drawer));
menu.findItem(R.id.share_node_id).setVisible(drawerOpen); menu.findItem(R.id.share_node_id).setVisible(drawerOpen);
return true; return true;
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (mLocalNodeInfoFragment.onOptionsItemSelected(item) || if (mLocalNodeInfoFragment.onOptionsItemSelected(item) ||
mDrawerToggle.onOptionsItemSelected(item)) { mDrawerToggle.onOptionsItemSelected(item)) {
return true; return true;
} }
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.add_repo: case R.id.add_repo:
Intent intent = new Intent(this, SettingsActivity.class) Intent intent = new Intent(this, SettingsActivity.class)
.setAction(SettingsActivity.ACTION_REPO_SETTINGS_FRAGMENT) .setAction(SettingsActivity.ACTION_REPO_SETTINGS_FRAGMENT)
.putExtra(SettingsActivity.EXTRA_IS_CREATE, true); .putExtra(SettingsActivity.EXTRA_IS_CREATE, true);
startActivity(intent); startActivity(intent);
return true; return true;
case R.id.add_node: case R.id.add_node:
intent = new Intent(this, SettingsActivity.class) intent = new Intent(this, SettingsActivity.class)
.setAction(SettingsActivity.ACTION_NODE_SETTINGS_FRAGMENT) .setAction(SettingsActivity.ACTION_NODE_SETTINGS_FRAGMENT)
.putExtra(SettingsActivity.EXTRA_IS_CREATE, true); .putExtra(SettingsActivity.EXTRA_IS_CREATE, true);
startActivity(intent); startActivity(intent);
return true; return true;
case R.id.web_gui: case R.id.web_gui:
startActivity(new Intent(this, WebGuiActivity.class)); startActivity(new Intent(this, WebGuiActivity.class));
return true; return true;
case R.id.settings: case R.id.settings:
startActivity(new Intent(this, SettingsActivity.class) startActivity(new Intent(this, SettingsActivity.class)
.setAction(SettingsActivity.ACTION_APP_SETTINGS_FRAGMENT)); .setAction(SettingsActivity.ACTION_APP_SETTINGS_FRAGMENT));
return true; return true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
} }
@Override @Override
protected void onPostCreate(Bundle savedInstanceState) { protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState); super.onPostCreate(savedInstanceState);
mDrawerToggle.syncState(); mDrawerToggle.syncState();
} }
@Override @Override
public void onConfigurationChanged(Configuration newConfig) { public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
mDrawerToggle.onConfigurationChanged(newConfig); mDrawerToggle.onConfigurationChanged(newConfig);
} }
} }

View file

@ -14,75 +14,74 @@ import com.nutomic.syncthingandroid.fragments.SettingsFragment;
*/ */
public class SettingsActivity extends SyncthingActivity { public class SettingsActivity extends SyncthingActivity {
public static final String ACTION_APP_SETTINGS_FRAGMENT = "app_settings_fragment"; public static final String ACTION_APP_SETTINGS_FRAGMENT = "app_settings_fragment";
public static final String ACTION_NODE_SETTINGS_FRAGMENT = "node_settings_fragment"; public static final String ACTION_NODE_SETTINGS_FRAGMENT = "node_settings_fragment";
public static final String ACTION_REPO_SETTINGS_FRAGMENT = "repo_settings_fragment"; public static final String ACTION_REPO_SETTINGS_FRAGMENT = "repo_settings_fragment";
/** /**
* Must be set for {@link #ACTION_NODE_SETTINGS_FRAGMENT} and * Must be set for {@link #ACTION_NODE_SETTINGS_FRAGMENT} and
* {@link #ACTION_REPO_SETTINGS_FRAGMENT} to determine if an existing repo/node should be * {@link #ACTION_REPO_SETTINGS_FRAGMENT} to determine if an existing repo/node should be
* edited or a new one created. * edited or a new one created.
* * <p/>
* If this is false, {@link com.nutomic.syncthingandroid.fragments.RepoSettingsFragment#EXTRA_REPO_ID} or * If this is false, {@link com.nutomic.syncthingandroid.fragments.RepoSettingsFragment#EXTRA_REPO_ID} or
* {@link com.nutomic.syncthingandroid.fragments.NodeSettingsFragment#EXTRA_NODE_ID} must be set (according to the selected fragment). * {@link com.nutomic.syncthingandroid.fragments.NodeSettingsFragment#EXTRA_NODE_ID} must be set (according to the selected fragment).
*/ */
public static final String EXTRA_IS_CREATE = "create"; public static final String EXTRA_IS_CREATE = "create";
private Fragment mFragment; private Fragment mFragment;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
if (savedInstanceState != null) { if (savedInstanceState != null) {
mFragment = fm.getFragment(savedInstanceState, mFragment = fm.getFragment(savedInstanceState,
savedInstanceState.getString("fragment_name")); savedInstanceState.getString("fragment_name"));
} } else {
else { switch (getIntent().getAction()) {
switch (getIntent().getAction()) { case ACTION_APP_SETTINGS_FRAGMENT:
case ACTION_APP_SETTINGS_FRAGMENT: setTitle(R.string.settings_title);
setTitle(R.string.settings_title); mFragment = new SettingsFragment();
mFragment = new SettingsFragment(); break;
break; case ACTION_NODE_SETTINGS_FRAGMENT:
case ACTION_NODE_SETTINGS_FRAGMENT: mFragment = new NodeSettingsFragment();
mFragment = new NodeSettingsFragment(); if (!getIntent().hasExtra(EXTRA_IS_CREATE)) {
if (!getIntent().hasExtra(EXTRA_IS_CREATE)) { throw new IllegalArgumentException("EXTRA_IS_CREATE must be set");
throw new IllegalArgumentException("EXTRA_IS_CREATE must be set"); }
} break;
break; case ACTION_REPO_SETTINGS_FRAGMENT:
case ACTION_REPO_SETTINGS_FRAGMENT: mFragment = new RepoSettingsFragment();
mFragment = new RepoSettingsFragment(); if (!getIntent().hasExtra(EXTRA_IS_CREATE)) {
if (!getIntent().hasExtra(EXTRA_IS_CREATE)) { throw new IllegalArgumentException("EXTRA_IS_CREATE must be set");
throw new IllegalArgumentException("EXTRA_IS_CREATE must be set"); }
} break;
break; default:
default: throw new IllegalArgumentException(
throw new IllegalArgumentException( "You must provide the requested fragment type as an extra.");
"You must provide the requested fragment type as an extra."); }
} }
}
fm.beginTransaction() fm.beginTransaction()
.replace(android.R.id.content, mFragment) .replace(android.R.id.content, mFragment)
.commit(); .commit();
} }
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
String fragmentClassName = mFragment.getClass().getName(); String fragmentClassName = mFragment.getClass().getName();
outState.putString("fragment_name", fragmentClassName); outState.putString("fragment_name", fragmentClassName);
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
fm.putFragment(outState, fragmentClassName, mFragment); fm.putFragment(outState, fragmentClassName, mFragment);
} }
public boolean getIsCreate() { public boolean getIsCreate() {
return getIntent().getBooleanExtra(EXTRA_IS_CREATE, false); return getIntent().getBooleanExtra(EXTRA_IS_CREATE, false);
} }
} }

View file

@ -19,71 +19,70 @@ import java.util.LinkedList;
*/ */
public class SyncthingActivity extends ActionBarActivity implements ServiceConnection { public class SyncthingActivity extends ActionBarActivity implements ServiceConnection {
private SyncthingService mSyncthingService; private SyncthingService mSyncthingService;
private LinkedList<OnServiceConnectedListener> mServiceConnectedListeners = new LinkedList<>(); private LinkedList<OnServiceConnectedListener> mServiceConnectedListeners = new LinkedList<>();
/** /**
* To be used for Fragments. * To be used for Fragments.
*/ */
public interface OnServiceConnectedListener { public interface OnServiceConnectedListener {
public void onServiceConnected(); public void onServiceConnected();
} }
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
bindService(new Intent(this, SyncthingService.class), bindService(new Intent(this, SyncthingService.class),
this, Context.BIND_AUTO_CREATE); this, Context.BIND_AUTO_CREATE);
} }
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
unbindService(this); unbindService(this);
} }
@Override @Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) { public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
SyncthingServiceBinder binder = (SyncthingServiceBinder) iBinder; SyncthingServiceBinder binder = (SyncthingServiceBinder) iBinder;
mSyncthingService = binder.getService(); mSyncthingService = binder.getService();
for (OnServiceConnectedListener listener : mServiceConnectedListeners) { for (OnServiceConnectedListener listener : mServiceConnectedListeners) {
listener.onServiceConnected(); listener.onServiceConnected();
} }
mServiceConnectedListeners.clear(); mServiceConnectedListeners.clear();
} }
@Override @Override
public void onServiceDisconnected(ComponentName componentName) { public void onServiceDisconnected(ComponentName componentName) {
mSyncthingService = null; mSyncthingService = null;
} }
/** /**
* Used for Fragments to use the Activity's service connection. * Used for Fragments to use the Activity's service connection.
*/ */
public void registerOnServiceConnectedListener(OnServiceConnectedListener listener) { public void registerOnServiceConnectedListener(OnServiceConnectedListener listener) {
if (mSyncthingService != null) { if (mSyncthingService != null) {
listener.onServiceConnected(); listener.onServiceConnected();
} } else {
else { mServiceConnectedListeners.addLast(listener);
mServiceConnectedListeners.addLast(listener); }
} }
}
/** /**
* Returns service object (or null if not bound). * Returns service object (or null if not bound).
*/ */
public SyncthingService getService() { public SyncthingService getService() {
return mSyncthingService; return mSyncthingService;
} }
/** /**
* Returns RestApi instance, or null if SyncthingService is not yet connected. * Returns RestApi instance, or null if SyncthingService is not yet connected.
*/ */
public RestApi getApi() { public RestApi getApi() {
return (getService() != null) return (getService() != null)
? getService().getApi() ? getService().getApi()
: null; : null;
} }
} }

View file

@ -17,56 +17,56 @@ import com.nutomic.syncthingandroid.syncthing.SyncthingService;
*/ */
public class WebGuiActivity extends SyncthingActivity implements SyncthingService.OnWebGuiAvailableListener { public class WebGuiActivity extends SyncthingActivity implements SyncthingService.OnWebGuiAvailableListener {
private WebView mWebView; private WebView mWebView;
private View mLoadingView; private View mLoadingView;
/** /**
* Hides the loading screen and shows the WebView once it is fully loaded. * Hides the loading screen and shows the WebView once it is fully loaded.
*/ */
private final WebViewClient mWebViewClient = new WebViewClient() { private final WebViewClient mWebViewClient = new WebViewClient() {
@Override @Override
public void onPageFinished(WebView view, String url) { public void onPageFinished(WebView view, String url) {
mWebView.setVisibility(View.VISIBLE); mWebView.setVisibility(View.VISIBLE);
mLoadingView.setVisibility(View.GONE); mLoadingView.setVisibility(View.GONE);
} }
}; };
/** /**
* Initialize WebView. * Initialize WebView.
* * <p/>
* Ignore lint javascript warning as js is loaded only from our known, local service. * Ignore lint javascript warning as js is loaded only from our known, local service.
*/ */
@Override @Override
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.web_gui_activity); setContentView(R.layout.web_gui_activity);
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mLoadingView = findViewById(R.id.loading); mLoadingView = findViewById(R.id.loading);
ProgressBar pb = (ProgressBar) mLoadingView.findViewById(R.id.progress); ProgressBar pb = (ProgressBar) mLoadingView.findViewById(R.id.progress);
pb.setIndeterminate(true); pb.setIndeterminate(true);
mWebView = (WebView) findViewById(R.id.webview); mWebView = (WebView) findViewById(R.id.webview);
mWebView.getSettings().setJavaScriptEnabled(true); mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setWebViewClient(mWebViewClient); mWebView.setWebViewClient(mWebViewClient);
} }
@Override @Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) { public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
super.onServiceConnected(componentName, iBinder); super.onServiceConnected(componentName, iBinder);
getService().registerOnWebGuiAvailableListener(WebGuiActivity.this); getService().registerOnWebGuiAvailableListener(WebGuiActivity.this);
} }
/** /**
* Loads and shows WebView, hides loading view. * Loads and shows WebView, hides loading view.
*/ */
@Override @Override
public void onWebGuiAvailable() { public void onWebGuiAvailable() {
mWebView.loadUrl(getApi().getUrl()); mWebView.loadUrl(getApi().getUrl());
} }
} }

View file

@ -26,152 +26,153 @@ import java.util.TimerTask;
* Displays information about the local node. * Displays information about the local node.
*/ */
public class LocalNodeInfoFragment extends Fragment public class LocalNodeInfoFragment extends Fragment
implements RestApi.OnReceiveSystemInfoListener, RestApi.OnReceiveConnectionsListener { implements RestApi.OnReceiveSystemInfoListener, RestApi.OnReceiveConnectionsListener {
private TextView mNodeId; private TextView mNodeId;
private TextView mCpuUsage; private TextView mCpuUsage;
private TextView mRamUsage; private TextView mRamUsage;
private TextView mDownload; private TextView mDownload;
private TextView mUpload; private TextView mUpload;
private TextView mAnnounceServer; private TextView mAnnounceServer;
private Timer mTimer; private Timer mTimer;
private MainActivity mActivity; private MainActivity mActivity;
/** /**
* Starts polling for status when opened, stops when closed. * Starts polling for status when opened, stops when closed.
*/ */
public class Toggle extends ActionBarDrawerToggle { public class Toggle extends ActionBarDrawerToggle {
public Toggle(Activity activity, DrawerLayout drawerLayout, int drawerImageRes) { public Toggle(Activity activity, DrawerLayout drawerLayout, int drawerImageRes) {
super(activity, drawerLayout, drawerImageRes, R.string.app_name, R.string.system_info); super(activity, drawerLayout, drawerImageRes, R.string.app_name, R.string.system_info);
} }
@Override @Override
public void onDrawerClosed(View view) { public void onDrawerClosed(View view) {
super.onDrawerClosed(view); super.onDrawerClosed(view);
mTimer.cancel(); mTimer.cancel();
mTimer = null; mTimer = null;
mActivity.getSupportActionBar().setTitle(R.string.app_name); mActivity.getSupportActionBar().setTitle(R.string.app_name);
mActivity.supportInvalidateOptionsMenu(); mActivity.supportInvalidateOptionsMenu();
} }
@Override @Override
public void onDrawerOpened(View drawerView) { public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView); super.onDrawerOpened(drawerView);
LocalNodeInfoFragment.this.onDrawerOpened(); LocalNodeInfoFragment.this.onDrawerOpened();
} }
}; }
@Override ;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.local_node_info_fragment, container, false);
mNodeId = (TextView) view.findViewById(R.id.node_id);
mCpuUsage = (TextView) view.findViewById(R.id.cpu_usage);
mRamUsage = (TextView) view.findViewById(R.id.ram_usage);
mDownload = (TextView) view.findViewById(R.id.download);
mUpload = (TextView) view.findViewById(R.id.upload);
mAnnounceServer = (TextView) view.findViewById(R.id.announce_server);
return view; @Override
} public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.local_node_info_fragment, container, false);
mNodeId = (TextView) view.findViewById(R.id.node_id);
mCpuUsage = (TextView) view.findViewById(R.id.cpu_usage);
mRamUsage = (TextView) view.findViewById(R.id.ram_usage);
mDownload = (TextView) view.findViewById(R.id.download);
mUpload = (TextView) view.findViewById(R.id.upload);
mAnnounceServer = (TextView) view.findViewById(R.id.announce_server);
@Override return view;
public void onActivityCreated(Bundle savedInstanceState) { }
super.onActivityCreated(savedInstanceState);
mActivity = (MainActivity) getActivity();
if (savedInstanceState != null && savedInstanceState.getBoolean("active")) { @Override
onDrawerOpened(); public void onActivityCreated(Bundle savedInstanceState) {
} super.onActivityCreated(savedInstanceState);
} mActivity = (MainActivity) getActivity();
@Override if (savedInstanceState != null && savedInstanceState.getBoolean("active")) {
public void onSaveInstanceState(Bundle outState) { onDrawerOpened();
super.onSaveInstanceState(outState); }
outState.putBoolean("active", mTimer != null); }
}
private void onDrawerOpened() { @Override
// FIXME: never called public void onSaveInstanceState(Bundle outState) {
mTimer = new Timer(); super.onSaveInstanceState(outState);
mTimer.schedule(new TimerTask() { outState.putBoolean("active", mTimer != null);
@Override }
public void run() {
updateGui();
}
}, 0, SyncthingService.GUI_UPDATE_INTERVAL); private void onDrawerOpened() {
mActivity.getSupportActionBar().setTitle(R.string.system_info); // FIXME: never called
mActivity.supportInvalidateOptionsMenu(); mTimer = new Timer();
} mTimer.schedule(new TimerTask() {
@Override
public void run() {
updateGui();
}
/** }, 0, SyncthingService.GUI_UPDATE_INTERVAL);
* Invokes status callbacks. mActivity.getSupportActionBar().setTitle(R.string.system_info);
*/ mActivity.supportInvalidateOptionsMenu();
private void updateGui() { }
if (mActivity.getApi() != null) {
mActivity.getApi().getSystemInfo(this);
mActivity.getApi().getConnections(this);
}
}
/** /**
* Populates views with status received via {@link RestApi#getSystemInfo}. * Invokes status callbacks.
*/ */
@Override private void updateGui() {
public void onReceiveSystemInfo(RestApi.SystemInfo info) { if (mActivity.getApi() != null) {
if (getActivity() == null) mActivity.getApi().getSystemInfo(this);
return; mActivity.getApi().getConnections(this);
}
}
mNodeId.setText(info.myID); /**
mNodeId.setOnTouchListener(new View.OnTouchListener() { * Populates views with status received via {@link RestApi#getSystemInfo}.
@Override */
public boolean onTouch(View view, MotionEvent motionEvent) { @Override
mActivity.getApi().copyNodeId(mNodeId.getText().toString()); public void onReceiveSystemInfo(RestApi.SystemInfo info) {
view.performClick(); if (getActivity() == null)
return true; return;
}
});
mCpuUsage.setText(new DecimalFormat("0.00").format(info.cpuPercent) + "%");
mRamUsage.setText(RestApi.readableFileSize(mActivity, info.sys));
if (info.extAnnounceOK) {
mAnnounceServer.setText("Online");
mAnnounceServer.setTextColor(getResources().getColor(R.color.text_green));
}
else {
mAnnounceServer.setText("Offline");
mAnnounceServer.setTextColor(getResources().getColor(R.color.text_red));
}
}
/** mNodeId.setText(info.myID);
* Populates views with status received via {@link RestApi#getConnections}. mNodeId.setOnTouchListener(new View.OnTouchListener() {
*/ @Override
@Override public boolean onTouch(View view, MotionEvent motionEvent) {
public void onReceiveConnections(Map<String, RestApi.Connection> connections) { mActivity.getApi().copyNodeId(mNodeId.getText().toString());
RestApi.Connection c = connections.get(RestApi.LOCAL_NODE_CONNECTIONS); view.performClick();
mDownload.setText(RestApi.readableTransferRate(mActivity, c.InBits)); return true;
mUpload.setText(RestApi.readableTransferRate(mActivity, c.OutBits)); }
} });
mCpuUsage.setText(new DecimalFormat("0.00").format(info.cpuPercent) + "%");
mRamUsage.setText(RestApi.readableFileSize(mActivity, info.sys));
if (info.extAnnounceOK) {
mAnnounceServer.setText("Online");
mAnnounceServer.setTextColor(getResources().getColor(R.color.text_green));
} else {
mAnnounceServer.setText("Offline");
mAnnounceServer.setTextColor(getResources().getColor(R.color.text_red));
}
}
/** /**
* Shares the local node ID when "share" is clicked. * Populates views with status received via {@link RestApi#getConnections}.
*/ */
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public void onReceiveConnections(Map<String, RestApi.Connection> connections) {
switch (item.getItemId()) { RestApi.Connection c = connections.get(RestApi.LOCAL_NODE_CONNECTIONS);
case R.id.share_node_id: mDownload.setText(RestApi.readableTransferRate(mActivity, c.InBits));
RestApi.shareNodeId(getActivity(), mNodeId.getText().toString()); mUpload.setText(RestApi.readableTransferRate(mActivity, c.OutBits));
return true; }
default:
return super.onOptionsItemSelected(item); /**
} * Shares the local node ID when "share" is clicked.
} */
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.share_node_id:
RestApi.shareNodeId(getActivity(), mNodeId.getText().toString());
return true;
default:
return super.onOptionsItemSelected(item);
}
}
} }

View file

@ -1,6 +1,5 @@
package com.nutomic.syncthingandroid.fragments; package com.nutomic.syncthingandroid.fragments;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
@ -29,259 +28,253 @@ import java.util.Map;
* Shows node details and allows changing them. * Shows node details and allows changing them.
*/ */
public class NodeSettingsFragment extends PreferenceFragment implements public class NodeSettingsFragment extends PreferenceFragment implements
SyncthingActivity.OnServiceConnectedListener, Preference.OnPreferenceChangeListener, SyncthingActivity.OnServiceConnectedListener, Preference.OnPreferenceChangeListener,
Preference.OnPreferenceClickListener, RestApi.OnReceiveConnectionsListener, Preference.OnPreferenceClickListener, RestApi.OnReceiveConnectionsListener,
SyncthingService.OnApiChangeListener, RestApi.OnNodeIdNormalizedListener { SyncthingService.OnApiChangeListener, RestApi.OnNodeIdNormalizedListener {
public static final String EXTRA_NODE_ID = "node_id"; public static final String EXTRA_NODE_ID = "node_id";
private static final int SCAN_QR_REQUEST_CODE = 235; private static final int SCAN_QR_REQUEST_CODE = 235;
private SyncthingService mSyncthingService; private SyncthingService mSyncthingService;
private RestApi.Node mNode; private RestApi.Node mNode;
private Preference mNodeId; private Preference mNodeId;
private EditTextPreference mName; private EditTextPreference mName;
private EditTextPreference mAddresses; private EditTextPreference mAddresses;
private Preference mVersion; private Preference mVersion;
private Preference mCurrentAddress; private Preference mCurrentAddress;
private boolean mIsCreate; private boolean mIsCreate;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
((SyncthingActivity) getActivity()).registerOnServiceConnectedListener(this); ((SyncthingActivity) getActivity()).registerOnServiceConnectedListener(this);
mIsCreate = ((SettingsActivity) getActivity()).getIsCreate(); mIsCreate = ((SettingsActivity) getActivity()).getIsCreate();
setHasOptionsMenu(true); setHasOptionsMenu(true);
if (mIsCreate) { if (mIsCreate) {
addPreferencesFromResource(R.xml.node_settings_create); addPreferencesFromResource(R.xml.node_settings_create);
} } else {
else { addPreferencesFromResource(R.xml.node_settings_edit);
addPreferencesFromResource(R.xml.node_settings_edit); }
}
mNodeId = findPreference("node_id"); mNodeId = findPreference("node_id");
mNodeId.setOnPreferenceChangeListener(this); mNodeId.setOnPreferenceChangeListener(this);
mName = (EditTextPreference) findPreference("name"); mName = (EditTextPreference) findPreference("name");
mName.setOnPreferenceChangeListener(this); mName.setOnPreferenceChangeListener(this);
mAddresses = (EditTextPreference) findPreference("addresses"); mAddresses = (EditTextPreference) findPreference("addresses");
mAddresses.setOnPreferenceChangeListener(this); mAddresses.setOnPreferenceChangeListener(this);
if (!mIsCreate) { if (!mIsCreate) {
mVersion = findPreference("version"); mVersion = findPreference("version");
mVersion.setSummary("?"); mVersion.setSummary("?");
mCurrentAddress = findPreference("current_address"); mCurrentAddress = findPreference("current_address");
mCurrentAddress.setSummary("?"); mCurrentAddress.setSummary("?");
} }
} }
@Override @Override
public void onServiceConnected() { public void onServiceConnected() {
mSyncthingService = ((SyncthingActivity) getActivity()).getService(); mSyncthingService = ((SyncthingActivity) getActivity()).getService();
mSyncthingService.registerOnApiChangeListener(this); mSyncthingService.registerOnApiChangeListener(this);
} }
@Override @Override
public void onApiChange(SyncthingService.State currentState) { public void onApiChange(SyncthingService.State currentState) {
if (currentState != SyncthingService.State.ACTIVE) { if (currentState != SyncthingService.State.ACTIVE) {
getActivity().finish(); getActivity().finish();
return; return;
} }
if (getActivity() == null || getActivity().isFinishing()) if (getActivity() == null || getActivity().isFinishing())
return; return;
if (mIsCreate) { if (mIsCreate) {
getActivity().setTitle(R.string.add_node); getActivity().setTitle(R.string.add_node);
mNode = new RestApi.Node(); mNode = new RestApi.Node();
mNode.Name = ""; mNode.Name = "";
mNode.NodeID = ""; mNode.NodeID = "";
mNode.Addresses = "dynamic"; mNode.Addresses = "dynamic";
((EditTextPreference) mNodeId).setText(mNode.NodeID); ((EditTextPreference) mNodeId).setText(mNode.NodeID);
} } else {
else { getActivity().setTitle(R.string.edit_node);
getActivity().setTitle(R.string.edit_node); List<RestApi.Node> nodes = mSyncthingService.getApi().getNodes();
List<RestApi.Node> nodes = mSyncthingService.getApi().getNodes(); for (int i = 0; i < nodes.size(); i++) {
for (int i = 0; i < nodes.size(); i++) { if (nodes.get(i).NodeID.equals(
if (nodes.get(i).NodeID.equals( getActivity().getIntent().getStringExtra(EXTRA_NODE_ID))) {
getActivity().getIntent().getStringExtra(EXTRA_NODE_ID))) { mNode = nodes.get(i);
mNode = nodes.get(i); break;
break; }
} }
} mNodeId.setOnPreferenceClickListener(this);
mNodeId.setOnPreferenceClickListener(this); }
} mSyncthingService.getApi().getConnections(NodeSettingsFragment.this);
mSyncthingService.getApi().getConnections(NodeSettingsFragment.this);
mNodeId.setSummary(mNode.NodeID); mNodeId.setSummary(mNode.NodeID);
mName.setText((mNode.Name)); mName.setText((mNode.Name));
mName.setSummary(mNode.Name); mName.setSummary(mNode.Name);
mAddresses.setText(mNode.Addresses); mAddresses.setText(mNode.Addresses);
mAddresses.setSummary(mNode.Addresses); mAddresses.setSummary(mNode.Addresses);
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.node_settings, menu); inflater.inflate(R.menu.node_settings, menu);
} }
@Override @Override
public void onPrepareOptionsMenu(Menu menu) { public void onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.create).setVisible(mIsCreate); menu.findItem(R.id.create).setVisible(mIsCreate);
menu.findItem(R.id.share_node_id).setVisible(!mIsCreate); menu.findItem(R.id.share_node_id).setVisible(!mIsCreate);
menu.findItem(R.id.delete).setVisible(!mIsCreate); menu.findItem(R.id.delete).setVisible(!mIsCreate);
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.create: case R.id.create:
if (mNode.NodeID.equals("")) { if (mNode.NodeID.equals("")) {
Toast.makeText(getActivity(), R.string.node_id_required, Toast.LENGTH_LONG) Toast.makeText(getActivity(), R.string.node_id_required, Toast.LENGTH_LONG)
.show(); .show();
return true; return true;
} }
if (mNode.Name.equals("")) { if (mNode.Name.equals("")) {
Toast.makeText(getActivity(), R.string.node_name_required, Toast.LENGTH_LONG) Toast.makeText(getActivity(), R.string.node_name_required, Toast.LENGTH_LONG)
.show(); .show();
return true; return true;
} }
mSyncthingService.getApi().editNode(mNode, this); mSyncthingService.getApi().editNode(mNode, this);
return true; return true;
case R.id.share_node_id: case R.id.share_node_id:
RestApi.shareNodeId(getActivity(), mNode.NodeID); RestApi.shareNodeId(getActivity(), mNode.NodeID);
return true; return true;
case R.id.delete: case R.id.delete:
new AlertDialog.Builder(getActivity()) new AlertDialog.Builder(getActivity())
.setMessage(R.string.delete_node_confirm) .setMessage(R.string.delete_node_confirm)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialogInterface, int i) { public void onClick(DialogInterface dialogInterface, int i) {
mSyncthingService.getApi().deleteNode(mNode, getActivity()); mSyncthingService.getApi().deleteNode(mNode, getActivity());
getActivity().finish(); getActivity().finish();
} }
}) })
.setNegativeButton(android.R.string.no, null) .setNegativeButton(android.R.string.no, null)
.show(); .show();
return true; return true;
case android.R.id.home: case android.R.id.home:
getActivity().finish(); getActivity().finish();
return true; return true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
} }
@Override @Override
public boolean onPreferenceChange(Preference preference, Object o) { public boolean onPreferenceChange(Preference preference, Object o) {
if (preference instanceof EditTextPreference) { if (preference instanceof EditTextPreference) {
EditTextPreference pref = (EditTextPreference) preference; EditTextPreference pref = (EditTextPreference) preference;
pref.setSummary((String) o); pref.setSummary((String) o);
} }
if (preference.equals(mNodeId)) { if (preference.equals(mNodeId)) {
mNode.NodeID = (String) o; mNode.NodeID = (String) o;
nodeUpdated(); nodeUpdated();
return true; return true;
} } else if (preference.equals(mName)) {
else if (preference.equals(mName)) { mNode.Name = (String) o;
mNode.Name = (String) o; nodeUpdated();
nodeUpdated(); return true;
return true; } else if (preference.equals(mAddresses)) {
} mNode.Addresses = (String) o;
else if (preference.equals(mAddresses)) { nodeUpdated();
mNode.Addresses = (String) o; return true;
nodeUpdated(); }
return true; return false;
} }
return false;
}
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
if (preference.equals(mNodeId)) { if (preference.equals(mNodeId)) {
mSyncthingService.getApi().copyNodeId(mNode.NodeID); mSyncthingService.getApi().copyNodeId(mNode.NodeID);
return true; return true;
} }
return false; return false;
} }
/** /**
* Sets version and current address of the node. * Sets version and current address of the node.
* * <p/>
* NOTE: This is only called once on startup, should be called more often to properly display * NOTE: This is only called once on startup, should be called more often to properly display
* version/address changes. * version/address changes.
*/ */
@Override @Override
public void onReceiveConnections(Map<String, RestApi.Connection> connections) { public void onReceiveConnections(Map<String, RestApi.Connection> connections) {
if (connections.containsKey(mNode.NodeID)) { if (connections.containsKey(mNode.NodeID)) {
mVersion.setSummary(connections.get(mNode.NodeID).ClientVersion); mVersion.setSummary(connections.get(mNode.NodeID).ClientVersion);
mCurrentAddress.setSummary(connections.get(mNode.NodeID).Address); mCurrentAddress.setSummary(connections.get(mNode.NodeID).Address);
} }
} }
/** /**
* Sends the updated node info if in edit mode. * Sends the updated node info if in edit mode.
*/ */
private void nodeUpdated() { private void nodeUpdated() {
if (!mIsCreate) { if (!mIsCreate) {
mSyncthingService.getApi().editNode(mNode, this); mSyncthingService.getApi().editNode(mNode, this);
} }
} }
/** /**
* Sends QR code scanning intent when clicking the qrcode icon. * Sends QR code scanning intent when clicking the qrcode icon.
*/ */
public void onClick(View view) { public void onClick(View view) {
Intent intentScan = new Intent("com.google.zxing.client.android.SCAN"); Intent intentScan = new Intent("com.google.zxing.client.android.SCAN");
intentScan.addCategory(Intent.CATEGORY_DEFAULT); intentScan.addCategory(Intent.CATEGORY_DEFAULT);
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
try { try {
startActivityForResult(intentScan, SCAN_QR_REQUEST_CODE); startActivityForResult(intentScan, SCAN_QR_REQUEST_CODE);
} } catch (ActivityNotFoundException e) {
catch (ActivityNotFoundException e) { Toast.makeText(getActivity(), R.string.no_qr_scanner_installed,
Toast.makeText(getActivity(), R.string.no_qr_scanner_installed, Toast.LENGTH_LONG).show();
Toast.LENGTH_LONG).show(); }
} }
}
/** /**
* Receives value of scanned QR code and sets it as node ID. * Receives value of scanned QR code and sets it as node ID.
*/ */
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == SCAN_QR_REQUEST_CODE && resultCode == Activity.RESULT_OK) { if (requestCode == SCAN_QR_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
mNode.NodeID = data.getStringExtra("SCAN_RESULT"); mNode.NodeID = data.getStringExtra("SCAN_RESULT");
((EditTextPreference) mNodeId).setText(mNode.NodeID); ((EditTextPreference) mNodeId).setText(mNode.NodeID);
mNodeId.setSummary(mNode.NodeID); mNodeId.setSummary(mNode.NodeID);
} }
} }
/** /**
* Callback for {@link RestApi#editNode(RestApi.Node, RestApi.OnNodeIdNormalizedListener)}. * Callback for {@link RestApi#editNode(RestApi.Node, RestApi.OnNodeIdNormalizedListener)}.
* Displays an error message if present, or finishes the Activity on success in edit mode. * Displays an error message if present, or finishes the Activity on success in edit mode.
* *
* @param normalizedId The normalized node ID, or null on error. * @param normalizedId The normalized node ID, or null on error.
* @param error An error message, or null on success. * @param error An error message, or null on success.
*/ */
@Override @Override
public void onNodeIdNormalized(String normalizedId, String error) { public void onNodeIdNormalized(String normalizedId, String error) {
if (error != null) { if (error != null) {
Toast.makeText(getActivity(), error, Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(), error, Toast.LENGTH_LONG).show();
} } else if (mIsCreate) {
else if (mIsCreate) { getActivity().finish();
getActivity().finish(); }
} }
}
} }

View file

@ -21,80 +21,79 @@ import java.util.TimerTask;
* Displays a list of all existing nodes. * Displays a list of all existing nodes.
*/ */
public class NodesFragment extends ListFragment implements SyncthingService.OnApiChangeListener, public class NodesFragment extends ListFragment implements SyncthingService.OnApiChangeListener,
ListView.OnItemClickListener { ListView.OnItemClickListener {
private NodesAdapter mAdapter; private NodesAdapter mAdapter;
private Timer mTimer; private Timer mTimer;
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
setListShown(true); setListShown(true);
} }
@Override @Override
public void onApiChange(SyncthingService.State currentState) { public void onApiChange(SyncthingService.State currentState) {
if (currentState != SyncthingService.State.ACTIVE) if (currentState != SyncthingService.State.ACTIVE)
return; return;
initAdapter(); initAdapter();
} }
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
initAdapter(); initAdapter();
} }
private void initAdapter() { private void initAdapter() {
SyncthingActivity activity = (SyncthingActivity) getActivity(); SyncthingActivity activity = (SyncthingActivity) getActivity();
if (activity == null || activity.getApi() == null) if (activity == null || activity.getApi() == null)
return; return;
mAdapter = new NodesAdapter(activity); mAdapter = new NodesAdapter(activity);
mAdapter.add(activity.getApi().getNodes()); mAdapter.add(activity.getApi().getNodes());
setListAdapter(mAdapter); setListAdapter(mAdapter);
setEmptyText(getString(R.string.nodes_list_empty)); setEmptyText(getString(R.string.nodes_list_empty));
getListView().setOnItemClickListener(this); getListView().setOnItemClickListener(this);
} }
private void updateList() { private void updateList() {
if (mAdapter == null || getView() == null) if (mAdapter == null || getView() == null)
return; return;
MainActivity activity = (MainActivity) getActivity(); MainActivity activity = (MainActivity) getActivity();
mAdapter.updateConnections(activity.getApi(), getListView()); mAdapter.updateConnections(activity.getApi(), getListView());
} }
@Override @Override
public void setUserVisibleHint(boolean isVisibleToUser) { public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser); super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) { if (isVisibleToUser) {
mTimer = new Timer(); mTimer = new Timer();
mTimer.schedule(new TimerTask() { mTimer.schedule(new TimerTask() {
@Override @Override
public void run() { public void run() {
updateList(); updateList();
} }
}, 0, SyncthingService.GUI_UPDATE_INTERVAL); }, 0, SyncthingService.GUI_UPDATE_INTERVAL);
} } else if (mTimer != null) {
else if (mTimer != null) { mTimer.cancel();
mTimer.cancel(); mTimer = null;
mTimer = null; }
} }
}
@Override @Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Intent intent = new Intent(getActivity(), SettingsActivity.class); Intent intent = new Intent(getActivity(), SettingsActivity.class);
intent.setAction(SettingsActivity.ACTION_NODE_SETTINGS_FRAGMENT); intent.setAction(SettingsActivity.ACTION_NODE_SETTINGS_FRAGMENT);
intent.putExtra(SettingsActivity.EXTRA_IS_CREATE, false); intent.putExtra(SettingsActivity.EXTRA_IS_CREATE, false);
intent.putExtra(NodeSettingsFragment.EXTRA_NODE_ID, mAdapter.getItem(i).NodeID); intent.putExtra(NodeSettingsFragment.EXTRA_NODE_ID, mAdapter.getItem(i).NodeID);
startActivity(intent); startActivity(intent);
} }
} }

View file

@ -31,275 +31,265 @@ import java.util.List;
* Shows repo details and allows changing them. * Shows repo details and allows changing them.
*/ */
public class RepoSettingsFragment extends PreferenceFragment public class RepoSettingsFragment extends PreferenceFragment
implements SyncthingActivity.OnServiceConnectedListener, implements SyncthingActivity.OnServiceConnectedListener,
Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener,
SyncthingService.OnApiChangeListener { SyncthingService.OnApiChangeListener {
private static final int DIRECTORY_REQUEST_CODE = 234; private static final int DIRECTORY_REQUEST_CODE = 234;
/** /**
* The ID of the repo to be edited. To be used with {@link com.nutomic.syncthingandroid.activities.SettingsActivity#EXTRA_IS_CREATE} * The ID of the repo to be edited. To be used with {@link com.nutomic.syncthingandroid.activities.SettingsActivity#EXTRA_IS_CREATE}
* set to false. * set to false.
*/ */
public static final String EXTRA_REPO_ID = "repo_id"; public static final String EXTRA_REPO_ID = "repo_id";
private static final String KEY_NODE_SHARED = "node_shared"; private static final String KEY_NODE_SHARED = "node_shared";
private SyncthingService mSyncthingService; private SyncthingService mSyncthingService;
private RestApi.Repo mRepo; private RestApi.Repo mRepo;
private EditTextPreference mRepoId; private EditTextPreference mRepoId;
private Preference mDirectory; private Preference mDirectory;
private CheckBoxPreference mRepoMaster; private CheckBoxPreference mRepoMaster;
private PreferenceScreen mNodes; private PreferenceScreen mNodes;
private CheckBoxPreference mVersioning; private CheckBoxPreference mVersioning;
private EditTextPreference mVersioningKeep; private EditTextPreference mVersioningKeep;
private boolean mIsCreate; private boolean mIsCreate;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
SettingsActivity activity = (SettingsActivity) getActivity(); SettingsActivity activity = (SettingsActivity) getActivity();
activity.registerOnServiceConnectedListener(this); activity.registerOnServiceConnectedListener(this);
mIsCreate = activity.getIsCreate(); mIsCreate = activity.getIsCreate();
setHasOptionsMenu(true); setHasOptionsMenu(true);
if (mIsCreate) { if (mIsCreate) {
addPreferencesFromResource(R.xml.repo_settings_create); addPreferencesFromResource(R.xml.repo_settings_create);
} } else {
else { addPreferencesFromResource(R.xml.repo_settings_edit);
addPreferencesFromResource(R.xml.repo_settings_edit); }
}
mRepoId = (EditTextPreference) findPreference("repo_id"); mRepoId = (EditTextPreference) findPreference("repo_id");
mRepoId.setOnPreferenceChangeListener(this); mRepoId.setOnPreferenceChangeListener(this);
mDirectory = findPreference("directory"); mDirectory = findPreference("directory");
mDirectory.setOnPreferenceClickListener(this); mDirectory.setOnPreferenceClickListener(this);
mRepoMaster = (CheckBoxPreference) findPreference("repo_master"); mRepoMaster = (CheckBoxPreference) findPreference("repo_master");
mRepoMaster.setOnPreferenceChangeListener(this); mRepoMaster.setOnPreferenceChangeListener(this);
mNodes = (PreferenceScreen) findPreference("nodes"); mNodes = (PreferenceScreen) findPreference("nodes");
mNodes.setOnPreferenceClickListener(this); mNodes.setOnPreferenceClickListener(this);
mVersioning = (CheckBoxPreference) findPreference("versioning"); mVersioning = (CheckBoxPreference) findPreference("versioning");
mVersioning.setOnPreferenceChangeListener(this); mVersioning.setOnPreferenceChangeListener(this);
mVersioningKeep = (EditTextPreference) findPreference("versioning_keep"); mVersioningKeep = (EditTextPreference) findPreference("versioning_keep");
mVersioningKeep.setOnPreferenceChangeListener(this); mVersioningKeep.setOnPreferenceChangeListener(this);
} }
@Override @Override
public void onApiChange(SyncthingService.State currentState) { public void onApiChange(SyncthingService.State currentState) {
if (currentState != SyncthingService.State.ACTIVE) { if (currentState != SyncthingService.State.ACTIVE) {
getActivity().finish(); getActivity().finish();
return; return;
} }
if (getActivity() == null || getActivity().isFinishing()) if (getActivity() == null || getActivity().isFinishing())
return; return;
if (mIsCreate) { if (mIsCreate) {
getActivity().setTitle(R.string.create_repo); getActivity().setTitle(R.string.create_repo);
mRepo = new RestApi.Repo(); mRepo = new RestApi.Repo();
mRepo.ID = ""; mRepo.ID = "";
mRepo.Directory = ""; mRepo.Directory = "";
mRepo.Nodes = new ArrayList<RestApi.Node>(); mRepo.Nodes = new ArrayList<RestApi.Node>();
mRepo.Versioning = new RestApi.Versioning(); mRepo.Versioning = new RestApi.Versioning();
} } else {
else { getActivity().setTitle(R.string.edit_repo);
getActivity().setTitle(R.string.edit_repo); List<RestApi.Repo> repos = mSyncthingService.getApi().getRepos();
List<RestApi.Repo> repos = mSyncthingService.getApi().getRepos(); for (int i = 0; i < repos.size(); i++) {
for (int i = 0; i < repos.size(); i++) { if (repos.get(i).ID.equals(
if (repos.get(i).ID.equals( getActivity().getIntent().getStringExtra(EXTRA_REPO_ID))) {
getActivity().getIntent().getStringExtra(EXTRA_REPO_ID))) { mRepo = repos.get(i);
mRepo = repos.get(i); break;
break; }
} }
} }
}
mRepoId.setText(mRepo.ID); mRepoId.setText(mRepo.ID);
mRepoId.setSummary(mRepo.ID); mRepoId.setSummary(mRepo.ID);
mDirectory.setSummary(mRepo.Directory); mDirectory.setSummary(mRepo.Directory);
mRepoMaster.setChecked(mRepo.ReadOnly); mRepoMaster.setChecked(mRepo.ReadOnly);
List<RestApi.Node> nodesList = mSyncthingService.getApi().getNodes(); List<RestApi.Node> nodesList = mSyncthingService.getApi().getNodes();
for (RestApi.Node n : nodesList) { for (RestApi.Node n : nodesList) {
ExtendedCheckBoxPreference cbp = new ExtendedCheckBoxPreference(getActivity(), n); ExtendedCheckBoxPreference cbp = new ExtendedCheckBoxPreference(getActivity(), n);
cbp.setTitle(n.Name); cbp.setTitle(n.Name);
cbp.setKey(KEY_NODE_SHARED); cbp.setKey(KEY_NODE_SHARED);
cbp.setOnPreferenceChangeListener(RepoSettingsFragment.this); cbp.setOnPreferenceChangeListener(RepoSettingsFragment.this);
cbp.setChecked(false); cbp.setChecked(false);
for (RestApi.Node n2 : mRepo.Nodes) { for (RestApi.Node n2 : mRepo.Nodes) {
if (n2.NodeID.equals(n.NodeID)) { if (n2.NodeID.equals(n.NodeID)) {
cbp.setChecked(true); cbp.setChecked(true);
} }
} }
mNodes.addPreference(cbp); mNodes.addPreference(cbp);
} }
mVersioning.setChecked(mRepo.Versioning instanceof RestApi.SimpleVersioning); mVersioning.setChecked(mRepo.Versioning instanceof RestApi.SimpleVersioning);
if (mVersioning.isChecked()) { if (mVersioning.isChecked()) {
mVersioningKeep.setText(mRepo.Versioning.getParams().get("keep")); mVersioningKeep.setText(mRepo.Versioning.getParams().get("keep"));
mVersioningKeep.setSummary(mRepo.Versioning.getParams().get("keep")); mVersioningKeep.setSummary(mRepo.Versioning.getParams().get("keep"));
mVersioningKeep.setEnabled(true); mVersioningKeep.setEnabled(true);
} } else {
else { mVersioningKeep.setEnabled(false);
mVersioningKeep.setEnabled(false); }
} }
}
@Override @Override
public void onServiceConnected() { public void onServiceConnected() {
mSyncthingService = ((SyncthingActivity) getActivity()).getService(); mSyncthingService = ((SyncthingActivity) getActivity()).getService();
mSyncthingService.registerOnApiChangeListener(this); mSyncthingService.registerOnApiChangeListener(this);
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.repo_settings, menu); inflater.inflate(R.menu.repo_settings, menu);
} }
@Override @Override
public void onPrepareOptionsMenu(Menu menu) { public void onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.create).setVisible(mIsCreate); menu.findItem(R.id.create).setVisible(mIsCreate);
menu.findItem(R.id.delete).setVisible(!mIsCreate); menu.findItem(R.id.delete).setVisible(!mIsCreate);
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.create: case R.id.create:
if (mRepo.ID.equals("")) { if (mRepo.ID.equals("")) {
Toast.makeText(getActivity(), R.string.repo_id_required, Toast.LENGTH_LONG) Toast.makeText(getActivity(), R.string.repo_id_required, Toast.LENGTH_LONG)
.show(); .show();
return true; return true;
} }
if (mRepo.Directory.equals("")) { if (mRepo.Directory.equals("")) {
Toast.makeText(getActivity(), R.string.repo_path_required, Toast.LENGTH_LONG) Toast.makeText(getActivity(), R.string.repo_path_required, Toast.LENGTH_LONG)
.show(); .show();
return true; return true;
} }
mSyncthingService.getApi().editRepo(mRepo, true, getActivity()); mSyncthingService.getApi().editRepo(mRepo, true, getActivity());
getActivity().finish(); getActivity().finish();
return true; return true;
case R.id.delete: case R.id.delete:
new AlertDialog.Builder(getActivity()) new AlertDialog.Builder(getActivity())
.setMessage(R.string.delete_repo_confirm) .setMessage(R.string.delete_repo_confirm)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialogInterface, int i) { public void onClick(DialogInterface dialogInterface, int i) {
mSyncthingService.getApi().deleteRepo(mRepo, getActivity()); mSyncthingService.getApi().deleteRepo(mRepo, getActivity());
getActivity().finish(); getActivity().finish();
} }
}) })
.setNegativeButton(android.R.string.no, null) .setNegativeButton(android.R.string.no, null)
.show(); .show();
return true; return true;
case android.R.id.home: case android.R.id.home:
getActivity().finish(); getActivity().finish();
return true; return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@Override @Override
public boolean onPreferenceChange(Preference preference, Object o) { public boolean onPreferenceChange(Preference preference, Object o) {
if (preference instanceof EditTextPreference) { if (preference instanceof EditTextPreference) {
EditTextPreference pref = (EditTextPreference) preference; EditTextPreference pref = (EditTextPreference) preference;
pref.setSummary((String) o); pref.setSummary((String) o);
} }
if (preference.equals(mRepoId)) { if (preference.equals(mRepoId)) {
mRepo.ID = (String) o; mRepo.ID = (String) o;
repoUpdated(); repoUpdated();
return true; return true;
} } else if (preference.equals(mDirectory)) {
else if (preference.equals(mDirectory)) { mRepo.Directory = (String) o;
mRepo.Directory = (String) o; repoUpdated();
repoUpdated(); return true;
return true; } else if (preference.equals(mRepoMaster)) {
} mRepo.ReadOnly = (Boolean) o;
else if (preference.equals(mRepoMaster)) { repoUpdated();
mRepo.ReadOnly = (Boolean) o; return true;
repoUpdated(); } else if (preference.getKey().equals(KEY_NODE_SHARED)) {
return true; ExtendedCheckBoxPreference pref = (ExtendedCheckBoxPreference) preference;
} RestApi.Node node = (RestApi.Node) pref.getObject();
else if (preference.getKey().equals(KEY_NODE_SHARED)) { if ((Boolean) o) {
ExtendedCheckBoxPreference pref = (ExtendedCheckBoxPreference) preference; mRepo.Nodes.add(node);
RestApi.Node node = (RestApi.Node) pref.getObject(); } else {
if ((Boolean) o) { for (RestApi.Node n : mRepo.Nodes) {
mRepo.Nodes.add(node); if (n.NodeID.equals(node.NodeID)) {
} mRepo.Nodes.remove(n);
else { }
for (RestApi.Node n : mRepo.Nodes) { }
if (n.NodeID.equals(node.NodeID)) { }
mRepo.Nodes.remove(n); repoUpdated();
} return true;
} } else if (preference.equals(mVersioning)) {
} mVersioningKeep.setEnabled((Boolean) o);
repoUpdated(); if ((Boolean) o) {
return true; RestApi.SimpleVersioning v = new RestApi.SimpleVersioning();
} mRepo.Versioning = v;
else if (preference.equals(mVersioning)) { v.setParams(5);
mVersioningKeep.setEnabled((Boolean) o); mVersioningKeep.setText("5");
if ((Boolean) o) { mVersioningKeep.setSummary("5");
RestApi.SimpleVersioning v = new RestApi.SimpleVersioning(); } else {
mRepo.Versioning = v; mRepo.Versioning = new RestApi.Versioning();
v.setParams(5); }
mVersioningKeep.setText("5"); repoUpdated();
mVersioningKeep.setSummary("5"); return true;
} } else if (preference.equals(mVersioningKeep)) {
else { ((RestApi.SimpleVersioning) mRepo.Versioning)
mRepo.Versioning = new RestApi.Versioning(); .setParams(Integer.parseInt((String) o));
} repoUpdated();
repoUpdated(); return true;
return true; }
}
else if (preference.equals(mVersioningKeep)) {
((RestApi.SimpleVersioning) mRepo.Versioning)
.setParams(Integer.parseInt((String) o));
repoUpdated();
return true;
}
return false; return false;
} }
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
if (preference.equals(mDirectory)) { if (preference.equals(mDirectory)) {
Intent intent = new Intent(getActivity(), FolderPickerActivity.class) Intent intent = new Intent(getActivity(), FolderPickerActivity.class)
.putExtra(FolderPickerActivity.EXTRA_INITIAL_DIRECTORY, .putExtra(FolderPickerActivity.EXTRA_INITIAL_DIRECTORY,
(mRepo.Directory.length() != 0) (mRepo.Directory.length() != 0)
? mRepo.Directory ? mRepo.Directory
: Environment.getExternalStorageDirectory().getAbsolutePath()); : Environment.getExternalStorageDirectory().getAbsolutePath()
startActivityForResult(intent, DIRECTORY_REQUEST_CODE); );
} startActivityForResult(intent, DIRECTORY_REQUEST_CODE);
else if (preference.equals(mNodes) && mSyncthingService.getApi().getNodes().isEmpty()) { } else if (preference.equals(mNodes) && mSyncthingService.getApi().getNodes().isEmpty()) {
Toast.makeText(getActivity(), R.string.no_nodes, Toast.LENGTH_SHORT) Toast.makeText(getActivity(), R.string.no_nodes, Toast.LENGTH_SHORT)
.show(); .show();
} }
return false; return false;
} }
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK && requestCode == DIRECTORY_REQUEST_CODE) { if (resultCode == Activity.RESULT_OK && requestCode == DIRECTORY_REQUEST_CODE) {
mRepo.Directory = data.getStringExtra(FolderPickerActivity.EXTRA_RESULT_DIRECTORY); mRepo.Directory = data.getStringExtra(FolderPickerActivity.EXTRA_RESULT_DIRECTORY);
mDirectory.setSummary(mRepo.Directory); mDirectory.setSummary(mRepo.Directory);
repoUpdated(); repoUpdated();
} }
} }
private void repoUpdated() { private void repoUpdated() {
if (!mIsCreate) { if (!mIsCreate) {
mSyncthingService.getApi().editRepo(mRepo, false, getActivity()); mSyncthingService.getApi().editRepo(mRepo, false, getActivity());
} }
} }
} }

View file

@ -19,80 +19,79 @@ import java.util.TimerTask;
* Displays a list of all existing repositories. * Displays a list of all existing repositories.
*/ */
public class ReposFragment extends ListFragment implements SyncthingService.OnApiChangeListener, public class ReposFragment extends ListFragment implements SyncthingService.OnApiChangeListener,
AdapterView.OnItemClickListener { AdapterView.OnItemClickListener {
private ReposAdapter mAdapter; private ReposAdapter mAdapter;
private Timer mTimer; private Timer mTimer;
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
setListShown(true); setListShown(true);
} }
@Override @Override
public void onApiChange(SyncthingService.State currentState) { public void onApiChange(SyncthingService.State currentState) {
if (currentState != SyncthingService.State.ACTIVE) if (currentState != SyncthingService.State.ACTIVE)
return; return;
initAdapter(); initAdapter();
} }
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
initAdapter(); initAdapter();
} }
private void initAdapter() { private void initAdapter() {
MainActivity activity = (MainActivity) getActivity(); MainActivity activity = (MainActivity) getActivity();
if (activity == null || activity.getApi() == null) if (activity == null || activity.getApi() == null)
return; return;
mAdapter = new ReposAdapter(activity); mAdapter = new ReposAdapter(activity);
mAdapter.add(activity.getApi().getRepos()); mAdapter.add(activity.getApi().getRepos());
setListAdapter(mAdapter); setListAdapter(mAdapter);
setEmptyText(getString(R.string.repositories_list_empty)); setEmptyText(getString(R.string.repositories_list_empty));
getListView().setOnItemClickListener(this); getListView().setOnItemClickListener(this);
} }
private void updateList() { private void updateList() {
if (mAdapter == null || getView() == null) if (mAdapter == null || getView() == null)
return; return;
MainActivity activity = (MainActivity) getActivity(); MainActivity activity = (MainActivity) getActivity();
mAdapter.updateModel(activity.getApi(), getListView()); mAdapter.updateModel(activity.getApi(), getListView());
} }
@Override @Override
public void setUserVisibleHint(boolean isVisibleToUser) { public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser); super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) { if (isVisibleToUser) {
mTimer = new Timer(); mTimer = new Timer();
mTimer.schedule(new TimerTask() { mTimer.schedule(new TimerTask() {
@Override @Override
public void run() { public void run() {
updateList(); updateList();
} }
}, 0, SyncthingService.GUI_UPDATE_INTERVAL); }, 0, SyncthingService.GUI_UPDATE_INTERVAL);
} } else if (mTimer != null) {
else if (mTimer != null) { mTimer.cancel();
mTimer.cancel(); mTimer = null;
mTimer = null; }
} }
}
@Override @Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Intent intent = new Intent(getActivity(), SettingsActivity.class) Intent intent = new Intent(getActivity(), SettingsActivity.class)
.setAction(SettingsActivity.ACTION_REPO_SETTINGS_FRAGMENT) .setAction(SettingsActivity.ACTION_REPO_SETTINGS_FRAGMENT)
.putExtra(SettingsActivity.EXTRA_IS_CREATE, false) .putExtra(SettingsActivity.EXTRA_IS_CREATE, false)
.putExtra(RepoSettingsFragment.EXTRA_REPO_ID, mAdapter.getItem(i).ID); .putExtra(RepoSettingsFragment.EXTRA_REPO_ID, mAdapter.getItem(i).ID);
startActivity(intent); startActivity(intent);
} }
} }

View file

@ -16,139 +16,136 @@ import com.nutomic.syncthingandroid.syncthing.RestApi;
import com.nutomic.syncthingandroid.syncthing.SyncthingService; import com.nutomic.syncthingandroid.syncthing.SyncthingService;
public class SettingsFragment extends PreferenceFragment public class SettingsFragment extends PreferenceFragment
implements SyncthingActivity.OnServiceConnectedListener, implements SyncthingActivity.OnServiceConnectedListener,
SyncthingService.OnApiChangeListener, Preference.OnPreferenceChangeListener { SyncthingService.OnApiChangeListener, Preference.OnPreferenceChangeListener {
private static final String SYNCTHING_OPTIONS_KEY = "syncthing_options"; private static final String SYNCTHING_OPTIONS_KEY = "syncthing_options";
private static final String SYNCTHING_GUI_KEY = "syncthing_gui"; private static final String SYNCTHING_GUI_KEY = "syncthing_gui";
private static final String SYNCTHING_VERSION_KEY = "syncthing_version"; private static final String SYNCTHING_VERSION_KEY = "syncthing_version";
private CheckBoxPreference mStopNotCharging; private CheckBoxPreference mStopNotCharging;
private CheckBoxPreference mStopMobileData; private CheckBoxPreference mStopMobileData;
private Preference mVersion; private Preference mVersion;
private PreferenceScreen mOptionsScreen; private PreferenceScreen mOptionsScreen;
private PreferenceScreen mGuiScreen; private PreferenceScreen mGuiScreen;
private SyncthingService mSyncthingService; private SyncthingService mSyncthingService;
@Override @Override
public void onApiChange(SyncthingService.State currentState) { public void onApiChange(SyncthingService.State currentState) {
mOptionsScreen.setEnabled(currentState == SyncthingService.State.ACTIVE); mOptionsScreen.setEnabled(currentState == SyncthingService.State.ACTIVE);
mGuiScreen.setEnabled(currentState == SyncthingService.State.ACTIVE); mGuiScreen.setEnabled(currentState == SyncthingService.State.ACTIVE);
mVersion.setSummary(mSyncthingService.getApi().getVersion()); mVersion.setSummary(mSyncthingService.getApi().getVersion());
if (currentState == SyncthingService.State.ACTIVE) { 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(SettingsFragment.this); pref.setOnPreferenceChangeListener(SettingsFragment.this);
String value = mSyncthingService.getApi() String value = mSyncthingService.getApi()
.getValue(RestApi.TYPE_OPTIONS, pref.getKey()); .getValue(RestApi.TYPE_OPTIONS, pref.getKey());
applyPreference(pref, value); applyPreference(pref, value);
} }
for (int i = 0; i < mGuiScreen.getPreferenceCount(); i++) { for (int i = 0; i < mGuiScreen.getPreferenceCount(); i++) {
Preference pref = mGuiScreen.getPreference(i); Preference pref = mGuiScreen.getPreference(i);
pref.setOnPreferenceChangeListener(SettingsFragment.this); pref.setOnPreferenceChangeListener(SettingsFragment.this);
String value = mSyncthingService.getApi() String value = mSyncthingService.getApi()
.getValue(RestApi.TYPE_GUI, pref.getKey()); .getValue(RestApi.TYPE_GUI, pref.getKey());
applyPreference(pref, value); applyPreference(pref, value);
} }
} }
} }
/** /**
* Applies the given value to the preference. * Applies the given value to the preference.
* * <p/>
* If pref is an EditTextPreference, setText is used and the value shown as summary. If pref is * If pref is an EditTextPreference, setText is used and the value shown as summary. If pref is
* a CheckBoxPreference, setChecked is used (by parsing value as Boolean). * a CheckBoxPreference, setChecked is used (by parsing value as Boolean).
*/ */
private void applyPreference(Preference pref, String value) { private void applyPreference(Preference pref, String value) {
if (pref instanceof EditTextPreference) { if (pref instanceof EditTextPreference) {
((EditTextPreference) pref).setText(value); ((EditTextPreference) pref).setText(value);
pref.setSummary(value); pref.setSummary(value);
} } else if (pref instanceof CheckBoxPreference) {
else if (pref instanceof CheckBoxPreference) { ((CheckBoxPreference) pref).setChecked(Boolean.parseBoolean(value));
((CheckBoxPreference) pref).setChecked(Boolean.parseBoolean(value)); }
} }
}
/** /**
* Loads layout, sets version from Rest API. * Loads layout, sets version from Rest API.
* * <p/>
* Manual target API as we manually check if ActionBar is available (for ActionBar back button). * Manual target API as we manually check if ActionBar is available (for ActionBar back button).
*/ */
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
((SyncthingActivity) getActivity()).registerOnServiceConnectedListener(this); ((SyncthingActivity) getActivity()).registerOnServiceConnectedListener(this);
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 = (CheckBoxPreference) findPreference("stop_sync_on_mobile_data");
mStopNotCharging.setOnPreferenceChangeListener(this); mStopNotCharging.setOnPreferenceChangeListener(this);
mStopMobileData = (CheckBoxPreference) findPreference("stop_sync_while_not_charging"); mStopMobileData = (CheckBoxPreference) findPreference("stop_sync_while_not_charging");
mStopMobileData.setOnPreferenceChangeListener(this); 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);
} }
@Override @Override
public void onServiceConnected() { public void onServiceConnected() {
mSyncthingService = ((SyncthingActivity) getActivity()).getService(); mSyncthingService = ((SyncthingActivity) getActivity()).getService();
mSyncthingService.registerOnApiChangeListener(this); mSyncthingService.registerOnApiChangeListener(this);
} }
/** /**
* Handles ActionBar back selected. * Handles ActionBar back selected.
*/ */
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case android.R.id.home: case android.R.id.home:
NavUtils.navigateUpFromSameTask(getActivity()); NavUtils.navigateUpFromSameTask(getActivity());
return true; return true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
} }
/** /**
* Sends the updated value to {@link }RestApi}, and sets it as the summary * Sends the updated value to {@link }RestApi}, and sets it as the summary
* for EditTextPreference. * for EditTextPreference.
*/ */
@Override @Override
public boolean onPreferenceChange(Preference preference, Object o) { public boolean onPreferenceChange(Preference preference, Object o) {
if (preference instanceof EditTextPreference) { if (preference instanceof EditTextPreference) {
String value = (String) o; String value = (String) o;
preference.setSummary(value); preference.setSummary(value);
EditTextPreference etp = (EditTextPreference) preference; EditTextPreference etp = (EditTextPreference) preference;
if (etp.getEditText().getInputType() == InputType.TYPE_CLASS_NUMBER) { if (etp.getEditText().getInputType() == InputType.TYPE_CLASS_NUMBER) {
o = Integer.parseInt((String) o); o = Integer.parseInt((String) o);
} }
} }
if (preference.equals(mStopNotCharging) || preference.equals(mStopMobileData)) { if (preference.equals(mStopNotCharging) || preference.equals(mStopMobileData)) {
mSyncthingService.updateState(); mSyncthingService.updateState();
} } else if (mOptionsScreen.findPreference(preference.getKey()) != null) {
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"), getActivity());
preference.getKey().equals("ListenAddress"), getActivity()); } else if (mGuiScreen.findPreference(preference.getKey()) != null) {
} mSyncthingService.getApi().setValue(
else if (mGuiScreen.findPreference(preference.getKey()) != null) { RestApi.TYPE_GUI, preference.getKey(), o, false, getActivity());
mSyncthingService.getApi().setValue( }
RestApi.TYPE_GUI, preference.getKey(), o, false, getActivity());
}
return true; return true;
} }
} }

View file

@ -9,12 +9,12 @@ import android.content.Intent;
*/ */
public class BatteryReceiver extends BroadcastReceiver { public class BatteryReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
boolean isCharging = Intent.ACTION_POWER_CONNECTED.equals(intent.getAction()); boolean isCharging = Intent.ACTION_POWER_CONNECTED.equals(intent.getAction());
Intent i = new Intent(context, SyncthingService.class); Intent i = new Intent(context, SyncthingService.class);
i.putExtra(DeviceStateHolder.EXTRA_IS_CHARGING, isCharging); i.putExtra(DeviceStateHolder.EXTRA_IS_CHARGING, isCharging);
context.startService(i); context.startService(i);
} }
} }

View file

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

View file

@ -25,60 +25,59 @@ import java.util.LinkedList;
*/ */
public class GetTask extends AsyncTask<String, Void, String> { public class GetTask extends AsyncTask<String, Void, String> {
private static final String TAG = "GetTask"; private static final String TAG = "GetTask";
public static final String URI_CONFIG = "/rest/config"; public static final String URI_CONFIG = "/rest/config";
public static final String URI_VERSION = "/rest/version"; public static final String URI_VERSION = "/rest/version";
public static final String URI_SYSTEM = "/rest/system"; public static final String URI_SYSTEM = "/rest/system";
public static final String URI_CONNECTIONS = "/rest/connections"; public static final String URI_CONNECTIONS = "/rest/connections";
public static final String URI_MODEL = "/rest/model"; public static final String URI_MODEL = "/rest/model";
public static final String URI_NODEID = "/rest/nodeid"; public static final String URI_NODEID = "/rest/nodeid";
/** /**
* params[0] Syncthing hostname * params[0] Syncthing hostname
* params[1] URI to call * params[1] URI to call
* params[2] Syncthing API key * params[2] Syncthing API key
* params[3] optional parameter key * params[3] optional parameter key
* params[4] optional parameter value * params[4] optional parameter value
*/ */
@Override @Override
protected String doInBackground(String... params) { protected String doInBackground(String... params) {
String fullUri = params[0] + params[1]; String fullUri = params[0] + params[1];
HttpClient httpclient = new DefaultHttpClient(); HttpClient httpclient = new DefaultHttpClient();
if (params.length == 5) { if (params.length == 5) {
LinkedList<NameValuePair> urlParams = new LinkedList<>(); LinkedList<NameValuePair> urlParams = new LinkedList<>();
urlParams.add(new BasicNameValuePair(params[3], params[4])); urlParams.add(new BasicNameValuePair(params[3], params[4]));
fullUri += "?" + URLEncodedUtils.format(urlParams, "utf-8"); fullUri += "?" + URLEncodedUtils.format(urlParams, "utf-8");
} }
HttpGet get = new HttpGet(fullUri); HttpGet get = new HttpGet(fullUri);
get.addHeader(new BasicHeader("X-API-Key", params[2])); get.addHeader(new BasicHeader("X-API-Key", params[2]));
try { try {
HttpResponse response = httpclient.execute(get); HttpResponse response = httpclient.execute(get);
HttpEntity entity = response.getEntity(); HttpEntity entity = response.getEntity();
if (entity != null) { if (entity != null) {
InputStream is = entity.getContent(); InputStream is = entity.getContent();
BufferedReader br = new BufferedReader(new InputStreamReader(is)); BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line; String line;
String result = ""; String result = "";
while((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
result += line; result += line;
} }
br.close(); br.close();
return result; return result;
} }
} } catch (IOException e) {
catch (IOException e) { Log.w(TAG, "Failed to call Rest API at " + fullUri, e);
Log.w(TAG, "Failed to call Rest API at " + fullUri, e); }
} return null;
return null; }
}
} }

View file

@ -11,17 +11,17 @@ import android.net.NetworkInfo;
*/ */
public class NetworkReceiver extends BroadcastReceiver { public class NetworkReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
ConnectivityManager cm = ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo wifiInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI); NetworkInfo wifiInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo(); NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
boolean isWifiConnected = (wifiInfo != null && wifiInfo.isConnected()) || boolean isWifiConnected = (wifiInfo != null && wifiInfo.isConnected()) ||
activeNetworkInfo == null; activeNetworkInfo == null;
Intent i = new Intent(context, SyncthingService.class); Intent i = new Intent(context, SyncthingService.class);
i.putExtra(DeviceStateHolder.EXTRA_HAS_WIFI, isWifiConnected); i.putExtra(DeviceStateHolder.EXTRA_HAS_WIFI, isWifiConnected);
context.startService(i); context.startService(i);
} }
} }

View file

@ -16,37 +16,36 @@ import java.io.IOException;
*/ */
public class PostTask extends AsyncTask<String, Void, Void> { public class PostTask extends AsyncTask<String, Void, Void> {
private static final String TAG = "PostTask"; private static final String TAG = "PostTask";
public static final String URI_CONFIG = "/rest/config"; public static final String URI_CONFIG = "/rest/config";
public static final String URI_RESTART = "/rest/restart"; public static final String URI_RESTART = "/rest/restart";
public static final String URI_SHUTDOWN = "/rest/shutdown"; public static final String URI_SHUTDOWN = "/rest/shutdown";
/** /**
* params[0] Syncthing hostname * params[0] Syncthing hostname
* params[1] URI to call * params[1] URI to call
* params[2] Syncthing API key * params[2] Syncthing API key
* params[3] The request content (optional) * params[3] The request content (optional)
*/ */
@Override @Override
protected Void doInBackground(String... params) { protected Void doInBackground(String... params) {
String fullUri = params[0] + params[1]; String fullUri = params[0] + params[1];
HttpClient httpclient = new DefaultHttpClient(); HttpClient httpclient = new DefaultHttpClient();
HttpPost post = new HttpPost(fullUri); HttpPost post = new HttpPost(fullUri);
post.addHeader(new BasicHeader("X-API-Key", params[2])); post.addHeader(new BasicHeader("X-API-Key", params[2]));
try { try {
if (params.length > 3) { if (params.length > 3) {
post.setEntity(new StringEntity(params[3])); post.setEntity(new StringEntity(params[3]));
} }
httpclient.execute(post); httpclient.execute(post);
} } catch (IOException e) {
catch (IOException e) { Log.w(TAG, "Failed to call Rest API at " + fullUri, e);
Log.w(TAG, "Failed to call Rest API at " + fullUri, e); }
} return null;
return null; }
}
} }

View file

@ -43,7 +43,7 @@ public class SyncthingRunnable implements Runnable {
DataOutputStream dos = null; DataOutputStream dos = null;
int ret = 1; int ret = 1;
Process process = null; Process process = null;
try { try {
process = Runtime.getRuntime().exec("sh"); process = Runtime.getRuntime().exec("sh");
dos = new DataOutputStream(process.getOutputStream()); dos = new DataOutputStream(process.getOutputStream());
// Set home directory to data folder for syncthing to use. // Set home directory to data folder for syncthing to use.
@ -57,22 +57,19 @@ public class SyncthingRunnable implements Runnable {
log(process.getInputStream()); log(process.getInputStream());
ret = process.waitFor(); ret = process.waitFor();
} } catch (IOException | InterruptedException e) {
catch(IOException | InterruptedException e) {
Log.e(TAG, "Failed to execute syncthing binary or read output", e); Log.e(TAG, "Failed to execute syncthing binary or read output", e);
} } finally {
finally {
try { try {
dos.close(); dos.close();
} } catch (IOException e) {
catch (IOException e) {
Log.w(TAG, "Failed to close shell stream", e); Log.w(TAG, "Failed to close shell stream", e);
} }
process.destroy(); process.destroy();
final int retVal = ret; final int retVal = ret;
if (ret != 0) { if (ret != 0) {
Log.w(TAG_NATIVE, "Syncthing binary crashed with error code " + Integer.toString(retVal)); Log.w(TAG_NATIVE, "Syncthing binary crashed with error code " + Integer.toString(retVal));
postCrashDialog(retVal); postCrashDialog(retVal);
} }
} }
} }
@ -95,7 +92,8 @@ public class SyncthingRunnable implements Runnable {
int i) { int i) {
System.exit(0); System.exit(0);
} }
}) }
)
.create(); .create();
dialog.getWindow() dialog.getWindow()
.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); .setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
@ -120,8 +118,7 @@ public class SyncthingRunnable implements Runnable {
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
Log.i(TAG_NATIVE, line); Log.i(TAG_NATIVE, line);
} }
} } catch (IOException e) {
catch (IOException e) {
// NOTE: This is sometimes called on shutdown, as // NOTE: This is sometimes called on shutdown, as
// Process.destroy() closes the stream. // Process.destroy() closes the stream.
Log.w(TAG, "Failed to read syncthing command line output", e); Log.w(TAG, "Failed to read syncthing command line output", e);

View file

@ -40,56 +40,56 @@ import java.util.Iterator;
*/ */
public class SyncthingService extends Service { public class SyncthingService extends Service {
private static final String TAG = "SyncthingService"; private static final String TAG = "SyncthingService";
private static final int NOTIFICATION_RUNNING = 1; private static final int NOTIFICATION_RUNNING = 1;
/** /**
* Intent action to perform a syncthing restart. * Intent action to perform a syncthing restart.
*/ */
public static final String ACTION_RESTART = "restart"; public static final String ACTION_RESTART = "restart";
/** /**
* Interval in ms at which the GUI is updated (eg {@link }LocalNodeInfoFragment}). * Interval in ms at which the GUI is updated (eg {@link }LocalNodeInfoFragment}).
*/ */
public static final int GUI_UPDATE_INTERVAL = 1000; public static final int GUI_UPDATE_INTERVAL = 1000;
/** /**
* Interval in ms, at which connections to the web gui are performed on first start * Interval in ms, at which connections to the web gui are performed on first start
* to find out if it's online. * to find out if it's online.
*/ */
private static final long WEB_GUI_POLL_INTERVAL = 100; private static final long WEB_GUI_POLL_INTERVAL = 100;
/** /**
* Name of the public key file in the data directory. * Name of the public key file in the data directory.
*/ */
private static final String PUBLIC_KEY_FILE = "cert.pem"; private static final String PUBLIC_KEY_FILE = "cert.pem";
/** /**
* Name of the private key file in the data directory. * Name of the private key file in the data directory.
*/ */
private static final String PRIVATE_KEY_FILE = "key.pem"; private static final String PRIVATE_KEY_FILE = "key.pem";
private RestApi mApi; private RestApi mApi;
private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this); private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this);
/** /**
* Callback for when the Syncthing web interface becomes first available after service start. * Callback for when the Syncthing web interface becomes first available after service start.
*/ */
public interface OnWebGuiAvailableListener { public interface OnWebGuiAvailableListener {
public void onWebGuiAvailable(); public void onWebGuiAvailable();
} }
private final HashSet<OnWebGuiAvailableListener> mOnWebGuiAvailableListeners = private final HashSet<OnWebGuiAvailableListener> mOnWebGuiAvailableListeners =
new HashSet<>(); new HashSet<>();
public interface OnApiChangeListener { public interface OnApiChangeListener {
public void onApiChange(State currentState); public void onApiChange(State currentState);
} }
private final HashSet<WeakReference<OnApiChangeListener>> mOnApiChangeListeners = private final HashSet<WeakReference<OnApiChangeListener>> mOnApiChangeListeners =
new HashSet<>(); new HashSet<>();
/** /**
* INIT: Service is starting up and initializing. * INIT: Service is starting up and initializing.
@ -97,334 +97,328 @@ public class SyncthingService extends Service {
* ACTIVE: Syncthing binary is up and running. * ACTIVE: Syncthing binary is up and running.
* DISABLED: Syncthing binary is stopped according to user preferences. * DISABLED: Syncthing binary is stopped according to user preferences.
*/ */
public enum State { public enum State {
INIT, INIT,
STARTING, STARTING,
ACTIVE, ACTIVE,
DISABLED DISABLED
} }
private State mCurrentState = State.INIT; private State mCurrentState = State.INIT;
/** /**
* True if a stop was requested while syncthing is starting, in that case, perform stop in * True if a stop was requested while syncthing is starting, in that case, perform stop in
* {@link PollWebGuiAvailableTask}. * {@link PollWebGuiAvailableTask}.
*/ */
private boolean mStopScheduled = false; private boolean mStopScheduled = false;
private DeviceStateHolder mDeviceStateHolder; private DeviceStateHolder mDeviceStateHolder;
/** /**
* Handles intents, either {@link #ACTION_RESTART}, or intents having * Handles intents, either {@link #ACTION_RESTART}, or intents having
* {@link DeviceStateHolder.EXTRA_HAS_WIFI} or {@link DeviceStateHolder.EXTRA_IS_CHARGING} * {@link DeviceStateHolder.EXTRA_HAS_WIFI} or {@link DeviceStateHolder.EXTRA_IS_CHARGING}
* (which are handled by {@link DeviceStateHolder}. * (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) {
// Just catch the empty intent and return. // Just catch the empty intent and return.
if (intent == null) { if (intent == null) {
} } else if (ACTION_RESTART.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
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) { ConfigXml config = new ConfigXml(SyncthingService.this);
ConfigXml config = new ConfigXml(SyncthingService.this); mApi = new RestApi(SyncthingService.this,
mApi = new RestApi(SyncthingService.this, config.getWebGuiUrl(), config.getApiKey());
config.getWebGuiUrl(), config.getApiKey()); mCurrentState = State.STARTING;
mCurrentState = State.STARTING; registerOnWebGuiAvailableListener(mApi);
registerOnWebGuiAvailableListener(mApi); new PollWebGuiAvailableTask().execute();
new PollWebGuiAvailableTask().execute(); }
} }.execute(mApi.getUrl(), PostTask.URI_RESTART, mApi.getApiKey());
}.execute(mApi.getUrl(), PostTask.URI_RESTART, mApi.getApiKey()); } else if (mCurrentState != State.INIT) {
} mDeviceStateHolder.update(intent);
else if (mCurrentState != State.INIT) { updateState();
mDeviceStateHolder.update(intent); }
updateState(); return START_STICKY;
} }
return START_STICKY;
}
/** /**
* Checks according to preferences and charging/wifi state, whether syncthing should be enabled * Checks according to preferences and charging/wifi state, whether syncthing should be enabled
* or not. * or not.
* * <p/>
* Depending on the result, syncthing is started or stopped, and {@link #onApiChange()} is * Depending on the result, syncthing is started or stopped, and {@link #onApiChange()} is
* called. * called.
*/ */
public void updateState() { public void updateState() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean prefStopMobileData = prefs.getBoolean("stop_sync_on_mobile_data", true); boolean prefStopMobileData = prefs.getBoolean("stop_sync_on_mobile_data", true);
boolean prefStopNotCharging = prefs.getBoolean("stop_sync_while_not_charging", true); boolean prefStopNotCharging = prefs.getBoolean("stop_sync_while_not_charging", true);
// Start syncthing. // Start syncthing.
if ((mDeviceStateHolder.isCharging() || !prefStopNotCharging) && if ((mDeviceStateHolder.isCharging() || !prefStopNotCharging) &&
(mDeviceStateHolder.isWifiConnected() || !prefStopMobileData)) { (mDeviceStateHolder.isWifiConnected() || !prefStopMobileData)) {
if (mCurrentState == State.ACTIVE || mCurrentState == State.STARTING) { if (mCurrentState == State.ACTIVE || mCurrentState == State.STARTING) {
mStopScheduled = false; mStopScheduled = false;
return; return;
} }
mCurrentState = State.STARTING; mCurrentState = State.STARTING;
registerOnWebGuiAvailableListener(mApi); registerOnWebGuiAvailableListener(mApi);
new PollWebGuiAvailableTask().execute(); new PollWebGuiAvailableTask().execute();
new Thread(new SyncthingRunnable(this)).start(); new Thread(new SyncthingRunnable(this)).start();
} }
// Stop syncthing. // Stop syncthing.
else { else {
if (mCurrentState == State.DISABLED) if (mCurrentState == State.DISABLED)
return; return;
mCurrentState = State.DISABLED; mCurrentState = State.DISABLED;
// Syncthing is currently started, perform the stop later. // Syncthing is currently started, perform the stop later.
if (mCurrentState == State.STARTING) { if (mCurrentState == State.STARTING) {
mStopScheduled = true; mStopScheduled = true;
} else if (mApi != null) { } else if (mApi != null) {
mApi.shutdown(); mApi.shutdown();
} }
} }
onApiChange(); onApiChange();
} }
/** /**
* Polls SYNCTHING_URL until it returns HTTP status OK, then calls all listeners * Polls SYNCTHING_URL until it returns HTTP status OK, then calls all listeners
* in mOnWebGuiAvailableListeners and clears it. * in mOnWebGuiAvailableListeners and clears it.
*/ */
private class PollWebGuiAvailableTask extends AsyncTask<Void, Void, Void> { private class PollWebGuiAvailableTask extends AsyncTask<Void, Void, Void> {
@Override @Override
protected Void doInBackground(Void... voids) { protected Void doInBackground(Void... voids) {
int status = 0; int status = 0;
HttpClient httpclient = new DefaultHttpClient(); HttpClient httpclient = new DefaultHttpClient();
HttpHead head = new HttpHead(mApi.getUrl()); HttpHead head = new HttpHead(mApi.getUrl());
do { do {
try { try {
Thread.sleep(WEB_GUI_POLL_INTERVAL); Thread.sleep(WEB_GUI_POLL_INTERVAL);
HttpResponse response = httpclient.execute(head); HttpResponse response = httpclient.execute(head);
// NOTE: status is not really needed, as HttpHostConnectException is thrown // NOTE: status is not really needed, as HttpHostConnectException is thrown
// earlier. // earlier.
status = response.getStatusLine().getStatusCode(); status = response.getStatusLine().getStatusCode();
} } catch (HttpHostConnectException e) {
catch (HttpHostConnectException e) { // We catch this in every call, as long as the service is not online,
// We catch this in every call, as long as the service is not online, // so we ignore and continue.
// so we ignore and continue. } catch (IOException e) {
} Log.w(TAG, "Failed to poll for web interface", e);
catch (IOException e) { } catch (InterruptedException e) {
Log.w(TAG, "Failed to poll for web interface", e); Log.w(TAG, "Failed to poll for web interface", e);
} }
catch (InterruptedException e) { } while (status != HttpStatus.SC_OK);
Log.w(TAG, "Failed to poll for web interface", e); return null;
} }
} while(status != HttpStatus.SC_OK);
return null;
}
@Override @Override
protected void onPostExecute(Void aVoid) { protected void onPostExecute(Void aVoid) {
if (mStopScheduled) { if (mStopScheduled) {
mCurrentState = State.DISABLED; mCurrentState = State.DISABLED;
onApiChange(); onApiChange();
mApi.shutdown(); mApi.shutdown();
mStopScheduled = false; mStopScheduled = false;
return; return;
} }
Log.i(TAG, "Web GUI has come online at " + mApi.getUrl()); Log.i(TAG, "Web GUI has come online at " + mApi.getUrl());
mCurrentState = State.ACTIVE; mCurrentState = State.ACTIVE;
for (OnWebGuiAvailableListener listener : mOnWebGuiAvailableListeners) { for (OnWebGuiAvailableListener listener : mOnWebGuiAvailableListeners) {
listener.onWebGuiAvailable(); listener.onWebGuiAvailable();
} }
mOnWebGuiAvailableListeners.clear(); mOnWebGuiAvailableListeners.clear();
} }
} }
/** /**
* Move config file, keys, and index files to "official" folder * Move config file, keys, and index files to "official" folder
* * <p/>
* Intended to bring the file locations in older installs in line with * Intended to bring the file locations in older installs in line with
* newer versions. * newer versions.
*/ */
private void moveConfigFiles() { private void moveConfigFiles() {
FilenameFilter idxFilter = new FilenameFilter() { FilenameFilter idxFilter = new FilenameFilter() {
public boolean accept(File dir, String name) { public boolean accept(File dir, String name) {
return name.endsWith(".idx.gz"); return name.endsWith(".idx.gz");
} }
}; };
if (new File(getApplicationInfo().dataDir, PUBLIC_KEY_FILE).exists()) { if (new File(getApplicationInfo().dataDir, PUBLIC_KEY_FILE).exists()) {
File publicKey = new File(getApplicationInfo().dataDir, PUBLIC_KEY_FILE); File publicKey = new File(getApplicationInfo().dataDir, PUBLIC_KEY_FILE);
publicKey.renameTo(new File(getFilesDir(), PUBLIC_KEY_FILE)); publicKey.renameTo(new File(getFilesDir(), PUBLIC_KEY_FILE));
File privateKey = new File(getApplicationInfo().dataDir, PRIVATE_KEY_FILE); File privateKey = new File(getApplicationInfo().dataDir, PRIVATE_KEY_FILE);
privateKey.renameTo(new File(getFilesDir(), PRIVATE_KEY_FILE)); privateKey.renameTo(new File(getFilesDir(), PRIVATE_KEY_FILE));
File config = new File(getApplicationInfo().dataDir, ConfigXml.CONFIG_FILE); File config = new File(getApplicationInfo().dataDir, ConfigXml.CONFIG_FILE);
config.renameTo(new File(getFilesDir(), ConfigXml.CONFIG_FILE)); config.renameTo(new File(getFilesDir(), ConfigXml.CONFIG_FILE));
File oldStorageDir = new File(getApplicationInfo().dataDir); File oldStorageDir = new File(getApplicationInfo().dataDir);
File[] files = oldStorageDir.listFiles(idxFilter); File[] files = oldStorageDir.listFiles(idxFilter);
for (File file : files) { for (File file : files) {
if (file.isFile()) { if (file.isFile()) {
file.renameTo(new File(getFilesDir(), file.getName())); file.renameTo(new File(getFilesDir(), file.getName()));
} }
} }
} }
} }
/** /**
* Creates notification, starts native binary. * Creates notification, starts native binary.
*/ */
@Override @Override
public void onCreate() { public void onCreate() {
PendingIntent pi = PendingIntent.getActivity( PendingIntent pi = PendingIntent.getActivity(
this, 0, new Intent(this, MainActivity.class), this, 0, new Intent(this, MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT);
Notification n = new NotificationCompat.Builder(this) Notification n = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.app_name)) .setContentTitle(getString(R.string.app_name))
.setSmallIcon(R.drawable.ic_launcher) .setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pi) .setContentIntent(pi)
.setPriority(NotificationCompat.PRIORITY_MIN) .setPriority(NotificationCompat.PRIORITY_MIN)
.build(); .build();
n.flags |= Notification.FLAG_ONGOING_EVENT; n.flags |= Notification.FLAG_ONGOING_EVENT;
startForeground(NOTIFICATION_RUNNING, n); startForeground(NOTIFICATION_RUNNING, n);
mDeviceStateHolder = new DeviceStateHolder(SyncthingService.this); mDeviceStateHolder = new DeviceStateHolder(SyncthingService.this);
registerReceiver(mDeviceStateHolder, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); registerReceiver(mDeviceStateHolder, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
new StartupTask().execute(); new StartupTask().execute();
} }
/** /**
* Sets up the initial configuration, updates the config when coming from an old * Sets up the initial configuration, updates the config when coming from an old
* version, and reads syncthing URL and API key (these are passed internally as * version, and reads syncthing URL and API key (these are passed internally as
* {@code Pair<String, String>}. * {@code Pair<String, String>}.
*/ */
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) {
moveConfigFiles(); moveConfigFiles();
ConfigXml config = new ConfigXml(SyncthingService.this); ConfigXml config = new ConfigXml(SyncthingService.this);
config.updateIfNeeded(); config.updateIfNeeded();
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");
config.createCameraRepo(); config.createCameraRepo();
} }
return new Pair<>(config.getWebGuiUrl(), config.getApiKey()); return new Pair<>(config.getWebGuiUrl(), config.getApiKey());
} }
@Override @Override
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 updateIfNeeded). // shutdown (eg Play Store updateIfNeeded).
// NOTE: This will log an exception if syncthing is not actually running. // NOTE: This will log an exception if syncthing is not actually running.
mApi.shutdown(); mApi.shutdown();
updateState(); updateState();
} }
} }
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return mBinder; return mBinder;
} }
/** /**
* Stops the native binary. * Stops the native binary.
*/ */
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
Log.i(TAG, "Shutting down service"); Log.i(TAG, "Shutting down service");
mApi.shutdown(); mApi.shutdown();
} }
/** /**
* Register a listener for the web gui becoming available.. * Register a listener for the web gui becoming available..
* * <p/>
* If the web gui is already available, listener will be called immediately. * If the web gui is already available, listener will be called immediately.
* 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 (mCurrentState == State.ACTIVE) { if (mCurrentState == State.ACTIVE) {
listener.onWebGuiAvailable(); listener.onWebGuiAvailable();
} } else {
else { mOnWebGuiAvailableListeners.add(listener);
mOnWebGuiAvailableListeners.add(listener); }
} }
}
/** /**
* Returns true if this service has not been started before (ie config.xml does not exist). * Returns true if this service has not been started before (ie config.xml does not exist).
* * <p/>
* This will return true until the public key file has been generated. * This will return true until the public key file has been generated.
*/ */
public boolean isFirstStart() { public boolean isFirstStart() {
return !new File(getFilesDir(), PUBLIC_KEY_FILE).exists(); return !new File(getFilesDir(), PUBLIC_KEY_FILE).exists();
} }
public RestApi getApi() { public RestApi getApi() {
return mApi; return mApi;
} }
/** /**
* Register a listener for the syncthing API state changing. * Register a listener for the syncthing API state changing.
* * <p/>
* The listener is called immediately with the current state, and again whenever the state * The listener is called immediately with the current state, and again whenever the state
* changes. * changes.
*/ */
public void registerOnApiChangeListener(OnApiChangeListener listener) { public void registerOnApiChangeListener(OnApiChangeListener listener) {
// Make sure we don't send an invalid state or syncthing might shwow a "disabled" message // Make sure we don't send an invalid state or syncthing might shwow a "disabled" message
// when it's just starting up. // when it's just starting up.
listener.onApiChange(mCurrentState); listener.onApiChange(mCurrentState);
mOnApiChangeListeners.add(new WeakReference<OnApiChangeListener>(listener)); mOnApiChangeListeners.add(new WeakReference<OnApiChangeListener>(listener));
} }
/** /**
* Called to notifiy listeners of an API change. * Called to notifiy listeners of an API change.
* * <p/>
* Must only be called from SyncthingService or {@link RestApi}. * Must only be called from SyncthingService or {@link RestApi}.
*/ */
public void onApiChange() { public void onApiChange() {
for (Iterator<WeakReference<OnApiChangeListener>> i = mOnApiChangeListeners.iterator(); for (Iterator<WeakReference<OnApiChangeListener>> i = mOnApiChangeListeners.iterator();
i.hasNext(); ) { i.hasNext(); ) {
WeakReference<OnApiChangeListener> listener = i.next(); WeakReference<OnApiChangeListener> listener = i.next();
if (listener.get() != null) { if (listener.get() != null) {
listener.get().onApiChange(mCurrentState); listener.get().onApiChange(mCurrentState);
} } else {
else { i.remove();
i.remove(); }
} }
} }
}
/** /**
* Dialog to be shown when attempting to start syncthing while it is disabled according * 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). * to settings (because the device is not charging or wifi is disconnected).
*/ */
public static void showDisabledDialog(final Activity activity) { public static void showDisabledDialog(final Activity activity) {
new AlertDialog.Builder(activity) new AlertDialog.Builder(activity)
.setTitle(R.string.syncthing_disabled_title) .setTitle(R.string.syncthing_disabled_title)
.setMessage(R.string.syncthing_disabled_message) .setMessage(R.string.syncthing_disabled_message)
.setPositiveButton(R.string.syncthing_disabled_change_settings, .setPositiveButton(R.string.syncthing_disabled_change_settings,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialogInterface, int i) { public void onClick(DialogInterface dialogInterface, int i) {
activity.finish(); activity.finish();
Intent intent = new Intent(activity, SettingsActivity.class) Intent intent = new Intent(activity, SettingsActivity.class)
.setAction(SettingsActivity.ACTION_APP_SETTINGS_FRAGMENT); .setAction(SettingsActivity.ACTION_APP_SETTINGS_FRAGMENT);
activity.startActivity(intent); activity.startActivity(intent);
} }
}) }
.setNegativeButton(R.string.exit, )
new DialogInterface.OnClickListener() { .setNegativeButton(R.string.exit,
@Override new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) { @Override
activity.finish(); public void onClick(DialogInterface dialogInterface, int i) {
} activity.finish();
} }
) }
.show() )
.setCancelable(false); .show()
} .setCancelable(false);
}
} }

View file

@ -4,14 +4,14 @@ import android.os.Binder;
public class SyncthingServiceBinder extends Binder { public class SyncthingServiceBinder extends Binder {
private final SyncthingService mService; private final SyncthingService mService;
public SyncthingServiceBinder(SyncthingService service) { public SyncthingServiceBinder(SyncthingService service) {
mService = service; mService = service;
} }
public SyncthingService getService() { public SyncthingService getService() {
return mService; return mService;
} }
} }

View file

@ -28,184 +28,180 @@ import javax.xml.transform.stream.StreamResult;
/** /**
* Provides direct access to the config.xml file in the file system. * Provides direct access to the config.xml file in the file system.
* * <p/>
* This class should only be used if the syncthing API is not available (usually during startup). * This class should only be used if the syncthing API is not available (usually during startup).
*/ */
public class ConfigXml { public class ConfigXml {
private static final String TAG = "ConfigXml"; private static final String TAG = "ConfigXml";
/** /**
* File in the config folder that contains configuration. * File in the config folder that contains configuration.
*/ */
public static final String CONFIG_FILE = "config.xml"; public static final String CONFIG_FILE = "config.xml";
private File mConfigFile; private File mConfigFile;
private Document mConfig; private Document mConfig;
public ConfigXml(Context context) { public ConfigXml(Context context) {
mConfigFile = new File(context.getFilesDir(), CONFIG_FILE); mConfigFile = new File(context.getFilesDir(), CONFIG_FILE);
if (!mConfigFile.exists()) { if (!mConfigFile.exists()) {
copyDefaultConfig(context); copyDefaultConfig(context);
} }
try { try {
DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
mConfig = db.parse(mConfigFile); mConfig = db.parse(mConfigFile);
} catch (SAXException | ParserConfigurationException | IOException e) { } catch (SAXException | ParserConfigurationException | IOException e) {
throw new RuntimeException("Failed to open config file", e); throw new RuntimeException("Failed to open config file", e);
} }
} }
public String getWebGuiUrl() { public String getWebGuiUrl() {
return "http://" + getGuiElement().getElementsByTagName("address").item(0).getTextContent(); return "http://" + getGuiElement().getElementsByTagName("address").item(0).getTextContent();
} }
public String getApiKey() { public String getApiKey() {
return getGuiElement().getElementsByTagName("apikey").item(0).getTextContent(); return getGuiElement().getElementsByTagName("apikey").item(0).getTextContent();
} }
/** /**
* Updates the config file. * Updates the config file.
* * <p/>
* Coming from 0.2.0 and earlier, globalAnnounceServer value "announce.syncthing.net:22025" is * Coming from 0.2.0 and earlier, globalAnnounceServer value "announce.syncthing.net:22025" is
* replaced with "194.126.249.5:22025" (as domain resolve is broken). * replaced with "194.126.249.5:22025" (as domain resolve is broken).
* * <p/>
* Coming from 0.3.0 and earlier, the ignorePerms flag is set to true on every repository. * Coming from 0.3.0 and earlier, the ignorePerms flag is set to true on every repository.
*/ */
@SuppressWarnings("SdCardPath") @SuppressWarnings("SdCardPath")
public void updateIfNeeded() { public void updateIfNeeded() {
Log.i(TAG, "Checking for needed config updates"); Log.i(TAG, "Checking for needed config updates");
boolean changed = false; boolean changed = false;
Element options = (Element) mConfig.getDocumentElement() Element options = (Element) mConfig.getDocumentElement()
.getElementsByTagName("options").item(0); .getElementsByTagName("options").item(0);
Element gui = (Element) mConfig.getDocumentElement() Element gui = (Element) mConfig.getDocumentElement()
.getElementsByTagName("gui").item(0); .getElementsByTagName("gui").item(0);
// Create an API key if it does not exist. // Create an API key if it does not exist.
if (gui.getElementsByTagName("apikey").getLength() == 0) { if (gui.getElementsByTagName("apikey").getLength() == 0) {
Log.i(TAG, "Initializing API key with random string"); Log.i(TAG, "Initializing API key with random string");
char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray(); char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
Random random = new Random(); Random random = new Random();
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
sb.append(chars[random.nextInt(chars.length)]); sb.append(chars[random.nextInt(chars.length)]);
} }
Element apiKey = mConfig.createElement("apikey"); Element apiKey = mConfig.createElement("apikey");
apiKey.setTextContent(sb.toString()); apiKey.setTextContent(sb.toString());
gui.appendChild(apiKey); gui.appendChild(apiKey);
changed = true; changed = true;
} }
// Hardcode default globalAnnounceServer ip. // Hardcode default globalAnnounceServer ip.
Element globalAnnounceServer = (Element) Element globalAnnounceServer = (Element)
options.getElementsByTagName("globalAnnounceServer").item(0); options.getElementsByTagName("globalAnnounceServer").item(0);
if (globalAnnounceServer.getTextContent().equals("announce.syncthing.net:22025")) { if (globalAnnounceServer.getTextContent().equals("announce.syncthing.net:22025")) {
Log.i(TAG, "Replacing globalAnnounceServer host with ip"); Log.i(TAG, "Replacing globalAnnounceServer host with ip");
globalAnnounceServer.setTextContent("194.126.249.5:22025"); globalAnnounceServer.setTextContent("194.126.249.5:22025");
changed = true; changed = true;
} }
NodeList repos = mConfig.getDocumentElement().getElementsByTagName("repository"); NodeList repos = mConfig.getDocumentElement().getElementsByTagName("repository");
for (int i = 0; i < repos.getLength(); i++) { for (int i = 0; i < repos.getLength(); i++) {
Element r = (Element) repos.item(i); Element r = (Element) repos.item(i);
// Set ignorePerms attribute. // Set ignorePerms attribute.
if (!r.hasAttribute("ignorePerms") || if (!r.hasAttribute("ignorePerms") ||
!Boolean.parseBoolean(r.getAttribute("ignorePerms"))) { !Boolean.parseBoolean(r.getAttribute("ignorePerms"))) {
Log.i(TAG, "Set 'ignorePerms' on repository " + r.getAttribute("id")); Log.i(TAG, "Set 'ignorePerms' on repository " + r.getAttribute("id"));
r.setAttribute("ignorePerms", Boolean.toString(true)); r.setAttribute("ignorePerms", Boolean.toString(true));
changed = true; changed = true;
} }
// Replace /sdcard/ in repository path with proper path. // Replace /sdcard/ in repository path with proper path.
String dir = r.getAttribute("directory"); String dir = r.getAttribute("directory");
if (dir.startsWith("/sdcard")) { if (dir.startsWith("/sdcard")) {
String newDir = dir.replace("/sdcard", String newDir = dir.replace("/sdcard",
Environment.getExternalStorageDirectory().getAbsolutePath()); Environment.getExternalStorageDirectory().getAbsolutePath());
r.setAttribute("directory", newDir); r.setAttribute("directory", newDir);
changed = true; changed = true;
} }
} }
// Change global announce server port to 22026 for syncthing v0.9.0. // Change global announce server port to 22026 for syncthing v0.9.0.
if (globalAnnounceServer.getTextContent().equals("194.126.249.5:22025")) { if (globalAnnounceServer.getTextContent().equals("194.126.249.5:22025")) {
Log.i(TAG, "Changing announce server port for v0.9.0"); Log.i(TAG, "Changing announce server port for v0.9.0");
globalAnnounceServer.setTextContent("194.126.249.5:22026"); globalAnnounceServer.setTextContent("194.126.249.5:22026");
changed = true; changed = true;
} }
if (changed) { if (changed) {
saveChanges(); saveChanges();
} }
} }
private Element getGuiElement() { private Element getGuiElement() {
return (Element) mConfig.getDocumentElement() return (Element) mConfig.getDocumentElement()
.getElementsByTagName("gui").item(0); .getElementsByTagName("gui").item(0);
} }
/** /**
* Creates a repository for the default camera folder. * Creates a repository for the default camera folder.
*/ */
public void createCameraRepo() { public void createCameraRepo() {
File cameraFolder = File cameraFolder =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM); Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
Element cameraRepo = mConfig.createElement("repository"); Element cameraRepo = mConfig.createElement("repository");
cameraRepo.setAttribute("id", "camera"); cameraRepo.setAttribute("id", "camera");
cameraRepo.setAttribute("directory", cameraFolder.getAbsolutePath()); cameraRepo.setAttribute("directory", cameraFolder.getAbsolutePath());
cameraRepo.setAttribute("ro", "true"); cameraRepo.setAttribute("ro", "true");
cameraRepo.setAttribute("ignorePerms", "true"); cameraRepo.setAttribute("ignorePerms", "true");
mConfig.getDocumentElement().appendChild(cameraRepo); mConfig.getDocumentElement().appendChild(cameraRepo);
saveChanges(); saveChanges();
} }
/** /**
* Writes updated mConfig back to file. * Writes updated mConfig back to file.
*/ */
private void saveChanges() { private void saveChanges() {
try { try {
Log.i(TAG, "Writing updated config back to file"); Log.i(TAG, "Writing updated config back to file");
TransformerFactory transformerFactory = TransformerFactory.newInstance(); TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer(); Transformer transformer = transformerFactory.newTransformer();
DOMSource domSource = new DOMSource(mConfig); DOMSource domSource = new DOMSource(mConfig);
StreamResult streamResult = new StreamResult(mConfigFile); StreamResult streamResult = new StreamResult(mConfigFile);
transformer.transform(domSource, streamResult); transformer.transform(domSource, streamResult);
} } catch (TransformerException e) {
catch (TransformerException e) { Log.w(TAG, "Failed to save updated config", e);
Log.w(TAG, "Failed to save updated config", e); }
} }
}
/** /**
* Copies the default config file from res/raw/config_default.xml to (data folder)/config.xml. * Copies the default config file from res/raw/config_default.xml to (data folder)/config.xml.
*/ */
private void copyDefaultConfig(Context context) { private void copyDefaultConfig(Context context) {
InputStream in = null; InputStream in = null;
FileOutputStream out = null; FileOutputStream out = null;
try { try {
in = context.getResources().openRawResource(R.raw.config_default); in = context.getResources().openRawResource(R.raw.config_default);
out = new FileOutputStream(mConfigFile); out = new FileOutputStream(mConfigFile);
byte[] buff = new byte[1024]; byte[] buff = new byte[1024];
int read; int read;
while ((read = in.read(buff)) > 0) { while ((read = in.read(buff)) > 0) {
out.write(buff, 0, read); out.write(buff, 0, read);
} }
} } catch (IOException e) {
catch (IOException e) { throw new RuntimeException("Failed to write config file", e);
throw new RuntimeException("Failed to write config file", e); } finally {
} try {
finally { in.close();
try { out.close();
in.close(); } catch (IOException e) {
out.close(); Log.w(TAG, "Failed to close stream while copying config", e);
} }
catch (IOException e) { }
Log.w(TAG, "Failed to close stream while copying config", e); }
}
}
}
} }

View file

@ -8,15 +8,15 @@ import android.preference.CheckBoxPreference;
*/ */
public class ExtendedCheckBoxPreference extends CheckBoxPreference { public class ExtendedCheckBoxPreference extends CheckBoxPreference {
private final Object mObject; private final Object mObject;
public ExtendedCheckBoxPreference(Context context, Object object) { public ExtendedCheckBoxPreference(Context context, Object object) {
super(context); super(context);
mObject = object; mObject = object;
} }
public Object getObject() { public Object getObject() {
return mObject; return mObject;
} }
} }

View file

@ -20,79 +20,77 @@ import java.util.Map;
* Generates item views for node items. * Generates item views for node items.
*/ */
public class NodesAdapter extends ArrayAdapter<RestApi.Node> public class NodesAdapter extends ArrayAdapter<RestApi.Node>
implements RestApi.OnReceiveConnectionsListener { implements RestApi.OnReceiveConnectionsListener {
private Map<String, RestApi.Connection> mConnections = private Map<String, RestApi.Connection> mConnections =
new HashMap<>(); new HashMap<>();
public NodesAdapter(Context context) { public NodesAdapter(Context context) {
super(context, R.layout.node_list_item); super(context, R.layout.node_list_item);
} }
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) { if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) getContext() LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE); .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.node_list_item, parent, false); convertView = inflater.inflate(R.layout.node_list_item, parent, false);
} }
TextView name = (TextView) convertView.findViewById(R.id.name); TextView name = (TextView) convertView.findViewById(R.id.name);
TextView status = (TextView) convertView.findViewById(R.id.status); TextView status = (TextView) convertView.findViewById(R.id.status);
TextView download = (TextView) convertView.findViewById(R.id.download); TextView download = (TextView) convertView.findViewById(R.id.download);
TextView upload = (TextView) convertView.findViewById(R.id.upload); TextView upload = (TextView) convertView.findViewById(R.id.upload);
name.setText(getItem(position).Name); name.setText(getItem(position).Name);
final String nodeId = getItem(position).NodeID; final String nodeId = getItem(position).NodeID;
RestApi.Connection conn = mConnections.get(nodeId); RestApi.Connection conn = mConnections.get(nodeId);
Resources res = getContext().getResources(); Resources res = getContext().getResources();
if (conn != null) { if (conn != null) {
if (conn.Completion == 100) { if (conn.Completion == 100) {
status.setText(res.getString(R.string.node_up_to_date)); status.setText(res.getString(R.string.node_up_to_date));
status.setTextColor(res.getColor(R.color.text_green)); status.setTextColor(res.getColor(R.color.text_green));
} } else {
else { status.setText(res.getString(R.string.node_syncing, conn.Completion));
status.setText(res.getString(R.string.node_syncing, conn.Completion)); status.setTextColor(res.getColor(R.color.text_blue));
status.setTextColor(res.getColor(R.color.text_blue)); }
} download.setText(RestApi.readableTransferRate(getContext(), conn.InBits));
download.setText(RestApi.readableTransferRate(getContext(), conn.InBits)); upload.setText(RestApi.readableTransferRate(getContext(), conn.OutBits));
upload.setText(RestApi.readableTransferRate(getContext(), conn.OutBits)); } else {
} download.setText("0 " + res.getStringArray(R.array.transfer_rate_units)[0]);
else { upload.setText("0 " + res.getStringArray(R.array.transfer_rate_units)[0]);
download.setText("0 " + res.getStringArray(R.array.transfer_rate_units)[0]); status.setText(res.getString(R.string.node_disconnected));
upload.setText("0 " + res.getStringArray(R.array.transfer_rate_units)[0]); status.setTextColor(res.getColor(R.color.text_red));
status.setText(res.getString(R.string.node_disconnected)); }
status.setTextColor(res.getColor(R.color.text_red));
}
return convertView; return convertView;
} }
/** /**
* Replacement for addAll, which is not implemented on lower API levels. * Replacement for addAll, which is not implemented on lower API levels.
*/ */
public void add(List<RestApi.Node> nodes) { public void add(List<RestApi.Node> nodes) {
for (RestApi.Node n : nodes) { for (RestApi.Node n : nodes) {
add(n); add(n);
} }
} }
/** /**
* Requests new connection info for all nodes visible in listView. * Requests new connection info for all nodes visible in listView.
*/ */
public void updateConnections(RestApi api, ListView listView) { public void updateConnections(RestApi api, ListView listView) {
for (int i = 0; i < getCount(); i++) { for (int i = 0; i < getCount(); i++) {
if ( i >= listView.getFirstVisiblePosition() && if (i >= listView.getFirstVisiblePosition() &&
i <= listView.getLastVisiblePosition()) { i <= listView.getLastVisiblePosition()) {
api.getConnections(this); api.getConnections(this);
} }
} }
} }
@Override @Override
public void onReceiveConnections(Map<String, RestApi.Connection> connections) { public void onReceiveConnections(Map<String, RestApi.Connection> connections) {
mConnections = connections; mConnections = connections;
notifyDataSetInvalidated(); notifyDataSetInvalidated();
} }
} }

View file

@ -18,77 +18,77 @@ import java.util.List;
* Generates item views for repository items. * Generates item views for repository items.
*/ */
public class ReposAdapter extends ArrayAdapter<RestApi.Repo> public class ReposAdapter extends ArrayAdapter<RestApi.Repo>
implements RestApi.OnReceiveModelListener { implements RestApi.OnReceiveModelListener {
private HashMap<String, RestApi.Model> mModels = new HashMap<>(); private HashMap<String, RestApi.Model> mModels = new HashMap<>();
public ReposAdapter(Context context) { public ReposAdapter(Context context) {
super(context, R.layout.repo_list_item); super(context, R.layout.repo_list_item);
} }
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) { if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) getContext() LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE); .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.repo_list_item, parent, false); convertView = inflater.inflate(R.layout.repo_list_item, parent, false);
} }
TextView id = (TextView) convertView.findViewById(R.id.id); TextView id = (TextView) convertView.findViewById(R.id.id);
TextView state = (TextView) convertView.findViewById(R.id.state); TextView state = (TextView) convertView.findViewById(R.id.state);
TextView folder = (TextView) convertView.findViewById(R.id.folder); TextView folder = (TextView) convertView.findViewById(R.id.folder);
TextView items = (TextView) convertView.findViewById(R.id.items); TextView items = (TextView) convertView.findViewById(R.id.items);
TextView size = (TextView) convertView.findViewById(R.id.size); TextView size = (TextView) convertView.findViewById(R.id.size);
TextView invalid = (TextView) convertView.findViewById(R.id.invalid); TextView invalid = (TextView) convertView.findViewById(R.id.invalid);
id.setText(getItem(position).ID); id.setText(getItem(position).ID);
state.setTextColor(getContext().getResources().getColor(R.color.text_green)); state.setTextColor(getContext().getResources().getColor(R.color.text_green));
folder.setText((getItem(position).Directory)); folder.setText((getItem(position).Directory));
RestApi.Model model = mModels.get(getItem(position).ID); RestApi.Model model = mModels.get(getItem(position).ID);
if (model != null) { if (model != null) {
state.setText(getContext().getString(R.string.repo_progress_format, model.state, state.setText(getContext().getString(R.string.repo_progress_format, model.state,
(model.globalBytes <= 0) (model.globalBytes <= 0)
? 100 ? 100
: (int) ((model.localBytes / (float) model.globalBytes) * 100))); : (int) ((model.localBytes / (float) model.globalBytes) * 100)
items.setText(getContext() ));
.getString(R.string.files, model.localFiles, model.globalFiles)); items.setText(getContext()
size.setText(RestApi.readableFileSize(getContext(), model.localBytes) + " / " + .getString(R.string.files, model.localFiles, model.globalFiles));
RestApi.readableFileSize(getContext(), model.globalBytes)); size.setText(RestApi.readableFileSize(getContext(), model.localBytes) + " / " +
invalid.setText(model.invalid); RestApi.readableFileSize(getContext(), model.globalBytes));
invalid.setVisibility((model.invalid.equals("")) ? View.GONE : View.VISIBLE); invalid.setText(model.invalid);
} invalid.setVisibility((model.invalid.equals("")) ? View.GONE : View.VISIBLE);
else { } else {
invalid.setVisibility(View.GONE); invalid.setVisibility(View.GONE);
} }
return convertView; return convertView;
} }
/** /**
* Replacement for addAll, which is not implemented on lower API levels. * Replacement for addAll, which is not implemented on lower API levels.
*/ */
public void add(List<RestApi.Repo> nodes) { public void add(List<RestApi.Repo> nodes) {
for (RestApi.Repo r : nodes) { for (RestApi.Repo r : nodes) {
add(r); add(r);
} }
} }
/** /**
* Requests updated model info from the api for all visible items. * Requests updated model info from the api for all visible items.
*/ */
public void updateModel(RestApi api, ListView listView) { public void updateModel(RestApi api, ListView listView) {
for (int i = 0; i < getCount(); i++) { for (int i = 0; i < getCount(); i++) {
if ( i >= listView.getFirstVisiblePosition() && if (i >= listView.getFirstVisiblePosition() &&
i <= listView.getLastVisiblePosition()) { i <= listView.getLastVisiblePosition()) {
api.getModel(getItem(i).ID, this); api.getModel(getItem(i).ID, this);
} }
} }
} }
@Override @Override
public void onReceiveModel(String repoId, RestApi.Model model) { public void onReceiveModel(String repoId, RestApi.Model model) {
mModels.put(repoId, model); mModels.put(repoId, model);
notifyDataSetChanged(); notifyDataSetChanged();
} }
} }

View file

@ -1,17 +1,17 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" > android:layout_height="match_parent">
<ListView <ListView
android:id="@android:id/list" android:id="@android:id/list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<TextView <TextView
android:id="@android:id/empty" android:id="@android:id/empty"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/directory_empty" android:text="@string/directory_empty"
android:layout_centerInParent="true"/> android:layout_centerInParent="true" />
</RelativeLayout> </RelativeLayout>

View file

@ -1,24 +1,24 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_gravity="center" android:layout_gravity="center"
android:padding="10dip" android:padding="10dip"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" > android:layout_height="wrap_content">
<ProgressBar <ProgressBar
style="?android:attr/progressBarStyleLarge" style="?android:attr/progressBarStyleLarge"
android:id="@+id/progress" android:id="@+id/progress"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<TextView <TextView
android:id="@+id/loading_text" android:id="@+id/loading_text"
android:layout_toRightOf="@id/progress" android:layout_toRightOf="@id/progress"
android:layout_alignBottom="@id/progress" android:layout_alignBottom="@id/progress"
android:layout_alignTop="@id/progress" android:layout_alignTop="@id/progress"
android:gravity="center" android:gravity="center"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</RelativeLayout> </RelativeLayout>

View file

@ -1,117 +1,123 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:orientation="vertical"
android:padding="10dip" android:padding="10dip"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" > android:layout_height="match_parent">
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<TextView
android:id="@+id/node_id_title"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView <TextView
android:id="@+id/node_id" android:id="@+id/node_id_title"
android:lines="1" android:textStyle="bold"
android:ellipsize="end" android:layout_width="wrap_content"
android:layout_below="@id/node_id_title" android:layout_height="wrap_content" />
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
<RelativeLayout <TextView
android:layout_width="match_parent" android:id="@+id/node_id"
android:layout_height="wrap_content" > android:lines="1"
<TextView android:ellipsize="end"
android:id="@+id/cpu_usage_title" android:layout_below="@id/node_id_title"
android:text="@string/cpu_usage" android:layout_width="wrap_content"
android:textStyle="bold" android:layout_height="wrap_content" />
android:layout_width="wrap_content" </RelativeLayout>
android:layout_height="wrap_content" />
<TextView <RelativeLayout
android:id="@+id/cpu_usage" android:layout_width="match_parent"
android:layout_alignBottom="@id/cpu_usage_title" android:layout_height="wrap_content">
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
<RelativeLayout <TextView
android:layout_width="match_parent" android:id="@+id/cpu_usage_title"
android:layout_height="wrap_content" > android:text="@string/cpu_usage"
<TextView android:textStyle="bold"
android:id="@+id/ram_usage_title" android:layout_width="wrap_content"
android:text="@string/ram_usage" android:layout_height="wrap_content" />
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView <TextView
android:id="@+id/ram_usage" android:id="@+id/cpu_usage"
android:layout_alignBottom="@id/ram_usage_title" android:layout_alignBottom="@id/cpu_usage_title"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</RelativeLayout> </RelativeLayout>
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" > android:layout_height="wrap_content">
<TextView
android:id="@+id/download_title"
android:text="@string/download_title"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView <TextView
android:id="@+id/download" android:id="@+id/ram_usage_title"
android:layout_alignBottom="@id/download_title" android:text="@string/ram_usage"
android:layout_alignParentRight="true" android:textStyle="bold"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</RelativeLayout>
<RelativeLayout <TextView
android:layout_width="match_parent" android:id="@+id/ram_usage"
android:layout_height="wrap_content" > android:layout_alignBottom="@id/ram_usage_title"
<TextView android:layout_alignParentRight="true"
android:id="@+id/upload_title" android:layout_width="wrap_content"
android:text="@string/upload_title" android:layout_height="wrap_content" />
android:textStyle="bold" </RelativeLayout>
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView <RelativeLayout
android:id="@+id/upload" android:layout_width="match_parent"
android:layout_alignBottom="@id/upload_title" android:layout_height="wrap_content">
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
<RelativeLayout <TextView
android:layout_width="match_parent" android:id="@+id/download_title"
android:layout_height="wrap_content" > android:text="@string/download_title"
<TextView android:textStyle="bold"
android:id="@+id/announce_server_title" android:layout_width="wrap_content"
android:text="@string/announce_server" android:layout_height="wrap_content" />
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView <TextView
android:id="@+id/announce_server" android:id="@+id/download"
android:layout_alignBottom="@id/announce_server_title" android:layout_alignBottom="@id/download_title"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</RelativeLayout> </RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/upload_title"
android:text="@string/upload_title"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/upload"
android:layout_alignBottom="@id/upload_title"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/announce_server_title"
android:text="@string/announce_server"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/announce_server"
android:layout_alignBottom="@id/announce_server_title"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
</LinearLayout> </LinearLayout>

View file

@ -1,23 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/drawer_layout"
android:id="@+id/drawer_layout" android:layout_width="match_parent"
android:layout_width="match_parent" android:layout_height="match_parent">
android:layout_height="match_parent">
<android.support.v4.view.ViewPager <android.support.v4.view.ViewPager
android:id="@+id/pager" android:id="@+id/pager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<LinearLayout <LinearLayout
android:id="@+id/drawer" android:id="@+id/drawer"
android:orientation="vertical" android:orientation="vertical"
android:layout_gravity="start" android:layout_gravity="start"
android:layout_width="260dip" android:layout_width="260dip"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clickable="true" android:clickable="true"
android:background="@android:color/background_light" /> android:background="@android:color/background_light" />
</android.support.v4.widget.DrawerLayout> </android.support.v4.widget.DrawerLayout>

View file

@ -1,59 +1,59 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="4dip" > android:padding="4dip">
<TextView <TextView
android:id="@+id/name" android:id="@+id/name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_toLeftOf="@+id/status" android:layout_toLeftOf="@+id/status"
android:lines="1" android:lines="1"
android:ellipsize="end" /> android:ellipsize="end" />
<TextView <TextView
android:id="@+id/status" android:id="@+id/status"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_alignBottom="@+id/name" android:layout_alignBottom="@+id/name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium" />
android:ellipsize="end" />
<TextView <TextView
android:id="@+id/download_title" android:id="@+id/download_title"
android:text="@string/download_title_colon" android:text="@string/download_title_colon"
android:layout_below="@id/name" android:layout_below="@id/name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" /> android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView <TextView
android:id="@+id/download" android:id="@+id/download"
android:layout_alignBaseline="@id/download_title" android:layout_alignBaseline="@id/download_title"
android:layout_toRightOf="@id/download_title" android:layout_toRightOf="@id/download_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" /> android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView <TextView
android:id="@+id/upload_title" android:id="@+id/upload_title"
android:text="@string/upload_title_colon" android:text="@string/upload_title_colon"
android:layout_below="@id/download_title" android:layout_below="@id/download_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" /> android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView <TextView
android:id="@+id/upload" android:id="@+id/upload"
android:layout_alignBaseline="@id/upload_title" android:layout_alignBaseline="@id/upload_title"
android:layout_toRightOf="@id/upload_title" android:layout_toRightOf="@id/upload_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" /> android:textAppearance="?android:attr/textAppearanceSmall" />
>>>>>>> Changed code style to use spaces instead of tabs.
</RelativeLayout> </RelativeLayout>

View file

@ -1,55 +1,55 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="4dip" > android:padding="4dip">
<TextView <TextView
android:id="@+id/id" android:id="@+id/id"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_toLeftOf="@+id/state" android:layout_toLeftOf="@+id/state"
android:lines="1" android:lines="1"
android:ellipsize="end" /> android:ellipsize="end" />
<TextView <TextView
android:id="@+id/state" android:id="@+id/state"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_alignBottom="@+id/id" android:layout_alignBottom="@+id/id"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<TextView <TextView
android:id="@+id/folder" android:id="@+id/folder"
android:layout_below="@id/state" android:layout_below="@id/state"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" /> android:ellipsize="end" />
<TextView <TextView
android:id="@+id/items" android:id="@+id/items"
android:layout_below="@id/folder" android:layout_below="@id/folder"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<TextView <TextView
android:id="@+id/size" android:id="@+id/size"
android:layout_below="@id/items" android:layout_below="@id/items"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<TextView <TextView
android:id="@+id/invalid" android:id="@+id/invalid"
android:textColor="@color/text_red" android:textColor="@color/text_red"
android:layout_below="@id/size" android:layout_below="@id/size"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</RelativeLayout> </RelativeLayout>

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android" <ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="48dip" android:layout_width="48dip"
android:layout_height="48dip" android:layout_height="48dip"
android:src="@drawable/ic_qrcode" android:src="@drawable/ic_qrcode"
android:onClick="onClick" android:onClick="onClick"
android:contentDescription="@string/scan_qr_code_description" /> android:contentDescription="@string/scan_qr_code_description" />

View file

@ -1,37 +1,36 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_width="match_parent" android:layout_height="match_parent">
android:layout_height="match_parent" >
<WebView <WebView
android:id="@+id/webview" android:id="@+id/webview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:visibility="gone" /> android:visibility="gone" />
<RelativeLayout <RelativeLayout
android:id="@+id/loading" android:id="@+id/loading"
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" > android:layout_height="wrap_content">
<ProgressBar <ProgressBar
android:id="@+id/progress" android:id="@+id/progress"
android:layout_centerInParent="true" android:layout_centerInParent="true"
style="?android:attr/progressBarStyleLarge" style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="10dip" /> android:layout_marginBottom="10dip" />
<TextView <TextView
android:id="@+id/loading_text" android:id="@+id/loading_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/progress" android:layout_below="@id/progress"
android:text="@string/web_gui_loading" /> android:text="@string/web_gui_loading" />
</RelativeLayout> </RelativeLayout>
</RelativeLayout> </RelativeLayout>

View file

@ -1,17 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto" >
<item <item
android:id="@+id/select" android:id="@+id/select"
android:title="@string/select_folder" android:title="@string/select_folder"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/create_folder" android:id="@+id/create_folder"
android:title="@string/create_folder" android:title="@string/create_folder"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
</menu> </menu>

View file

@ -1,32 +1,31 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto" >
<item <item
android:id="@+id/share_node_id" android:id="@+id/share_node_id"
android:icon="@android:drawable/ic_menu_share" android:icon="@android:drawable/ic_menu_share"
android:title="@string/share_node_id" android:title="@string/share_node_id"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/add_repo" android:id="@+id/add_repo"
android:title="@string/add_repo" android:title="@string/add_repo"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/add_node" android:id="@+id/add_node"
android:title="@string/add_node" android:title="@string/add_node"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/web_gui" android:id="@+id/web_gui"
android:title="@string/web_gui_title" /> android:title="@string/web_gui_title" />
<item <item
android:id="@+id/settings" android:id="@+id/settings"
android:title="@string/settings_title" /> android:title="@string/settings_title" />
</menu> </menu>

View file

@ -1,23 +1,23 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" > xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/create" android:id="@+id/create"
android:title="@string/add" android:title="@string/add"
app:showAsAction="always" /> app:showAsAction="always" />
<item <item
android:id="@+id/share_node_id" android:id="@+id/share_node_id"
android:icon="@android:drawable/ic_menu_share" android:icon="@android:drawable/ic_menu_share"
android:title="@string/share_node_id" android:title="@string/share_node_id"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/delete" android:id="@+id/delete"
android:icon="@drawable/ic_delete" android:icon="@drawable/ic_delete"
android:title="@string/delete_node" android:title="@string/delete_node"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
</menu> </menu>

View file

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" > xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/create" android:id="@+id/create"
android:title="@string/create" android:title="@string/create"
app:showAsAction="always" /> app:showAsAction="always" />
<item <item
android:id="@+id/delete" android:id="@+id/delete"
android:icon="@drawable/ic_delete" android:icon="@drawable/ic_delete"
android:title="@string/delete_repo" android:title="@string/delete_repo"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
</menu> </menu>

View file

@ -1,18 +1,18 @@
<configuration version="2"> <configuration version="2">
<gui enabled="true"> <gui enabled="true">
<address>127.0.0.1:8080</address> <address>127.0.0.1:8080</address>
</gui> </gui>
<options> <options>
<listenAddress>0.0.0.0:22000</listenAddress> <listenAddress>0.0.0.0:22000</listenAddress>
<globalAnnounceServer>194.126.249.5:22026</globalAnnounceServer> <globalAnnounceServer>194.126.249.5:22026</globalAnnounceServer>
<globalAnnounceEnabled>true</globalAnnounceEnabled> <globalAnnounceEnabled>true</globalAnnounceEnabled>
<localAnnounceEnabled>true</localAnnounceEnabled> <localAnnounceEnabled>true</localAnnounceEnabled>
<parallelRequests>16</parallelRequests> <parallelRequests>16</parallelRequests>
<maxSendKbps>0</maxSendKbps> <maxSendKbps>0</maxSendKbps>
<rescanIntervalS>60</rescanIntervalS> <rescanIntervalS>60</rescanIntervalS>
<reconnectionIntervalS>60</reconnectionIntervalS> <reconnectionIntervalS>60</reconnectionIntervalS>
<maxChangeKbps>1000</maxChangeKbps> <maxChangeKbps>1000</maxChangeKbps>
<startBrowser>false</startBrowser> <startBrowser>false</startBrowser>
<upnpEnabled>true</upnpEnabled> <upnpEnabled>true</upnpEnabled>
</options> </options>
</configuration> </configuration>

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="AppTheme" parent="@style/Theme.AppCompat.Light.DarkActionBar" />
<style name="AppTheme" parent="@style/Theme.AppCompat.Light.DarkActionBar" />
</resources> </resources>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="text_red">#ffff4444</color> <color name="text_red">#ffff4444</color>
<color name="text_blue">#ff33b5e5</color> <color name="text_blue">#ff33b5e5</color>
<color name="text_green">#ff99cc00</color> <color name="text_green">#ff99cc00</color>
</resources> </resources>

View file

@ -1,234 +1,234 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Syncthing</string> <string name="app_name">Syncthing</string>
<!-- MainActivity --> <!-- MainActivity -->
<!-- Title of the "add repo" menu action --> <!-- Title of the "add repo" menu action -->
<string name="add_repo">Add Repository</string> <string name="add_repo">Add Repository</string>
<!-- Title of the "share node id" menu action --> <!-- Title of the "share node id" menu action -->
<string name="share_node_id">Share Node ID</string> <string name="share_node_id">Share Node ID</string>
<!-- Shown in the chooser dialog when sharing a Node ID --> <!-- Shown in the chooser dialog when sharing a Node ID -->
<string name="send_node_id_to">Send Node ID to</string> <string name="send_node_id_to">Send Node ID to</string>
<!-- Text for RepositoriesFragment and NodesFragment loading view --> <!-- Text for RepositoriesFragment and NodesFragment loading view -->
<string name="api_loading">Waiting for API</string> <string name="api_loading">Waiting for API</string>
<!-- 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 -->
<string name="repo_progress_format">%1$s (%2$d%%)</string> <string name="repo_progress_format">%1$s (%2$d%%)</string>
<!-- Shown if no repos exist --> <!-- Shown if no repos exist -->
<string name="repositories_list_empty">No repositories found</string> <string name="repositories_list_empty">No repositories found</string>
<!-- Format string for repository file count --> <!-- Format string for repository file count -->
<string name="files">%1$d / %2$d Files</string> <string name="files">%1$d / %2$d Files</string>
<!-- 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 -->
<string name="nodes_list_empty">No nodes found</string> <string name="nodes_list_empty">No nodes found</string>
<!-- Indicates that a repo is fully synced to the local node --> <!-- Indicates that a repo is fully synced to the local node -->
<string name="node_up_to_date">Up to Date</string> <string name="node_up_to_date">Up to Date</string>
<!-- Indicates that the node is currently syncing. Parameter is sync percentage --> <!-- Indicates that the node is currently syncing. Parameter is sync percentage -->
<string name="node_syncing">Syncing (%1$d%%)</string> <string name="node_syncing">Syncing (%1$d%%)</string>
<!-- Indicates that there is no connection to the node --> <!-- Indicates that there is no connection to the node -->
<string name="node_disconnected">Disconnected</string> <string name="node_disconnected">Disconnected</string>
<!-- Title for current download rate --> <!-- Title for current download rate -->
<string name="download_title">Download</string> <string name="download_title">Download</string>
<!-- Title for current upload rate --> <!-- Title for current upload rate -->
<string name="upload_title">Upload</string> <string name="upload_title">Upload</string>
<!-- LocalNodeInfoFragment --> <!-- LocalNodeInfoFragment -->
<!-- ActionBar title shown when the drawer is open --> <!-- ActionBar title shown when the drawer is open -->
<string name="system_info">System Info</string> <string name="system_info">System Info</string>
<!-- Same as download_title with a colon and space appended --> <!-- Same as download_title with a colon and space appended -->
<string name="download_title_colon">Download:\u0020</string> <string name="download_title_colon">Download:\u0020</string>
<!-- Same as upload_title with a colon and space appended --> <!-- Same as upload_title with a colon and space appended -->
<string name="upload_title_colon">Upload:\u0020</string> <string name="upload_title_colon">Upload:\u0020</string>
<!-- Title for current CPU usage --> <!-- Title for current CPU usage -->
<string name="cpu_usage">CPU Usage</string> <string name="cpu_usage">CPU Usage</string>
<!-- Title for current RAM usage --> <!-- Title for current RAM usage -->
<string name="ram_usage">RAM Usage</string> <string name="ram_usage">RAM Usage</string>
<!-- Title for announce server status --> <!-- Title for announce server status -->
<string name="announce_server">Announce Server</string> <string name="announce_server">Announce Server</string>
<!-- RepoSettingsFragment --> <!-- RepoSettingsFragment -->
<!-- Setting title --> <!-- Setting title -->
<string name="repo_id">Repository ID</string> <string name="repo_id">Repository ID</string>
<!-- Setting title --> <!-- Setting title -->
<string name="directory">Directory</string> <string name="directory">Directory</string>
<!-- Setting title --> <!-- Setting title -->
<string name="repo_master">Repository Master</string> <string name="repo_master">Repository Master</string>
<!-- Setting title --> <!-- Setting title -->
<string name="nodes">Nodes</string> <string name="nodes">Nodes</string>
<!-- Setting title --> <!-- Setting title -->
<string name="file_versioning">File Versioning</string> <string name="file_versioning">File Versioning</string>
<!-- Setting title --> <!-- Setting title -->
<string name="keep_versions">Keep Versions</string> <string name="keep_versions">Keep Versions</string>
<!-- Setting title --> <!-- Setting title -->
<string name="delete_repo">Delete Repository</string> <string name="delete_repo">Delete Repository</string>
<!-- Title for RepoSettingsFragment in create mode --> <!-- Title for RepoSettingsFragment in create mode -->
<string name="create_repo">Create Repository</string> <string name="create_repo">Create Repository</string>
<!-- Title for RepoSettingsFragment in edit mode --> <!-- Title for RepoSettingsFragment in edit mode -->
<string name="edit_repo">Edit Repository</string> <string name="edit_repo">Edit Repository</string>
<!-- Menu item to confirm repo creation --> <!-- Menu item to confirm repo creation -->
<string name="create">Create</string> <string name="create">Create</string>
<!-- Dialog shown when attempting to delete a repository --> <!-- Dialog shown when attempting to delete a repository -->
<string name="delete_repo_confirm">Do you really want to delete this repository?</string> <string name="delete_repo_confirm">Do you really want to delete this repository?</string>
<!-- Toast shown when trying to create a repository with an empty ID --> <!-- Toast shown when trying to create a repository with an empty ID -->
<string name="repo_id_required">The repository ID must not be empty</string> <string name="repo_id_required">The repository ID must not be empty</string>
<!-- Toast shown when trying to create a repository with an empty path --> <!-- Toast shown when trying to create a repository with an empty path -->
<string name="repo_path_required">The repository path must not be empty</string> <string name="repo_path_required">The repository path must not be empty</string>
<!-- Toast shown when selecting 'nodes' if no nodes have been added --> <!-- Toast shown when selecting 'nodes' if no nodes have been added -->
<string name="no_nodes">Please connect a node first.</string> <string name="no_nodes">Please connect a node first.</string>
<!-- NodeSettingsFragment --> <!-- NodeSettingsFragment -->
<!-- Setting title --> <!-- Setting title -->
<string name="node_id">Node ID</string> <string name="node_id">Node ID</string>
<!-- Setting title --> <!-- Setting title -->
<string name="name">Name</string> <string name="name">Name</string>
<!-- Setting title --> <!-- Setting title -->
<string name="addresses">Addresses</string> <string name="addresses">Addresses</string>
<!-- Setting title --> <!-- Setting title -->
<string name="current_address">Current Address</string> <string name="current_address">Current Address</string>
<!-- Setting title --> <!-- Setting title -->
<string name="delete_node">Delete Node</string> <string name="delete_node">Delete Node</string>
<!-- Title for NodeSettingsFragment in create mode --> <!-- Title for NodeSettingsFragment in create mode -->
<string name="add_node">Add Node</string> <string name="add_node">Add Node</string>
<!-- Menu item to confirm adding a node --> <!-- Menu item to confirm adding a node -->
<string name="add">Add</string> <string name="add">Add</string>
<!-- Title for NodeSettingsFragment in edit mode --> <!-- Title for NodeSettingsFragment in edit mode -->
<string name="edit_node">Edit Node</string> <string name="edit_node">Edit Node</string>
<!-- Dialog shown when attempting to delete a node --> <!-- Dialog shown when attempting to delete a node -->
<string name="delete_node_confirm">Do you really want to delete this node?</string> <string name="delete_node_confirm">Do you really want to delete this node?</string>
<!-- Toast shown when trying to create a node with an empty ID --> <!-- Toast shown when trying to create a node with an empty ID -->
<string name="node_id_required">The node ID must not be empty</string> <string name="node_id_required">The node ID must not be empty</string>
<!-- Toast shown when trying to create a node with an empty name --> <!-- Toast shown when trying to create a node with an empty name -->
<string name="node_name_required">The node name must not be empty</string> <string name="node_name_required">The node name must not be empty</string>
<!-- Content description for node ID qr code icon --> <!-- Content description for node ID qr code icon -->
<string name="scan_qr_code_description">Scan QR Code</string> <string name="scan_qr_code_description">Scan QR Code</string>
<!-- Text for toast shown if the "scan QR code" intent fails with an ActivityNotFoundException --> <!-- Text for toast shown if the "scan QR code" intent fails with an ActivityNotFoundException -->
<string name="no_qr_scanner_installed">You have no QR scanner installed or your scanner is not supported. \ <string name="no_qr_scanner_installed">You have no QR scanner installed or your scanner is not supported. \
Please install a scanner or enter the node ID manually.</string> Please install a scanner or enter the node ID manually.</string>
<!-- WebGuiActivity --> <!-- WebGuiActivity -->
<!-- Title of the web gui activity --> <!-- Title of the web gui activity -->
<string name="web_gui_title">Web GUI</string> <string name="web_gui_title">Web GUI</string>
<!-- Text for WebGuiActivity loading view --> <!-- Text for WebGuiActivity loading view -->
<string name="web_gui_loading">Waiting for GUI</string> <string name="web_gui_loading">Waiting for GUI</string>
<!-- Shown instead of web_gui_loading if the key does not exist and has to be created --> <!-- Shown instead of web_gui_loading if the key does not exist and has to be created -->
<string name="web_gui_creating_key">Generating keys. This may take a while.</string> <string name="web_gui_creating_key">Generating keys. This may take a while.</string>
<!-- Title for dialog displayed on first start --> <!-- Title for dialog displayed on first start -->
<string name="welcome_title">First Start</string> <string name="welcome_title">First Start</string>
<!-- Text for dialog displayed on first start --> <!-- Text for dialog displayed on first start -->
<string name="welcome_text">Welcome to Syncthing for Android!\n\n\ <string name="welcome_text">Welcome to Syncthing for Android!\n\n\
This app is currently in Alpha state, and you may experience bugs, performance problems or data loss.\n\n\ This app is currently in Alpha state, and you may experience bugs, performance problems or data loss.\n\n\
There is currently no special handling for mobile data, so it may use up your data volume if active.\n\n\ There is currently no special handling for mobile data, so it may use up your data volume if active.\n\n\
Please report any problems you encounter.</string> Please report any problems you encounter.</string>
<!-- SettingsFragment --> <!-- SettingsFragment -->
<!-- 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_while_not_charging">Stop sync when not charging</string>
<string name="stop_sync_on_mobile_data">Stop sync on mobile data</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>
<!-- Summary for the issue tracker settings item --> <!-- Summary for the issue tracker settings item -->
<string name="report_issue_summary">Open the Syncthing-Android issue tracker</string> <string name="report_issue_summary">Open the Syncthing-Android issue tracker</string>
<!-- URL of the issue tracker --> <!-- URL of the issue tracker -->
<string name="issue_tracker_url" translatable="false">https://github.com/Nutomic/syncthing-android/issues</string> <string name="issue_tracker_url" translatable="false">https://github.com/Nutomic/syncthing-android/issues</string>
<!-- Title of the preference showing upstream version name --> <!-- Title of the preference showing upstream version name -->
<string name="syncthing_version_title">Syncthing Version</string> <string name="syncthing_version_title">Syncthing Version</string>
<!-- FolderPickerAcitivity --> <!-- FolderPickerAcitivity -->
<!-- Activity title --> <!-- Activity title -->
<string name="folder_picker_title">Folder Picker</string> <string name="folder_picker_title">Folder Picker</string>
<!-- ListView empty text --> <!-- ListView empty text -->
<string name="directory_empty">Directory is Empty</string> <string name="directory_empty">Directory is Empty</string>
<!-- Menu item to create folder --> <!-- Menu item to create folder -->
<string name="create_folder">Create new Folder</string> <string name="create_folder">Create new Folder</string>
<!-- Menu item to select the current folder --> <!-- Menu item to select the current folder -->
<string name="select_folder">Select Folder</string> <string name="select_folder">Select Folder</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>
<!-- Message of the dialog shown when the syncthing binary returns an error --> <!-- Message of the dialog shown when the syncthing binary returns an error -->
<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>
@ -238,43 +238,43 @@ If this error persists, try reinstalling the app and restarting your device.\n\n
<!-- Message of the "syncthing disabled" dialog --> <!-- Message of the "syncthing disabled" dialog -->
<string name="syncthing_disabled_message">Do you want to change your preferences?</string> <string name="syncthing_disabled_message">Do you want to change your preferences?</string>
<!-- Button text on the "syncthing disabled" dialog --> <!-- Button text on the "syncthing disabled" dialog -->
<string name="syncthing_disabled_change_settings">Change Settings</string> <string name="syncthing_disabled_change_settings">Change Settings</string>
<!-- Button text on the "syncthing disabled" dialog --> <!-- Button text on the "syncthing disabled" dialog -->
<string name="exit">Exit</string> <string name="exit">Exit</string>
<!-- RestApi --> <!-- RestApi -->
<!-- Title of the notification shown when a restart is needed --> <!-- Title of the notification shown when a restart is needed -->
<string name="restart_title">Restart Needed</string> <string name="restart_title">Restart Needed</string>
<!-- Text of the notification shown when a restart is needed --> <!-- Text of the notification shown when a restart is needed -->
<string name="restart_notification_text">Click here to restart syncthing now</string> <string name="restart_notification_text">Click here to restart syncthing now</string>
<!-- Text for the dismiss button of the restart Activity --> <!-- Text for the dismiss button of the restart Activity -->
<string name="restart_later">Restart Later</string> <string name="restart_later">Restart Later</string>
<!-- Shown when a node ID is copied to the clipboard --> <!-- Shown when a node ID is copied to the clipboard -->
<string name="node_id_copied_to_clipboard">Node ID copied to clipboard</string> <string name="node_id_copied_to_clipboard">Node ID copied to clipboard</string>
<!-- Strings representing units for file sizes, from smallest to largest --> <!-- Strings representing units for file sizes, from smallest to largest -->
<string-array name="file_size_units"> <string-array name="file_size_units">
<item>B</item> <item>B</item>
<item>KB</item> <item>KB</item>
<item>MB</item> <item>MB</item>
<item>GB</item> <item>GB</item>
<item>TB</item> <item>TB</item>
</string-array> </string-array>
<!-- Strings representing units for transfer rates, from smallest to largest --> <!-- Strings representing units for transfer rates, from smallest to largest -->
<string-array name="transfer_rate_units"> <string-array name="transfer_rate_units">
<item>b/s</item> <item>b/s</item>
<item>Kb/s</item> <item>Kb/s</item>
<item>Mb/s</item> <item>Mb/s</item>
<item>Gb/s</item> <item>Gb/s</item>
<item>Tb/s</item> <item>Tb/s</item>
</string-array> </string-array>
</resources> </resources>

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="AppTheme" parent="@style/Theme.AppCompat" />
<style name="AppTheme" parent="@style/Theme.AppCompat" />
</resources> </resources>

View file

@ -1,106 +1,106 @@
<?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 <CheckBoxPreference
android:key="stop_sync_while_not_charging" android:key="stop_sync_while_not_charging"
android:title="@string/stop_sync_while_not_charging" android:title="@string/stop_sync_while_not_charging"
android:defaultValue="true" /> android:defaultValue="true" />
<CheckBoxPreference <CheckBoxPreference
android:key="stop_sync_on_mobile_data" android:key="stop_sync_on_mobile_data"
android:title="@string/stop_sync_on_mobile_data" android:title="@string/stop_sync_on_mobile_data"
android:defaultValue="true" /> android:defaultValue="true" />
<PreferenceScreen <PreferenceScreen
android:key="syncthing_options" android:key="syncthing_options"
android:title="Syncthing Options" android:title="Syncthing Options"
android:persistent="false" > android:persistent="false">
<EditTextPreference <EditTextPreference
android:key="ListenAddress" android:key="ListenAddress"
android:title="Sync Protocol Listen Addresses" android:title="Sync Protocol Listen Addresses"
android:defaultValue="0.0.0.0:22000" /> android:defaultValue="0.0.0.0:22000" />
<EditTextPreference <EditTextPreference
android:key="MaxSendKbps" android:key="MaxSendKbps"
android:title="Outgoing Rate Limit (KiB/s)" android:title="Outgoing Rate Limit (KiB/s)"
android:numeric="integer" /> android:numeric="integer" />
<EditTextPreference <EditTextPreference
android:key="RescanIntervalS" android:key="RescanIntervalS"
android:title="Rescan Interval (s)" android:title="Rescan Interval (s)"
android:numeric="integer" /> android:numeric="integer" />
<EditTextPreference <EditTextPreference
android:key="ReconnectIntervalS" android:key="ReconnectIntervalS"
android:title="Reconnect Interval (s)" android:title="Reconnect Interval (s)"
android:numeric="integer" /> android:numeric="integer" />
<EditTextPreference <EditTextPreference
android:key="ParallelRequests" android:key="ParallelRequests"
android:title="Max Outstanding Requests" android:title="Max Outstanding Requests"
android:numeric="integer" /> android:numeric="integer" />
<EditTextPreference <EditTextPreference
android:key="MaxChangeKbps" android:key="MaxChangeKbps"
android:title="Max File Change Rate (KiB/s)" android:title="Max File Change Rate (KiB/s)"
android:numeric="integer" /> android:numeric="integer" />
<CheckBoxPreference <CheckBoxPreference
android:key="GlobalAnnEnabled" android:key="GlobalAnnEnabled"
android:title="Global Discovery" /> android:title="Global Discovery" />
<CheckBoxPreference <CheckBoxPreference
android:key="LocalAnnEnabled" android:key="LocalAnnEnabled"
android:title="Local Discovery" /> android:title="Local Discovery" />
<EditTextPreference <EditTextPreference
android:key="LocalAnnPort" android:key="LocalAnnPort"
android:title="Local Discovery Port" android:title="Local Discovery Port"
android:numeric="integer" /> android:numeric="integer" />
<CheckBoxPreference <CheckBoxPreference
android:key="UPnPEnabled" android:key="UPnPEnabled"
android:title="Enable UPnP" /> android:title="Enable UPnP" />
</PreferenceScreen> </PreferenceScreen>
<PreferenceScreen <PreferenceScreen
android:key="syncthing_gui" android:key="syncthing_gui"
android:title="Syncthing GUI" android:title="Syncthing GUI"
android:persistent="false" > android:persistent="false">
<EditTextPreference <EditTextPreference
android:key="Address" android:key="Address"
android:title="GUI Listen Addresses" /> android:title="GUI Listen Addresses" />
<EditTextPreference <EditTextPreference
android:key="User" android:key="User"
android:title="GUI Authentication User" /> android:title="GUI Authentication User" />
<EditTextPreference <EditTextPreference
android:key="Password" android:key="Password"
android:title="GUI Authentication Password" android:title="GUI Authentication Password"
android:inputType="textPassword" /> android:inputType="textPassword" />
<CheckBoxPreference <CheckBoxPreference
android:key="UseTLS" android:key="UseTLS"
android:title="Use HTTPS for GUI" android:title="Use HTTPS for GUI"
android:enabled="false" /> android:enabled="false" />
</PreferenceScreen> </PreferenceScreen>
<Preference <Preference
android:title="@string/report_issue_title" android:title="@string/report_issue_title"
android:summary="@string/report_issue_summary" > android:summary="@string/report_issue_summary">
<intent <intent
android:action="android.intent.action.VIEW" android:action="android.intent.action.VIEW"
android:data="@string/issue_tracker_url" /> android:data="@string/issue_tracker_url" />
</Preference> </Preference>
<Preference <Preference
android:key="syncthing_version" android:key="syncthing_version"
android:title="@string/syncthing_version_title" android:title="@string/syncthing_version_title"
style="?android:preferenceInformationStyle" /> style="?android:preferenceInformationStyle" />
</PreferenceScreen> </PreferenceScreen>

View file

@ -1,18 +1,18 @@
<?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"
android:persistent="false" > android:persistent="false">
<EditTextPreference <EditTextPreference
android:key="node_id" android:key="node_id"
android:title="@string/node_id" android:title="@string/node_id"
android:widgetLayout="@layout/scan_qr_code_widget" /> android:widgetLayout="@layout/scan_qr_code_widget" />
<EditTextPreference <EditTextPreference
android:key="name" android:key="name"
android:title="@string/name" /> android:title="@string/name" />
<EditTextPreference <EditTextPreference
android:key="addresses" android:key="addresses"
android:title="@string/addresses" /> android:title="@string/addresses" />
</PreferenceScreen> </PreferenceScreen>

View file

@ -1,27 +1,27 @@
<?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"
android:persistent="false" > android:persistent="false">
<Preference <Preference
android:key="node_id" android:key="node_id"
android:title="@string/node_id"/> android:title="@string/node_id" />
<EditTextPreference <EditTextPreference
android:key="name" android:key="name"
android:title="@string/name" /> android:title="@string/name" />
<EditTextPreference <EditTextPreference
android:key="addresses" android:key="addresses"
android:title="@string/addresses" /> android:title="@string/addresses" />
<Preference <Preference
android:key="version" android:key="version"
android:title="@string/syncthing_version_title" android:title="@string/syncthing_version_title"
style="?android:preferenceInformationStyle" /> style="?android:preferenceInformationStyle" />
<Preference <Preference
android:key="current_address" android:key="current_address"
android:title="@string/current_address" android:title="@string/current_address"
style="?android:preferenceInformationStyle" /> style="?android:preferenceInformationStyle" />
</PreferenceScreen> </PreferenceScreen>

View file

@ -1,30 +1,30 @@
<?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"
android:persistent="false" > android:persistent="false">
<EditTextPreference <EditTextPreference
android:key="repo_id" android:key="repo_id"
android:title="@string/repo_id" /> android:title="@string/repo_id" />
<Preference <Preference
android:key="directory" android:key="directory"
android:title="@string/directory" /> android:title="@string/directory" />
<CheckBoxPreference <CheckBoxPreference
android:key="repo_master" android:key="repo_master"
android:title="@string/repo_master" /> android:title="@string/repo_master" />
<PreferenceScreen <PreferenceScreen
android:key="nodes" android:key="nodes"
android:title="@string/nodes" /> android:title="@string/nodes" />
<CheckBoxPreference <CheckBoxPreference
android:key="versioning" android:key="versioning"
android:title="@string/file_versioning" /> android:title="@string/file_versioning" />
<EditTextPreference <EditTextPreference
android:key="versioning_keep" android:key="versioning_keep"
android:title="@string/keep_versions" android:title="@string/keep_versions"
android:inputType="numberDecimal" /> android:inputType="numberDecimal" />
</PreferenceScreen> </PreferenceScreen>

View file

@ -1,34 +1,34 @@
<?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"
android:persistent="false" > android:persistent="false">
<EditTextPreference <EditTextPreference
android:key="repo_id" android:key="repo_id"
android:title="@string/repo_id" android:title="@string/repo_id"
android:enabled="false" android:enabled="false"
style="?android:preferenceInformationStyle" /> style="?android:preferenceInformationStyle" />
<Preference <Preference
android:key="directory" android:key="directory"
android:title="@string/directory" android:title="@string/directory"
android:enabled="false" android:enabled="false"
style="?android:preferenceInformationStyle" /> style="?android:preferenceInformationStyle" />
<CheckBoxPreference <CheckBoxPreference
android:key="repo_master" android:key="repo_master"
android:title="@string/repo_master" /> android:title="@string/repo_master" />
<PreferenceScreen <PreferenceScreen
android:key="nodes" android:key="nodes"
android:title="@string/nodes" /> android:title="@string/nodes" />
<CheckBoxPreference <CheckBoxPreference
android:key="versioning" android:key="versioning"
android:title="File Versioning" /> android:title="File Versioning" />
<EditTextPreference <EditTextPreference
android:key="versioning_keep" android:key="versioning_keep"
android:title="@string/keep_versions" android:title="@string/keep_versions"
android:inputType="numberDecimal" /> android:inputType="numberDecimal" />
</PreferenceScreen> </PreferenceScreen>