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.
Related
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
I'm attempting to retrieve both email and phone via a single cursor described below. I'm using the Email.CONTENT_URI, hence I don't retrieve the phone number, so my phoneColumn is returning email. I tried using Phone.CONTENT_URI but it only returns a smaller subset of contacts (possibly because it only fetches ones that have phone numbers). Is there a way to get both email and phone with a specific Uri or how can I do it with two cursors?
Cursor cursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Email.CONTENT_URI,
projection, null, null, order);
int idColumn = cursor.getColumnIndex(ContactsContract.Data._ID);
int nameColumn = cursor.getColumnIndex(ContactsContract.Data.DISPLAY_NAME);
int emailColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA);
int phoneColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DATA);
where projection is:
String[] projection = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Data.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Email.DATA,
ContactsContract.CommonDataKinds.Phone.DATA, }
Please have a look here. You should find all the necessary examples for such kind of querying.
For an example, you can fetch all information of a contactId in this way:
Cursor data = context.getContentResolver().query(
Data.CONTENT_URI, new String[] { Data._ID,Data.MIMETYPE,
Email.ADDRESS, Photo.PHOTO},Data.CONTACT_ID
+ "=?" + " AND " + "(" + Data.MIMETYPE + "='"
+ Photo.CONTENT_ITEM_TYPE + "' OR " + Data.MIMETYPE
+ "='" + Email.CONTENT_ITEM_TYPE +"')",
new String[] {String.valueOf(contactId)}, null);
You can fetch info for all contacts in this way and then probably sort it according to your criteria. You can build queries like this based on your need.
I have created this library to solve all your queries. It will only save contacts with at least one email or phone number. Also it will remove duplicates from emails and phone numbers from same contacts (created by 3rd party apps like whatsapp).
Please have a look at it.
Link : https://github.com/raghavsatyadev/ContactFetcher/
I'm trying to get "notes" from a single contact. It added fine but retrieving it has been a problem.
String selection = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME+" like'%" + sender +"%'";
String[] projection = new String[] { ContactsContract.CommonDataKinds.Note.NOTE};
Cursor c2 = getContentResolver().query(ContactsContract.Data.CONTENT_URI, projection, selection, null, null);
if (c2.moveToFirst()) {
notes = c2.getString(0);
}
It works fine with other values like name or phone number but can't seem to get notes to retrieve correctly. It retrieves a random value like email instead.
I believe that your problem is that not all rows in the table represent contact types that have notes. You have to request the proper MIME Type.
ContactsContract.CommonDataKinds.Note is an alias for the 'data1' column that is present on all rows, so when you get a row of a different MIME Type, it represents different data.
How to get contacts in Android should give you an idea of how to do this.
I would like to list all contact groups in a Google account, and make it possible for a user to select some of the groups as "special". When an incoming number belongs to one of the "special" groups, I want to take actions.
I can't find too many examples around this. Has someone done anything similar that you would like to share?
You have three questions here:
How to enumerate the contact groups for a given account.
How to mark a group as special.
How to take action on contacts in that group.
So, going down the list...
1. Enumerating contact groups
The ContactsContract.Groups table stores the list of contact groups on the system. So, you'll want to issue a query that looks like this:
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
Uri uri = ContactsContract.Groups.CONTENT_URI;
Log.i(TAG, "URI: " + uri);
String[] projection = new String[] {
ContactsContract.Groups._ID,
ContactsContract.Groups.TITLE
};
return new CursorLoader(this, uri, projection, null, null, null);
}
This loader will get you the list of all the groups on the system, and their database IDs.
How to mark a group as special
This is something your application will need to take care of. Just maintain a list of group IDs that are in your special list.
To determine whether a contact is in the "special" group, you can query the ContactsContract.Data table using a SQL where clause like the following:
String where = ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID
+ "="
+ groupid
+ " AND "
+ ContactsContract.CommonDataKinds.GroupMembership.MIMETYPE
+ "='"
+ ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE
+ "'";
where groupid is the database ID of the group you've marked as special. (If you have more than one group, start adding OR clauses.)
(You need to check for the CONTENT_ITEM_TYPE mimetype because the ContactsContract.Data table is used for storing arbitrary metadata for contacts, and the meaning of the columns in that table vary by mimetype. GroupMembership.GROUP_ROW_ID is simply a pointer to the column data1.)
3. How to take action on contacts in that group
This depends on what you're trying to accomplish, but in general, you'll create a broadcast receiver that listens for android.intent.action.PHONE_STATE. If the state in TelephonyManager.EXTRA_STATE is listed as TelephonyManager.EXTRA_STATE_RINGING, then you can get the phone number by looking in TelephonyManager.EXTRA_INCOMING_NUMBER to find the phone number.
(There's sample code for this attached to this article: http://www.krvarma.com/2010/08/detecting-incoming-and-outgoing-calls-in-android/)
You'll then need to check the Contacts database again to find any known contacts with that phone number. You can do this using the ContactsContract.PhoneLookup table.
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
resolver.query(uri, new String[]{PhoneLookup.DISPLAY_NAME,...
That will get you the Contact's database ID in the ContactsContract.Contacts table. The last step here is to match that up with the ContactsContract.RawContacts rows for that contact (search using the CONTACT_ID column), and find the set of RawContacts _IDs that represent that contact. You'll then use this to search through the group membership table, as described above.
I found way to get correct groups by all accounts in Android:
#Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle bundle) {
Uri uri = ContactsContract.Groups.CONTENT_SUMMARY_URI;
String[] projection = null;
//this is correct selection for retrieving groups from android
String selection = ContactsContract.Groups.ACCOUNT_TYPE + " NOT NULL AND " +
ContactsContract.Groups.ACCOUNT_NAME + " NOT NULL AND " + ContactsContract.Groups.DELETED + "=0";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
selection += " AND " + ContactsContract.Groups.AUTO_ADD + "=0 AND " + ContactsContract.Groups.FAVORITES + "=0";
}
String[] selectionArgs = null;
String sortOrder = ContactsContract.Groups.TITLE + " ASC";
Loader<Cursor> loader = null;
switch(loaderId) {
case GROUPS_LOADER:
loader = new CursorLoader(getActivity(), uri, projection, selection, selectionArgs, sortOrder);
break;
default:
break;
}
return loader;
}
source: grepcode.com
I'm using the code below to retrieve a cursor for events and contacts and it's working just fine for events with a year specified. Unfortunately, events where only the day and month are set are not returned by this query.
I have set these dates in Google Contacts for some contact birthdays and I'm wondering if I need to use RawContacts rather than ContactsContract to get these values?
Update: It seems that events entered into the phone without a year are retrieved. It is only events added via http://www.google.com/contacts that are not retrieved. I can see these events in the Contacts app though - displayed as something like --05-23.
String[] projection = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
Event.CONTACT_ID,
Event.START_DATE,
Event.TYPE
};
Cursor c = mContext.getContentResolver().query(
ContactsContract.Data.CONTENT_URI,
projection,
ContactsContract.Data.MIMETYPE + "= ?",
new String[] { Event.CONTENT_ITEM_TYPE },
Event.START_DATE);
c.moveToFirst();
int nameIndex = c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
int dateIndex = c.getColumnIndex(Event.START_DATE);
String name, date;
while (c.moveToNext()) {
name = c.getString(nameIndex);
date = c.getString(dateIndex);
Log.d(TAG, "Event for " + name + " is " + date);
}
c.close();
I'm running on the Nexus S (2.3.4) and I have used your exact code and I get all contacts printed out including ones with no year.
07-19 17:08:36.847: DEBUG/test(24646): Event for Warren Upton is --12-01
07-19 17:08:36.851: DEBUG/test(24646): Event for Marion Atkins is 1934-11-24
Can you give any more details on how to reproduce the problem?
I'm guessing events with no year don't have Event.START_DATE set (since by definition a date would be day/month/year). If you remove it from the projection and sort do they get retrieved?