mirror of
https://github.com/nextcloud/talk-android
synced 2025-03-06 14:27:24 +00:00
fix "chat via"-links in phonebook (no multiple entries, fix deletion)
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
parent
711afaccf5
commit
adc0a91dac
@ -9,11 +9,14 @@ Types of changes can be: Added/Changed/Deprecated/Removed/Fixed/Security
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
- improve conversation list design wand dark/light theming
|
||||
- improve conversation list design and dark/light theming
|
||||
|
||||
### Fixed
|
||||
- @ in username is allowed for phonebook sync
|
||||
- avoid sync when phonebook is empty
|
||||
- avoid creation of multiple "chat via"-links in phonebook
|
||||
- delete "chat via"-link from phonebook if phone number was deleted on server
|
||||
- remove all "chat via"-links from phonebook when sync is disabled
|
||||
|
||||
## [11.1.0] - 2021-03-12
|
||||
### Added
|
||||
|
@ -176,7 +176,8 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
||||
if (userUtils.currentUser?.baseUrl?.endsWith(baseUrl) == true) {
|
||||
startConversation(user)
|
||||
} else {
|
||||
Snackbar.make(container, "Account not found", Snackbar.LENGTH_LONG).show()
|
||||
Snackbar.make(container, R.string.nc_phone_book_integration_account_not_found, Snackbar
|
||||
.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.RemoteException
|
||||
import android.provider.ContactsContract
|
||||
import android.provider.ContactsContract.CommonDataKinds.Phone.NUMBER
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
@ -99,15 +98,14 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
||||
Log.d(TAG, "Account already exists")
|
||||
}
|
||||
|
||||
// collect all contacts with phone number
|
||||
val contactsWithNumbers = collectPhoneNumbers()
|
||||
val deviceContactsWithNumbers = collectContactsWithPhoneNumbersFromDevice()
|
||||
|
||||
if(contactsWithNumbers.isNotEmpty()){
|
||||
if(deviceContactsWithNumbers.isNotEmpty()){
|
||||
val currentLocale = ConfigurationCompat.getLocales(context.resources.configuration)[0].country
|
||||
|
||||
val map = mutableMapOf<String, Any>()
|
||||
map["location"] = currentLocale
|
||||
map["search"] = contactsWithNumbers
|
||||
map["search"] = deviceContactsWithNumbers
|
||||
|
||||
val json = Gson().toJson(map)
|
||||
|
||||
@ -125,13 +123,14 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
||||
}
|
||||
|
||||
override fun onNext(foundContacts: ContactsByNumberOverall) {
|
||||
up(foundContacts)
|
||||
val contactsWithAssociatedPhoneNumbers = foundContacts.ocs.map
|
||||
deleteLinkedAccounts(contactsWithAssociatedPhoneNumbers)
|
||||
createLinkedAccounts(contactsWithAssociatedPhoneNumbers)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e(javaClass.simpleName, "Failed to searchContactsByPhoneNumber", e)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@ -141,8 +140,8 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
private fun collectPhoneNumbers(): MutableMap<String, List<String>> {
|
||||
val result: MutableMap<String, List<String>> = mutableMapOf()
|
||||
private fun collectContactsWithPhoneNumbersFromDevice(): MutableMap<String, List<String>> {
|
||||
val deviceContactsWithNumbers: MutableMap<String, List<String>> = mutableMapOf()
|
||||
|
||||
val contactCursor = context.contentResolver.query(
|
||||
ContactsContract.Contacts.CONTENT_URI,
|
||||
@ -156,42 +155,32 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
||||
if (contactCursor.count > 0) {
|
||||
contactCursor.moveToFirst()
|
||||
for (i in 0 until contactCursor.count) {
|
||||
val numbers: MutableList<String> = mutableListOf()
|
||||
|
||||
val id = contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts._ID))
|
||||
val lookup = contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY))
|
||||
|
||||
val phonesCursor = context.contentResolver.query(
|
||||
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
|
||||
null,
|
||||
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + id,
|
||||
null,
|
||||
null)
|
||||
|
||||
if (phonesCursor != null) {
|
||||
while (phonesCursor.moveToNext()) {
|
||||
numbers.add(phonesCursor.getString(phonesCursor.getColumnIndex(NUMBER)))
|
||||
}
|
||||
|
||||
result[lookup] = numbers
|
||||
|
||||
phonesCursor.close()
|
||||
}
|
||||
|
||||
deviceContactsWithNumbers[lookup] = getPhoneNumbersFromDeviceContact(id)
|
||||
contactCursor.moveToNext()
|
||||
}
|
||||
}
|
||||
|
||||
contactCursor.close()
|
||||
}
|
||||
|
||||
return result
|
||||
Log.d(TAG, "collected contacts with phonenumbers: " + deviceContactsWithNumbers.size)
|
||||
return deviceContactsWithNumbers
|
||||
}
|
||||
|
||||
private fun up(foundContacts: ContactsByNumberOverall) {
|
||||
val map = foundContacts.ocs.map
|
||||
private fun deleteLinkedAccounts(contactsWithAssociatedPhoneNumbers: Map<String, String>?) {
|
||||
Log.d(TAG, "deleteLinkedAccount")
|
||||
fun deleteLinkedAccount(id: String) {
|
||||
val rawContactUri = ContactsContract.RawContacts.CONTENT_URI
|
||||
.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, accountName)
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType)
|
||||
.build()
|
||||
val count = context.contentResolver.delete(rawContactUri, ContactsContract.RawContacts.CONTACT_ID + " " +
|
||||
"LIKE \"" + id + "\"", null)
|
||||
Log.d(TAG, "deleted $count linked accounts for id $id")
|
||||
}
|
||||
|
||||
// Delete all old associations (those that are associated on phone, but not in server response)
|
||||
val rawContactUri = ContactsContract.Data.CONTENT_URI
|
||||
.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
@ -200,7 +189,6 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
||||
.appendQueryParameter(ContactsContract.Data.MIMETYPE, "vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat")
|
||||
.build()
|
||||
|
||||
// get all raw contacts
|
||||
val rawContactsCursor = context.contentResolver.query(
|
||||
rawContactUri,
|
||||
null,
|
||||
@ -212,35 +200,56 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
||||
if (rawContactsCursor != null) {
|
||||
if (rawContactsCursor.count > 0) {
|
||||
while (rawContactsCursor.moveToNext()) {
|
||||
val id = rawContactsCursor.getString(rawContactsCursor.getColumnIndex(ContactsContract.RawContacts._ID))
|
||||
val sync1 = rawContactsCursor.getString(rawContactsCursor.getColumnIndex(ContactsContract.Data.SYNC1))
|
||||
val lookupKey = rawContactsCursor.getString(rawContactsCursor.getColumnIndex(ContactsContract.Data.LOOKUP_KEY))
|
||||
Log.d("Contact", "Found associated: $id")
|
||||
val contactId = rawContactsCursor.getString(rawContactsCursor.getColumnIndex(ContactsContract.Data.CONTACT_ID))
|
||||
|
||||
if (map == null || !map.containsKey(lookupKey)) {
|
||||
if (sync1 != null) {
|
||||
deleteAssociation(sync1)
|
||||
if (contactsWithAssociatedPhoneNumbers == null || !contactsWithAssociatedPhoneNumbers.containsKey(lookupKey)) {
|
||||
deleteLinkedAccount(contactId)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "no contacts with linked Talk Accounts found. Nothing to delete...")
|
||||
}
|
||||
}
|
||||
|
||||
rawContactsCursor.close()
|
||||
}
|
||||
|
||||
// update / change found
|
||||
if (map != null && map.isNotEmpty()) {
|
||||
for (contact in foundContacts.ocs.map) {
|
||||
val lookupKey = contact.key
|
||||
val cloudId = contact.value
|
||||
|
||||
update(lookupKey, cloudId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun update(uniqueId: String, cloudId: String) {
|
||||
val lookupUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, uniqueId)
|
||||
private fun createLinkedAccounts(contactsWithAssociatedPhoneNumbers: Map<String, String>?) {
|
||||
fun hasLinkedAccount(id: String) : Boolean {
|
||||
var hasLinkedAccount = false
|
||||
val where = ContactsContract.Data.MIMETYPE + " = ? AND " + ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " = ?"
|
||||
val params = arrayOf(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, id)
|
||||
|
||||
val rawContactUri = ContactsContract.Data.CONTENT_URI
|
||||
.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, accountName)
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType)
|
||||
.appendQueryParameter(ContactsContract.Data.MIMETYPE, "vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat")
|
||||
.build()
|
||||
|
||||
val rawContactsCursor = context.contentResolver.query(
|
||||
rawContactUri,
|
||||
null,
|
||||
where,
|
||||
params,
|
||||
null
|
||||
)
|
||||
|
||||
if (rawContactsCursor != null) {
|
||||
if (rawContactsCursor.count > 0) {
|
||||
hasLinkedAccount = true
|
||||
Log.d(TAG, "contact with id $id already has a linked account")
|
||||
} else {
|
||||
hasLinkedAccount = false
|
||||
}
|
||||
rawContactsCursor.close()
|
||||
}
|
||||
return hasLinkedAccount
|
||||
}
|
||||
|
||||
fun createLinkedAccount(lookupKey: String, cloudId: String) {
|
||||
val lookupUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey)
|
||||
val lookupContactUri = ContactsContract.Contacts.lookupContact(context.contentResolver, lookupUri)
|
||||
val contactCursor = context.contentResolver.query(
|
||||
lookupContactUri,
|
||||
@ -254,25 +263,78 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
||||
contactCursor.moveToFirst()
|
||||
|
||||
val id = contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts._ID))
|
||||
|
||||
val phonesCursor = context.contentResolver.query(
|
||||
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
|
||||
null,
|
||||
ContactsContract.Data.CONTACT_ID + " = " + id,
|
||||
null,
|
||||
null)
|
||||
|
||||
val numbers = mutableListOf<String>()
|
||||
if (phonesCursor != null) {
|
||||
while (phonesCursor.moveToNext()) {
|
||||
numbers.add(phonesCursor.getString(phonesCursor.getColumnIndex(NUMBER)))
|
||||
if(hasLinkedAccount(id)){
|
||||
return
|
||||
}
|
||||
|
||||
phonesCursor.close()
|
||||
val numbers = getPhoneNumbersFromDeviceContact(id)
|
||||
val displayName = getDisplayNameFromDeviceContact(id)
|
||||
|
||||
if (displayName == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val ops = ArrayList<ContentProviderOperation>()
|
||||
val rawContactsUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon().build()
|
||||
val dataUri = ContactsContract.Data.CONTENT_URI.buildUpon().build()
|
||||
|
||||
ops.add(ContentProviderOperation
|
||||
.newInsert(rawContactsUri)
|
||||
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, accountName)
|
||||
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType)
|
||||
.withValue(ContactsContract.RawContacts.AGGREGATION_MODE,
|
||||
ContactsContract.RawContacts.AGGREGATION_MODE_DEFAULT)
|
||||
.withValue(ContactsContract.RawContacts.SYNC2, cloudId)
|
||||
.build())
|
||||
ops.add(ContentProviderOperation
|
||||
.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
|
||||
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, numbers[0])
|
||||
.build())
|
||||
ops.add(ContentProviderOperation
|
||||
.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
|
||||
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
|
||||
.build())
|
||||
ops.add(ContentProviderOperation
|
||||
.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, "vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat")
|
||||
.withValue(ContactsContract.Data.DATA1, cloudId)
|
||||
.withValue(ContactsContract.Data.DATA2, String.format(context.resources.getString(R
|
||||
.string.nc_phone_book_integration_chat_via), accountName))
|
||||
.build())
|
||||
|
||||
try {
|
||||
context.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
|
||||
} catch (e: OperationApplicationException) {
|
||||
Log.e(javaClass.simpleName, "", e)
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(javaClass.simpleName, "", e)
|
||||
}
|
||||
|
||||
Log.d(TAG, "added new entry for contact $displayName (cloudId: $cloudId | lookupKey: $lookupKey" +
|
||||
" | id: $id)")
|
||||
}
|
||||
contactCursor.close()
|
||||
}
|
||||
}
|
||||
|
||||
if (contactsWithAssociatedPhoneNumbers != null && contactsWithAssociatedPhoneNumbers.isNotEmpty()) {
|
||||
for (contact in contactsWithAssociatedPhoneNumbers) {
|
||||
val lookupKey = contact.key
|
||||
val cloudId = contact.value
|
||||
createLinkedAccount(lookupKey, cloudId)
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "no contacts with linked Talk Accounts found. No linked accounts created.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDisplayNameFromDeviceContact(id: String?): String? {
|
||||
var displayName:String? = null
|
||||
|
||||
val whereName = ContactsContract.Data.MIMETYPE + " = ? AND " + ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " = ?"
|
||||
val whereNameParams = arrayOf(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, id)
|
||||
val nameCursor = context.contentResolver.query(
|
||||
@ -287,72 +349,28 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
|
||||
}
|
||||
nameCursor.close()
|
||||
}
|
||||
|
||||
if (displayName == null) {
|
||||
return
|
||||
return displayName
|
||||
}
|
||||
|
||||
// update entries
|
||||
val ops = ArrayList<ContentProviderOperation>()
|
||||
val rawContactsUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon()
|
||||
.build()
|
||||
val dataUri = ContactsContract.Data.CONTENT_URI.buildUpon()
|
||||
.build()
|
||||
private fun getPhoneNumbersFromDeviceContact(id: String?): MutableList<String> {
|
||||
val numbers = mutableListOf<String>()
|
||||
val phonesNumbersCursor = context.contentResolver.query(
|
||||
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
|
||||
null,
|
||||
ContactsContract.Data.CONTACT_ID + " = " + id,
|
||||
null,
|
||||
null)
|
||||
|
||||
ops.add(ContentProviderOperation
|
||||
.newInsert(rawContactsUri)
|
||||
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, accountName)
|
||||
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType)
|
||||
.withValue(ContactsContract.RawContacts.AGGREGATION_MODE,
|
||||
ContactsContract.RawContacts.AGGREGATION_MODE_DEFAULT)
|
||||
.withValue(ContactsContract.RawContacts.SYNC2, cloudId)
|
||||
.build())
|
||||
ops.add(ContentProviderOperation
|
||||
.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
|
||||
.withValue(NUMBER, numbers[0])
|
||||
.build())
|
||||
ops.add(ContentProviderOperation
|
||||
.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
|
||||
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
|
||||
.build())
|
||||
ops.add(ContentProviderOperation
|
||||
.newInsert(dataUri)
|
||||
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
|
||||
.withValue(ContactsContract.Data.MIMETYPE, "vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat")
|
||||
.withValue(ContactsContract.Data.DATA1, cloudId)
|
||||
.withValue(ContactsContract.Data.DATA2, "Chat via " + context.resources.getString(R.string.nc_app_name))
|
||||
.build())
|
||||
|
||||
try {
|
||||
context.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
|
||||
} catch (e: OperationApplicationException) {
|
||||
Log.e(javaClass.simpleName, "", e)
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(javaClass.simpleName, "", e)
|
||||
if (phonesNumbersCursor != null) {
|
||||
while (phonesNumbersCursor.moveToNext()) {
|
||||
numbers.add(phonesNumbersCursor.getString(phonesNumbersCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)))
|
||||
}
|
||||
phonesNumbersCursor.close()
|
||||
}
|
||||
|
||||
contactCursor.close()
|
||||
if(numbers.size > 0){
|
||||
Log.d(TAG, "Found ${numbers.size} phone numbers for contact with id $id")
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteAssociation(id: String) {
|
||||
Log.d("Contact", "Delete associated: $id")
|
||||
|
||||
val rawContactUri = ContactsContract.RawContacts.CONTENT_URI
|
||||
.buildUpon()
|
||||
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, accountName)
|
||||
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType)
|
||||
.build()
|
||||
|
||||
|
||||
val count = context.contentResolver.delete(rawContactUri, ContactsContract.RawContacts.SYNC2 + " LIKE \"" + id + "\"", null)
|
||||
Log.d("Contact", "deleted $count for id $id")
|
||||
return numbers
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -354,6 +354,8 @@
|
||||
<string name="nc_settings_phone_book_integration_phone_number_dialog_invalid">Invalid phone number</string>
|
||||
<string name="nc_settings_phone_book_integration_phone_number_dialog_success">Phone number set successfully</string>
|
||||
<string name="no_phone_book_integration_due_to_permissions">No phone book integration due to missing permissions</string>
|
||||
<string name="nc_phone_book_integration_chat_via">Chat via %s</string>
|
||||
<string name="nc_phone_book_integration_account_not_found">Account not found</string>
|
||||
|
||||
<!-- Non-translatable strings -->
|
||||
<string name="path_password_strike_through" translatable="false"
|
||||
|
Loading…
Reference in New Issue
Block a user