From 03f0f49c7194d7e74f7ecee31fa8147dec0f727b Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 19 Aug 2020 16:32:15 +0200 Subject: [PATCH] Contacts creator improvements to be able to choose where to store newly created contact --- .../contact/adapters/SyncAccountAdapter.kt | 44 ++++++++ .../fragments/ContactEditorFragment.kt | 30 ++++-- .../fragments/SyncAccountPickerFragment.kt | 70 ++++++++++++ .../viewmodels/ContactEditorViewModel.kt | 12 +-- .../contact/viewmodels/ContactViewModel.kt | 18 ++-- .../viewmodels/ContactsSettingsViewModel.kt | 8 -- .../main/java/org/linphone/contact/Contact.kt | 4 + .../org/linphone/contact/ContactsManager.kt | 43 ++++++-- .../org/linphone/contact/NativeContact.kt | 4 + .../linphone/contact/NativeContactEditor.kt | 101 +++++++++++------- .../java/org/linphone/core/CorePreferences.kt | 6 -- .../contact_sync_account_picker_cell.xml | 22 ++++ .../contact_sync_account_picker_fragment.xml | 52 +++++++++ .../res/layout/settings_contacts_fragment.xml | 8 -- app/src/main/res/values/strings.xml | 4 +- app/src/main/res/xml/authenticator.xml | 1 + 16 files changed, 329 insertions(+), 98 deletions(-) create mode 100644 app/src/main/java/org/linphone/activities/main/contact/adapters/SyncAccountAdapter.kt create mode 100644 app/src/main/java/org/linphone/activities/main/contact/fragments/SyncAccountPickerFragment.kt create mode 100644 app/src/main/res/layout/contact_sync_account_picker_cell.xml create mode 100644 app/src/main/res/layout/contact_sync_account_picker_fragment.xml diff --git a/app/src/main/java/org/linphone/activities/main/contact/adapters/SyncAccountAdapter.kt b/app/src/main/java/org/linphone/activities/main/contact/adapters/SyncAccountAdapter.kt new file mode 100644 index 000000000..3e76972d2 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/contact/adapters/SyncAccountAdapter.kt @@ -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> = 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(R.id.account_icon) + icon.setImageDrawable(account.third) + val name = view.findViewById(R.id.account_name) + name.text = account.first + + return view + } + + override fun getItem(position: Int): Triple { + return accounts[position] + } + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + override fun getCount(): Int { + return accounts.size + } +} diff --git a/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt b/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt index d80a3548b..511ac61f9 100644 --- a/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt @@ -47,7 +47,7 @@ import org.linphone.utils.FileUtils import org.linphone.utils.ImageUtils import org.linphone.utils.PermissionHelper -class ContactEditorFragment : Fragment() { +class ContactEditorFragment : Fragment(), SyncAccountPickerFragment.SyncAccountPickedListener { private lateinit var binding: ContactEditorFragmentBinding private lateinit var viewModel: ContactEditorViewModel private lateinit var sharedViewModel: SharedMainViewModel @@ -86,13 +86,11 @@ class ContactEditorFragment : Fragment() { } binding.setSaveChangesClickListener { - val savedContact = viewModel.save() - if (savedContact is NativeContact) { - savedContact.syncValuesFromAndroidContact(requireContext()) - Log.i("[Contact Editor] Displaying contact $savedContact") - navigateToContact(savedContact) + if (viewModel.c == null) { + Log.i("[Contact Editor] New contact, ask user where to store it") + SyncAccountPickerFragment(this).show(childFragmentManager, "SyncAccountPicker") } 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( requestCode: Int, permissions: Array, @@ -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() { val cameraIntents = ArrayList() diff --git a/app/src/main/java/org/linphone/activities/main/contact/fragments/SyncAccountPickerFragment.kt b/app/src/main/java/org/linphone/activities/main/contact/fragments/SyncAccountPickerFragment.kt new file mode 100644 index 000000000..78e5cdc6a --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/contact/fragments/SyncAccountPickerFragment.kt @@ -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 . + */ +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?) + } +} diff --git a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactEditorViewModel.kt b/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactEditorViewModel.kt index 988dd36bd..a3d2aa29b 100644 --- a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactEditorViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactEditorViewModel.kt @@ -29,10 +29,8 @@ import java.io.ByteArrayOutputStream import java.io.IOException import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R import org.linphone.contact.* import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils import org.linphone.utils.ImageUtils import org.linphone.utils.PermissionHelper @@ -66,6 +64,9 @@ class ContactEditorViewModel(val c: Contact?) : ViewModel(), ContactViewModelInt val addresses = MutableLiveData>() + var syncAccountName: String? = null + var syncAccountType: String? = null + init { if (c != null) { contact.value = c @@ -83,12 +84,7 @@ class ContactEditorViewModel(val c: Contact?) : ViewModel(), ContactViewModelInt if (contact == null) { created = true contact = if (PermissionHelper.get().hasWriteContactsPermission()) { - // Store native contact in default sync account - 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()) - } + NativeContact(NativeContactEditor.createAndroidContact(syncAccountName, syncAccountType).toString()) } else { Contact() } diff --git a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactViewModel.kt b/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactViewModel.kt index e9cf86ac7..b41ea5531 100644 --- a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactViewModel.kt @@ -75,7 +75,7 @@ class ContactViewModel(private val c: Contact) : ErrorReportingViewModel(), Cont override fun onContactUpdated(contact: Contact) { if (c is NativeContact && contact is NativeContact && c.nativeId == contact.nativeId) { Log.d("[Contact] $contact has changed") - updateNumbersAndAddresses() + updateNumbersAndAddresses(contact) } } } @@ -122,7 +122,7 @@ class ContactViewModel(private val c: Contact) : ErrorReportingViewModel(), Cont init { contact.value = c - updateNumbersAndAddresses() + updateNumbersAndAddresses(c) coreContext.contactsManager.addListener(contactsUpdatedListener) 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() - for (address in c.sipAddresses) { + for (address in contact.sipAddresses) { val value = address.asStringUriOnly() - val presenceModel = c.friend?.getPresenceModelForUriOrTel(value) + val presenceModel = contact.friend?.getPresenceModelForUriOrTel(value) val hasPresence = presenceModel?.basicStatus == PresenceBasicStatus.Open 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) list.add(noa) } - for (number in c.phoneNumbers) { - val presenceModel = c.friend?.getPresenceModelForUriOrTel(number) + for (number in contact.phoneNumbers) { + val presenceModel = contact.friend?.getPresenceModelForUriOrTel(number) val hasPresence = presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open val contactAddress = presenceModel?.contact ?: number val address = coreContext.core.interpretUrl(contactAddress) 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) list.add(noa) } diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt index 2f214e1b3..be6a160ae 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt @@ -38,13 +38,6 @@ class ContactsSettingsViewModel : GenericSettingsViewModel() { } val friendListSubscribe = MutableLiveData() - val contactStorageAppSyncAccountListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.storeCreatedContactsInAppSyncAccount = newValue - } - } - val contactStorageAppSyncAccount = MutableLiveData() - val nativePresenceListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { if (newValue) { @@ -80,7 +73,6 @@ class ContactsSettingsViewModel : GenericSettingsViewModel() { readContactsPermissionGranted.value = PermissionHelper.get().hasReadContactsPermission() friendListSubscribe.value = core.isFriendListSubscriptionEnabled - contactStorageAppSyncAccount.value = prefs.storeCreatedContactsInAppSyncAccount nativePresence.value = prefs.storePresenceInNativeContact showOrganization.value = prefs.displayOrganization launcherShortcuts.value = prefs.contactsShortcuts diff --git a/app/src/main/java/org/linphone/contact/Contact.kt b/app/src/main/java/org/linphone/contact/Contact.kt index 93feb9356..154553ac1 100644 --- a/app/src/main/java/org/linphone/contact/Contact.kt +++ b/app/src/main/java/org/linphone/contact/Contact.kt @@ -163,4 +163,8 @@ open class Contact : Comparable { if (presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open) return presenceModel.contact return null } + + override fun toString(): String { + return "${super.toString()}: name [$fullName]" + } } diff --git a/app/src/main/java/org/linphone/contact/ContactsManager.kt b/app/src/main/java/org/linphone/contact/ContactsManager.kt index 2e8fabb28..dbfd0020c 100644 --- a/app/src/main/java/org/linphone/contact/ContactsManager.kt +++ b/app/src/main/java/org/linphone/contact/ContactsManager.kt @@ -21,9 +21,11 @@ package org.linphone.contact import android.accounts.Account import android.accounts.AccountManager +import android.accounts.AuthenticatorDescription import android.content.ContentResolver import android.content.Context import android.database.ContentObserver +import android.graphics.drawable.Drawable import android.net.Uri import android.os.AsyncTask import android.os.AsyncTask.THREAD_POOL_EXECUTOR @@ -193,7 +195,10 @@ class ContactsManager(private val context: Context) { // Restart contacts async fetching fetchContactsAsync() + } else { + Log.d("[Contacts Manager] Found contact with id [$id]: ${found?.fullName}") } + return found } @@ -249,7 +254,11 @@ class ContactsManager(private val context: Context) { val accountManager = context.getSystemService(Context.ACCOUNT_SERVICE) as AccountManager val accounts = accountManager.getAccountsByType(context.getString(R.string.sync_account_type)) 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 { accountManager.addAccountExplicitly(newAccount, null, null) Log.i("[Contacts Manager] Contact account added") @@ -263,19 +272,28 @@ class ContactsManager(private val context: Context) { } } - fun getAvailableSyncAccounts(): List> { + fun getAvailableSyncAccounts(): List> { val accountManager = context.getSystemService(Context.ACCOUNT_SERVICE) as AccountManager + val packageManager = context.packageManager val syncAdapters = ContentResolver.getSyncAdapterTypes() - val available = arrayListOf>() + val authenticators: Array = accountManager.authenticatorTypes + val available = arrayListOf>() for (syncAdapter in syncAdapters) { if (syncAdapter.authority == "com.android.contacts" && syncAdapter.isUserVisible) { - Log.i("[Contacts Manager] Found sync adapter for com.android.contacts authority: ${syncAdapter.accountType}") - val accounts = accountManager.getAccountsByType(syncAdapter.accountType) - for (account in accounts) { - Log.i("[Contacts Manager] Found account for account type ${syncAdapter.accountType}: ${account.name}") - val pair = Pair(account.name, account.type) - available.add(pair) + if (syncAdapter.supportsUploading() || syncAdapter.accountType == context.getString(R.string.sync_account_type)) { + Log.i("[Contacts Manager] Found sync adapter for com.android.contacts authority: ${syncAdapter.accountType}") + val accounts = accountManager.getAccountsByType(syncAdapter.accountType) + for (account in accounts) { + Log.i("[Contacts Manager] Found account for account type ${syncAdapter.accountType}: ${account.name}") + 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) if (sipAddress != null) { 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) coroutineScope.launch { val deferred = async { withContext(Dispatchers.IO) { - contactEditor.ensureSyncAccountRawIdExists().setPresenceInformation(phoneNumber, sipAddress).commit() + contactEditor.setPresenceInformation( + phoneNumber, + sipAddress + ).commit() } } deferred.await() diff --git a/app/src/main/java/org/linphone/contact/NativeContact.kt b/app/src/main/java/org/linphone/contact/NativeContact.kt index 713ba175e..b7165fa3d 100644 --- a/app/src/main/java/org/linphone/contact/NativeContact.kt +++ b/app/src/main/java/org/linphone/contact/NativeContact.kt @@ -227,4 +227,8 @@ class NativeContact(val nativeId: String, private val lookupKey: String? = null) cursor.close() } } + + override fun toString(): String { + return "${super.toString()}: id [$nativeId], name [$fullName]" + } } diff --git a/app/src/main/java/org/linphone/contact/NativeContactEditor.kt b/app/src/main/java/org/linphone/contact/NativeContactEditor.kt index b07f63d57..d1fd26965 100644 --- a/app/src/main/java/org/linphone/contact/NativeContactEditor.kt +++ b/app/src/main/java/org/linphone/contact/NativeContactEditor.kt @@ -21,7 +21,6 @@ package org.linphone.contact import android.content.ContentProviderOperation import android.content.ContentUris -import android.content.ContentValues import android.net.Uri import android.provider.ContactsContract import android.provider.ContactsContract.CommonDataKinds @@ -33,20 +32,33 @@ import org.linphone.core.tools.Log import org.linphone.utils.AppUtils import org.linphone.utils.PermissionHelper -class NativeContactEditor( - val contact: NativeContact, - private var syncAccountName: String? = null, - private var syncAccountType: String? = null -) { +class NativeContactEditor(val contact: NativeContact) { companion object { fun createAndroidContact(accountName: String?, accountType: String?): Long { - val values = ContentValues() - values.put(RawContacts.ACCOUNT_NAME, accountName) - values.put(RawContacts.ACCOUNT_TYPE, accountType) + Log.i("[Native Contact Editor] Using sync account $accountName with type $accountType") - val rawContactUri = coreContext.context.contentResolver - .insert(RawContacts.CONTENT_URI, values) - return ContentUris.parseId(rawContactUri) + val changes = arrayListOf() + changes.add( + 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} =?" private val phoneNumberSelection = "$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 = "${ContactsContract.Data.CONTACT_ID} =? AND ${ContactsContract.Data.MIMETYPE} =? AND data3=?" private val contactUri = ContactsContract.Data.CONTENT_URI @@ -66,11 +76,10 @@ class NativeContactEditor( private var pictureByteArray: ByteArray? = null init { - Log.d("[Native Contact Editor] Using sync account $syncAccountName with type $syncAccountType") val contentResolver = coreContext.context.contentResolver val cursor = contentResolver.query( RawContacts.CONTENT_URI, - arrayOf(RawContacts._ID, RawContacts.ACCOUNT_TYPE), + arrayOf(RawContacts._ID), "${RawContacts.CONTACT_ID} =?", arrayOf(contact.nativeId), null @@ -79,20 +88,11 @@ class NativeContactEditor( do { if (rawId == null) { 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}") } - - 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) + } while (cursor.moveToNext() && rawId == null) } 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 { @@ -237,12 +237,34 @@ class NativeContactEditor( return this } - fun ensureSyncAccountRawIdExists(): NativeContactEditor { + fun setPresenceInformation(phoneNumber: String, sipAddress: String): NativeContactEditor { 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) - .withValue(RawContacts.ACCOUNT_TYPE, syncAccountType) - .withValue(RawContacts.ACCOUNT_NAME, syncAccountName) + .withValue(RawContacts.ACCOUNT_NAME, AppUtils.getString(R.string.sync_account_name)) + .withValue(RawContacts.ACCOUNT_TYPE, AppUtils.getString(R.string.sync_account_type)) .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT) .build() addChanges(insert) @@ -259,12 +281,9 @@ class NativeContactEditor( ) .build() addChanges(update) - commit() + commit(true) } - return this - } - fun setPresenceInformation(phoneNumber: String, sipAddress: String): NativeContactEditor { if (syncAccountRawId == null) { Log.e("[Native Contact Editor] Can't add presence to contact in Linphone sync account, no raw id") return this @@ -275,15 +294,15 @@ class NativeContactEditor( return this } - fun commit() { + fun commit(updateSyncAccountRawId: Boolean = false) { if (PermissionHelper.get().hasWriteContactsPermission()) { try { if (changes.isNotEmpty()) { val contentResolver = coreContext.context.contentResolver val results = contentResolver.applyBatch(ContactsContract.AUTHORITY, changes) for (result in results) { - Log.i("[Native Contact Editor] Result is $result") - if (syncAccountRawId == null && result?.uri != null) { + Log.i("[Native Contact Editor] Result is ${result.uri}") + if (updateSyncAccountRawId && syncAccountRawId == null && result?.uri != null) { syncAccountRawId = ContentUris.parseId(result.uri).toString() Log.i("[Native Contact Editor] Sync account raw id is $syncAccountRawId") } @@ -422,7 +441,11 @@ class NativeContactEditor( ContactsContract.Data.CONTENT_URI, arrayOf("data1"), "${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 ) val count = cursor?.count ?: 0 diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index 715c986bf..25e6a7aa3 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -119,12 +119,6 @@ class CorePreferences constructor(private val context: Context) { /* 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 get() = config.getBool("app", "store_presence_in_native_contact", false) set(value) { diff --git a/app/src/main/res/layout/contact_sync_account_picker_cell.xml b/app/src/main/res/layout/contact_sync_account_picker_cell.xml new file mode 100644 index 000000000..3a663084b --- /dev/null +++ b/app/src/main/res/layout/contact_sync_account_picker_cell.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/contact_sync_account_picker_fragment.xml b/app/src/main/res/layout/contact_sync_account_picker_fragment.xml new file mode 100644 index 000000000..ce4ed4c77 --- /dev/null +++ b/app/src/main/res/layout/contact_sync_account_picker_fragment.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/settings_contacts_fragment.xml b/app/src/main/res/layout/settings_contacts_fragment.xml index 5d21c62d9..9c1b6810b 100644 --- a/app/src/main/res/layout/settings_contacts_fragment.xml +++ b/app/src/main/res/layout/settings_contacts_fragment.xml @@ -72,14 +72,6 @@ linphone:checked="@={viewModel.friendListSubscribe}" linphone:enabled="@{viewModel.readContactsPermissionGranted}"/> - - @string/contact_delete_one_dialog @string/contact_delete_many_dialog + Choose where to save the contact + Store locally Enter a number or an address @@ -430,8 +432,6 @@ Friendlist subscribe - Store new contacts in the app sync account - If enabled, contacts created via the app will be removed when the app will be uninstalled. Presence information in native contact Inserting information shortcuts from the &appName; contact into native Android contacts Display contact organization diff --git a/app/src/main/res/xml/authenticator.xml b/app/src/main/res/xml/authenticator.xml index 3f1fdd0d6..2680ec3e5 100644 --- a/app/src/main/res/xml/authenticator.xml +++ b/app/src/main/res/xml/authenticator.xml @@ -3,4 +3,5 @@ \ No newline at end of file