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();
}
}
Related
So I've written a query to extract the WhatsApp contacts of a phone. My initial query goes like this:
Cursor c = con.getContentResolver().query(
ContactsContract.RawContacts.CONTENT_URI,
new String[]{ContactsContract.RawContacts.CONTACT_ID, ContactsContract.RawContacts.DISPLAY_NAME_PRIMARY},
ContactsContract.RawContacts.ACCOUNT_TYPE + "= ?",
new String[]{"com.whatsapp"},
null
);
ArrayList<String> myWhatsappContacts = new ArrayList<String>();
int contactNameColumn = c.getColumnIndex(ContactsContract.RawContacts.DISPLAY_NAME_PRIMARY);
while (c.moveToNext()) {
// You can also read RawContacts.CONTACT_ID to read the
// ContactsContract.Contacts table or any of the other related ones.
myWhatsappContacts.add(c.getString(contactNameColumn));
}
The purpose of this is to find out how many WhatsApp contacts the phone has at any one time. When I do:
Log.i("WhatsApp contacts found:", Integer.toString(myWhatsappContacts.size());
It should print out how many WhatsApp contacts there were into LogCat. And this works - up to a point.
Let's say for example that the number of WhatsApp contacts I have now is 101. The next phase of this little project is to delete away ALL contacts if there are more than 100 of them. In which case, we go:
if (myWhatsappContacts.size() > 100) {
//Delete all contacts code here
}
I've tested the delete contacts code, it works. I check the contacts directory of the phone via the contacts app, and it says 0. But now when I do the query again (refer to code above), it still shows 101! What's going on?
If it helps, my DeleteContacts method is as follows:
private void deleteContact(Context ctx, String phone, String name) {
Uri contactUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phone));
Cursor cur = ctx.getContentResolver().query(contactUri, null, null, null, null);
try {
if (cur.moveToFirst()) {
do {
if (cur.getString(cur.getColumnIndex(ContactsContract.PhoneLookup.DISPLAY_NAME)).equalsIgnoreCase(name)) {
String lookupKey = cur.getString(cur.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY));
Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey);
ctx.getContentResolver().delete(uri, null, null);
return;
}
} while (cur.moveToNext());
}
} catch (Exception e) {
System.out.println(e.getStackTrace());
} finally {
cur.close();
}
return;
}
What am I doing wrong? Is my DeleteContacts code faulty? Or is the query itself faulty?
I implemented a code to retrieve all contacts but it is not showing all contacts where few of them are missed.
Here is my code:
String[] projection = new String[]{
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER,
};
Cursor cursor = null;
try {
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
projection, null, null, null);
} catch (SecurityException e) {
}
if (cursor != null) {
try {
HashSet<String> normalizedNumbersAlreadyFound = new HashSet<>();
int indexOfNormalizedNumber = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER);
int indexOfDisplayName = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
int indexOfDisplayNumber = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
while (cursor.moveToNext()) {
String normalizedNumber = cursor.getString(indexOfNormalizedNumber);
if (normalizedNumbersAlreadyFound.add(normalizedNumber)) {
String displayName = cursor.getString(indexOfDisplayName);
String displayNumber = cursor.getString(indexOfDisplayNumber);
listOfContacts.add(new PhoneContactsModel(displayName, displayNumber, false));
} else {
}
}
Log.d("tag", "size of listOfContacts =1====" + listOfContacts.size());
} finally {
cursor.close();
}
}
don't know what is happening. Please help me.
There are many issues in the code:
You're querying over the CommonDataKinds.Phone.CONTENT_URI table, so naturally, you won't get contacts that have no phone numbers (e.g. contacts with name and email)
You're skipping contacts that contains phones you've already encountered in normalizedNumbersAlreadyFound, so if you have two contacts with a shared phone (like a home phone number) you might skip one of them.
CommonDataKinds.Phone.NORMALIZED_NUMBER may be null, in which case you'll skip many contacts that do not have their NORMALIZED_NUMBER field set
If you need to also include contacts that have no phones, I would recommend a completely different code. If you only need to get contacts with phones, I would recommend not relying on NORMALIZED_NUMBER, and instead add CommonDataKinds.Phone.CONTACT_ID to your projection, and have that as your unique key per contact.
Whenever I want to add new data to an existing Android contact, I use the following function to retrieve all RawContacts IDs for the given contact ID:
protected ArrayList<Long> getRawContactID(String contact_id) {
ArrayList<Long> rawContactIDs = new ArrayList<Long>();
String[] projection = new String[] { ContactsContract.RawContacts._ID };
String where = ContactsContract.RawContacts.CONTACT_ID + " = ?";
String[] selection = new String[] { contact_id };
Cursor c = getContentResolver().query(ContactsContract.RawContacts.CONTENT_URI, projection, where, selection, null);
try {
while (c.moveToNext()) {
rawContactIDs.add(c.getLong(0));
}
}
finally {
c.close();
}
return rawContactIDs;
}
After that, I just insert the data using the ContentResolver:
getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values);
This is done for all RawContacts IDs that have been found previously. The effect is, of course, that all data is added repeatedly. Thus I want to return only one result now, but this has to meet special requirements.
I would like to adjust my function above so that its result meets the following requirements:
ContactsContract.RawContactsColumn.DELETED must be 0
The RawContacts entry must not be a secured one like Facebook's
ContactsContract.SyncColumns.ACCOUNT_TYPE is preferably "com.google". So if there is one entry that meets this requirement, it should be returned. If there is none, return any of the remaining entries.
How can I do this (most efficiently)? I don't want to make the query to complex.
I have given this some thought, from my experience with contact r/w, and with your needs in mind. I hope this helps you solve the issue and or points you in the direction you are looking for.
Please note that i have no device available with any sync adapters such as facebook so unfortunately i cannot confirm my answer viability (the read only bit mainly which might changeable to a simple != '' ).
Same getRawContactID function with some adjustments
protected ArrayList<Long> getRawContactID(String contact_id) {
HashMap<String,Long> rawContactIDs = new HashMap<String,Long>();
String[] projection = new String[] { ContactsContract.RawContacts._ID, ContactsContract.RawContacts.ACCOUNT_TYPE };
String where = ContactsContract.RawContacts.CONTACT_ID + " = ? AND " + ContactsContract.RawContacts.DELETED + " != 1 AND " + ContactsContract.RawContacts.RAW_CONTACT_IS_READ_ONLY + " != 1" ;
String[] selection = new String[] { contact_id };
Cursor c = getContentResolver().query(ContactsContract.RawContacts.CONTENT_URI, projection, where, selection, null);
try {
while (c.moveToNext()) {
rawContactIDs.put(c.getString(1),c.getLong(0));
}
}
finally {
c.close();
}
return getBestRawID(rawContactIDs);
}
And another getBestRawID function to find the best suited account -
protected ArrayList<Long> getBestRawID(Map<String,Long> rawContactIDs)
{
ArrayList<Long> out = new ArrayList<Long>();
for (String key : rawContactIDs.KeySet())
{
if (key.equals("com.google"))
{
out.clear(); // might be better to seperate handling of this to another function to prevent WW3.
out.add(rawContactIDs.get(key));
return out;
} else {
out.add(rawContactIDs.get(key));
}
}
return out;
}
Also note - I wrote most of the code without running / testing it. Apologies in advance.
I want to store the values of a particular column in the array, As I am a fresher I don't know how to do this. I am getting values from sqlite as
1
2
123
432
3
5
I want to store these values in string array. Please tell me I am not finding any appropriate example by googling about this.. thanx in advance.
public void fun(String query){
Cursor cursor = db.rawQuery(query, null);
try{
String[] arr = new String[cursor.getCount()];
if(cursor != null){
while(cursor.moveToNext()){
for(int i = 0; i < cursor.getCount(); i++){
arr[i] = cursor.getString(0).trim();
}
cursor.moveToNext();
}
cursor.close();
}
}finally{
cursor.close();
}
}
Here query is
SELECT <COLUMN_NAME> FROM <TABLE_NAME> WHERE <CONDITION>;
I think I am doing it wrong please correct my errors...
I consider using rawQuery is a bad habit, try to avoid this(except in extreme cases)
Try as follows to solve your problem, hope this will help you:
public ArrayList<String> getAllStringValues() {
ArrayList<String> yourStringValues = new ArrayList<String>();
Cursor result = db.query(true, YOUR_TABLE,
new String[] { YOUR_COLUMN_NAME }, null, null, null, null,
null, null);
if (result.moveToFirst()) {
do {
yourStringValues.add(result.getString(result
.getColumnIndex(YOUR_COLUMN_NAME)));
} while (result.moveToNext());
} else {
return null;
}
return yourStringValues;
}
Use this method in YourCustomDBManager class. consider NotePad example of android developers sites example programmers guide for getting better concept. It will help you to learn how to deal with SQLite. I am also new in Android, but I learned everything about SQLite from NotePad example.
Vector<String> vecInt = new Vector<String>; // you can use any datatype <int><float>
cursor.moveToFirst();
for(i=0;i<cursor.getCount();i++)
{
vecInt.add(cursor.getString(COLUMN_NUM));// if you are using datatype other then string then need to convert here
}
int [] val = new int[cursor.getCount()]; // integer array
for(int i= 0; i<cursor.getCount(); i++)
val[i] = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME));
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.