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.
Related
My problem is that I try to disallow to native Android Contacts application to delete my application's contacts (which are specified by my application account type) from device.
For that I've specified my own SyncAdapter with it's own Service and it's meta-data described in syncadapter.xml. The value of supportsUploading is set to false (this way I say that contacts created by my application are read-only)
However, when I try to delete a contact of my application from standard Contacts app I get the message which says:
You can't delete contacts from read-only accounts, but you can hide
them in your contacts list
Everything seems fine until I try to get data of the contact which I previously deleted ("hide") from standard "Contacts" in my own application.
The returned cursor is null because there isn't any row in Data table associated with this contact's RAW_CONTACT_ID. I also check if the contact exists on the device looking for
it's DELETED flag value in RawContacts table and observed that it has been set to 1 which means that contact has been deleted.
As official documentation describes:
Some sync adapters are read-only, meaning that they only sync
server-side changes to the phone, but not the reverse. If one of those
raw contacts is marked for deletion, it will remain on the phone.
However it will be effectively invisible, because it will not be part
of any aggregate contact.
So the problem is that I can't display this contact's data in my application any more because I don't know how to retrieve them from contacts database. If somebody knows how to handle this situation I would appreciate any advice. Thanks
So after more meticulous search I found the way to retrieve data for any RAW_CONTACT in my application independently of was it deleted from some other application or not.
Using of RawContacts.Entity API does this job.
Previously I tried to retrieve contact's data using such logic:
public Cursor getContactData(long rawContactId) {
return getContentResolver().query(ContactsContract.Data.CONTENT_URI, null,
ContactsContract.Data.RAW_CONTACT_ID + "=" + rawContactId, null);
}
And this method always returned null for deleted contact.
But using RawContacts.Entity such way:
public Cursor getContactData(long rawContactId) {
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, id);
rawContactUri = Uri.withAppendedPath(rawContactUri, RawContacts.Entity.CONTENT_DIRECTORY);
return getResolver().query(rawContactUri, null, null, null);
}
allows to fetch contact's data inside application with appropriate authority regardless was it deleted by 3d-party application or not.
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.
I am building a custom phonebook application. As part of this, I need to add new contacts, or additional info (phone numbers or emails) under existing contacts. While this works well under simple test cases, there are certain situations (and it's not obvious when) where I end up with two issues:
Multiple copies of a contact get created, often hundreds! These are all blank, and simply share the same display name with the original contact that was modified
Multiple numbers (exactly the same) under the contact. The really unusual thing is that these numbers have the same _ID in the contactContracts.Data table, so can't understand how there can be multiple instances with the same unique ID. I checked to see if it's a display issue, but doesn't seem to be
Both of the above makes me question if I am inserting information correctly. So what I want to know is:
When creating a new contact, is there a default account_name and account_type I should be specifying? I am currently using null for both
When I add a new phone number to an existing contact, what is the right approach to inserting data into the contactContracts.Data table? I am currently inserting this number with rawContactID as a reference ID, and I am getting this rawContactID from the contactContracts.RawContacts table, by using my contact's contactID as reference. Note that because I often get multiple raw contacts, I end up picking up the last one (arbitrary choice) to do my phone number insert.
You can use AccountManager Class for getting account name & account type information.
final AccountManager accountTypes = AccountManager.get(getApplicationContext());
final Account accounts[] = accountTypes.getAccounts();
for (final Account account: accounts) {
Log.i(TAG, account.name);
}
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.
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