Contacts creator improvements to be able to choose where to store newly created contact

This commit is contained in:
Sylvain Berfini 2020-08-19 16:32:15 +02:00
parent c28f2373d9
commit 03f0f49c71
16 changed files with 329 additions and 98 deletions

View file

@ -0,0 +1,44 @@
package org.linphone.activities.main.contact.adapters
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ImageView
import android.widget.TextView
import kotlin.collections.ArrayList
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
class SyncAccountAdapter : BaseAdapter() {
private var accounts: ArrayList<Triple<String, String, Drawable>> = arrayListOf()
init {
accounts.addAll(coreContext.contactsManager.getAvailableSyncAccounts())
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view: View = convertView ?: LayoutInflater.from(parent.context).inflate(R.layout.contact_sync_account_picker_cell, parent, false)
val account = getItem(position)
val icon = view.findViewById<ImageView>(R.id.account_icon)
icon.setImageDrawable(account.third)
val name = view.findViewById<TextView>(R.id.account_name)
name.text = account.first
return view
}
override fun getItem(position: Int): Triple<String, String, Drawable> {
return accounts[position]
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getCount(): Int {
return accounts.size
}
}

View file

@ -47,7 +47,7 @@ import org.linphone.utils.FileUtils
import org.linphone.utils.ImageUtils import org.linphone.utils.ImageUtils
import org.linphone.utils.PermissionHelper import org.linphone.utils.PermissionHelper
class ContactEditorFragment : Fragment() { class ContactEditorFragment : Fragment(), SyncAccountPickerFragment.SyncAccountPickedListener {
private lateinit var binding: ContactEditorFragmentBinding private lateinit var binding: ContactEditorFragmentBinding
private lateinit var viewModel: ContactEditorViewModel private lateinit var viewModel: ContactEditorViewModel
private lateinit var sharedViewModel: SharedMainViewModel private lateinit var sharedViewModel: SharedMainViewModel
@ -86,13 +86,11 @@ class ContactEditorFragment : Fragment() {
} }
binding.setSaveChangesClickListener { binding.setSaveChangesClickListener {
val savedContact = viewModel.save() if (viewModel.c == null) {
if (savedContact is NativeContact) { Log.i("[Contact Editor] New contact, ask user where to store it")
savedContact.syncValuesFromAndroidContact(requireContext()) SyncAccountPickerFragment(this).show(childFragmentManager, "SyncAccountPicker")
Log.i("[Contact Editor] Displaying contact $savedContact")
navigateToContact(savedContact)
} else { } else {
findNavController().popBackStack() saveContact()
} }
} }
@ -114,6 +112,13 @@ class ContactEditorFragment : Fragment() {
} }
} }
override fun onSyncAccountClicked(name: String?, type: String?) {
Log.i("[Contact Editor] Using account $name / $type")
viewModel.syncAccountName = name
viewModel.syncAccountType = type
saveContact()
}
override fun onRequestPermissionsResult( override fun onRequestPermissionsResult(
requestCode: Int, requestCode: Int,
permissions: Array<out String>, permissions: Array<out String>,
@ -142,6 +147,17 @@ class ContactEditorFragment : Fragment() {
} }
} }
private fun saveContact() {
val savedContact = viewModel.save()
if (savedContact is NativeContact) {
savedContact.syncValuesFromAndroidContact(requireContext())
Log.i("[Contact Editor] Displaying contact $savedContact")
navigateToContact(savedContact)
} else {
findNavController().popBackStack()
}
}
private fun pickFile() { private fun pickFile() {
val cameraIntents = ArrayList<Intent>() val cameraIntents = ArrayList<Intent>()

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.activities.main.contact.fragments
import android.os.Bundle
import android.view.*
import androidx.fragment.app.DialogFragment
import org.linphone.R
import org.linphone.activities.main.contact.adapters.SyncAccountAdapter
import org.linphone.core.tools.Log
import org.linphone.databinding.ContactSyncAccountPickerFragmentBinding
class SyncAccountPickerFragment(private val listener: SyncAccountPickedListener) : DialogFragment() {
private lateinit var binding: ContactSyncAccountPickerFragmentBinding
private lateinit var adapter: SyncAccountAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.assistant_country_dialog_style)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = ContactSyncAccountPickerFragmentBinding.inflate(inflater, container, false)
adapter = SyncAccountAdapter()
binding.accountsList.adapter = adapter
binding.accountsList.setOnItemClickListener { _, _, position, _ ->
if (position >= 0 && position < adapter.count) {
val account = adapter.getItem(position)
Log.i("[Sync Account Picker] Picked ${account.first} / ${account.second}")
listener.onSyncAccountClicked(account.first, account.second)
}
dismiss()
}
binding.setLocalSyncAccountClickListener {
Log.i("[Sync Account Picker] Picked local account")
listener.onSyncAccountClicked(null, null)
dismiss()
}
return binding.root
}
interface SyncAccountPickedListener {
fun onSyncAccountClicked(name: String?, type: String?)
}
}

View file

@ -29,10 +29,8 @@ import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.contact.* import org.linphone.contact.*
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.AppUtils
import org.linphone.utils.ImageUtils import org.linphone.utils.ImageUtils
import org.linphone.utils.PermissionHelper import org.linphone.utils.PermissionHelper
@ -66,6 +64,9 @@ class ContactEditorViewModel(val c: Contact?) : ViewModel(), ContactViewModelInt
val addresses = MutableLiveData<ArrayList<NumberOrAddressEditorViewModel>>() val addresses = MutableLiveData<ArrayList<NumberOrAddressEditorViewModel>>()
var syncAccountName: String? = null
var syncAccountType: String? = null
init { init {
if (c != null) { if (c != null) {
contact.value = c contact.value = c
@ -83,12 +84,7 @@ class ContactEditorViewModel(val c: Contact?) : ViewModel(), ContactViewModelInt
if (contact == null) { if (contact == null) {
created = true created = true
contact = if (PermissionHelper.get().hasWriteContactsPermission()) { contact = if (PermissionHelper.get().hasWriteContactsPermission()) {
// Store native contact in default sync account NativeContact(NativeContactEditor.createAndroidContact(syncAccountName, syncAccountType).toString())
if (corePreferences.storeCreatedContactsInAppSyncAccount) {
NativeContact(NativeContactEditor.createAndroidContact(AppUtils.getString(R.string.sync_account_name), AppUtils.getString(R.string.sync_account_type)).toString())
} else {
NativeContact(NativeContactEditor.createAndroidContact(null, null).toString())
}
} else { } else {
Contact() Contact()
} }

View file

@ -75,7 +75,7 @@ class ContactViewModel(private val c: Contact) : ErrorReportingViewModel(), Cont
override fun onContactUpdated(contact: Contact) { override fun onContactUpdated(contact: Contact) {
if (c is NativeContact && contact is NativeContact && c.nativeId == contact.nativeId) { if (c is NativeContact && contact is NativeContact && c.nativeId == contact.nativeId) {
Log.d("[Contact] $contact has changed") Log.d("[Contact] $contact has changed")
updateNumbersAndAddresses() updateNumbersAndAddresses(contact)
} }
} }
} }
@ -122,7 +122,7 @@ class ContactViewModel(private val c: Contact) : ErrorReportingViewModel(), Cont
init { init {
contact.value = c contact.value = c
updateNumbersAndAddresses() updateNumbersAndAddresses(c)
coreContext.contactsManager.addListener(contactsUpdatedListener) coreContext.contactsManager.addListener(contactsUpdatedListener)
waitForChatRoomCreation.value = false waitForChatRoomCreation.value = false
} }
@ -162,24 +162,24 @@ class ContactViewModel(private val c: Contact) : ErrorReportingViewModel(), Cont
} }
} }
private fun updateNumbersAndAddresses() { private fun updateNumbersAndAddresses(contact: Contact) {
val list = arrayListOf<ContactNumberOrAddressViewModel>() val list = arrayListOf<ContactNumberOrAddressViewModel>()
for (address in c.sipAddresses) { for (address in contact.sipAddresses) {
val value = address.asStringUriOnly() val value = address.asStringUriOnly()
val presenceModel = c.friend?.getPresenceModelForUriOrTel(value) val presenceModel = contact.friend?.getPresenceModelForUriOrTel(value)
val hasPresence = presenceModel?.basicStatus == PresenceBasicStatus.Open val hasPresence = presenceModel?.basicStatus == PresenceBasicStatus.Open
val isMe = coreContext.core.defaultProxyConfig?.identityAddress?.weakEqual(address) ?: false val isMe = coreContext.core.defaultProxyConfig?.identityAddress?.weakEqual(address) ?: false
val secureChatAllowed = !isMe && c.friend?.getPresenceModelForUriOrTel(value)?.hasCapability(FriendCapability.LimeX3Dh) ?: false val secureChatAllowed = !isMe && contact.friend?.getPresenceModelForUriOrTel(value)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
val noa = ContactNumberOrAddressViewModel(address, hasPresence, LinphoneUtils.getDisplayName(address), showSecureChat = secureChatAllowed, listener = listener) val noa = ContactNumberOrAddressViewModel(address, hasPresence, LinphoneUtils.getDisplayName(address), showSecureChat = secureChatAllowed, listener = listener)
list.add(noa) list.add(noa)
} }
for (number in c.phoneNumbers) { for (number in contact.phoneNumbers) {
val presenceModel = c.friend?.getPresenceModelForUriOrTel(number) val presenceModel = contact.friend?.getPresenceModelForUriOrTel(number)
val hasPresence = presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open val hasPresence = presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open
val contactAddress = presenceModel?.contact ?: number val contactAddress = presenceModel?.contact ?: number
val address = coreContext.core.interpretUrl(contactAddress) val address = coreContext.core.interpretUrl(contactAddress)
val isMe = if (address != null) coreContext.core.defaultProxyConfig?.identityAddress?.weakEqual(address) ?: false else false val isMe = if (address != null) coreContext.core.defaultProxyConfig?.identityAddress?.weakEqual(address) ?: false else false
val secureChatAllowed = !isMe && c.friend?.getPresenceModelForUriOrTel(number)?.hasCapability(FriendCapability.LimeX3Dh) ?: false val secureChatAllowed = !isMe && contact.friend?.getPresenceModelForUriOrTel(number)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
val noa = ContactNumberOrAddressViewModel(address, hasPresence, number, isSip = false, showSecureChat = secureChatAllowed, listener = listener) val noa = ContactNumberOrAddressViewModel(address, hasPresence, number, isSip = false, showSecureChat = secureChatAllowed, listener = listener)
list.add(noa) list.add(noa)
} }

View file

@ -38,13 +38,6 @@ class ContactsSettingsViewModel : GenericSettingsViewModel() {
} }
val friendListSubscribe = MutableLiveData<Boolean>() val friendListSubscribe = MutableLiveData<Boolean>()
val contactStorageAppSyncAccountListener = object : SettingListenerStub() {
override fun onBoolValueChanged(newValue: Boolean) {
prefs.storeCreatedContactsInAppSyncAccount = newValue
}
}
val contactStorageAppSyncAccount = MutableLiveData<Boolean>()
val nativePresenceListener = object : SettingListenerStub() { val nativePresenceListener = object : SettingListenerStub() {
override fun onBoolValueChanged(newValue: Boolean) { override fun onBoolValueChanged(newValue: Boolean) {
if (newValue) { if (newValue) {
@ -80,7 +73,6 @@ class ContactsSettingsViewModel : GenericSettingsViewModel() {
readContactsPermissionGranted.value = PermissionHelper.get().hasReadContactsPermission() readContactsPermissionGranted.value = PermissionHelper.get().hasReadContactsPermission()
friendListSubscribe.value = core.isFriendListSubscriptionEnabled friendListSubscribe.value = core.isFriendListSubscriptionEnabled
contactStorageAppSyncAccount.value = prefs.storeCreatedContactsInAppSyncAccount
nativePresence.value = prefs.storePresenceInNativeContact nativePresence.value = prefs.storePresenceInNativeContact
showOrganization.value = prefs.displayOrganization showOrganization.value = prefs.displayOrganization
launcherShortcuts.value = prefs.contactsShortcuts launcherShortcuts.value = prefs.contactsShortcuts

View file

@ -163,4 +163,8 @@ open class Contact : Comparable<Contact> {
if (presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open) return presenceModel.contact if (presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open) return presenceModel.contact
return null return null
} }
override fun toString(): String {
return "${super.toString()}: name [$fullName]"
}
} }

View file

@ -21,9 +21,11 @@ package org.linphone.contact
import android.accounts.Account import android.accounts.Account
import android.accounts.AccountManager import android.accounts.AccountManager
import android.accounts.AuthenticatorDescription
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context import android.content.Context
import android.database.ContentObserver import android.database.ContentObserver
import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.os.AsyncTask import android.os.AsyncTask
import android.os.AsyncTask.THREAD_POOL_EXECUTOR import android.os.AsyncTask.THREAD_POOL_EXECUTOR
@ -193,7 +195,10 @@ class ContactsManager(private val context: Context) {
// Restart contacts async fetching // Restart contacts async fetching
fetchContactsAsync() fetchContactsAsync()
} else {
Log.d("[Contacts Manager] Found contact with id [$id]: ${found?.fullName}")
} }
return found return found
} }
@ -249,7 +254,11 @@ class ContactsManager(private val context: Context) {
val accountManager = context.getSystemService(Context.ACCOUNT_SERVICE) as AccountManager val accountManager = context.getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
val accounts = accountManager.getAccountsByType(context.getString(R.string.sync_account_type)) val accounts = accountManager.getAccountsByType(context.getString(R.string.sync_account_type))
if (accounts.isEmpty()) { if (accounts.isEmpty()) {
val newAccount = Account(context.getString(R.string.sync_account_name), context.getString(R.string.sync_account_type)) val newAccount = Account(
context.getString(R.string.sync_account_name), context.getString(
R.string.sync_account_type
)
)
try { try {
accountManager.addAccountExplicitly(newAccount, null, null) accountManager.addAccountExplicitly(newAccount, null, null)
Log.i("[Contacts Manager] Contact account added") Log.i("[Contacts Manager] Contact account added")
@ -263,19 +272,28 @@ class ContactsManager(private val context: Context) {
} }
} }
fun getAvailableSyncAccounts(): List<Pair<String, String>> { fun getAvailableSyncAccounts(): List<Triple<String, String, Drawable>> {
val accountManager = context.getSystemService(Context.ACCOUNT_SERVICE) as AccountManager val accountManager = context.getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
val packageManager = context.packageManager
val syncAdapters = ContentResolver.getSyncAdapterTypes() val syncAdapters = ContentResolver.getSyncAdapterTypes()
val available = arrayListOf<Pair<String, String>>() val authenticators: Array<AuthenticatorDescription> = accountManager.authenticatorTypes
val available = arrayListOf<Triple<String, String, Drawable>>()
for (syncAdapter in syncAdapters) { for (syncAdapter in syncAdapters) {
if (syncAdapter.authority == "com.android.contacts" && syncAdapter.isUserVisible) { if (syncAdapter.authority == "com.android.contacts" && syncAdapter.isUserVisible) {
Log.i("[Contacts Manager] Found sync adapter for com.android.contacts authority: ${syncAdapter.accountType}") if (syncAdapter.supportsUploading() || syncAdapter.accountType == context.getString(R.string.sync_account_type)) {
val accounts = accountManager.getAccountsByType(syncAdapter.accountType) Log.i("[Contacts Manager] Found sync adapter for com.android.contacts authority: ${syncAdapter.accountType}")
for (account in accounts) { val accounts = accountManager.getAccountsByType(syncAdapter.accountType)
Log.i("[Contacts Manager] Found account for account type ${syncAdapter.accountType}: ${account.name}") for (account in accounts) {
val pair = Pair(account.name, account.type) Log.i("[Contacts Manager] Found account for account type ${syncAdapter.accountType}: ${account.name}")
available.add(pair) for (authenticator in authenticators) {
if (authenticator.type == account.type) {
val drawable = packageManager.getDrawable(authenticator.packageName, authenticator.smallIconId, null)
val triple = Triple(account.name, account.type, drawable)
available.add(triple)
}
}
}
} }
} }
} }
@ -337,12 +355,15 @@ class ContactsManager(private val context: Context) {
val sipAddress = contact.getContactForPhoneNumberOrAddress(phoneNumber) val sipAddress = contact.getContactForPhoneNumberOrAddress(phoneNumber)
if (sipAddress != null) { if (sipAddress != null) {
Log.d("[Contacts Manager] Found presence information to store in native contact $contact under Linphone sync account") Log.d("[Contacts Manager] Found presence information to store in native contact $contact under Linphone sync account")
val contactEditor = NativeContactEditor(contact, context.getString(R.string.sync_account_name), context.getString(R.string.sync_account_type)) val contactEditor = NativeContactEditor(contact)
val coroutineScope = CoroutineScope(Dispatchers.Main) val coroutineScope = CoroutineScope(Dispatchers.Main)
coroutineScope.launch { coroutineScope.launch {
val deferred = async { val deferred = async {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
contactEditor.ensureSyncAccountRawIdExists().setPresenceInformation(phoneNumber, sipAddress).commit() contactEditor.setPresenceInformation(
phoneNumber,
sipAddress
).commit()
} }
} }
deferred.await() deferred.await()

View file

@ -227,4 +227,8 @@ class NativeContact(val nativeId: String, private val lookupKey: String? = null)
cursor.close() cursor.close()
} }
} }
override fun toString(): String {
return "${super.toString()}: id [$nativeId], name [$fullName]"
}
} }

View file

@ -21,7 +21,6 @@ package org.linphone.contact
import android.content.ContentProviderOperation import android.content.ContentProviderOperation
import android.content.ContentUris import android.content.ContentUris
import android.content.ContentValues
import android.net.Uri import android.net.Uri
import android.provider.ContactsContract import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds import android.provider.ContactsContract.CommonDataKinds
@ -33,20 +32,33 @@ import org.linphone.core.tools.Log
import org.linphone.utils.AppUtils import org.linphone.utils.AppUtils
import org.linphone.utils.PermissionHelper import org.linphone.utils.PermissionHelper
class NativeContactEditor( class NativeContactEditor(val contact: NativeContact) {
val contact: NativeContact,
private var syncAccountName: String? = null,
private var syncAccountType: String? = null
) {
companion object { companion object {
fun createAndroidContact(accountName: String?, accountType: String?): Long { fun createAndroidContact(accountName: String?, accountType: String?): Long {
val values = ContentValues() Log.i("[Native Contact Editor] Using sync account $accountName with type $accountType")
values.put(RawContacts.ACCOUNT_NAME, accountName)
values.put(RawContacts.ACCOUNT_TYPE, accountType)
val rawContactUri = coreContext.context.contentResolver val changes = arrayListOf<ContentProviderOperation>()
.insert(RawContacts.CONTENT_URI, values) changes.add(
return ContentUris.parseId(rawContactUri) ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
.withValue(RawContacts.ACCOUNT_NAME, accountName)
.withValue(RawContacts.ACCOUNT_TYPE, accountType)
.build()
)
val contentResolver = coreContext.context.contentResolver
val results = contentResolver.applyBatch(ContactsContract.AUTHORITY, changes)
for (result in results) {
Log.i("[Native Contact Editor] Contact creation result is ${result.uri}")
val cursor = contentResolver.query(result.uri, arrayOf(RawContacts.CONTACT_ID), null, null, null)
if (cursor != null) {
cursor.moveToNext()
val contactId: Long = cursor.getLong(0)
Log.i("[Native Contact Editor] New contact id is $contactId")
cursor.close()
return contactId
}
}
return 0
} }
} }
@ -55,8 +67,6 @@ class NativeContactEditor(
"${ContactsContract.Data.CONTACT_ID} =? AND ${ContactsContract.Data.MIMETYPE} =?" "${ContactsContract.Data.CONTACT_ID} =? AND ${ContactsContract.Data.MIMETYPE} =?"
private val phoneNumberSelection = private val phoneNumberSelection =
"$selection AND (${CommonDataKinds.Phone.NUMBER}=? OR ${CommonDataKinds.Phone.NORMALIZED_NUMBER}=?)" "$selection AND (${CommonDataKinds.Phone.NUMBER}=? OR ${CommonDataKinds.Phone.NORMALIZED_NUMBER}=?)"
private val sipAddressSelection =
"${ContactsContract.Data.CONTACT_ID} =? AND (${ContactsContract.Data.MIMETYPE} =? OR ${ContactsContract.Data.MIMETYPE} =?) AND data1=?"
private val presenceUpdateSelection = private val presenceUpdateSelection =
"${ContactsContract.Data.CONTACT_ID} =? AND ${ContactsContract.Data.MIMETYPE} =? AND data3=?" "${ContactsContract.Data.CONTACT_ID} =? AND ${ContactsContract.Data.MIMETYPE} =? AND data3=?"
private val contactUri = ContactsContract.Data.CONTENT_URI private val contactUri = ContactsContract.Data.CONTENT_URI
@ -66,11 +76,10 @@ class NativeContactEditor(
private var pictureByteArray: ByteArray? = null private var pictureByteArray: ByteArray? = null
init { init {
Log.d("[Native Contact Editor] Using sync account $syncAccountName with type $syncAccountType")
val contentResolver = coreContext.context.contentResolver val contentResolver = coreContext.context.contentResolver
val cursor = contentResolver.query( val cursor = contentResolver.query(
RawContacts.CONTENT_URI, RawContacts.CONTENT_URI,
arrayOf(RawContacts._ID, RawContacts.ACCOUNT_TYPE), arrayOf(RawContacts._ID),
"${RawContacts.CONTACT_ID} =?", "${RawContacts.CONTACT_ID} =?",
arrayOf(contact.nativeId), arrayOf(contact.nativeId),
null null
@ -79,20 +88,11 @@ class NativeContactEditor(
do { do {
if (rawId == null) { if (rawId == null) {
rawId = cursor.getString(cursor.getColumnIndex(RawContacts._ID)) rawId = cursor.getString(cursor.getColumnIndex(RawContacts._ID))
Log.d("[Native Contact Editor] Found raw id $rawId for native contact with id ${contact.nativeId}") Log.i("[Native Contact Editor] Found raw id $rawId for native contact with id ${contact.nativeId}")
} }
} while (cursor.moveToNext() && rawId == null)
val accountType = cursor.getString(cursor.getColumnIndex(RawContacts.ACCOUNT_TYPE))
if (accountType == syncAccountType && syncAccountRawId == null) {
syncAccountRawId = cursor.getString(cursor.getColumnIndex(RawContacts._ID))
Log.d("[Native Contact Editor] Found sync account raw id $syncAccountRawId for native contact with id ${contact.nativeId}")
}
} while (cursor.moveToNext() && syncAccountRawId == null)
} }
cursor?.close() cursor?.close()
// When contact has been created with NativeContactEditor.createAndroidContact this is required
if (rawId == null) rawId = contact.nativeId
} }
fun setFirstAndLastNames(firstName: String, lastName: String): NativeContactEditor { fun setFirstAndLastNames(firstName: String, lastName: String): NativeContactEditor {
@ -237,12 +237,34 @@ class NativeContactEditor(
return this return this
} }
fun ensureSyncAccountRawIdExists(): NativeContactEditor { fun setPresenceInformation(phoneNumber: String, sipAddress: String): NativeContactEditor {
if (syncAccountRawId == null) { if (syncAccountRawId == null) {
Log.w("[Native Contact Editor] Sync account raw id not found") val contentResolver = coreContext.context.contentResolver
val cursor = contentResolver.query(
RawContacts.CONTENT_URI,
arrayOf(RawContacts._ID, RawContacts.ACCOUNT_TYPE),
"${RawContacts.CONTACT_ID} =?",
arrayOf(contact.nativeId),
null
)
if (cursor?.moveToFirst() == true) {
do {
val accountType =
cursor.getString(cursor.getColumnIndex(RawContacts.ACCOUNT_TYPE))
if (accountType == AppUtils.getString(R.string.sync_account_type) && syncAccountRawId == null) {
syncAccountRawId = cursor.getString(cursor.getColumnIndex(RawContacts._ID))
Log.d("[Native Contact Editor] Found linphone raw id $syncAccountRawId for native contact with id ${contact.nativeId}")
}
} while (cursor.moveToNext() && syncAccountRawId == null)
}
cursor?.close()
}
if (syncAccountRawId == null) {
Log.w("[Native Contact Editor] Linphone raw id not found")
val insert = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) val insert = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
.withValue(RawContacts.ACCOUNT_TYPE, syncAccountType) .withValue(RawContacts.ACCOUNT_NAME, AppUtils.getString(R.string.sync_account_name))
.withValue(RawContacts.ACCOUNT_NAME, syncAccountName) .withValue(RawContacts.ACCOUNT_TYPE, AppUtils.getString(R.string.sync_account_type))
.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT) .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT)
.build() .build()
addChanges(insert) addChanges(insert)
@ -259,12 +281,9 @@ class NativeContactEditor(
) )
.build() .build()
addChanges(update) addChanges(update)
commit() commit(true)
} }
return this
}
fun setPresenceInformation(phoneNumber: String, sipAddress: String): NativeContactEditor {
if (syncAccountRawId == null) { if (syncAccountRawId == null) {
Log.e("[Native Contact Editor] Can't add presence to contact in Linphone sync account, no raw id") Log.e("[Native Contact Editor] Can't add presence to contact in Linphone sync account, no raw id")
return this return this
@ -275,15 +294,15 @@ class NativeContactEditor(
return this return this
} }
fun commit() { fun commit(updateSyncAccountRawId: Boolean = false) {
if (PermissionHelper.get().hasWriteContactsPermission()) { if (PermissionHelper.get().hasWriteContactsPermission()) {
try { try {
if (changes.isNotEmpty()) { if (changes.isNotEmpty()) {
val contentResolver = coreContext.context.contentResolver val contentResolver = coreContext.context.contentResolver
val results = contentResolver.applyBatch(ContactsContract.AUTHORITY, changes) val results = contentResolver.applyBatch(ContactsContract.AUTHORITY, changes)
for (result in results) { for (result in results) {
Log.i("[Native Contact Editor] Result is $result") Log.i("[Native Contact Editor] Result is ${result.uri}")
if (syncAccountRawId == null && result?.uri != null) { if (updateSyncAccountRawId && syncAccountRawId == null && result?.uri != null) {
syncAccountRawId = ContentUris.parseId(result.uri).toString() syncAccountRawId = ContentUris.parseId(result.uri).toString()
Log.i("[Native Contact Editor] Sync account raw id is $syncAccountRawId") Log.i("[Native Contact Editor] Sync account raw id is $syncAccountRawId")
} }
@ -422,7 +441,11 @@ class NativeContactEditor(
ContactsContract.Data.CONTENT_URI, ContactsContract.Data.CONTENT_URI,
arrayOf("data1"), arrayOf("data1"),
"${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? AND data3 = ?", "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? AND data3 = ?",
arrayOf(syncAccountRawId, AppUtils.getString(R.string.linphone_address_mime_type), phoneNumber), arrayOf(
syncAccountRawId,
AppUtils.getString(R.string.linphone_address_mime_type),
phoneNumber
),
null null
) )
val count = cursor?.count ?: 0 val count = cursor?.count ?: 0

View file

@ -119,12 +119,6 @@ class CorePreferences constructor(private val context: Context) {
/* Contacts */ /* Contacts */
var storeCreatedContactsInAppSyncAccount: Boolean
get() = config.getBool("app", "store_contacts_in_app_sync_account", false)
set(value) {
config.setBool("app", "store_contacts_in_app_sync_account", value)
}
var storePresenceInNativeContact: Boolean var storePresenceInNativeContact: Boolean
get() = config.getBool("app", "store_presence_in_native_contact", false) get() = config.getBool("app", "store_presence_in_native_contact", false)
set(value) { set(value) {

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_gravity="center"
android:orientation="horizontal"
android:padding="5dp">
<ImageView
android:id="@+id/account_icon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:adjustViewBounds="true"/>
<TextView
android:id="@+id/account_name"
style="@style/standard_text_font"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="5dp" />
</LinearLayout>

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View"/>
<variable
name="localSyncAccountClickListener"
type="android.view.View.OnClickListener"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<TextView
style="@style/standard_text_font"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical|center"
android:singleLine="true"
android:text="@string/contact_new_choose_sync_account" />
</LinearLayout>
<ListView
android:id="@+id/accountsList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:cacheColorHint="@color/transparent_color"
android:divider="@color/light_grey_color"
android:dividerHeight="1dp" />
<TextView
android:onClick="@{localSyncAccountClickListener}"
style="@style/button_font"
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/contact_local_sync_account"
android:gravity="right" />
</LinearLayout>
</layout>

View file

@ -72,14 +72,6 @@
linphone:checked="@={viewModel.friendListSubscribe}" linphone:checked="@={viewModel.friendListSubscribe}"
linphone:enabled="@{viewModel.readContactsPermissionGranted}"/> linphone:enabled="@{viewModel.readContactsPermissionGranted}"/>
<include
layout="@layout/settings_widget_switch"
linphone:title="@{@string/contacts_settings_contact_storage_title}"
linphone:subtitle="@{@string/contacts_settings_contact_storage_summary}"
linphone:listener="@{viewModel.contactStorageAppSyncAccountListener}"
linphone:checked="@={viewModel.contactStorageAppSyncAccount}"
linphone:enabled="@{viewModel.readContactsPermissionGranted}"/>
<include <include
layout="@layout/settings_widget_switch" layout="@layout/settings_widget_switch"
linphone:title="@{@string/contacts_settings_native_presence_title}" linphone:title="@{@string/contacts_settings_native_presence_title}"

View file

@ -107,6 +107,8 @@
<item quantity="one">@string/contact_delete_one_dialog</item> <item quantity="one">@string/contact_delete_one_dialog</item>
<item quantity="other">@string/contact_delete_many_dialog</item> <item quantity="other">@string/contact_delete_many_dialog</item>
</plurals> </plurals>
<string name="contact_new_choose_sync_account">Choose where to save the contact</string>
<string name="contact_local_sync_account">Store locally</string>
<!-- Dialer --> <!-- Dialer -->
<string name="dialer_address_bar_hint">Enter a number or an address</string> <string name="dialer_address_bar_hint">Enter a number or an address</string>
@ -430,8 +432,6 @@
<!-- Contacts settings --> <!-- Contacts settings -->
<string name="contacts_settings_friendlist_subscribe_title">Friendlist subscribe</string> <string name="contacts_settings_friendlist_subscribe_title">Friendlist subscribe</string>
<string name="contacts_settings_friendlist_subscribe_summary"></string> <string name="contacts_settings_friendlist_subscribe_summary"></string>
<string name="contacts_settings_contact_storage_title">Store new contacts in the app sync account</string>
<string name="contacts_settings_contact_storage_summary">If enabled, contacts created via the app will be removed when the app will be uninstalled.</string>
<string name="contacts_settings_native_presence_title">Presence information in native contact</string> <string name="contacts_settings_native_presence_title">Presence information in native contact</string>
<string name="contacts_settings_native_presence_summary">Inserting information shortcuts from the &appName; contact into native Android contacts</string> <string name="contacts_settings_native_presence_summary">Inserting information shortcuts from the &appName; contact into native Android contacts</string>
<string name="contacts_settings_show_organization_title">Display contact organization</string> <string name="contacts_settings_show_organization_title">Display contact organization</string>

View file

@ -3,4 +3,5 @@
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/sync_account_type" android:accountType="@string/sync_account_type"
android:icon="@mipmap/ic_launcher_round" android:icon="@mipmap/ic_launcher_round"
android:smallIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name" /> android:label="@string/app_name" />