mirror of
https://github.com/bitfireAT/davx5-ose
synced 2024-07-23 11:39:15 +00:00
Refactor DavService to WorkManager (bitfireAT/#164)
* SyncManager: remove retry intent * refactor DavService to RefreshCollectionsWorker now using WorkManager * move DavResourceFinder to new service detection package * Optimize imports Co-authored-by: Ricki Hirner <hirner@bitfire.at>
This commit is contained in:
parent
3f2fcda6d3
commit
02885947da
|
@ -113,15 +113,19 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.5.1'
|
||||
implementation 'androidx.browser:browser:1.4.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.9.0'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.5.4'
|
||||
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.5.1'
|
||||
implementation 'androidx.paging:paging-runtime-ktx:3.1.1'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||
implementation 'androidx.security:security-crypto:1.1.0-alpha04'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'androidx.work:work-runtime-ktx:2.7.1'
|
||||
implementation 'com.google.android.flexbox:flexbox:3.0.0'
|
||||
implementation 'com.google.android.material:material:1.7.0'
|
||||
|
||||
|
@ -160,6 +164,7 @@ dependencies {
|
|||
androidTestImplementation 'androidx.test:runner:1.5.1'
|
||||
androidTestImplementation 'androidx.test:rules:1.5.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.4'
|
||||
androidTestImplementation 'androidx.work:work-testing:2.7.1'
|
||||
androidTestImplementation "com.squareup.okhttp3:mockwebserver:${versions.okhttp}"
|
||||
androidTestImplementation 'io.mockk:mockk-android:1.13.2'
|
||||
androidTestImplementation 'junit:junit:4.13.2'
|
||||
|
|
|
@ -13,8 +13,9 @@ import at.bitfire.dav4jvm.property.ResourceType
|
|||
import at.bitfire.davdroid.HttpClient
|
||||
import at.bitfire.davdroid.db.Credentials
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.servicedetection.DavResourceFinder
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import at.bitfire.davdroid.ui.setup.DavResourceFinder.Configuration.ServiceInfo
|
||||
import at.bitfire.davdroid.servicedetection.DavResourceFinder.Configuration.ServiceInfo
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import okhttp3.mockwebserver.Dispatcher
|
||||
|
|
|
@ -50,7 +50,13 @@
|
|||
tools:ignore="UnusedAttribute"
|
||||
android:supportsRtl="true">
|
||||
|
||||
<service android:name=".DavService"/>
|
||||
<!-- required for Hilt/WorkManager integration -->
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
tools:node="remove">
|
||||
</provider>
|
||||
|
||||
<service android:name=".ForegroundService"/>
|
||||
|
||||
<activity android:name=".ui.intro.IntroActivity" android:theme="@style/AppTheme.NoActionBar" />
|
||||
|
|
|
@ -10,6 +10,8 @@ import android.net.Uri
|
|||
import android.os.StrictMode
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.work.Configuration
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import at.bitfire.davdroid.syncadapter.AccountsUpdatedListener
|
||||
|
@ -24,7 +26,7 @@ import kotlin.concurrent.thread
|
|||
import kotlin.system.exitProcess
|
||||
|
||||
@HiltAndroidApp
|
||||
class App: Application(), Thread.UncaughtExceptionHandler {
|
||||
class App: Application(), Thread.UncaughtExceptionHandler, Configuration.Provider {
|
||||
|
||||
companion object {
|
||||
|
||||
|
@ -42,7 +44,7 @@ class App: Application(), Thread.UncaughtExceptionHandler {
|
|||
|
||||
@Inject lateinit var accountsUpdatedListener: AccountsUpdatedListener
|
||||
@Inject lateinit var storageLowReceiver: StorageLowReceiver
|
||||
|
||||
@Inject lateinit var workerFactory: HiltWorkerFactory
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
@ -92,6 +94,11 @@ class App: Application(), Thread.UncaughtExceptionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
override fun getWorkManagerConfiguration() =
|
||||
Configuration.Builder()
|
||||
.setWorkerFactory(workerFactory)
|
||||
.build()
|
||||
|
||||
override fun uncaughtException(t: Thread, e: Throwable) {
|
||||
Logger.log.log(Level.SEVERE, "Unhandled exception!", e)
|
||||
|
||||
|
|
|
@ -1,431 +0,0 @@
|
|||
/***************************************************************************************************
|
||||
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
||||
**************************************************************************************************/
|
||||
|
||||
package at.bitfire.davdroid
|
||||
|
||||
import android.accounts.Account
|
||||
import android.app.IntentService
|
||||
import android.app.PendingIntent
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import at.bitfire.dav4jvm.DavResource
|
||||
import at.bitfire.dav4jvm.Response
|
||||
import at.bitfire.dav4jvm.UrlUtils
|
||||
import at.bitfire.dav4jvm.exception.HttpException
|
||||
import at.bitfire.dav4jvm.property.*
|
||||
import at.bitfire.davdroid.db.*
|
||||
import at.bitfire.davdroid.db.Collection
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import at.bitfire.davdroid.settings.Settings
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import at.bitfire.davdroid.ui.DebugInfoActivity
|
||||
import at.bitfire.davdroid.ui.NotificationUtils
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
import java.util.logging.Level
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.*
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@AndroidEntryPoint
|
||||
class DavService: IntentService("DavService") {
|
||||
|
||||
companion object {
|
||||
|
||||
const val ACTION_REFRESH_COLLECTIONS = "refreshCollections"
|
||||
const val EXTRA_DAV_SERVICE_ID = "davServiceID"
|
||||
|
||||
/** Initialize a forced synchronization. Expects intent data
|
||||
to be an URI of this format:
|
||||
contents://<authority>/<account.type>/<account name>
|
||||
**/
|
||||
const val ACTION_FORCE_SYNC = "forceSync"
|
||||
|
||||
val DAV_COLLECTION_PROPERTIES = arrayOf(
|
||||
ResourceType.NAME,
|
||||
CurrentUserPrivilegeSet.NAME,
|
||||
DisplayName.NAME,
|
||||
Owner.NAME,
|
||||
AddressbookDescription.NAME, SupportedAddressData.NAME,
|
||||
CalendarDescription.NAME, CalendarColor.NAME, SupportedCalendarComponentSet.NAME,
|
||||
Source.NAME
|
||||
)
|
||||
|
||||
fun refreshCollections(context: Context, serviceId: Long) {
|
||||
val intent = Intent(context, DavService::class.java)
|
||||
intent.action = DavService.ACTION_REFRESH_COLLECTIONS
|
||||
intent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, serviceId)
|
||||
context.startService(intent)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Inject lateinit var db: AppDatabase
|
||||
@Inject lateinit var settings: SettingsManager
|
||||
|
||||
/**
|
||||
* List of [Service] IDs for which the collections are currently refreshed
|
||||
*/
|
||||
private val runningRefresh = Collections.synchronizedSet(HashSet<Long>())
|
||||
|
||||
/**
|
||||
* Currently registered [RefreshingStatusListener]s, which will be notified
|
||||
* when a collection refresh status changes
|
||||
*/
|
||||
private val refreshingStatusListeners = Collections.synchronizedList(LinkedList<WeakReference<RefreshingStatusListener>>())
|
||||
|
||||
@WorkerThread
|
||||
override fun onHandleIntent(intent: Intent?) {
|
||||
if (intent == null)
|
||||
return
|
||||
|
||||
val id = intent.getLongExtra(EXTRA_DAV_SERVICE_ID, -1)
|
||||
|
||||
when (intent.action) {
|
||||
ACTION_REFRESH_COLLECTIONS ->
|
||||
if (runningRefresh.add(id)) {
|
||||
refreshingStatusListeners.forEach { listener ->
|
||||
listener.get()?.onDavRefreshStatusChanged(id, true)
|
||||
}
|
||||
|
||||
refreshCollections(id)
|
||||
}
|
||||
|
||||
ACTION_FORCE_SYNC -> {
|
||||
val uri = intent.data!!
|
||||
val authority = uri.authority!!
|
||||
val account = Account(
|
||||
uri.pathSegments[1],
|
||||
uri.pathSegments[0]
|
||||
)
|
||||
forceSync(authority, account)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* BOUND SERVICE PART
|
||||
for communicating with the activities
|
||||
*/
|
||||
|
||||
interface RefreshingStatusListener {
|
||||
fun onDavRefreshStatusChanged(id: Long, refreshing: Boolean)
|
||||
}
|
||||
|
||||
private val binder = InfoBinder()
|
||||
|
||||
inner class InfoBinder: Binder() {
|
||||
fun isRefreshing(id: Long) = runningRefresh.contains(id)
|
||||
|
||||
fun addRefreshingStatusListener(listener: RefreshingStatusListener, callImmediateIfRunning: Boolean) {
|
||||
refreshingStatusListeners += WeakReference<RefreshingStatusListener>(listener)
|
||||
if (callImmediateIfRunning)
|
||||
synchronized(runningRefresh) {
|
||||
for (id in runningRefresh)
|
||||
listener.onDavRefreshStatusChanged(id, true)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeRefreshingStatusListener(listener: RefreshingStatusListener) {
|
||||
val iter = refreshingStatusListeners.iterator()
|
||||
while (iter.hasNext()) {
|
||||
val item = iter.next().get()
|
||||
if (item == listener || item == null)
|
||||
iter.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?) = binder
|
||||
|
||||
|
||||
|
||||
/* ACTION RUNNABLES
|
||||
which actually do the work
|
||||
*/
|
||||
|
||||
private fun forceSync(authority: String, account: Account) {
|
||||
Logger.log.info("Forcing $authority synchronization of $account")
|
||||
val extras = Bundle(2)
|
||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true) // manual sync
|
||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true) // run immediately (don't queue)
|
||||
ContentResolver.requestSync(account, authority, extras)
|
||||
}
|
||||
|
||||
private fun refreshCollections(serviceId: Long) {
|
||||
val syncAllCollections = settings.getBoolean(Settings.SYNC_ALL_COLLECTIONS)
|
||||
|
||||
val homeSetDao = db.homeSetDao()
|
||||
val collectionDao = db.collectionDao()
|
||||
|
||||
val service = db.serviceDao().get(serviceId) ?: throw IllegalArgumentException("Service not found")
|
||||
val account = Account(service.accountName, getString(R.string.account_type))
|
||||
|
||||
val homeSets = homeSetDao.getByService(serviceId).associateBy { it.url }.toMutableMap()
|
||||
val collections = collectionDao.getByService(serviceId).associateBy { it.url }.toMutableMap()
|
||||
|
||||
/**
|
||||
* Checks if the given URL defines home sets and adds them to the home set list.
|
||||
*
|
||||
* @param personal Whether this is the "outer" call of the recursion.
|
||||
*
|
||||
* *true* = found home sets belong to the current-user-principal; recurse if
|
||||
* calendar proxies or group memberships are found
|
||||
*
|
||||
* *false* = found home sets don't directly belong to the current-user-principal; don't recurse
|
||||
*
|
||||
* @throws java.io.IOException
|
||||
* @throws HttpException
|
||||
* @throws at.bitfire.dav4jvm.exception.DavException
|
||||
*/
|
||||
fun queryHomeSets(client: OkHttpClient, url: HttpUrl, personal: Boolean = true) {
|
||||
val related = mutableSetOf<HttpUrl>()
|
||||
|
||||
fun findRelated(root: HttpUrl, dav: Response) {
|
||||
// refresh home sets: calendar-proxy-read/write-for
|
||||
dav[CalendarProxyReadFor::class.java]?.let {
|
||||
for (href in it.hrefs) {
|
||||
Logger.log.fine("Principal is a read-only proxy for $href, checking for home sets")
|
||||
root.resolve(href)?.let { proxyReadFor ->
|
||||
related += proxyReadFor
|
||||
}
|
||||
}
|
||||
}
|
||||
dav[CalendarProxyWriteFor::class.java]?.let {
|
||||
for (href in it.hrefs) {
|
||||
Logger.log.fine("Principal is a read/write proxy for $href, checking for home sets")
|
||||
root.resolve(href)?.let { proxyWriteFor ->
|
||||
related += proxyWriteFor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// refresh home sets: direct group memberships
|
||||
dav[GroupMembership::class.java]?.let {
|
||||
for (href in it.hrefs) {
|
||||
Logger.log.fine("Principal is member of group $href, checking for home sets")
|
||||
root.resolve(href)?.let { groupMembership ->
|
||||
related += groupMembership
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dav = DavResource(client, url)
|
||||
when (service.type) {
|
||||
Service.TYPE_CARDDAV ->
|
||||
try {
|
||||
dav.propfind(0, DisplayName.NAME, AddressbookHomeSet.NAME, GroupMembership.NAME) { response, _ ->
|
||||
response[AddressbookHomeSet::class.java]?.let { homeSet ->
|
||||
for (href in homeSet.hrefs)
|
||||
dav.location.resolve(href)?.let {
|
||||
val foundUrl = UrlUtils.withTrailingSlash(it)
|
||||
homeSets[foundUrl] = HomeSet(0, service.id, personal, foundUrl)
|
||||
}
|
||||
}
|
||||
|
||||
if (personal)
|
||||
findRelated(dav.location, response)
|
||||
}
|
||||
} catch (e: HttpException) {
|
||||
if (e.code/100 == 4)
|
||||
Logger.log.log(Level.INFO, "Ignoring Client Error 4xx while looking for addressbook home sets", e)
|
||||
else
|
||||
throw e
|
||||
}
|
||||
Service.TYPE_CALDAV -> {
|
||||
try {
|
||||
dav.propfind(0, DisplayName.NAME, CalendarHomeSet.NAME, CalendarProxyReadFor.NAME, CalendarProxyWriteFor.NAME, GroupMembership.NAME) { response, _ ->
|
||||
response[CalendarHomeSet::class.java]?.let { homeSet ->
|
||||
for (href in homeSet.hrefs)
|
||||
dav.location.resolve(href)?.let {
|
||||
val foundUrl = UrlUtils.withTrailingSlash(it)
|
||||
homeSets[foundUrl] = HomeSet(0, service.id, personal, foundUrl)
|
||||
}
|
||||
}
|
||||
|
||||
if (personal)
|
||||
findRelated(dav.location, response)
|
||||
}
|
||||
} catch (e: HttpException) {
|
||||
if (e.code/100 == 4)
|
||||
Logger.log.log(Level.INFO, "Ignoring Client Error 4xx while looking for calendar home sets", e)
|
||||
else
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// query related homesets (those that do not belong to the current-user-principal)
|
||||
for (resource in related)
|
||||
queryHomeSets(client, resource, false)
|
||||
}
|
||||
|
||||
fun saveHomesets() {
|
||||
// syncAll sets the ID of the new homeset to the ID of the old one when the URLs are matching
|
||||
DaoTools(homeSetDao).syncAll(
|
||||
homeSetDao.getByService(serviceId),
|
||||
homeSets,
|
||||
{ it.url })
|
||||
}
|
||||
|
||||
fun saveCollections() {
|
||||
// syncAll sets the ID of the new collection to the ID of the old one when the URLs are matching
|
||||
DaoTools(collectionDao).syncAll(
|
||||
collectionDao.getByService(serviceId),
|
||||
collections, { it.url }) { new, old ->
|
||||
// use old settings of "force read only" and "sync", regardless of detection results
|
||||
new.forceReadOnly = old.forceReadOnly
|
||||
new.sync = old.sync
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Logger.log.info("Refreshing ${service.type} collections of service #$service")
|
||||
|
||||
// cancel previous notification
|
||||
NotificationManagerCompat.from(this)
|
||||
.cancel(serviceId.toString(), NotificationUtils.NOTIFY_REFRESH_COLLECTIONS)
|
||||
|
||||
// create authenticating OkHttpClient (credentials taken from account settings)
|
||||
HttpClient.Builder(this, AccountSettings(this, account))
|
||||
.setForeground(true)
|
||||
.build().use { client ->
|
||||
val httpClient = client.okHttpClient
|
||||
|
||||
// refresh home set list (from principal)
|
||||
service.principal?.let { principalUrl ->
|
||||
Logger.log.fine("Querying principal $principalUrl for home sets")
|
||||
queryHomeSets(httpClient, principalUrl)
|
||||
}
|
||||
|
||||
// now refresh homesets and their member collections
|
||||
val itHomeSets = homeSets.iterator()
|
||||
while (itHomeSets.hasNext()) {
|
||||
val (homeSetUrl, homeSet) = itHomeSets.next()
|
||||
Logger.log.fine("Listing home set $homeSetUrl")
|
||||
|
||||
try {
|
||||
DavResource(httpClient, homeSetUrl).propfind(1, *DAV_COLLECTION_PROPERTIES) { response, relation ->
|
||||
if (!response.isSuccess())
|
||||
return@propfind
|
||||
|
||||
if (relation == Response.HrefRelation.SELF) {
|
||||
// this response is about the homeset itself
|
||||
homeSet.displayName = response[DisplayName::class.java]?.displayName
|
||||
homeSet.privBind = response[CurrentUserPrivilegeSet::class.java]?.mayBind ?: true
|
||||
}
|
||||
|
||||
// in any case, check whether the response is about a useable collection
|
||||
val info = Collection.fromDavResponse(response) ?: return@propfind
|
||||
info.serviceId = serviceId
|
||||
info.refHomeSet = homeSet
|
||||
info.confirmed = true
|
||||
|
||||
// whether new collections are selected for synchronization by default (controlled by managed setting)
|
||||
info.sync = syncAllCollections
|
||||
|
||||
info.owner = response[Owner::class.java]?.href?.let { response.href.resolve(it) }
|
||||
Logger.log.log(Level.FINE, "Found collection", info)
|
||||
|
||||
// remember usable collections
|
||||
if ((service.type == Service.TYPE_CARDDAV && info.type == Collection.TYPE_ADDRESSBOOK) ||
|
||||
(service.type == Service.TYPE_CALDAV && arrayOf(Collection.TYPE_CALENDAR, Collection.TYPE_WEBCAL).contains(info.type)))
|
||||
collections[response.href] = info
|
||||
}
|
||||
} catch(e: HttpException) {
|
||||
if (e.code in arrayOf(403, 404, 410))
|
||||
// delete home set only if it was not accessible (40x)
|
||||
itHomeSets.remove()
|
||||
}
|
||||
}
|
||||
|
||||
// check/refresh unconfirmed collections
|
||||
val collectionsIter = collections.entries.iterator()
|
||||
while (collectionsIter.hasNext()) {
|
||||
val currentCollection = collectionsIter.next()
|
||||
val (url, info) = currentCollection
|
||||
if (!info.confirmed)
|
||||
try {
|
||||
// this collection doesn't belong to a homeset anymore, otherwise it would have been confirmed
|
||||
info.homeSetId = null
|
||||
|
||||
DavResource(httpClient, url).propfind(0, *DAV_COLLECTION_PROPERTIES) { response, _ ->
|
||||
if (!response.isSuccess())
|
||||
return@propfind
|
||||
|
||||
val collection = Collection.fromDavResponse(response) ?: return@propfind
|
||||
collection.serviceId = info.serviceId // use same service ID as previous entry
|
||||
collection.confirmed = true
|
||||
|
||||
// remove unusable collections
|
||||
if ((service.type == Service.TYPE_CARDDAV && collection.type != Collection.TYPE_ADDRESSBOOK) ||
|
||||
(service.type == Service.TYPE_CALDAV && !arrayOf(Collection.TYPE_CALENDAR, Collection.TYPE_WEBCAL).contains(collection.type)) ||
|
||||
(collection.type == Collection.TYPE_WEBCAL && collection.source == null))
|
||||
collectionsIter.remove()
|
||||
else
|
||||
// update this collection in list
|
||||
currentCollection.setValue(collection)
|
||||
}
|
||||
} catch(e: HttpException) {
|
||||
if (e.code in arrayOf(403, 404, 410))
|
||||
// delete collection only if it was not accessible (40x)
|
||||
collectionsIter.remove()
|
||||
else
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.runInTransaction {
|
||||
saveHomesets()
|
||||
|
||||
// use refHomeSet (if available) to determine homeset ID
|
||||
for (collection in collections.values)
|
||||
collection.refHomeSet?.let { homeSet ->
|
||||
collection.homeSetId = homeSet.id
|
||||
}
|
||||
saveCollections()
|
||||
}
|
||||
|
||||
} catch(e: InvalidAccountException) {
|
||||
Logger.log.log(Level.SEVERE, "Invalid account", e)
|
||||
} catch(e: Exception) {
|
||||
Logger.log.log(Level.SEVERE, "Couldn't refresh collection list", e)
|
||||
|
||||
val debugIntent = DebugInfoActivity.IntentBuilder(this)
|
||||
.withCause(e)
|
||||
.withAccount(account)
|
||||
.build()
|
||||
val notify = NotificationUtils.newBuilder(this, NotificationUtils.CHANNEL_GENERAL)
|
||||
.setSmallIcon(R.drawable.ic_sync_problem_notify)
|
||||
.setContentTitle(getString(R.string.dav_service_refresh_failed))
|
||||
.setContentText(getString(R.string.dav_service_refresh_couldnt_refresh))
|
||||
.setContentIntent(PendingIntent.getActivity(this, 0, debugIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE))
|
||||
.setSubText(account.name)
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
.build()
|
||||
NotificationManagerCompat.from(this)
|
||||
.notify(serviceId.toString(), NotificationUtils.NOTIFY_REFRESH_COLLECTIONS, notify)
|
||||
} finally {
|
||||
runningRefresh.remove(serviceId)
|
||||
refreshingStatusListeners.mapNotNull { it.get() }.forEach {
|
||||
it.onDavRefreshStatusChanged(serviceId, false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,9 +17,7 @@ import at.bitfire.davdroid.log.Logger
|
|||
import at.bitfire.ical4android.AndroidTaskList
|
||||
import at.bitfire.ical4android.AndroidTaskListFactory
|
||||
import at.bitfire.ical4android.TaskProvider
|
||||
import org.dmfs.tasks.contract.TaskContract.Tasks
|
||||
import org.dmfs.tasks.contract.TaskContract.TaskLists
|
||||
import org.dmfs.tasks.contract.TaskContract.TaskListColumns
|
||||
import org.dmfs.tasks.contract.TaskContract.*
|
||||
import java.util.logging.Level
|
||||
|
||||
class LocalTaskList private constructor(
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
/***************************************************************************************************
|
||||
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
||||
**************************************************************************************************/
|
||||
package at.bitfire.davdroid.ui.setup
|
||||
|
||||
/***************************************************************************************************
|
||||
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
||||
**************************************************************************************************/
|
||||
package at.bitfire.davdroid.servicedetection
|
||||
|
||||
import android.content.Context
|
||||
import at.bitfire.dav4jvm.DavResource
|
||||
|
@ -15,6 +19,7 @@ import at.bitfire.davdroid.DavUtils
|
|||
import at.bitfire.davdroid.HttpClient
|
||||
import at.bitfire.davdroid.db.Collection
|
||||
import at.bitfire.davdroid.log.StringHandler
|
||||
import at.bitfire.davdroid.ui.setup.LoginModel
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.apache.commons.lang3.builder.ReflectionToStringBuilder
|
||||
|
@ -434,11 +439,11 @@ class DavResourceFinder(
|
|||
// data classes
|
||||
|
||||
class Configuration(
|
||||
val cardDAV: ServiceInfo?,
|
||||
val calDAV: ServiceInfo?,
|
||||
val cardDAV: ServiceInfo?,
|
||||
val calDAV: ServiceInfo?,
|
||||
|
||||
val encountered401: Boolean,
|
||||
val logs: String
|
||||
val encountered401: Boolean,
|
||||
val logs: String
|
||||
) {
|
||||
|
||||
data class ServiceInfo(
|
|
@ -0,0 +1,385 @@
|
|||
/***************************************************************************************************
|
||||
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
||||
**************************************************************************************************/
|
||||
|
||||
package at.bitfire.davdroid.servicedetection
|
||||
|
||||
import android.accounts.Account
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import androidx.concurrent.futures.CallbackToFutureAdapter
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.work.*
|
||||
import at.bitfire.dav4jvm.DavResource
|
||||
import at.bitfire.dav4jvm.Response
|
||||
import at.bitfire.dav4jvm.UrlUtils
|
||||
import at.bitfire.dav4jvm.exception.HttpException
|
||||
import at.bitfire.dav4jvm.property.*
|
||||
import at.bitfire.davdroid.HttpClient
|
||||
import at.bitfire.davdroid.InvalidAccountException
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.db.*
|
||||
import at.bitfire.davdroid.db.Collection
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import at.bitfire.davdroid.settings.Settings
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import at.bitfire.davdroid.ui.DebugInfoActivity
|
||||
import at.bitfire.davdroid.ui.NotificationUtils
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import java.util.logging.Level
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.*
|
||||
|
||||
@HiltWorker
|
||||
class RefreshCollectionsWorker @AssistedInject constructor(
|
||||
@Assisted appContext: Context,
|
||||
@Assisted workerParams: WorkerParameters
|
||||
): Worker(appContext, workerParams) {
|
||||
|
||||
companion object {
|
||||
|
||||
const val ARG_SERVICE_ID = "serviceId"
|
||||
const val REFRESH_COLLECTION_WORKER_TAG = "refreshCollectionWorker"
|
||||
|
||||
val DAV_COLLECTION_PROPERTIES = arrayOf(
|
||||
ResourceType.NAME,
|
||||
CurrentUserPrivilegeSet.NAME,
|
||||
DisplayName.NAME,
|
||||
Owner.NAME,
|
||||
AddressbookDescription.NAME, SupportedAddressData.NAME,
|
||||
CalendarDescription.NAME, CalendarColor.NAME, SupportedCalendarComponentSet.NAME,
|
||||
Source.NAME
|
||||
)
|
||||
|
||||
fun workerName(serviceId: Long): String {
|
||||
return "$REFRESH_COLLECTION_WORKER_TAG-$serviceId"
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests immediate refresh of a given service
|
||||
*
|
||||
* @param serviceId serviceId which is to be refreshed
|
||||
*/
|
||||
fun refreshCollections(context: Context, serviceId: Long) {
|
||||
val arguments = Data.Builder()
|
||||
.putLong(ARG_SERVICE_ID, serviceId)
|
||||
.build()
|
||||
val workRequest = OneTimeWorkRequestBuilder<RefreshCollectionsWorker>()
|
||||
.setInputData(arguments)
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||
workerName(serviceId),
|
||||
ExistingWorkPolicy.KEEP, // if refresh is already running, just continue
|
||||
workRequest
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Will tell whether a refresh worker with given service id and state exists
|
||||
*
|
||||
* @param serviceId the service which the worker(s) belong to
|
||||
* @param workState state of worker to match
|
||||
* @return boolean true if worker with matching state was found
|
||||
*/
|
||||
fun isWorkerInState(context: Context, serviceId: Long, workState: WorkInfo.State) = Transformations.map(
|
||||
WorkManager.getInstance(context).getWorkInfosForUniqueWorkLiveData(workerName(serviceId))
|
||||
) { workInfoList -> workInfoList.any { workInfo -> workInfo.state == workState } }
|
||||
|
||||
}
|
||||
|
||||
@Inject lateinit var db: AppDatabase
|
||||
@Inject lateinit var settings: SettingsManager
|
||||
|
||||
override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> =
|
||||
CallbackToFutureAdapter.getFuture { completer ->
|
||||
val notification = NotificationUtils.newBuilder(applicationContext, NotificationUtils.CHANNEL_STATUS)
|
||||
.setSmallIcon(R.drawable.ic_foreground_notify)
|
||||
.setContentTitle(applicationContext.getString(R.string.foreground_service_notify_title))
|
||||
.setContentText(applicationContext.getString(R.string.foreground_service_notify_text))
|
||||
.setStyle(NotificationCompat.BigTextStyle())
|
||||
.setCategory(NotificationCompat.CATEGORY_STATUS)
|
||||
.setOngoing(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.build()
|
||||
completer.set(ForegroundInfo(NotificationUtils.NOTIFY_SYNC_EXPEDITED, notification))
|
||||
}
|
||||
|
||||
override fun doWork(): Result {
|
||||
val serviceId = inputData.getLong(ARG_SERVICE_ID, -1)
|
||||
|
||||
if (serviceId == -1L)
|
||||
return Result.failure()
|
||||
|
||||
val syncAllCollections = settings.getBoolean(Settings.SYNC_ALL_COLLECTIONS)
|
||||
|
||||
val homeSetDao = db.homeSetDao()
|
||||
val collectionDao = db.collectionDao()
|
||||
|
||||
val service = db.serviceDao().get(serviceId) ?: throw IllegalArgumentException("Service not found")
|
||||
val account = Account(service.accountName, applicationContext.getString(R.string.account_type))
|
||||
|
||||
val homeSets = homeSetDao.getByService(serviceId).associateBy { it.url }.toMutableMap()
|
||||
val collections = collectionDao.getByService(serviceId).associateBy { it.url }.toMutableMap()
|
||||
|
||||
/**
|
||||
* Checks if the given URL defines home sets and adds them to the home set list.
|
||||
*
|
||||
* @param personal Whether this is the "outer" call of the recursion.
|
||||
*
|
||||
* *true* = found home sets belong to the current-user-principal; recurse if
|
||||
* calendar proxies or group memberships are found
|
||||
*
|
||||
* *false* = found home sets don't directly belong to the current-user-principal; don't recurse
|
||||
*
|
||||
* @throws java.io.IOException
|
||||
* @throws HttpException
|
||||
* @throws at.bitfire.dav4jvm.exception.DavException
|
||||
*/
|
||||
fun queryHomeSets(client: OkHttpClient, url: HttpUrl, personal: Boolean = true) {
|
||||
val related = mutableSetOf<HttpUrl>()
|
||||
|
||||
fun findRelated(root: HttpUrl, dav: Response) {
|
||||
// refresh home sets: calendar-proxy-read/write-for
|
||||
dav[CalendarProxyReadFor::class.java]?.let {
|
||||
for (href in it.hrefs) {
|
||||
Logger.log.fine("Principal is a read-only proxy for $href, checking for home sets")
|
||||
root.resolve(href)?.let { proxyReadFor ->
|
||||
related += proxyReadFor
|
||||
}
|
||||
}
|
||||
}
|
||||
dav[CalendarProxyWriteFor::class.java]?.let {
|
||||
for (href in it.hrefs) {
|
||||
Logger.log.fine("Principal is a read/write proxy for $href, checking for home sets")
|
||||
root.resolve(href)?.let { proxyWriteFor ->
|
||||
related += proxyWriteFor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// refresh home sets: direct group memberships
|
||||
dav[GroupMembership::class.java]?.let {
|
||||
for (href in it.hrefs) {
|
||||
Logger.log.fine("Principal is member of group $href, checking for home sets")
|
||||
root.resolve(href)?.let { groupMembership ->
|
||||
related += groupMembership
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dav = DavResource(client, url)
|
||||
when (service.type) {
|
||||
Service.TYPE_CARDDAV ->
|
||||
try {
|
||||
dav.propfind(0, DisplayName.NAME, AddressbookHomeSet.NAME, GroupMembership.NAME) { response, _ ->
|
||||
response[AddressbookHomeSet::class.java]?.let { homeSet ->
|
||||
for (href in homeSet.hrefs)
|
||||
dav.location.resolve(href)?.let {
|
||||
val foundUrl = UrlUtils.withTrailingSlash(it)
|
||||
homeSets[foundUrl] = HomeSet(0, service.id, personal, foundUrl)
|
||||
}
|
||||
}
|
||||
|
||||
if (personal)
|
||||
findRelated(dav.location, response)
|
||||
}
|
||||
} catch (e: HttpException) {
|
||||
if (e.code/100 == 4)
|
||||
Logger.log.log(Level.INFO, "Ignoring Client Error 4xx while looking for addressbook home sets", e)
|
||||
else
|
||||
throw e
|
||||
}
|
||||
Service.TYPE_CALDAV -> {
|
||||
try {
|
||||
dav.propfind(0, DisplayName.NAME, CalendarHomeSet.NAME, CalendarProxyReadFor.NAME, CalendarProxyWriteFor.NAME, GroupMembership.NAME) { response, _ ->
|
||||
response[CalendarHomeSet::class.java]?.let { homeSet ->
|
||||
for (href in homeSet.hrefs)
|
||||
dav.location.resolve(href)?.let {
|
||||
val foundUrl = UrlUtils.withTrailingSlash(it)
|
||||
homeSets[foundUrl] = HomeSet(0, service.id, personal, foundUrl)
|
||||
}
|
||||
}
|
||||
|
||||
if (personal)
|
||||
findRelated(dav.location, response)
|
||||
}
|
||||
} catch (e: HttpException) {
|
||||
if (e.code/100 == 4)
|
||||
Logger.log.log(Level.INFO, "Ignoring Client Error 4xx while looking for calendar home sets", e)
|
||||
else
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// query related homesets (those that do not belong to the current-user-principal)
|
||||
for (resource in related)
|
||||
queryHomeSets(client, resource, false)
|
||||
}
|
||||
|
||||
fun saveHomesets() {
|
||||
// syncAll sets the ID of the new homeset to the ID of the old one when the URLs are matching
|
||||
DaoTools(homeSetDao).syncAll(
|
||||
homeSetDao.getByService(serviceId),
|
||||
homeSets,
|
||||
{ it.url })
|
||||
}
|
||||
|
||||
fun saveCollections() {
|
||||
// syncAll sets the ID of the new collection to the ID of the old one when the URLs are matching
|
||||
DaoTools(collectionDao).syncAll(
|
||||
collectionDao.getByService(serviceId),
|
||||
collections, { it.url }) { new, old ->
|
||||
// use old settings of "force read only" and "sync", regardless of detection results
|
||||
new.forceReadOnly = old.forceReadOnly
|
||||
new.sync = old.sync
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Logger.log.info("Refreshing ${service.type} collections of service #$service")
|
||||
|
||||
// cancel previous notification
|
||||
NotificationManagerCompat.from(applicationContext)
|
||||
.cancel(serviceId.toString(), NotificationUtils.NOTIFY_REFRESH_COLLECTIONS)
|
||||
|
||||
// create authenticating OkHttpClient (credentials taken from account settings)
|
||||
HttpClient.Builder(applicationContext, AccountSettings(applicationContext, account))
|
||||
.setForeground(true)
|
||||
.build().use { client ->
|
||||
val httpClient = client.okHttpClient
|
||||
|
||||
// refresh home set list (from principal)
|
||||
service.principal?.let { principalUrl ->
|
||||
Logger.log.fine("Querying principal $principalUrl for home sets")
|
||||
queryHomeSets(httpClient, principalUrl)
|
||||
}
|
||||
|
||||
// now refresh homesets and their member collections
|
||||
val itHomeSets = homeSets.iterator()
|
||||
while (itHomeSets.hasNext()) {
|
||||
val (homeSetUrl, homeSet) = itHomeSets.next()
|
||||
Logger.log.fine("Listing home set $homeSetUrl")
|
||||
|
||||
try {
|
||||
DavResource(httpClient, homeSetUrl).propfind(1, *DAV_COLLECTION_PROPERTIES) { response, relation ->
|
||||
if (!response.isSuccess())
|
||||
return@propfind
|
||||
|
||||
if (relation == Response.HrefRelation.SELF) {
|
||||
// this response is about the homeset itself
|
||||
homeSet.displayName = response[DisplayName::class.java]?.displayName
|
||||
homeSet.privBind = response[CurrentUserPrivilegeSet::class.java]?.mayBind ?: true
|
||||
}
|
||||
|
||||
// in any case, check whether the response is about a useable collection
|
||||
val info = Collection.fromDavResponse(response) ?: return@propfind
|
||||
info.serviceId = serviceId
|
||||
info.refHomeSet = homeSet
|
||||
info.confirmed = true
|
||||
|
||||
// whether new collections are selected for synchronization by default (controlled by managed setting)
|
||||
info.sync = syncAllCollections
|
||||
|
||||
info.owner = response[Owner::class.java]?.href?.let { response.href.resolve(it) }
|
||||
Logger.log.log(Level.FINE, "Found collection", info)
|
||||
|
||||
// remember usable collections
|
||||
if ((service.type == Service.TYPE_CARDDAV && info.type == Collection.TYPE_ADDRESSBOOK) ||
|
||||
(service.type == Service.TYPE_CALDAV && arrayOf(Collection.TYPE_CALENDAR, Collection.TYPE_WEBCAL).contains(info.type)))
|
||||
collections[response.href] = info
|
||||
}
|
||||
} catch(e: HttpException) {
|
||||
if (e.code in arrayOf(403, 404, 410))
|
||||
// delete home set only if it was not accessible (40x)
|
||||
itHomeSets.remove()
|
||||
}
|
||||
}
|
||||
|
||||
// check/refresh unconfirmed collections
|
||||
val collectionsIter = collections.entries.iterator()
|
||||
while (collectionsIter.hasNext()) {
|
||||
val currentCollection = collectionsIter.next()
|
||||
val (url, info) = currentCollection
|
||||
if (!info.confirmed)
|
||||
try {
|
||||
// this collection doesn't belong to a homeset anymore, otherwise it would have been confirmed
|
||||
info.homeSetId = null
|
||||
|
||||
DavResource(httpClient, url).propfind(0, *DAV_COLLECTION_PROPERTIES) { response, _ ->
|
||||
if (!response.isSuccess())
|
||||
return@propfind
|
||||
|
||||
val collection = Collection.fromDavResponse(response) ?: return@propfind
|
||||
collection.serviceId = info.serviceId // use same service ID as previous entry
|
||||
collection.confirmed = true
|
||||
|
||||
// remove unusable collections
|
||||
if ((service.type == Service.TYPE_CARDDAV && collection.type != Collection.TYPE_ADDRESSBOOK) ||
|
||||
(service.type == Service.TYPE_CALDAV && !arrayOf(Collection.TYPE_CALENDAR, Collection.TYPE_WEBCAL).contains(collection.type)) ||
|
||||
(collection.type == Collection.TYPE_WEBCAL && collection.source == null))
|
||||
collectionsIter.remove()
|
||||
else
|
||||
// update this collection in list
|
||||
currentCollection.setValue(collection)
|
||||
}
|
||||
} catch(e: HttpException) {
|
||||
if (e.code in arrayOf(403, 404, 410))
|
||||
// delete collection only if it was not accessible (40x)
|
||||
collectionsIter.remove()
|
||||
else
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.runInTransaction {
|
||||
saveHomesets()
|
||||
|
||||
// use refHomeSet (if available) to determine homeset ID
|
||||
for (collection in collections.values)
|
||||
collection.refHomeSet?.let { homeSet ->
|
||||
collection.homeSetId = homeSet.id
|
||||
}
|
||||
saveCollections()
|
||||
}
|
||||
|
||||
} catch(e: InvalidAccountException) {
|
||||
Logger.log.log(Level.SEVERE, "Invalid account", e)
|
||||
return Result.failure()
|
||||
} catch(e: Exception) {
|
||||
Logger.log.log(Level.SEVERE, "Couldn't refresh collection list", e)
|
||||
|
||||
val debugIntent = DebugInfoActivity.IntentBuilder(applicationContext)
|
||||
.withCause(e)
|
||||
.withAccount(account)
|
||||
.build()
|
||||
val notify = NotificationUtils.newBuilder(applicationContext, NotificationUtils.CHANNEL_GENERAL)
|
||||
.setSmallIcon(R.drawable.ic_sync_problem_notify)
|
||||
.setContentTitle(applicationContext.getString(R.string.refresh_collections_worker_refresh_failed))
|
||||
.setContentText(applicationContext.getString(R.string.refresh_collections_worker_refresh_couldnt_refresh))
|
||||
.setContentIntent(PendingIntent.getActivity(applicationContext, 0, debugIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE))
|
||||
.setSubText(account.name)
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
.build()
|
||||
NotificationManagerCompat.from(applicationContext)
|
||||
.notify(serviceId.toString(), NotificationUtils.NOTIFY_REFRESH_COLLECTIONS, notify)
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
// Success
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
}
|
|
@ -22,9 +22,9 @@ import at.bitfire.davdroid.resource.LocalCalendar
|
|||
import at.bitfire.davdroid.resource.LocalEvent
|
||||
import at.bitfire.davdroid.resource.LocalResource
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import at.bitfire.ical4android.util.DateUtils
|
||||
import at.bitfire.ical4android.Event
|
||||
import at.bitfire.ical4android.InvalidCalendarException
|
||||
import at.bitfire.ical4android.util.DateUtils
|
||||
import net.fortuna.ical4j.model.Component
|
||||
import net.fortuna.ical4j.model.component.VAlarm
|
||||
import okhttp3.HttpUrl
|
||||
|
|
|
@ -775,7 +775,6 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L
|
|||
.setPriority(priority)
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
viewItemAction?.let { builder.addAction(it) }
|
||||
builder.addAction(buildRetryAction())
|
||||
|
||||
notificationManager.notify(notificationTag, NotificationUtils.NOTIFY_SYNC_ERROR, builder.build())
|
||||
}
|
||||
|
@ -796,32 +795,6 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L
|
|||
.withRemoteResource(remote)
|
||||
.build()
|
||||
|
||||
private fun buildRetryAction(): NotificationCompat.Action {
|
||||
val retryIntent = Intent(context, DavService::class.java)
|
||||
retryIntent.action = DavService.ACTION_FORCE_SYNC
|
||||
|
||||
val syncAuthority: String
|
||||
val syncAccount: Account
|
||||
if (authority == ContactsContract.AUTHORITY) {
|
||||
// if this is a contacts sync, retry syncing all address books of the main account
|
||||
syncAuthority = context.getString(R.string.address_books_authority)
|
||||
syncAccount = mainAccount
|
||||
} else {
|
||||
syncAuthority = authority
|
||||
syncAccount = account
|
||||
}
|
||||
|
||||
retryIntent.data = Uri.parse("sync://").buildUpon()
|
||||
.authority(syncAuthority)
|
||||
.appendPath(syncAccount.type)
|
||||
.appendPath(syncAccount.name)
|
||||
.build()
|
||||
|
||||
return NotificationCompat.Action(
|
||||
android.R.drawable.ic_menu_rotate, context.getString(R.string.sync_error_retry),
|
||||
PendingIntent.getService(context, 0, retryIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE))
|
||||
}
|
||||
|
||||
private fun buildViewItemAction(local: ResourceType): NotificationCompat.Action? {
|
||||
Logger.log.log(Level.FINE, "Adding view action for local resource", local)
|
||||
val intent = local.id?.let { id ->
|
||||
|
|
|
@ -21,7 +21,6 @@ import android.os.Bundle
|
|||
import android.provider.Settings
|
||||
import android.view.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.PackageManagerCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
|
@ -33,7 +32,6 @@ import androidx.recyclerview.widget.ListAdapter
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import at.bitfire.davdroid.DavUtils
|
||||
import at.bitfire.davdroid.DavUtils.SyncStatus
|
||||
import at.bitfire.davdroid.PermissionUtils
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.StorageLowReceiver
|
||||
import at.bitfire.davdroid.databinding.AccountListBinding
|
||||
|
|
|
@ -43,8 +43,8 @@ import at.bitfire.davdroid.log.Logger
|
|||
import at.bitfire.davdroid.resource.LocalAddressBook
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import at.bitfire.ical4android.util.MiscUtils.ContentProviderClientHelper.closeCompat
|
||||
import at.bitfire.ical4android.TaskProvider.ProviderName
|
||||
import at.bitfire.ical4android.util.MiscUtils.ContentProviderClientHelper.closeCompat
|
||||
import at.techbee.jtx.JtxContract
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
|
@ -28,6 +28,7 @@ object NotificationUtils {
|
|||
const val NOTIFY_INVALID_RESOURCE = 11
|
||||
const val NOTIFY_WEBDAV_ACCESS = 12
|
||||
const val NOTIFY_LOW_STORAGE = 13
|
||||
const val NOTIFY_SYNC_EXPEDITED = 14
|
||||
const val NOTIFY_TASKS_PROVIDER_TOO_OLD = 20
|
||||
const val NOTIFY_PERMISSIONS = 21
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ package at.bitfire.davdroid.ui.account
|
|||
|
||||
import android.content.*
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.provider.CalendarContract
|
||||
import android.provider.ContactsContract
|
||||
import android.view.*
|
||||
|
@ -23,14 +22,15 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import androidx.work.WorkInfo
|
||||
import at.bitfire.davdroid.Constants
|
||||
import at.bitfire.davdroid.DavService
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.databinding.AccountCollectionsBinding
|
||||
import at.bitfire.davdroid.db.AppDatabase
|
||||
import at.bitfire.davdroid.db.Collection
|
||||
import at.bitfire.davdroid.resource.LocalAddressBook
|
||||
import at.bitfire.davdroid.resource.TaskUtils
|
||||
import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker
|
||||
import at.bitfire.davdroid.ui.PermissionsActivity
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
@ -278,7 +278,7 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList
|
|||
@Assisted val accountModel: AccountActivity.Model,
|
||||
@Assisted val serviceId: Long,
|
||||
@Assisted val collectionType: String
|
||||
): ViewModel(), DavService.RefreshingStatusListener, SyncStatusObserver {
|
||||
): ViewModel(), SyncStatusObserver {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
@ -302,21 +302,8 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList
|
|||
}
|
||||
}
|
||||
|
||||
// observe DavService refresh status
|
||||
@Volatile
|
||||
private var davService: DavService.InfoBinder? = null
|
||||
private var davServiceConn: ServiceConnection? = null
|
||||
private val svcConn = object: ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
val svc = service as DavService.InfoBinder
|
||||
davService = svc
|
||||
svc.addRefreshingStatusListener(this@Model, true)
|
||||
}
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
davService = null
|
||||
}
|
||||
}
|
||||
val isRefreshing = MutableLiveData<Boolean>()
|
||||
// observe RefreshCollectionsWorker status
|
||||
val isRefreshing = RefreshCollectionsWorker.isWorkerInState(context, serviceId, WorkInfo.State.RUNNING)
|
||||
|
||||
// observe whether sync is active
|
||||
private var syncStatusHandle: Any? = null
|
||||
|
@ -325,9 +312,6 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList
|
|||
|
||||
|
||||
init {
|
||||
if (context.bindService(Intent(context, DavService::class.java), svcConn, Context.BIND_AUTO_CREATE))
|
||||
davServiceConn = svcConn
|
||||
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
syncStatusHandle = ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_PENDING + ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE, this@Model)
|
||||
checkSyncStatus()
|
||||
|
@ -336,22 +320,10 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList
|
|||
|
||||
override fun onCleared() {
|
||||
syncStatusHandle?.let { ContentResolver.removeStatusChangeListener(it) }
|
||||
|
||||
davService?.removeRefreshingStatusListener(this)
|
||||
davServiceConn?.let {
|
||||
context.unbindService(it)
|
||||
davServiceConn = null
|
||||
}
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
DavService.refreshCollections(context, serviceId)
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
override fun onDavRefreshStatusChanged(id: Long, refreshing: Boolean) {
|
||||
if (id == serviceId)
|
||||
isRefreshing.postValue(refreshing)
|
||||
RefreshCollectionsWorker.refreshCollections(context, serviceId)
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
|
|
|
@ -15,13 +15,13 @@ import androidx.fragment.app.viewModels
|
|||
import androidx.lifecycle.*
|
||||
import at.bitfire.dav4jvm.DavResource
|
||||
import at.bitfire.dav4jvm.XmlUtils
|
||||
import at.bitfire.davdroid.DavService
|
||||
import at.bitfire.davdroid.DavUtils
|
||||
import at.bitfire.davdroid.HttpClient
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.db.AppDatabase
|
||||
import at.bitfire.davdroid.db.Collection
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import at.bitfire.davdroid.ui.ExceptionInfoFragment
|
||||
import dagger.assisted.Assisted
|
||||
|
@ -148,7 +148,7 @@ class CreateCollectionFragment: DialogFragment() {
|
|||
db.collectionDao().insert(collection)
|
||||
|
||||
// trigger service detection (because the collection may have other properties than the ones we have inserted)
|
||||
DavService.refreshCollections(context, service.id)
|
||||
RefreshCollectionsWorker.refreshCollections(context, service.id)
|
||||
}
|
||||
|
||||
// post success
|
||||
|
|
|
@ -15,11 +15,6 @@ import at.bitfire.davdroid.PermissionUtils.CALENDAR_PERMISSIONS
|
|||
import at.bitfire.davdroid.PermissionUtils.CONTACT_PERMISSIONS
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.ical4android.TaskProvider
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import dagger.multibindings.IntoSet
|
||||
import javax.inject.Inject
|
||||
|
||||
class PermissionsIntroFragment : Fragment() {
|
||||
|
|
|
@ -17,11 +17,6 @@ import at.bitfire.davdroid.R
|
|||
import at.bitfire.davdroid.resource.TaskUtils
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import at.bitfire.davdroid.ui.TasksFragment
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.IntoSet
|
||||
import javax.inject.Inject
|
||||
|
||||
class TasksIntroFragment : Fragment() {
|
||||
|
|
|
@ -20,7 +20,6 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.*
|
||||
import at.bitfire.davdroid.DavService
|
||||
import at.bitfire.davdroid.InvalidAccountException
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.databinding.LoginAccountDetailsBinding
|
||||
|
@ -30,6 +29,8 @@ import at.bitfire.davdroid.db.HomeSet
|
|||
import at.bitfire.davdroid.db.Service
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.resource.TaskUtils
|
||||
import at.bitfire.davdroid.servicedetection.DavResourceFinder
|
||||
import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import at.bitfire.davdroid.settings.Settings
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
|
@ -174,9 +175,6 @@ class AccountDetailsFragment : Fragment() {
|
|||
val accountSettings = AccountSettings(context, account)
|
||||
val defaultSyncInterval = settingsManager.getLong(Settings.DEFAULT_SYNC_INTERVAL)
|
||||
|
||||
val refreshIntent = Intent(context, DavService::class.java)
|
||||
refreshIntent.action = DavService.ACTION_REFRESH_COLLECTIONS
|
||||
|
||||
val addrBookAuthority = context.getString(R.string.address_books_authority)
|
||||
if (config.cardDAV != null) {
|
||||
// insert CardDAV service
|
||||
|
@ -186,8 +184,7 @@ class AccountDetailsFragment : Fragment() {
|
|||
accountSettings.setGroupMethod(groupMethod)
|
||||
|
||||
// start CardDAV service detection (refresh collections)
|
||||
refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id)
|
||||
context.startService(refreshIntent)
|
||||
RefreshCollectionsWorker.refreshCollections(context, id)
|
||||
|
||||
// set default sync interval and enable sync regardless of permissions
|
||||
ContentResolver.setIsSyncable(account, addrBookAuthority, 1)
|
||||
|
@ -200,8 +197,7 @@ class AccountDetailsFragment : Fragment() {
|
|||
val id = insertService(name, Service.TYPE_CALDAV, config.calDAV)
|
||||
|
||||
// start CalDAV service detection (refresh collections)
|
||||
refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id)
|
||||
context.startService(refreshIntent)
|
||||
RefreshCollectionsWorker.refreshCollections(context, id)
|
||||
|
||||
// set default sync interval and enable sync regardless of permissions
|
||||
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 1)
|
||||
|
|
|
@ -19,6 +19,7 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.servicedetection.DavResourceFinder
|
||||
import at.bitfire.davdroid.ui.DebugInfoActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.lang.ref.WeakReference
|
||||
|
|
|
@ -6,6 +6,7 @@ package at.bitfire.davdroid.ui.setup
|
|||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import at.bitfire.davdroid.db.Credentials
|
||||
import at.bitfire.davdroid.servicedetection.DavResourceFinder
|
||||
import java.net.URI
|
||||
|
||||
class LoginModel: ViewModel() {
|
||||
|
|
|
@ -26,7 +26,6 @@ import android.provider.DocumentsProvider
|
|||
import android.webkit.MimeTypeMap
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.Observer
|
||||
import at.bitfire.dav4jvm.DavCollection
|
||||
import at.bitfire.dav4jvm.DavResource
|
||||
import at.bitfire.dav4jvm.Response
|
||||
|
@ -38,7 +37,6 @@ import at.bitfire.davdroid.R
|
|||
import at.bitfire.davdroid.db.AppDatabase
|
||||
import at.bitfire.davdroid.db.DaoTools
|
||||
import at.bitfire.davdroid.db.WebDavDocument
|
||||
import at.bitfire.davdroid.db.WebDavMount
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.ui.webdav.WebdavMountsActivity
|
||||
import at.bitfire.davdroid.webdav.cache.HeadResponseCache
|
||||
|
|
|
@ -43,9 +43,9 @@
|
|||
<string name="account_list_empty">مرحباً بك في DAVx⁵ !\n\n يمكنك إضافة حساب CalDAV أو CardDAV الآن.</string>
|
||||
<string name="accounts_global_sync_disabled">تم تعطيل المزامنة التلقائية على مستوى النظام</string>
|
||||
<string name="accounts_global_sync_enable">تفعيل</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">فشل اكتشاف الخدمة</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">لم يتمكن التطبيق من تجديد قائمة المجموعة</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">فشل اكتشاف الخدمة</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">لم يتمكن التطبيق من تجديد قائمة المجموعة</string>
|
||||
<!--Battery Optimization-->
|
||||
<!--ForegroundService-->
|
||||
<!--AppSettingsActivity-->
|
||||
|
|
|
@ -127,9 +127,9 @@
|
|||
<string name="accounts_global_sync_disabled">Автоматичното синхронизиране е изключено на ниво система</string>
|
||||
<string name="accounts_global_sync_enable">Включване</string>
|
||||
<string name="accounts_sync_all">Синхронизиране на всички профили</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Откриването на услугите се провали</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Грешка при презареждане</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Откриването на услугите се провали</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Грешка при презареждане</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">Не може да работи на преден план</string>
|
||||
<string name="battery_optimization_notify_text">Необходимо е да спрете оптимизирането на батерията за приложението</string>
|
||||
|
|
|
@ -133,9 +133,9 @@
|
|||
<string name="accounts_global_sync_disabled">La sincronització automàtica de tot el sistema està inhabilitada</string>
|
||||
<string name="accounts_global_sync_enable">Activa</string>
|
||||
<string name="accounts_sync_all">Sincronitza tots els comptes</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Ha fallat la detecció del servei</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">No s\'ha pogut actualitzar la llista de col·leccions</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Ha fallat la detecció del servei</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">No s\'ha pogut actualitzar la llista de col·leccions</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">No s\'ha pogut executar en segon pla</string>
|
||||
<string name="battery_optimization_notify_text">Es requereix incloure-la a la llista blanca d\'optimització de la bateria</string>
|
||||
|
|
|
@ -130,9 +130,9 @@
|
|||
<string name="accounts_global_sync_disabled">Automatická synchronizace v rámci celého systému je vypnutá</string>
|
||||
<string name="accounts_global_sync_enable">Zapnout</string>
|
||||
<string name="accounts_sync_all">Synchronizovat všechny účty</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Vyhledání služby se nezdařilo</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Nedaří se znovu načíst seznam sady</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Vyhledání služby se nezdařilo</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Nedaří se znovu načíst seznam sady</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">Běh na pozadí není zařízením umožňován</string>
|
||||
<string name="battery_optimization_notify_text">Je zapotřebí zařadit na seznam výjimek z optimalizace akumulátoru</string>
|
||||
|
|
|
@ -127,9 +127,9 @@
|
|||
<string name="accounts_global_sync_disabled">Automatisk synkronisering på tværs af systemet er deaktiveret</string>
|
||||
<string name="accounts_global_sync_enable">Aktivere</string>
|
||||
<string name="accounts_sync_all">Synkronisere alle konti</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Registrering af tjeneste kunne ikke foretages</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Kunne ikke opdatere samling liste</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Registrering af tjeneste kunne ikke foretages</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Kunne ikke opdatere samling liste</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">Kan ikke køre i forgrunden</string>
|
||||
<string name="battery_optimization_notify_text">Batteri optimering hvid listet påkrævet</string>
|
||||
|
|
|
@ -135,9 +135,9 @@
|
|||
<string name="accounts_global_sync_disabled">Systemweite automatische Synchronisierung ist nicht aktiv</string>
|
||||
<string name="accounts_global_sync_enable">Aktivieren</string>
|
||||
<string name="accounts_sync_all">Alle Konten synchronisieren</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Diensterkennung fehlgeschlagen</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Ordnerliste konnte nicht aktualisiert werden</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Diensterkennung fehlgeschlagen</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Ordnerliste konnte nicht aktualisiert werden</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">Darf nicht im Vordergrund ausgeführt werden</string>
|
||||
<string name="battery_optimization_notify_text">Ausnahme von Akku-Optimierung erforderlich</string>
|
||||
|
|
|
@ -112,9 +112,9 @@
|
|||
<string name="accounts_global_sync_disabled">Ο αυτόματος συγχρονισμός σε όλο το σύστημα είναι απενεργοποιημένος</string>
|
||||
<string name="accounts_global_sync_enable">Ενεργοποίηση</string>
|
||||
<string name="accounts_sync_all">Συγχρονισμός όλων των λογαριασμών</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Αποτυχία ανίχνευσης υπηρεσίας</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Αδυναμία ανανέωσης της λίστας συλλογής</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Αποτυχία ανίχνευσης υπηρεσίας</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Αδυναμία ανανέωσης της λίστας συλλογής</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">Αδυναμία εκτέλεσης στο προσκήνιο</string>
|
||||
<!--ForegroundService-->
|
||||
|
|
|
@ -127,9 +127,9 @@
|
|||
<string name="accounts_global_sync_disabled">Sincronización automática del sistema completo está deshabilitada</string>
|
||||
<string name="accounts_global_sync_enable">Activar</string>
|
||||
<string name="accounts_sync_all">Sincronizar todas las cuentas</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Falló la detección del servicio</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">No se pudo refrescar lista de colección</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Falló la detección del servicio</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">No se pudo refrescar lista de colección</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">No puede ejecutarse en primer plano</string>
|
||||
<string name="battery_optimization_notify_text">Se requiere agregar a la lista blanca de optimización de batería</string>
|
||||
|
|
|
@ -127,9 +127,9 @@
|
|||
<string name="accounts_global_sync_disabled">Sistema osoko sinkronizazio automatikoa desgaituta dago</string>
|
||||
<string name="accounts_global_sync_enable">Gaitu</string>
|
||||
<string name="accounts_sync_all">Sinkronizatu kontu guztiak</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Zerbitzuaren detekzioak huts egin du</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Ezin izan da bilduma zerrenda freskatu</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Zerbitzuaren detekzioak huts egin du</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Ezin izan da bilduma zerrenda freskatu</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">Ezin da aurrealdean exekutatu</string>
|
||||
<string name="battery_optimization_notify_text">Bateriaren optimizazioaren zerrenda zuria behar da</string>
|
||||
|
|
|
@ -106,9 +106,9 @@
|
|||
<string name="accounts_global_sync_disabled">همگام سازی خودکار در کل سیستم غیرفعال است</string>
|
||||
<string name="accounts_global_sync_enable">فعال</string>
|
||||
<string name="accounts_sync_all">همگام سازی همه حسابها</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">تشخیص سرویس ناموفق بود</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">لیست مجموعه به روز نشد</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">تشخیص سرویس ناموفق بود</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">لیست مجموعه به روز نشد</string>
|
||||
<!--Battery Optimization-->
|
||||
<!--ForegroundService-->
|
||||
<string name="foreground_service_notify_title">در حال اجرا در پیش زمینه</string>
|
||||
|
|
|
@ -122,9 +122,9 @@
|
|||
<string name="accounts_global_sync_disabled">La synchronisation automatique globale est désactivée</string>
|
||||
<string name="accounts_global_sync_enable">Activer</string>
|
||||
<string name="accounts_sync_all">Synchroniser tous les comptes</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">La détection du service a échoué</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Impossible d\'actualiser la liste de collection</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">La détection du service a échoué</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Impossible d\'actualiser la liste de collection</string>
|
||||
<!--Battery Optimization-->
|
||||
<!--ForegroundService-->
|
||||
<string name="foreground_service_notify_title">Fonctionne au premier plan</string>
|
||||
|
|
|
@ -135,9 +135,9 @@
|
|||
<string name="accounts_global_sync_disabled">A sincronización global automática do sistema está desactivada</string>
|
||||
<string name="accounts_global_sync_enable">Activar</string>
|
||||
<string name="accounts_sync_all">Sincroniza todas as contas</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Fallou a detección do servizo</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Non se actualizou a lista da colección</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Fallou a detección do servizo</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Non se actualizou a lista da colección</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">Non pode executarse en segundo plano</string>
|
||||
<string name="battery_optimization_notify_text">Require ter permiso para optimizacións de batería</string>
|
||||
|
|
|
@ -110,9 +110,9 @@
|
|||
<string name="accounts_global_sync_disabled">Automatska sinkronizacija je onemogućena na razini sustava</string>
|
||||
<string name="accounts_global_sync_enable">Omogući</string>
|
||||
<string name="accounts_sync_all">Sinkroniziraj sve račune</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Detekcija servisa nije uspjela</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Nije moguće osvježiti popis zbirki</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Detekcija servisa nije uspjela</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Nije moguće osvježiti popis zbirki</string>
|
||||
<!--Battery Optimization-->
|
||||
<!--ForegroundService-->
|
||||
<string name="foreground_service_notify_title">Pokrenuto u prednjem planu</string>
|
||||
|
|
|
@ -125,9 +125,9 @@
|
|||
<string name="accounts_global_sync_disabled">A rendszerszintű automatikus szinkronizálás ki van kapcsolva</string>
|
||||
<string name="accounts_global_sync_enable">Bekapcsolás</string>
|
||||
<string name="accounts_sync_all">Az összes fiók szinkronizálása</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Szolgáltatások felderítése nem sikerült</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Gyűjteménylista frissítése nem sikerült</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Szolgáltatások felderítése nem sikerült</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Gyűjteménylista frissítése nem sikerült</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">Nem lehetséges az előtérben való futtatás</string>
|
||||
<string name="battery_optimization_notify_text">Kivétel az akkumulátorhasználat optimalizálása alól</string>
|
||||
|
|
|
@ -116,9 +116,9 @@
|
|||
<string name="accounts_global_sync_disabled">La sincronizzazione automatica dell\'intero sistema è disabilitata</string>
|
||||
<string name="accounts_global_sync_enable">Attiva</string>
|
||||
<string name="accounts_sync_all">Sincronizzazione di tutti gli account</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Fallita l\'individuazione dei servizi</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Impossibile aggiornare la lista delle raccolte</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Fallita l\'individuazione dei servizi</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Impossibile aggiornare la lista delle raccolte</string>
|
||||
<!--Battery Optimization-->
|
||||
<!--ForegroundService-->
|
||||
<string name="foreground_service_notify_title">Esecuzione in primo piano</string>
|
||||
|
|
|
@ -133,9 +133,9 @@
|
|||
<string name="accounts_global_sync_disabled">システム全体の自動同期が無効です</string>
|
||||
<string name="accounts_global_sync_enable">有効</string>
|
||||
<string name="accounts_sync_all">すべてのアカウントを同期</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">サービスの検出に失敗しました</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">コレクション リストを更新できませんでした</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">サービスの検出に失敗しました</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">コレクション リストを更新できませんでした</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">フォアグラウンドで実行できません</string>
|
||||
<string name="battery_optimization_notify_text">バッテリー最適化を無効にしてください</string>
|
||||
|
|
|
@ -126,9 +126,9 @@
|
|||
<string name="accounts_global_sync_disabled">시스템 전체 자동 동기화가 비활성화됩니다.</string>
|
||||
<string name="accounts_global_sync_enable">활성</string>
|
||||
<string name="accounts_sync_all">모든 계정 동기화</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">서비스 검색 실패</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">collection 목록을 새로 고칠 수 없습니다.</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">서비스 검색 실패</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">collection 목록을 새로 고칠 수 없습니다.</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">foreground에서 실행할 수 없습니다.</string>
|
||||
<string name="battery_optimization_notify_text">배터리 최적화 허용목록 필요</string>
|
||||
|
|
|
@ -44,9 +44,9 @@
|
|||
<string name="account_list_empty">Velkommen til DAVx⁵.\n\nDu kan legge til en CalDAV/CardDAV-konto nå.</string>
|
||||
<string name="accounts_global_sync_disabled">Systemomspennende automatisk synkronisering avskrudd</string>
|
||||
<string name="accounts_global_sync_enable">Skru på</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Tjenesteoppdagelse mislyktes</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Kunne ikke gjenoppfriske innsamlingsliste</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Tjenesteoppdagelse mislyktes</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Kunne ikke gjenoppfriske innsamlingsliste</string>
|
||||
<!--Battery Optimization-->
|
||||
<!--ForegroundService-->
|
||||
<!--AppSettingsActivity-->
|
||||
|
|
|
@ -127,9 +127,9 @@
|
|||
<string name="accounts_global_sync_disabled">Automatisch synchroniseren is voor alle accounts uitgeschakeld</string>
|
||||
<string name="accounts_global_sync_enable">Inschakelen</string>
|
||||
<string name="accounts_sync_all">Alle accounts synchroniseren</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Service herkenning is mislukt</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">De collectielijst is niet bijgewerkt</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Service herkenning is mislukt</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">De collectielijst is niet bijgewerkt</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">Kan niet op de voorgrond draaien</string>
|
||||
<string name="battery_optimization_notify_text">Toestemming tot onbeperkt batterijgebruik is vereist</string>
|
||||
|
|
|
@ -135,9 +135,9 @@
|
|||
<string name="accounts_global_sync_disabled">Automatyczna synchronizacja dla całego systemu jest wyłączona</string>
|
||||
<string name="accounts_global_sync_enable">Włącz</string>
|
||||
<string name="accounts_sync_all">Synchronizuj wszystkie konta</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Wykrycie serwisu nie powiodło się</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Nie można odświeżyć listy kolekcji</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Wykrycie serwisu nie powiodło się</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Nie można odświeżyć listy kolekcji</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">Nie można uruchomić na pierwszym planie</string>
|
||||
<string name="battery_optimization_notify_text">Wymagana biała lista optymalizacji baterii</string>
|
||||
|
|
|
@ -100,9 +100,9 @@
|
|||
<string name="accounts_global_sync_disabled">A sincronização automática pelo sistema está desativada</string>
|
||||
<string name="accounts_global_sync_enable">Ativar</string>
|
||||
<string name="accounts_sync_all">Sincronizar todas as contas</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Falha na detecção do serviço</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Não foi possível atualizar a lista da coleção</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Falha na detecção do serviço</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Não foi possível atualizar a lista da coleção</string>
|
||||
<!--Battery Optimization-->
|
||||
<!--ForegroundService-->
|
||||
<!--AppSettingsActivity-->
|
||||
|
|
|
@ -135,9 +135,9 @@
|
|||
<string name="accounts_global_sync_disabled">Синхронизация отключена на уровне устройства</string>
|
||||
<string name="accounts_global_sync_enable">Включить</string>
|
||||
<string name="accounts_sync_all">Синхронизировать все аккаунты</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Не удалось обнаружить службу</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Не удалось обновить список коллекций</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Не удалось обнаружить службу</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Не удалось обновить список коллекций</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">Невозможно запустить в приоритетном режиме</string>
|
||||
<string name="battery_optimization_notify_text">Необходимо добавить в белый список оптимизации батареи</string>
|
||||
|
|
|
@ -47,9 +47,9 @@
|
|||
<string name="account_list_empty">Vitajte v programe DAVx⁵!\n\nTeraz môžete pridať používateľský účet pre CalDAV/CardDAV .</string>
|
||||
<string name="accounts_global_sync_disabled">Automatická synchronizácia platná pre celý systém je zakázaná</string>
|
||||
<string name="accounts_global_sync_enable">Povoliť</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Zisťovanie služby zlyhalo</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Nie je možné obnoviť zoznam kolekcií</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Zisťovanie služby zlyhalo</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Nie je možné obnoviť zoznam kolekcií</string>
|
||||
<!--Battery Optimization-->
|
||||
<!--ForegroundService-->
|
||||
<!--AppSettingsActivity-->
|
||||
|
|
|
@ -43,9 +43,9 @@
|
|||
<string name="account_list_empty">Dobrodošli v DAVx⁵!\n\nZdaj lahko dodate CalDAV/CardDAV račun.</string>
|
||||
<string name="accounts_global_sync_disabled">Sistemska avtomatska sinhronizacija je izklopljena</string>
|
||||
<string name="accounts_global_sync_enable">Omogoči</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Zaznava storitve ni uspela</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Zbirke ni bilo mogoče osvežiti</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Zaznava storitve ni uspela</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Zbirke ni bilo mogoče osvežiti</string>
|
||||
<!--Battery Optimization-->
|
||||
<!--ForegroundService-->
|
||||
<!--AppSettingsActivity-->
|
||||
|
|
|
@ -83,9 +83,9 @@
|
|||
<string name="accounts_global_sync_disabled">Синхронизација је системски искључена</string>
|
||||
<string name="accounts_global_sync_enable">Укључи</string>
|
||||
<string name="accounts_sync_all">Синхронизуј све налоге</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Откривање услуге није успело</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Не могох да освежим списак збирки</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Откривање услуге није успело</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Не могох да освежим списак збирки</string>
|
||||
<!--Battery Optimization-->
|
||||
<!--ForegroundService-->
|
||||
<!--AppSettingsActivity-->
|
||||
|
|
|
@ -133,9 +133,9 @@
|
|||
<string name="accounts_global_sync_disabled">Systemomfattande automatisk synkronisering är inaktiverad</string>
|
||||
<string name="accounts_global_sync_enable">Aktivera</string>
|
||||
<string name="accounts_sync_all">Synkronisera alla konton</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Servicedetektering misslyckades</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Det gick inte att uppdatera samlingslistan</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Servicedetektering misslyckades</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Det gick inte att uppdatera samlingslistan</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">Kan inte köras i förgrunden</string>
|
||||
<string name="battery_optimization_notify_text">Vitlista för batterioptimering krävs</string>
|
||||
|
|
|
@ -66,9 +66,9 @@
|
|||
<string name="account_list_empty">Witōmy w DAVx⁵!\n\nMożesz teroz przidać kōnto CalDAV/CardDAV.</string>
|
||||
<string name="accounts_global_sync_disabled">Autōmatyczno synchrōnizacyjo dlo cołkigo systymu je zastawiōno</string>
|
||||
<string name="accounts_global_sync_enable">Włōncz</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Niy szło ôdświyżyć serwisu</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Niy szło ôdświyżyć listy kolekcyje</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Niy szło ôdświyżyć serwisu</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Niy szło ôdświyżyć listy kolekcyje</string>
|
||||
<!--Battery Optimization-->
|
||||
<!--ForegroundService-->
|
||||
<!--AppSettingsActivity-->
|
||||
|
|
|
@ -29,9 +29,9 @@
|
|||
<string name="navigation_drawer_faq">SSS</string>
|
||||
<string name="navigation_drawer_donate">Bağış yap</string>
|
||||
<string name="account_list_empty">DAVx⁵\'e hoşgeldin!\n\nŞimdi bir CalDAV/CardDAV hesabı ekleyebilirsin.</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Servis keşfi başarısız</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Kolleksiyon listesi yenilenemedi</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Servis keşfi başarısız</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Kolleksiyon listesi yenilenemedi</string>
|
||||
<!--Battery Optimization-->
|
||||
<!--ForegroundService-->
|
||||
<!--AppSettingsActivity-->
|
||||
|
|
|
@ -80,9 +80,9 @@
|
|||
<string name="accounts_global_sync_disabled">Автоматичну синхронізацію вимкнено зі сторони системи</string>
|
||||
<string name="accounts_global_sync_enable">Увімкнути</string>
|
||||
<string name="accounts_sync_all">Синхронізувати всі обліківки</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Не вдалося виявити сервіси</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Не вдалося оновити перелік колекції</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Не вдалося виявити сервіси</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Не вдалося оновити перелік колекції</string>
|
||||
<!--Battery Optimization-->
|
||||
<!--ForegroundService-->
|
||||
<!--AppSettingsActivity-->
|
||||
|
|
|
@ -127,9 +127,9 @@
|
|||
<string name="accounts_global_sync_disabled">Đồng bộ hoá tự động trên toàn hệ thống bị tắt</string>
|
||||
<string name="accounts_global_sync_enable">Bật</string>
|
||||
<string name="accounts_sync_all">Đồng bộ tất cả tài khoản</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Dò tìm dịch vụ thất bại</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Không thể làm mới danh sách bộ sưu tập</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">Dò tìm dịch vụ thất bại</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Không thể làm mới danh sách bộ sưu tập</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">Không thể chạy ở trước</string>
|
||||
<string name="battery_optimization_notify_text">Bắt buộc không tối ưu hoá pin</string>
|
||||
|
|
|
@ -72,9 +72,9 @@
|
|||
<string name="account_list_empty">歡迎使用 DAVx⁵!\n\n您現在可以新增 CalDAV/CardDAV 帳號</string>
|
||||
<string name="accounts_global_sync_disabled">操作系統的自動同步被關閉了</string>
|
||||
<string name="accounts_global_sync_enable">啟用</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">未發現遠端服務</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">無法更新清單</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">未發現遠端服務</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">無法更新清單</string>
|
||||
<!--Battery Optimization-->
|
||||
<!--ForegroundService-->
|
||||
<!--AppSettingsActivity-->
|
||||
|
|
|
@ -135,9 +135,9 @@
|
|||
<string name="accounts_global_sync_disabled">系统全局自动同步已禁用</string>
|
||||
<string name="accounts_global_sync_enable">启用</string>
|
||||
<string name="accounts_sync_all">同步所有账户</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">服务配置检测失败</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">无法刷新集合列表</string>
|
||||
<!--RefreshCollectionsWorker-->
|
||||
<string name="refresh_collections_worker_refresh_failed">服务配置检测失败</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">无法刷新集合列表</string>
|
||||
<!--Battery Optimization-->
|
||||
<string name="battery_optimization_notify_title">无法在前台运行</string>
|
||||
<string name="battery_optimization_notify_text">需要在电池优化中排除本应用</string>
|
||||
|
|
|
@ -150,9 +150,9 @@
|
|||
<string name="accounts_global_sync_enable">Enable</string>
|
||||
<string name="accounts_sync_all">Sync all accounts</string>
|
||||
|
||||
<!-- DavService -->
|
||||
<string name="dav_service_refresh_failed">Service detection failed</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Couldn\'t refresh collection list</string>
|
||||
<!-- RefreshCollectionsWorker -->
|
||||
<string name="refresh_collections_worker_refresh_failed">Service detection failed</string>
|
||||
<string name="refresh_collections_worker_refresh_couldnt_refresh">Couldn\'t refresh collection list</string>
|
||||
|
||||
<!-- Battery Optimization -->
|
||||
<string name="battery_optimization_notify_title">Can not run in foreground</string>
|
||||
|
|
Loading…
Reference in a new issue