If a SyncAdapter A for Account A creates a raw contact in the ContactsContract.RawContacts table, can SyncAdapter B for Account B update the RawContact added by SyncAdapter A.
Of particular interest is the sync1 or any of the sync fields in the raw contacts table?
If this is possible what are the possible issues that might arise as a result.
Can anyone show a sample of how this should be done?
There's no per-contact-permission-model on the Contacts DB, every app and every SyncAdapter with the Contacts permission can read/write to any field in any contact.
However, the syncX fields under RawContact are used for proprietary purposes by the owning SyncAdapter (e.g. to keep track on what contact requires syncing, which one is dirty, when was it last synced, the backend-contact-id value, etc.), so make sure you don't touch anything you don't know what it's for, or you might risk corrupting some contacts.
To modify the Sync1 value on RawContact with id 1234, do:
ContentResolver cr = context.getContentResolver();
ContentValues values = new ContentValues(2);
values.put(RawContacts.SYNC1, "Hello World");
cr.update(RawContacts.CONTENT_URI, values, RawContacts._ID + "=" + 1234, null);
UPDATE - batch updates
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
ops.add(ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI)
.withSelection(RawContacts._ID + "=" + 1234, null)
.withValue(RawContacts.SYNC1, "Hello")
.build());
ops.add(ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI)
.withSelection(RawContacts._ID + "=" + 5678, null)
.withValue(RawContacts.SYNC1, "World")
.build());
getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
Related
i use the following the code to update the number:
ContentValues mobilePhoneValues = new ContentValues();
mobilePhoneValues.put(ContactsContract.CommonDataKinds.Phone.TYPE, typePhone);
mobilePhoneValues.put(ContactsContract.CommonDataKinds.Phone.NUMBER, binding.numberEdit.getText().toString());
getContentResolver().update(ContactsContract.Data.CONTENT_URI, mobilePhoneValues,
ContactsContract.CommonDataKinds.Phone.NUMBER + "=?", new String[]{mobilePhoneNumbers.get(0)});
It works perfectly but when i try to update the email using same logic it fails to update it.
Code:
ContentValues contentValues1 = new ContentValues();
contentValues1.put(ContactsContract.CommonDataKinds.Email.TYPE, typeEmail);
contentValues1.put(ContactsContract.CommonDataKinds.Email.DATA, binding.emailEdit.getText().toString());
getContentResolver().update(ContactsContract.Data.CONTENT_URI, contentValues1,
ContactsContract.CommonDataKinds.Email.DATA + "=?", new String[]{homeEmailNumbers.get(0)});
Why is it the same logic updates the number but fails to update the email ?
Please help
There's a big problem in both examples.
You are telling the API - change any occurrence of phone (or email) "X" to "Y" across all contacts.
It depends on your use case, but I'm assuming you want to let users edit contact information.
For that use case that's not what you intend to be doing. instead, you should tell the API - take row with ID "12345" and change its value to "Y".
Adding the contact-id to the WHERE clause will also have unintended consequences when you consider a case where a certain contact has 2 duplicate phone numbers, and the user wants to edit one of them - you don't want to modify both numbers in this case.
You should first change the way you read and present the data, so every data row for every contact also holds the specific Data table row ID (Data._ID)
and then the code is:
batch update approach (preferred):
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI)
.withSelection(Data._ID + "=?", new String[]{String.valueOf(dataId)})
.withValue(CommonColumns.TYPE, newType)
.withValue(CommonColumns.DATA, newData)
.build());
getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
single change approach:
ContentValues values = new ContentValues();
values.put(CommonColumns.TYPE, newType);
values.put(CommonColumns.DATA, newData);
getContentResolver().update(Data.CONTENT_URI, values,
Data._ID + "=?", new String[]{String.valueOf(dataId)});
The use of CommonColumns and Data._ID allows for the same code to work for both phones, emails, and some other common types.
I am currently working on a project in which I want to access the mobile contacts, So I have managed to create account with accountmanager and also able to perform Syncadapter operation. I could see my account got created in the mobile settings->Accounts. However, when I try to get all the contacts with my account with below code ,it does not work. Its showing all apps(google.com and WhatsApp.com) contacts except my app account contacts.
Cursor cursor = getContext().getContentResolver().query(ContactsContract.RawContacts.CONTENT_URI,
new String[]{ContactsContract.RawContacts.DIRTY, ContactsContract.RawContacts.ACCOUNT_TYPE},
null,
null,
null);
if (cursor != null && cursor.getCount() >0) {
cursor.moveToFirst();
while(!cursor.isAfterLast()) {
Log.d("Dirty",cursor.getString(cursor.getColumnIndex(ContactsContract.RawContacts.DIRTY)));
Log.d("ACCountType",cursor.getString(cursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_TYPE)));
cursor.moveToNext();
}
cursor.close();
}
What I dont understand is do I need to create ContentProvider and insert all contacts back to Contactsprovider on behalf of my account?
Not sure if you've fully understood how the ContactsProvider works.
There are a few things that you should know:
Every RawContact is uniquely assigned to one specific account, it can not belong to more than one account (hence your app usually can't sync existing contacts, because they already have an account).
All apps have the same view on all the contacts, in particular all apps can see and modify all contacts (given they have the permissions), though there are a few exceptions to that rule.
When you sync a contact to your account you must specify your account as shown on ContactsContract.RawContacts
ContentValues values = new ContentValues();
values.put(RawContacts.ACCOUNT_TYPE, accountType);
values.put(RawContacts.ACCOUNT_NAME, accountName);
Uri rawContactUri = getContentResolver().insert(RawContacts.CONTENT_URI, values);
long rawContactId = ContentUris.parseId(rawContactUri);
When you read contacts you get contacts of all accounts, unless you specify Uri query parameters or a selection:
Uri rawContactUri = RawContacts.CONTENT_URI.buildUpon()
.appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName)
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType)
.build();
Cursor c1 = getContentResolver().query(rawContactUri,
RawContacts.STARRED + "<>0", null, null, null)
This query returns all starred contacts of the specified account.
If your code operates as a sync adapter you also have to add the Uri query parameter CALLER_IS_SYNC_ADAPTER, otherwise you may get different results for many operations.
I am using the generic columns DATA1 to Data15 to store some data. My question is whether it is safe to assume that these columns are not being used by other android applications to store their own data. IF not how would I make sure that a particular column is not being used by any other application?
ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI)
.withValue(Data.DATA10,"Data")
Retrive data:
ContentResolver cr=context.getContentResolver();
Cursor emailCur = cr.query(
ContactsContract.Data.CONTENT_URI,
null,
ContactsContract.Data._ID + " = ?",
new String[]{str_id}, null);
while (emailCur.moveToNext())
{
String name=emailCur.getString(
emailCur.getColumnIndex(Data.DATA10));
}
It will be safe, if you use your own MIME type value for the MIMETYPE column. When querying the data table, a MIME is specified to query for specific results (Phone numbers, names, addresses and so on), so if you use a value special for your application, other applications will probably not use it/change it.
Read more about the data table here.
EDIT: Append this call to the ContentProviderOperation creation chain:
.withValue(Data.MIMETYPE, "yourCustomMimeType")
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'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.