#656 Background synchronization

Handle sync requests without proper SingleSignOn-Account

Signed-off-by: stefan-niedermann <info@niedermann.it>
This commit is contained in:
stefan-niedermann 2020-01-31 12:53:45 +01:00
parent a1b83d415b
commit 822e121039
6 changed files with 149 additions and 105 deletions

View file

@ -10,9 +10,6 @@ import androidx.recyclerview.widget.ItemTouchHelper;
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 it.niedermann.owncloud.notes.R;
@ -27,6 +24,7 @@ public class NotesListViewItemTouchHelper extends ItemTouchHelper {
private static final String TAG = NotesListViewItemTouchHelper.class.getCanonicalName();
public NotesListViewItemTouchHelper(
SingleSignOnAccount ssoAccount,
Context context,
ViewProvider viewProvider,
NoteSQLiteOpenHelper db,
@ -61,36 +59,31 @@ public class NotesListViewItemTouchHelper extends ItemTouchHelper {
*/
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
try {
SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context);
switch (direction) {
case ItemTouchHelper.LEFT: {
final DBNote dbNoteWithoutContent = (DBNote) adapter.getItem(viewHolder.getAdapterPosition());
final DBNote dbNote = db.getNote(dbNoteWithoutContent.getAccountId(), dbNoteWithoutContent.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(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();
})
.show();
break;
}
case ItemTouchHelper.RIGHT: {
final DBNote dbNote = (DBNote) adapter.getItem(viewHolder.getAdapterPosition());
db.toggleFavorite(ssoAccount, dbNote, syncCallBack);
refreshLists.run();
break;
}
switch (direction) {
case ItemTouchHelper.LEFT: {
final DBNote dbNoteWithoutContent = (DBNote) adapter.getItem(viewHolder.getAdapterPosition());
final DBNote dbNote = db.getNote(dbNoteWithoutContent.getAccountId(), dbNoteWithoutContent.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(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();
})
.show();
break;
}
case ItemTouchHelper.RIGHT: {
final DBNote dbNote = (DBNote) adapter.getItem(viewHolder.getAdapterPosition());
db.toggleFavorite(ssoAccount, dbNote, syncCallBack);
refreshLists.run();
break;
}
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
e.printStackTrace();
}
}

View file

@ -265,7 +265,7 @@ public class EditNoteActivity extends AppCompatActivity implements BaseNoteFragm
}
} 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();
}

View file

@ -191,18 +191,22 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap
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();
synchronize();
db.getNoteServerSyncHelper().addCallbackPull(ssoAccount, syncCallBack);
if (db.getNoteServerSyncHelper().isSyncPossible()) {
synchronize();
}
}
super.onResume();
}
@ -232,18 +236,19 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap
private void selectAccount(String accountName) {
fabCreate.hide();
SingleAccountHelper.setCurrentAccount(getApplicationContext(), accountName);
localAccount = db.getLocalAccountByAccountName(accountName);
try {
ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext());
localAccount = db.getLocalAccountByAccountName(accountName);
synchronize();
refreshLists();
fabCreate.show();
setupHeader();
setupNavigationList(ADAPTER_KEY_RECENT);
updateUsernameInDrawer();
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
e.printStackTrace();
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();
}
private void handleNotAuthorizedAccount() {
@ -316,13 +321,12 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap
}
});
// 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();
}
});
@ -533,7 +537,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap
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() {
@ -803,11 +807,23 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap
}
private void synchronize() {
swipeRefreshLayout.setRefreshing(true);
db.getNoteServerSyncHelper().addCallbackPull(ssoAccount, syncCallBack);
db.getNoteServerSyncHelper().scheduleSync(ssoAccount, 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());

View file

@ -45,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 {
@ -210,7 +211,7 @@ 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);
try {
SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(getContext());

View file

@ -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;
@ -47,6 +48,7 @@ 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.util.SSOUtil;
import it.niedermann.owncloud.notes.util.ServerResponse;
import static android.content.Context.CLIPBOARD_SERVICE;
@ -64,6 +66,7 @@ public class NoteServerSyncHelper {
private Context context;
// Track network connection changes using a BroadcastReceiver
private boolean isSyncPossible = false;
private boolean networkConnected = false;
private String syncOnlyOnWifiKey;
private boolean syncOnlyOnWifi;
@ -82,7 +85,7 @@ public class NoteServerSyncHelper {
@Override
public void onReceive(Context context, Intent intent) {
updateNetworkStatus();
if (isSyncPossible()) {
if (isSyncPossible() && SSOUtil.isConfigured(context)) {
try {
scheduleSync(SingleAccountHelper.getCurrentSingleSignOnAccount(context), false);
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
@ -101,7 +104,6 @@ public class NoteServerSyncHelper {
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();
@ -139,27 +141,26 @@ public class NoteServerSyncHelper {
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;
}
/**
@ -171,10 +172,16 @@ public class NoteServerSyncHelper {
* @param callback Implementation of ISyncCallback, contains one method that shall be executed.
*/
public void addCallbackPush(SingleSignOnAccount ssoAccount, ISyncCallback callback) {
if (!callbacksPush.containsKey(ssoAccount.name)) {
callbacksPush.put(ssoAccount.name, new ArrayList<>());
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);
}
Objects.requireNonNull(callbacksPush.get(ssoAccount.name)).add(callback);
}
/**
@ -186,10 +193,16 @@ public class NoteServerSyncHelper {
* @param callback Implementation of ISyncCallback, contains one method that shall be executed.
*/
public void addCallbackPull(SingleSignOnAccount ssoAccount, ISyncCallback callback) {
if (!callbacksPull.containsKey(ssoAccount.name)) {
callbacksPull.put(ssoAccount.name, new ArrayList<>());
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);
}
Objects.requireNonNull(callbacksPull.get(ssoAccount.name)).add(callback);
}
@ -200,33 +213,37 @@ 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(SingleSignOnAccount ssoAccount, boolean onlyLocalChanges) {
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();
}
}
if (ssoAccount == null) {
Log.i(TAG, "ssoAccount is null. Is this a local account?");
} 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();
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();
}
}
}
}
@ -237,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.");
}
}
@ -264,7 +283,7 @@ public class NoteServerSyncHelper {
private final Map<String, List<ISyncCallback>> callbacks = new HashMap<>();
private List<Throwable> exceptions = new ArrayList<>();
SyncTask(LocalAccount localAccount, SingleSignOnAccount ssoAccount, boolean onlyLocalChanges) {
SyncTask(@NonNull LocalAccount localAccount, @NonNull SingleSignOnAccount ssoAccount, boolean onlyLocalChanges) {
this.localAccount = localAccount;
this.ssoAccount = ssoAccount;
this.onlyLocalChanges = onlyLocalChanges;

View file

@ -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;
}
}
}