Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/stefan-niedermann/nextcloud-notes.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
authorstefan-niedermann <info@niedermann.it>2020-01-31 14:58:45 +0300
committerstefan-niedermann <info@niedermann.it>2020-01-31 14:58:45 +0300
commit68ed8c9998b56e0ecc801f42551d5bd5f9c77719 (patch)
treed7d2133ce7efec165cbf85ac6c0e282494bb9797 /app/src
parent8c07d32d9a4b35862b8b041e7fe31a1fd6e4b6ea (diff)
parent3dfbf53d23f770d670cf55087f3a2d193f0ec3cb (diff)
Merge branch 'master' into 354-password-protection
# Conflicts: # app/src/main/java/it/niedermann/owncloud/notes/android/fragment/PreferencesFragment.java # app/src/main/res/values/strings.xml
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/android/MultiSelectedActionModeCallback.java63
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/android/NotesListViewItemTouchHelper.java12
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java2
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java63
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java30
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java24
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/android/fragment/PreferencesFragment.java18
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java40
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java202
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesClient.java78
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/SyncWorker.java78
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java15
-rw-r--r--app/src/main/res/drawable/ic_network_wifi_grey600_24dp.xml6
-rw-r--r--app/src/main/res/layout/item_notes_list_note_item.xml3
-rw-r--r--app/src/main/res/values-sl/strings.xml1
-rw-r--r--app/src/main/res/values-sr/strings.xml9
-rw-r--r--app/src/main/res/values/arrays.xml6
-rw-r--r--app/src/main/res/values/strings.xml16
-rw-r--r--app/src/main/res/xml/preferences.xml12
19 files changed, 444 insertions, 234 deletions
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/MultiSelectedActionModeCallback.java b/app/src/main/java/it/niedermann/owncloud/notes/android/MultiSelectedActionModeCallback.java
index 46b0fa69..96ccb878 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/android/MultiSelectedActionModeCallback.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/android/MultiSelectedActionModeCallback.java
@@ -12,6 +12,10 @@ import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar;
+import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
+import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
+import com.nextcloud.android.sso.helper.SingleAccountHelper;
+import com.nextcloud.android.sso.model.SingleSignOnAccount;
import java.util.ArrayList;
import java.util.List;
@@ -70,34 +74,39 @@ public class MultiSelectedActionModeCallback implements Callback {
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_delete: {
- List<DBNote> deletedNotes = new ArrayList<>();
- List<Integer> selection = adapter.getSelected();
- for (Integer i : selection) {
- DBNote note = (DBNote) adapter.getItem(i);
- deletedNotes.add(db.getNote(note.getAccountId(), note.getId()));
- db.deleteNoteAndSync(note.getId());
+ try {
+ SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context);
+ List<DBNote> deletedNotes = new ArrayList<>();
+ List<Integer> selection = adapter.getSelected();
+ for (Integer i : selection) {
+ DBNote note = (DBNote) adapter.getItem(i);
+ deletedNotes.add(db.getNote(note.getAccountId(), note.getId()));
+ db.deleteNoteAndSync(ssoAccount, note.getId());
+ }
+ mode.finish(); // Action picked, so close the CAB
+ //after delete selection has to be cleared
+ searchView.setIconified(true);
+ refreshLists.run();
+ String deletedSnackbarTitle = deletedNotes.size() == 1
+ ? context.getString(R.string.action_note_deleted, deletedNotes.get(0).getTitle())
+ : context.getString(R.string.bulk_notes_deleted, deletedNotes.size());
+ Snackbar.make(viewProvider.getView(), deletedSnackbarTitle, Snackbar.LENGTH_LONG)
+ .setAction(R.string.action_undo, (View v) -> {
+ db.getNoteServerSyncHelper().addCallbackPush(ssoAccount, refreshLists::run);
+ for (DBNote deletedNote : deletedNotes) {
+ db.addNoteAndSync(ssoAccount, deletedNote.getAccountId(), deletedNote);
+ }
+ refreshLists.run();
+ String restoreSnackbarTitle = deletedNotes.size() == 1
+ ? context.getString(R.string.action_note_restored, deletedNotes.get(0).getTitle())
+ : context.getString(R.string.bulk_notes_restored, deletedNotes.size());
+ Snackbar.make(viewProvider.getView(), restoreSnackbarTitle, Snackbar.LENGTH_SHORT)
+ .show();
+ })
+ .show();
+ } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
+ e.printStackTrace();
}
- mode.finish(); // Action picked, so close the CAB
- //after delete selection has to be cleared
- searchView.setIconified(true);
- refreshLists.run();
- String deletedSnackbarTitle = deletedNotes.size() == 1
- ? context.getString(R.string.action_note_deleted, deletedNotes.get(0).getTitle())
- : context.getString(R.string.bulk_notes_deleted, deletedNotes.size());
- Snackbar.make(viewProvider.getView(), deletedSnackbarTitle, Snackbar.LENGTH_LONG)
- .setAction(R.string.action_undo, (View v) -> {
- db.getNoteServerSyncHelper().addCallbackPush(refreshLists::run);
- for (DBNote deletedNote : deletedNotes) {
- db.addNoteAndSync(deletedNote.getAccountId(), deletedNote);
- }
- refreshLists.run();
- String restoreSnackbarTitle = deletedNotes.size() == 1
- ? context.getString(R.string.action_note_restored, deletedNotes.get(0).getTitle())
- : context.getString(R.string.bulk_notes_restored, deletedNotes.size());
- Snackbar.make(viewProvider.getView(), restoreSnackbarTitle, Snackbar.LENGTH_SHORT)
- .show();
- })
- .show();
return true;
}
case R.id.menu_move: {
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/NotesListViewItemTouchHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/android/NotesListViewItemTouchHelper.java
index 63de4b70..3e851ba5 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/android/NotesListViewItemTouchHelper.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/android/NotesListViewItemTouchHelper.java
@@ -10,19 +10,21 @@ import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar;
+import com.nextcloud.android.sso.model.SingleSignOnAccount;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.model.DBNote;
+import it.niedermann.owncloud.notes.model.ISyncCallback;
import it.niedermann.owncloud.notes.model.ItemAdapter;
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
import it.niedermann.owncloud.notes.persistence.NoteServerSyncHelper.ViewProvider;
-import it.niedermann.owncloud.notes.model.ISyncCallback;
public class NotesListViewItemTouchHelper extends ItemTouchHelper {
private static final String TAG = NotesListViewItemTouchHelper.class.getCanonicalName();
public NotesListViewItemTouchHelper(
+ SingleSignOnAccount ssoAccount,
Context context,
ViewProvider viewProvider,
NoteSQLiteOpenHelper db,
@@ -61,14 +63,14 @@ public class NotesListViewItemTouchHelper extends ItemTouchHelper {
case ItemTouchHelper.LEFT: {
final DBNote dbNoteWithoutContent = (DBNote) adapter.getItem(viewHolder.getAdapterPosition());
final DBNote dbNote = db.getNote(dbNoteWithoutContent.getAccountId(), dbNoteWithoutContent.getId());
- db.deleteNoteAndSync(dbNote.getId());
+ db.deleteNoteAndSync(ssoAccount, dbNote.getId());
adapter.remove(dbNote);
refreshLists.run();
Log.v(TAG, "Item deleted through swipe ----------------------------------------------");
Snackbar.make(viewProvider.getView(), context.getString(R.string.action_note_deleted, dbNote.getTitle()), Snackbar.LENGTH_LONG)
.setAction(R.string.action_undo, (View v) -> {
- db.getNoteServerSyncHelper().addCallbackPush(refreshLists::run);
- db.addNoteAndSync(dbNote.getAccountId(), dbNote);
+ db.getNoteServerSyncHelper().addCallbackPush(ssoAccount, refreshLists::run);
+ db.addNoteAndSync(ssoAccount, dbNote.getAccountId(), dbNote);
refreshLists.run();
Snackbar.make(viewProvider.getView(), context.getString(R.string.action_note_restored, dbNote.getTitle()), Snackbar.LENGTH_SHORT)
.show();
@@ -78,7 +80,7 @@ public class NotesListViewItemTouchHelper extends ItemTouchHelper {
}
case ItemTouchHelper.RIGHT: {
final DBNote dbNote = (DBNote) adapter.getItem(viewHolder.getAdapterPosition());
- db.toggleFavorite(dbNote, syncCallBack);
+ db.toggleFavorite(ssoAccount, dbNote, syncCallBack);
refreshLists.run();
break;
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java
index 4441b720..8d95f018 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java
@@ -261,7 +261,7 @@ public class EditNoteActivity extends LockedActivity implements BaseNoteFragment
}
} else {
// Maybe account is not authenticated -> note == null
- Log.e(TAG, "note is null, start NotesListViewActivity");
+ Log.e(TAG, "note is null, start " + NotesListViewActivity.class.getSimpleName());
startActivity(new Intent(this, NotesListViewActivity.class));
finish();
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java
index db842340..fcac49d5 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java
@@ -96,6 +96,7 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
*/
private boolean notAuthorizedAccountHandled = false;
+ private SingleSignOnAccount ssoAccount;
private LocalAccount localAccount;
@BindView(R.id.coordinatorLayout)
@@ -182,23 +183,27 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
@Override
protected void onResume() {
try {
- String ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext()).name;
- if (localAccount == null || !localAccount.getAccountName().equals(ssoAccount)) {
- selectAccount(SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext()).name);
+ ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext());
+ if (localAccount == null || !localAccount.getAccountName().equals(ssoAccount.name)) {
+ selectAccount(ssoAccount.name);
}
} catch (NoCurrentAccountSelectedException | NextcloudFilesAppAccountNotFoundException e) {
+ if (localAccount == null) {
+ List<LocalAccount> localAccounts = db.getAccounts();
+ if (localAccounts.size() > 0) {
+ localAccount = localAccounts.get(0);
+ }
+ }
if (!notAuthorizedAccountHandled) {
handleNotAuthorizedAccount();
}
}
// refresh and sync every time the activity gets
+ refreshLists();
if (localAccount != null) {
- refreshLists();
- db.getNoteServerSyncHelper().addCallbackPull(syncCallBack);
- if (db.getNoteServerSyncHelper().isSyncPossible()) {
- synchronize();
- }
+ synchronize();
+ db.getNoteServerSyncHelper().addCallbackPull(ssoAccount, syncCallBack);
}
super.onResume();
}
@@ -230,13 +235,14 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
SingleAccountHelper.setCurrentAccount(getApplicationContext(), accountName);
localAccount = db.getLocalAccountByAccountName(accountName);
try {
- db.getNoteServerSyncHelper().updateAccount();
+ ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext());
synchronize();
- refreshLists();
- fabCreate.show();
- } catch (NextcloudFilesAppAccountNotFoundException e) {
+ } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
+ Log.i(TAG, "Tried to select account, but got an " + e.getClass().getSimpleName() + ". Asking for importing an account...");
handleNotAuthorizedAccount();
}
+ refreshLists();
+ fabCreate.show();
setupHeader();
setupNavigationList(ADAPTER_KEY_RECENT);
updateUsernameInDrawer();
@@ -312,13 +318,12 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
}
});
- // Pull to Refresh
swipeRefreshLayout.setOnRefreshListener(() -> {
- if (db.getNoteServerSyncHelper().isSyncPossible()) {
- synchronize();
- } else {
+ if (ssoAccount == null) {
swipeRefreshLayout.setRefreshing(false);
- Snackbar.make(coordinatorLayout, getString(R.string.error_sync, getString(LoginStatus.NO_NETWORK.str)), Snackbar.LENGTH_LONG).show();
+ askForNewAccount(this);
+ } else {
+ synchronize();
}
});
@@ -529,7 +534,7 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
adapter = new ItemAdapter(this);
listView.setAdapter(adapter);
listView.setLayoutManager(new LinearLayoutManager(this));
- new NotesListViewItemTouchHelper(this, this, db, adapter, syncCallBack, this::refreshLists).attachToRecyclerView(listView);
+ new NotesListViewItemTouchHelper(ssoAccount, this, this, db, adapter, syncCallBack, this::refreshLists).attachToRecyclerView(listView);
}
private void refreshLists() {
@@ -775,7 +780,7 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
public void onNoteFavoriteClick(int position, View view) {
DBNote note = (DBNote) adapter.getItem(position);
NoteSQLiteOpenHelper db = NoteSQLiteOpenHelper.getInstance(view.getContext());
- db.toggleFavorite(note, syncCallBack);
+ db.toggleFavorite(ssoAccount, note, syncCallBack);
adapter.notifyItemChanged(position);
refreshLists();
}
@@ -804,11 +809,23 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
}
private void synchronize() {
- swipeRefreshLayout.setRefreshing(true);
- db.getNoteServerSyncHelper().addCallbackPull(syncCallBack);
- db.getNoteServerSyncHelper().scheduleSync(false);
+ NoteServerSyncHelper syncHelper = db.getNoteServerSyncHelper();
+ if (syncHelper.isSyncPossible()) {
+ swipeRefreshLayout.setRefreshing(true);
+ syncHelper.addCallbackPull(ssoAccount, syncCallBack);
+ syncHelper.scheduleSync(ssoAccount, false);
+ } else { // Sync is not possible
+ swipeRefreshLayout.setRefreshing(false);
+ if (syncHelper.isNetworkConnected() && syncHelper.isSyncOnlyOnWifi()) {
+ Log.d(TAG, "Network is connected, but sync is not possible");
+ } else {
+ Log.d(TAG, "Sync is not possible, because network is not connected");
+ Snackbar.make(coordinatorLayout, getString(R.string.error_sync, getString(LoginStatus.NO_NETWORK.str)), Snackbar.LENGTH_LONG).show();
+ }
+ }
}
+
@Override
public void onAccountChosen(LocalAccount account) {
List<Integer> selection = new ArrayList<>(adapter.getSelected());
@@ -816,7 +833,7 @@ public class NotesListViewActivity extends LockedActivity implements ItemAdapter
adapter.deselect(0);
for (Integer i : selection) {
DBNote note = (DBNote) adapter.getItem(i);
- db.moveNoteToAnotherAccount(note.getAccountId(), db.getNote(note.getAccountId(), note.getId()), account.getId());
+ db.moveNoteToAnotherAccount(ssoAccount, note.getAccountId(), db.getNote(note.getAccountId(), note.getId()), account.getId());
RecyclerView.ViewHolder viewHolder = listView.findViewHolderForAdapterPosition(i);
if (viewHolder != null) {
viewHolder.itemView.setSelected(false);
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java
index 529e5c03..2f42bc8d 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/BaseNoteFragment.java
@@ -24,6 +24,7 @@ import androidx.fragment.app.FragmentManager;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
import com.nextcloud.android.sso.helper.SingleAccountHelper;
+import com.nextcloud.android.sso.model.SingleSignOnAccount;
import java.util.Objects;
@@ -33,9 +34,9 @@ import it.niedermann.owncloud.notes.android.fragment.CategoryDialogFragment.Cate
import it.niedermann.owncloud.notes.model.CloudNote;
import it.niedermann.owncloud.notes.model.DBNote;
import it.niedermann.owncloud.notes.model.DBStatus;
+import it.niedermann.owncloud.notes.model.ISyncCallback;
import it.niedermann.owncloud.notes.model.LocalAccount;
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
-import it.niedermann.owncloud.notes.model.ISyncCallback;
import it.niedermann.owncloud.notes.util.NoteUtil;
import static androidx.core.content.pm.ShortcutManagerCompat.isRequestPinShortcutSupported;
@@ -54,6 +55,7 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
private static final String SAVEDKEY_ORIGINAL_NOTE = "original_note";
private LocalAccount localAccount;
+ private SingleSignOnAccount ssoAccount;
protected DBNote note;
@Nullable
@@ -67,7 +69,8 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
- this.localAccount = db.getLocalAccountByAccountName(SingleAccountHelper.getCurrentSingleSignOnAccount(getActivity().getApplicationContext()).name);
+ this.ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(getActivity().getApplicationContext());
+ this.localAccount = db.getLocalAccountByAccountName(ssoAccount.name);
if (savedInstanceState == null) {
long id = getArguments().getLong(PARAM_NOTE_ID);
@@ -77,11 +80,6 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
/* Switch account if account id has been provided */
this.localAccount = db.getAccount(accountId);
SingleAccountHelper.setCurrentAccount(getActivity().getApplicationContext(), localAccount.getAccountName());
- try {
- db.getNoteServerSyncHelper().updateAccount();
- } catch (NextcloudFilesAppAccountNotFoundException e) {
- e.printStackTrace();
- }
}
isNew = false;
note = originalNote = db.getNote(localAccount.getId(), id);
@@ -95,7 +93,7 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
note = new DBNote(-1, -1, null, NoteUtil.generateNoteTitle(content), content, false, getString(R.string.category_readonly), null, DBStatus.VOID, -1, "");
}
} else {
- note = db.getNote(localAccount.getId(), db.addNoteAndSync(localAccount.getId(), cloudNote));
+ note = db.getNote(localAccount.getId(), db.addNoteAndSync(ssoAccount, localAccount.getId(), cloudNote));
originalNote = null;
}
}
@@ -177,18 +175,18 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
switch (item.getItemId()) {
case R.id.menu_cancel:
if (originalNote == null) {
- db.deleteNoteAndSync(note.getId());
+ db.deleteNoteAndSync(ssoAccount, note.getId());
} else {
- db.updateNoteAndSync(localAccount.getId(), originalNote, null, null);
+ db.updateNoteAndSync(ssoAccount, localAccount.getId(), originalNote, null, null);
}
listener.close();
return true;
case R.id.menu_delete:
- db.deleteNoteAndSync(note.getId());
+ db.deleteNoteAndSync(ssoAccount, note.getId());
listener.close();
return true;
case R.id.menu_favorite:
- db.toggleFavorite(note, null);
+ db.toggleFavorite(ssoAccount, note, null);
listener.onNoteUpdated(note);
prepareFavoriteOption(item);
return true;
@@ -248,7 +246,7 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
public void onCloseNote() {
if (originalNote == null && getContent().isEmpty()) {
- db.deleteNoteAndSync(note.getId());
+ db.deleteNoteAndSync(ssoAccount, note.getId());
}
}
@@ -264,7 +262,7 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
if (note.getContent().equals(newContent)) {
Log.v(TAG, "... not saving, since nothing has changed");
} else {
- note = db.updateNoteAndSync(localAccount.getId(), note, newContent, callback);
+ note = db.updateNoteAndSync(ssoAccount, localAccount.getId(), note, newContent, callback);
listener.onNoteUpdated(note);
}
} else {
@@ -310,12 +308,12 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
@Override
public void onCategoryChosen(String category) {
- db.setCategory(note, category, null);
+ db.setCategory(ssoAccount, note, category, null);
listener.onNoteUpdated(note);
}
public void moveNote(LocalAccount account) {
- db.moveNoteToAnotherAccount(note.getAccountId(), note, account.getId());
+ db.moveNoteToAnotherAccount(ssoAccount, note.getAccountId(), note, account.getId());
listener.close();
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java
index 5cdeb25f..c85eb8a0 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java
@@ -26,6 +26,10 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
+import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
+import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
+import com.nextcloud.android.sso.helper.SingleAccountHelper;
+import com.nextcloud.android.sso.model.SingleSignOnAccount;
import com.yydcdut.markdown.MarkdownProcessor;
import com.yydcdut.markdown.MarkdownTextView;
import com.yydcdut.markdown.syntax.text.TextFactory;
@@ -41,6 +45,7 @@ import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
import it.niedermann.owncloud.notes.util.DisplayUtils;
import it.niedermann.owncloud.notes.util.MarkDownUtil;
import it.niedermann.owncloud.notes.util.NoteLinksUtils;
+import it.niedermann.owncloud.notes.util.SSOUtil;
public class NotePreviewFragment extends SearchableBaseNoteFragment implements OnRefreshListener {
@@ -206,14 +211,19 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O
@Override
public void onRefresh() {
- if (db.getNoteServerSyncHelper().isSyncPossible()) {
+ if (db.getNoteServerSyncHelper().isSyncPossible() && SSOUtil.isConfigured(getContext())) {
swipeRefreshLayout.setRefreshing(true);
- db.getNoteServerSyncHelper().addCallbackPull(() -> {
- note = db.getNote(note.getAccountId(), note.getId());
- noteContent.setText(markdownProcessor.parse(NoteLinksUtils.replaceNoteLinksWithDummyUrls(note.getContent(), db.getRemoteIds(note.getAccountId()))));
- swipeRefreshLayout.setRefreshing(false);
- });
- db.getNoteServerSyncHelper().scheduleSync(false);
+ try {
+ SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(getContext());
+ db.getNoteServerSyncHelper().addCallbackPull(ssoAccount, () -> {
+ note = db.getNote(note.getAccountId(), note.getId());
+ noteContent.setText(markdownProcessor.parse(NoteLinksUtils.replaceNoteLinksWithDummyUrls(note.getContent(), db.getRemoteIds(note.getAccountId()))));
+ swipeRefreshLayout.setRefreshing(false);
+ });
+ db.getNoteServerSyncHelper().scheduleSync(ssoAccount, false);
+ } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
+ e.printStackTrace();
+ }
} else {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getContext(), getString(R.string.error_sync, getString(LoginStatus.NO_NETWORK.str)), Toast.LENGTH_LONG).show();
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/PreferencesFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/PreferencesFragment.java
index 3cddea75..2dd1050f 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/PreferencesFragment.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/PreferencesFragment.java
@@ -5,11 +5,13 @@ import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
+import androidx.preference.ListPreference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreference;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.util.DeviceCredentialUtil;
+import it.niedermann.owncloud.notes.persistence.SyncWorker;
import it.niedermann.owncloud.notes.util.Notes;
public class PreferencesFragment extends PreferenceFragmentCompat {
@@ -36,18 +38,26 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
final SwitchPreference themePref = findPreference(getString(R.string.pref_key_theme));
+ assert themePref != null;
themePref.setOnPreferenceChangeListener((preference, newValue) -> {
- Boolean darkTheme = (Boolean) newValue;
- Notes.setAppTheme(darkTheme);
+ Notes.setAppTheme((Boolean) newValue);
getActivity().setResult(Activity.RESULT_OK);
getActivity().recreate();
return true;
});
final SwitchPreference wifiOnlyPref = findPreference(getString(R.string.pref_key_wifi_only));
+ assert wifiOnlyPref != null;
wifiOnlyPref.setOnPreferenceChangeListener((preference, newValue) -> {
- Boolean syncOnWifiOnly = (Boolean) newValue;
- Log.v(TAG, "syncOnWifiOnly: " + syncOnWifiOnly);
+ Log.v(TAG, "syncOnWifiOnly: " + newValue);
+ return true;
+ });
+
+ final ListPreference syncPref = findPreference(getString(R.string.pref_key_background_sync));
+ assert syncPref != null;
+ syncPref.setOnPreferenceChangeListener((preference, newValue) -> {
+ Log.v(TAG, "syncPref: " + preference + " - newValue: " + newValue);
+ SyncWorker.update(getContext(), newValue.toString());
return true;
});
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java
index c0562c73..06c5f0ed 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java
@@ -24,6 +24,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
+import com.nextcloud.android.sso.model.SingleSignOnAccount;
+
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
@@ -199,7 +201,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
String username = sharedPreferences.getString("settingsUsername", "");
String url = sharedPreferences.getString("settingsUrl", "");
- if (url != null && url.endsWith("/")) {
+ if (!url.isEmpty() && url.endsWith("/")) {
url = url.substring(0, url.length() - 1);
try {
String accountName = username + "@" + new URL(url).getHost();
@@ -272,7 +274,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
return;
}
} else {
- Log.e(TAG, "Previous URL is null. Recreating database...");
+ Log.e(TAG, "Previous URL is empty or does not end with a '/' character. Recreating database...");
recreateDatabase(db);
return;
}
@@ -318,11 +320,11 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
*
* @param note Note
*/
- public long addNoteAndSync(long accountId, CloudNote note) {
+ public long addNoteAndSync(SingleSignOnAccount ssoAccount, long accountId, CloudNote note) {
DBNote dbNote = new DBNote(0, 0, note.getModified(), note.getTitle(), note.getContent(), note.isFavorite(), note.getCategory(), note.getEtag(), DBStatus.LOCAL_EDITED, accountId, NoteUtil.generateNoteExcerpt(note.getContent()));
long id = addNote(accountId, dbNote);
notifyNotesChanged();
- getNoteServerSyncHelper().scheduleSync(true);
+ getNoteServerSyncHelper().scheduleSync(ssoAccount, true);
return id;
}
@@ -360,13 +362,13 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
return db.insert(table_notes, null, values);
}
- public void moveNoteToAnotherAccount(long oldAccountId, DBNote note, long newAccountId) {
+ public void moveNoteToAnotherAccount(SingleSignOnAccount ssoAccount, long oldAccountId, DBNote note, long newAccountId) {
// Add new note
- addNoteAndSync(newAccountId, new CloudNote(0, note.getModified(), note.getTitle(), note.getContent(), note.isFavorite(), note.getCategory(), null));
- deleteNoteAndSync(note.getId());
+ addNoteAndSync(ssoAccount, newAccountId, new CloudNote(0, note.getModified(), note.getTitle(), note.getContent(), note.isFavorite(), note.getCategory(), null));
+ deleteNoteAndSync(ssoAccount, note.getId());
notifyNotesChanged();
- getNoteServerSyncHelper().scheduleSync(true);
+ getNoteServerSyncHelper().scheduleSync(ssoAccount,true);
}
/**
@@ -658,7 +660,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
return categories;
}
- public void toggleFavorite(@NonNull DBNote note, @Nullable ISyncCallback callback) {
+ public void toggleFavorite(SingleSignOnAccount ssoAccount, @NonNull DBNote note, @Nullable ISyncCallback callback) {
note.setFavorite(!note.isFavorite());
note.setStatus(DBStatus.LOCAL_EDITED);
SQLiteDatabase db = this.getWritableDatabase();
@@ -667,12 +669,12 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
values.put(key_favorite, note.isFavorite() ? "1" : "0");
db.update(table_notes, values, key_id + " = ?", new String[]{String.valueOf(note.getId())});
if (callback != null) {
- serverSyncHelper.addCallbackPush(callback);
+ serverSyncHelper.addCallbackPush(ssoAccount, callback);
}
- serverSyncHelper.scheduleSync(true);
+ serverSyncHelper.scheduleSync(ssoAccount, true);
}
- public void setCategory(@NonNull DBNote note, @NonNull String category, @Nullable ISyncCallback callback) {
+ public void setCategory(SingleSignOnAccount ssoAccount, @NonNull DBNote note, @NonNull String category, @Nullable ISyncCallback callback) {
note.setCategory(category);
note.setStatus(DBStatus.LOCAL_EDITED);
SQLiteDatabase db = this.getWritableDatabase();
@@ -681,9 +683,9 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
values.put(key_category, note.getCategory());
db.update(table_notes, values, key_id + " = ?", new String[]{String.valueOf(note.getId())});
if (callback != null) {
- serverSyncHelper.addCallbackPush(callback);
+ serverSyncHelper.addCallbackPush(ssoAccount, callback);
}
- serverSyncHelper.scheduleSync(true);
+ serverSyncHelper.scheduleSync(ssoAccount,true);
}
/**
@@ -695,7 +697,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
* @param callback When the synchronization is finished, this callback will be invoked (optional).
* @return changed note if differs from database, otherwise the old note.
*/
- public DBNote updateNoteAndSync(long accountId, @NonNull DBNote oldNote, @Nullable String newContent, @Nullable ISyncCallback callback) {
+ public DBNote updateNoteAndSync(SingleSignOnAccount ssoAccount, long accountId, @NonNull DBNote oldNote, @Nullable String newContent, @Nullable ISyncCallback callback) {
//debugPrintFullDB();
DBNote newNote;
if (newContent == null) {
@@ -716,9 +718,9 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
if (rows > 0) {
notifyNotesChanged();
if (callback != null) {
- serverSyncHelper.addCallbackPush(callback);
+ serverSyncHelper.addCallbackPush(ssoAccount, callback);
}
- serverSyncHelper.scheduleSync(true);
+ serverSyncHelper.scheduleSync(ssoAccount, true);
return newNote;
} else {
if (callback != null) {
@@ -781,7 +783,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
*
* @param id long - ID of the Note that should be deleted
*/
- public void deleteNoteAndSync(long id) {
+ public void deleteNoteAndSync(SingleSignOnAccount ssoAccount, long id) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(key_status, DBStatus.LOCAL_DELETED.getTitle());
@@ -790,7 +792,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
key_id + " = ?",
new String[]{String.valueOf(id)});
notifyNotesChanged();
- getNoteServerSyncHelper().scheduleSync(true);
+ getNoteServerSyncHelper().scheduleSync(ssoAccount, true);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java
index ebea9cb9..6d5c3492 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java
@@ -15,6 +15,7 @@ import android.util.Log;
import android.view.View;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
@@ -25,25 +26,29 @@ import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedExcepti
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
import com.nextcloud.android.sso.helper.SingleAccountHelper;
+import com.nextcloud.android.sso.model.SingleSignOnAccount;
import org.json.JSONException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.model.CloudNote;
import it.niedermann.owncloud.notes.model.DBNote;
import it.niedermann.owncloud.notes.model.DBStatus;
+import it.niedermann.owncloud.notes.model.ISyncCallback;
import it.niedermann.owncloud.notes.model.LocalAccount;
import it.niedermann.owncloud.notes.model.LoginStatus;
import it.niedermann.owncloud.notes.util.ExceptionUtil;
-import it.niedermann.owncloud.notes.model.ISyncCallback;
+import it.niedermann.owncloud.notes.util.SSOUtil;
import it.niedermann.owncloud.notes.util.ServerResponse;
import static android.content.Context.CLIPBOARD_SERVICE;
@@ -59,9 +64,9 @@ public class NoteServerSyncHelper {
private NoteSQLiteOpenHelper db;
private Context context;
- private LocalAccount localAccount;
// Track network connection changes using a BroadcastReceiver
+ private boolean isSyncPossible = false;
private boolean networkConnected = false;
private String syncOnlyOnWifiKey;
private boolean syncOnlyOnWifi;
@@ -80,30 +85,29 @@ public class NoteServerSyncHelper {
@Override
public void onReceive(Context context, Intent intent) {
updateNetworkStatus();
- if (isSyncPossible()) {
- scheduleSync(false);
+ if (isSyncPossible() && SSOUtil.isConfigured(context)) {
+ try {
+ scheduleSync(SingleAccountHelper.getCurrentSingleSignOnAccount(context), false);
+ } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
+ Log.v(TAG, "Can not select current SingleSignOn account after network changed, do not sync.");
+ }
}
}
};
// current state of the synchronization
- private boolean syncActive = false;
- private boolean syncScheduled = false;
+ private Map<String, Boolean> syncActive = new HashMap<>();
+ private Map<String, Boolean> syncScheduled = new HashMap<>();
private NotesClient notesClient;
// list of callbacks for both parts of synchronziation
- private List<ISyncCallback> callbacksPush = new ArrayList<>();
- private List<ISyncCallback> callbacksPull = new ArrayList<>();
-
+ private Map<String, List<ISyncCallback>> callbacksPush = new HashMap<>();
+ private Map<String, List<ISyncCallback>> callbacksPull = new HashMap<>();
private NoteServerSyncHelper(NoteSQLiteOpenHelper db) {
this.db = db;
this.context = db.getContext();
- try {
- updateAccount();
- } catch (NextcloudFilesAppAccountNotFoundException e) {
- e.printStackTrace();
- }
+ notesClient = new NotesClient(context.getApplicationContext());
this.syncOnlyOnWifiKey = context.getApplicationContext().getResources().getString(R.string.pref_key_wifi_only);
// Registers BroadcastReceiver to track network connection changes.
@@ -131,50 +135,32 @@ public class NoteServerSyncHelper {
return instance;
}
- public void updateAccount() throws NextcloudFilesAppAccountNotFoundException {
- try {
- this.localAccount = db.getLocalAccountByAccountName(SingleAccountHelper.getCurrentSingleSignOnAccount(context.getApplicationContext()).name);
- if (notesClient == null) {
- if (this.localAccount != null) {
- notesClient = new NotesClient(context.getApplicationContext());
- }
- } else {
- notesClient.updateAccount();
- }
- Log.v(TAG, "NextcloudRequest account: " + localAccount);
- } catch (NoCurrentAccountSelectedException e) {
- e.printStackTrace();
- }
- Log.v(TAG, "Reinstanziation NotesClient because of SSO acc changed");
- }
-
@Override
protected void finalize() throws Throwable {
context.getApplicationContext().unregisterReceiver(networkReceiver);
super.finalize();
}
- private static boolean isConfigured(Context context) {
- try {
- SingleAccountHelper.getCurrentSingleSignOnAccount(context);
- return true;
- } catch (NextcloudFilesAppAccountNotFoundException e) {
- return false;
- } catch (NoCurrentAccountSelectedException e) {
- return false;
- }
- }
-
/**
- * Synchronization is only possible, if there is an active network connection and
- * SingleSignOn is available
+ * Synchronization is only possible, if there is an active network connection.
+ *
+ * This method respects the user preference "Sync on Wi-Fi only".
+ *
* NoteServerSyncHelper observes changes in the network connection.
* The current state can be retrieved with this method.
*
* @return true if sync is possible, otherwise false.
*/
public boolean isSyncPossible() {
- return networkConnected && isConfigured(context.getApplicationContext());
+ return isSyncPossible;
+ }
+
+ public boolean isNetworkConnected() {
+ return networkConnected;
+ }
+
+ public boolean isSyncOnlyOnWifi() {
+ return syncOnlyOnWifi;
}
/**
@@ -185,8 +171,17 @@ public class NoteServerSyncHelper {
*
* @param callback Implementation of ISyncCallback, contains one method that shall be executed.
*/
- public void addCallbackPush(ISyncCallback callback) {
- callbacksPush.add(callback);
+ public void addCallbackPush(SingleSignOnAccount ssoAccount, ISyncCallback callback) {
+ if (ssoAccount == null) {
+ Log.i(TAG, "ssoAccount is null. Is this a local account?");
+ callback.onScheduled();
+ callback.onFinish();
+ } else {
+ if (!callbacksPush.containsKey(ssoAccount.name)) {
+ callbacksPush.put(ssoAccount.name, new ArrayList<>());
+ }
+ Objects.requireNonNull(callbacksPush.get(ssoAccount.name)).add(callback);
+ }
}
/**
@@ -197,8 +192,17 @@ public class NoteServerSyncHelper {
*
* @param callback Implementation of ISyncCallback, contains one method that shall be executed.
*/
- public void addCallbackPull(ISyncCallback callback) {
- callbacksPull.add(callback);
+ public void addCallbackPull(SingleSignOnAccount ssoAccount, ISyncCallback callback) {
+ if (ssoAccount == null) {
+ Log.i(TAG, "ssoAccount is null. Is this a local account?");
+ callback.onScheduled();
+ callback.onFinish();
+ } else {
+ if (!callbacksPull.containsKey(ssoAccount.name)) {
+ callbacksPull.put(ssoAccount.name, new ArrayList<>());
+ }
+ Objects.requireNonNull(callbacksPull.get(ssoAccount.name)).add(callback);
+ }
}
@@ -208,28 +212,39 @@ public class NoteServerSyncHelper {
*
* @param onlyLocalChanges Whether to only push local changes to the server or to also load the whole list of notes from the server.
*/
- public void scheduleSync(boolean onlyLocalChanges) {
- Log.d(TAG, "Sync requested (" + (onlyLocalChanges ? "onlyLocalChanges" : "full") + "; " + (syncActive ? "sync active" : "sync NOT active") + ") ...");
- if (isSyncPossible() && (!syncActive || onlyLocalChanges)) {
- Log.d(TAG, "... starting now");
- SyncTask syncTask = new SyncTask(onlyLocalChanges);
- syncTask.addCallbacks(callbacksPush);
- callbacksPush = new ArrayList<>();
- if (!onlyLocalChanges) {
- syncTask.addCallbacks(callbacksPull);
- callbacksPull = new ArrayList<>();
- }
- syncTask.execute();
- } else if (!onlyLocalChanges) {
- Log.d(TAG, "... scheduled");
- syncScheduled = true;
- for (ISyncCallback callback : callbacksPush) {
- callback.onScheduled();
- }
+ public void scheduleSync(SingleSignOnAccount ssoAccount, boolean onlyLocalChanges) {
+ if (ssoAccount == null) {
+ Log.i(TAG, "ssoAccount is null. Is this a local account?");
} else {
- Log.d(TAG, "... do nothing");
- for (ISyncCallback callback : callbacksPush) {
- callback.onScheduled();
+ if (syncActive.get(ssoAccount.name) == null) {
+ syncActive.put(ssoAccount.name, false);
+ }
+ Log.d(TAG, "Sync requested (" + (onlyLocalChanges ? "onlyLocalChanges" : "full") + "; " + (syncActive.get(ssoAccount.name) ? "sync active" : "sync NOT active") + ") ...");
+ if (isSyncPossible() && (!syncActive.get(ssoAccount.name) || onlyLocalChanges)) {
+ Log.d(TAG, "... starting now");
+ SyncTask syncTask = new SyncTask(db.getLocalAccountByAccountName(ssoAccount.name), ssoAccount, onlyLocalChanges);
+ syncTask.addCallbacks(ssoAccount, callbacksPush.get(ssoAccount.name));
+ callbacksPush.put(ssoAccount.name, new ArrayList<>());
+ if (!onlyLocalChanges) {
+ syncTask.addCallbacks(ssoAccount, callbacksPull.get(ssoAccount.name));
+ callbacksPull.put(ssoAccount.name, new ArrayList<>());
+ }
+ syncTask.execute();
+ } else if (!onlyLocalChanges) {
+ Log.d(TAG, "... scheduled");
+ syncScheduled.put(ssoAccount.name, true);
+ if (callbacksPush.containsKey(ssoAccount.name) && callbacksPush.get(ssoAccount.name) != null) {
+ for (ISyncCallback callback : callbacksPush.get(ssoAccount.name)) {
+ callback.onScheduled();
+ }
+ }
+ } else {
+ Log.d(TAG, "... do nothing");
+ if (callbacksPull.containsKey(ssoAccount.name) && callbacksPull.get(ssoAccount.name) != null) {
+ for (ISyncCallback callback : callbacksPush.get(ssoAccount.name)) {
+ callback.onScheduled();
+ }
+ }
}
}
}
@@ -239,18 +254,20 @@ public class NoteServerSyncHelper {
NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
if (activeInfo != null && activeInfo.isConnected()) {
- networkConnected =
+ networkConnected = true;
+ isSyncPossible =
!syncOnlyOnWifi || ((ConnectivityManager) context.getApplicationContext()
.getSystemService(Context.CONNECTIVITY_SERVICE))
.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected();
- if (networkConnected) {
+ if (isSyncPossible) {
Log.d(TAG, "Network connection established.");
} else {
Log.d(TAG, "Network connected, but not used because only synced on wifi.");
}
} else {
networkConnected = false;
+ isSyncPossible = false;
Log.d(TAG, "No network connection.");
}
}
@@ -260,25 +277,32 @@ public class NoteServerSyncHelper {
* Synchronization consists of two parts: pushLocalChanges and pullRemoteChanges.
*/
private class SyncTask extends AsyncTask<Void, Void, LoginStatus> {
+ private final LocalAccount localAccount;
+ private final SingleSignOnAccount ssoAccount;
private final boolean onlyLocalChanges;
- private final List<ISyncCallback> callbacks = new ArrayList<>();
+ private final Map<String, List<ISyncCallback>> callbacks = new HashMap<>();
private List<Throwable> exceptions = new ArrayList<>();
- SyncTask(boolean onlyLocalChanges) {
+ SyncTask(@NonNull LocalAccount localAccount, @NonNull SingleSignOnAccount ssoAccount, boolean onlyLocalChanges) {
+ this.localAccount = localAccount;
+ this.ssoAccount = ssoAccount;
this.onlyLocalChanges = onlyLocalChanges;
}
- void addCallbacks(List<ISyncCallback> callbacks) {
- this.callbacks.addAll(callbacks);
+ void addCallbacks(SingleSignOnAccount ssoAccount, List<ISyncCallback> callbacks) {
+ this.callbacks.put(ssoAccount.name, callbacks);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
- if (!onlyLocalChanges && syncScheduled) {
- syncScheduled = false;
+ if (!syncScheduled.containsKey(ssoAccount.name) || syncScheduled.get(ssoAccount.name) == null) {
+ syncScheduled.put(ssoAccount.name, false);
}
- syncActive = true;
+ if (!onlyLocalChanges && syncScheduled.get(ssoAccount.name)) {
+ syncScheduled.put(ssoAccount.name, false);
+ }
+ syncActive.put(ssoAccount.name, true);
}
@Override
@@ -314,20 +338,20 @@ public class NoteServerSyncHelper {
// if note is not new, try to edit it.
if (note.getRemoteId() > 0) {
Log.v(TAG, " ...try to edit");
- remoteNote = notesClient.editNote(note).getNote();
+ remoteNote = notesClient.editNote(ssoAccount, note).getNote();
}
// However, the note may be deleted on the server meanwhile; or was never synchronized -> (re)create
// Please note, thas db.updateNote() realizes an optimistic conflict resolution, which is required for parallel changes of this Note from the UI.
if (remoteNote == null) {
Log.v(TAG, " ...Note does not exist on server -> (re)create");
- remoteNote = notesClient.createNote(note).getNote();
+ remoteNote = notesClient.createNote(ssoAccount, note).getNote();
}
db.updateNote(note.getId(), remoteNote, note);
break;
case LOCAL_DELETED:
if (note.getRemoteId() > 0) {
Log.v(TAG, " ...delete (from server and local)");
- notesClient.deleteNote(note.getRemoteId());
+ notesClient.deleteNote(ssoAccount, note.getRemoteId());
} else {
Log.v(TAG, " ...delete (only local, since it was not synchronized)");
}
@@ -360,7 +384,7 @@ public class NoteServerSyncHelper {
LoginStatus status;
try {
Map<Long, Long> idMap = db.getIdMap(localAccount.getId());
- ServerResponse.NotesResponse response = notesClient.getNotes(localAccount.getModified(), localAccount.getEtag());
+ ServerResponse.NotesResponse response = notesClient.getNotes(ssoAccount, localAccount.getModified(), localAccount.getEtag());
List<CloudNote> remoteNotes = response.getNotes();
Set<Long> remoteIDs = new HashSet<>();
// pull remote changes: update or create each remote note
@@ -453,16 +477,18 @@ public class NoteServerSyncHelper {
}
}
}
- syncActive = false;
+ syncActive.put(ssoAccount.name, false);
// notify callbacks
- for (ISyncCallback callback : callbacks) {
- callback.onFinish();
+ if (callbacks.containsKey(ssoAccount.name) && callbacks.get(ssoAccount.name) != null) {
+ for (ISyncCallback callback : callbacks.get(ssoAccount.name)) {
+ callback.onFinish();
+ }
}
db.notifyNotesChanged();
db.updateDynamicShortcuts(localAccount.getId());
// start next sync if scheduled meanwhile
- if (syncScheduled) {
- scheduleSync(false);
+ if (syncScheduled.containsKey(ssoAccount.name) && syncScheduled.get(ssoAccount.name) != null && syncScheduled.get(ssoAccount.name)) {
+ scheduleSync(ssoAccount, false);
}
}
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesClient.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesClient.java
index 9c3fda3f..76d2f538 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesClient.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesClient.java
@@ -11,10 +11,7 @@ import com.nextcloud.android.sso.aidl.NextcloudRequest;
import com.nextcloud.android.sso.api.AidlNetworkRequest;
import com.nextcloud.android.sso.api.NextcloudAPI;
import com.nextcloud.android.sso.api.Response;
-import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedException;
-import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
-import com.nextcloud.android.sso.helper.SingleAccountHelper;
import com.nextcloud.android.sso.model.SingleSignOnAccount;
import org.json.JSONObject;
@@ -38,7 +35,7 @@ public class NotesClient {
private static final String TAG = NotesClient.class.getSimpleName();
private final Context appContext;
- private NextcloudAPI mNextcloudAPI;
+ private static Map<String, NextcloudAPI> mNextcloudAPIs = new HashMap<>();
/**
* This entity class is used to return relevant data of the HTTP reponse.
@@ -48,7 +45,7 @@ public class NotesClient {
private final String etag;
private final long lastModified;
- public ResponseData(String content, String etag, long lastModified) {
+ ResponseData(String content, String etag, long lastModified) {
this.content = content;
this.etag = etag;
this.lastModified = lastModified;
@@ -93,45 +90,21 @@ public class NotesClient {
NotesClient(Context appContext) {
this.appContext = appContext;
- updateAccount();
}
- void updateAccount() {
- if (mNextcloudAPI != null) {
- mNextcloudAPI.stop();
- }
- try {
- SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(appContext);
- Log.v(TAG, "NextcloudRequest account: " + ssoAccount.name);
- mNextcloudAPI = new NextcloudAPI(appContext, ssoAccount, new GsonBuilder().create(), new NextcloudAPI.ApiConnectedListener() {
- @Override
- public void onConnected() {
- Log.v(TAG, "SSO API connected");
- }
-
- @Override
- public void onError(Exception ex) {
- ex.printStackTrace();
- }
- });
- } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
- e.printStackTrace();
- }
- }
-
- NotesResponse getNotes(long lastModified, String lastETag) throws Exception {
+ NotesResponse getNotes(SingleSignOnAccount ssoAccount, long lastModified, String lastETag) throws Exception {
Map<String, String> parameter = new HashMap<>();
parameter.put(GET_PARAM_KEY_PRUNE_BEFORE, Long.toString(lastModified));
- return new NotesResponse(requestServer("notes", METHOD_GET, parameter, null, lastETag));
+ return new NotesResponse(requestServer(ssoAccount, "notes", METHOD_GET, parameter, null, lastETag));
}
- private NoteResponse putNote(CloudNote note, String path, String method) throws Exception {
+ private NoteResponse putNote(SingleSignOnAccount ssoAccount, CloudNote note, String path, String method) throws Exception {
JSONObject paramObject = new JSONObject();
paramObject.accumulate(JSON_CONTENT, note.getContent());
paramObject.accumulate(JSON_MODIFIED, note.getModified().getTimeInMillis() / 1000);
paramObject.accumulate(JSON_FAVORITE, note.isFavorite());
paramObject.accumulate(JSON_CATEGORY, note.getCategory());
- return new NoteResponse(requestServer(path, method, null, paramObject, null));
+ return new NoteResponse(requestServer(ssoAccount, path, method, null, paramObject, null));
}
/**
@@ -141,17 +114,17 @@ public class NotesClient {
* @return Created Note including generated Title, ID and lastModified-Date
* @throws Exception
*/
- NoteResponse createNote(CloudNote note) throws Exception {
- return putNote(note, "notes", METHOD_POST);
+ NoteResponse createNote(SingleSignOnAccount ssoAccount, CloudNote note) throws Exception {
+ return putNote(ssoAccount, note, "notes", METHOD_POST);
}
- NoteResponse editNote(CloudNote note) throws Exception {
- return putNote(note, "notes/" + note.getRemoteId(), METHOD_PUT);
+ NoteResponse editNote(SingleSignOnAccount ssoAccount, CloudNote note) throws Exception {
+ return putNote(ssoAccount, note, "notes/" + note.getRemoteId(), METHOD_PUT);
}
- void deleteNote(long noteId) {
+ void deleteNote(SingleSignOnAccount ssoAccount, long noteId) {
try {
- this.requestServer("notes/" + noteId, METHOD_DELETE, null, null, null);
+ this.requestServer(ssoAccount, "notes/" + noteId, METHOD_DELETE, null, null, null);
} catch (Exception e) {
e.printStackTrace();
}
@@ -167,7 +140,7 @@ public class NotesClient {
* @param lastETag optional ETag of last response
* @return Body of answer
*/
- private ResponseData requestServer(String target, String method, Map<String, String> parameter, JSONObject requestBody, String lastETag) throws Exception {
+ private ResponseData requestServer(SingleSignOnAccount ssoAccount, String target, String method, Map<String, String> parameter, JSONObject requestBody, String lastETag) throws Exception {
NextcloudRequest.Builder requestBuilder = new NextcloudRequest.Builder()
.setMethod(method)
.setUrl(API_PATH + target);
@@ -189,9 +162,9 @@ public class NotesClient {
StringBuilder result = new StringBuilder();
- Log.v(TAG, "NextcloudRequest: " + nextcloudRequest.toString());
try {
- Response response = mNextcloudAPI.performNetworkRequestV2(nextcloudRequest);
+ Log.v(TAG, ssoAccount.name + " => " + nextcloudRequest.getMethod() + " " + nextcloudRequest.getUrl() + " ");
+ Response response = getNextcloudAPI(appContext, ssoAccount).performNetworkRequestV2(nextcloudRequest);
Log.v(TAG, "NextcloudRequest: " + nextcloudRequest.toString());
BufferedReader rd = new BufferedReader(new InputStreamReader(response.getBody()));
String line;
@@ -223,4 +196,25 @@ public class NotesClient {
}
}
}
+
+ private static NextcloudAPI getNextcloudAPI(Context appContext, SingleSignOnAccount ssoAccount) {
+ if (mNextcloudAPIs.containsKey(ssoAccount.name)) {
+ return mNextcloudAPIs.get(ssoAccount.name);
+ } else {
+ Log.v(TAG, "NextcloudRequest account: " + ssoAccount.name);
+ NextcloudAPI nextcloudAPI = new NextcloudAPI(appContext, ssoAccount, new GsonBuilder().create(), new NextcloudAPI.ApiConnectedListener() {
+ @Override
+ public void onConnected() {
+ Log.v(TAG, "SSO API connected for " + ssoAccount);
+ }
+
+ @Override
+ public void onError(Exception ex) {
+ ex.printStackTrace();
+ }
+ });
+ mNextcloudAPIs.put(ssoAccount.name, nextcloudAPI);
+ return nextcloudAPI;
+ }
+ }
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/SyncWorker.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/SyncWorker.java
new file mode 100644
index 00000000..05daacef
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/SyncWorker.java
@@ -0,0 +1,78 @@
+package it.niedermann.owncloud.notes.persistence;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.work.Constraints;
+import androidx.work.ExistingPeriodicWorkPolicy;
+import androidx.work.NetworkType;
+import androidx.work.PeriodicWorkRequest;
+import androidx.work.WorkManager;
+import androidx.work.Worker;
+import androidx.work.WorkerParameters;
+
+import com.nextcloud.android.sso.AccountImporter;
+import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
+import com.nextcloud.android.sso.model.SingleSignOnAccount;
+
+import java.util.concurrent.TimeUnit;
+
+import it.niedermann.owncloud.notes.R;
+import it.niedermann.owncloud.notes.model.LocalAccount;
+
+public class SyncWorker extends Worker {
+
+ private static final String TAG = SyncWorker.class.getCanonicalName();
+ private static final String WORKER_TAG = "background_synchronization";
+
+ private static final Constraints constraints = new Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .build();
+
+ public SyncWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
+ super(context, workerParams);
+ }
+
+ @NonNull
+ @Override
+ public Result doWork() {
+ NoteSQLiteOpenHelper db = NoteSQLiteOpenHelper.getInstance(getApplicationContext());
+ for (LocalAccount account : db.getAccounts()) {
+ try {
+ SingleSignOnAccount ssoAccount = AccountImporter.getSingleSignOnAccount(getApplicationContext(), account.getAccountName());
+ Log.v(TAG, "Starting background synchronization for " + ssoAccount.name);
+ db.getNoteServerSyncHelper().addCallbackPull(ssoAccount, () -> Log.v(TAG, "Finished background synchronization for " + ssoAccount.name));
+ db.getNoteServerSyncHelper().scheduleSync(ssoAccount, false);
+ } catch (NextcloudFilesAppAccountNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+ // TODO return result depending on callbackPull
+ return Result.success();
+ }
+
+ public static void update(@NonNull Context context, @NonNull String preferenceValue) {
+ deregister(context);
+ if (!context.getString(R.string.pref_value_sync_off).equals(preferenceValue)) {
+ int repeatInterval = 15;
+ TimeUnit unit = TimeUnit.MINUTES;
+ if (context.getString(R.string.pref_value_sync_1_hour).equals(preferenceValue)) {
+ repeatInterval = 1;
+ unit = TimeUnit.HOURS;
+ } else if (context.getString(R.string.pref_value_sync_6_hours).equals(preferenceValue)) {
+ repeatInterval = 6;
+ unit = TimeUnit.HOURS;
+ }
+ PeriodicWorkRequest work = new PeriodicWorkRequest.Builder(SyncWorker.class, repeatInterval, unit)
+ .setConstraints(constraints).build();
+ WorkManager.getInstance(context.getApplicationContext()).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, work);
+ Log.v(TAG, "Registering worker running each " + repeatInterval + " " + unit);
+ }
+ }
+
+ private static void deregister(@NonNull Context context) {
+ Log.v(TAG, "Deregistering all workers with tag \"" + WORKER_TAG + "\"");
+ WorkManager.getInstance(context.getApplicationContext()).cancelAllWorkByTag(WORKER_TAG);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java
index 6759d223..5089f14d 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/util/SSOUtil.java
@@ -1,13 +1,17 @@
package it.niedermann.owncloud.notes.util;
import android.app.Activity;
+import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import com.nextcloud.android.sso.AccountImporter;
import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGranted;
+import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException;
+import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
+import com.nextcloud.android.sso.helper.SingleAccountHelper;
import com.nextcloud.android.sso.ui.UiExceptionManager;
public class SSOUtil {
@@ -36,4 +40,15 @@ public class SSOUtil {
AccountImporter.requestAndroidAccountPermissionsAndPickAccount(activity);
}
}
+
+ public static boolean isConfigured(Context context) {
+ try {
+ SingleAccountHelper.getCurrentSingleSignOnAccount(context);
+ return true;
+ } catch (NextcloudFilesAppAccountNotFoundException e) {
+ return false;
+ } catch (NoCurrentAccountSelectedException e) {
+ return false;
+ }
+ }
}
diff --git a/app/src/main/res/drawable/ic_network_wifi_grey600_24dp.xml b/app/src/main/res/drawable/ic_network_wifi_grey600_24dp.xml
new file mode 100644
index 00000000..1c3b9a41
--- /dev/null
+++ b/app/src/main/res/drawable/ic_network_wifi_grey600_24dp.xml
@@ -0,0 +1,6 @@
+<vector android:autoMirrored="true" android:height="24dp"
+ android:tint="#757575" android:viewportHeight="24.0"
+ android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillAlpha=".3" android:fillColor="#FF000000" android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"/>
+ <path android:fillColor="#FF000000" android:pathData="M3.53,10.95l8.46,10.54 0.01,0.01 0.01,-0.01 8.46,-10.54C20.04,10.62 16.81,8 12,8c-4.81,0 -8.04,2.62 -8.47,2.95z"/>
+</vector>
diff --git a/app/src/main/res/layout/item_notes_list_note_item.xml b/app/src/main/res/layout/item_notes_list_note_item.xml
index 19ba07b4..2c6a0eed 100644
--- a/app/src/main/res/layout/item_notes_list_note_item.xml
+++ b/app/src/main/res/layout/item_notes_list_note_item.xml
@@ -79,7 +79,8 @@
android:id="@+id/noteTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:singleLine="true"
+ android:ellipsize="end"
+ android:maxLines="1"
android:layout_weight="1"
android:textSize="@dimen/primary_font_size"
android:textColor="?android:textColorPrimary"
diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml
index f5011dc4..c0899102 100644
--- a/app/src/main/res/values-sl/strings.xml
+++ b/app/src/main/res/values-sl/strings.xml
@@ -119,6 +119,7 @@
<string name="bulk_notes_deleted">Izbrisane %1$d zabeležke</string>
<string name="bulk_notes_restored">Obnovljene %1$d zabeležke</string>
<string name="category_readonly">Le za branje</string>
+ <string name="no_category">Brez kategorije</string>
<string name="add_category">Dodaj %1$s</string>
<!-- Array: note modes -->
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 50060128..ae276031 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -16,6 +16,8 @@
<string name="simple_bold">Подебљано</string>
<string name="simple_link">Веза</string>
<string name="simple_italic">Курзив</string>
+ <string name="action_note_deleted">Обрисано %1$s</string>
+ <string name="action_note_restored">Повраћено %1$s</string>
<string name="action_undo">Опозови</string>
<string name="action_drawer_open">отвори навигацију</string>
<string name="action_drawer_close">затвори навигацију</string>
@@ -50,6 +52,7 @@
<string name="error_sync">Синхронизација није успела%1$s</string>
<string name="error_json">Да ли је апликација Белешки активирана на серверу?</string>
<string name="error_no_network">нема мрежне везе</string>
+ <string name="error_files_app">да ли имате инсталирану апликацију за фајлове?</string>
<string name="error_unknown">Десила се непозната грешка</string>
<!-- About -->
@@ -108,9 +111,15 @@
<string name="account_already_imported">Налог је већ увезен</string>
<string name="no_notes_yet">Још нема белешки</string>
<string name="no_notes_yet_description">Притисните + дугме да направите нову белешку</string>
+ <string name="could_not_load_preview_two_digit_numbered_list">Не могу да учитам преглед. Проверите да ли постоји двоцифрена ставка у листи без садржаја.</string>
<string name="simple_more">Још</string>
<string name="simple_move">Помери</string>
+ <string name="error_files_app_version_too_old">Да ли је Ваша апликација за фајлове на најновијој верзији?</string>
+ <string name="checkbox_could_not_be_toggled">Поље за потврду не може да се укључи/искључи.</string>
+ <string name="bulk_notes_deleted">Обрисано %1$d белешки</string>
+ <string name="bulk_notes_restored">Повраћено %1$d белешки</string>
<string name="category_readonly">Само за читање</string>
+ <string name="no_category">Нема категорије</string>
<string name="add_category">Додај %1$s</string>
<!-- Array: note modes -->
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 1d4ed668..cc342086 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -10,4 +10,10 @@
<item>@string/pref_value_font_size_medium</item>
<item>@string/pref_value_font_size_large</item>
</string-array>
+ <string-array name="sync_values">
+ <item>@string/pref_value_sync_off</item>
+ <item>@string/pref_value_sync_15_minutes</item>
+ <item>@string/pref_value_sync_1_hour</item>
+ <item>@string/pref_value_sync_6_hours</item>
+ </string-array>
</resources> \ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3b5ff041..8b353149 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -44,6 +44,7 @@
<string name="settings_font_size">Font size</string>
<string name="settings_wifi_only">Sync only on Wi-Fi</string>
<string name="settings_lock">Password protection</string>
+ <string name="settings_background_sync">Background synchronization</string>
<!-- Certificates -->
@@ -114,16 +115,23 @@
<string name="pref_key_wifi_only" translatable="false">wifiOnly</string>
<string name="pref_key_lock" translatable="false">lock</string>
<string name="pref_key_last_note_mode" translatable="false">lastNoteMode</string>
+ <string name="pref_key_background_sync" translatable="false">backgroundSync</string>
<string name="pref_value_mode_edit" translatable="false">edit</string>
<string name="pref_value_mode_preview" translatable="false">preview</string>
<string name="pref_value_mode_last" translatable="false">last</string>
<string name="pref_value_font_size_small" translatable="false">small</string>
<string name="pref_value_font_size_medium" translatable="false">medium</string>
<string name="pref_value_font_size_large" translatable="false">large</string>
+ <string name="pref_value_sync_off" translatable="false">off</string>
+ <string name="pref_value_sync_15_minutes" translatable="false">15_minutes</string>
+ <string name="pref_value_sync_1_hour" translatable="false">1_hour</string>
+ <string name="pref_value_sync_6_hours" translatable="false">6_hours</string>
+ <!-- These values should not be translateable. They should be migrated at some point. -->
<string name="pref_value_theme_light">Light</string>
<string name="pref_value_font_normal">Normal</string>
<string name="pref_value_wifi_and_mobile">Sync on Wi-Fi and mobile data</string>
<string name="pref_value_lock">Password protection</string>
+
<string name="simple_error">Error</string>
<string name="simple_close">Close</string>
<string name="simple_copy">Copy</string>
@@ -166,6 +174,14 @@
<item>Large</item>
</string-array>
+ <!-- Array: background synchronization -->
+ <string-array name="sync_entries">
+ <item>Off</item>
+ <item>15 minutes</item>
+ <item>1 hour</item>
+ <item>6 hours</item>
+ </string-array>
+
<!-- Plurals -->
<plurals name="ab_selected">
<item quantity="one">%d selected</item>
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 3b3c4ead..9219480f 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -45,9 +45,19 @@
<SwitchPreference
android:defaultValue="@string/pref_value_wifi_and_mobile"
- android:icon="@drawable/ic_sync_black_24dp"
+ android:icon="@drawable/ic_network_wifi_grey600_24dp"
android:key="@string/pref_key_wifi_only"
android:layout="@layout/item_pref"
android:title="@string/settings_wifi_only" />
+ <ListPreference
+ android:defaultValue="@string/pref_value_sync_off"
+ android:entries="@array/sync_entries"
+ android:entryValues="@array/sync_values"
+ android:icon="@drawable/ic_sync_black_24dp"
+ android:key="@string/pref_key_background_sync"
+ android:layout="@layout/item_pref"
+ android:summary="%s"
+ android:title="@string/settings_background_sync" />
+
</PreferenceScreen>