Show contacts with multiple phone numbers using CursorLoader - android

I want to retrieve all the contacts in an Android phone along with all their phone numbers. Let's say following is the list of contacts on my phone along with their phone numbers:
A - 1111 (Mobile), 2222(Home), 3333(Work)
B - 4444 (Mobile), 5555(Home)
C - 6666 (Mobile), 7777(Home), 8888(Work)
I want to display the contacts in the following fashion but using CursorLoader.
Things I have tried :
Get all the contacts and their phone numbers at a time and save them in an array list of custom contact objects and use them to display the list in a RecyclerView.
Issue with this is that if there are a lot of phone numbers then re-visiting this screen causes a blank screen possibly either due to resource not being free or memory leak issues.
Stack Overflow won't indent the code properly so here it is: https://pastebin.com/y9i5R5iN
final Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, String.valueOf(contactListCursor.getLong(ContactsQuery.ID)));
Uri thisContactUri = ContactsContract.Contacts.lookupContact(context.getContentResolver(), uri);
final UserContact contact = new UserContact();
contact.setName(contactListCursor.getString(contactListCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)));
final ArrayList<PhoneType> multipleContacts = new ArrayList<>();
final Uri phoneNumUri = Uri.withAppendedPath(thisContactUri, ContactsContract.Contacts.Data.CONTENT_DIRECTORY);
final Cursor contactPhoneNumCursor = contentResolver.query(phoneNumUri, ContactPhoneQuery.PROJECTION, ContactPhoneQuery.SELECTION, null, null);
Get all the contacts using a CursorLoader and a CursorAdapter by querying the ContactsContract.Contacts.CONTENT_URI and getting the ID and DISPLAY_NAME_PRIMARY. In the getView() method I thought I could initialize a second CursorLoader to call into the details of the contact that the first cursor has given me. Something like this :
IF the user has multiple contacts then show a list of radio buttons, else show a textview with the only number they have.
This seems to load the phone numbers in advance and not update the view later on. Either because there's a delay or something I am not sure.

Found a way to do it.
Used the CursorLoader to pass to the CustomAdapter for initial query of contacts and then used the _ID field to lookup the contact and created a URI for the contact and looked up the Phone specific details inside the CustomAdapter's getView method itself.
I was trying to use CursorLoaders in both or Bulk loads in both. The hybrid approach worked out best.
Hope this helps someone. :)

You can use a single query to get all phone numbers in the Database along with their contact-ids and contact-names.
I would run this single query once, store all data in a map (contact-id => name, phone, ...)
And then use a custom adapter to display the map as a list of contacts.
Here's the code for querying all phones along with contact-ids and names (make sure you import everything from ContactsContract):
Map<Long, List<String>> contacts = new HashMap<Long, List<String>>();
String[] projection = { Data.CONTACT_ID, Data.DISPLAY_NAME, Data.MIMETYPE, Data.DATA1, Data.DATA2, Data.DATA3 };
String selection = Data.MIMETYPE + " IN ('" + Phone.CONTENT_ITEM_TYPE + "')";
Cursor cur = cr.query(Data.CONTENT_URI, projection, selection, null, null);
while (cur != null && cur.moveToNext()) {
long id = cur.getLong(0);
String name = cur.getString(1);
String mime = cur.getString(2); // type of data (e.g. "phone")
String data = cur.getString(3); // the actual info, e.g. +1-212-555-1234
int type = cur.getInt(4); // a numeric value representing type: e.g. home / office / personal
String label = cur.getString(5); // a custom label in case type is "TYPE_CUSTOM"
String labelStr = Phone.getTypeLabel(getResources(), type, label);
Log.d(TAG, "got " + id + ", " + name + ", " + kind + " - " + data + " (" + labelStr + ")");
// add info to existing list if this contact-id was already found, or create a new list in case it's new
List<String> infos;
if (contacts.containsKey(id)) {
infos = contacts.get(id);
} else {
infos = new ArrayList<String>();
infos.add("name = " + name);
contacts.put(id, infos);
}
infos.add(kind + " = " + data + " (" + labelStr + ")");
}
Note: The map's value is a list of strings, just for code-clarity. You should change it to be a custom Contact object that holds the name and list of phones in a proper Java Object

Related

Android Content provider. How to get all data by one request from contact book

Is it possible to retrieve all data from contact book by one request (phone number, email, first name, last name, photo, thumbnail). Right now I am running a separate request for a first and last name, a separate request for a phone number and a separate request for an email, and so on. Is it possible to get all this data in one request? Please help me.
Yes, you can.
You're probably running your queries on Alias tables such as CommonDataKinds.Phone and CommonDataKinds.Email, but the actual data for all these tables is stored in a single table called Data.
So you should query directly on Data and use the MIMETYPE column to figure out the "type" of the current row you're iterating over.
Here's an example of getting name, email, phone.
You can add more mimetypes to the list to get more types of data.
I'm using a HashMap to keep a list of values for each contact-ID but you would probably want to create a custom Contact class and put that info in it.
Map<Long, List<String>> contacts = new HashMap<Long, List<String>>();
String[] projection = {Data.CONTACT_ID, Data.MIMETYPE, Data.DATA1, Data.DATA2, Data.DATA3};
// query only name/emails/phones
String selection = Data.MIMETYPE + " IN ('" + StructuredName.CONTENT_ITEM_TYPE + "', '" + Phone.CONTENT_ITEM_TYPE + "', '" + Email.CONTENT_ITEM_TYPE + "')";
ContentResolver cr = getContentResolver();
Cursor cur = cr.query(Data.CONTENT_URI, projection, selection, null, null);
while (cur != null && cur.moveToNext()) {
long id = cur.getLong(0);
String mime = cur.getString(1); // type of data (name / phone / email)
String data = cur.getString(2); // the actual info, e.g. +1-212-555-1234
String kind = "unknown";
switch (mime) {
case Phone.CONTENT_ITEM_TYPE:
kind = "phone";
break;
case StructuredName.CONTENT_ITEM_TYPE:
kind = "name";
break;
case Email.CONTENT_ITEM_TYPE:
kind = "email";
break;
}
Log.d(TAG, "got " + id + ", " + kind + " - " + data);
// add info to existing list if this contact-id was already found, or create a new list in case it's new
List<String> infos;
if (contacts.containsKey(id)) {
infos = contacts.get(id);
} else {
infos = new ArrayList<String>();
contacts.put(id, infos);
}
infos.add(kind + " = " + data);
}

showing contacts by filtering account_type column from RawContacts

Im getting showing contacts on a RecyclerView and below code retrieve contacts
Uri Contact_URI=ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
return new CursorLoader(getActivity(),Contact_URI,null,null,null,Build.VERSION.SDK_INT
>= Build.VERSION_CODES.HONEYCOMB ?
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY :
ContactsContract.Contacts.DISPLAY_NAME+ "ASC");
but some contacts are being shown multiple times so I decided to filter Contacts on account_type column. Below code filter based on account_type
if(list.getString(list.getColumnIndex("account_type")).equals("Local Phone Account") || list.getString(list.getColumnIndex("account_type")).equals("SIM Account") ) {
textView.setText(list.getString(list.getColumnIndex(Build.VERSION.SDK_INT
>= Build.VERSION_CODES.HONEYCOMB ?
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY :
ContactsContract.Contacts.DISPLAY_NAME)));
number.setText(list.getString(list.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)));
number.setText(list.getString(list.getColumnIndex("account_type")));
}
Problem is values of account_type for sim and phone contacts varies from device to device. In Samsung GT-l9082 gives values for sim "Sim Account" and for Phone "Local Phone Account" but When I tested it on Galaxy J5 it shows different values against account_type for sim and phone contacts.I want to show only Sim and Phone Contacts
This is not the way to go.
CommonDataKinds.Phone.CONTENT_URI is a table of all the phones in the Contacts DB, not contacts.
So even if you filter to just one account, if a contact contains more then one phone, it'll appear twice in your list.
If you want to display only one row per contact, but you still need to display a phone in your main list, you can't use the CursorLoader paradigm (which actually sucks and I wouldn't use it anyway).
Instead run a simple query for all the items in the Phones.CONTENT_URI table, and create a HashMap from CONTACT_ID to a list of NUMBERs, and then display one row per item in the map, and you'll get access to that contact's list of phones as well for display.
Map<String, List<String>> contacts = new HashMap<String, List<String>>();
String[] projection = { Phone.CONTACT_ID, Phone.DISPLAY_NAME, Phone.NUMBER };
Cursor cur = cr.query(Phone.CONTENT_URI, projection, null, null, null);
while (cur != null && cur.moveToNext()) {
long id = cur.getLong(0);
String name = cur.getString(1);
String data = cur.getString(2); // the actual info, e.g. +1-212-555-1234
Log.d(TAG, "got " + id + ", " + name + ", " + data);
// add info to existing list if this contact-id was already found, or create a new list in case it's new
String key = id + " - " + name;
List<String> infos;
if (contacts.containsKey(key)) {
infos = contacts.get(key);
} else {
infos = new ArrayList<String>();
contacts.put(key, infos);
}
infos.add(data);
}
// now you can iterate over the 'contacts' map to display all contacts

Search contacts provider for phone numbers in multiple formats?

I've been working on a block of code to let the user search (character by character using an AutoCompleteTextView) contacts by name, email or phone number. I've worked out the below code:
// General contact data, so we have to get the DATA1 attribute and use MIMETYPE
// to figure out what it is. Usually we'd query, say, ContactsContract.CommonDataKinds.Email.CONTENT_URI
Uri uri = ContactsContract.Data.CONTENT_URI;
// Limit the query results to only the columns we need for faster operations.
// Using a projection also seems to make the query DISTINCT
String[] projection = new String[] {ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Data.DATA1,
ContactsContract.Data.MIMETYPE};
// Find contact records with an email address or phone number
// Search the name and data1 field (which may contain an email or phone number)
// for user-entered search phrase
String filter = "(" + ContactsContract.Data.MIMETYPE + "=? OR " + ContactsContract.Data.MIMETYPE + "=?)"
+ " AND (" + ContactsContract.Data.DATA1 + " LIKE ? OR " + ContactsContract.Data.DISPLAY_NAME + " LIKE ?)";
String wildcardedConstraint = "%" + constraintString + "%";
String[] filterParams = new String[]{ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, wildcardedConstraint, wildcardedConstraint};
// Sort contacts with the most recently contacted ones first. That's often 0 (unset)
// so do a sub-sort by last updated date, most recent contacts first
String orderBy = ContactsContract.Contacts.LAST_TIME_CONTACTED + " DESC, " + ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + " DESC";
Cursor cursor = getContext().getContentResolver().query(uri, projection, filter, filterParams, orderBy);
if (cursor != null) {
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DISPLAY_NAME));
String data1 = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DATA1));
String mimetype = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.MIMETYPE));
String number = null;
String email = null;
if (mimetype.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
email = data1;
} else if (mimetype.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
number = data1;
}
items.add(new Person(name, number, email));
Log.e("temp", name + " " + data1 + " " + mimetype);
}
cursor.close();
}
There is a problem with the phone number search, however. In contacts, phone numbers are in many different formats:
+101234567890
(123) 456-7890
1234567890
123-456-7890
And so on.
How can I adapt my Contacts query filter so the user's input will find phone numbers in any format--preferably without making the entire query extremely slow?
Some solutions I've found rely on editing table data to standardize the phone numbers, which isn't an option with contacts. Maybe that normalized number field would work... if I could find a way to easily build it into this query on the Contacts Data table. I know I could do extra phone number searches for each record, or use Java to make the checks, but I think that would make it very slow. Perhaps a regexp SQL operator in the query--but I don't know how I could make it work for the user's character-by-character search where they may have only entered part of the phone number.
Any ideas?
You can do this with Android's built-in SQLite function PHONE_NUMBERS_EQUAL, which compares two numbers and will return 1 if they're identical enough for caller ID purposes.
You simply need to change your filter as follows:
String filter = "(" + ContactsContract.Data.MIMETYPE + "=? OR "
+ ContactsContract.Data.MIMETYPE + "=?) AND "
+ "(PHONE_NUMBERS_EQUAL(" + ContactsContract.Data.DATA1 + ", ?, 0) OR "
+ ContactsContract.Data.DATA1 + " LIKE ? OR "
+ ContactsContract.Data.DISPLAY_NAME + " LIKE ?)";
And add another wildcardedConstraint to your filterParams:
String[] filterParams = new String[] { ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
wildcardedConstraint,
wildcardedConstraint,
wildcardedConstraint };
The final INTEGER parameter in the PHONE_NUMBERS_EQUAL function indicates whether to use strict number comparation; 1 meaning do use strict, 0 meaning non-strict. Apparently this is a system-wide setting that can be retrieved from the system Resources, but I am uncertain as to what factors dictate how this is determined for a particular environment. The example above just uses non-strict comparation. However, if it is a concern, the actual resource value can be obtained like so:
private static final String STRICT_COMPARE = "config_use_strict_phone_number_comparation";
...
int strictResId = Resources.getSystem().getIdentifier(STRICT_COMPARE, "bool", "android");
boolean useStrict = Resources.getSystem().getBoolean(strictResId);

Android Contact Picking Uri

I have a string content//com.android.contacts/contacts/contacts/2 ie aft the user has selected a particular number.
From this string, how do i get the phone number of this particular contact?
I am new to android environment, so my question might seem a bit primitive.
Cursor c = (Cursor)mAdapter.getItem(a.keyAt(i));
Long id = c.getLong(c.getColumnIndex(ContactsContract.Contacts._ID));
contacts.add(ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id));
chosenContacts = sb.toString();
chosenContacts is my string and that contains content//com.android.contacts/contacts/contacts/2
You need a second request. You can use the id of your snippet for it:
Cursor phoneNoCursor = contentResolver.query(
Data.CONTENT_URI,
new String[] {Phone.NUMBER, Phone.TYPE, Phone.LABEL},
Data.MIMETYPE + " = ? AND " + Data.CONTACT_ID + " = ? ",
new String[] {String.valueOf(Phone.CONTENT_ITEM_TYPE, id)},
null);
For the list of possible types see the description of the Phone class. But maybe the number itself and its label are enough anyway.

Android contacts tables update but do not get saved to SIM

I am trying to update the phone number associated with a contact on the SIM. I am able to update the Data table's row ok, but the change does not get propagated to the SIM. I dump out the Data row after it has been updated; the _ID is still the same, the phone (data1) col has the new phone number. When I powercycle the phone, the entry has the old value. The raw contact number of the contact has been incremented by 1. Any ideas on how to make the change persistent in the SIM? (Yes, I know some doc says access to SIM is not supported. But the Contacts app can do it - there must be a way for an app to update the SIM.) There may be typos - I had to (re)type the code by hand here.
Here is the code I use to update the row.
String rawId = "87"; // provided by caller; hardcoded here for brevity
String num = "212-222-3333"; // new phone #
ContentResolver cr = ...get actvity's resolver...
Uri uri = ContactsContract.Data.CONTENT_URI;
ContentValues cv = new ContentValues();
cv.put(ContactsContract.CommonDataKinds.Phone.Number, num);
String select = ContactsContract.Data.RAW_CONTACT_ID " + " = " + rawId + " and " +
ContactsContract.Data.MIMETYPE + " = '" +
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_ITEM + "'";
String[] selectArgs = {};
cr.update(uri, cv, select, selectArgs);
Catch exception and other details omitted.
Thanks for any clues!

Categories

Resources