ContactsContract: update RawContact ignores display_name - android

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.

Related

How to add city field to a contact in Android using ContentProviderOperation

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()
);

Loader for "Profile" contact's details

I am basing my code on the Login sample supplied with Android Studio. That sample contains code to populate an AutoCompleteTextView with email addresses related to the device's ContactsContract.Profile contact. i.e. the phone's owner, me.
I need to keep using the LoaderCallbacks interface methods - onCreateLoader() and onLoaderFinished().
I want to fetch extra details on the contact like:
phone number
given name
family name
To achieve this, I have tried adding extra fields to the ProfileQuery interface defined in the sample (that works correctly to fetch email address):
private interface ProfileQuery {
String[] PROJECTION = {
// these fields as per Android Studio sample
ContactsContract.CommonDataKinds.Email.ADDRESS,
ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
// these fields added by me
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME,
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME
};
}
I have modified the onCreateLoader() method to remove the sample's WHERE clause in the hope to get the extra data:
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return new CursorLoader(this,
// Retrieve data rows for the device user's 'profile' contact.
Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
// select all fields
null, null,
// Show primary email addresses first. Note that there won't be
// a primary email address if the user hasn't specified one.
ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
}
For what it is worth, at the moment my onLoadFinished() just logs the received data out:
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
Log.d("xxx", cursor.getString(0) + cursor.getString(1) + cursor.getString(2) + cursor.getString(3) + cursor.getString(4));
cursor.moveToNext();
}
}
I would like each cursor row to give me a complete set of data relating to the Profile contact. Instead, I am getting seemingly random fields from that contact.
My CursorLoader construction is clearly wrong, but I do not know how to fix that.
How can I get the following details from my Profile contact:
email address
phone number
given name
family name?
The code above is based on a misunderstanding of how the contacts data is stored.
The Uri being used is pointing to the ContactsContract.Data table. The Developer docs explain the structure of this table. Each row in the table is an item of information, not a contact. e.g. an email address, a phone number etc.
The projection defined is specific to email addresses (since the sample deals only with emails). A more general projection that can handle different data types could be something like:
private interface ProfileQuery {
String[] PROJECTION = {
ContactsContract.Data.MIMETYPE,
ContactsContract.Data.DATA1,
ContactsContract.Data.DATA2,
ContactsContract.Data.DATA3,
};
}
As the docs explain:
DATA1 is an indexed column and should be used for the data element
that is expected to be most frequently used in query selections. For
example, in the case of a row representing email addresses DATA1
should probably be used for the email address itself, while DATA2 etc
can be used for auxiliary information like type of email address.
.
For example,
Phone.NUMBER is the same as Data.DATA1.
The query then returns a cursor row for each piece of information in the Profile contact. In my case, the 3 email addresses I stored, a phone number and my name.
For each row the Data.MIMETYPE column defines how the other data columns should be read. The mimetypes can be queried based on constants declared in ContactsContract.CommonDataKinds class, such as:
Email.CONTENT_ITEM_TYPE
Phone.CONTENT_ITEM_TYPE
StructuredName.CONTENT_ITEM_TYPE
Organization.CONTENT_ITEM_TYPE

How to aggregate the rawContacts using aggregationException content correclty

I'm backing up and restoring contacts, I'm able to do it, but while aggregation of rawcontacts for linked contacts(joined contacts) using ContactsContract.AggregationExceptions
If i join 3 contacts, say Contact A (master) and Contact B (linked) and C (linked)
I do the backup ,and restore,for the newly created id's with correct mapping, I'm updating the ContactsContract.AggregationExceptions.CONTENT_URI with following code:
private boolean aggregateContactLinks() {
ContentResolver cr = context.getContentResolver();
int listCount = linkList.size();
ContentValues values = new ContentValues(3);
Log.i(tag, "aggregating contacts");
for (int i=0; i<listCount; i++) {
values.put(AggregationExceptions.RAW_CONTACT_ID1,
linkList.get(i).newId1);
values.put(AggregationExceptions.RAW_CONTACT_ID2,
linkList.get(i).newId2);
values.put(AggregationExceptions.TYPE,
AggregationExceptions.TYPE_KEEP_TOGETHER);
Log.i(tag," new master id(id1) :"+linkList.get(i).newId+"\nlinkid2 :"+newId2);
Log.i(tag,
"result :"
+ cr.update(AggregationExceptions.CONTENT_URI,
values, null, null));
Log.i(tag, "\nupdated one contact");
}
values.clear();
return true;
}
output , on phone i can see the linked content but in display name of contact is c instead of a
before backup
after backup, deleting the contacts and after restore
can anyone know where exactly im going wrong,thanks in advance
If you want to tell Android contact provider that it should use the specific raw contact name for aggregated contact (not display names of linked contacts B and C but the display name of master contact A), you may use RawContactsColumn.NAME_VERIFIED. Simply set it to "1" for your master raw contact after updating aggregation exceptions.
I found this trick in the standard Contacts application's
code:
// Mark the original contact as "name verified" to make sure that the contact
// display name does not change as a result of the join
if (verifiedNameRawContactId != -1) {
Builder builder = ContentProviderOperation.newUpdate(
ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId));
builder.withValue(RawContacts.NAME_VERIFIED, 1);
operations.add(builder.build());
}
RawContactsColumn interface is protected, so you should use "name_verified" as column name. Because of the fact that RawContactsColumn isn't open, maybe you should also check if "name_verified" column exists before updating to avoid crashes.

getting notes from a single contact - android

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.

Getting Contact Name Using This Implementation

I'm trying to use the implementation of the code found in this question post: How to read contacts on Android 2.0 but I can't figure out how to get it also run through the given, family, or display name columns. How can I get this implementation (the large one in the linked question) to give me the given and display names of the contacts as it loops through each row? I want to use this implementation specifically because it loops through the specified columns in each row and returns the information in the order it is in the row.
Here is the implementation from the other question that I'm referring to:\
Cursor cursor = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,null, null, null, null);
while (cursor.moveToNext()) {
String contactId = cursor.getString(cursor.getColumnIndex(
ContactsContract.Contacts._ID));
String hasPhone = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER));
if (Boolean.parseBoolean(hasPhone)) {
// You know it has a number so now query it like this
Cursor phones = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = "+ contactId, null, null);
while (phones.moveToNext()) {
String phoneNumber = phones.getString(phones.getColumnIndex( ContactsContract.CommonDataKinds.Phone.NUMBER));
}
phones.close();
}
Cursor emails = getContentResolver().query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null, ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = " + contactId, null, null);
while (emails.moveToNext()) {
// This would allow you get several email addresses
String emailAddress = emails.getString(
emails.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA));
}
emails.close();
}
cursor.close();
First of all, the answer in the linked post is a bit obsolete, because there now is documentation for Contacts Provider at developer.android.com.
Second, the problem you're having is that you're querying the "data" table with a contact ID for the contacts table, and that won't work.
The Contacts Provider is a three-tiered arrangement of tables. The top level is the Contacts table, whose constants are defined in ContactsContract.Contacts. One of its columns is
ContactsContract.Contacts._ID, which identifies a contact row. HOWEVER, a row in this table is an aggregation of individual contacts from various sources.
The individual contacts are stored in ContactsContract.RawContacts. For each ContactsContract.Contacts._ID, there can be more than one row in ContactsContract.RawContacts.
For each row in ContactsContract.RawContacts, there are one or more rows in ContactsContract.Data. Each row has a MIME type that tells you what type of data it is. For example, a row in ContactsContract.RawContacts can have three rows in ContactsContract.Data that have the MIME type for phone numbers. Each of the three "data" rows is a different type of phone number (home, mobile, work) for the contact in ContactsContract.RawContacts.
You can see why looking for ContactsContract.Contacts._ID in ContactsContract.Data won't work; that's the wrong ID to look for.
Rather than re-write the documentation here, I suggest you take a look at it. It has some nice illustrations that help explain what I'm getting at:
Contacts Provider

Categories

Resources