diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f9934458fa6c5cc4a686c4bf80221cf6a2ecd7b2..6c74facedea67ae2e092b4ac27472734a418574b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:1263-Add_java_17_support" +image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:master" stages: - update-from-upstream diff --git a/app/build.gradle b/app/build.gradle index 4912627102985803c08a9ac760944c70480789dd..8aad4900db9a8afd885faf2cbcfb3385dc049a5a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,14 +13,13 @@ plugins { // Android configuration android { - compileSdkVersion 33 - buildToolsVersion '33.0.2' + compileSdk 34 defaultConfig { applicationId "foundation.e.accountmanager" - versionCode 403050200 - versionName '4.3.5.2' + versionCode 403060100 + versionName '4.3.6.1' buildConfigField "long", "buildTime", System.currentTimeMillis() + "L" @@ -53,7 +52,7 @@ android { composeOptions { // Keep this in sync with Kotlin version: // https://developer.android.com/jetpack/androidx/releases/compose-kotlin - kotlinCompilerExtensionVersion = "1.4.8" + kotlinCompilerExtensionVersion = "1.5.1" } // Java namespace for our classes (not to be confused with Android package ID) @@ -134,7 +133,7 @@ ksp { } configurations { - all { + configureEach { // exclude modules which are in conflict with system libraries exclude module: "commons-logging" exclude group: "org.json", module: "json" @@ -146,7 +145,7 @@ configurations { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' implementation "com.google.dagger:hilt-android:${versions.hilt}" @@ -154,21 +153,21 @@ dependencies { // support libs implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'androidx.browser:browser:1.5.0' + implementation 'androidx.browser:browser:1.6.0' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.concurrent:concurrent-futures-ktx:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.core:core-ktx:1.10.1' - implementation 'androidx.fragment:fragment-ktx:1.6.0' + implementation 'androidx.fragment:fragment-ktx:1.6.1' implementation 'androidx.hilt:hilt-work:1.0.0' kapt 'androidx.hilt:hilt-compiler:1.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' - implementation 'androidx.paging:paging-runtime-ktx:3.1.1' - implementation 'androidx.preference:preference-ktx:1.2.0' + implementation 'androidx.paging:paging-runtime-ktx:3.2.0' + implementation 'androidx.preference:preference-ktx:1.2.1' implementation 'androidx.security:security-crypto:1.1.0-alpha06' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' - implementation 'androidx.work:work-runtime-ktx:2.8.1' + implementation "androidx.work:work-runtime-ktx:${versions.workManager}" implementation 'com.google.android.flexbox:flexbox:3.0.0' implementation 'androidx.recyclerview:recyclerview:1.3.0' implementation 'com.google.android.material:material:1.9.0' @@ -190,11 +189,11 @@ dependencies { // own libraries implementation "com.github.bitfireAT:cert4android:${versions.cert4android}" - implementation files('../libs/ical4android.aar') + implementation "foundation.e.lib:ical4android:${versions.ical4android}" implementation "foundation.e.lib:vcard4android:${versions.vcard4android}" // third-party libs - implementation 'org.mnode.ical4j:ical4j:3.2.11' + implementation 'org.mnode.ical4j:ical4j:3.2.13' implementation 'com.jaredrummler:colorpicker:1.1.0' implementation "com.github.AppIntro:AppIntro:${versions.appIntro}" implementation("com.github.bitfireAT:dav4jvm:${versions.dav4jvm}") { @@ -240,9 +239,9 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test:rules:1.5.0' androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5' - androidTestImplementation 'androidx.work:work-testing:2.8.1' + androidTestImplementation "androidx.work:work-testing:${versions.workManager}" androidTestImplementation "com.squareup.okhttp3:mockwebserver:${versions.okhttp}" - androidTestImplementation 'io.mockk:mockk-android:1.13.4' + androidTestImplementation 'io.mockk:mockk-android:1.13.7' androidTestImplementation 'junit:junit:4.13.2' testImplementation "com.squareup.okhttp3:mockwebserver:${versions.okhttp}" diff --git a/app/src/androidTest/java/at/bitfire/davdroid/TestUtils.kt b/app/src/androidTest/java/at/bitfire/davdroid/TestUtils.kt index 05b1e7e9698e5bd434591afc7c2a28fe9e70512f..c7b9193aaf2d1a847f473433258502ec5ec0043a 100644 --- a/app/src/androidTest/java/at/bitfire/davdroid/TestUtils.kt +++ b/app/src/androidTest/java/at/bitfire/davdroid/TestUtils.kt @@ -11,12 +11,21 @@ import androidx.work.WorkInfo import androidx.work.WorkManager import androidx.work.WorkQuery import org.jetbrains.annotations.TestOnly +import org.junit.Assert.assertTrue import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException object TestUtils { + fun assertWithin(expected: Long, actual: Long, tolerance: Long) { + val absDifference = Math.abs(expected - actual) + assertTrue( + "$actual not within ($expected ± $tolerance)", + absDifference <= tolerance + ) + } + @TestOnly fun workScheduledOrRunning(context: Context, workerName: String): Boolean = workInStates(context, workerName, listOf( diff --git a/app/src/androidTest/java/at/bitfire/davdroid/resource/LocalCalendarTest.kt b/app/src/androidTest/java/at/bitfire/davdroid/resource/LocalCalendarTest.kt index af99d2c23481fd838afc37e83d032906813ac4ee..79a951b119b3e8b0830130a96104e9a354183345 100644 --- a/app/src/androidTest/java/at/bitfire/davdroid/resource/LocalCalendarTest.kt +++ b/app/src/androidTest/java/at/bitfire/davdroid/resource/LocalCalendarTest.kt @@ -15,8 +15,8 @@ import androidx.test.platform.app.InstrumentationRegistry import at.bitfire.davdroid.InitCalendarProviderRule import at.bitfire.ical4android.AndroidCalendar import at.bitfire.ical4android.Event -import at.bitfire.ical4android.util.MiscUtils.ContentProviderClientHelper.closeCompat -import at.bitfire.ical4android.util.MiscUtils.UriHelper.asSyncAdapter +import at.bitfire.ical4android.util.MiscUtils.closeCompat +import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter import net.fortuna.ical4j.model.property.DtStart import net.fortuna.ical4j.model.property.RRule import net.fortuna.ical4j.model.property.RecurrenceId diff --git a/app/src/androidTest/java/at/bitfire/davdroid/resource/LocalEventTest.kt b/app/src/androidTest/java/at/bitfire/davdroid/resource/LocalEventTest.kt index 69629f5793340cba69c35920e0a70f0711d4a24b..47e432e2fecbaf26152984f3e26ff5c72dc39a6c 100644 --- a/app/src/androidTest/java/at/bitfire/davdroid/resource/LocalEventTest.kt +++ b/app/src/androidTest/java/at/bitfire/davdroid/resource/LocalEventTest.kt @@ -16,7 +16,7 @@ import androidx.test.platform.app.InstrumentationRegistry import at.bitfire.davdroid.InitCalendarProviderRule import at.bitfire.ical4android.AndroidCalendar import at.bitfire.ical4android.Event -import at.bitfire.ical4android.util.MiscUtils.ContentProviderClientHelper.closeCompat +import at.bitfire.ical4android.util.MiscUtils.closeCompat import at.techbee.jtx.JtxContract.asSyncAdapter import net.fortuna.ical4j.model.Date import net.fortuna.ical4j.model.DateList diff --git a/app/src/androidTestOse/java/at/bitfire/davdroid/syncadapter/SyncManagerTest.kt b/app/src/androidTestOse/java/at/bitfire/davdroid/syncadapter/SyncManagerTest.kt index 1eaf2d534c28b181a894d2785aff1d84a85ded32..26bd340a448b6ddf5dd7d7264af84c7849fd71e1 100644 --- a/app/src/androidTestOse/java/at/bitfire/davdroid/syncadapter/SyncManagerTest.kt +++ b/app/src/androidTestOse/java/at/bitfire/davdroid/syncadapter/SyncManagerTest.kt @@ -19,6 +19,7 @@ import at.bitfire.dav4jvm.Response import at.bitfire.dav4jvm.Response.HrefRelation import at.bitfire.dav4jvm.property.GetETag import at.bitfire.davdroid.R +import at.bitfire.davdroid.TestUtils.assertWithin import at.bitfire.davdroid.db.Credentials import at.bitfire.davdroid.db.SyncState import at.bitfire.davdroid.network.HttpClient @@ -30,8 +31,15 @@ import okhttp3.Protocol import okhttp3.internal.http.StatusLine import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer -import org.junit.* -import org.junit.Assert.* +import org.junit.After +import org.junit.AfterClass +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Rule +import org.junit.Test import java.time.Instant import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -112,6 +120,30 @@ class SyncManagerTest { } + @Test + fun testGetDelayUntil_defaultOnNull() { + val now = Instant.now() + val delayUntil = SyncManager.getDelayUntil(null).epochSecond + val default = now.plusSeconds(SyncManager.DELAY_UNTIL_DEFAULT).epochSecond + assertWithin(default, delayUntil, 5) + } + + @Test + fun testGetDelayUntil_reducesToMax() { + val now = Instant.now() + val delayUntil = SyncManager.getDelayUntil(now.plusSeconds(10*24*60*60)).epochSecond + val max = now.plusSeconds(SyncManager.DELAY_UNTIL_MAX).epochSecond + assertWithin(max, delayUntil, 5) + } + + @Test + fun testGetDelayUntil_increasesToMin() { + val delayUntil = SyncManager.getDelayUntil(Instant.EPOCH).epochSecond + val min = Instant.now().plusSeconds(SyncManager.DELAY_UNTIL_MIN).epochSecond + assertWithin(min, delayUntil, 5) + } + + private fun queryCapabilitiesResponse(cTag: String? = null): MockResponse { val body = StringBuilder() body.append("\n" + @@ -148,7 +180,7 @@ class SyncManagerTest { .plusSeconds(60) .toEpochMilli() // 5 sec tolerance for test - assertTrue(result.delayUntil > (expected - 5000) && result.delayUntil < (expected + 5000)) + assertWithin(expected, result.delayUntil*1000, 5000) } @Test diff --git a/app/src/androidTestOse/java/at/bitfire/davdroid/syncadapter/SyncWorkerTest.kt b/app/src/androidTestOse/java/at/bitfire/davdroid/syncadapter/SyncWorkerTest.kt index a0c9ce9a1ac566723fdf94a598c85208a6117067..3ab4f15739e33e7528a522dec1485933ed6dd30a 100644 --- a/app/src/androidTestOse/java/at/bitfire/davdroid/syncadapter/SyncWorkerTest.kt +++ b/app/src/androidTestOse/java/at/bitfire/davdroid/syncadapter/SyncWorkerTest.kt @@ -18,6 +18,7 @@ import androidx.work.workDataOf import at.bitfire.davdroid.R import at.bitfire.davdroid.TestUtils.workScheduledOrRunningOrSuccessful import at.bitfire.davdroid.db.Credentials +import at.bitfire.davdroid.network.ConnectionUtils import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.ui.NotificationUtils import dagger.hilt.android.testing.HiltAndroidRule @@ -96,8 +97,9 @@ class SyncWorkerTest { val accountSettings = AccountSettings(context, account) accountSettings.setSyncWiFiOnly(true) + mockkObject(ConnectionUtils) + every { ConnectionUtils.wifiAvailable(any()) } returns true mockkObject(SyncWorker.Companion) - every { SyncWorker.Companion.wifiAvailable(any()) } returns true every { SyncWorker.Companion.correctWifiSsid(any(), any()) } returns true assertTrue(SyncWorker.wifiConditionsMet(context, accountSettings)) @@ -108,8 +110,9 @@ class SyncWorkerTest { val accountSettings = AccountSettings(context, account) accountSettings.setSyncWiFiOnly(true) + mockkObject(ConnectionUtils) + every { ConnectionUtils.wifiAvailable(any()) } returns false mockkObject(SyncWorker.Companion) - every { SyncWorker.Companion.wifiAvailable(any()) } returns false every { SyncWorker.Companion.correctWifiSsid(any(), any()) } returns true assertFalse(SyncWorker.wifiConditionsMet(context, accountSettings)) diff --git a/app/src/main/assets/translators.json b/app/src/main/assets/translators.json index eb66b1fa116d40e294d0a0c5189e6b82b9705025..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/app/src/main/assets/translators.json +++ b/app/src/main/assets/translators.json @@ -1 +0,0 @@ -{"ar_SA":["abdunnasir"],"bg":["dpa_transifex"],"ca":["Kintu","jordibrus","zagur"],"cs":["pavelb","tomas.odehnal"],"da":["Tntdruid_","knutztar","mjjzf","twikedk"],"de":["Atalanttore","TheName","Wyrrrd","YvanM","amandablue","anestiskaci","corppneq","crit12","hammaschlach","maxkl","nicolas_git","owncube"],"el":["KristinaQejvanaj","anestiskaci","diamond_gr"],"es":["Ark74","Elhea","GranPC","aluaces","jcvielma","plaguna","polkhas","xphnx"],"eu":["Osoitz","Thadah","cockeredradiation"],"fa":["Numb","ahangarha","amiraliakbari","joojoojoo","maryambehzi","mtashackori","taranehsaei"],"fr":["AlainR","Amadeen","Floflr","Llorc","LoiX07","Novick","Poussinou","Thecross","YvanM","alkino2","boutil","callmemagnus","chfo","chrcha","grenatrad","jokx","mathieugfortin","paullbn","vincen","ÉricB."],"fr_FR":["Llorc","Poussinou","chrcha"],"gl":["aluaces","pikamoku"],"hu":["Roshek","jtg"],"it":["Damtux","FranzMari","ed0","malaerba","noccio","nwandy","rickyroo","technezio"],"it_IT":["malaerba"],"ja":["Naofumi","yanorei32"],"nb_NO":["elonus"],"nl":["XtremeNova","davtemp","dehart","erikhubers","frankyboy1963","toonvangerwen"],"pl":["TORminator","TheName","Valdnet","gsz","mg6","oskarjakiela"],"pt":["amalvarenga","wanderlei.huttel"],"pt_BR":["wanderlei.huttel"],"ru":["aigoshin","anm","astalavister","nick.savin","vaddd"],"sk_SK":["brango67","tiborepcek"],"sl_SI":["MrLaaky","uroszor"],"sr":["daimonion"],"sv":["Mikaelb","campbelldavid"],"szl":["chlodny"],"tr_TR":["ooguz","pultars"],"uk":["androsua","olexn","twixi007"],"uk_UA":["astalavister"],"zh_CN":["anolir","jxj2zzz79pfp9bpo","linuxbckp","mofitt2016","oksjd","phy","spice2wolf"],"zh_TW":["linuxbckp","mofitt2016","phy","waiabsfabuloushk"]} diff --git a/app/src/main/java/at/bitfire/davdroid/log/Logger.kt b/app/src/main/java/at/bitfire/davdroid/log/Logger.kt index a4c3fdf401d006d57292513d1e6526e80703b846..0b0303f015c06dbd5cd284dfdf3029e179641b5d 100644 --- a/app/src/main/java/at/bitfire/davdroid/log/Logger.kt +++ b/app/src/main/java/at/bitfire/davdroid/log/Logger.kt @@ -45,7 +45,7 @@ object Logger : SharedPreferences.OnSharedPreferenceChangeListener { reinitialize() } - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { if (key == LOG_TO_FILE) { log.info("Logging settings changed; re-initializing logger") reinitialize() diff --git a/app/src/main/java/at/bitfire/davdroid/network/ConnectionUtils.kt b/app/src/main/java/at/bitfire/davdroid/network/ConnectionUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..f54ff2d471731b6278ff1aa297f9c695ca593d64 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/network/ConnectionUtils.kt @@ -0,0 +1,79 @@ +/*************************************************************************************************** + * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. + **************************************************************************************************/ + +package at.bitfire.davdroid.network + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import androidx.annotation.RequiresApi +import androidx.core.content.getSystemService +import at.bitfire.davdroid.log.Logger +import java.util.logging.Level + +object ConnectionUtils { + + /** + * Checks whether we are connected to working WiFi + */ + internal fun wifiAvailable(context: Context): Boolean { + val connectivityManager = context.getSystemService()!! + connectivityManager.allNetworks.forEach { network -> + connectivityManager.getNetworkCapabilities(network)?.let { capabilities -> + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && + capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) + return true + } + } + return false + } + + /** + * Checks whether we are connected to the Internet. + * + * On API 26+ devices, if a VPN is used, WorkManager might start the SyncWorker without an + * internet connection (because NET_CAPABILITY_VALIDATED is always set for VPN connections). + * To prevent the start without internet access, we don't check for VPN connections by default + * (by using [NetworkCapabilities.NET_CAPABILITY_NOT_VPN]). + * + * However in special occasions (when syncing over a VPN without validated Internet on the + * underlying connection) we do not want to exclude VPNs. + * + * @param ignoreVpns *true* filters VPN connections in the Internet check; *false* allows them as valid connection + * @return whether we are connected to the Internet + */ + @RequiresApi(23) + internal fun internetAvailable(context: Context, ignoreVpns: Boolean): Boolean { + val connectivityManager = context.getSystemService()!! + return connectivityManager.allNetworks.any { network -> + val capabilities = connectivityManager.getNetworkCapabilities(network) + Logger.log.log(Level.FINE, "Looking for validated Internet over this connection.", + arrayOf(connectivityManager.getNetworkInfo(network), capabilities)) + + if (capabilities != null) { + if (!capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + Logger.log.fine("Missing network capability: INTERNET") + return@any false + } + + if (!capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) { + Logger.log.fine("Missing network capability: VALIDATED") + return@any false + } + + if (ignoreVpns) + if (!capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)) { + Logger.log.fine("Missing network capability: NOT_VPN") + return@any false + } + + Logger.log.fine("This connection can be used.") + /* return@any */ true + } else + // no network capabilities available, we can't use this connection + /* return@any */ false + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/network/HttpClient.kt b/app/src/main/java/at/bitfire/davdroid/network/HttpClient.kt index 9b85b6ab17f0ab142af36eb9fee8a078e57b502f..9a8535e27f2d78086d3c6c51ad8b15339a9a8274 100644 --- a/app/src/main/java/at/bitfire/davdroid/network/HttpClient.kt +++ b/app/src/main/java/at/bitfire/davdroid/network/HttpClient.kt @@ -8,6 +8,7 @@ import android.content.Context import android.os.Build import android.security.KeyChain import at.bitfire.cert4android.CustomCertManager +import at.bitfire.cert4android.CustomHostnameVerifier import at.bitfire.dav4jvm.BasicDigestAuthHandler import at.bitfire.dav4jvm.UrlUtils import at.bitfire.davdroid.BuildConfig @@ -21,6 +22,7 @@ import dagger.hilt.EntryPoint import dagger.hilt.InstallIn import dagger.hilt.android.EntryPointAccessors import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.flow.MutableStateFlow import net.openid.appauth.AuthState import net.openid.appauth.AuthorizationService import okhttp3.* @@ -41,7 +43,6 @@ import javax.net.ssl.* class HttpClient private constructor( val okHttpClient: OkHttpClient, - private val certManager: CustomCertManager? = null, private var authService: AuthorizationService? = null ): AutoCloseable { @@ -88,7 +89,6 @@ class HttpClient private constructor( override fun close() { authService?.dispose() okHttpClient.cache?.close() - certManager?.close() } @@ -103,7 +103,8 @@ class HttpClient private constructor( fun certManager(): CustomCertManager } - private var appInForeground = false + private var appInForeground: MutableStateFlow? = + MutableStateFlow(false) private var authService: AuthorizationService? = null private var certManagerProducer: CertManagerProducer? = null private var certificateAlias: String? = null @@ -152,7 +153,7 @@ class HttpClient private constructor( customCertManager { // by default, use a CustomCertManager that respects the "distrust system certificates" setting val trustSystemCerts = !settings.getBoolean(Settings.DISTRUST_SYSTEM_CERTIFICATES) - CustomCertManager(context, true /*BuildConfig.customCertsUI*/, trustSystemCerts) + CustomCertManager(context, trustSystemCerts, appInForeground) } // use account settings for authentication and cookies @@ -230,7 +231,7 @@ class HttpClient private constructor( certManagerProducer = producer } fun setForeground(foreground: Boolean): Builder { - appInForeground = foreground + appInForeground?.value = foreground return this } @@ -286,33 +287,32 @@ class HttpClient private constructor( orig.protocols(listOf(Protocol.HTTP_1_1)) } - val certManager = - if (certManagerProducer != null || keyManager != null) { - val manager = certManagerProducer?.certManager() - manager?.appInForeground = appInForeground + if (certManagerProducer != null || keyManager != null) { + val manager = certManagerProducer?.certManager() - val trustManager = manager ?: { // fall back to system default trust manager - val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + val trustManager = manager ?: /* fall back to system default trust manager */ + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + .let { factory -> factory.init(null as KeyStore?) factory.trustManagers.first() as X509TrustManager - }() - - val hostnameVerifier = manager?.hostnameVerifier(OkHostnameVerifier) - ?: OkHostnameVerifier - - val sslContext = SSLContext.getInstance("TLS") - sslContext.init( - if (keyManager != null) arrayOf(keyManager) else null, - arrayOf(trustManager), - null) - orig.sslSocketFactory(sslContext.socketFactory, trustManager) - orig.hostnameVerifier(hostnameVerifier) + } - manager - } else - null + val hostnameVerifier = + if (manager != null) + CustomHostnameVerifier(context, OkHostnameVerifier) + else + OkHostnameVerifier + + val sslContext = SSLContext.getInstance("TLS") + sslContext.init( + if (keyManager != null) arrayOf(keyManager) else null, + arrayOf(trustManager), + null) + orig.sslSocketFactory(sslContext.socketFactory, trustManager) + orig.hostnameVerifier(hostnameVerifier) + } - return HttpClient(orig.build(), certManager = certManager, authService = authService) + return HttpClient(orig.build(), authService = authService) } } diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt b/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt index fc5645ceebcd40d7c6061cdec0fdfe592fbe0696..4f260d8919114409d631df0e2cfed5ce8395c812 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt @@ -22,6 +22,7 @@ import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.syncadapter.AccountUtils import at.bitfire.davdroid.util.DavUtils +import at.bitfire.davdroid.util.setAndVerifyUserData import at.bitfire.vcard4android.* import java.io.ByteArrayOutputStream @@ -171,8 +172,8 @@ open class LocalAddressBook( } set(newMainAccount) { AccountManager.get(context).let { accountManager -> - accountManager.setUserData(account, USER_DATA_MAIN_ACCOUNT_NAME, newMainAccount.name) - accountManager.setUserData(account, USER_DATA_MAIN_ACCOUNT_TYPE, newMainAccount.type) + accountManager.setAndVerifyUserData(account, USER_DATA_MAIN_ACCOUNT_NAME, newMainAccount.name) + accountManager.setAndVerifyUserData(account, USER_DATA_MAIN_ACCOUNT_TYPE, newMainAccount.type) } _mainAccount = newMainAccount @@ -181,11 +182,11 @@ open class LocalAddressBook( var url: String get() = AccountManager.get(context).getUserData(account, USER_DATA_URL) ?: throw IllegalStateException("Address book has no URL") - set(url) = AccountManager.get(context).setUserData(account, USER_DATA_URL, url) + set(url) = AccountManager.get(context).setAndVerifyUserData(account, USER_DATA_URL, url) override var readOnly: Boolean get() = AccountManager.get(context).getUserData(account, USER_DATA_READ_ONLY) != null - set(readOnly) = AccountManager.get(context).setUserData(account, USER_DATA_READ_ONLY, if (readOnly) "1" else null) + set(readOnly) = AccountManager.get(context).setAndVerifyUserData(account, USER_DATA_READ_ONLY, if (readOnly) "1" else null) override var lastSyncState: SyncState? get() = syncState?.let { SyncState.fromString(String(it)) } diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt b/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt index fb15ebb051c0c2d27e2b39e1f7e80aec6449e509..033d02d8eda99702fe25176ecdb642b44bad43e5 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt @@ -20,7 +20,7 @@ import at.bitfire.ical4android.AndroidCalendar import at.bitfire.ical4android.AndroidCalendarFactory import at.bitfire.ical4android.BatchOperation import at.bitfire.ical4android.util.DateUtils -import at.bitfire.ical4android.util.MiscUtils.UriHelper.asSyncAdapter +import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter import java.util.* import java.util.logging.Level diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalEvent.kt b/app/src/main/java/at/bitfire/davdroid/resource/LocalEvent.kt index 495e7d42f8ff916fc7d0fb08476ee6cda89f2706..9a2a01fc4e2811141b9bc3761b0dfd697ec6bba9 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalEvent.kt +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalEvent.kt @@ -12,7 +12,7 @@ import android.provider.CalendarContract import android.provider.CalendarContract.Events import at.bitfire.davdroid.BuildConfig import at.bitfire.ical4android.* -import at.bitfire.ical4android.util.MiscUtils.UriHelper.asSyncAdapter +import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter import net.fortuna.ical4j.model.property.ProdId import org.apache.commons.lang3.StringUtils import java.util.* diff --git a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt index b3ebfe9d9eba69a25ee33e8adc7921fa7d358563..4bd79bbd2e78b571f29dd54f9587c25256b2974a 100644 --- a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt +++ b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt @@ -19,6 +19,7 @@ import at.bitfire.davdroid.resource.LocalAddressBook import at.bitfire.davdroid.syncadapter.AccountUtils import at.bitfire.davdroid.syncadapter.PeriodicSyncWorker import at.bitfire.davdroid.syncadapter.SyncUtils +import at.bitfire.davdroid.util.setAndVerifyUserData import at.bitfire.ical4android.TaskProvider import at.bitfire.vcard4android.GroupMethod import dagger.hilt.EntryPoint @@ -71,6 +72,7 @@ class AccountSettings( const val KEY_WIFI_ONLY = "wifi_only" // sync on WiFi only (default: false) const val KEY_WIFI_ONLY_SSIDS = "wifi_only_ssids" // restrict sync to specific WiFi SSIDs + const val KEY_IGNORE_VPNS = "ignore_vpns" // ignore vpns at connection detection /** Time range limitation to the past [in days]. Values: * @@ -232,18 +234,16 @@ class AccountSettings( } fun credentials(credentials: Credentials) { - if (credentials.authState == null) { - accountManager.setUserData(account, KEY_USERNAME, credentials.userName) - accountManager.setPassword(account, credentials.password) - accountManager.setUserData(account, KEY_CERTIFICATE_ALIAS, credentials.certificateAlias) - } - else { - accountManager.setUserData(account, KEY_USERNAME, credentials.userName) - accountManager.setPassword(account, credentials.password) - accountManager.setUserData(account, KEY_AUTH_STATE, credentials.authState.jsonSerializeString()) - accountManager.setUserData(account, KEY_CERTIFICATE_ALIAS, credentials.certificateAlias) - accountManager.setUserData(account, KEY_CLIENT_SECRET, credentials.clientSecret) - } + // Basic/Digest auth + accountManager.setAndVerifyUserData(account, KEY_USERNAME, credentials.userName) + accountManager.setPassword(account, credentials.password) + + // client certificate + accountManager.setAndVerifyUserData(account, KEY_CERTIFICATE_ALIAS, credentials.certificateAlias) + + // OAuth + accountManager.setAndVerifyUserData(account, KEY_AUTH_STATE, credentials.authState?.jsonSerializeString()) + accountManager.setAndVerifyUserData(account, KEY_CLIENT_SECRET, credentials.clientSecret) } @@ -310,7 +310,7 @@ class AccountSettings( else -> throw IllegalArgumentException("Sync interval not applicable to authority $authority") } - accountManager.setUserData(account, key, seconds.toString()) + accountManager.setAndVerifyUserData(account, key, seconds.toString()) // update sync workers (needs already updated sync interval in AccountSettings) updatePeriodicSyncWorker(authority, seconds, getSyncWifiOnly()) @@ -366,13 +366,13 @@ class AccountSettings( } fun getSyncWifiOnly() = - if (settings.containsKey(KEY_WIFI_ONLY)) - settings.getBoolean(KEY_WIFI_ONLY) - else - accountManager.getUserData(account, KEY_WIFI_ONLY) != null + if (settings.containsKey(KEY_WIFI_ONLY)) + settings.getBoolean(KEY_WIFI_ONLY) + else + accountManager.getUserData(account, KEY_WIFI_ONLY) != null fun setSyncWiFiOnly(wiFiOnly: Boolean) { - accountManager.setUserData(account, KEY_WIFI_ONLY, if (wiFiOnly) "1" else null) + accountManager.setAndVerifyUserData(account, KEY_WIFI_ONLY, if (wiFiOnly) "1" else null) // update sync workers (needs already updated wifi-only flag in AccountSettings) for (authority in SyncUtils.syncAuthorities(context)) @@ -380,16 +380,26 @@ class AccountSettings( } fun getSyncWifiOnlySSIDs(): List? = - if (getSyncWifiOnly()) { - val strSsids = if (settings.containsKey(KEY_WIFI_ONLY_SSIDS)) - settings.getString(KEY_WIFI_ONLY_SSIDS) - else - accountManager.getUserData(account, KEY_WIFI_ONLY_SSIDS) - strSsids?.split(',') - } else - null + if (getSyncWifiOnly()) { + val strSsids = if (settings.containsKey(KEY_WIFI_ONLY_SSIDS)) + settings.getString(KEY_WIFI_ONLY_SSIDS) + else + accountManager.getUserData(account, KEY_WIFI_ONLY_SSIDS) + strSsids?.split(',') + } else + null fun setSyncWifiOnlySSIDs(ssids: List?) = - accountManager.setUserData(account, KEY_WIFI_ONLY_SSIDS, StringUtils.trimToNull(ssids?.joinToString(","))) + accountManager.setAndVerifyUserData(account, KEY_WIFI_ONLY_SSIDS, StringUtils.trimToNull(ssids?.joinToString(","))) + + fun getIgnoreVpns(): Boolean = + when (accountManager.getUserData(account, KEY_IGNORE_VPNS)) { + null -> settings.getBoolean(KEY_IGNORE_VPNS) + "0" -> false + else -> true + } + + fun setIgnoreVpns(ignoreVpns: Boolean) = + accountManager.setAndVerifyUserData(account, KEY_IGNORE_VPNS, if (ignoreVpns) "1" else "0") /** * Updates the periodic sync worker of an authority according to @@ -431,7 +441,7 @@ class AccountSettings( } fun setTimeRangePastDays(days: Int?) = - accountManager.setUserData(account, KEY_TIME_RANGE_PAST_DAYS, (days ?: -1).toString()) + accountManager.setAndVerifyUserData(account, KEY_TIME_RANGE_PAST_DAYS, (days ?: -1).toString()) /** * Takes the default alarm setting (in this order) from @@ -456,7 +466,7 @@ class AccountSettings( * start of every non-full-day event without reminder. *null*: No default reminders shall be created. */ fun setDefaultAlarm(minBefore: Int?) = - accountManager.setUserData(account, KEY_DEFAULT_ALARM, + accountManager.setAndVerifyUserData(account, KEY_DEFAULT_ALARM, if (minBefore == settings.getIntOrNull(KEY_DEFAULT_ALARM)?.takeIf { it != -1 }) null else @@ -467,14 +477,14 @@ class AccountSettings( else accountManager.getUserData(account, KEY_MANAGE_CALENDAR_COLORS) == null fun setManageCalendarColors(manage: Boolean) = - accountManager.setUserData(account, KEY_MANAGE_CALENDAR_COLORS, if (manage) null else "0") + accountManager.setAndVerifyUserData(account, KEY_MANAGE_CALENDAR_COLORS, if (manage) null else "0") fun getEventColors() = if (settings.containsKey(KEY_EVENT_COLORS)) settings.getBoolean(KEY_EVENT_COLORS) else accountManager.getUserData(account, KEY_EVENT_COLORS) != null fun setEventColors(useColors: Boolean) = - accountManager.setUserData(account, KEY_EVENT_COLORS, if (useColors) "1" else null) + accountManager.setAndVerifyUserData(account, KEY_EVENT_COLORS, if (useColors) "1" else null) // CardDAV settings @@ -491,7 +501,7 @@ class AccountSettings( } fun setGroupMethod(method: GroupMethod) { - accountManager.setUserData(account, KEY_CONTACT_GROUP_METHOD, method.name) + accountManager.setAndVerifyUserData(account, KEY_CONTACT_GROUP_METHOD, method.name) } @@ -513,7 +523,7 @@ class AccountSettings( } fun setShowOnlyPersonal(showOnlyPersonal: Boolean) { - accountManager.setUserData(account, KEY_SHOW_ONLY_PERSONAL, if (showOnlyPersonal) "1" else null) + accountManager.setAndVerifyUserData(account, KEY_SHOW_ONLY_PERSONAL, if (showOnlyPersonal) "1" else null) } @@ -536,7 +546,7 @@ class AccountSettings( updateProc.invoke(migrations) Logger.log.info("Account version update successful") - accountManager.setUserData(account, KEY_SETTINGS_VERSION, toVersion.toString()) + accountManager.setAndVerifyUserData(account, KEY_SETTINGS_VERSION, toVersion.toString()) } catch (e: Exception) { Logger.log.log(Level.SEVERE, "Couldn't update account settings", e) } diff --git a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettingsMigrations.kt b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettingsMigrations.kt index 70c491d2441e087ce0da7c02bd472ea7f9169269..0bec6539b889cebf69ced31339bedf2be3cf8dfa 100644 --- a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettingsMigrations.kt +++ b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettingsMigrations.kt @@ -26,6 +26,7 @@ import at.bitfire.davdroid.resource.LocalTask import at.bitfire.davdroid.resource.TaskUtils import at.bitfire.davdroid.syncadapter.SyncUtils import at.bitfire.davdroid.util.closeCompat +import at.bitfire.davdroid.util.setAndVerifyUserData import at.bitfire.ical4android.AndroidCalendar import at.bitfire.ical4android.AndroidEvent import at.bitfire.ical4android.TaskProvider @@ -231,7 +232,7 @@ class AccountSettingsMigrations( TaskUtils.currentProvider(context)?.let { provider -> val interval = accountSettings.getSyncInterval(provider.authority) if (interval != null) - accountManager.setUserData(account, + accountManager.setAndVerifyUserData(account, AccountSettings.KEY_SYNC_INTERVAL_TASKS, interval.toString()) } } @@ -320,8 +321,8 @@ class AccountSettingsMigrations( // update allowed WiFi settings key val onlySSID = accountManager.getUserData(account, "wifi_only_ssid") - accountManager.setUserData(account, AccountSettings.KEY_WIFI_ONLY_SSIDS, onlySSID) - accountManager.setUserData(account, "wifi_only_ssid", null) + accountManager.setAndVerifyUserData(account, AccountSettings.KEY_WIFI_ONLY_SSIDS, onlySSID) + accountManager.setAndVerifyUserData(account, "wifi_only_ssid", null) } @Suppress("unused") @@ -384,7 +385,7 @@ class AccountSettingsMigrations( } // update version number so that further syncs don't repeat the migration - accountManager.setUserData(account, AccountSettings.KEY_SETTINGS_VERSION, "6") + accountManager.setAndVerifyUserData(account, AccountSettings.KEY_SETTINGS_VERSION, "6") // request sync of new address book account ContentResolver.setIsSyncable(account, context.getString(R.string.address_books_authority), 1) diff --git a/app/src/main/java/at/bitfire/davdroid/settings/DefaultsProvider.kt b/app/src/main/java/at/bitfire/davdroid/settings/DefaultsProvider.kt index 276c9a8588dd3913dce9c6162278667224ba4f8b..b2677eb427b9dce38b43d8fa4aaebeee0fc13827 100644 --- a/app/src/main/java/at/bitfire/davdroid/settings/DefaultsProvider.kt +++ b/app/src/main/java/at/bitfire/davdroid/settings/DefaultsProvider.kt @@ -20,7 +20,8 @@ class DefaultsProvider( override val booleanDefaults = mutableMapOf( Pair(Settings.DISTRUST_SYSTEM_CERTIFICATES, false), - Pair(Settings.FORCE_READ_ONLY_ADDRESSBOOKS, false) + Pair(Settings.FORCE_READ_ONLY_ADDRESSBOOKS, false), + Pair(Settings.IGNORE_VPN_NETWORK_CAPABILITY, true) ) override val intDefaults = mapOf( diff --git a/app/src/main/java/at/bitfire/davdroid/settings/Settings.kt b/app/src/main/java/at/bitfire/davdroid/settings/Settings.kt index 5e9734a0e9923bb8f600f7a02889e553a1cd4058..cbdfa2c63ee0d52f050bb570d8357b0137a61221 100644 --- a/app/src/main/java/at/bitfire/davdroid/settings/Settings.kt +++ b/app/src/main/java/at/bitfire/davdroid/settings/Settings.kt @@ -22,7 +22,13 @@ object Settings { const val PROXY_PORT = "proxy_port" // Integer /** - * Default sync interval (long), in seconds. + * Whether to ignore VPNs at internet connection detection, true by default because VPN connections + * seem to include "VALIDATED" by default even without actual internet connection + */ + const val IGNORE_VPN_NETWORK_CAPABILITY = "ignore_vpns" // Boolean + + /** + * Default sync interval (Long), in seconds. * Used to initialize an account. */ const val DEFAULT_SYNC_INTERVAL = "default_sync_interval" diff --git a/app/src/main/java/at/bitfire/davdroid/settings/SharedPreferencesProvider.kt b/app/src/main/java/at/bitfire/davdroid/settings/SharedPreferencesProvider.kt index ac2d9e3b7fddfd0e574ae47b2d5371fc67dafee1..70d8caed9de9389aa87ea75350020a2285c82b34 100644 --- a/app/src/main/java/at/bitfire/davdroid/settings/SharedPreferencesProvider.kt +++ b/app/src/main/java/at/bitfire/davdroid/settings/SharedPreferencesProvider.kt @@ -52,7 +52,7 @@ class SharedPreferencesProvider( override fun canWrite() = true - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { settingsManager.onSettingsChanged() } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountUtils.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountUtils.kt index 22ebaf52938ff06cce0dba3b908f9d64fb0ceab6..5dcaed9c6b7900f4ab1e60eed2a0f6dfa4d90cb0 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountUtils.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountUtils.kt @@ -10,6 +10,7 @@ import android.content.Context import android.os.Bundle import at.bitfire.davdroid.R import at.bitfire.davdroid.log.Logger +import at.bitfire.davdroid.util.setAndVerifyUserData object AccountUtils { @@ -48,7 +49,7 @@ object AccountUtils { // https://forums.bitfire.at/post/11644 if (!verifyUserData(context, account, userData)) for (key in userData.keySet()) - manager.setUserData(account, key, userData.getString(key)) + manager.setAndVerifyUserData(account, key, userData.getString(key)) if (!verifyUserData(context, account, userData)) throw IllegalStateException("Android doesn't store user data in account") diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactSyncer.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactSyncer.kt index cbb33887fdcaef4d2342ce6bda6aa66cc82f784f..56764968807156262c4f83b59aa4495500fe7484 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactSyncer.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactSyncer.kt @@ -13,6 +13,7 @@ import at.bitfire.davdroid.network.HttpClient import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalAddressBook import at.bitfire.davdroid.settings.AccountSettings +import at.bitfire.davdroid.util.setAndVerifyUserData import java.util.logging.Level /** @@ -50,7 +51,7 @@ class ContactSyncer(context: Context): Syncer(context) { addressBook.syncState = null } } - accountSettings.accountManager.setUserData(account, PREVIOUS_GROUP_METHOD, groupMethod) + accountSettings.accountManager.setAndVerifyUserData(account, PREVIOUS_GROUP_METHOD, groupMethod) Logger.log.info("Synchronizing address book: ${addressBook.url}") Logger.log.info("Taking settings from: ${addressBook.mainAccount}") diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt index f76bd6c6d6f033b8c5090ac309827fc8c1c27141..32b18322f439fdb1e11fab06424e89813f4d85d5 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt @@ -59,6 +59,7 @@ import java.lang.ref.WeakReference import java.net.ConnectException import java.net.HttpURLConnection import java.security.cert.CertificateException +import java.time.Instant import java.util.* import java.util.concurrent.Executors import java.util.concurrent.LinkedBlockingQueue @@ -95,11 +96,41 @@ abstract class SyncManager, out CollectionType: L companion object { const val DEBUG_INFO_MAX_RESOURCE_DUMP_SIZE = 100*FileUtils.ONE_KB.toInt() + const val MAX_MULTIGET_RESOURCES = 10 const val DEFAULT_RETRY_AFTER = 5 const val DEFAULT_SECOND_RETRY_AFTER = 8 const val DEFAULT_MAX_RETRY_TIME = 21 + const val DELAY_UNTIL_DEFAULT = 15*60L // 15 min + const val DELAY_UNTIL_MIN = 1*60L // 1 min + const val DELAY_UNTIL_MAX = 2*60*60L // 2 hours + + /** + * Returns appropriate sync retry delay in seconds, considering the servers suggestion + * ([DELAY_UNTIL_DEFAULT] if no server suggestion). + * + * Takes current time into account to calculate intervals. Interval + * will be restricted to values between [DELAY_UNTIL_MIN] and [DELAY_UNTIL_MAX]. + * + * @param retryAfter optional server suggestion on how long to wait before retrying + * @return until when to wait before sync can be retried + */ + fun getDelayUntil(retryAfter: Instant?): Instant { + val now = Instant.now() + + if (retryAfter == null) + return now.plusSeconds(DELAY_UNTIL_DEFAULT) + + // take server suggestion, but restricted to plausible min/max values + val min = now.plusSeconds(DELAY_UNTIL_MIN) + val max = now.plusSeconds(DELAY_UNTIL_MAX) + return when { + min > retryAfter -> min + max < retryAfter -> max + else -> retryAfter + } + } var _workDispatcher: WeakReference? = null /** @@ -121,6 +152,7 @@ abstract class SyncManager, out CollectionType: L ).asCoroutineDispatcher() return newDispatcher } + } init { @@ -338,9 +370,9 @@ abstract class SyncManager, out CollectionType: L // specific HTTP errors is ServiceUnavailableException -> { Logger.log.log(Level.WARNING, "Got 503 Service unavailable, trying again later", e) - e.retryAfter?.let { retryAfter -> - syncResult.delayUntil = retryAfter.toEpochMilli() - } + // determine when to retry + syncResult.delayUntil = getDelayUntil(e.retryAfter).epochSecond + syncResult.stats.numIoExceptions++ // Indicate a soft error occurred } // sometimes sync is scheduled & kicked just the time network becomes available but not ready; in this case it throws ConnectException. diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncWorker.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncWorker.kt index 1d456f9b391b856789989139525e8e963855447d..438af86d22fe6aa7e734a6d60f87cd50c45287e4 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncWorker.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncWorker.kt @@ -10,14 +10,11 @@ import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.SyncResult -import android.net.ConnectivityManager -import android.net.NetworkCapabilities import android.net.wifi.WifiManager import android.os.Build import android.provider.CalendarContract import android.provider.ContactsContract import androidx.annotation.IntDef -import androidx.annotation.RequiresApi import androidx.concurrent.futures.CallbackToFutureAdapter import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat @@ -41,6 +38,8 @@ import androidx.work.Worker import androidx.work.WorkerParameters import at.bitfire.davdroid.R import at.bitfire.davdroid.log.Logger +import at.bitfire.davdroid.network.ConnectionUtils.internetAvailable +import at.bitfire.davdroid.network.ConnectionUtils.wifiAvailable import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.ui.NotificationUtils import at.bitfire.davdroid.ui.NotificationUtils.notifyIfPossible @@ -148,7 +147,7 @@ class SyncWorker @AssistedInject constructor( .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) .setBackoffCriteria( BackoffPolicy.EXPONENTIAL, - WorkRequest.MIN_BACKOFF_MILLIS, + WorkRequest.DEFAULT_BACKOFF_DELAY_MILLIS, // 30 sec TimeUnit.MILLISECONDS ) .setConstraints(constraints) @@ -204,44 +203,7 @@ class SyncWorker @AssistedInject constructor( } - /** - * Checks whether user imposed sync conditions from settings are met: - * - Sync only on WiFi? - * - Sync only on specific WiFi (SSID)? - * - * @param accountSettings Account settings of the account to check (and is to be synced) - * @return *true* if conditions are met; *false* if not - */ - internal fun wifiConditionsMet(context: Context, accountSettings: AccountSettings): Boolean { - // May we sync without WiFi? - if (!accountSettings.getSyncWifiOnly()) - return true // yes, continue - - // WiFi required, is it available? - if (!wifiAvailable(context)) { - Logger.log.info("Not on connected WiFi, stopping") - return false - } - // If execution reaches this point, we're on a connected WiFi - - // Check whether we are connected to the correct WiFi (in case SSID was provided) - return correctWifiSsid(context, accountSettings) - } - - /** - * Checks whether we are connected to working WiFi - */ - internal fun wifiAvailable(context: Context): Boolean { - val connectivityManager = context.getSystemService()!! - connectivityManager.allNetworks.forEach { network -> - connectivityManager.getNetworkCapabilities(network)?.let { capabilities -> - if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && - capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) - return true - } - } - return false - } + // connection checks /** * Checks whether we are connected to the correct wifi (SSID) defined by user in the @@ -277,28 +239,54 @@ class SyncWorker @AssistedInject constructor( } return true } + + /** + * Checks whether user imposed sync conditions from settings are met: + * - Sync only on WiFi? + * - Sync only on specific WiFi (SSID)? + * + * @param accountSettings Account settings of the account to check (and is to be synced) + * @return *true* if conditions are met; *false* if not + */ + internal fun wifiConditionsMet(context: Context, accountSettings: AccountSettings): Boolean { + // May we sync without WiFi? + if (!accountSettings.getSyncWifiOnly()) + return true // yes, continue + + // WiFi required, is it available? + if (!wifiAvailable(context)) { + Logger.log.info("Not on connected WiFi, stopping") + return false + } + // If execution reaches this point, we're on a connected WiFi + + // Check whether we are connected to the correct WiFi (in case SSID was provided) + return correctWifiSsid(context, accountSettings) + } + } + private val notificationManager = NotificationManagerCompat.from(applicationContext) /** thread which runs the actual sync code (can be interrupted to stop synchronization) */ var syncThread: Thread? = null override fun doWork(): Result { - - // Check internet connection. This is especially important on API 26+ where when a VPN is used, - // WorkManager may start the SyncWorker without a working underlying Internet connection. - if (Build.VERSION.SDK_INT >= 23 && !internetAvailable(applicationContext)) { - Logger.log.info("WorkManager started SyncWorker without Internet connection. Aborting.") - return Result.failure() - } - // ensure we got the required arguments val account = Account( inputData.getString(ARG_ACCOUNT_NAME) ?: throw IllegalArgumentException("$ARG_ACCOUNT_NAME required"), inputData.getString(ARG_ACCOUNT_TYPE) ?: throw IllegalArgumentException("$ARG_ACCOUNT_TYPE required") ) val authority = inputData.getString(ARG_AUTHORITY) ?: throw IllegalArgumentException("$ARG_AUTHORITY required") + + // Check internet connection + val ignoreVpns = AccountSettings(applicationContext, account).getIgnoreVpns() + if (Build.VERSION.SDK_INT >= 23 && !internetAvailable(applicationContext, ignoreVpns)) { + Logger.log.info("WorkManager started SyncWorker without Internet connection. Aborting.") + return Result.failure() + } + Logger.log.info("Running sync worker: account=$account, authority=$authority") if (ContentResolver.getIsSyncable(account, authority) <= 0) { @@ -375,10 +363,20 @@ class SyncWorker @AssistedInject constructor( .putString("syncResultStats", result.stats.toString()) .build() + val softErrorNotificationTag = account.type + "-" + account.name + "-" + authority + // On soft errors the sync is retried a few times before considered failed if (result.hasSoftError()) { Logger.log.warning("Soft error while syncing: result=$result, stats=${result.stats}") if (runAttemptCount < MAX_RUN_ATTEMPTS) { + val blockDuration = result.delayUntil - System.currentTimeMillis()/1000 + Logger.log.warning("Waiting for $blockDuration seconds, before retrying ...") + + // We block the SyncWorker here so that it won't be started by the sync framework immediately again. + // This should be replaced by proper work scheduling as soon as we don't depend on the sync framework anymore. + if (blockDuration > 0) + Thread.sleep(blockDuration*1000) + Logger.log.warning("Retrying on soft error (attempt $runAttemptCount of $MAX_RUN_ATTEMPTS)") return Result.retry() } @@ -386,6 +384,7 @@ class SyncWorker @AssistedInject constructor( Logger.log.warning("Max retries on soft errors reached ($runAttemptCount of $MAX_RUN_ATTEMPTS). Treating as failed") notificationManager.notifyIfPossible( + softErrorNotificationTag, NotificationUtils.NOTIFY_SYNC_ERROR, NotificationUtils.newBuilder(applicationContext, NotificationUtils.CHANNEL_SYNC_IO_ERRORS) .setSmallIcon(R.drawable.ic_sync_problem_notify) @@ -393,7 +392,7 @@ class SyncWorker @AssistedInject constructor( .setContentText(applicationContext.getString(R.string.sync_error_retry_limit_reached)) .setSubText(account.name) .setOnlyAlertOnce(true) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setPriority(NotificationCompat.PRIORITY_MIN) .setCategory(NotificationCompat.CATEGORY_ERROR) .build() ) @@ -401,6 +400,12 @@ class SyncWorker @AssistedInject constructor( return Result.failure(syncResult) } + // If no soft error found, dismiss sync error notification + notificationManager.cancel( + softErrorNotificationTag, + NotificationUtils.NOTIFY_SYNC_ERROR + ) + // On a hard error - fail with an error message // Note: SyncManager should have notified the user if (result.hasHardError()) { @@ -412,31 +417,6 @@ class SyncWorker @AssistedInject constructor( return Result.success() } - /** - * Checks whether we are connected to the internet. - * - * On API 26+ devices, when a VPN is used, WorkManager might start the SyncWorker without an - * internet connection. To prevent this we do an extra check at the start of doWork() with this - * method. - * - * Every VPN connection also has an underlying non-vpn connection, which we find with - * [NetworkCapabilities.NET_CAPABILITY_NOT_VPN] and then check if that has validated internet - * access or not, using [NetworkCapabilities.NET_CAPABILITY_VALIDATED]. - * - * @return whether we are connected to the internet - */ - @RequiresApi(23) - private fun internetAvailable(context: Context): Boolean { - val connectivityManager = context.getSystemService()!! - return connectivityManager.allNetworks.any { network -> - connectivityManager.getNetworkCapabilities(network)?.let { capabilities -> - capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) // filter out VPNs - && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) - && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - } ?: false - } - } - override fun onStopped() { Logger.log.info("Stopping sync thread") syncThread?.interrupt() diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AccountsActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/AccountsActivity.kt index 0c6eb4fa803dffd944d7ee860828a51b0b344b4d..e50d1a5613948dece35430a05933792c62df2829 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/AccountsActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/AccountsActivity.kt @@ -12,6 +12,7 @@ import android.view.MenuItem import androidx.activity.viewModels import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.TooltipCompat import androidx.core.view.GravityCompat import androidx.lifecycle.AndroidViewModel import at.bitfire.davdroid.R @@ -45,6 +46,7 @@ class AccountsActivity: AppCompatActivity(), NavigationView.OnNavigationItemSele binding = ActivityAccountsBinding.inflate(layoutInflater) setContentView(binding.root) + TooltipCompat.setTooltipText(binding.content.fab, binding.content.fab.contentDescription) binding.content.fab.setOnClickListener { startActivity(Intent(this, LoginActivity::class.java)) } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.kt index 9f1cd224e6e93f016bc780de72c495d838696387..9ed784ff9f49b5cef5525adf9879b3e5d182b780 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.kt @@ -15,9 +15,8 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.UiThread import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate -import androidx.core.os.LocaleListCompat import androidx.preference.* -import at.bitfire.cert4android.CustomCertManager +import at.bitfire.cert4android.CustomCertStore import at.bitfire.davdroid.BuildConfig import at.bitfire.davdroid.ForegroundService import at.bitfire.davdroid.R @@ -33,7 +32,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.net.URI import java.net.URISyntaxException -import java.util.* import javax.inject.Inject import kotlin.math.roundToInt @@ -291,8 +289,9 @@ class AppSettingsActivity: AppCompatActivity() { } private fun resetCertificates() { - if (CustomCertManager.resetCertificates(requireActivity())) - Snackbar.make(requireView(), getString(R.string.app_settings_reset_certificates_success), Snackbar.LENGTH_LONG).show() + CustomCertStore.getInstance(requireActivity()).clearUserDecisions() + + Snackbar.make(requireView(), getString(R.string.app_settings_reset_certificates_success), Snackbar.LENGTH_LONG).show() } } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt index fe2cb62b750b283771e2c0066ab32377bbbf0083..27038353a3a6083bcd0ea8bf1fd875d92b73a456 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt @@ -8,21 +8,12 @@ import android.accounts.Account import android.accounts.AccountManager import android.app.Application import android.app.usage.UsageStatsManager -import android.content.ContentProviderClient -import android.content.ContentResolver -import android.content.ContentUris -import android.content.Context -import android.content.Intent +import android.content.* import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.net.ConnectivityManager import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.os.Environment -import android.os.LocaleList -import android.os.PowerManager -import android.os.StatFs +import android.os.* import android.provider.CalendarContract import android.provider.ContactsContract import android.view.View @@ -35,11 +26,7 @@ import androidx.core.content.FileProvider import androidx.core.content.getSystemService import androidx.core.content.pm.PackageInfoCompat import androidx.databinding.DataBindingUtil -import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.* import androidx.work.WorkManager import androidx.work.WorkQuery import at.bitfire.dav4jvm.exception.DavException @@ -57,9 +44,9 @@ import at.bitfire.davdroid.settings.SettingsManager import at.bitfire.davdroid.syncadapter.AccountUtils import at.bitfire.davdroid.syncadapter.PeriodicSyncWorker import at.bitfire.davdroid.syncadapter.SyncWorker +import at.bitfire.davdroid.util.closeCompat import at.bitfire.ical4android.TaskProvider import at.bitfire.ical4android.TaskProvider.ProviderName -import at.bitfire.ical4android.util.MiscUtils.ContentProviderClientHelper.closeCompat import at.techbee.jtx.JtxContract import com.google.android.material.snackbar.Snackbar import dagger.assisted.Assisted @@ -75,18 +62,14 @@ import org.apache.commons.io.IOUtils import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.exception.ExceptionUtils import org.dmfs.tasks.contract.TaskContract -import java.io.File -import java.io.IOError -import java.io.IOException -import java.io.StringReader -import java.io.Writer +import java.io.* import java.util.Locale import java.util.TimeZone import java.util.logging.Level import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream import javax.inject.Inject -import at.bitfire.ical4android.util.MiscUtils.UriHelper.asSyncAdapter as asCalendarSyncAdapter +import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter as asCalendarSyncAdapter import at.bitfire.vcard4android.Utils.asSyncAdapter as asContactsSyncAdapter import at.techbee.jtx.JtxContract.asSyncAdapter as asJtxSyncAdapter diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt index bb29c3471d2011389b51fc8739859b180c8a42a4..1ce2d987762c1ccbb3a1ca34d6d8ca79f77040b3 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt @@ -95,6 +95,7 @@ class AccountActivity: AppCompatActivity() { } // "Sync now" fab + TooltipCompat.setTooltipText(binding.sync, binding.sync.contentDescription) model.networkAvailable.observe(this) { networkAvailable -> binding.sync.setOnClickListener { if (!networkAvailable) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt index 0dc6ee59ed94d36f588ecc2ea55decceb5579186..4de4b1895a5c27c4e0517aa4be34f7a41e7518f8 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt @@ -225,6 +225,18 @@ class SettingsActivity: AppCompatActivity() { } } + findPreference(getString(R.string.settings_ignore_vpns_key))!!.let { + model.ignoreVpns.observe(viewLifecycleOwner) { ignoreVpns -> + it.isEnabled = true + it.isChecked = ignoreVpns + it.isVisible = Build.VERSION.SDK_INT >= 23 + it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, prefValue -> + model.updateIgnoreVpns(prefValue as Boolean) + false + } + } + } + // preference group: authentication val prefUserName = findPreference(getString(R.string.settings_username_key))!! val prefPassword = findPreference(getString(R.string.settings_password_key))!! @@ -463,6 +475,7 @@ class SettingsActivity: AppCompatActivity() { val syncWifiOnly = MutableLiveData() val syncWifiOnlySSIDs = MutableLiveData>() + val ignoreVpns = MutableLiveData() val credentials = MutableLiveData() @@ -501,6 +514,7 @@ class SettingsActivity: AppCompatActivity() { syncWifiOnly.postValue(accountSettings.getSyncWifiOnly()) syncWifiOnlySSIDs.postValue(accountSettings.getSyncWifiOnlySSIDs()) + ignoreVpns.postValue(accountSettings.getIgnoreVpns()) credentials.postValue(accountSettings.credentials()) @@ -530,6 +544,11 @@ class SettingsActivity: AppCompatActivity() { reload() } + fun updateIgnoreVpns(ignoreVpns: Boolean) { + accountSettings?.setIgnoreVpns(ignoreVpns) + reload() + } + fun updateCredentials(credentials: Credentials) { accountSettings?.credentials(credentials) reload() diff --git a/app/src/main/java/at/bitfire/davdroid/util/CompatUtils.kt b/app/src/main/java/at/bitfire/davdroid/util/CompatUtils.kt index 639758afcf024f34b1a713e555f9a3ab67316e0c..1ff99ba9423922e8c29fa59617acc192feafaaa3 100644 --- a/app/src/main/java/at/bitfire/davdroid/util/CompatUtils.kt +++ b/app/src/main/java/at/bitfire/davdroid/util/CompatUtils.kt @@ -4,8 +4,29 @@ package at.bitfire.davdroid.util +import android.accounts.Account +import android.accounts.AccountManager import android.content.ContentProviderClient import android.os.Build +import at.bitfire.davdroid.log.Logger + +/** + * [AccountManager.setUserData] has been found to be unreliable at times. This extension function + * checks whether the user data has actually been set and retries up to ten times before failing silently. + * + * Note: In the future we want to store accounts + associated data in the database, never calling + * so this method will become obsolete then. + */ +fun AccountManager.setAndVerifyUserData(account: Account, key: String, value: String?) { + for (i in 1..10) { + setUserData(account, key, value) + if (getUserData(account, key) == value) + return /* success */ + + Thread.sleep(100) + } + Logger.log.warning("AccountManager failed to set $account user data $key := $value") +} @Suppress("DEPRECATION") fun ContentProviderClient.closeCompat() { diff --git a/app/src/main/res/layout/accounts_content.xml b/app/src/main/res/layout/accounts_content.xml index 68b153ec56bde21283d078333a4fc8d197eb21bc..08f75c653242385d43397d1b70795fa3896df238 100644 --- a/app/src/main/res/layout/accounts_content.xml +++ b/app/src/main/res/layout/accounts_content.xml @@ -31,13 +31,12 @@ app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"/> diff --git a/app/src/main/res/layout/activity_account.xml b/app/src/main/res/layout/activity_account.xml index 62c6e2cbec768d949d148a07652284859d186692..8845b6c7e3d5972c200904b41313e45cd343920b 100644 --- a/app/src/main/res/layout/activity_account.xml +++ b/app/src/main/res/layout/activity_account.xml @@ -41,26 +41,25 @@ + app:layout_anchorGravity="top|center" + tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/layout/activity_webdav_mounts.xml b/app/src/main/res/layout/activity_webdav_mounts.xml index a621578a5d2ba1599c9a5be46548b58e4cef8480..1f63013385a1e91f910b44b96367da7aa92d8afc 100644 --- a/app/src/main/res/layout/activity_webdav_mounts.xml +++ b/app/src/main/res/layout/activity_webdav_mounts.xml @@ -52,6 +52,7 @@ android:layout_height="wrap_content" android:layout_margin="@dimen/fab_margin" android:layout_gravity="right|end|bottom" + android:contentDescription="@string/webdav_add_mount_add" app:srcCompat="@drawable/ic_add_white" /> \ No newline at end of file diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 1eb67b49474e4d423945cf636b47aa1fb11f373d..301595c2e5f8933e6bb52e710c7ddbdc0b4285f5 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -215,231 +215,255 @@ Google WebDAV ВНИМАНИЕ - - Вашите данни. Вашият избор - Поемете контрол - Периодично синхронизиране - Изключено (не се препоръчва) - Включено (препоръчително) - За да синхронизира периодично, %s се нуждае от разрешение да работи на заден план. В противен случай Android може да спре синхронизацията по всяко време. - Не желая периодично синхронизиране.* - %s съвместимост - Устройството вероятно прекъсва синхронизацията. Единственият начин да резрешите проблема е ръчната промяна на настройките. - Необходимите промени са направени. Без повторно напомняне.* - * Оставете без отметка за повторно напомняне. Може да бъде нулирано от настройките на приложението / %s. - Допълнителна информация - jtx Board - - Поддръжка на задачи - Ако сървърът поддържа задачи, те могат да бъдат синхронизирани с приложение за задачи: - OpenTasks - Изглежда не се разработва вече, не се препоръчва. - Tasks.org - (още) не се поддържат.]]> - Няма достъпен магазин за приложения - Не се нуждая от поддръжка на задачи.* - Приложение с отворен код - Радваме се, че използвате %s. Това е приложение с отворен код. Разработката, издръжката и поддръжката му са тежка работа. Моля да допринесете (има много начини) или да дарите. Вашият жест ще бъде високо оценен. - Как да допринеса или даря - Без повторно показване в близко бъдеще - - Разрешения - За да работи нормално %s се нуждае от разрешения. - Всички от изброените по-долу - За да включите вички възможности, изберете това (препоръчано) - Всичко е разрешено - Разрешения за контактите - Контактите не могат да бъдат синхронизирани (не се препоръчва) - Контактите могат да бъдат синхронизирани - Разрешения за календара - Календарите не могат да бъдат синхронизирани (не се препоръчва) - Календарите могат да бъдат синхронизирани - Разрешение за известия - Известията са изключени (непрепоръчително) - Известията са еключени - Разрешения за jtx Board - Няма (инсталирана) синхронизация на задачи, дневници и бележки - Без синхронизиране на задачи, дневници и бележки - Възможно синхронизиране на задачи, дневници и бележки - Разрешения за OpenTasks - Разрешения за Tasks - Няма (инсталирана) синхронизация на задачи - Няма синхронизация на задачи - Задачите могат да бъдат синхронизирани - Предпазване от нулиране на разрешенията - Разрешенията могат да бъдат нулирани автоматично (не се препоръчва) - Разрешенията не могат да бъдат нулирани автоматично - Докоснете Разрешения и махнете отметката от „Премахване на разрешенията, ако приложението не се използва“ - Ако някой превключвател не работи, използвайте настройките на приложението / Разрешения. - Настройки на приложението - - Разрешения за SSID на Wi-Fi - За достъп до името на текущата мрежа на Wi-Fi (SSID) трябва да бъдат изпълнени следните условия: - Разрешение за достъп до точното месоположение - Има разрешение за местоположението - Липсва разрешение за местоположението - Достъп до местоположението във фонов режим - Разрешаване във всички случаи - %s]]> - %s]]> - %s използва разрешението за местоположение за достъп до името на мрежата, когато се изисква синхронизиране само през определена мрежа. Това се случва дори и когато приложението се изпълнява във фонов режим. Не се събират, съхраняват, обработват и разпространяват данни за местоположението. - Винаги включено местоположение - Услугата за местоположението е включена - Услугата за местоположението е изключена - - Преводи - © Ricki Hirner, Bernhard Stockmann (bitfire web engineering GmbH) и сътрудници - Благодарение на: %s]]> - - Преглед/споделяне - Изключване - - Отваря менюто - Затваря менюто - Обратна връзка към бета - Инсталиране приложение за електронна поща - Инсталиране на мрежов четец - Инструменти - Общност - Известията са изключени. Няма да бъдете известявани за грешки при синхронизиране. - Управление на връзките - Няма достатъчно свободно място. Android няма да извършва синхронизация. - Управление на хранилището - Включена е икономия на данни. Синхронизацията във фонов режим е ограничена. - Икономия на трафик - Синхронизиране на всички профили - - Откриването на услугите се провали - Грешка при презареждане - - Не може да работи на преден план - Необходимо е да спрете оптимизирането на батерията за приложението - - Работи на преден план - На някои устройства това е необходимо, за да работи автоматичното синхронизиране. - - Оптимизиране на батерията - Приложението е в белия списък (препоръчително) - Приложението не е в белия списък (непрепоръчително) - Поддържане на преден план - Може да помогне ако устройството възпрепятства автоматичното синхронизиране - Вид на сървъра на прокси - - Според системата - Без сървър на прокси - HTTP - SOCKS (за Orbot) - - Име на хоста на сървъра на прокси - Порт на сървъра на прокси - Разрешения за приложението - Преглед на необходимите за синхронизиране разрешения - Нулиране на (не)доверените сертификати - Нулиране на доверието към всички потребителски сертификати - Всички потребителски сертификати са премахнати - Избиране на тема - - Според системата - Светла - Тъмна - - Всички подсказки, които са били разкарани, ще бъдат показани отново - Интеграция - Приложението Tasks - Синхронизиране чрез %s - Не е инсталирана съвместимо приложение за задачи. - - Контактите не се синхронизират (липсват разрешения) - Календарите те не се синхронизират (липсват разрешения) - Задачите не се синхронизират (липсват разрешения) - Календари и задачи не се синхронизират (липсват разрешения) - Няма достъп до календарите (липсват разрешения) - Разрешения - Това име на регистрация вече се използва - Грешка при преименуване - дневник - Само лични - Няма инсталирана програма за Webcal абонамент. - - Използването на апострофи (\') води до проблеми при някои устройства. - Използвайте адрес за електронна поща вместо име, защото Android ще го използва като адрес на организатора на събитията, които създавате. Не може да има две регистрации с еднакво име. - Вход за напреднали потребители (специални случаи) - С потребителско име/парола - Със сертификат за удостоверяване - Не е намерен сертификат - Инсталиране на сертификат - Грешно потребителско име или парола? - - Ограниченията на Wi-Fi по SSID изискват допълнителни настройки - Управление - Псевдоним на клиентския сертификат - Няма избран сертификат - Подразбирано напомняне - - Подразбирано напомняне една минута преди събитието - Подразбирано напомняне %d минути преди събитието - - Няма създадени подразбирани напомняния - Към събитията без напомняне ще бъде добавяно подразбирано напомняне: желания брой минути преди събитието. Оставете празно, за да изключите подразбираните напомняния. - - Групите са отделни vCards - Групите са категории във всеки контакт - - - по желание - Местоположението на хранилището е задължително - Собственик - - Информация за отстраняване на дефекти - Архив на ZIP - Съдържа информацията за отстраняване на дефекти и дневниците - Споделете архива, за да го пренесете на компютър, да го изпратите по електронна поща или да го прикачите към билет за поддръжка. - Споделяне на архива - Информацията за отстраняване на дефекти е приложена към това съобщение (получаващото приложение трябва да поддръжа прикачени файлове). - Грешка на HTTP - Грешка на сървъра - Грешка на WebDAV - Грешка на входа/изхода - Заявката е отказана. За подробности проверете свързаните със завката ресурси и информацията за отстраняване на дефекти. - Заявеният ресурс не съществува (вече). За подробности проверете свързаните със завката ресурси и информацията за отстраняване на дефекти. - Възникнал е проблем от страна на сървъра. Свържете се с поддръжката му. - Възникнала е неочаквана грешка. За подробности проверете информацията за отстраняване на дефекти. - Подробности - Информацията за отстраняване на дефекта е събрана - Ресурси, имащи отношение - Свързани с проблема - Отдалечен ресурс: - Местен ресурс: - Дневници - Налични са подробни дневници - Преглед - - Синхронизирането е спряно - Почти няма свободно пространство - - Дялове на WebDAV - Използвана квота: %1$s / налична: %2$s - Споделяне на съдържание - Демонтиране - Монтиране на дял на WebDAV - Получете директен достъп до файловете си в облака като монтирате дял на WebDAV. - как работят дяловете на WebDAVв ръководството.]]> - Видимо име - Адрес на WebDAV - Недействителен адрес - Потребителско име - Парола - Монтиране - Няма услуга на WebDAV на адреса - Премахване на точката на монтиране - Ще бъдат изгубени подробности за връзката, но файлове няма да бъдат премахвани. - Достъпва се файл на WebDAV - Изтегля се файл на WebDAV - Изпраща се файл на WebDAV - Дял на WebDAV - - Приложението %s е твърде старо - Минимално необходимо издание: %1$s - Грешка (достигнат максимален брой опити) - Преглед + Регистрацията (вече) не съществува + Управление на регистрации + Споделяне + Няма връзка с интернет, насрочено е синхронизиране за по-късно + Повредено хранилище + Всички местни профили са премахнати. + Отстраняване на дефекти + Съобщения за състоянието с нисък приоритет + Нефатални проблеми със синхронизацията като някои невалидни файлове + + Вашите данни. Вашият избор + Поемете контрол + Периодично синхронизиране + Изключено (не се препоръчва) + Включено (препоръчително) + За да синхронизира периодично, %s се нуждае от разрешение да работи на заден план. В противен случай Android може да спре синхронизацията по всяко време. + Не желая периодично синхронизиране.* + %s съвместимост + Устройството вероятно прекъсва синхронизацията. Единственият начин да резрешите проблема е ръчната промяна на настройките. + Необходимите промени са направени. Без повторно напомняне.* + * Оставете без отметка за повторно напомняне. Може да бъде нулирано от настройките на приложението / %s. + Допълнителна информация + jtx Board + + Поддръжка на задачи + Ако сървърът поддържа задачи, те могат да бъдат синхронизирани с приложение за задачи: + OpenTasks + Изглежда не се разработва вече, не се препоръчва. + Tasks.org + (още) не се поддържат.]]> + Няма достъпен магазин за приложения + Не се нуждая от поддръжка на задачи.* + Приложение с отворен код + Радваме се, че използвате %s. Това е приложение с отворен код. Разработката, издръжката и поддръжката му са тежка работа. Молим да допринесете (има много начини) или да дарите. Вашият жест ще бъде високо оценен. + Как да допринеса или даря + Без повторно показване в близко бъдеще + + Разрешения + За да работи нормално %s се нуждае от разрешения. + Всички от изброените по-долу + За да включите вички възможности, изберете това (препоръчано) + Всичко е разрешено + Разрешения за контактите + Контактите не могат да бъдат синхронизирани (не се препоръчва) + Контактите могат да бъдат синхронизирани + Разрешения за календара + Календарите не могат да бъдат синхронизирани (не се препоръчва) + Календарите могат да бъдат синхронизирани + Разрешение за известия + Известията са изключени (непрепоръчително) + Известията са еключени + Разрешения за jtx Board + Няма (инсталирана) синхронизация на задачи, дневници и бележки + Без синхронизиране на задачи, дневници и бележки + Възможно синхронизиране на задачи, дневници и бележки + Разрешения за OpenTasks + Разрешения за Tasks + Няма (инсталирана) синхронизация на задачи + Няма синхронизация на задачи + Задачите могат да бъдат синхронизирани + Предпазване от нулиране на разрешенията + Разрешенията могат да бъдат нулирани автоматично (не се препоръчва) + Разрешенията не могат да бъдат нулирани автоматично + Докоснете Разрешения и махнете отметката от „Премахване на разрешенията, ако приложението не се използва“ + Ако някой превключвател не работи, използвайте настройките на приложението / Разрешения. + Настройки на приложението + + Разрешения за SSID на Wi-Fi + За достъп до името на текущата мрежа на Wi-Fi (SSID) трябва да бъдат изпълнени следните условия: + Разрешение за достъп до точното месоположение + Има разрешение за местоположението + Липсва разрешение за местоположението + Достъп до местоположението във фонов режим + Разрешаване във всички случаи + %s]]> + %s]]> + %s използва разрешението за местоположение за достъп до името на мрежата, когато се изисква синхронизиране само през определена мрежа. Това се случва дори и когато приложението се изпълнява във фонов режим. Не се събират, съхраняват, обработват и разпространяват данни за местоположението. + Винаги включено местоположение + Услугата за местоположението е включена + Услугата за местоположението е изключена + + Преводи + © Ricki Hirner, Bernhard Stockmann (bitfire web engineering GmbH) и сътрудници + Благодарение на: %s]]> + + Преглед/споделяне + Изключване + + Отваря менюто + Затваря менюто + Обратна връзка към бета + Инсталиране приложение за електронна поща + Инсталиране на мрежов четец + Инструменти + Общност + Известията са изключени. Няма да бъдете известявани за грешки при синхронизиране. + Управление на връзките + Няма достатъчно свободно място. Android няма да извършва синхронизация. + Управление на хранилището + Включена е икономия на данни. Синхронизацията във фонов режим е ограничена. + Икономия на трафик + Синхронизиране на всички профили + + Откриването на услугите се провали + Грешка при презареждане + + Не може да работи на преден план + Необходимо е да спрете оптимизирането на батерията за приложението + + Работи на преден план + На някои устройства това е необходимо, за да работи автоматичното синхронизиране. + + Оптимизиране на батерията + Приложението е в белия списък (препоръчително) + Приложението не е в белия списък (непрепоръчително) + Поддържане на преден план + Може да помогне ако устройството възпрепятства автоматичното синхронизиране + Вид на сървъра на прокси + + Според системата + Без сървър на прокси + HTTP + SOCKS (за Orbot) + + Име на хоста на сървъра на прокси + Порт на сървъра на прокси + Разрешения за приложението + Преглед на необходимите за синхронизиране разрешения + Нулиране на (не)доверените сертификати + Нулиране на доверието към всички потребителски сертификати + Всички потребителски сертификати са премахнати + Избиране на тема + + Според системата + Светла + Тъмна + + Всички подсказки, които са били разкарани, ще бъдат показани отново + Интеграция + Приложението Tasks + Синхронизиране чрез %s + Не е инсталирана съвместимо приложение за задачи. + + Контактите не се синхронизират (липсват разрешения) + Календарите те не се синхронизират (липсват разрешения) + Задачите не се синхронизират (липсват разрешения) + Календари и задачи не се синхронизират (липсват разрешения) + Няма достъп до календарите (липсват разрешения) + Разрешения + Това име на регистрация вече се използва + Грешка при преименуване + дневник + Само лични + Няма инсталирана програма за Webcal абонамент. + + Използването на апострофи (\') води до проблеми при някои устройства. + Използвайте адрес за електронна поща вместо име, защото Android ще го използва като адрес на организатора на събитията, които създавате. Не може да има две регистрации с еднакво име. + Вход за напреднали потребители (специални случаи) + С потребителско име/парола + Със сертификат за удостоверяване + Не е намерен сертификат + Инсталиране на сертификат + Google Contacts / Calendar + Погледнете страницата „Проверено с Гугъл“ за последната информация. + Може да получите неочаквани предупреждения и/или да се наложи да създадете собствен клиентски инентификатор. + Профил в Гугъл + Вход с Гугъл + Идентификатор на клиент (по желание) + Не може да бъде получен код за упълномощаване + Грешно потребителско име или парола? + + Ограниченията на Wi-Fi по SSID изискват допълнителни настройки + Управление + Свързаност през ВЧМ + Необходима е свързаност извън ВЧМ (препоръчително) + ВЧМ се счита като интернет + Повторно удостоверяване + Изпълнява повторно влизане чрез OAuth + потребителско име + Псевдоним на клиентския сертификат + Няма избран сертификат + Подразбирано напомняне + + Подразбирано напомняне една минута преди събитието + Подразбирано напомняне %d минути преди събитието + + Няма създадени подразбирани напомняния + Към събитията без напомняне ще бъде добавяно подразбирано напомняне: желания брой минути преди събитието. Оставете празно, за да изключите подразбираните напомняния. + + Групите са отделни vCards + Групите са категории във всеки контакт + + + по желание + Местоположението на хранилището е задължително + Последно синхронизирано: + Никога + Собственик + + Информация за отстраняване на дефекти + Архив на ZIP + Съдържа информацията за отстраняване на дефекти и дневниците + Споделете архива, за да го пренесете на компютър, да го изпратите по електронна поща или да го прикачите към билет за поддръжка. + Споделяне на архива + Информацията за отстраняване на дефекти е приложена към това съобщение (получаващото приложение трябва да поддръжа прикачени файлове). + Грешка на HTTP + Грешка на сървъра + Грешка на WebDAV + Грешка на входа/изхода + Заявката е отказана. За подробности проверете свързаните със завката ресурси и информацията за отстраняване на дефекти. + Заявеният ресурс не съществува (вече). За подробности проверете свързаните със завката ресурси и информацията за отстраняване на дефекти. + Възникнал е проблем от страна на сървъра. Свържете се с поддръжката му. + Възникнала е неочаквана грешка. За подробности проверете информацията за отстраняване на дефекти. + Подробности + Информацията за отстраняване на дефекта е събрана + Ресурси, имащи отношение + Свързани с проблема + Отдалечен ресурс: + Местен ресурс: + Дневници + Налични са подробни дневници + Преглед + + Синхронизирането е спряно + Почти няма свободно пространство + + Дялове на WebDAV + Използвана квота: %1$s / налична: %2$s + Споделяне на съдържание + Демонтиране + Монтиране на дял на WebDAV + Получете директен достъп до файловете си в облака като монтирате дял на WebDAV. + как работят дяловете на WebDAVв ръководството.]]> + Видимо име + Адрес на WebDAV + Недействителен адрес + Потребителско име + Парола + Монтиране + Няма услуга на WebDAV на адреса + Премахване на точката на монтиране + Ще бъдат изгубени подробности за връзката, но файлове няма да бъдат премахвани. + Достъпва се файл на WebDAV + Изтегля се файл на WebDAV + Изпраща се файл на WebDAV + Дял на WebDAV + + Приложението %s е твърде старо + Минимално необходимо издание: %1$s + Грешка (достигнат максимален брой опити) + Преглед diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 216409aa12ddd26450eaccdef044cfdcd85f53e2..d3ecf4be11473b5a5b4846bc08e8ec8ebb124611 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -261,6 +261,7 @@ Per favor, llig la nostra guia \"Tested with Google\" per a obtenir informació actualitzada És possible que sorgisca alguna advertència o error, en eixe cas hauràs de crear el teu propi ID de client. Compte de Google + Inicia la sessió amb Google ID de Client (opcional) política de privadesa per als detalls.]]> Política de dades d\'usuari dels servis de l\'API de Google, incloent els requisits d\'ús limitat.]]> diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 725bf7528946c29e117d8fd138a30885857e729f..b6a594e5414e085d4ff29a9190dd8bc39b2f10b2 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -503,4 +503,5 @@ Benutzername Authentifizierung ist fehlgeschlagen. Bitte geben Sie gültige Anmeldedaten ein. Anmeldefehler (die maximalen Versuche wurden erreicht) - \ No newline at end of file + Konten verwalten + diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 2d79997bf88f847defbb342ff247122c6464b08a..664452499ac2d8666ba19f604a78df81c3a6ae72 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -235,243 +235,243 @@ Lehenetsitako abisua Iraganean egun kopuru hau (0 izan daiteke) baino lehenagoko gertaerak ezikusiko dira. Hutsik utzi gertaera guztiak sinkronizatzeko. - Kontua ez da existitzen (dagoeneko) - Kendu - Utzi - Eremu hau beharrezkoa da - Kudeatu kontuak - Partekatu - Ez dago internetik, sinkronizazioa programatuta - Datu-basea hondatua - Kontu lokal guztiak ezabatu dira. - Prioritate baxuko egoera mezuak - - Zure datuak. Zure aukera. - Har ezazu kontrola. - Sinkronizazio tarte erregularrak - Desgaituta (ez gomendatuta) - Gaituta (gomendatuta) - Sinkronizazioa tarte erregularretan ahalbidetzeko, %s atzeko planoan exekutatzen utzi behar da. Bestela, Androidek sinkronizazioa gelditu dezake edozein unean. - Ez ditut sinkronizazio tarte erregularrak behar.* - %s bateragarritasuna - Gailu honek ziurrenik sinkronizazioa blokeatzen du. Hau jasaten baduzu, eskuz konpondu dezakezu soilik. - Beharrezko ezarpenak bukatu ditut. Ez gogorarazi berriro.* - * Utzi aktibatu gabe gero gogorarazteko. Aplikazioaren ezarpenetan berrezarri daiteke / %s - Informazio gehiago - jtx taula - - Tasks bateragarritasuna - Zereginak zure zerbitzarian onartuta badaude, onartutako zeregin aplikazio batekin sinkronizatu daitezke: - OpenTasks - Badirudi ez dela garatzen – ez da gomendatzen. - Tasks.org - ez dira onartzen (oraindik).]]> - Ez dago denda aplikaziorik eskuragarri - Ez dut zereginen funtzionalitatea behar.* - Kode irekiko softwarea - %s erabiltzen duzula pozik gaude, software irekia delako. Garapen, mantentze eta laguntza lan gogorrak dira. Mesedez pentsatu kolaboratzen (modu asko daude) edo dohaintza bat. Asko eskertuko genuke! - Nola lagundu/dirua eman - Ez erakutsi etorkizunean - - Baimenak - %s baimenak behar ditu ondo funtzionatzeko. - Azpiko guztiak - Erabili hau ezaugarri guztiak gaitzeko (gomendatuta) - Baimen guztiak eman dira - Kontaktuen baimenak - Kontaktu sinkronizaziorik ez (ez gomendatuta) - Kontaktuen sinkronizazioa posible - Egutegiaren baimenak - Egutegi sinkronizaziorik ez (ez gomendatuta) - Egutegiaren sinkronizazioa posible - Jakinarazpen baimena - Jakinarazpenak desgaituta (ez gomendatuta) - Jakinarazpenak gaituta - jtx taularen baimenak - Zereginen, egunkarien eta noten sinkronizaziorik ez (instalatu gabe) - Zeregin, egunkari, noten sinkronizaziorik ez - Zeregin, egunkari, noten sinkronizazioa posible - OpenTasks baimenak - Tasks baimenak - Ez sinkronizatu zereginak (instalatu gabe) - Zeregin sink. ez - Zereginen sinkronizazioa posible - Mantendu baimenak - Baimenak automatikoki berezarri daitezke (ez gomendatuta) - Baimenak ez dira automatikoki berezarriko - Egin klik Baimenak atalean > kendu marka \"Kendu baimenak aplikazioa ez bada erabiltzen\" aukeratik - Interruptore batek funtzionatzen ez badu, erabili aplikazioaren ezarpenak / Baimenak. - Aplikazioaren ezarpenak - - WiFi SSID baimenak - Uneko WiFi izena (SSID) atzitzeko, baldintza hauek bete behar dira: - Kokapen zehatz baimena - Kokapen baimena emanda - Kokapen baimena ukatuta - Atzeko planoko kokapen baimena - Baimendu beti - %s(e)ra ezarri da]]> - %s(e)ra ezarri]]> - %s zure kokapen baimena erabiltzen du uneko WiFi SSIDa SSIDz murriztatutako kontuen kontra egiaztatzeko. Hau aplikazioa atzeko planoan badago ere gertatuko da. Ez da kokapen daturik biltzen, gordetzen, prozesatzen edo inora bidaltzen. - Kokapena beti gaituta - Kokapen zerbitzua gaituta dago - Kokapen zerbitzua desgaituta dago - - Itzulpenak - © Ricki Hirner, Bernhard Stockmann (bitfire web engineering GmbH) eta kolaboratzaileak - Eskerrik asko: %s]]> - - Ikusi/partekatu - Desgaitu - - Tresnak - Komunitatea - Jakinarazpenak desgaituta. Ez zaitugu sinkronizazio-erroreei buruz jakinaraziko. - Kudeatu konexioak - Biltegiratze-lekua baxua da. Androidek ez du sinkronizatuko. - Kudeatu biltegia - Datu-aurrezpena gaituta. Atzeko planoko sinkronizazioa murriztuko da. - Kudeatu datu-aurrezpena - Sinkronizatu kontu guztiak - - Zerbitzuaren detekzioak huts egin du - Ezin izan da bilduma zerrenda freskatu - - Ezin da aurrealdean exekutatu - Bateriaren optimizazioaren zerrenda zuria behar da - - Aurrealdean exekutatzen - Gailu batzuetan, hau beharrezkoa da sinkronizazio automatikorako. - - Bateria optimizazioa - Aplikazioa zerrenda zurian dago (gomendatuta) - Aplikazioa ez dago zerrenda zurian (ez gomendatuta) - Mantendu aurrealdean - Zure gailuak sinkronizazio automatikoa galarazten badu lagundu dezake - Proxy mota - - Sistemaren lehenetsia - Proxyrik ez - HTTP - SOCKS (Orbot-erako) - - Proxy ostalariaren izena - Proxy ataka - Aplikazioaren baimenak - Berrikusi sinkronizaziorako beharrezkoak diren baimenak - Hautatu gaia - - Sistemaren lehenetsia - Argia - Iluna - - Integrazioa - Tasks aplikazioa - %s(r)ekin sinkronizatzen - Ez da zeregin aplikazio bategarririk aurkitu - - Kontaktu sinkronizaziorik ez (baimenak falta dira) - Egutegi sinkronizaziorik ez (baimenak falta dira) - Zeregin sinkronizaziorik ez (baimenak falta dira) - Egutegi eta zeregin sinkronizaziorik ez (baimenak falta dira) - Ezin dira egutegiak atzitu (baimenak falta dira) - Baimenak - Sinkronizatu bildumak - Kontuaren izena hartuta dago - Ezin izan da kontua berrizendatu - egunkaria - Erakutsi pertsonala soilik - Apostrofeen erabilera (\'), gailu batzuetan arazoak sortzen ditu. - Saio-hasiera aurreratua (kasu bereziak) - Erabili erabiltzaile-izena/pasahitza - Erabili bezero ziurtagiria - Ez da ziurtagiririk aurkitu - Instalatu ziurtagiria - Google Kontaktuak / Egutegia - Mesedez, ikusi gure \"Google-ekin probatuta\" orria informazio eguneraturako. - Oharrak jaso edo zure bezero ID sortzea beharrezkoa izatea gertatu daiteke. - Google kontua - Hasi saioa Google-rekin - Bezeroaren ID (aukerazkoa) - Zure bezeroaren ID erabili dezakezu, gureak funtzionatzen ez badu. - Erakutsi nola! - Pribatutasun politika xehetasunetarako.]]> - <![CDATA[%1$s -k <a href="%2$s">Google API Zerbitzuen Erabiltzaileen Datuen Gidalerroak</a>, erabilera mugatuko eskakizunak barne.]]. - Ezin izan da baimen-kodea lortu - Erabiltzaile-izena (eposta) / pasahitza txarto dago? - - - Eskuz soilik - 15 minuturo - 30 minuturo - Ordu batero - 2 orduro - 4 orduro - Egunero - - WiFi SSID murriztapenak ezarpen gehiago behar ditu - Kudeatu - Berriro autentifikatu - Egin OAuth saioa berriro - erabiltzaile-izena - Ez da ziurtagiririk hautatu - - Taldeak vCard banatuak dira - Taldeak kontaktu bakoitzeko kategoriak dira - - Biltegiaren kokapena beharrezkoa da - Azkenengoz sinkronizatuta: - Ez da inoiz sinkronizatu - Jabea: - - ZIP artxiboa - Arazte informazioa eta erregistroak ditu - Partekatu artxiboa ordenagailu batera transferitzeko, e-posta edo ticket baten bidez bidaltzeko. - Partekatu artxiboa - Arazketa informazioa mezuan erantsiko da (hartuko dituen aplikazioak eranskinak onartu behar ditu). - HTTP errorea - Zerbitzari errorea - WebDAV errorea - S/I errorea - Eskaera ukatu egin da. Egiaztatu parte hartzen dituzten baliabideak eta arazketa informazioa xehetasun gehiagorako. - Eskatutako baliabidea ez dago (jada). Egiaztatu parte hartzen dituzten baliabideak eta arazketa informazioa xehetasun gehiagorako. - Zerbitzariak arazo bat izan du. Mesedez jarri harremanetan zure zerbitzariaren laguntzarekin. - Ustekabeko errore bat gertatu da. Ikusi arazketa informazioa xehetasunentzako. - Ikusi xehetasunak - Arazketa informazioa lortu da - Parte hartzen duten baliabideak - Arazoarekin erlazionatuta - Kanpoko baliabidea: - Baliabide lokala: - Egunkariak - Erregistro xehetuak eskuragarri daude - Ikusi egunkariak - - Sinkronizazioa geldituta - Ia ez dago leku librerik - - WebDAV muntaiak - Erabilitako kuota: %1$s / eskuragarri: %2$s - Partekatu edukia - Desmuntatu - Gehitu WebDAV muntaia - Atzitu zure cloud fitxategiak zuzenean WebDAV muntaia bat gehitzen! - WebDAV muntaiak nola funtzionatzen duten.]]> - Bistaratze-izena - WebDAV URL - URL baliogabea - Erabiltzaile izena - Pasahitza - Gehitu muntaia - Ez dago WebDAV zerbitzurik URL honetan - Kendu muntaia-puntua - Konexio xehetasunak galduko dira, baina ez da fitxategirik ezabatuko. - WebDAV fitxategia atzitzen - WebDAV fitxategia deskargatzen - WebDAV fitxategia kargatzen - WebDAV muntaia - - %s zaharregia - Beharrezko bertsio minimoa: %1$s - Errore leuna (saiakera maximora heldu da) + Kontua ez da existitzen (dagoeneko) + Kendu + Utzi + Eremu hau beharrezkoa da + Kudeatu kontuak + Partekatu + Ez dago internetik, sinkronizazioa programatuta + Datu-basea hondatua + Kontu lokal guztiak ezabatu dira. + Prioritate baxuko egoera mezuak + + Zure datuak. Zure aukera. + Har ezazu kontrola. + Sinkronizazio tarte erregularrak + Desgaituta (ez gomendatuta) + Gaituta (gomendatuta) + Sinkronizazioa tarte erregularretan ahalbidetzeko, %s atzeko planoan exekutatzen utzi behar da. Bestela, Androidek sinkronizazioa gelditu dezake edozein unean. + Ez ditut sinkronizazio tarte erregularrak behar.* + %s bateragarritasuna + Gailu honek ziurrenik sinkronizazioa blokeatzen du. Hau jasaten baduzu, eskuz konpondu dezakezu soilik. + Beharrezko ezarpenak bukatu ditut. Ez gogorarazi berriro.* + * Utzi aktibatu gabe gero gogorarazteko. Aplikazioaren ezarpenetan berrezarri daiteke / %s + Informazio gehiago + jtx taula + + Tasks bateragarritasuna + Zereginak zure zerbitzarian onartuta badaude, onartutako zeregin aplikazio batekin sinkronizatu daitezke: + OpenTasks + Badirudi ez dela garatzen – ez da gomendatzen. + Tasks.org + ez dira onartzen (oraindik).]]> + Ez dago denda aplikaziorik eskuragarri + Ez dut zereginen funtzionalitatea behar.* + Kode irekiko softwarea + %s erabiltzen duzula pozik gaude, software irekia delako. Garapen, mantentze eta laguntza lan gogorrak dira. Mesedez pentsatu kolaboratzen (modu asko daude) edo dohaintza bat. Asko eskertuko genuke! + Nola lagundu/dirua eman + Ez erakutsi etorkizunean + + Baimenak + %s baimenak behar ditu ondo funtzionatzeko. + Azpiko guztiak + Erabili hau ezaugarri guztiak gaitzeko (gomendatuta) + Baimen guztiak eman dira + Kontaktuen baimenak + Kontaktu sinkronizaziorik ez (ez gomendatuta) + Kontaktuen sinkronizazioa posible + Egutegiaren baimenak + Egutegi sinkronizaziorik ez (ez gomendatuta) + Egutegiaren sinkronizazioa posible + Jakinarazpen baimena + Jakinarazpenak desgaituta (ez gomendatuta) + Jakinarazpenak gaituta + jtx taularen baimenak + Zereginen, egunkarien eta noten sinkronizaziorik ez (instalatu gabe) + Zeregin, egunkari, noten sinkronizaziorik ez + Zeregin, egunkari, noten sinkronizazioa posible + OpenTasks baimenak + Tasks baimenak + Ez sinkronizatu zereginak (instalatu gabe) + Zeregin sink. ez + Zereginen sinkronizazioa posible + Mantendu baimenak + Baimenak automatikoki berezarri daitezke (ez gomendatuta) + Baimenak ez dira automatikoki berezarriko + Egin klik Baimenak atalean > kendu marka \"Kendu baimenak aplikazioa ez bada erabiltzen\" aukeratik + Interruptore batek funtzionatzen ez badu, erabili aplikazioaren ezarpenak / Baimenak. + Aplikazioaren ezarpenak + + WiFi SSID baimenak + Uneko WiFi izena (SSID) atzitzeko, baldintza hauek bete behar dira: + Kokapen zehatz baimena + Kokapen baimena emanda + Kokapen baimena ukatuta + Atzeko planoko kokapen baimena + Baimendu beti + %s(e)ra ezarri da]]> + %s(e)ra ezarri]]> + %s zure kokapen baimena erabiltzen du uneko WiFi SSIDa SSIDz murriztatutako kontuen kontra egiaztatzeko. Hau aplikazioa atzeko planoan badago ere gertatuko da. Ez da kokapen daturik biltzen, gordetzen, prozesatzen edo inora bidaltzen. + Kokapena beti gaituta + Kokapen zerbitzua gaituta dago + Kokapen zerbitzua desgaituta dago + + Itzulpenak + © Ricki Hirner, Bernhard Stockmann (bitfire web engineering GmbH) eta kolaboratzaileak + Eskerrik asko: %s]]> + + Ikusi/partekatu + Desgaitu + + Tresnak + Komunitatea + Jakinarazpenak desgaituta. Ez zaitugu sinkronizazio-erroreei buruz jakinaraziko. + Kudeatu konexioak + Biltegiratze-lekua baxua da. Androidek ez du sinkronizatuko. + Kudeatu biltegia + Datu-aurrezpena gaituta. Atzeko planoko sinkronizazioa murriztuko da. + Kudeatu datu-aurrezpena + Sinkronizatu kontu guztiak + + Zerbitzuaren detekzioak huts egin du + Ezin izan da bilduma zerrenda freskatu + + Ezin da aurrealdean exekutatu + Bateriaren optimizazioaren zerrenda zuria behar da + + Aurrealdean exekutatzen + Gailu batzuetan, hau beharrezkoa da sinkronizazio automatikorako. + + Bateria optimizazioa + Aplikazioa zerrenda zurian dago (gomendatuta) + Aplikazioa ez dago zerrenda zurian (ez gomendatuta) + Mantendu aurrealdean + Zure gailuak sinkronizazio automatikoa galarazten badu lagundu dezake + Proxy mota + + Sistemaren lehenetsia + Proxyrik ez + HTTP + SOCKS (Orbot-erako) + + Proxy ostalariaren izena + Proxy ataka + Aplikazioaren baimenak + Berrikusi sinkronizaziorako beharrezkoak diren baimenak + Hautatu gaia + + Sistemaren lehenetsia + Argia + Iluna + + Integrazioa + Tasks aplikazioa + %s(r)ekin sinkronizatzen + Ez da zeregin aplikazio bategarririk aurkitu + + Kontaktu sinkronizaziorik ez (baimenak falta dira) + Egutegi sinkronizaziorik ez (baimenak falta dira) + Zeregin sinkronizaziorik ez (baimenak falta dira) + Egutegi eta zeregin sinkronizaziorik ez (baimenak falta dira) + Ezin dira egutegiak atzitu (baimenak falta dira) + Baimenak + Sinkronizatu bildumak + Kontuaren izena hartuta dago + Ezin izan da kontua berrizendatu + egunkaria + Erakutsi pertsonala soilik + Apostrofeen erabilera (\'), gailu batzuetan arazoak sortzen ditu. + Saio-hasiera aurreratua (kasu bereziak) + Erabili erabiltzaile-izena/pasahitza + Erabili bezero ziurtagiria + Ez da ziurtagiririk aurkitu + Instalatu ziurtagiria + Google Kontaktuak / Egutegia + Mesedez, ikusi gure \"Google-ekin probatuta\" orria informazio eguneraturako. + Oharrak jaso edo zure bezero ID sortzea beharrezkoa izatea gertatu daiteke. + Google kontua + Hasi saioa Google-rekin + Bezeroaren ID (aukerazkoa) + Zure bezeroaren ID erabili dezakezu, gureak funtzionatzen ez badu. + Erakutsi nola! + Pribatutasun politika xehetasunetarako.]]> + <![CDATA[%1$s -k <a href="%2$s">Google API Zerbitzuen Erabiltzaileen Datuen Gidalerroak</a>, erabilera mugatuko eskakizunak barne.]]. + Ezin izan da baimen-kodea lortu + Erabiltzaile-izena (eposta) / pasahitza txarto dago? + + + Eskuz soilik + 15 minuturo + 30 minuturo + Ordu batero + 2 orduro + 4 orduro + Egunero + + WiFi SSID murriztapenak ezarpen gehiago behar ditu + Kudeatu + Berriro autentifikatu + Egin OAuth saioa berriro + erabiltzaile-izena + Ez da ziurtagiririk hautatu + + Taldeak vCard banatuak dira + Taldeak kontaktu bakoitzeko kategoriak dira + + Biltegiaren kokapena beharrezkoa da + Azkenengoz sinkronizatuta: + Ez da inoiz sinkronizatu + Jabea: + + ZIP artxiboa + Arazte informazioa eta erregistroak ditu + Partekatu artxiboa ordenagailu batera transferitzeko, e-posta edo ticket baten bidez bidaltzeko. + Partekatu artxiboa + Arazketa informazioa mezuan erantsiko da (hartuko dituen aplikazioak eranskinak onartu behar ditu). + HTTP errorea + Zerbitzari errorea + WebDAV errorea + S/I errorea + Eskaera ukatu egin da. Egiaztatu parte hartzen dituzten baliabideak eta arazketa informazioa xehetasun gehiagorako. + Eskatutako baliabidea ez dago (jada). Egiaztatu parte hartzen dituzten baliabideak eta arazketa informazioa xehetasun gehiagorako. + Zerbitzariak arazo bat izan du. Mesedez jarri harremanetan zure zerbitzariaren laguntzarekin. + Ustekabeko errore bat gertatu da. Ikusi arazketa informazioa xehetasunentzako. + Ikusi xehetasunak + Arazketa informazioa lortu da + Parte hartzen duten baliabideak + Arazoarekin erlazionatuta + Kanpoko baliabidea: + Baliabide lokala: + Egunkariak + Erregistro xehetuak eskuragarri daude + Ikusi egunkariak + + Sinkronizazioa geldituta + Ia ez dago leku librerik + + WebDAV muntaiak + Erabilitako kuota: %1$s / eskuragarri: %2$s + Partekatu edukia + Desmuntatu + Gehitu WebDAV muntaia + Atzitu zure cloud fitxategiak zuzenean WebDAV muntaia bat gehitzen! + WebDAV muntaiak nola funtzionatzen duten.]]> + Bistaratze-izena + WebDAV URL + URL baliogabea + Erabiltzaile izena + Pasahitza + Gehitu muntaia + Ez dago WebDAV zerbitzurik URL honetan + Kendu muntaia-puntua + Konexio xehetasunak galduko dira, baina ez da fitxategirik ezabatuko. + WebDAV fitxategia atzitzen + WebDAV fitxategia deskargatzen + WebDAV fitxategia kargatzen + WebDAV muntaia + + %s zaharregia + Beharrezko bertsio minimoa: %1$s + Errore leuna (saiakera maximora heldu da) diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index affc24578e8222e64052d9149f5cf448ee33656e..3fc5c3cd27a2cd22296e011c2a305ebcc6d70284 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -244,233 +244,233 @@ Murena.io Axenda Murena.io Google - A conta non existe (definitivamente) - Eliminar - Desbotar - Este campo é requerido - Xestionar contas - Compartir - Sen internet, programando a sincr. - Base de datos estragada - Foron eliminadas tódalas contas locais - Mensaxes de estado de baixa prioridade - - Os teus datos. Ti elixes. - Toma o control. - Intervalos regulares de sincronización - Desactivado (non recomendado) - Activado (recomendado) - Para sincronizar a intervalos regulares, %s debe ter permiso para executarse en segundo plano. Se non, Android podería deter a sincronización. - Non preciso sincr. con regularidade.* - Compatibilidade de %s - Este dispositivo probablemente está a bloquear a sincronización. Esto só se pode solucionar de xeito manual. - Xa fixen o que me pediades. Non mo lembres máis.* - * Deixar sen marcar para lembrar máis tarde. Pode restablecerse nos axustes da app / %s. - Máis información - jtx Board - - Soporte para Tasks - Se as tarefas están soportadas polo teu servidor, poden ser sincronizadas cunha app que soporte tarefas: - OpenTasks - Semella que xa non está en desenvolvemento – non recomendado. - Task.org - non están soportadas (aínda).]]> - Sen tenda de apps dispoñible - Non necesito soporte para tarefas.* - Software de código aberto - Encántanos que uses %s, que é software de código aberto. O desenvolvemento, mantemento e soporte son un traballo difícil. Considera colaborar (hai moitos xeitos) ou facer unha doazón. Sería de agradecer! - Como contribuír/doar - Non mostrar no futuro próximo - - Permisos - %s require permisos para funcionar axeitadamente. - Todos os de abaixo - Usa isto para habilitar tódalas características (recomendado) - Todos os permisos concedidos - Permisos de contactos - Sen sincronización de contactos (non recomendado) - É posible sincronizar os contactos - Permisos de calendario - Sen sincronización de calendario (non se recomenda) - É posible sincronizar o calendario - Permiso de notificacións - Notificacións desactivadas (non recomendado) - Notificacións activadas - permisos para jtx Board - Sen sincr. de tarefas, diarios e notas (non instalado) - Sen sincr. de tarefas, diarios, notas - Dispoñible sincr. de tarefas, diarios e notas - Permisos para OpenTasks - Permisos para Tasks - Sen sincronización de tarefas (non instalado) - Tarefas non sincr. - É posible sincronizar as tarefas - Manter permisos - Os permisos poden restablecerse automáticamente (non recomendado) - Os permisos non se restablecerán automáticamente - Preme en Permisos > desmarca \"Eliminar permisos se a app non se usa\" - Se unha opción non funciona, usa axustes da aplicación / Permisos. - Permisos da aplicación - - Permisos WiFi SSID - Para poder acceder á WiFi (SSID) actual, deben darse estas condicións: - Permiso para localización precisa - Permiso de localización outorgado - Permiso de localización denegado - Permiso de localización en segundo plano - Permitir en todo momento - %s]]> - %s]]> - %s utiliza o permiso de Localización só para determinar o SSID da WiFi para contas restrinxidas a uns SSIDs. Esto acontecerá incluso cando a app está en segundo plano. Non se recollen datos de localización, nin se gardan, procesan ou envían a ningún sitio. - Localización sempre activada - Servizo de localización activado - Servizo de localización desactivado - - Traducións - © Ricki Hirner, Bernhard Stockmann (bitfire web engineering GmbH) e colaboradoras - Grazas a: %s]]> - - Ver/compartir - Desactivar - - Ferramentas - Comunidade - Notificacións desactivadas. Non verás os avisos de erro na sincr. - Xestionar conexións - Queda pouco espazo, Android non executará a sincronización. - Xestionar almacenaxe - O aforro de datos está activado. A sincronización en segundo plano está restrinxida. - Xestionar aforro de datos - Sincroniza todas as contas - - Fallou a detección do servizo - Non se actualizou a lista da colección - - Non pode executarse en segundo plano - Require ter permiso para optimizacións de batería - - Executándose en primeiro plano - En algúns dispositivos esto é necesario para a sincronización automática. - - Optimización da batería - App está permitida (recomendado) - App sen permiso (non recomendado) - Manter en primeiro plano - Podería ser útil se o dispositivo dificulta a sincronización automática - Tipo de Proxy - - Por defecto no sistema - Sen proxy - HTTP - SOCKS (para Orbot) - - Servidor do proxy - Porto do proxy - Permisos da App - Revisa os permisos requeridos para a sincronización - Elixe decorado - - Por defecto no sistema - Claro - Escuro - - Integración - App Tarefas - Sincronizando con %s - Non se atopan app de tarefas compatible - - Sen sincronización de contactos (faltan permisos) - Sen sincronización de calendario (faltan permisos) - Sen sincronización de tarefas (faltan permisos) - Sen sincronización de calendario e tarefas (faltan permisos) - Non se pode acceder aos calendarios (faltan permisos) - Permisos - Sincronizar coleccións - O nome de conta xa está a ser utilizado - Non cambiou o nome da conta - diario - Mostrar só personal - - O uso de apóstrofes (\') pode causar problemas nalgúns dispositivos, según nos informan. - Conexión avanzada (casos especiais) - Usar nome de usuaria/contrasinal - Usar certificado cliente - Non se atopa certificado - Instalar certificado - Contactos / Calendario de Google - Le a nosa páxina \"Probado con Google\" para ter información actualizada. - Pode que recibas algún aviso e/ou teñas que crear o teu propio ID de cliente. - Conta de Google - Inicia sesión con Google - ID Cliente (optativo) - política de Privacidade para saber máis.]]> - Google API Services User Data Policy, incluíndo os requerimentos de Limited Use.]]> - Non se puido obter o código de autorización - ¿Usuaria (enderezo email) / contrasinal incorrectos? - - A restrición WiFi SSID precisa máis axustes - Xestionar - Volver a autenticar - Intentar acceder con OAuth outra vez - identificador - Non hai certificado seleccionado - - Grupos son vCards separados - Grupos son categorías por contacto - - Requírese a localización da almacenaxe - Última sincronización: - Nunca - Propietaria: - - Arquivo ZIP - Contén info de depuración e rexistros - Comparte o arquivo para pasalo á computadora, envialo por email ou anexalo a un tícket de axuda - Compartir arquivo - Info de depuración anexa a esta mensaxe (require soporte de anexos na app receptora). - Erro HTTP - Fallo no servidor - Fallo WebDAV - Fallo I/O - A solicitude foi denegada. Comproba os recursos implicados e o rexistro de depuración para máis info. - O recurso solicitado xa non existe. Comproba os recursos implicados e a información de depuración. - Hai un fallo no lado do servidor. Contacta co soporte do servidor. - Aconteceu un fallo non agardado. Mira a info de depuración para detalles. - Ver detalles - Recolleuse a info de depuración - Recursos implicados - Relacionado co problema - Recurso remoto: - Recurso local: - Rexistros - Están dispoñibles rexistros explicativos - Ver rexistros - - Sincronización en pausa - Case non queda espazo libre - - Montaxes WebDAV - Cota utilizada: %1$s / dispoñible: %2$s - Compartir contido - Desmontar - Engadir montaxe WebDAV - Accede aos teus ficheiros na nube engadindo unha montaxe WebDAV! - como funciona a montaxe WebDAV.]]> - Nome mostrado - URL WebDAV - URL inválido - Nome de usuaria - Contrasinal - Engadir montaxe - Neste URL non hai ningún servizo WebDAV - Eliminar punto de montaxe - Perderanse os detalles da conexión, mais non se eliminarán ficheiros. - Accedendo ao ficheiro WebDAV - Descargando ficheiro WebDAV - Subindo ficheiro WebDAV - Montaxe WebDAV - - %s demasiado antigo - Versión mínima requerida: %1$s - Erro (acadouse o máx. de reintentos) + A conta non existe (definitivamente) + Eliminar + Desbotar + Este campo é requerido + Xestionar contas + Compartir + Sen internet, programando a sincr. + Base de datos estragada + Foron eliminadas tódalas contas locais + Mensaxes de estado de baixa prioridade + + Os teus datos. Ti elixes. + Toma o control. + Intervalos regulares de sincronización + Desactivado (non recomendado) + Activado (recomendado) + Para sincronizar a intervalos regulares, %s debe ter permiso para executarse en segundo plano. Se non, Android podería deter a sincronización. + Non preciso sincr. con regularidade.* + Compatibilidade de %s + Este dispositivo probablemente está a bloquear a sincronización. Esto só se pode solucionar de xeito manual. + Xa fixen o que me pediades. Non mo lembres máis.* + * Deixar sen marcar para lembrar máis tarde. Pode restablecerse nos axustes da app / %s. + Máis información + jtx Board + + Soporte para Tasks + Se as tarefas están soportadas polo teu servidor, poden ser sincronizadas cunha app que soporte tarefas: + OpenTasks + Semella que xa non está en desenvolvemento – non recomendado. + Task.org + non están soportadas (aínda).]]> + Sen tenda de apps dispoñible + Non necesito soporte para tarefas.* + Software de código aberto + Encántanos que uses %s, que é software de código aberto. O desenvolvemento, mantemento e soporte son un traballo difícil. Considera colaborar (hai moitos xeitos) ou facer unha doazón. Sería de agradecer! + Como contribuír/doar + Non mostrar no futuro próximo + + Permisos + %s require permisos para funcionar axeitadamente. + Todos os de abaixo + Usa isto para habilitar tódalas características (recomendado) + Todos os permisos concedidos + Permisos de contactos + Sen sincronización de contactos (non recomendado) + É posible sincronizar os contactos + Permisos de calendario + Sen sincronización de calendario (non se recomenda) + É posible sincronizar o calendario + Permiso de notificacións + Notificacións desactivadas (non recomendado) + Notificacións activadas + permisos para jtx Board + Sen sincr. de tarefas, diarios e notas (non instalado) + Sen sincr. de tarefas, diarios, notas + Dispoñible sincr. de tarefas, diarios e notas + Permisos para OpenTasks + Permisos para Tasks + Sen sincronización de tarefas (non instalado) + Tarefas non sincr. + É posible sincronizar as tarefas + Manter permisos + Os permisos poden restablecerse automáticamente (non recomendado) + Os permisos non se restablecerán automáticamente + Preme en Permisos > desmarca \"Eliminar permisos se a app non se usa\" + Se unha opción non funciona, usa axustes da aplicación / Permisos. + Permisos da aplicación + + Permisos WiFi SSID + Para poder acceder á WiFi (SSID) actual, deben darse estas condicións: + Permiso para localización precisa + Permiso de localización outorgado + Permiso de localización denegado + Permiso de localización en segundo plano + Permitir en todo momento + %s]]> + %s]]> + %s utiliza o permiso de Localización só para determinar o SSID da WiFi para contas restrinxidas a uns SSIDs. Esto acontecerá incluso cando a app está en segundo plano. Non se recollen datos de localización, nin se gardan, procesan ou envían a ningún sitio. + Localización sempre activada + Servizo de localización activado + Servizo de localización desactivado + + Traducións + © Ricki Hirner, Bernhard Stockmann (bitfire web engineering GmbH) e colaboradoras + Grazas a: %s]]> + + Ver/compartir + Desactivar + + Ferramentas + Comunidade + Notificacións desactivadas. Non verás os avisos de erro na sincr. + Xestionar conexións + Queda pouco espazo, Android non executará a sincronización. + Xestionar almacenaxe + O aforro de datos está activado. A sincronización en segundo plano está restrinxida. + Xestionar aforro de datos + Sincroniza todas as contas + + Fallou a detección do servizo + Non se actualizou a lista da colección + + Non pode executarse en segundo plano + Require ter permiso para optimizacións de batería + + Executándose en primeiro plano + En algúns dispositivos esto é necesario para a sincronización automática. + + Optimización da batería + App está permitida (recomendado) + App sen permiso (non recomendado) + Manter en primeiro plano + Podería ser útil se o dispositivo dificulta a sincronización automática + Tipo de Proxy + + Por defecto no sistema + Sen proxy + HTTP + SOCKS (para Orbot) + + Servidor do proxy + Porto do proxy + Permisos da App + Revisa os permisos requeridos para a sincronización + Elixe decorado + + Por defecto no sistema + Claro + Escuro + + Integración + App Tarefas + Sincronizando con %s + Non se atopan app de tarefas compatible + + Sen sincronización de contactos (faltan permisos) + Sen sincronización de calendario (faltan permisos) + Sen sincronización de tarefas (faltan permisos) + Sen sincronización de calendario e tarefas (faltan permisos) + Non se pode acceder aos calendarios (faltan permisos) + Permisos + Sincronizar coleccións + O nome de conta xa está a ser utilizado + Non cambiou o nome da conta + diario + Mostrar só personal + + O uso de apóstrofes (\') pode causar problemas nalgúns dispositivos, según nos informan. + Conexión avanzada (casos especiais) + Usar nome de usuaria/contrasinal + Usar certificado cliente + Non se atopa certificado + Instalar certificado + Contactos / Calendario de Google + Le a nosa páxina \"Probado con Google\" para ter información actualizada. + Pode que recibas algún aviso e/ou teñas que crear o teu propio ID de cliente. + Conta de Google + Inicia sesión con Google + ID Cliente (optativo) + política de Privacidade para saber máis.]]> + Google API Services User Data Policy, incluíndo os requerimentos de Limited Use.]]> + Non se puido obter o código de autorización + ¿Usuaria (enderezo email) / contrasinal incorrectos? + + A restrición WiFi SSID precisa máis axustes + Xestionar + Volver a autenticar + Intentar acceder con OAuth outra vez + identificador + Non hai certificado seleccionado + + Grupos son vCards separados + Grupos son categorías por contacto + + Requírese a localización da almacenaxe + Última sincronización: + Nunca + Propietaria: + + Arquivo ZIP + Contén info de depuración e rexistros + Comparte o arquivo para pasalo á computadora, envialo por email ou anexalo a un tícket de axuda + Compartir arquivo + Info de depuración anexa a esta mensaxe (require soporte de anexos na app receptora). + Erro HTTP + Fallo no servidor + Fallo WebDAV + Fallo I/O + A solicitude foi denegada. Comproba os recursos implicados e o rexistro de depuración para máis info. + O recurso solicitado xa non existe. Comproba os recursos implicados e a información de depuración. + Hai un fallo no lado do servidor. Contacta co soporte do servidor. + Aconteceu un fallo non agardado. Mira a info de depuración para detalles. + Ver detalles + Recolleuse a info de depuración + Recursos implicados + Relacionado co problema + Recurso remoto: + Recurso local: + Rexistros + Están dispoñibles rexistros explicativos + Ver rexistros + + Sincronización en pausa + Case non queda espazo libre + + Montaxes WebDAV + Cota utilizada: %1$s / dispoñible: %2$s + Compartir contido + Desmontar + Engadir montaxe WebDAV + Accede aos teus ficheiros na nube engadindo unha montaxe WebDAV! + como funciona a montaxe WebDAV.]]> + Nome mostrado + URL WebDAV + URL inválido + Nome de usuaria + Contrasinal + Engadir montaxe + Neste URL non hai ningún servizo WebDAV + Eliminar punto de montaxe + Perderanse os detalles da conexión, mais non se eliminarán ficheiros. + Accedendo ao ficheiro WebDAV + Descargando ficheiro WebDAV + Subindo ficheiro WebDAV + Montaxe WebDAV + + %s demasiado antigo + Versión mínima requerida: %1$s + Erro (acadouse o máx. de reintentos) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index c2e7244942a9d6f17272fc29122836bcf3ebe291..61c0b31d188885387283689fbe52555d6d2c2a23 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -210,7 +210,6 @@ カレンダーのサブスクリプションは (まだ) ありません。 下にスワイプすると、サーバーからリストを更新します。 今すぐ同期 - コレクションを同期 アカウント設定 アカウントの名前を変更 未保存のローカルデータが破棄されることがあります。名前の変更後に再同期が必要です。新しいアカウント名: @@ -302,6 +301,9 @@ 利用可能な WiFi ネットワークのカンマ区切りの名前 (SSID) (空白にするとすべて) WiFi SSID 制限にはさらに設定が必要です 管理 + VPN 接続 + VPN でない接続を要求 (推奨) + VPN を一般のインターネット接続として扱う 認証 再認証 もう一度 OAuth ログインを実行 diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index ca3019c3f678cbcdadb6e0459389a459855652b4..9c33c2929bd38daacd8ee9b21e25915bee61b5e3 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -290,209 +290,219 @@ Yahoo adresboek Alle accounts zijn lokaal verwijderd. Regelmatige synchronisatie intervallen - Accounts beheren - Geen internet, planning de sync - Om op gezette tijden te synchroniseren moet %s zonder beperking op de achtergrond kunnen draaien. Anders kan Android het synchroniseren op elk moment onderbreken. - Synchroniseren op gezette tijden is niet nodig.* - %s compatibiliteit - Waarschijnlijk blokkeert dit toestel het synchroniseren. In dat geval is dit alleen handmatig op te lossen. - De vereiste instellingen zijn verricht. Er aan herinneren is niet meer nodig.* - * Niet aanvinken om later herinnerd te worden. Kan teruggezet in app instellingen / %s. - Meer informatie - jtx Board - - Ondersteunt taken - Als de server taken ondersteunt, synchroniseert een geschikte taken-app ze: - OpenTasks - Schijnt niet meer ontwikkeld te worden - niet aanbevolen. - Tasks.org - worden (nog) niet ondersteund.]]> - Geen app-store beschikbaar - Ik hoef geen ondersteuning van taken.* - Open-source software - We zijn blij dat de keuze valt op open source software %s. Ontwikkelen, onderhouden en ondersteunen is veel werk. Overweeg daarom bij te dragen (kan op vele manieren) of een donatie. Wij waarderen het zeer! - Hoe bijdragen/doneren - In de nabije toekomst niet weergeven - - Rechten toestaan - %s heeft rechten nodig om goed te werken. - Alle onderstaande - Gebruik dit om alle functies in te schakelen (aanbevolen) - Alle rechten toegekend - Contacten toestaan - Geen contacten synchroniseren (niet aanbevolen) - Contacten synchroniseren mogelijk - Kalender machtigingen - Geen kalenders synchroniseren (niet aanbevolen) - Kalenders synchroniseren mogelijk - Toestemming voor meldingen - Meldingen uitgeschakeld (niet aanbevolen) - Meldingen ingeschakeld - jtx Board-rechten - Geen taak-, kalender- en notitiesync (niet geïnstalleerd) - Geen taak-, kalender- en notitiesync - Taak-, kalender- en notitiesync mogelijk - OpenTasks rechten - Rechten voor taken - Geen taak-sync (niet geïnstalleerd) - Geen taak-sync - Taak-sync mogelijk - Rechten behouden - Rechten kunnen automatisch worden teruggezet (niet aanbevolen) - Rechten worden niet automatisch teruggezet - Klik op App Rechten > vinkje uit bij \"Rechten intrekken\" - Als een schakeloptie niet werkt, gebruik dan App-info / Rechten. - App instellingen - - WiFi SSID rechten - Voor toegang tot de huidige WiFi-naam (SSID), moet aan deze voorwaarden worden voldaan: - Recht van toegang tot exacte locatie - Toegang tot locatie verleend - Toegang tot locatie geweigerd - Toegang tot locatie op de achtergrond - Onbeperkt toestaan - %s]]> - %s]]> - %sgebruikt het locatie recht alleen om de huidige WiFi-SSID voor SSID-beperkte accounts te bepalen. Dit gebeurt zelfs als de app zich op de achtergrond bevindt. Locatiegegevens worden niet verzameld, opgeslagen, verwerkt of verzonden. - Toegang tot locatie altijd ingeschakeld - Toegang tot locatie is ingeschakeld - Toegang tot locatie is uitgeschakeld - - Vertalingen - © Ricki Hirner, Bernhard Stockmann (bitfire web engineering GmbH) en bijdragers - Dank aan: %s]]> - - Bekijken/delen - Uitschakelen - - Gereedschap - Community - Meldingen uitgeschakeld. U krijgt geen meldingen over synchronisatiefouten. - Verbindingen beheren - Er is te weinig opslagruimte. Android zal niet synchroniseren. - Opslag beheren - Gegevensbesparing ingeschakeld. Synchronisatie op de achtergrond is beperkt. - Beheer van gegevensbesparing - Alle accounts synchroniseren - - Service herkenning is mislukt - De collectielijst is niet bijgewerkt - - Kan niet op de voorgrond draaien - Toestemming tot onbeperkt batterijgebruik is vereist - - Draait op de voorgrond - Op sommige toestellen is dit nodig voor automatische synchronisatie. - - Batterijoptimalisatie - Onbeperkt batterijgebruik toestaan (aanbevolen) - Beperkt batterijgebruik toestaan (niet aanbevolen) - Op de voorgrond houden - Kan helpen als automatische synchronisatie niet plaatsvindt - Proxy type - - Systeem standaard - Geen proxy - HTTP - SOCKS (voor Orbot) - - Proxy host naam - Proxy poort - App rechten - De vereiste rechten om te synchroniseren controleren - Thema selecteren - - Systeem standaard - Licht - Donker - - Integratie - Taken app - Synchroniseren met %s - Geen compatibele taken app gevonden - - Geen contacten synchronisatie (ontbrekende rechten) - Geen kalender synchronisatie (ontbrekende rechten) - Geen taken synchronisatie (ontbrekende rechten) - Geen synchronisatie van kalender en taken (ontbrekende rechten) - Geen toegang tot kalenders (ontbrekende rechten) - Rechten - Accountnaam is al in gebruik - Naam account is niet gewijzigd - logboek - Alleen persoonlijk tonen - - Het gebruik van apostrofs (\') heeft op sommige apparaten problemen veroorzaakt. - Geavanceerde login (speciale gevallen) - Gebruikersnaam/wachtwoord gebruiken - Cliëntcertificaat gebruiken - Geen certificaat gevonden - Certificaat installeren - Google Contacten / Kalender - Google account - Is gebruikersnaam (e-mailadres) / wachtwoord verkeerd? - - Beperking WiFi-SSID vereist verdere instellingen - Beheren - Geen certificaat geselecteerd - - Groepen zijn afzonderlijke vCards - Groepen zijn categorieën per contact - - - Opslaglocatie is verplicht - Laatst gesynchroniseerd: - Nooit gesynchroniseerd: - Eigenaar: - - ZIP archief - Bevat debuginformatie en logbestanden - Deel het archief om over te zetten naar een computer, per e-mail te verzenden of als bijlage bij een supportticket te voegen.. - Archief delen - Debug info als bijlage bij dit bericht (vereist ondersteuning voor bijlagen van de ontvangende app). - HTTP-fout - Serverfout - WebDAV fout - I/O-fout - Het verzoek is afgewezen. Controleer de betrokken bronnen en debug-info voor details. - De gevraagde bron bestaat niet (meer). Controleer de betrokken bronnen en debug-info voor details. - Er is bij de server een probleem opgetreden. Neem contact op met de server-ondersteuning. - Er is een onverwachte fout opgetreden. Bekijk debug-info voor details. - Details bekijken - Debug-info is verzameld - Betrokken bronnen - Gerelateerd aan het probleem - Externe bron: - Lokale bron: - Logboeken - Uitgebreide logboeken zijn beschikbaar - Details bekijken - - Synchroniseren is onderbroken - Bijna geen vrije ruimte meer - - WebDAV-koppelingen - Quotum gebruikt: %1$s / Beschikbaar: %2$s - Inhoud delen - Ontkoppelen - WebDAV-koppeling toevoegen - Verkrijg directe toegang tot cloudbestanden met een WebDAV-koppeling! - het koppelen van WebDAV.]]> - Weergavenaam - WebDAV-URL - Ongeldige URL - Gebruikersnaam - Wachtwoord - Koppeling toevoegen - Geen WebDAV-service op deze URL - Verwijder het koppelpunt - Verbindingsgegevens gaan verloren, maar er worden geen bestanden gewist. - WebDAV-bestand openen - WebDAV-bestand downloaden - WebDAV-bestand uploaden - WebDAV-koppeling - - %ste oud - Minimaal vereiste versie: %1$s - Soft error (max. aantal pogingen bereikt) + Accounts beheren + Geen internet, planning de sync + Om op gezette tijden te synchroniseren moet %s zonder beperking op de achtergrond kunnen draaien. Anders kan Android het synchroniseren op elk moment onderbreken. + Synchroniseren op gezette tijden is niet nodig.* + %s compatibiliteit + Waarschijnlijk blokkeert dit toestel het synchroniseren. In dat geval is dit alleen handmatig op te lossen. + De vereiste instellingen zijn verricht. Er aan herinneren is niet meer nodig.* + * Niet aanvinken om later herinnerd te worden. Kan teruggezet in app instellingen / %s. + Meer informatie + jtx Board + + Ondersteunt taken + Als de server taken ondersteunt, synchroniseert een geschikte taken-app ze: + OpenTasks + Schijnt niet meer ontwikkeld te worden - niet aanbevolen. + Tasks.org + worden (nog) niet ondersteund.]]> + Geen app-store beschikbaar + Ik hoef geen ondersteuning van taken.* + Open-source software + We zijn blij dat de keuze valt op open source software %s. Ontwikkelen, onderhouden en ondersteunen is veel werk. Overweeg daarom bij te dragen (kan op vele manieren) of een donatie. Wij waarderen het zeer! + Hoe bijdragen/doneren + In de nabije toekomst niet weergeven + + Rechten toestaan + %s heeft rechten nodig om goed te werken. + Alle onderstaande + Gebruik dit om alle functies in te schakelen (aanbevolen) + Alle rechten toegekend + Contacten toestaan + Geen contacten synchroniseren (niet aanbevolen) + Contacten synchroniseren mogelijk + Kalender machtigingen + Geen kalenders synchroniseren (niet aanbevolen) + Kalenders synchroniseren mogelijk + Toestemming voor meldingen + Meldingen uitgeschakeld (niet aanbevolen) + Meldingen ingeschakeld + jtx Board-rechten + Geen taak-, kalender- en notitiesync (niet geïnstalleerd) + Geen taak-, kalender- en notitiesync + Taak-, kalender- en notitiesync mogelijk + OpenTasks rechten + Rechten voor taken + Geen taak-sync (niet geïnstalleerd) + Geen taak-sync + Taak-sync mogelijk + Rechten behouden + Rechten kunnen automatisch worden teruggezet (niet aanbevolen) + Rechten worden niet automatisch teruggezet + Klik op App Rechten > vinkje uit bij \"Rechten intrekken\" + Als een schakeloptie niet werkt, gebruik dan App-info / Rechten. + App instellingen + + WiFi SSID rechten + Voor toegang tot de huidige WiFi-naam (SSID), moet aan deze voorwaarden worden voldaan: + Recht van toegang tot exacte locatie + Toegang tot locatie verleend + Toegang tot locatie geweigerd + Toegang tot locatie op de achtergrond + Onbeperkt toestaan + %s]]> + %s]]> + %sgebruikt het locatie recht alleen om de huidige WiFi-SSID voor SSID-beperkte accounts te bepalen. Dit gebeurt zelfs als de app zich op de achtergrond bevindt. Locatiegegevens worden niet verzameld, opgeslagen, verwerkt of verzonden. + Toegang tot locatie altijd ingeschakeld + Toegang tot locatie is ingeschakeld + Toegang tot locatie is uitgeschakeld + + Vertalingen + © Ricki Hirner, Bernhard Stockmann (bitfire web engineering GmbH) en bijdragers + Dank aan: %s]]> + + Bekijken/delen + Uitschakelen + + Gereedschap + Community + Meldingen uitgeschakeld. U krijgt geen meldingen over synchronisatiefouten. + Verbindingen beheren + Er is te weinig opslagruimte. Android zal niet synchroniseren. + Opslag beheren + Gegevensbesparing ingeschakeld. Synchronisatie op de achtergrond is beperkt. + Beheer van gegevensbesparing + Alle accounts synchroniseren + + Service herkenning is mislukt + De collectielijst is niet bijgewerkt + + Kan niet op de voorgrond draaien + Toestemming tot onbeperkt batterijgebruik is vereist + + Draait op de voorgrond + Op sommige toestellen is dit nodig voor automatische synchronisatie. + + Batterijoptimalisatie + Onbeperkt batterijgebruik toestaan (aanbevolen) + Beperkt batterijgebruik toestaan (niet aanbevolen) + Op de voorgrond houden + Kan helpen als automatische synchronisatie niet plaatsvindt + Proxy type + + Systeem standaard + Geen proxy + HTTP + SOCKS (voor Orbot) + + Proxy host naam + Proxy poort + App rechten + De vereiste rechten om te synchroniseren controleren + Thema selecteren + + Systeem standaard + Licht + Donker + + Integratie + Taken app + Synchroniseren met %s + Geen compatibele taken app gevonden + + Geen contacten synchronisatie (ontbrekende rechten) + Geen kalender synchronisatie (ontbrekende rechten) + Geen taken synchronisatie (ontbrekende rechten) + Geen synchronisatie van kalender en taken (ontbrekende rechten) + Geen toegang tot kalenders (ontbrekende rechten) + Rechten + Accountnaam is al in gebruik + Naam account is niet gewijzigd + logboek + Alleen persoonlijk tonen + + Het gebruik van apostrofs (\') heeft op sommige apparaten problemen veroorzaakt. + Geavanceerde login (speciale gevallen) + Gebruikersnaam/wachtwoord gebruiken + Cliëntcertificaat gebruiken + Geen certificaat gevonden + Certificaat installeren + Google Contacten / Kalender + Raadpleeg onze pagina \"Getest met Google\" voor actuele informatie. + Het kan zijn dat je onverwachte waarschuwingen krijgt en/of je eigen client-ID moet aanmaken. + Google account + Inloggen met Google + Client ID (optioneel) + Privacybeleid voor meer informatie.]]> + beleid voor gebruikersgegevens van Google API Services, met inbegrip van de vereisten voor beperkt gebruik.]]> + Kon geen autorisatiecode verkrijgen + Is gebruikersnaam (e-mailadres) / wachtwoord verkeerd? + + Beperking WiFi-SSID vereist verdere instellingen + Beheren + Opnieuw authenticeren + Voer OAuth-aanmelding opnieuw uit + Gebruikersnaam + Geen certificaat geselecteerd + + Groepen zijn afzonderlijke vCards + Groepen zijn categorieën per contact + + + Opslaglocatie is verplicht + Laatst gesynchroniseerd: + Nooit gesynchroniseerd: + Eigenaar: + + ZIP archief + Bevat debuginformatie en logbestanden + Deel het archief om over te zetten naar een computer, per e-mail te verzenden of als bijlage bij een supportticket te voegen.. + Archief delen + Debug info als bijlage bij dit bericht (vereist ondersteuning voor bijlagen van de ontvangende app). + HTTP-fout + Serverfout + WebDAV fout + I/O-fout + Het verzoek is afgewezen. Controleer de betrokken bronnen en debug-info voor details. + De gevraagde bron bestaat niet (meer). Controleer de betrokken bronnen en debug-info voor details. + Er is bij de server een probleem opgetreden. Neem contact op met de server-ondersteuning. + Er is een onverwachte fout opgetreden. Bekijk debug-info voor details. + Details bekijken + Debug-info is verzameld + Betrokken bronnen + Gerelateerd aan het probleem + Externe bron: + Lokale bron: + Logboeken + Uitgebreide logboeken zijn beschikbaar + Details bekijken + + Synchroniseren is onderbroken + Bijna geen vrije ruimte meer + + WebDAV-koppelingen + Quotum gebruikt: %1$s / Beschikbaar: %2$s + Inhoud delen + Ontkoppelen + WebDAV-koppeling toevoegen + Verkrijg directe toegang tot cloudbestanden met een WebDAV-koppeling! + het koppelen van WebDAV.]]> + Weergavenaam + WebDAV-URL + Ongeldige URL + Gebruikersnaam + Wachtwoord + Koppeling toevoegen + Geen WebDAV-service op deze URL + Verwijder het koppelpunt + Verbindingsgegevens gaan verloren, maar er worden geen bestanden gewist. + WebDAV-bestand openen + WebDAV-bestand downloaden + WebDAV-bestand uploaden + WebDAV-koppeling + + %ste oud + Minimaal vereiste versie: %1$s + Soft error (max. aantal pogingen bereikt) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 7e39bd22549e7ab024c6b9afad247e6cea07d8fd..c3933ba50df4ee3f1ca7f725d330245a1c7a84c9 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -209,7 +209,6 @@ Nie ma (jeszcze) subskrypcji kalendarzy. Przesuń palcem w dół, aby odświeżyć listę z serwera. Synchronizuj teraz - Synchronizuj kolekcje Ustawienia konta Zmień nazwę konta Niezapisane dane lokalne mogą zostać usunięte. Po zmianie nazwy jest wymagana ponowna synchronizacja. Nowa nazwa konta: diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 8380c5851ad9b58210e0d838977c615e75670feb..fd9de6a93b2c969e522c49809b992ce5a5ed3803 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -382,121 +382,121 @@ © Рикки Хирнер, Бернхард Стокманн (bitfire web engineering GmbH) и авторы статьи Переводы Служба определения местоположения отключена - Управление аккаунтами - Нет интернета, синхронизация по расписанию - Может помочь, если ваше устройство препятствует автоматической синхронизации - Тип прокси - - Определен системой - Без прокси - HTTP - SOCKS (для Orbot) - - Имя хоста прокси - Порт прокси - Разрешения приложения - Проверка разрешений, необходимых для синхронизации - Выбор темы - - Определена системой - Светлая - Темная - - Интеграция - Приложение Tasks - Синхронизация с %s - Не найдено совместимое приложение для задач - - Контакты не синхронизируются (отсутствуют разрешения) - Календарь не синхронизируется (отсутствуют разрешения) - Задачи не синхронизируются (отсутствуют разрешения) - Календарь и задачи не синхронизируются (отсутствуют разрешения) - Нет доступа к календарям (отсутствуют разрешения) - Разрешения - Синхронизировать коллекции - Название аккаунта уже используется - Не удалось переименовать аккаунт - журнал - Показать только личные - - По имеющимся данным, использование апострофов (\') вызывает проблемы на некоторых устройствах. - Расширенная авторизация (особые случаи использования) - Использовать имя пользователя/пароль - Использовать сертификат клиента - Сертификат не найден - Установить сертификат - Google Контакты / Календарь - Актуальную информацию смотрите на нашей странице \" Протестировано с Google\". - Вы можете столкнуться с неожиданными предупреждениями и/или вам придется создать свой собственный ID клиента. - Google аккаунт - Войти через Google - ID клиента (необязательно) - Вы можете использовать свой собственный ID клиента, если наш не работает. - Покажите мне как! - Политику конфиденциальности.]]> - Политику в отношении пользовательских данных Google API Services, включая требования Ограниченного использования.]]> - Не удалось получить код авторизации - Имя пользователя (адрес email) / пароль неверны? - - Ограничение WiFi SSID требует дополнительных настроек - Управлять - Повторная аутентификация - Выполните вход через OAuth повторно - имя пользователя - Сертификат не выбран - Место хранения обязательно - Последняя синхронизация: - Никогда не синхронизировалось - Владелец: - - ZIP-архив - Содержит отладочную информацию и логи - Поделитесь архивом, чтобы перенести его на компьютер, отправить по электронной почте или прикрепить к запросу в службу поддержки. - Поделиться архивом - Отладочная информация, прикреплена к данному сообщению (требует поддержки вложений со стороны принимающего приложения). - Ошибка HTTP - Ошибка сервера - Ошибка WebDAV - Ошибка ввода/вывода - Запрос был отклонен. Для получения подробной информации проверьте задействованные ресурсы и отладочную информацию. - Запрошенного ресурса не существует (больше не существует). Проверьте задействованные ресурсы и отладочную информацию для получения подробной информации. - Возникла проблема на стороне сервера. Пожалуйста, свяжитесь со службой поддержки вашего сервера. - Произошла неожиданная ошибка. Просмотрите отладочную информацию для получения подробностей. - Просмотреть информацию - Собрана отладочная информация - Вовлеченные ресурсы - Связанная с этим проблема - Удаленный ресурс: - Локальный ресурс: - Логи - Доступны подробные логи - Просмотр логов - - Синхронизация приостановлена - Почти не осталось свободного места - - Точки монтирования WebDAV - Использованная квота: %1$s / доступно: %2$s - Поделиться контентом - Отмонтировать - Добавление точки монтирования WebDAV - Прямой доступ к вашим облачным файлам с помощью точки монтирования WebDAV! - как работают точки монтирования WebDAV.]]> - Отображаемое имя - WebDAV URL - Некорректный URL - Имя пользователя - Пароль - Добавить точку монтирования - Служба WebDAV отсутствует на данном URL - Удалить точку монтирования - Информация о подключении будет потеряна, но никакие файлы не будут удалены. - Доступ к файлу WebDAV - Загрузка файла WebDAV - Выгрузка файла WebDAV - Точка монтирования WebDAV - - Приложение %s устарело - Минимально необходимая версия: %1$s - Ошибка (достигнуто максимальное количество повторных попыток) + Управление аккаунтами + Нет интернета, синхронизация по расписанию + Может помочь, если ваше устройство препятствует автоматической синхронизации + Тип прокси + + Определен системой + Без прокси + HTTP + SOCKS (для Orbot) + + Имя хоста прокси + Порт прокси + Разрешения приложения + Проверка разрешений, необходимых для синхронизации + Выбор темы + + Определена системой + Светлая + Темная + + Интеграция + Приложение Tasks + Синхронизация с %s + Не найдено совместимое приложение для задач + + Контакты не синхронизируются (отсутствуют разрешения) + Календарь не синхронизируется (отсутствуют разрешения) + Задачи не синхронизируются (отсутствуют разрешения) + Календарь и задачи не синхронизируются (отсутствуют разрешения) + Нет доступа к календарям (отсутствуют разрешения) + Разрешения + Синхронизировать коллекции + Название аккаунта уже используется + Не удалось переименовать аккаунт + журнал + Показать только личные + + По имеющимся данным, использование апострофов (\') вызывает проблемы на некоторых устройствах. + Расширенная авторизация (особые случаи использования) + Использовать имя пользователя/пароль + Использовать сертификат клиента + Сертификат не найден + Установить сертификат + Google Контакты / Календарь + Актуальную информацию смотрите на нашей странице \"Протестировано с Google\". + Вы можете столкнуться с неожиданными предупреждениями и/или вам придется создать свой собственный ID клиента. + Google аккаунт + Войти через Google + ID клиента (необязательно) + Вы можете использовать свой собственный ID клиента, если наш не работает. + Покажите мне как! + Политику конфиденциальности.]]> + Политику в отношении пользовательских данных Google API Services, включая требования Ограниченного использования.]]> + Не удалось получить код авторизации + Имя пользователя (адрес email) / пароль неверны? + + Ограничение WiFi SSID требует дополнительных настроек + Управлять + Повторная аутентификация + Выполните вход через OAuth повторно + имя пользователя + Сертификат не выбран + Место хранения обязательно + Последняя синхронизация: + Никогда не синхронизировалось + Владелец: + + ZIP-архив + Содержит отладочную информацию и логи + Поделитесь архивом, чтобы перенести его на компьютер, отправить по электронной почте или прикрепить к запросу в службу поддержки. + Поделиться архивом + Отладочная информация, прикреплена к данному сообщению (требует поддержки вложений со стороны принимающего приложения). + Ошибка HTTP + Ошибка сервера + Ошибка WebDAV + Ошибка ввода/вывода + Запрос был отклонен. Для получения подробной информации проверьте задействованные ресурсы и отладочную информацию. + Запрошенного ресурса не существует (больше не существует). Проверьте задействованные ресурсы и отладочную информацию для получения подробной информации. + Возникла проблема на стороне сервера. Пожалуйста, свяжитесь со службой поддержки вашего сервера. + Произошла неожиданная ошибка. Просмотрите отладочную информацию для получения подробностей. + Просмотреть информацию + Собрана отладочная информация + Вовлеченные ресурсы + Связанная с этим проблема + Удаленный ресурс: + Локальный ресурс: + Логи + Доступны подробные логи + Просмотр логов + + Синхронизация приостановлена + Почти не осталось свободного места + + Точки монтирования WebDAV + Использованная квота: %1$s / доступно: %2$s + Поделиться контентом + Отмонтировать + Добавление точки монтирования WebDAV + Прямой доступ к вашим облачным файлам с помощью точки монтирования WebDAV! + как работают точки монтирования WebDAV.]]> + Отображаемое имя + WebDAV URL + Некорректный URL + Имя пользователя + Пароль + Добавить точку монтирования + Служба WebDAV отсутствует на данном URL + Удалить точку монтирования + Информация о подключении будет потеряна, но никакие файлы не будут удалены. + Доступ к файлу WebDAV + Загрузка файла WebDAV + Выгрузка файла WebDAV + Точка монтирования WebDAV + + Приложение %s устарело + Минимально необходимая версия: %1$s + Ошибка (достигнуто максимальное количество повторных попыток) diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index ddedd6115e5d68d62913817db38f82019a574755..aaf904d813ba4785e256a6cd9150f48b56feb82d 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -209,7 +209,6 @@ 暂无日历订阅。 下拉可从服务器获取最新列表。 立即同步 - 同步收藏 账户设置 重命名账户 重命名后,未上传的本地修改会被撤销,您需要重新执行同步。新账户名: diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 45cbd6c6c8a19235257aafe8f08a3ef86ff6b63b..7e158e2bd131f74803c78b6abca53ad75383dc32 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -261,7 +261,6 @@ There are no calendar subscriptions (yet). Swipe down to refresh the list from the server. Synchronize now - Synchronize collections Account settings Rename account Unsaved local data may be dismissed. Re-synchronization is required after renaming. New account name: @@ -383,6 +382,10 @@ Comma-separated names (SSIDs) of allowed WiFi networks (leave blank for all) WiFi SSID restriction requires further settings Manage + ignore_vpns + VPN connectivity + Non-VPN connection required (recommended) + VPN counts as Internet connection Authentication oauth Re-authenticate diff --git a/app/src/main/res/xml/settings_account.xml b/app/src/main/res/xml/settings_account.xml index 9e3e54962ceab47670d3264fb165b4313e529600..97ea221a39e473d797ed3bf629de63fca02e4397 100644 --- a/app/src/main/res/xml/settings_account.xml +++ b/app/src/main/res/xml/settings_account.xml @@ -43,6 +43,13 @@ android:title="@string/settings_sync_wifi_only_ssids" android:dialogMessage="@string/settings_sync_wifi_only_ssids_message"/> + +