Create an option to pre-select the read-only setting for address books (bitfireAT/davx5#141)

* [WIP] add managed restriction to force read-only addressbooks
* Honor app-wide read-only address book setting when syncing address books
* reflect status of force read-only address books setting in the GUI

Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
This commit is contained in:
Ricki Hirner
2022-10-12 17:29:23 +02:00
parent e14460111e
commit d2614bd87e
6 changed files with 69 additions and 11 deletions

View File

@@ -46,7 +46,16 @@ open class LocalAddressBook(
const val USER_DATA_URL = "url"
const val USER_DATA_READ_ONLY = "read_only"
fun create(context: Context, provider: ContentProviderClient, mainAccount: Account, info: Collection): LocalAddressBook {
/**
* Creates a local address book.
*
* @param context app context to resolve string resources
* @param provider contacts provider client
* @param mainAccount main account this address book (account) belongs to
* @param info collection where to take the name and settings from
* @param forceReadOnly `true`: set the address book to "force read-only"; `false`: determine read-only flag from [info]
*/
fun create(context: Context, provider: ContentProviderClient, mainAccount: Account, info: Collection, forceReadOnly: Boolean): LocalAddressBook {
val account = Account(accountName(mainAccount, info), context.getString(R.string.account_type_address_book))
val userData = initialUserData(mainAccount, info.url.toString())
Logger.log.log(Level.INFO, "Creating local address book $account", userData)
@@ -61,7 +70,7 @@ open class LocalAddressBook(
values.put(ContactsContract.Settings.SHOULD_SYNC, 1)
values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, 1)
addressBook.settings = values
addressBook.readOnly = !info.privWriteContent || info.forceReadOnly
addressBook.readOnly = forceReadOnly || !info.privWriteContent || info.forceReadOnly
return addressBook
}
@@ -199,7 +208,13 @@ open class LocalAddressBook(
return number
}
fun update(info: Collection) {
/**
* Updates the address book settings.
*
* @param info collection where to take the settings from
* @param forceReadOnly `true`: set the address book to "force read-only"; `false`: determine read-only flag from [info]
*/
fun update(info: Collection, forceReadOnly: Boolean) {
val newAccountName = accountName(mainAccount, info)
if (account.name != newAccountName && Build.VERSION.SDK_INT >= 21) {
@@ -209,7 +224,7 @@ open class LocalAddressBook(
account = future.result
}
val nowReadOnly = !info.privWriteContent || info.forceReadOnly
val nowReadOnly = forceReadOnly || !info.privWriteContent || info.forceReadOnly
if (nowReadOnly != readOnly) {
Constants.log.info("Address book now read-only = $nowReadOnly, updating contacts")

View File

@@ -26,7 +26,8 @@ class DefaultsProvider(
override val booleanDefaults = mutableMapOf(
Pair(Settings.DISTRUST_SYSTEM_CERTIFICATES, false),
Pair(Settings.SYNC_ALL_COLLECTIONS, false)
Pair(Settings.SYNC_ALL_COLLECTIONS, false),
Pair(Settings.FORCE_READ_ONLY_ADDRESSBOOKS, false)
)
override val intDefaults = mapOf(

View File

@@ -38,5 +38,8 @@ object Settings {
/** whether detected collections are selected for synchronization for default */
const val SYNC_ALL_COLLECTIONS = "sync_all_collections"
/** whether all address books are forced to be read-only */
const val FORCE_READ_ONLY_ADDRESSBOOKS = "force_read_only_addressbooks"
}

View File

@@ -21,6 +21,12 @@ import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.resource.LocalAddressBook
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.settings.SettingsManager
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import java.util.logging.Level
@@ -35,6 +41,15 @@ class AddressBooksSyncAdapterService : SyncAdapterService() {
appDatabase: AppDatabase
) : SyncAdapter(context, appDatabase) {
@EntryPoint
@InstallIn(SingletonComponent::class)
interface AddressBooksSyncAdapterEntryPoint {
fun settingsManager(): SettingsManager
}
val entryPoint = EntryPointAccessors.fromApplication(context, AddressBooksSyncAdapterEntryPoint::class.java)
val settingsManager = entryPoint.settingsManager()
override fun sync(account: Account, extras: Bundle, authority: String, httpClient: Lazy<HttpClient>, provider: ContentProviderClient, syncResult: SyncResult) {
try {
val accountSettings = AccountSettings(context, account)
@@ -85,6 +100,8 @@ class AddressBooksSyncAdapterService : SyncAdapterService() {
return false
}
val forceAllReadOnly = settingsManager.getBoolean(Settings.FORCE_READ_ONLY_ADDRESSBOOKS)
// delete/update local address books
for (addressBook in LocalAddressBook.findAll(context, contactsProvider, account)) {
val url = addressBook.url.toHttpUrl()
@@ -96,7 +113,7 @@ class AddressBooksSyncAdapterService : SyncAdapterService() {
// remote CollectionInfo found for this local collection, update data
try {
Logger.log.log(Level.FINE, "Updating local address book $url", info)
addressBook.update(info)
addressBook.update(info, forceAllReadOnly)
} catch (e: Exception) {
Logger.log.log(Level.WARNING, "Couldn't rename address book account", e)
}
@@ -108,7 +125,7 @@ class AddressBooksSyncAdapterService : SyncAdapterService() {
// create new local address books
for ((_, info) in remoteAddressBooks) {
Logger.log.log(Level.INFO, "Adding local address book", info)
LocalAddressBook.create(context, contactsProvider, account, info)
LocalAddressBook.create(context, contactsProvider, account, info, forceAllReadOnly)
}
} finally {
contactsProvider?.closeCompat()

View File

@@ -11,6 +11,12 @@ import at.bitfire.davdroid.PermissionUtils
import at.bitfire.davdroid.R
import at.bitfire.davdroid.databinding.AccountCarddavItemBinding
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.settings.SettingsManager
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
class AddressBooksFragment: CollectionsFragment() {
@@ -53,9 +59,18 @@ class AddressBooksFragment: CollectionsFragment() {
class AddressBookViewHolder(
parent: ViewGroup,
accountModel: AccountActivity.Model,
val fragmentManager: FragmentManager
val fragmentManager: FragmentManager,
): CollectionViewHolder<AccountCarddavItemBinding>(parent, AccountCarddavItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), accountModel) {
@EntryPoint
@InstallIn(SingletonComponent::class)
interface AddressBookViewHolderEntryPoint {
fun settingsManager(): SettingsManager
}
private val settings = EntryPointAccessors.fromApplication(parent.context, AddressBookViewHolderEntryPoint::class.java).settingsManager()
private val forceReadOnlyAddressBooks = settings.getBoolean(Settings.FORCE_READ_ONLY_ADDRESSBOOKS) // managed restriction
override fun bindTo(item: Collection) {
binding.sync.isChecked = item.sync
binding.title.text = item.title()
@@ -67,12 +82,12 @@ class AddressBooksFragment: CollectionsFragment() {
binding.description.visibility = View.VISIBLE
}
binding.readOnly.visibility = if (item.readOnly()) View.VISIBLE else View.GONE
binding.readOnly.visibility = if (item.readOnly() || forceReadOnlyAddressBooks) View.VISIBLE else View.GONE
itemView.setOnClickListener {
accountModel.toggleSync(item)
}
binding.actionOverflow.setOnClickListener(CollectionPopupListener(accountModel, item, fragmentManager))
binding.actionOverflow.setOnClickListener(CollectionPopupListener(accountModel, item, fragmentManager, forceReadOnlyAddressBooks))
}
}

View File

@@ -226,7 +226,8 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList
class CollectionPopupListener(
private val accountModel: AccountActivity.Model,
private val item: Collection,
private val fragmentManager: FragmentManager
private val fragmentManager: FragmentManager,
private val forceReadOnly: Boolean = false
): View.OnClickListener {
override fun onClick(anchor: View) {
@@ -244,6 +245,12 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList
else
isVisible = false
}
if (item.type == Collection.TYPE_ADDRESSBOOK && forceReadOnly) {
// managed restriction "force read-only address books" is active
isChecked = true
isEnabled = false
}
}
popup.menu.findItem(R.id.delete_collection).isVisible = item.privUnbind