I am working on Unit Tests for my Android app, and am doing a lot with Contacts. I have to insert contacts into the Android Content Providers, and delete them after running my tests. Trouble is, they do not get actually deleted:
Insertion:
ArrayList<ContentProviderOperation> contactOps = new ArrayList<ContentProviderOperation>();
int backRefIndex = 0;
Random r = new Random();
contactOps.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
.build());
contactOps.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, backRefIndex)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, "Sample Name" + r.nextInt())
.build());
contactOps.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, backRefIndex)
.withValue(ContactsContract.CommonDataKinds.Phone.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, "020" + r.nextInt())
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, r.nextInt(20)
.build());
try {
ContentProviderResult[] result = context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, contactOps);
} catch (Exception e) {
e.printStackTrace();
}
Deletion method 1 (returns number of raw contacts, but they do not actually get deleted):
int deletedRawContacts = context.getContentResolver().delete(ContactsContract.RawContacts.CONTENT_URI, ContactsContract.RawContacts._ID + " >= ?", new String[]{"0"});
Deletion method 2 (same result as deletion method 1, but different approach):
private static int deleteAllRawContacts(Context context) {
ContentResolver cr = context.getContentResolver();
Cursor cur = cr.query(ContactsContract.RawContacts.CONTENT_URI, null, null, null, null);
int count = 0;
while (cur.moveToNext()) {
try {
String contactId = cur.getString(cur.getColumnIndex(ContactsContract.RawContacts._ID));
count += cr.delete(ContactsContract.RawContacts.CONTENT_URI, ContactsContract.RawContacts._ID + " = ?", new String[]{contactId});
} catch (Exception e) {
System.out.println(e.getStackTrace());
}
}
return count;
}
The deletion method for Contacts works, but the deletion method for Raw Contacts will return a false value. It will "tell" me, that it deleted all contacts, but when I run my next test case, the old Raw Contacts can still be found (i.e. the count of inserted contacts vs. present contacts is wrong). Note: All testing is done on the Android emulator.
Any ideas how to solve this?
I saw a similar question here: How to delete a contact? - but the solution does not seem to solve the given problem either.
As wiseideal already mentioned the way you delete your rawcontacts will only set the "deleted"-flag to 1.
What you need to do is to set the caller_is_syncadapter-flag in your URI to true like this:
RawContacts.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build()
And then use this new URI to call the delete-method:
int deletedRawContacts = context.getContentResolver().delete(RawContacts.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(), ContactsContract.RawContacts._ID + " >= ?", new String[]{"0"});
The corresponding part in the documentation is here (Operations->delete).
Hope this helps and happy coding :)
I am working on the same issue.I found the delete column is setted to 1 once I "delete" it.So I think contentresolver doesnt delete the rawcontact data physically,it just set a delete flag.Maybe we should avoid these tag.
Related
I am trying to delete a contact from phone contacts. The contact gets deleted from phone contacts but it's not getting deleted from the server-side (Google contacts) and when the Google contact sync triggers then that deleted contact re-appears. Below is my code.
public static void deleteContact(long rawid, ContentResolver contentResolver) {
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
Uri uri = ContactsContract.RawContacts.CONTENT_URI
.buildUpon()
.appendQueryParameter(
ContactsContract.CALLER_IS_SYNCADAPTER,
"true")
.build();
ops.add(ContentProviderOperation
.newDelete(uri)
.withSelection(
ContactsContract.RawContacts._ID + " = ?",
new String[]{Long.toString(rawid)})
.build());
try {
contentResolver.applyBatch(
ContactsContract.AUTHORITY,
ops);
} catch (RemoteException | OperationApplicationException e) {
e.printStackTrace();
}
}
You should try with ContactsContract.CALLER_IS_SYNCADAPTER as false in your code. While set to true, the contact is permanently deleted from the database. But when the next sync happens the contact is synched back. How Google sync checks for deleted contacts, is using a deleted flag which is set only if you set ContactsContract.CALLER_IS_SYNCADAPTER as false. Below is a snippet of code from the ContactsProvider class (contentprovider for contacts datastore)
if (callerIsSyncAdapter || rawContactIsLocal(rawContactId)) {
// When a raw contact is deleted, a SQLite trigger deletes the parent contact.
// TODO: all contact deletes was consolidated into ContactTableUtil but this one can't
// because it's in a trigger. Consider removing trigger and replacing with java code.
// This has to happen before the raw contact is deleted since it relies on the number
// of raw contacts.
db.delete(Tables.PRESENCE, PresenceColumns.RAW_CONTACT_ID + "=" + rawContactId, null);
count = db.delete(Tables.RAW_CONTACTS, RawContacts._ID + "=" + rawContactId, null);
mTransactionContext.get().markRawContactChangedOrDeletedOrInserted(rawContactId);
} else {
count = markRawContactAsDeleted(db, rawContactId, callerIsSyncAdapter);
}
How do I update the display name for a contact? The operation in the code below completes without throwing anything and appears to work - that is, when I requeried the ContactsContract.Contact table, a row came back with the name changed. However, when I tried running the stock "people" app on my tablet, it crashed. Evidentally I did something wrong.
Here is the code. Early on, it fetches an id from the aggregate contacts as follows, where key is the lookup_key:
String[] projection = new String[] {
Contacts._ID, // 0
Contacts.DISPLAY_NAME, // 1
};
Uri uri = Uri.parse (Contacts.CONTENT_LOOKUP_URI + "/" + key);
ContentResolver cr = getContentResolver();
Cursor cursor = cr.query (uri, projection, null, null, null);
if (!cursor.moveToNext()) // move to first (and only) row.
throw new IllegalStateException ("contact no longer exists for key");
origId = cursor.getLong(0);
cursor.close();
Then, after the user has done his edits, I call this block of code to update the display_name:
ArrayList<ContentProviderOperation> opers = new ArrayList<ContentProviderOperation>();
ContentProviderOperation.Builder builder = null;
String[] args = { Long.toString (origId) };
builder = ContentProviderOperation.newUpdate (Data.CONTENT_URI);
builder.withSelection (RawContacts.CONTACT_ID + "=?", args);
builder.withValue(CommonDataKinds.StructuredName.DISPLAY_NAME, name);
opers.add(builder.build());
ContentProviderResult[] results = null;
try {
results = getContentResolver().applyBatch(ContactsContract.AUTHORITY, opers);
} catch ...
I realize I don't need the ContentProviderOperation for this example; that's for later when I have more stuff to update.
To be honest, I'm pretty confused about which ID I'm actually using. The names aren't that clear to me and I may be using the wrong ID for this operation.
For what it's worth, looking at results after the update I saw a result code of 5. I can't find any documentation for that, so have no idea if that is significant.
The IDs (and altering contacts in general) can be pretty confusing... I had some dramas getting my head around them as well.
Here is some working code I use for updating. The main difference I can see is how you are declaring the raw ID; it needs to be included in as a content value.
Cursor cursor = _context.getContentResolver().query(contactUri,
new String[] { Contacts._ID }, null, null, null);
try {
if (cursor.moveToFirst()) {
String rawContactId = cursor.getString(0);
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
ContentValues contentValues = new ContentValues();
contentValues.put(Data.RAW_CONTACT_ID, rawContactId);
contentValues
.put(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
contentValues.put(
ContactsContract.CommonDataKinds.Phone.NUMBER,
phoneNumber);
contentValues.put(ContactsContract.CommonDataKinds.Phone.TYPE,
ContactsContract.CommonDataKinds.Phone.TYPE_WORK);
ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
.withValues(contentValues).build());
String contactId = contactUri.getLastPathSegment();
ops.add(ContentProviderOperation
.newUpdate(ContactsContract.Data.CONTENT_URI)
.withSelection(
ContactsContract.Data.CONTACT_ID
+ "=? AND "
+ ContactsContract.Data.MIMETYPE
+ "=?",
new String[] {
contactId,
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE })
.withValue(
ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
newName).build());
result = _context.getContentResolver().applyBatch(
ContactsContract.AUTHORITY, ops);
}
} finally {
cursor.close();
}
hopefully it helps!
Answer above is generally correct. However, when I inserted
ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME
and then tried to do newUpdate
ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME
with code as above - it made contact displayed in Contacts app with name mixed of old and new data. I found out inserting and updating ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME for example works as I expect. In Contacts app on my Android 4x when editing contact I cannot see family and given separately, look to me DISPLAY_NAME is made of them by Android.
Im developing an application which is dealing with the android contacts API. I implemented methods to insert, update and query contacts. So far everything worked (writing and reading contacts).
At one point in my project Im experiencing a strange behaviour.
I insert a contact using batch mode. I receive the URI to the RawContact. I do this in a background thread.
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
int rawContactInsertIndex = ops.size();
// create rawContact
ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
.withValue(RawContacts.ACCOUNT_TYPE, ConstantsContract.ACCOUNT_TYPE)
.withValue(RawContacts.ACCOUNT_NAME, accountName).build());
ops.add(createInsertOperation().withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
.withValue(StructuredName.DISPLAY_NAME, displayName).withValue(StructuredName.GIVEN_NAME, firstName)
.withValue(StructuredName.FAMILY_NAME, lastName).build());
ContentProviderResult[] results = context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
if (results.length > 0) {
result = results[0];
}
Then i request and store the lookup uri
RawContacts.getContactLookupUri(this.getContentResolver(), myContantRawContactUri);
I am able to query the contact using the rawContactUri directly after inserting it (in the same thread). The lookup uri returns null.
Uri rawContactUri = appUser.getRawContactUri(ctx);
if (rawContactUri == null) {
return null;
}
String lastPathSegment = rawContactUri.getLastPathSegment();
long rawContactId = Long.decode(lastPathSegment);
if (rawContactUri != null) {
contact = readContactWithID(rawContactId, ContactsContract.Data.RAW_CONTACT_ID);
In a different place in the project I want to query the contact i inserted by the stored lookup uri or raw contact uri. Both return no rows from the content provider. I tried it in the main thread and in another background thread.
ctx.getContentResolver().query(ContactsContract.Data.CONTENT_URI, null, ContactsContract.Data.RAW_CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?", new String[] { contactID + "", ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE }, null);
My first thought was that it could be related to the context.getContentResolver(). But the android documentation states, that the ContentResolver objects scope is the application's package, so you have on ContentResolver for the whole app. Am I right?
What am I doing wrong? Why does the same rawContactUri return the contact at one place and does not on another place? And why do I get a lookup uri from a raw contact, which is not working at all?
Update:
I analyzed the database using sqlite. When I insert the contact, the rows in raw_contacts and contacts are created. The deleted flag is set to 0, so it is not marked for deletion. If I then read the contact in another place in the application, it returns null. The database dump at this point of time does not contain the rows for the contact anymore.
Update 2:
I tested my app with the emulator in versions 2.3.3, 4.0, and 4.1. The described behavior only appears with 4.1 Jelly Bean.
Ok, i found the solution. It was actually my own fault. I added a contact before i added my custom account to the AccountManager. This obviously deleted the contact because it had the same account type set.
I am creating a Bluetooth sync application with Outlook Express. All of the work has been done fantastically well but I have one small problem left. When I sync my contacts from Outlook to Android it merges the contacts with similar names. For instance, if I have two contacts called "Najhi" and "Najhi Ullah" then after syncing they will be merged in Android under one name "Najhi". Is there any solution to separate all merged contacts programmatically?
I have found the solution on my own if anyone have the same problem, they can find this post.
private void separate_merged_contacts(){
Cursor cur1 = obj.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,new String[]{"_id"} , null, null,null);
Cursor cur_raw;
ArrayList<String> raw_contact_id = new ArrayList<String>();
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
while (cur1.moveToNext()) {
raw_contact_id.clear();
ops.clear();
for (int i = 0; i < cur1.getColumnCount(); i++) {
cur_raw = obj.getContentResolver().query(ContactsContract.RawContacts.CONTENT_URI, new String[]{ContactsContract.RawContacts._ID}, ContactsContract.RawContacts.CONTACT_ID+"=?",new String[]{cur1.getString(cur1.getColumnIndex(ContactsContract.Contacts._ID))} , null);
while(cur_raw.moveToNext()){
for (int i = 0; i < cur_raw.getColumnCount(); i++) {
raw_contact_id.add(cur_raw.getString(cur_raw.getColumnIndexOrThrow(ContactsContract.RawContacts._ID)));
}
}
for(int i=0 ; i<raw_contact_id.size();i++){
for(int j=0;j<raw_contact_id.size();j++)
ops.add(ContentProviderOperation.newUpdate(ContactsContract.AggregationExceptions.CONTENT_URI)
.withValue(AggregationExceptions.TYPE,AggregationExceptions.TYPE_KEEP_SEPARATE)
.withValue(AggregationExceptions.RAW_CONTACT_ID1,Integer.parseInt(raw_contact_id.get(i)))
.withValue(AggregationExceptions.RAW_CONTACT_ID2,Integer.parseInt(raw_contact_id.get(j))).build());
try {
obj.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
While inserting the contact itself you can add one more content value
You can set RawContacts.AGGREGATION_MODE as RawContacts.AGGREGATION_MODE_DISABLED
For more details see :
http://developer.android.com/reference/android/provider/ContactsContract.RawContacts.html
The code snippet in the accepted answer (https://stackoverflow.com/a/7063235/4957915) certainly gets the job done, but I see 2 problems with it.
1) We are creating unnecessary entries to AggregationExceptions table. If we have 1,000 raw contacts, then we'll end up with 1,000,000 entries. Any phones manufactured within the last few years should be able to handle it without skipping a beat, but it's still a waste.
2) More importantly, once the contacts are separated, you may or may not be able to merge those contacts again, depending on how the entries are entered. The only way to remedy such a problem is to delete the unmerge-able contact and recreate it.
A better approach is to update only the existing AggregationExceptions entries.
ArrayList<ContentProviderOperation> operations = new ArrayList<>();
// Get all entries in AggregationExceptions.
cursor = mContext.getContentResolver().query(
ContactsContract.AggregationExceptions.CONTENT_URI,
null, null, null, null);
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
columnIndex = cursor.getColumnIndex(ContactsContract.AggregationExceptions.TYPE);
int type = cursor.getInt(columnIndex);
columnIndex = cursor.getColumnIndex(ContactsContract.AggregationExceptions.RAW_CONTACT_ID1);
long rawContactId1 = cursor.getLong(columnIndex);
columnIndex = cursor.getColumnIndex(ContactsContract.AggregationExceptions.RAW_CONTACT_ID2);
long rawContactId2 = cursor.getLong(columnIndex);
ContentProviderOperation.Builder builder = ContentProviderOperation.newUpdate(
ContactsContract.AggregationExceptions.CONTENT_URI);
builder.withValue(ContactsContract.AggregationExceptions.TYPE,
ContactsContract.AggregationExceptions.TYPE_KEEP_SEPARATE); // <--
builder.withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
builder.withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
operations.add(builder.build());
}
cursor.close();
if (!operations.isEmpty()) {
try {
mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
} catch (Exception e) {
e.printStackTrace();
}
}
Note that Android performs contact aggregation (i.e. merging) automatically when the contacts are similar to each other: 2 contacts having the same display name, for example. In that case, there won't be an AggregationExceptions entry for those merged contacts. It's automatic.
If you merge 2 contacts manually using Contacts app, then a new AggregationExceptions entry will be added with TYPE_KEEP_TOGETHER, but nothing will be changed to the raw contacts themselves. If you manually separate the merged contacts, then the entry with TYPE_KEEP_TOGETHER will be marked as deleted and a new entry with TYPE_KEEP_SEPARATE will be added. Keep in mind that once an entry is added, it will be there until the corresponding contact is deleted, because AggregationExceptions doesn't support "delete" operation (https://developer.android.com/reference/android/provider/ContactsContract.AggregationExceptions.html).
Improved code from the answer of najhi ullah worked like charm for me.
ContentResolver contentResolver = getContentResolver();
if (contentResolver != null) {
Cursor contactCursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, new String[]{"_id"}, null, null, null);
if (contactCursor != null) {
ArrayList<String> rawContactIds = new ArrayList<>();
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
while (contactCursor.moveToNext()) {
rawContactIds.clear();
ops.clear();
for (int i = 0; i < contactCursor.getColumnCount(); i++) {
Cursor cursorRawContact = contentResolver.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{ContactsContract.RawContacts._ID}, ContactsContract.RawContacts.CONTACT_ID + "=?", new String[]{contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts._ID))}, null);
if (cursorRawContact != null) {
while (cursorRawContact.moveToNext()) {
rawContactIds.add(cursorRawContact.getString(cursorRawContact.getColumnIndexOrThrow(ContactsContract.RawContacts._ID)));
}
for (String rawContactId : rawContactIds) {
for (String rawContactId2 : rawContactIds) {
ops.add(ContentProviderOperation.newUpdate(ContactsContract.AggregationExceptions.CONTENT_URI)
.withValue(ContactsContract.AggregationExceptions.TYPE, ContactsContract.AggregationExceptions.TYPE_KEEP_SEPARATE)
.withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID1, Integer.parseInt(rawContactId))
.withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID2, Integer.parseInt(rawContactId2)).build());
}
try {
contentResolver.applyBatch(ContactsContract.AUTHORITY, ops);
} catch (Exception e) {
e.printStackTrace();
}
}
cursorRawContact.close();
}
}
}
contactCursor.close();
}
}
I want to create a new contact group. I can query the group and display all the group names but I can't create a group in android I tried as creating contacts method but not created...
ContentResolver cr = this.getContentResolver();
groupValues = new ContentValues();
Log.e("Group","start");
groupValues.put(android.provider.Contacts.GroupMembership.GROUP_ID, 4);
groupValues.put(android.provider.Contacts.GroupMembership.NAME, "Sriseshaa");
groupValues.put(android.provider.Contacts.GroupMembership.PERSON_ID, 1);
cr.insert(android.provider.Contacts.GroupMembership.CONTENT_URI, groupValues);
i found the answer.i found in two ways but i dont know which is correct or best way to use.i am sharing those here..
its simple way like adding contact,
ContentValues groupValues;
create group()
{
ContentResolver cr = this.getContentResolver();
groupValues = new ContentValues();
groupValues.put(ContactsContract.Groups.TITLE, "MyContactGroup");
cr.insert(ContactsContract.Groups.CONTENT_URI, groupValues);
}
Another method using ContentProviderOperation
private void createGroup() {
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
ops.add(ContentProviderOperation
.newInsert(ContactsContract.Groups.CONTENT_URI)
.withValue(ContactsContract.Groups.TITLE, "SRI").build());
try {
getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
} catch (Exception e) {
Log.e("Error", e.toString());
}
}
Thanks
adithi's answer is enough for Android 4.2.2, in which the name of Contacts manager application is "Contacts" , but the group created by that code will not show on Android 4.4,6 in which the name of Contacts manager application is "People".
The group would show up after adding the account type/name information while insertion happens.
private void createGroup() {
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
ops.add(ContentProviderOperation
.newInsert(ContactsContract.Groups.CONTENT_URI)
.withValue(
ContactsContract.Groups.TITLE,
Constants.CC_CONTACT_GROUP_TITLE)
.withValue(
ContactsContract.Groups.ACCOUNT_TYPE,
Constants.CC_CONTACT_GROUP_ACCOUNT_TYPE)
.withValue(
ContactsContract.Groups.ACCOUNT_NAME,
Constants.CC_CONTACT_GROUP_ACCOUNT_NAME)
.build());
try {
getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
} catch (Exception e) {
Log.e("Error", e.toString());
}
}
Why are you specifying group ID with groupValues.put(android.provider.Contacts.GroupMembership.GROUP_ID, 4); Its androids job to determined the group ID, you cant specify it because you don't know whether this id is already taken or not.