Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 68647e32 authored by Stefan Niedermann's avatar Stefan Niedermann
Browse files

#1303 Hide exceptions related to temporary network issues when not pulling manually

parent 2579a54e
Loading
Loading
Loading
Loading
+9 −5
Original line number Diff line number Diff line
@@ -212,10 +212,14 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A

        mainViewModel.hasMultipleAccountsConfigured().observe(this, hasMultipleAccountsConfigured -> canMoveNoteToAnotherAccounts = hasMultipleAccountsConfigured);
        mainViewModel.getSyncStatus().observe(this, syncStatus -> swipeRefreshLayout.setRefreshing(syncStatus));
        mainViewModel.getSyncErrors().observe(this, exceptions -> BrandedSnackbar.make(coordinatorLayout, R.string.error_synchronization, Snackbar.LENGTH_LONG)
        mainViewModel.getSyncErrors().observe(this, exceptions -> {
            if (mainViewModel.containsNonInfrastructureRelatedItems(exceptions)) {
                BrandedSnackbar.make(coordinatorLayout, R.string.error_synchronization, Snackbar.LENGTH_LONG)
                        .setAction(R.string.simple_more, v -> ExceptionDialogFragment.newInstance(exceptions)
                                .show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()))
                .show());
                        .show();
            }
        });
        mainViewModel.getSelectedCategory().observe(this, (selectedCategory) -> {
            binding.activityNotesListView.emptyContentView.getRoot().setVisibility(GONE);
            adapter.setShowCategory(selectedCategory.getType() == RECENT || selectedCategory.getType() == FAVORITES);
@@ -306,7 +310,7 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A
                    .apply(RequestOptions.circleCropTransform())
                    .into(activityBinding.launchAccountSwitcher);

            mainViewModel.synchronizeNotes(nextAccount, new IResponseCallback<Void>() {
            mainViewModel.synchronizeNotes(nextAccount, new IResponseCallback<>() {
                @Override
                public void onSuccess(Void v) {
                    Log.d(TAG, "Successfully synchronized notes for " + nextAccount.getAccountName());
+60 −18
Original line number Diff line number Diff line
package it.niedermann.owncloud.notes.main;

import static androidx.lifecycle.Transformations.distinctUntilChanged;
import static androidx.lifecycle.Transformations.map;
import static androidx.lifecycle.Transformations.switchMap;
import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
import static it.niedermann.owncloud.notes.main.MainActivity.ADAPTER_KEY_RECENT;
import static it.niedermann.owncloud.notes.main.MainActivity.ADAPTER_KEY_STARRED;
import static it.niedermann.owncloud.notes.main.slots.SlotterUtil.fillListByCategory;
import static it.niedermann.owncloud.notes.main.slots.SlotterUtil.fillListByInitials;
import static it.niedermann.owncloud.notes.main.slots.SlotterUtil.fillListByTime;
import static it.niedermann.owncloud.notes.shared.model.CategorySortingMethod.SORT_MODIFIED_DESC;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.DEFAULT_CATEGORY;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.FAVORITES;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.RECENT;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.UNCATEGORIZED;
import static it.niedermann.owncloud.notes.shared.util.DisplayUtils.convertToCategoryNavigationItem;

import android.accounts.NetworkErrorException;
import android.app.Application;
import android.content.Context;
@@ -19,13 +35,14 @@ import androidx.lifecycle.SavedStateHandle;
import com.nextcloud.android.sso.AccountImporter;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
import com.nextcloud.android.sso.exceptions.UnknownErrorException;
import com.nextcloud.android.sso.helper.SingleAccountHelper;
import com.nextcloud.android.sso.model.SingleSignOnAccount;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
@@ -50,22 +67,6 @@ import it.niedermann.owncloud.notes.shared.model.ImportStatus;
import it.niedermann.owncloud.notes.shared.model.Item;
import it.niedermann.owncloud.notes.shared.model.NavigationCategory;

import static androidx.lifecycle.Transformations.distinctUntilChanged;
import static androidx.lifecycle.Transformations.map;
import static androidx.lifecycle.Transformations.switchMap;
import static it.niedermann.owncloud.notes.main.MainActivity.ADAPTER_KEY_RECENT;
import static it.niedermann.owncloud.notes.main.MainActivity.ADAPTER_KEY_STARRED;
import static it.niedermann.owncloud.notes.main.slots.SlotterUtil.fillListByCategory;
import static it.niedermann.owncloud.notes.main.slots.SlotterUtil.fillListByInitials;
import static it.niedermann.owncloud.notes.main.slots.SlotterUtil.fillListByTime;
import static it.niedermann.owncloud.notes.shared.model.CategorySortingMethod.SORT_MODIFIED_DESC;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.DEFAULT_CATEGORY;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.FAVORITES;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.RECENT;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.UNCATEGORIZED;
import static it.niedermann.owncloud.notes.shared.util.DisplayUtils.convertToCategoryNavigationItem;
import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;

public class MainViewModel extends AndroidViewModel {

    private static final String TAG = MainViewModel.class.getSimpleName();
@@ -621,4 +622,45 @@ public class MainViewModel extends AndroidViewModel {
        }
        return noteContents.toString();
    }

    /**
     * @return <code>true</code> if {@param exceptions} contains at least one exception which is not caused by flaky infrastructure.
     * @see <a href="https://github.com/stefan-niedermann/nextcloud-notes/issues/1303">Issue #1303</a>
     */
    public boolean containsNonInfrastructureRelatedItems(@Nullable Collection<Throwable> exceptions) {
        if (exceptions == null || exceptions.isEmpty()) {
            return false;
        }

        return exceptions.stream().anyMatch(e -> !exceptionIsInfrastructureRelated(e));
    }

    private boolean exceptionIsInfrastructureRelated(@Nullable Throwable e) {
        if (e == null) {
            return false;
        }

        if (e instanceof RuntimeException || e instanceof UnknownErrorException) {
            if (isSoftwareCausedConnectionAbort(e.getMessage()) || isNetworkUnreachable(e.getMessage())) {
                return true;
            }
        }

        return exceptionIsInfrastructureRelated(e.getCause());
    }

    private boolean isSoftwareCausedConnectionAbort(@Nullable String input) {
        if (input == null) {
            return false;
        }
        return input.toLowerCase(Locale.ROOT).contains("software caused connection abort");
    }

    private boolean isNetworkUnreachable(@Nullable String input) {
        if (input == null) {
            return false;
        }
        final var lower = input.toLowerCase(Locale.ROOT);
        return lower.contains("failed to connect") && lower.contains("network is unreachable");
    }
}
 No newline at end of file
+38 −0
Original line number Diff line number Diff line
package it.niedermann.owncloud.notes.main;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;

import android.content.Context;
@@ -11,6 +13,8 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
import androidx.lifecycle.SavedStateHandle;
import androidx.test.core.app.ApplicationProvider;

import com.nextcloud.android.sso.exceptions.UnknownErrorException;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -121,6 +125,40 @@ public class MainViewModelTest {
        assertEquals("Bar", navigationItems.get(2).label);
    }

    @Test
    public void containsNonInfrastructureRelatedItems() {
        //noinspection ConstantConditions
        final var vm = new MainViewModel(ApplicationProvider.getApplicationContext(), null);
        assertFalse(vm.containsNonInfrastructureRelatedItems(null));
        assertFalse(vm.containsNonInfrastructureRelatedItems(Collections.emptyList()));

        assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new UnknownErrorException("Software caused connection abort"))));
        assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new UnknownErrorException("failed to connect to example.com (port 443) from /:: (port 39885) after 5000ms: connect failed: ENETUNREACH (Network is unreachable)"))));
        assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new UnknownErrorException("Foo bar"))));
        assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"))));
        assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"), new UnknownErrorException("Software caused connection abort"))));

        assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException("Software caused connection abort"))));
        assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException("failed to connect to example.com (port 443) from /:: (port 39885) after 5000ms: connect failed: ENETUNREACH (Network is unreachable)"))));
        assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException("Foo bar"))));
        assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"))));
        assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"), new RuntimeException("Software caused connection abort"))));

        assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException(new UnknownErrorException("Software caused connection abort")))));
        assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException(new UnknownErrorException("failed to connect to example.com (port 443) from /:: (port 39885) after 5000ms: connect failed: ENETUNREACH (Network is unreachable)")))));
        assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException(new UnknownErrorException("Foo bar")))));
        assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"))));
        assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"), new RuntimeException(new UnknownErrorException("Software caused connection abort")))));

        assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException("Foo", new UnknownErrorException("Software caused connection abort")))));
        assertFalse(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException("Foo", new UnknownErrorException("failed to connect to example.com (port 443) from /:: (port 39885) after 5000ms: connect failed: ENETUNREACH (Network is unreachable)")))));
        assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException("Foo", new UnknownErrorException("Foo bar")))));
        assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"))));
        assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new Exception("Software caused connection abort"), new RuntimeException("Foo", new UnknownErrorException("Software caused connection abort")))));

        assertTrue(vm.containsNonInfrastructureRelatedItems(List.of(new RuntimeException("Foo", new Exception("Software caused connection abort")))));
    }

    private static List<CategoryWithNotesCount> getSaneCategoriesWithNotesCount() {
        return List.of(
                new CategoryWithNotesCount(1, "Foo", 13),