I'm creating a contacts app that will have all the basic features of a Contacts app (and some extra features of course). While implementing the basic features, I'm stuck at a place:
I'm having an activity in which the user can change the city name of a contact. If the user is already having a city, I can update it using the following code:
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
String contactId = id; // got it from ContactsContract.Contacts._ID
String mimeType = ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE;
String selection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ? AND " + ContactsContract.CommonDataKinds.StructuredPostal.TYPE + " = ?";
String[] values = new String[]{contactId, mimeType, String.valueOf(ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME)};
ops.add(
android.content.ContentProviderOperation.newUpdate(
android.provider.ContactsContract.Data.CONTENT_URI)
.withSelection(selection, values)
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.CITY, "California")
.build()
);
contentResolver.applyBatch(ContactsContract.AUTHORITY, ops);
But, the above code is not working for a contact that doesn't have any details other than phone number. After browsing a lot, I've found the following way to do it:
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
String rawContactId = id;
String mimeType = ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE;
String[] values = new String[]{contactId, mimeType, String.valueOf(ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME)};
ops.add(
android.content.ContentProviderOperation.newInsert(
android.provider.ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
.withValue(ContactsContract.Data.MIMETYPE, mimeType)
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.CITY, "California")
.build()
);
contentResolver.applyBatch(ContactsContract.AUTHORITY, ops);
In the above case, I'm getting the rawContactId from ContactsContract.RawContacts.CONTENT_URI with ContactsContract.Data.CONTACT_ID equal to normal contactId. I was getting different rawContactId for different accounts - Google, WhatsApp, Skype, etc. I tried updating with all the rawContactIds, but still it was not getting updated. Can anybody please help me how to fix it?
You didn't specify what exactly is not working when you apply to above code, but I can see a few issues in it.
First you need to have a clear understanding of how contact data is stored in the database:
Contacts - each entry represents one contact, and groups together one or more RawContacts
RawContacts - each entry represents data about a contact that was synced in by some SyncAdapter (e.g. Whatsapp, Google, Facebook, Viber), this groups multiple Data entries
Data - The actual data about a contact, emails, phones, etc. each line is a single piece of data that belongs to a single RawContact
ISSUE 1
So if you're trying to update an existing postal-address, you should be careful not to use contactId as your key, because a single contact (referenced by a contactId) may have multiple postal-addresses in multiple raw-contacts each with multiple postal-address data rows.
your newUpdate call might then update the city in ALL addresses.
So if you have a contact "David" that has addresses:
123 lane, New York, United States
456 drive, Missouri, United States
789 alley, Paris, France
and your user is now trying to update "Paris" to "Lyon", your code might update ALL 3 addresses to Lyon.
Your key then must be the current Data._ID of the specific Data row you're trying to update.
ISSUE 2
If you're trying to insert a new data row to an existing raw-contact, for example a completely new postal-address, you'll need to specify the specific RawContact ID you're trying to insert into, and not use withValueBackReference - that's only useful when you're now creating a whole new RawContact, and don't know what will be the RawContact ID it'll get yet, so you're doing a back-reference to the ID your new RawContact will get from a previous call to newInsert of a RawContact row.
Also, in this case just the CITY value is not enough, as you'll get a whole postal-address comprised of CITY only, like this:
123 lane, New York, United States
456 drive, Missouri, United States
789 alley, Paris, France
Lyon
What you want to do here is collect all the postal-address values, and add them all into a single new Data row, like so:
ops.add(
ContentProviderOperation.newInsert(Data.CONTENT_URI)
.withValue(Data.RAW_CONTACT_ID, rawContactId)
.withValue(Data.MIMETYPE, mimeType)
.withValue(StructuredPostal.STREET, "123 Lane")
.withValue(StructuredPostal.CITY, "Los Angles")
.withValue(StructuredPostal.REGION, "California")
.withValue(StructuredPostal.COUNTRY, "United States")
.build()
);
ISSUE 3
If you're trying to insert just the CITY value to an existing postal-address row, you'll need to do an update, not an insert, into the specific Data ID, something like this:
String selection = Data._ID + " = ?";
ops.add(
ContentProviderOperation.newUpdate(Data.CONTENT_URI)
.withSelection(selection, new String[]{ dataId })
.withValue(StructuredPostal.CITY, "Los Angeles")
.build()
);
Related
When updating a ContactsContract.Data StructuredName row for a RawContact, my code sets the DISPLAY_NAME column with a value provided by the user. It also updates the first, middle, last with values provided by the user.
When fetching the contact DISPLAY_NAME back from ContactsContract.Contacts, the display name provided to the RawContact is ignored and, instead, Android has fabricated one based on the StructuredName name parts.
Is there a way to tell ContactsContract to use the display name provided?
For example, consider that the following are written to a StructuredName row:
DISPLAY_NAME: F L
GIVEN_NAME: F
MIDDLE_NAME: X
LAST_NAME: L
In this case, I would expect the aggregate contact display name to be "F L". However, it will be "F X L".
Here is the code to write a StructureName row, where the values being set to each column are member variables:
protected void prepareUpdate (ArrayList<ContentProviderOperation> ops)
{
String where = ContactsContract.Data._ID + " = " + dataId;
ContentProviderOperation.Builder builder;
builder = ContentProviderOperation.newUpdate (ContactsContract.Data.CONTENT_URI);
builder.withSelection (where, null);
builder.withValue (ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
builder.withValue (ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName);
builder.withValue (ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, givenName);
builder.withValue (ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, familyName);
builder.withValue (ContactsContract.CommonDataKinds.StructuredName.PREFIX, prefix);
builder.withValue (ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, middleName);
builder.withValue (ContactsContract.CommonDataKinds.StructuredName.SUFFIX, suffix);
builder.withValue (ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME, phoneticFamilyName);
builder.withValue (ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME, phoneticMiddleName);
builder.withValue (ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME, phoneticGivenName);
ops.add (builder.build());
}
Here's the code that executes the "ops":
ContentResolver resolver = context.getContentResolver();
ContentProviderResult[] results = resolver.applyBatch(ContactsContract.AUTHORITY, ops);
And, here's the code to fetch the aggregate contact's display name:
public static ContactData fetchContact (Cursor cursor)
{
String name = cursor.getString (cursor.getColumnIndex (ContactsContract.Contacts.DISPLAY_NAME_PRIMARY));
... do something with the feteched data ...
}
If Android is ignoring the display name provided by the user, what is the point of that column?
If you look at the docs for DISPLAY_NAME it says:
The name that should be used to display the contact. Unstructured
component of the name should be consistent with its structured
representation.
This means that whenever you update DISPLAY_NAME, it updates the name parts (GIVEN_NAME, etc.), and whenever you update one of the parts, it updates the DISPLAY_NAME, so at all times the name parts and the DISPLAY_NAME are consistent.
I'm guessing they do this to avoid issues like a user fixes a wrong name set to a phone number from "John Doe" to "Bob Dylan", but the name parts aren't being updated so they remain with "John" and "Doe", this is a obviously an undesired situation.
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.
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 using Filter with ListView which is populated trough Contact data which contains Names and Number.
Now i got two problems when i type a text into EditText which in turns fires adapter.getFilter().filter(s.toString())
1) When i type 'aa' latter (in my code )
i can see name starting from 'aa' for example aakruti , but at the
same time i am able to view email addresses too , which i don't wanted
to make it visible when a filter is fired.
2) When i type 'aa' latter (in phone's inbuilt contact list)
i can see name starting from 'aa' for example aakruti
but i am missing one name i.e. S A T Y A ( which is shown by contact
search when i type 'aa' latter into it )
here is my filter query , inside runQueryOnBackgroundThread
StringBuilder buffer = null;
String[] args = null;
if (constraint != null) {
buffer = new StringBuilder();
buffer.append("UPPER(");
buffer.append(ContactsContract.Contacts.DISPLAY_NAME);
buffer.append(") GLOB ?");
args = new String[] { constraint.toString().toUpperCase() + "*" };
}
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
System.out.println(buffer);
return mContent.query(
ContactsContract.Contacts.CONTENT_URI,
projection,
buffer == null ? null : buffer.toString(),
args,sortOrder
);
projection data
public static String[] projection = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME
};
EDIT
So far i tried to access
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME
ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME
ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME
But GIVEN_NAME displays few email address too and even it shows contact name which has email address
for example ,
xyz#gmail.com
Raul Jakson (which only has email address no contact number)
Raul Jakson ( i see this name twice as it has two different email address , but i wanna see it as name )
so can anyone tell me how can i limit the email addresses and show only NAMES which has only contact phone numbers ?
Firstly, the emails show up because that contact has no name. In these cases, Android will use the email as the display name. To avoid showing these, use GIVEN_NAME and FAMILY_NAME. See the api docs for StructuredName.
Secondly, you don't find SATIYA as your query is looking for a DISPLAY_NAME that begins with AA. SATIYA is not a word, it's a sequence of initials, e.g. S A T I Y A. If you want to find these, then you'll have to craft your query to cater for this. You could search for *A*A*, but you'll probably get many others hits too. I suspect Android is doing some variant of an initial+surname search where BO would find Barack Obama.
I'm trying to delete all contacts by a determinated group.
I've created the group with GROUP_ROW_ID = 987
So I should delete contacts with GROUP_ROW_ID = 987!
I don’t know how to delete them.
Can anyone help me?
If you haven't solved it yet this is how I do it. By no means do I suggest this is an effective solution. This is just the straight forward solution.
First find all contact-ids having a specific group id. Then creating a ContentProviderOperation for each contact to be deleted, and last apply the list of delete operations.
private void deletaAllInGroup(Context context, long groupId)
throws RemoteException, OperationApplicationException{
String where = String.format("%s = ?", GroupMembership.GROUP_ROW_ID);
String[] whereParmas = new String[] {Long.toString(groupId)};
String[] colSelection = new String[] {Data.CONTACT_ID};
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI,
colSelection,
where,
whereParmas,
null);
ArrayList<ContentProviderOperation> operations =
new ArrayList<ContentProviderOperation>();
// iterate over all contacts having groupId
// and add them to the list to be deleted
while(cursor.moveToNext()){
String where = String.format("%s = ?", RawContacts.CONTACT_ID);
String[] whereParams = new String[]{Long.toString(cursor.getLong(0))};
operations.add(ContentProviderOperation.newDelete(RawContacts.CONTENT_URI)
.withSelection(where, whereParams)
.build());
}
context.getContentResolver().applyBatch(
ContactsContract.AUTHORITY, operations );
}
Deleting a record
To delete a single record, call {ContentResolver.delete() with the URI of a specific row.
To delete multiple rows, call ContentResolver.delete() with the URI of the type of record to delete (for example, android.provider.Contacts.People.CONTENT_URI) and an SQL WHERE clause defining which rows to delete. (Caution: Be sure to include a valid WHERE clause if you're deleting a general type, or you risk deleting more records than you intended!).
Be careful with deleting Contacts! Deleting an aggregate contact deletes all constituent raw contacts. The corresponding sync adapters will notice the deletions of their respective raw contacts and remove them from their back end storage.
Specify READ_CONTACTS and WRITE_CONTACTS permissions in your AndroidManifest.xml.
Iterate through each contact and delete each record: Content Providers
Contacts
private final String MY_QUERY = "SELECT * FROM table_a a INNER JOIN table_b b ON a.id=b.other_id WHERE b.property_id=?";
db.rawQuery(MY_QUERY, new String[]{String.valueOf(propertyId)});
rawQuery
another example where I join two tables in one query
private static final String JOIN_QUERY=
"SELECT * FROM Employee JOIN User ON userId = employeeUserId";