I am creating an android app that keeps track of all contacts saved on the device (the ones that appear in the default android contacts app) and saves them in a MySQL table on the server.
I managed to read all contact data using ContectResolver:
//to read only android address book
String where = ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '1'";
String[] projection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY, ContactsContract.Contacts.HAS_PHONE_NUMBER};
ContentResolver cr = context.getContentResolver();
Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, projection, where, null, null);
Then, using the ContactsContract.Contacts._ID I query additional contact data:
// Perform a query to retrieve the contact's name parts
String[] nameProjection = new String[] {
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,
ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME,
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME,
ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME
};
Cursor nameCursor = cr.query(
ContactsContract.Data.CONTENT_URI,
nameProjection,
ContactsContract.Data.MIMETYPE + " = '" +
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "' AND " +
ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID
+ " = ?", new String[] { id }, null);
// Retrieve the name parts
String firstName = "", middleName = "", lastName = "", displayName = "";
if(nameCursor.moveToNext()) {
firstName = nameCursor.getString(nameCursor.getColumnIndex(
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME));
middleName = nameCursor.getString(nameCursor.getColumnIndex(
ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME));
lastName = nameCursor.getString(nameCursor.getColumnIndex(
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME));
displayName = nameCursor.getString(nameCursor.getColumnIndex(
ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME));
}
I then send all this data (including the contact's email addresses and phone numbers) to the server.
I want to be able to periodically backup the device's contacts like this, but to be able to detect changes in contact data, by comparing the old data to the new data. But to do this, i need to have some sort of link between the contacts in the android device, and my DB in which i save the contact data.
After some searching i found 3 options:
ContactsContract.Contacts._ID
ContactsContract.Contacts.LOOKUP_KEY
ContactsContract.RawContacts._ID
But from what I found, all of them are not reliable, and can change in certain situations - invalidating the link between my DB to the device's contact list.
I have considered the option to use ContentObserver to detect contact changes. But, I want to be able to detect contact changes even if my app has been uninstalled, then some contacts have changed and then my app has been reinstalled.
Is there a reliable identification key per contact that I can use, to know when a certain contact has been changed or deleted?
EDIT:
I now found this variable exists: ContactsContract.ContactsColumns.CONTACT_LAST_UPDATED_TIMESTAMP
The problem is that it was introduces only in API level 18. I am working on min API 15. Is there something that could replace it for the missing API levels?
I am afraid it is not possible to know if particular contact changed. Therefore you can register observer on whole address book URI. This way you will be able to listen when anything in address book changes.
Now having observer you can scan all contacts and perform "sync" data to your server.
In your particular case pair of ContactsContract.Contacts._ID and ContactsContract.Contacts.LOOKUP_KEY should be reliable enough. But in order to know if anything changed you need either:
send all contacts to server and perform sync logic on server (id and lookup key should be stored on MySQL)
keep local copy of contacts on your device (sqllite db, realm, any other storage) in order to avoid unchanged contacts to be sent to server every time you sync
Related
I'm working on an application in which I should be able to access all the contacts, able to update or delete the numbers in any contact.
I want to delete few numbers in a contact. I'm using batchOperations to perform delete operation.
phone = ContactsContract.Data.RAW_CONTACT_ID + " = ? AND " +
ContactsContract.Data.MIMETYPE + " = ? AND " +
ContactsContract.CommonDataKinds.Phone._ID + " = ?";
String[] phoneArgs = new String[]{Integer.toString(rawContactId), ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, String.valueOf(id)};
batchOperations.add(ContentProviderOperation.newDelete(Data.CONTENT_URI).withSelection(phone, phoneArgs).build());
this.mContext.getContentResolver().applyBatch("com.android.contacts", batchOperations);
batchOperations.clear();
I'm using this part of code to delete a specific number from the Contact. Using debugger, for a sample delete operation, I found the values as:
raw_contact_id = 4093
id = 21579
These values correspond to specific number("+814444444444") in a group of numbers in a sample contact.(Please refer image below)
But still the number is not getting deleted. I've been trying to figure it out for the last couple of hours but couldn't solve it. Please help.
Instead of supplying selection + selectionArgs, you can build a uri of the specific item you want to delete, like so:
Uri phoneUri = ContentUris.withAppendedId(Data.CONTENT_URI, phoneId);
batchOperations.add(ContentProviderOperation.newDelete(phoneUri).build());
A couple of other notes:
Always check the return value of applyBatch, if it's false, there's some issue with your code / permissions.
Use the constant ContactsContract.AUTHORITY instead of the hard-coded string "com.android.contacts".
Another reason for the observed behavior is that the phone "+814444444444" is stored on multiple RawContacts aggregated into a single contact.
In which case even when you properly delete the row from RawContact A, the contact profile would still get the number from RawContact B.
This is especially true for phone numbers when certain apps such as Whatsapp are installed which tend to copy over contacts' phone numbers to a separate RawContact under their own account.
If that's the issue you'll need to delete the phone number from ALL the contact's RawContacts holding that phone.
EDIT
Here's how to dump all phone numbers that belong to a specific contact, along with the raw-contact that holds them:
String selection = Data.CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?";
String[] selectionArgs = new String[] { contactId.toString(), Phone.CONTENT_ITEM_TYPE };
String[] projection = new String[] { Data.CONTACT_ID, Data.RAW_CONTACT_ID, Data.DATA1 };
Cursor cur = getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, projection);
DatabaseUtils.dumpCursor(cur);
Also, note that a rawContactId is not an integer, it's a long.
I am creating an app in which I want to show my app icon in front of every contact if contact is associated with my app like in WhatsApp. I have searched a lot but didn't find any appropriate solution.
Fetch all the contacts from your phone like this.
public void getNumber(ContentResolver cr)
{
mItems = new ArrayList<String>();
Cursor phones = cr.query(Phone.CONTENT_URI, null, null, null, Phone.DISPLAY_NAME + " ASC");
while (phones.moveToNext())
{
String name=phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String phoneNumberString = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
phoneNumberString.replace(" ", "");
contactName.add(name);
contactNumber.add(phoneNumberString);
mItems.add(name);
}
phones.close();
}
now send all these contacts to your server, compare each mobile number in your server database, and list only the contacts that are found in your server.
You need to maintain your own database, when registering user, save his number and while comparing, compare the fetched number to the register member's number in your own database
My issue is, when I'm fetching contacts from phone, it will get all the contacts (from phone, SIM, social accounts etc)
i want to know, if I can get contacts from social accounts separately?
As long as the contacts for the applications are synced, you can query the RawContacts (with the READ_CONTACTS permission in your manifest) and get them filtered by account type. For example:
Cursor c = getContentResolver().query(
RawContacts.CONTENT_URI,
new String[] { RawContacts.CONTACT_ID, RawContacts.DISPLAY_NAME_PRIMARY },
RawContacts.ACCOUNT_TYPE + "= ?",
new String[] { <account type here> },
null);
ArrayList<String> myContacts = new ArrayList<String>();
int contactNameColumn = c.getColumnIndex(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.
myContacts.add(c.getString(contactNameColumn));
}
It's just a matter of supplying the appropriate account type. For example:
"com.google" for Gmail,
"com.whatsapp" for WhatsApp,
"com.skype.contacts.sync" for Skype,
&c.
I have implemented Skype video/audio calling functionality using Intent in my application it is working fine. But now I want to get all contacts list from Skype account is it possible?.
Is there any alternate way to show list of contacts of Skype account please give any idea?
All contacts (provided they are synced) can be queried with the ContactsContract provider. The RawContacts.ACCOUNT_TYPE column of the RawContacts table indicates the account type for each entry ("raw" means that it contains all entries, e.g. multiple rows for a single person with multiple aggregated contacts).
To read the Skype contacts, you can do something like this:
Cursor c = getContentResolver().query(
RawContacts.CONTENT_URI,
new String[] { RawContacts.CONTACT_ID, RawContacts.DISPLAY_NAME_PRIMARY },
RawContacts.ACCOUNT_TYPE + "= ?",
new String[] { "com.skype.contacts.sync" },
null);
int contactNameColumn = c.getColumnIndex(RawContacts.DISPLAY_NAME_PRIMARY);
ArrayList<String> mySkypeContacts = new ArrayList<String>();
while (c.moveToNext())
{
/// You can also read RawContacts.CONTACT_ID to query the
// ContactsContract.Contacts table or any of the other related ones.
mySkypeContacts.add(c.getString(contactNameColumn));
}
Be sure to also request the android.permission.READ_CONTACTS permission in the AndroidManifest.xml file.
Can somebody help me on how to get all contacts per account? Meaning, I want to put a condition which will determine if the contact is from the phone (created by user) or from google and some other sync sources because as of now I was getting all contacts and its the combination of all sync sources e.g. local contacts, google or even yahoo contacts ?
Can somebody help me on how to get all contacts per account?
You can use next snippet to retrieve contacts for a particular account type:
String where = RawContacts.ACCOUNT_TYPE+ "=?";
String[] args = { accountType };
Cursor contacts = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, where, args, null);
int numberIndex = contacts.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
int displayNameIndex = contacts.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
for (contacts.moveToFirst(); !contacts.isAfterLast(); contacts.moveToNext()) {
String number = contacts.getString(numberIndex);
String displayName = contacts.getString(displayNameIndex);
// do something with account contacts
}
contacts.close();
To filter plain phone contacts (not connected to any account) you can use:
String where = RawContacts.ACCOUNT_TYPE+ " IS NULL";