_ID column in Contacts database - android

I read that the different entries in different tables are linked via the _ID column in that table. For example a contact might have an _ID = 1 I get via
ContactsContract.Contacts._ID
and now I want to read the phone number of that contact using
Cursor phoneCursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactId , null, null);
//...
String number = phoneCursor.getString(phoneCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER));
String id = phoneCursor.getString(phoneCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone._ID));
This works fine, but what I would expect is that if the _ID of the contact is 1 that the _ID of the phone number is as well one since they belong together, but they are not equal. So the question is how does Android match these entries?
Thanks, A.

A contact corresponds to 1 or more raw contacts. The actual data for the contact is stored as
ContactsContract.Contacts.Data
Each data item contains the id of the raw contact it belongs to.
Each raw contact contains the id of the contact it belongs to.
So, given a Contact id you can find what raw contacts it represents. Get the raw contact ids and then find what data is within this contact.

you could get more details when you see the database tables
as far as my understanding goes this is the information I got from viewing android contacts2.db file using sqlite3 db browser
I created some 6 contacts for testing my android contacts
in contacts table the _id and raw_contact_id is actually same (it means that ContactsContract.Contacts provider )
in data table we get the real details of the person like phone number and email and firstname and last name using mimetype Id as the where condition
when considering data table use raw_contact_id as the where condition to get a particular records of the contact
ex my raw_contact_id is 1
I got 3 rows which consists of email, phone and display name in data1 column
you may ask that what if we want only phone or email or display name ....there you will need the mimetype_id as the where condition
to get phonenumber mimetype_id is 5
to get display_name mimetype_id is 6
to get email mimetype_id is 1
you may not understand all these stuff if you are a beginner ...but once if u see the internal tables u get every thing which I told you
access contact details only by contact_id or raw_contact_id and not by _id which is in every table ..that column is for different purpose ..
you can have a look at what Im saying in this image
http://img94.imageshack.us/i/tablesxa.jpg

Related

Why is ContentResolver.query() returning duplicate entries from contacts?

So I'm reading in these contacts using Android's native implementation, using a test phone with about 1,056 contacts on it. The problem I'm having is that it's rather slow, so I'm logging the outcomes of my cursor.moveToNext(), and seeing that it's reading in contacts past the last in an overlapping, duplicating fashion. It does, however, have previous logic implemented so the app compares the hashed version of this list of entries to the previously saved one every second... which eventually brings the entries back to the correct value, order, and contents. However, using the code below, at worst it's pulling the entire contact list AGAIN, and then putting it in there with the same list (essentially reads the same address book twice and reads it in, doubling entries). This gets worse the larger your contact list is (smaller books, like this Galaxy I have with 9 contacts, don't seem to be affected; whereas my phone with about 106 is slightly, and this tester with 1,053 significantly) with the larger phones I've tested taking upwards of a minute and a half to two minutes before it's fully updated, accurate, and done.
What's confusing is that it somehow, even after all this duplication, the check manages to come back and be exactly what it was supposed to be (i.e 1,053 contact phone I see it adding the 2,106th row, then it immediately loads the proper 1,053 contact book).
Here's how my cursor is defined:
Cursor c = mContext.getContentResolver().query(addressBookURI,
CONTACTS_QUERY_PROJECTION, CONTACTS_QUERY_SELECTION, null, CONTACTS_QUERY_SORT);
Here's each of the components of said Cursor "c":
private static final Uri addressBookURI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "1").build();
private static final String[] CONTACTS_QUERY_PROJECTION = {
ContactsContract.Data.CONTACT_ID,
ContactsContract.Contacts.HAS_PHONE_NUMBER,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Data.DATA1,
ContactsContract.Data.DATA2,
"account_name"
};
private static final String CONTACTS_QUERY_SELECTION = "in_default_directory=1 and has_phone_number=1 and mimetype='vnd.android.cursor.item/phone_v2'";
private static final String CONTACTS_QUERY_SORT = "contact_id ASC";
Processing the Cursor "c"'s reads:
List<String[]> entries = new ArrayList<>();
while (c.moveToNext()) {
String id = c.getString(0);
String name = c.getString(2);
String phone = c.getString(3);
String type = c.getString(4);
String account_name = c.getString(5);
Log.d(sTag, "contact:row:" + id + ":" + name + ":" + phone + ":" + type + ":" +
account_name);
entries.add(new String[]{id, name, phone, type});
It's in that Log.d() statement where I can see duplicates of contacts, wherein the only difference between the original contact and follow-up duplicates is the "id" variable (keeps increasing, period).
For example (if it happened with smaller amounts of contacts; all values are made up):
contact:row:1:Maurine Lastpass:4145737719:3:D8:0B:9A:CC:88:BF
contact:row:2:Blondell Sosig:4013008122:3:D8:0B:9A:CC:88:BF
contact:row:3:Amber Altingle:8885554422:2:D8:0B:9A:CC:88:BF
contact:row:4:Frank Helgenson:8885554422:2:D8:0B:9A:CC:88:BF
contact:row:5:Hiro Xin:8885554422:2:D8:0B:9A:CC:88:BF
contact:row:6:Baley Balix:6316773675:2:D8:0B:9A:CC:88:BF
contact:row:7:Henry Halgor:6316773675:2:D8:0B:9A:CC:88:BF
contact:row:8:Hammy Xevronic:6316773675:2:D8:0B:9A:CC:88:BF
contact:row:9:Maurine Lastpass:4145737719:3:D8:0B:9A:CC:88:BF
contact:row:10:Blondell Sosig:4013008122:3:D8:0B:9A:CC:88:BF
contact:row:11:Amber Altingle:8885554422:2:D8:0B:9A:CC:88:BF
contact:row:12:Frank Helgenson:8885554422:2:D8:0B:9A:CC:88:BF
contact:row:13:Hiro Xin:8885554422:2:D8:0B:9A:CC:88:BF
contact:row:14:Baley Balix:6316773675:2:D8:0B:9A:CC:88:BF
contact:row:15:Henry Halgor:6316773675:2:D8:0B:9A:CC:88:BF
contact:row:16:Hammy Xevronic:6316773675:2:D8:0B:9A:CC:88:BF
I've tried storing the very first contact and letting it read from the Cursor until it matches up with that contact, but since the "id" variable keeps increasing, it makes the contact different enough to be ignored. I removed the ID portion of the contact, changed the URI from the default to one with an explicit REMOVE_DUPLICATE_ENTRIES = 1 parameter on it, and I tried to bring in the "real" count using ContactsContract.Data._COUNT in order to check the number of entries on the cursor against it, but that just crashes with "no such column _COUNT".
Is there any reason why the cursor would be pulling duplicate contacts like this? Is there something wrong with the structure of my query that is causing this sort of duplication?
let's recap the way ContactsContract DB is organized:
Contacts table - contains one row per contact, but hardly any information
RawContacts table - can have multiple rows, each assigned to a single contact-ID (from the previous table), contains a logical group of multiple Data rows usually for a single SyncProvider such as Google.
Data table - contains the actual data of a RawContact, each row has a MIMETYPE column which states what kind of data this row is about (phone, email, name, etc.) + 15 data columns to hold the information itself.
There are also pseudo tables, such as ContactsContract.CommonDataKinds.Phone which you are querying in your code, which basically queries over the Data table but with a specific MIMETYPE value, for example Phone.CONTENT_ITEM_TYPE.
In your code you are querying over CommonDataKinds.Phone.CONTENT_URI which means you are asking for all the different phone numbers in the DB.
So if a single contact has 3 phone numbers, you will get 3 rows for that contact (each with a different phone number).
However looking at your output, it seems like every contact has a single phone number in the DB, but it looks like the contacts themselves are duplicated.
So for example Amber Altingle has contact ID 3 and also 11, which means you have two separate contacts named Amber Altingle.
This is not duplication in the query code, but possibly duplication in the contact creation code.

How to uniquely identify a contact on ContactsContract.Contacts table

I have an app that gets the ContactsContract.Contacts.LOOKUP_KEY of a contact on the device and saves it on the app Db.
After reading this page I thought I could use the LOOKUP_KEY to uniquely identify a contact, even when a contact is edited (for example after editing the name of the contact).
Actually I saw that after editing a contact, its LOOKUP_KEY changes, so I cannot use anymore the LOOKUP_KEY I saved on my app DB.
My question is: is there a way to uniquely identify a contact on ContactsContract.Contacts from when it is created for the first time on the device until it is deleted from the device?
Thank you
A LOOKUP_KEY is not meant to be used as a key on its own, instead it should be used together with a contact's _ID to form a full lookupUri.
The lookupUri can then be used to find a contact in CONTENT_LOOKUP_URI tables.
The CONTENT_LOOKUP_URI basically first looks for the contact by _ID, if it fails to find it, or the _ID seems like the wrong contact, it uses hints from the LOOKUP_KEY part to try and track down the correct contact for you.
From CONTENT_LOOKUP_URI
A content:// style URI for this table that should be used to create
shortcuts or otherwise create long-term links to contacts. This URI
should always be followed by a "/" and the contact's LOOKUP_KEY. It
can optionally also have a "/" and last known contact ID appended
after that. This "complete" format is an important optimization and is
highly recommended.
As long as the contact's row ID remains the same, this URI is
equivalent to CONTENT_URI. If the contact's row ID changes as a result
of a sync or aggregation, this URI will look up the contact using
indirect information (sync IDs or constituent raw contacts).
Lookup key should be appended unencoded - it is stored in the encoded
form, ready for use in a URI.
From getLookupUri(long contactId, String lookupKey)
Build a CONTENT_LOOKUP_URI lookup Uri using the given _ID and
LOOKUP_KEY.
From LOOKUP_KEY
An opaque value that contains hints on how to find the contact if its
row id changed as a result of a sync or aggregation.
The row id (primary key) for each contact called _ID.

Retrieving all Contacts that have a specific Account attached to them

I am making a communication app that has been set up with a specific Account type in the AccountManager and has a SyncAdapter attached to it. Several of my contacts have a RawContact which has a MIMETYPE of my new account and some associated data.
I am looking for the correct way to query a list/cursor of every contact in a phones contact list that has one of these new accounts attached to it. I want to get the whole contact so i can do things like retrieve their phone numbers, display names and uris for pictures, but i only need the contacts that have my new type of account attached.
I have a feeling i need to join two tables together with some projection or selection parameters to get all of this data in one cursor object. Im just not sure how exactly to do this, though ive tried a bunch of different things already, namely being able to get all the contacts, or all the RawContact rows for my specific account type, but not both together.
Thanks
Get Raw_Id's of all contacts matching the account type and name:
getContentResolver().query(ContactsContract.RawContacts.CONTENT_URI,
new String[]{ContactsContract.RawContacts._ID,
},
ContactsContract.RawContacts.ACCOUNT_NAME + " = ? AND " +
ContactsContract.RawContacts.ACCOUNT_TYPE + " = ? ",
new String[]{mAcccountName,mAccountType},null
);
For each _ID, fetch all rows from ContactsContract.Data , ContactsContract.RawContacts._ID matches ContactsContract.Data.RAW_CONTACT_ID here.
These rows have all the data related to that contact's Raw_id.
Refer to the documentation for the structure of ContactsContract.Data table, and what data it holds.

Modifying contact information

I'm trying to insert and update a piece of information on an existing contact so I've created a sample application in order to develop the functionality. All I want my sample app to do is to insert (or if present) update an email address on a contact.
I'm selecting a contact through the system Intent like so:
startActivityForResult(new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI), PICK_CONTACT_REQUEST);
The URI which is returned is that of the Contact (RawContact?) which was selected and comes in this form:
content://com.android.contacts/contacts/lookup/0r2-2A90214945/2.
I can pull back all of the Data (RawContact?) items on this by performing the following code:
Cursor cursor = contentResolver.query(mContactUri, null, null, null, null);
try {
if (cursor.moveToFirst()) {
for(int i=0; i < cursor.getColumnCount(); i++) {
String message = cursor.getColumnName(i);
Log.v("", message);
}
}
} finally {
cursor.close();
}
From this I should be able to determine if the contact already has an CommonDataTypes.Email Data member:
cursor.getColumnIndex(CommonDataKinds.Email.CONTENT_ITEM_TYPE) != -1;
And then perform one of the following to either Insert or Update the Data:
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
ops.add(ContentProviderOperation.newInsert(mContactUri)
.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
.withValue(Email.DISPLAY_NAME, "somebody#android.com")
.withValue(Email.TYPE, Email.TYPE_HOME)
.build());
getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
But this gives me an exception:
java.lang.UnsupportedOperationException: URI: content://com.android.contacts/contacts/lookup/0r2-2A90314945/2, calling user:
Hopefully someone can see what I've missed.
PS, I'm using these permissions:
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
The Android people need to update their documentation. It actually served to make me know less about what was happening than I would have gotten from guessing. It suggests that you can pull back a Contact, which will contain many RawContacts which will contain Data.
That interpretation is completely wrong. ContactContracts data is instead three normal average everyday database tables*:
ContactContract Tables
Table: Contacts
Access URI: Contacts.CONTENT_URI
Primary Key**: Data._ID
Description:
This table contains information about a Contact (when was it added, what's is it's user icon, does it have a custom ringtone).
Relationship: It has a 1-to-many relationship with the RawContact table.
Table: RawContacts
Access URI: RawContacts.CONTENT_URI
Primary Key: Data._ID
Foreign Key**: Data.CONTACT_ID
Description:
This table contains information about a related set of Data items. A RawContact could contain Email Type, Email Display Name, Phone Number, Phone Display Name, etc. A RawContact can be aggregated with other RawContacts to make a Contact as a user sees it. A Contact could contain just one RawContact.
Relationship: It has a 1-to-many relationship with the Data table.
Table: Data
Access URI: Data.CONTENT_URI
Primary Key: Data._ID
Foreign Key: Data.RAW_CONTACT_ID
Description:
This table contains a single field of information. An email address, A phone number, A phone number type (home/work), A nickname, A display name.
In answer to the question
I've uploaded the entire sample project to GitHub in order to allow others to see how to query, update and insert records using ContactContract.
You can find the project to download here:
https://github.com/gwoodhouse/ContactContractSample
If you just want to look at the java code performing the query/update/insert here is the class file:
https://github.com/gwoodhouse/ContactContractSample/blob/master/ContactsIntegration/src/com/woodhouse/example/activity/ContactsIntegrationActivity.java
Hope this helps!
*Not a table, but a ContentProvider
** not strictly true.

in Android 2.0 or later, is it possible to identify all the raw contacts from which a single aggregate contact is formed?

as far as you might know, there are contacts (aggregate contacts) which are formed by aggregation of two or more raw contacts in Android V2.x
is it possible to identify all the raw contacts from which a single aggregate contacts is formed through a query on the ContactsContract.Contacts or is there a way to identify these
contacts at all?
i could not find any flag or database field that tells me that this aggregate contacts is linked with these raw contacts.
any suggestions?
You can check AggregationExceptions.CONTENT_URI Table where relationship type are AggregationExceptions.TYPE_KEEP_TOGETHER, AggregationExceptions.TYPE_KEEP_SEPARATE, etc.
and you can find Raw_contact_id1 and raw_contact_id2.
Example of data into database. Lets say 1,2,3,4 are in relation so you can find following pairs.
Raw_contact_id1 raw_contact_id2 Relationship type
1-> 2, 1->3, 1->4, 2->3, 2->4, 3->4
A Contact cannot be created explicitly. When a raw contact is inserted, the provider will first try to find a Contact representing the same person. If one is found, the raw contact's CONTACT_ID column gets the _ID of the aggregate Contact. If no match is found, the provider automatically inserts a new Contact and puts its _ID into the CONTACT_ID column of the newly inserted raw contact.
So, while reading all the contacts one by one we can take its _ID value and can retrieve all the contacts from raw_contacts where _ID matches with raw_contacts.CONTACT_ID.
If the count is greater than 1 then we can conclude that it is linked with those numbers of contacts else it is not linked with any other contact.

Categories

Resources