I'm trying to get a list of SMS conversations on Android. I made this Kotlin code:
kotlin
val threads = contentResolver.query(Telephony.Threads.CONTENT_URI, null, null, null, Telephony.Threads.DATE)
This throws this error:
java.lang.RuntimeException: java.lang.IllegalArgumentException: Invalid column null
I don't think am querying it the right way. What can I do to fix it? Thank you in advance.
To fetch an Inbox message try this code:
First, create a Model class of messages name Message
data class Message (
var messageNumber:String?=null,
var messageContent:String?=null
)
Then fetch inbox message list like this:
private fun fetchInboxMsg(): ArrayList<Message>? {
val messageList = ArrayList<Message>()
val uriSms: Uri = Uri.parse("content://sms")
val cursor = contentResolver.query(
uriSms, arrayOf("DISTINCT address", "body"), //DISTINCT
"address IS NOT NULL) GROUP BY (address", //GROUP BY
null, null
)
if (cursor!!.count > 0) {
cursor.moveToFirst()
for (i in 0 until cursor.count) {
val number: String? = cursor.getString(cursor.getColumnIndex("address"))
val content: String? = cursor.getString(cursor.getColumnIndex("body"))
val message = Message(number, content)
messageList.add(message)
cursor.moveToNext()
}
}
cursor.close()
return messageList
}
Don't forget to provide permission READ_SMS
Related
I've got the following code to retrieve a contact list with Name + PhoneNumber:
#SuppressLint("Range")
fun getNamePhoneDetails(): ArrayList<List<String>>? {
val names = ArrayList<List<String>>()
val cr = contentResolver
val cur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
null, null, null)
if (cur!!.count > 0) {
while (cur.moveToNext()) {
val id = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NAME_RAW_CONTACT_ID))
val name = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
val number = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
names.add(listOf(id, name, number))
}
}
return names
}
The output is correct once no contact has two phone numbers. However once any contact has two phone numbers I get the contact twice. It has the same id and name, but a different phone number. How can I make it so that the returned list does not have a contact twice but all phone numbers inside a list?
Something like
[1, Name Name, [phonenumber1, phonenumber2]],[2, Name Name, [phonenumber1]]
Then I could just iterate through the phonenumberlist and have all numbers as valid strings.
You should rather create a POJO Object and than assign the data into it and return a list of that POJO Object and then you can make use of distinct extension on the list to get a filtered result they way you want it .
This is how you can achieve what you want :
Create a POJO Object :
data class Contact(
val id : String ,
val name : String,
val number : String)
And then when retrieveing the data you can do the following :
#SuppressLint("Range")
fun getNamePhoneDetails(): MutableList<Contact>? {
val names = MutableList<Contact>()
val cr = contentResolver
val cur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
null, null, null)
if (cur!!.count > 0) {
while (cur.moveToNext()) {
val id = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NAME_RAW_CONTACT_ID))
val name = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
val number = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
names.add(Contact(id , name , number))
}
}
return names
}
And then while retreiving the data you need to just filter the list in the following manner :
val list = getNamePhoneDetails()
list.distinctBy { it.number }.forEach { println(it) }
I'm trying to get all messages from the user's phone, user number, text messages are retrieved successfully. But the problem is how can I get the UserName of the message sender saved in the user mobile device.
I'm getting the error Caused by: java.lang.NullPointerException: cur.getString(cur.getColumnIndex("person")) must not be null
Here is the method to get user SMS.
fun getSMS() {
msgArrayList = ArrayList()
msgArrayList?.clear()
val uriSMSURI: Uri = Uri.parse("content://sms/inbox")
val cur: Cursor? = contentResolver.query(uriSMSURI, null, null, null, null)
while (cur != null && cur.moveToNext()) {
val person: String = cur.getString(cur.getColumnIndex("person")) // error in this line occurs
val date: String = cur.getString(cur.getColumnIndex("date"))
val address: String = cur.getString(cur.getColumnIndex("address"))
val body: String = cur.getString(cur.getColumnIndexOrThrow("body"))
val model = AllMessagesModel(address ,body,person,date)
msgArrayList?.add(model)
}
cur?.close()
allMsgAdapter = MessageAdapter(this, msgArrayList)
val layoutManager =
LinearLayoutManager(applicationContext, RecyclerView.VERTICAL, false)
rvMessages!!.layoutManager = layoutManager
rvMessages?.adapter = allMsgAdapter
}
How can I solve this issue?
I want to fetch a list of local contacts with an anniversary date set.
I'm doing the following:
object WithAnniversary {
const val INDEX_CONTACT_ID = 0
const val INDEX_CONTACT_NAME = 1
const val INDEX_CONTACT_ANNIVERSARY = 2
const val INDEX_CONTACT_PHOTO = 3
val PROJECTION = arrayOf(
CommonDataKinds.Event.CONTACT_ID,
ContactsContract.Contacts.DISPLAY_NAME,
CommonDataKinds.Event.START_DATE,
CommonDataKinds.Phone.PHOTO_URI
)
const val WHERE = "${ContactsContract.Data.MIMETYPE} = ? AND " +
"${CommonDataKinds.Event.TYPE} = " +
"${CommonDataKinds.Event.TYPE_ANNIVERSARY}"
val SELECTION = arrayOf(CommonDataKinds.Event.CONTENT_ITEM_TYPE)
val SORT_ORDER: String? = null
}
#Throws(Exception::class)
fun obtainContactsWithAnniversaries(): List<Contact> {
val list = mutableListOf<Contact>()
val cursor = context.contentResolver.query(
ContactsContract.Data.CONTENT_URI,
WithAnniversary.PROJECTION,
WithAnniversary.WHERE,
WithAnniversary.SELECTION,
WithAnniversary.SORT_ORDER
)
if (cursor != null) {
while (cursor.moveToNext()) {
val id = cursor.getLong(WithAnniversary.INDEX_CONTACT_ID)
val name = cursor.getString(WithAnniversary.INDEX_CONTACT_NAME)
val date = cursor.getString(WithAnniversary.INDEX_CONTACT_ANNIVERSARY)
val avatarUri = cursor.getString(WithAnniversary.INDEX_CONTACT_PHOTO)
try {
val contact = contactFactory.create(id.toString(), name, null, date, avatarUri)
list.add(contact)
} catch (e: Exception) {
Log.d(TAG, "Could not parse contact with name: $name")
}
}
cursor.close()
return list.sorted()
} else {
throw Exception("Unable to retrieve contacts, returned cursor is null")
}
}
I use the exact same process for retrieving contacts with birthday dates, but using TYPE_BIRTHDAY instead of TYPE_ANNIVERSARY, but for some reason this doesn't work for anniversaries.
I have checked my local contact list and I have some contacts with birthdays and anniversaries. I can retrieve a list with contacts with birthdays but the list of contacts with anniversaries is empty.
Any help will be appreciated.
I assume the issue is with the conversion of the cursor row to your custom Contact class.
When I replace that part with just a log your code works for me:
...
while (cursor.moveToNext()) {
val id = cursor.getLong(WithAnniversary.INDEX_CONTACT_ID)
val name = cursor.getString(WithAnniversary.INDEX_CONTACT_NAME)
val date = cursor.getString(WithAnniversary.INDEX_CONTACT_ANNIVERSARY)
val avatarUri = cursor.getString(WithAnniversary.INDEX_CONTACT_PHOTO)
Log.d("TEMP", "contact $id $name $date $avatarUri")
}
...
Log:
D/TEMP: contact 98014 Test1 1979-10-06 content://com.android.contacts/contacts/98014/photo
contact 4603 test 1990-07-22 content://com.android.contacts/contacts/4603/photo
contact 98341 Voice Mail 2013-11-06 null
I have written the following query to read contacts from a device.
private fun getContactPhoneNumbers(resolver: ContentResolver): Map<String, NameAndPhoneList> {
val startTime = System.nanoTime()
val map = hashMapOf<String, NameAndPhoneList>()
val projection = arrayOf(
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.HAS_PHONE_NUMBER,
ContactsContract.Contacts.PHOTO_THUMBNAIL_URI
)
val selection =
"${ContactsContract.Contacts.DISPLAY_NAME} NOT LIKE '' and ${ContactsContract.Contacts.DISPLAY_NAME} NOT NULL"
resolver.query(
ContactsContract.Contacts.CONTENT_URI,
projection,
selection,
null,
null,
null
)?.let { cursor ->
if (cursor.count > 0) {
while (cursor.moveToNext()) {
val id: String? =
cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID))
val name: String? =
cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
val photoUri =
cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI))
val phone =
if (cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0) {
val pCur: Cursor = resolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
arrayOf(id),
null
)!!
val numbers = mutableListOf<String>()
while (pCur.moveToNext()) {
val phoneNo: String = pCur.getString(
pCur.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER
)
)
numbers.add(phoneNo)
}
pCur.close()
numbers
} else
null
// take contacts which either have email or phone numbers
if (id != null && name != null) {
map[id] = NameAndPhoneList(name, phone, photoUri?.let { Uri.parse(it) })
}
}
}
cursor.close()
}
val endTime = System.nanoTime() - startTime
Timber.i("$CONTACT_SYNC_PHONE_MAP_QUERY_TIME = $endTime")
return map
}
private data class NameAndPhoneList(
val name: String,
val phoneList: List<String>?,
val imageUri: Uri?
)
this is taking 112877872699 ns (~2 min) for a phonebook of length 6,300 contacts. Is this expected or can we optimize further?
for 6,300 contacts (assuming all have a phone) you're making 6,301 queries, which is why it's so slow...
Instead you can benefit from ContactsContract's "implicit join" feature, which allows you to get Contacts.CONTENT_URI fields when querying over the Data.CONTENT_URI table.
So, just query directly over the Data table, get all the phones including the CONTACT_ID, DISPLAY_NAME, etc. fields, and put it in some map contact-id => data.
Here's sample code in Java that can help - https://stackoverflow.com/a/44383937/819355
I am able to get user phone numbers from the contacts list, but i also need names with the numbers,
I know if i use custom adapter then i can get name and number both, but i want to use the default contact picker.
This is my code.
private fun launchMultiplePhonePicker() {
val phonebookIntent = Intent("intent.action.INTERACTION_TOPMENU")
phonebookIntent.putExtra("additional", "phone-multi")
phonebookIntent.putExtra("maxRecipientCount", 20)
phonebookIntent.putExtra("FromMMS", true)
startActivityForResult(phonebookIntent, 110)
}
This does work fine, but only returns phone numbers, and not contact names in onActivityResult.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val bundle = data?.extras
val result = bundle?.getString("result")
val contacts = bundle?.getStringArrayList("result")
}
Edit:
I found out that intent.action.INTERACTION_TOPMENU may not work in all devices, so i used the following approach, its giving me the names with number, but not allowing me to select multiple contacts.
val intent = Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Phone.CONTENT_URI)
startActivityForResult(intent, 10101)
My simple solution to get contacts, may be it will help you
Data class to hold extracted values:
data class ContactModel(val phoneNumber: String, val displayName: String)
Get contacts and map to model
val result = arrayListOf<ContactModel>()
val cursor.context.contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, ContactsContract.Contacts.SORT_KEY_PRIMARY + " ASC")
cursor?.let {
val nameIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)
val phoneIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
while (it.moveToNext()) {
val name = cursor.getString(nameIndex)
val phone = cursor.getString(phoneIndex)
var num = phone
.replace(" ", "")
.replace("-", "")
.replace("(", "")
.replace(")", "")
val contactModel = ContactModel(num, name)
//Prevents duplicated contacts on some devices
if (it.position != 0) {
if (contactModel != result[result.size - 1]) {
result.add(contactModel)
}
} else {
result.add(contactModel)
}
}
}
If you have a list of phone numbers and you need to get the display names, you can use ContactsContract.PhoneLookup, something like this:
private fun phoneToName(phone: String): String? {
val uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phone))
var cur = getContentResolver().query(uri, arrayOf(PhoneLookup.DISPLAY_NAME), null, null, null)
if (cur.moveToFirst()) {
return cur.getString(0)
}
cur.close()
return null
}
P.S. just note that "intent.action.INTERACTION_TOPMENU" is not an official Android API, and is probably not supported by all devices.
EDIT: there's no official way of using the phone-picker for multiple contacts, either you implement your own contact list and let the user choose multiple contacts within your app, or you can allow the user to pick contacts multiple times until they finish.