Fix synchronization
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="account_type_sync" translatable="false">org.kotatsu.debug.sync</string>
|
||||
<string name="sync_authority_history" translatable="false">org.koitharu.kotatsu.debug.history</string>
|
||||
<string name="sync_authority_favourites" translatable="false">org.koitharu.kotatsu.debug.favourites</string>
|
||||
</resources>
|
||||
|
||||
@@ -227,13 +227,13 @@
|
||||
</provider>
|
||||
<provider
|
||||
android:name="org.koitharu.kotatsu.sync.ui.favourites.FavouritesSyncProvider"
|
||||
android:authorities="${applicationId}.favourites"
|
||||
android:authorities="@string/sync_authority_favourites"
|
||||
android:exported="false"
|
||||
android:label="@string/favourites"
|
||||
android:syncable="true" />
|
||||
<provider
|
||||
android:name="org.koitharu.kotatsu.sync.ui.history.HistorySyncProvider"
|
||||
android:authorities="${applicationId}.history"
|
||||
android:authorities="@string/sync_authority_history"
|
||||
android:exported="false"
|
||||
android:label="@string/history"
|
||||
android:syncable="true" />
|
||||
|
||||
@@ -8,7 +8,6 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
||||
import org.koitharu.kotatsu.core.github.AppUpdateRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
@@ -16,7 +15,6 @@ import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsLiveData
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.sync.domain.SyncController
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.asFlowLiveData
|
||||
@@ -27,8 +25,6 @@ class MainViewModel @Inject constructor(
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val appUpdateRepository: AppUpdateRepository,
|
||||
private val trackingRepository: TrackingRepository,
|
||||
syncController: SyncController,
|
||||
database: MangaDatabase,
|
||||
private val settings: AppSettings,
|
||||
) : BaseViewModel() {
|
||||
|
||||
@@ -61,9 +57,6 @@ class MainViewModel @Inject constructor(
|
||||
launchJob {
|
||||
appUpdateRepository.fetchUpdate()
|
||||
}
|
||||
launchJob {
|
||||
syncController.requestFullSyncAndGc(database)
|
||||
}
|
||||
}
|
||||
|
||||
fun openLastReader() {
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.koitharu.kotatsu.shelf.domain.ShelfContent
|
||||
import org.koitharu.kotatsu.shelf.domain.ShelfRepository
|
||||
import org.koitharu.kotatsu.shelf.domain.ShelfSection
|
||||
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
|
||||
import org.koitharu.kotatsu.sync.domain.SyncController
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.asFlowLiveData
|
||||
@@ -46,6 +47,7 @@ class ShelfViewModel @Inject constructor(
|
||||
private val favouritesRepository: FavouritesRepository,
|
||||
private val trackingRepository: TrackingRepository,
|
||||
private val settings: AppSettings,
|
||||
syncController: SyncController,
|
||||
networkState: NetworkState,
|
||||
) : BaseViewModel(), ListExtraProvider {
|
||||
|
||||
@@ -63,6 +65,12 @@ class ShelfViewModel @Inject constructor(
|
||||
emit(listOf(e.toErrorState(canRetry = false)))
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
|
||||
|
||||
init {
|
||||
launchJob(Dispatchers.Default) {
|
||||
syncController.requestFullSync()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getCounter(mangaId: Long): Int {
|
||||
return if (settings.isTrackerEnabled) {
|
||||
trackingRepository.getNewChaptersCount(mangaId)
|
||||
|
||||
@@ -3,20 +3,21 @@ package org.koitharu.kotatsu.sync.domain
|
||||
import android.accounts.Account
|
||||
import android.accounts.AccountManager
|
||||
import android.content.ContentResolver
|
||||
import android.content.ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import androidx.collection.ArrayMap
|
||||
import androidx.room.InvalidationTracker
|
||||
import androidx.room.withTransaction
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES
|
||||
@@ -25,24 +26,21 @@ import org.koitharu.kotatsu.core.db.TABLE_HISTORY
|
||||
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class SyncController @Inject constructor(
|
||||
@ApplicationContext context: Context,
|
||||
private val dbProvider: Provider<MangaDatabase>,
|
||||
) : InvalidationTracker.Observer(arrayOf(TABLE_HISTORY, TABLE_FAVOURITES, TABLE_FAVOURITE_CATEGORIES)) {
|
||||
|
||||
private val authorityHistory = context.getString(R.string.sync_authority_history)
|
||||
private val authorityFavourites = context.getString(R.string.sync_authority_favourites)
|
||||
private val am = AccountManager.get(context)
|
||||
private val accountType = context.getString(R.string.account_type_sync)
|
||||
private val minSyncInterval = if (BuildConfig.DEBUG) {
|
||||
TimeUnit.SECONDS.toMillis(5)
|
||||
} else {
|
||||
TimeUnit.MINUTES.toMillis(4)
|
||||
}
|
||||
private val mutex = Mutex()
|
||||
private val jobs = ArrayMap<String, Job>(2)
|
||||
private val defaultGcPeriod: Long // gc period if sync disabled
|
||||
get() = TimeUnit.HOURS.toMillis(2)
|
||||
private val defaultGcPeriod = TimeUnit.DAYS.toMillis(2) // gc period if sync disabled
|
||||
|
||||
override fun onInvalidated(tables: Set<String>) {
|
||||
requestSync(
|
||||
@@ -57,79 +55,52 @@ class SyncController @Inject constructor(
|
||||
return rawValue.toLongOrNull() ?: 0L
|
||||
}
|
||||
|
||||
fun setLastSync(account: Account, authority: String, time: Long) {
|
||||
val key = "last_sync_" + authority.substringAfterLast('.')
|
||||
am.setUserData(account, key, time.toString())
|
||||
fun observeSyncStatus(): Flow<Boolean> = callbackFlow {
|
||||
val handle = ContentResolver.addStatusChangeListener(SYNC_OBSERVER_TYPE_ACTIVE) { which ->
|
||||
trySendBlocking(which and SYNC_OBSERVER_TYPE_ACTIVE != 0)
|
||||
}
|
||||
awaitClose { ContentResolver.removeStatusChangeListener(handle) }
|
||||
}
|
||||
|
||||
suspend fun requestFullSync() = withContext(Dispatchers.Default) {
|
||||
requestSyncImpl(favourites = true, history = true, db = null)
|
||||
}
|
||||
|
||||
suspend fun requestFullSyncAndGc(database: MangaDatabase) = withContext(Dispatchers.Default) {
|
||||
requestSyncImpl(favourites = true, history = true, db = database)
|
||||
requestSyncImpl(favourites = true, history = true)
|
||||
}
|
||||
|
||||
private fun requestSync(favourites: Boolean, history: Boolean) = processLifecycleScope.launch(Dispatchers.Default) {
|
||||
requestSyncImpl(favourites = favourites, history = history, db = null)
|
||||
requestSyncImpl(favourites = favourites, history = history)
|
||||
}
|
||||
|
||||
private suspend fun requestSyncImpl(favourites: Boolean, history: Boolean, db: MangaDatabase?) = mutex.withLock {
|
||||
private suspend fun requestSyncImpl(favourites: Boolean, history: Boolean) = mutex.withLock {
|
||||
if (!favourites && !history) {
|
||||
return
|
||||
}
|
||||
val db = dbProvider.get()
|
||||
val account = peekAccount()
|
||||
if (account == null || !ContentResolver.getMasterSyncAutomatically()) {
|
||||
db?.gc(favourites, history)
|
||||
db.gc(favourites, history)
|
||||
return
|
||||
}
|
||||
var gcHistory = false
|
||||
var gcFavourites = false
|
||||
if (favourites) {
|
||||
if (ContentResolver.getSyncAutomatically(account, AUTHORITY_FAVOURITES)) {
|
||||
scheduleSync(account, AUTHORITY_FAVOURITES)
|
||||
if (ContentResolver.getSyncAutomatically(account, authorityFavourites)) {
|
||||
ContentResolver.requestSync(account, authorityFavourites, Bundle.EMPTY)
|
||||
} else {
|
||||
gcFavourites = true
|
||||
}
|
||||
}
|
||||
if (history) {
|
||||
if (ContentResolver.getSyncAutomatically(account, AUTHORITY_HISTORY)) {
|
||||
scheduleSync(account, AUTHORITY_HISTORY)
|
||||
if (ContentResolver.getSyncAutomatically(account, authorityHistory)) {
|
||||
ContentResolver.requestSync(account, authorityHistory, Bundle.EMPTY)
|
||||
} else {
|
||||
gcHistory = true
|
||||
}
|
||||
}
|
||||
if (db != null && (gcHistory || gcFavourites)) {
|
||||
if (gcHistory || gcFavourites) {
|
||||
db.gc(gcFavourites, gcHistory)
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleSync(account: Account, authority: String) {
|
||||
if (ContentResolver.isSyncActive(account, authority) || ContentResolver.isSyncPending(account, authority)) {
|
||||
return
|
||||
}
|
||||
val job = jobs[authority]
|
||||
if (job?.isActive == true) {
|
||||
// already scheduled
|
||||
return
|
||||
}
|
||||
val lastSyncTime = getLastSync(account, authority)
|
||||
val timeLeft = System.currentTimeMillis() - lastSyncTime + minSyncInterval
|
||||
if (timeLeft <= 0) {
|
||||
jobs.remove(authority)
|
||||
ContentResolver.requestSync(account, authority, Bundle.EMPTY)
|
||||
} else {
|
||||
jobs[authority] = processLifecycleScope.launch(Dispatchers.Default) {
|
||||
try {
|
||||
delay(timeLeft)
|
||||
} finally {
|
||||
// run even if scope cancelled
|
||||
ContentResolver.requestSync(account, authority, Bundle.EMPTY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun peekAccount(): Account? {
|
||||
return am.getAccountsByType(accountType).firstOrNull()
|
||||
}
|
||||
@@ -144,4 +115,14 @@ class SyncController @Inject constructor(
|
||||
favouriteCategoriesDao.gc(deletedAt)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun setLastSync(context: Context, account: Account, authority: String, time: Long) {
|
||||
val key = "last_sync_" + authority.substringAfterLast('.')
|
||||
val am = AccountManager.get(context)
|
||||
am.setUserData(account, key, time.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package org.koitharu.kotatsu.sync.domain
|
||||
|
||||
import android.accounts.Account
|
||||
import android.content.*
|
||||
import android.content.ContentProviderClient
|
||||
import android.content.ContentProviderOperation
|
||||
import android.content.ContentProviderResult
|
||||
import android.content.Context
|
||||
import android.content.OperationApplicationException
|
||||
import android.content.SyncResult
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import androidx.annotation.WorkerThread
|
||||
@@ -11,7 +16,12 @@ import okhttp3.Request
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.db.*
|
||||
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES
|
||||
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES
|
||||
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
|
||||
import org.koitharu.kotatsu.core.db.TABLE_MANGA
|
||||
import org.koitharu.kotatsu.core.db.TABLE_MANGA_TAGS
|
||||
import org.koitharu.kotatsu.core.db.TABLE_TAGS
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSONTo
|
||||
import org.koitharu.kotatsu.sync.data.SyncAuthApi
|
||||
import org.koitharu.kotatsu.sync.data.SyncAuthenticator
|
||||
@@ -23,9 +33,6 @@ import org.koitharu.kotatsu.utils.ext.toJson
|
||||
import org.koitharu.kotatsu.utils.ext.toRequestBody
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
const val AUTHORITY_HISTORY = "org.koitharu.kotatsu.history"
|
||||
const val AUTHORITY_FAVOURITES = "org.koitharu.kotatsu.favourites"
|
||||
|
||||
private const val FIELD_TIMESTAMP = "timestamp"
|
||||
|
||||
/**
|
||||
@@ -38,6 +45,8 @@ class SyncHelper(
|
||||
private val provider: ContentProviderClient,
|
||||
) {
|
||||
|
||||
private val authorityHistory = context.getString(R.string.sync_authority_history)
|
||||
private val authorityFavourites = context.getString(R.string.sync_authority_favourites)
|
||||
private val httpClient = OkHttpClient.Builder()
|
||||
.authenticator(SyncAuthenticator(context, account, SyncAuthApi(context, OkHttpClient())))
|
||||
.addInterceptor(SyncInterceptor(context, account))
|
||||
@@ -86,13 +95,13 @@ class SyncHelper(
|
||||
}
|
||||
|
||||
private fun upsertHistory(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
|
||||
val uri = uri(AUTHORITY_HISTORY, TABLE_HISTORY)
|
||||
val uri = uri(authorityHistory, TABLE_HISTORY)
|
||||
val operations = ArrayList<ContentProviderOperation>()
|
||||
operations += ContentProviderOperation.newDelete(uri)
|
||||
.withSelection("updated_at < ?", arrayOf(timestamp.toString()))
|
||||
.build()
|
||||
json.mapJSONTo(operations) { jo ->
|
||||
operations.addAll(upsertManga(jo.removeJSONObject("manga"), AUTHORITY_HISTORY))
|
||||
operations.addAll(upsertManga(jo.removeJSONObject("manga"), authorityHistory))
|
||||
ContentProviderOperation.newInsert(uri)
|
||||
.withValues(jo.toContentValues())
|
||||
.build()
|
||||
@@ -101,7 +110,7 @@ class SyncHelper(
|
||||
}
|
||||
|
||||
private fun upsertFavouriteCategories(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
|
||||
val uri = uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITE_CATEGORIES)
|
||||
val uri = uri(authorityFavourites, TABLE_FAVOURITE_CATEGORIES)
|
||||
val operations = ArrayList<ContentProviderOperation>()
|
||||
operations += ContentProviderOperation.newDelete(uri)
|
||||
.withSelection("created_at < ?", arrayOf(timestamp.toString()))
|
||||
@@ -115,13 +124,13 @@ class SyncHelper(
|
||||
}
|
||||
|
||||
private fun upsertFavourites(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
|
||||
val uri = uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITES)
|
||||
val uri = uri(authorityFavourites, TABLE_FAVOURITES)
|
||||
val operations = ArrayList<ContentProviderOperation>()
|
||||
operations += ContentProviderOperation.newDelete(uri)
|
||||
.withSelection("created_at < ?", arrayOf(timestamp.toString()))
|
||||
.build()
|
||||
json.mapJSONTo(operations) { jo ->
|
||||
operations.addAll(upsertManga(jo.removeJSONObject("manga"), AUTHORITY_FAVOURITES))
|
||||
operations.addAll(upsertManga(jo.removeJSONObject("manga"), authorityFavourites))
|
||||
ContentProviderOperation.newInsert(uri)
|
||||
.withValues(jo.toContentValues())
|
||||
.build()
|
||||
@@ -142,25 +151,25 @@ class SyncHelper(
|
||||
contentValuesOf(
|
||||
"manga_id" to json.getLong("manga_id"),
|
||||
"tag_id" to tag.getLong("tag_id"),
|
||||
)
|
||||
),
|
||||
).build()
|
||||
}
|
||||
result.add(
|
||||
0,
|
||||
ContentProviderOperation.newInsert(uri(authority, TABLE_MANGA))
|
||||
.withValues(json.toContentValues())
|
||||
.build()
|
||||
.build(),
|
||||
)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getHistory(): JSONArray {
|
||||
return provider.query(AUTHORITY_HISTORY, TABLE_HISTORY).use { cursor ->
|
||||
return provider.query(authorityHistory, TABLE_HISTORY).use { cursor ->
|
||||
val json = JSONArray()
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
val jo = cursor.toJson()
|
||||
jo.put("manga", getManga(AUTHORITY_HISTORY, jo.getLong("manga_id")))
|
||||
jo.put("manga", getManga(authorityHistory, jo.getLong("manga_id")))
|
||||
json.put(jo)
|
||||
} while (cursor.moveToNext())
|
||||
}
|
||||
@@ -169,12 +178,12 @@ class SyncHelper(
|
||||
}
|
||||
|
||||
private fun getFavourites(): JSONArray {
|
||||
return provider.query(AUTHORITY_FAVOURITES, TABLE_FAVOURITES).use { cursor ->
|
||||
return provider.query(authorityFavourites, TABLE_FAVOURITES).use { cursor ->
|
||||
val json = JSONArray()
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
val jo = cursor.toJson()
|
||||
jo.put("manga", getManga(AUTHORITY_FAVOURITES, jo.getLong("manga_id")))
|
||||
jo.put("manga", getManga(authorityFavourites, jo.getLong("manga_id")))
|
||||
json.put(jo)
|
||||
} while (cursor.moveToNext())
|
||||
}
|
||||
@@ -183,7 +192,7 @@ class SyncHelper(
|
||||
}
|
||||
|
||||
private fun getFavouriteCategories(): JSONArray {
|
||||
return provider.query(AUTHORITY_FAVOURITES, TABLE_FAVOURITE_CATEGORIES).use { cursor ->
|
||||
return provider.query(authorityFavourites, TABLE_FAVOURITE_CATEGORIES).use { cursor ->
|
||||
val json = JSONArray()
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
@@ -247,15 +256,15 @@ class SyncHelper(
|
||||
val deletedAt = System.currentTimeMillis() - defaultGcPeriod
|
||||
val selection = "deleted_at != 0 AND deleted_at < ?"
|
||||
val args = arrayOf(deletedAt.toString())
|
||||
provider.delete(uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITES), selection, args)
|
||||
provider.delete(uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITE_CATEGORIES), selection, args)
|
||||
provider.delete(uri(authorityFavourites, TABLE_FAVOURITES), selection, args)
|
||||
provider.delete(uri(authorityFavourites, TABLE_FAVOURITE_CATEGORIES), selection, args)
|
||||
}
|
||||
|
||||
private fun gcHistory() {
|
||||
val deletedAt = System.currentTimeMillis() - defaultGcPeriod
|
||||
val selection = "deleted_at != 0 AND deleted_at < ?"
|
||||
val args = arrayOf(deletedAt.toString())
|
||||
provider.delete(uri(AUTHORITY_HISTORY, TABLE_HISTORY), selection, args)
|
||||
provider.delete(uri(authorityHistory, TABLE_HISTORY), selection, args)
|
||||
}
|
||||
|
||||
private fun ContentProviderClient.query(authority: String, table: String): Cursor {
|
||||
|
||||
@@ -27,7 +27,7 @@ class FavouritesSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(cont
|
||||
val syncHelper = SyncHelper(context, account, provider)
|
||||
runCatchingCancellable {
|
||||
syncHelper.syncFavourites(syncResult)
|
||||
SyncController(context).setLastSync(account, authority, System.currentTimeMillis())
|
||||
SyncController.setLastSync(context, account, authority, System.currentTimeMillis())
|
||||
}.onFailure(syncResult::onError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class HistorySyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context
|
||||
val syncHelper = SyncHelper(context, account, provider)
|
||||
runCatchingCancellable {
|
||||
syncHelper.syncHistory(syncResult)
|
||||
SyncController(context).setLastSync(account, authority, System.currentTimeMillis())
|
||||
SyncController.setLastSync(context, account, authority, System.currentTimeMillis())
|
||||
}.onFailure(syncResult::onError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<string name="mal_clientId" translatable="false">6cd8e6349e9a36bc1fc1ab97703c9fd1</string>
|
||||
<string name="acra_login" translatable="false">SxhkCVnqVLbGogvi</string>
|
||||
<string name="acra_password" translatable="false">xPDACTLHnHU9Nfjv</string>
|
||||
<string name="sync_authority_history" translatable="false">org.koitharu.kotatsu.history</string>
|
||||
<string name="sync_authority_favourites" translatable="false">org.koitharu.kotatsu.favourites</string>
|
||||
<string-array name="values_theme" translatable="false">
|
||||
<item>-1</item>
|
||||
<item>1</item>
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accountPreferences="@xml/pref_sync"
|
||||
android:accountType="@string/account_type_sync"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name" />
|
||||
android:icon="@mipmap/ic_launcher_round"
|
||||
android:label="@string/app_name" />
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accountType="@string/account_type_sync"
|
||||
android:allowParallelSyncs="false"
|
||||
android:contentAuthority="${applicationId}.favourites"
|
||||
android:contentAuthority="@string/sync_authority_favourites"
|
||||
android:isAlwaysSyncable="true"
|
||||
android:supportsUploading="true"
|
||||
android:userVisible="true" />
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accountType="@string/account_type_sync"
|
||||
android:allowParallelSyncs="false"
|
||||
android:contentAuthority="${applicationId}.history"
|
||||
android:contentAuthority="@string/sync_authority_history"
|
||||
android:isAlwaysSyncable="true"
|
||||
android:supportsUploading="true"
|
||||
android:userVisible="true" />
|
||||
|
||||
Reference in New Issue
Block a user